From de563a94ac60e6462a0ad36ea8d977d00b2ff333 Mon Sep 17 00:00:00 2001 From: Christian Kollmann Date: Wed, 23 Oct 2024 16:49:52 +0200 Subject: [PATCH 1/4] Demonstrate issue with deserializing issuer signed items --- .../lib/iso/IssuerSignedItemSerializer.kt | 47 +++++++++++++------ .../cbor/IssuerSignedItemSerializationTest.kt | 46 ++++++++++++------ 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt index b1abe717..221042a3 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt @@ -82,15 +82,16 @@ open class IssuerSignedItemSerializer(private val namespace: String) : KSerializ is ByteArray -> ByteArraySerializer() is Any -> CborCredentialSerializer.lookupSerializer(namespace, elementIdentifier) ?: error("serializer not found for $elementIdentifier, with value $elementValue") + else -> error("serializer not found for $elementIdentifier, with value $elementValue") } override fun deserialize(decoder: Decoder): IssuerSignedItem { var digestId = 0U - lateinit var random: ByteArray - lateinit var elementIdentifier: String - lateinit var elementValue: Any + var random: ByteArray? = null + var elementIdentifier: String? = null + var elementValue: Any? = null decoder.decodeStructure(descriptor) { while (true) { val name = decodeStringElement(descriptor, 0) @@ -100,32 +101,48 @@ open class IssuerSignedItemSerializer(private val namespace: String) : KSerializ PROP_DIGEST_ID -> digestId = decodeLongElement(descriptor, index).toUInt() PROP_RANDOM -> random = decodeSerializableElement(descriptor, index, ByteArraySerializer()) PROP_ELEMENT_ID -> elementIdentifier = decodeStringElement(descriptor, index) + // TODO How can we decode the elementValue, if the elementIdentifier is not yet known? + // this may be the case when the "elementValue" comes before "elementIdentifier" in the serialized byte array PROP_ELEMENT_VALUE -> elementValue = decodeAnything(index, elementIdentifier) } - if (index == 3) break + if (random != null && elementIdentifier != null && elementValue != null) break } } return IssuerSignedItem( digestId = digestId, - random = random, - elementIdentifier = elementIdentifier, - elementValue = elementValue + random = random!!, + elementIdentifier = elementIdentifier!!, + elementValue = reDecodeValue(elementIdentifier, elementValue), ) } - private fun CompositeDecoder.decodeAnything(index: Int, elementIdentifier: String): Any { + private fun reDecodeValue(elementIdentifier: String?, elementValue: Any?): Any { + // TODO This is a real hacky solution, and obviously doesn't cover all cases + val value = CborCredentialSerializer.lookupSerializer(namespace, elementIdentifier!!)?.let { + if (it.descriptor == LocalDate.serializer().descriptor) { + LocalDate.parse(elementValue!!.toString()) + } else { + elementValue!! + } + } ?: elementValue!! + return value + } + + private fun CompositeDecoder.decodeAnything(index: Int, elementIdentifier: String?): Any { if (namespace.isBlank()) Napier.w { "This decoder is not namespace-aware! Unspeakable things may happen…" } // Tags are not read out here but skipped because `decodeElementIndex` is never called, so we cannot // discriminate technically, this should be a good thing though, because otherwise we'd consume more from the // input - runCatching { - CborCredentialSerializer.decode(descriptor, index, this, elementIdentifier, namespace) - ?.let { return it } - ?: Napier.w { - "Could not find a registered decoder for namespace $namespace and elementIdentifier" + - " $elementIdentifier. Falling back to defaults" - } + if (elementIdentifier != null) { + runCatching { + CborCredentialSerializer.decode(descriptor, index, this, elementIdentifier, namespace) + ?.let { return it } + ?: Napier.w { + "Could not find a registered decoder for namespace $namespace and elementIdentifier" + + " $elementIdentifier. Falling back to defaults" + } + } } // These are the ones that map to different CBOR data types, the rest don't, so if it is not registered, we'll diff --git a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt index 2aa6883e..6fd26f4c 100644 --- a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt +++ b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt @@ -6,10 +6,13 @@ import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper import at.asitplus.wallet.lib.iso.* import com.benasher44.uuid.uuid4 import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldNotContain import io.matthewnelson.encoding.base16.Base16 +import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArray import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString +import kotlinx.datetime.LocalDate import kotlinx.serialization.builtins.ByteArraySerializer import kotlin.random.Random import kotlin.random.nextUInt @@ -17,38 +20,30 @@ import kotlin.random.nextUInt class IssuerSignedItemSerializationTest : FreeSpec({ "serialization with String" { + val namespace = uuid4().toString() val item = IssuerSignedItem( digestId = Random.nextUInt(), random = Random.nextBytes(16), elementIdentifier = uuid4().toString(), elementValue = uuid4().toString(), ) - - val serialized = item.serialize("foobar") + val serialized = item.serialize(namespace) serialized.encodeToString(Base16(true)).shouldNotContain("D903EC") - val parsed = IssuerSignedItem.deserialize(serialized, "").getOrThrow() + parsed shouldBe item } "document serialization with ByteArray" { - val elementId = uuid4().toString() val namespace = uuid4().toString() - - - CborCredentialSerializer.register( - mapOf(elementId to ByteArraySerializer()), - namespace - - ) + CborCredentialSerializer.register(mapOf(elementId to ByteArraySerializer()), namespace) val item = IssuerSignedItem( digestId = Random.nextUInt(), random = Random.nextBytes(16), elementIdentifier = elementId, elementValue = Random.nextBytes(32), ) - val protectedHeader = ByteStringWrapper(CoseHeader(), CoseHeader().serialize()) val issuerAuth = CoseSigned(protectedHeader, null, null, byteArrayOf()) val doc = Document( @@ -62,12 +57,35 @@ class IssuerSignedItemSerializationTest : FreeSpec({ DeviceAuth() ) ) - val serialized = doc.serialize() - serialized.encodeToString(Base16(true)).shouldNotContain("D903EC") + val parsed = Document.deserialize(serialized).getOrThrow() + parsed shouldBe doc } + "deserialize IssuerSigned from EUDI Ref Impl" { + CborCredentialSerializer.register(mapOf("birth_date" to LocalDate.serializer()), "eu.europa.ec.eudi.pid.1") + val input = """ + A26A697373756572417574688443A10126A118215902E9308202E53082026AA003020102021419040C2598027AD6AC99063EE39AB8C36FA6DAE4300A06082A8648CE3D040302305C311E301C06035504030C1550494420497373756572204341202D204555203031312D302B060355040A0C24455544492057616C6C6574205265666572656E636520496D706C656D656E746174696F6E310B3009060355040613024555301E170D3233303930323137333932385A170D3234313132353137333932375A30543116301406035504030C0D504944204453202D2030303031312D302B060355040A0C24455544492057616C6C6574205265666572656E636520496D706C656D656E746174696F6E310B30090603550406130245553059301306072A8648CE3D020106082A8648CE3D0301070342000464DF85FAA25CB3830A6F83ED10FDAD6A2068540205349D71DBB0B84B2BC32E6B178E5F3F698808922EAD03B60A359AE914042CCA0513E5D51F34AB0209605F99A38201103082010C301F0603551D23041830168014418B6176E18C81DC3FB25F563FFE6CB20681E01130160603551D250101FF040C300A06082B8102020000010230430603551D1F043C303A3038A036A034863268747470733A2F2F70726570726F642E706B692E65756469772E6465762F63726C2F7069645F43415F45555F30312E63726C301D0603551D0E041604146035E1769A8317A5D92E2FFBEF492992ED0F4418300E0603551D0F0101FF040403020780305D0603551D1204563054865268747470733A2F2F6769746875622E636F6D2F65752D6469676974616C2D6964656E746974792D77616C6C65742F6172636869746563747572652D616E642D7265666572656E63652D6672616D65776F726B300A06082A8648CE3D0403020369003066023100CF75AD412DD8A9365701BB85EB844617952682D53E93181B4C66C621D99D58BED432C074040219AE599E924B7A5224EE023100903B1BDCF9AF87AD3CF63EB68119D4C0AD8FD2C9F8F9314D0A504C4E3DB5018540656132389397AFD615A43826B70A2A590259D818590254A667646F63547970657765752E6575726F70612E65632E657564692E7069642E316776657273696F6E63312E306C76616C6964697479496E666FA3667369676E6564C074323032342D31302D32335431343A32343A32355A6976616C696446726F6DC074323032342D31302D32335431343A32343A32355A6A76616C6964556E74696CC074323032352D30312D32315430303A30303A30305A6C76616C756544696765737473A17765752E6575726F70612E65632E657564692E7069642E31A8005820B7FAC61165B43D7088E504E05332E544FD05A7944865ED1D84CAFB3F8CB98CA2015820B314A01D1963184513EC7D28CE76FE4D112308D7B6059B50352AC4337E8E33D00258207580DBF64DDCB633C52C898103D546533C5F8F3FD4DB93874C219BCF51CE1912035820ABD56C84EE00E01C88C1EE9C71BEFCD1B3E22890EC0A28193BD0FC8D60662B5804582087065EA6E65356C14DCD2523F13BA27D7932C075DD345529E5621C2E80C6919405582092D5D5435000AF5BACFD16259AC88CA26F949D945B43F55986F4A921549166330658207741AD9D5E0805A378B7EE837749C61539DA9A38BF5F7222C8A380C2AAF2C8CC075820BAE6B7B63C4F107216465DF1EFE817A10F3AE9D0AF4FB827DEADD188351CF5FE6D6465766963654B6579496E666FA1696465766963654B6579A4010220012158205B6FD9E2B13EB0E5687C2668281BA2E00B74A4AD878FB89125F3489C5F6E4BED225820B5C87386D05EBFEE6FE1DA7AF3530E8E659A70154F91C594D0F953D176430F9D6F646967657374416C676F726974686D675348412D32353658404DDD44CCC2761226F02391F45A23189A7C53A5B9ADA080A23DED77A93988DC5AF59A67EC936F594AF86AE188CDC667573E377DB23657618B9FB6A24F01BF99A06A6E616D65537061636573A17765752E6575726F70612E65632E657564692E7069642E3188D818586CA46672616E646F6D5820A0A2A777A503F660A157C7B09008F606D42D353CC5A86DCB51051F02EDA1D4AA686469676573744944006C656C656D656E7456616C7565D903EC6A313936352D30312D303171656C656D656E744964656E7469666965726A62697274685F64617465D818586FA46672616E646F6D5820F41ABFAFE91CFBA92E78C249C4C92F63C60D2347B3B74D21C2A3FEFEA2DF9569686469676573744944016C656C656D656E7456616C7565D903EC6A323032342D31302D323371656C656D656E744964656E7469666965726D69737375616E63655F64617465D8185865A46672616E646F6D5820105A81F2B0F3835025EC5C8AFC1C5C55D08AC7F91BB175E858F03FE1E8BA5395686469676573744944026C656C656D656E7456616C7565666A617669657271656C656D656E744964656E7469666965726A676976656E5F6E616D65D818586DA46672616E646F6D5820C58F218133297B5413268A51F4245D06F497E189BBCD63FF5B8F04D5B75321DE686469676573744944036C656C656D656E7456616C7565D903EC6A323032352D30312D323171656C656D656E744964656E7469666965726B6578706972795F64617465D8185860A46672616E646F6D58202B27AC89B308044EA57A26CE02388C005C34E415F0E8E1F259D51999915E958B686469676573744944046C656C656D656E7456616C7565F571656C656D656E744964656E7469666965726B6167655F6F7665725F3138D8185866A46672616E646F6D582051BFD38D542495BE08327AC2064A05846836B35FBAC4F0C467DE0338072DA30B686469676573744944056C656C656D656E7456616C75656647617263696171656C656D656E744964656E7469666965726B66616D696C795F6E616D65D8185866A46672616E646F6D58203DA715C789DE91788ACD9A6A919CE82DB180C1F7D3AEB41AE81A0F0027349C29686469676573744944066C656C656D656E7456616C756562455571656C656D656E744964656E7469666965726F69737375696E675F636F756E747279D8185875A46672616E646F6D58207A17BCCBCA82615A3CB1321343CB963287482156C2C8E8563C35B8E0A3825CF0686469676573744944076C656C656D656E7456616C75656F54657374205049442069737375657271656C656D656E744964656E7469666965727169737375696E675F617574686F72697479 + """.trimIndent().replace("\n", "") + + val parsed = IssuerSigned.deserialize(input.decodeToByteArray(Base16())) + .getOrThrow() + + val namespaces = parsed.namespaces + .shouldNotBeNull() + val issuerSignedList = namespaces.values.first() + val issuerSignedItems = issuerSignedList.entries.map { it.value } + issuerSignedItems.first { it.elementIdentifier == "given_name" } + .elementValue shouldBe "javier" + issuerSignedItems.first { it.elementIdentifier == "family_name" } + .elementValue shouldBe "Garcia" + issuerSignedItems.first { it.elementIdentifier == "issuing_authority" } + .elementValue shouldBe "Test PID issuer" + issuerSignedItems.first { it.elementIdentifier == "birth_date" } + .elementValue shouldBe LocalDate.parse("1965-01-01") + } + }) From 3e2515a310e3a95e2d4a89505c37f2f80b8b1e36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Pr=C3=BCnster?= Date: Sat, 26 Oct 2024 21:37:07 +0200 Subject: [PATCH 2/4] Integrate obor to deserialize issuer signed items --- .../src/main/kotlin/VcLibConventions.kt | 2 ++ .../src/main/kotlin/VcLibVersions.kt | 1 + .../src/main/resources/vcLibVersions.properties | 1 + gradle/libs.versions.toml | 2 ++ vck/build.gradle.kts | 1 - .../at/asitplus/wallet/lib/iso/IssuerSigned.kt | 5 +++++ .../asitplus/wallet/lib/iso/IssuerSignedItem.kt | 6 +++--- .../wallet/lib/iso/IssuerSignedItemSerializer.kt | 8 ++++---- .../wallet/lib/iso/IssuerSignedListSerializer.kt | 15 +++++++++++++-- .../lib/cbor/IssuerSignedItemSerializationTest.kt | 5 +++-- 10 files changed, 34 insertions(+), 12 deletions(-) create mode 100644 gradle/libs.versions.toml diff --git a/conventions-vclib/src/main/kotlin/VcLibConventions.kt b/conventions-vclib/src/main/kotlin/VcLibConventions.kt index e24a0348..aec5fc02 100644 --- a/conventions-vclib/src/main/kotlin/VcLibConventions.kt +++ b/conventions-vclib/src/main/kotlin/VcLibConventions.kt @@ -50,6 +50,8 @@ inline fun KotlinDependencyHandler.commonImplementationDependencies() { implementation(project.ktor("http")) implementation(project.napier()) implementation(project.ktor("utils")) + //project.AspVersions.versions["obor"] = VcLibVersions.obor + implementation("net.orandja.obor:obor:${VcLibVersions.obor}") } /** diff --git a/conventions-vclib/src/main/kotlin/VcLibVersions.kt b/conventions-vclib/src/main/kotlin/VcLibVersions.kt index c48bf0e2..85ba4a93 100644 --- a/conventions-vclib/src/main/kotlin/VcLibVersions.kt +++ b/conventions-vclib/src/main/kotlin/VcLibVersions.kt @@ -12,6 +12,7 @@ object VcLibVersions { val signum get() = versionOf("signum") val supreme get() = versionOf("supreme") val jsonpath get() = versionOf("jsonpath") + val obor get() = versionOf("obor") val eupidcredential get() = versionOf("eupid") val mdl get() = versionOf("mdl") diff --git a/conventions-vclib/src/main/resources/vcLibVersions.properties b/conventions-vclib/src/main/resources/vcLibVersions.properties index 9b89087e..2b7cdd23 100644 --- a/conventions-vclib/src/main/resources/vcLibVersions.properties +++ b/conventions-vclib/src/main/resources/vcLibVersions.properties @@ -6,6 +6,7 @@ jvm.json=20230618 jvm.cbor=1.18 eupid=2.2.0 mdl=1.1.0 +obor=2.1.3 android.compileSDK=34 android.minSDK=33 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..fd8c825c --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,2 @@ +[versions] +kmmresult = "1.8.0!!" \ No newline at end of file diff --git a/vck/build.gradle.kts b/vck/build.gradle.kts index 36089667..8b969ce1 100644 --- a/vck/build.gradle.kts +++ b/vck/build.gradle.kts @@ -57,7 +57,6 @@ kotlin { dependencies { implementation(signum.jose) implementation(kotlin("reflect")) - implementation("io.arrow-kt:arrow-core-jvm:1.2.4") //to make arrow's nonFatalOrThrow work in tests implementation("org.json:json:${VcLibVersions.Jvm.json}") implementation("com.authlete:cbor:${VcLibVersions.Jvm.`authlete-cbor`}") } diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSigned.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSigned.kt index bd1596bb..c9f7025c 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSigned.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSigned.kt @@ -3,7 +3,11 @@ package at.asitplus.wallet.lib.iso import at.asitplus.KmmResult.Companion.wrap import at.asitplus.catching import at.asitplus.signum.indispensable.cosef.CoseSigned +import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper +import at.asitplus.wallet.lib.iso.IssuerSignedItem.Companion.PROP_ELEMENT_ID import kotlinx.serialization.* +import net.orandja.obor.codec.Cbor +import net.orandja.obor.data.* /** * Part of the ISO/IEC 18013-5:2021 standard: Data structure for mdoc request (8.3.2.1.2.1) @@ -42,6 +46,7 @@ data class IssuerSigned private constructor( vckCborSerializer.decodeFromByteArray(it) }.wrap() + // Note: Can't be a secondary constructor, because it would have the same JVM signature as the primary one. /** * Ensures the serialization of this structure in [Document.issuerSigned]: diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItem.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItem.kt index 423dd2d6..6f6cd189 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItem.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItem.kt @@ -21,7 +21,7 @@ data class IssuerSignedItem( val elementValue: Any, ) { - fun serialize(namespace: String) = vckCborSerializer.encodeToByteArray(IssuerSignedItemSerializer(namespace), this) + fun serialize(namespace: String) = vckCborSerializer.encodeToByteArray(IssuerSignedItemSerializer(namespace, elementIdentifier), this) override fun equals(other: Any?): Boolean { if (this === other) return true @@ -61,8 +61,8 @@ data class IssuerSignedItem( } companion object { - fun deserialize(it: ByteArray, namespace: String) = kotlin.runCatching { - vckCborSerializer.decodeFromByteArray(IssuerSignedItemSerializer(namespace), it) + fun deserialize(it: ByteArray, namespace: String, elementIdentifier: String) = kotlin.runCatching { + vckCborSerializer.decodeFromByteArray(IssuerSignedItemSerializer(namespace, elementIdentifier), it) }.wrap() internal const val PROP_DIGEST_ID = "digestID" diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt index 221042a3..3fcd0231 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt @@ -15,8 +15,9 @@ import kotlinx.serialization.cbor.ValueTags import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.* +import net.orandja.obor.codec.CborDecoderException -open class IssuerSignedItemSerializer(private val namespace: String) : KSerializer { +open class IssuerSignedItemSerializer(private val namespace: String, private val elementIdentifier: String) : KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("IssuerSignedItem") { element(PROP_DIGEST_ID, Long.serializer().descriptor) @@ -90,7 +91,6 @@ open class IssuerSignedItemSerializer(private val namespace: String) : KSerializ override fun deserialize(decoder: Decoder): IssuerSignedItem { var digestId = 0U var random: ByteArray? = null - var elementIdentifier: String? = null var elementValue: Any? = null decoder.decodeStructure(descriptor) { while (true) { @@ -100,12 +100,12 @@ open class IssuerSignedItemSerializer(private val namespace: String) : KSerializ when (name) { PROP_DIGEST_ID -> digestId = decodeLongElement(descriptor, index).toUInt() PROP_RANDOM -> random = decodeSerializableElement(descriptor, index, ByteArraySerializer()) - PROP_ELEMENT_ID -> elementIdentifier = decodeStringElement(descriptor, index) + PROP_ELEMENT_ID -> if(elementIdentifier != decodeStringElement(descriptor, index)) throw IllegalArgumentException("Element identifier mismatch") // TODO How can we decode the elementValue, if the elementIdentifier is not yet known? // this may be the case when the "elementValue" comes before "elementIdentifier" in the serialized byte array PROP_ELEMENT_VALUE -> elementValue = decodeAnything(index, elementIdentifier) } - if (random != null && elementIdentifier != null && elementValue != null) break + if (random != null && elementValue != null) break } } return IssuerSignedItem( diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedListSerializer.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedListSerializer.kt index 480d1f2c..9ad0fa51 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedListSerializer.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedListSerializer.kt @@ -1,15 +1,21 @@ package at.asitplus.wallet.lib.iso import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper +import at.asitplus.wallet.lib.iso.IssuerSignedItem.Companion.PROP_ELEMENT_ID import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.builtins.ByteArraySerializer import kotlinx.serialization.builtins.serializer import kotlinx.serialization.cbor.ValueTags +import kotlinx.serialization.decodeFromByteArray import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialKind import kotlinx.serialization.descriptors.StructureKind import kotlinx.serialization.encoding.* +import net.orandja.obor.codec.Cbor +import net.orandja.obor.data.CborMap +import net.orandja.obor.data.CborObject +import net.orandja.obor.data.CborText /** * Serializes [IssuerSignedList.entries] as an "inline list", @@ -73,8 +79,13 @@ open class IssuerSignedListSerializer(private val namespace: String) : KSerializ break } val readBytes = decoder.decodeSerializableValue(ByteArraySerializer()) - val issuerSignedItem = IssuerSignedItem.deserialize(readBytes, namespace).getOrThrow() - entries += ByteStringWrapper(issuerSignedItem, readBytes) + val item = Cbor.decodeFromByteArray(readBytes) as CborMap + + val elementID = + (item.first { (it.key as CborText).value == PROP_ELEMENT_ID }.value as CborText).value + entries += ByteStringWrapper( + IssuerSignedItem.deserialize(item.cbor, namespace, elementID).getOrThrow(), item.cbor + ) } } return IssuerSignedList(entries) diff --git a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt index 6fd26f4c..8e7a1085 100644 --- a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt +++ b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt @@ -21,15 +21,16 @@ class IssuerSignedItemSerializationTest : FreeSpec({ "serialization with String" { val namespace = uuid4().toString() + val elementIdentifier = uuid4().toString() val item = IssuerSignedItem( digestId = Random.nextUInt(), random = Random.nextBytes(16), - elementIdentifier = uuid4().toString(), + elementIdentifier = elementIdentifier, elementValue = uuid4().toString(), ) val serialized = item.serialize(namespace) serialized.encodeToString(Base16(true)).shouldNotContain("D903EC") - val parsed = IssuerSignedItem.deserialize(serialized, "").getOrThrow() + val parsed = IssuerSignedItem.deserialize(serialized, "", elementIdentifier).getOrThrow() parsed shouldBe item } From 4ffc4742e60b27d8e48973c12dd08a6758813041 Mon Sep 17 00:00:00 2001 From: Christian Kollmann Date: Tue, 29 Oct 2024 16:12:33 +0100 Subject: [PATCH 3/4] Refactor deserializing issuer signed items --- CHANGELOG.md | 1 + .../lib/iso/IssuerSignedItemSerializer.kt | 53 ++++++------------- .../cbor/IssuerSignedItemSerializationTest.kt | 45 +++++++++++++++- 3 files changed, 62 insertions(+), 37 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01c70e7f..738760e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Release 5.1.0: - Replace `relyingPartyUrl` with `clientIdScheme` on `OidcSiopVerifier`s constructor, to clarify use of `client_id` in requests - Rename objects in `OpenIdConstants.ProofType`, `OpenIdConstants.CliendIdScheme` and `OpenIdConstants.ResponseMode` - In all OpenID data classes, serialize strings only, and parse them to crypto data classes (from signum) in a separate property (this increases interop, as we can deserialize unsupported algorithms too) + - ISO: Fix deserializing issuer signed items when element identifiers are read after the element values Release 5.0.1: - Update JsonPath4K to 2.4.0 diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt index 3fcd0231..d515fe2d 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedItemSerializer.kt @@ -15,9 +15,9 @@ import kotlinx.serialization.cbor.ValueTags import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.buildClassSerialDescriptor import kotlinx.serialization.encoding.* -import net.orandja.obor.codec.CborDecoderException -open class IssuerSignedItemSerializer(private val namespace: String, private val elementIdentifier: String) : KSerializer { +open class IssuerSignedItemSerializer(private val namespace: String, private val elementIdentifier: String) : + KSerializer { override val descriptor: SerialDescriptor = buildClassSerialDescriptor("IssuerSignedItem") { element(PROP_DIGEST_ID, Long.serializer().descriptor) @@ -36,17 +36,12 @@ open class IssuerSignedItemSerializer(private val namespace: String, private val } private fun CompositeEncoder.encodeAnything(value: IssuerSignedItem, index: Int) { - val elementValueSerializer = - buildElementValueSerializer(namespace, value.elementValue, value.elementIdentifier) + val elementValueSerializer = buildElementValueSerializer(namespace, value.elementValue, value.elementIdentifier) val descriptor = buildClassSerialDescriptor("IssuerSignedItem") { element(PROP_DIGEST_ID, Long.serializer().descriptor) element(PROP_RANDOM, ByteArraySerializer().descriptor) element(PROP_ELEMENT_ID, String.serializer().descriptor) - element( - elementName = PROP_ELEMENT_VALUE, - descriptor = elementValueSerializer.descriptor, - annotations = value.elementValue.annotations() - ) + element(PROP_ELEMENT_VALUE, elementValueSerializer.descriptor, value.elementValue.annotations()) } when (val it = value.elementValue) { @@ -100,9 +95,9 @@ open class IssuerSignedItemSerializer(private val namespace: String, private val when (name) { PROP_DIGEST_ID -> digestId = decodeLongElement(descriptor, index).toUInt() PROP_RANDOM -> random = decodeSerializableElement(descriptor, index, ByteArraySerializer()) - PROP_ELEMENT_ID -> if(elementIdentifier != decodeStringElement(descriptor, index)) throw IllegalArgumentException("Element identifier mismatch") - // TODO How can we decode the elementValue, if the elementIdentifier is not yet known? - // this may be the case when the "elementValue" comes before "elementIdentifier" in the serialized byte array + PROP_ELEMENT_ID -> if (elementIdentifier != decodeStringElement(descriptor, index)) + throw IllegalArgumentException("Element identifier mismatch") + PROP_ELEMENT_VALUE -> elementValue = decodeAnything(index, elementIdentifier) } if (random != null && elementValue != null) break @@ -111,38 +106,24 @@ open class IssuerSignedItemSerializer(private val namespace: String, private val return IssuerSignedItem( digestId = digestId, random = random!!, - elementIdentifier = elementIdentifier!!, - elementValue = reDecodeValue(elementIdentifier, elementValue), + elementIdentifier = elementIdentifier, + elementValue = elementValue!!, ) } - private fun reDecodeValue(elementIdentifier: String?, elementValue: Any?): Any { - // TODO This is a real hacky solution, and obviously doesn't cover all cases - val value = CborCredentialSerializer.lookupSerializer(namespace, elementIdentifier!!)?.let { - if (it.descriptor == LocalDate.serializer().descriptor) { - LocalDate.parse(elementValue!!.toString()) - } else { - elementValue!! - } - } ?: elementValue!! - return value - } - private fun CompositeDecoder.decodeAnything(index: Int, elementIdentifier: String?): Any { - if (namespace.isBlank()) Napier.w { "This decoder is not namespace-aware! Unspeakable things may happen…" } + if (namespace.isBlank()) + Napier.w("This decoder is not namespace-aware! Unspeakable things may happen…") // Tags are not read out here but skipped because `decodeElementIndex` is never called, so we cannot // discriminate technically, this should be a good thing though, because otherwise we'd consume more from the // input - if (elementIdentifier != null) { - runCatching { - CborCredentialSerializer.decode(descriptor, index, this, elementIdentifier, namespace) - ?.let { return it } - ?: Napier.w { - "Could not find a registered decoder for namespace $namespace and elementIdentifier" + - " $elementIdentifier. Falling back to defaults" - } - } + elementIdentifier?.let { + CborCredentialSerializer.decode(descriptor, index, this, elementIdentifier, namespace) + ?.let { return it } + ?: Napier.v( + "Falling back to defaults for namespace $namespace and elementIdentifier $elementIdentifier" + ) } // These are the ones that map to different CBOR data types, the rest don't, so if it is not registered, we'll diff --git a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt index 8e7a1085..aeca3edd 100644 --- a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt +++ b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/IssuerSignedItemSerializationTest.kt @@ -69,7 +69,50 @@ class IssuerSignedItemSerializationTest : FreeSpec({ "deserialize IssuerSigned from EUDI Ref Impl" { CborCredentialSerializer.register(mapOf("birth_date" to LocalDate.serializer()), "eu.europa.ec.eudi.pid.1") val input = """ - A26A697373756572417574688443A10126A118215902E9308202E53082026AA003020102021419040C2598027AD6AC99063EE39AB8C36FA6DAE4300A06082A8648CE3D040302305C311E301C06035504030C1550494420497373756572204341202D204555203031312D302B060355040A0C24455544492057616C6C6574205265666572656E636520496D706C656D656E746174696F6E310B3009060355040613024555301E170D3233303930323137333932385A170D3234313132353137333932375A30543116301406035504030C0D504944204453202D2030303031312D302B060355040A0C24455544492057616C6C6574205265666572656E636520496D706C656D656E746174696F6E310B30090603550406130245553059301306072A8648CE3D020106082A8648CE3D0301070342000464DF85FAA25CB3830A6F83ED10FDAD6A2068540205349D71DBB0B84B2BC32E6B178E5F3F698808922EAD03B60A359AE914042CCA0513E5D51F34AB0209605F99A38201103082010C301F0603551D23041830168014418B6176E18C81DC3FB25F563FFE6CB20681E01130160603551D250101FF040C300A06082B8102020000010230430603551D1F043C303A3038A036A034863268747470733A2F2F70726570726F642E706B692E65756469772E6465762F63726C2F7069645F43415F45555F30312E63726C301D0603551D0E041604146035E1769A8317A5D92E2FFBEF492992ED0F4418300E0603551D0F0101FF040403020780305D0603551D1204563054865268747470733A2F2F6769746875622E636F6D2F65752D6469676974616C2D6964656E746974792D77616C6C65742F6172636869746563747572652D616E642D7265666572656E63652D6672616D65776F726B300A06082A8648CE3D0403020369003066023100CF75AD412DD8A9365701BB85EB844617952682D53E93181B4C66C621D99D58BED432C074040219AE599E924B7A5224EE023100903B1BDCF9AF87AD3CF63EB68119D4C0AD8FD2C9F8F9314D0A504C4E3DB5018540656132389397AFD615A43826B70A2A590259D818590254A667646F63547970657765752E6575726F70612E65632E657564692E7069642E316776657273696F6E63312E306C76616C6964697479496E666FA3667369676E6564C074323032342D31302D32335431343A32343A32355A6976616C696446726F6DC074323032342D31302D32335431343A32343A32355A6A76616C6964556E74696CC074323032352D30312D32315430303A30303A30305A6C76616C756544696765737473A17765752E6575726F70612E65632E657564692E7069642E31A8005820B7FAC61165B43D7088E504E05332E544FD05A7944865ED1D84CAFB3F8CB98CA2015820B314A01D1963184513EC7D28CE76FE4D112308D7B6059B50352AC4337E8E33D00258207580DBF64DDCB633C52C898103D546533C5F8F3FD4DB93874C219BCF51CE1912035820ABD56C84EE00E01C88C1EE9C71BEFCD1B3E22890EC0A28193BD0FC8D60662B5804582087065EA6E65356C14DCD2523F13BA27D7932C075DD345529E5621C2E80C6919405582092D5D5435000AF5BACFD16259AC88CA26F949D945B43F55986F4A921549166330658207741AD9D5E0805A378B7EE837749C61539DA9A38BF5F7222C8A380C2AAF2C8CC075820BAE6B7B63C4F107216465DF1EFE817A10F3AE9D0AF4FB827DEADD188351CF5FE6D6465766963654B6579496E666FA1696465766963654B6579A4010220012158205B6FD9E2B13EB0E5687C2668281BA2E00B74A4AD878FB89125F3489C5F6E4BED225820B5C87386D05EBFEE6FE1DA7AF3530E8E659A70154F91C594D0F953D176430F9D6F646967657374416C676F726974686D675348412D32353658404DDD44CCC2761226F02391F45A23189A7C53A5B9ADA080A23DED77A93988DC5AF59A67EC936F594AF86AE188CDC667573E377DB23657618B9FB6A24F01BF99A06A6E616D65537061636573A17765752E6575726F70612E65632E657564692E7069642E3188D818586CA46672616E646F6D5820A0A2A777A503F660A157C7B09008F606D42D353CC5A86DCB51051F02EDA1D4AA686469676573744944006C656C656D656E7456616C7565D903EC6A313936352D30312D303171656C656D656E744964656E7469666965726A62697274685F64617465D818586FA46672616E646F6D5820F41ABFAFE91CFBA92E78C249C4C92F63C60D2347B3B74D21C2A3FEFEA2DF9569686469676573744944016C656C656D656E7456616C7565D903EC6A323032342D31302D323371656C656D656E744964656E7469666965726D69737375616E63655F64617465D8185865A46672616E646F6D5820105A81F2B0F3835025EC5C8AFC1C5C55D08AC7F91BB175E858F03FE1E8BA5395686469676573744944026C656C656D656E7456616C7565666A617669657271656C656D656E744964656E7469666965726A676976656E5F6E616D65D818586DA46672616E646F6D5820C58F218133297B5413268A51F4245D06F497E189BBCD63FF5B8F04D5B75321DE686469676573744944036C656C656D656E7456616C7565D903EC6A323032352D30312D323171656C656D656E744964656E7469666965726B6578706972795F64617465D8185860A46672616E646F6D58202B27AC89B308044EA57A26CE02388C005C34E415F0E8E1F259D51999915E958B686469676573744944046C656C656D656E7456616C7565F571656C656D656E744964656E7469666965726B6167655F6F7665725F3138D8185866A46672616E646F6D582051BFD38D542495BE08327AC2064A05846836B35FBAC4F0C467DE0338072DA30B686469676573744944056C656C656D656E7456616C75656647617263696171656C656D656E744964656E7469666965726B66616D696C795F6E616D65D8185866A46672616E646F6D58203DA715C789DE91788ACD9A6A919CE82DB180C1F7D3AEB41AE81A0F0027349C29686469676573744944066C656C656D656E7456616C756562455571656C656D656E744964656E7469666965726F69737375696E675F636F756E747279D8185875A46672616E646F6D58207A17BCCBCA82615A3CB1321343CB963287482156C2C8E8563C35B8E0A3825CF0686469676573744944076C656C656D656E7456616C75656F54657374205049442069737375657271656C656D656E744964656E7469666965727169737375696E675F617574686F72697479 + A26A697373756572417574688443A10126A118215902E9308202E53082026AA003020102021419040C2598027AD6AC99063EE39AB8C3 + 6FA6DAE4300A06082A8648CE3D040302305C311E301C06035504030C1550494420497373756572204341202D204555203031312D302B + 060355040A0C24455544492057616C6C6574205265666572656E636520496D706C656D656E746174696F6E310B300906035504061302 + 4555301E170D3233303930323137333932385A170D3234313132353137333932375A30543116301406035504030C0D50494420445320 + 2D2030303031312D302B060355040A0C24455544492057616C6C6574205265666572656E636520496D706C656D656E746174696F6E31 + 0B30090603550406130245553059301306072A8648CE3D020106082A8648CE3D0301070342000464DF85FAA25CB3830A6F83ED10FDAD + 6A2068540205349D71DBB0B84B2BC32E6B178E5F3F698808922EAD03B60A359AE914042CCA0513E5D51F34AB0209605F99A382011030 + 82010C301F0603551D23041830168014418B6176E18C81DC3FB25F563FFE6CB20681E01130160603551D250101FF040C300A06082B81 + 02020000010230430603551D1F043C303A3038A036A034863268747470733A2F2F70726570726F642E706B692E65756469772E646576 + 2F63726C2F7069645F43415F45555F30312E63726C301D0603551D0E041604146035E1769A8317A5D92E2FFBEF492992ED0F4418300E + 0603551D0F0101FF040403020780305D0603551D1204563054865268747470733A2F2F6769746875622E636F6D2F65752D6469676974 + 616C2D6964656E746974792D77616C6C65742F6172636869746563747572652D616E642D7265666572656E63652D6672616D65776F72 + 6B300A06082A8648CE3D0403020369003066023100CF75AD412DD8A9365701BB85EB844617952682D53E93181B4C66C621D99D58BED4 + 32C074040219AE599E924B7A5224EE023100903B1BDCF9AF87AD3CF63EB68119D4C0AD8FD2C9F8F9314D0A504C4E3DB5018540656132 + 389397AFD615A43826B70A2A590259D818590254A667646F63547970657765752E6575726F70612E65632E657564692E7069642E3167 + 76657273696F6E63312E306C76616C6964697479496E666FA3667369676E6564C074323032342D31302D32335431343A32343A32355A + 6976616C696446726F6DC074323032342D31302D32335431343A32343A32355A6A76616C6964556E74696CC074323032352D30312D32 + 315430303A30303A30305A6C76616C756544696765737473A17765752E6575726F70612E65632E657564692E7069642E31A8005820B7 + FAC61165B43D7088E504E05332E544FD05A7944865ED1D84CAFB3F8CB98CA2015820B314A01D1963184513EC7D28CE76FE4D112308D7 + B6059B50352AC4337E8E33D00258207580DBF64DDCB633C52C898103D546533C5F8F3FD4DB93874C219BCF51CE1912035820ABD56C84 + EE00E01C88C1EE9C71BEFCD1B3E22890EC0A28193BD0FC8D60662B5804582087065EA6E65356C14DCD2523F13BA27D7932C075DD3455 + 29E5621C2E80C6919405582092D5D5435000AF5BACFD16259AC88CA26F949D945B43F55986F4A921549166330658207741AD9D5E0805 + A378B7EE837749C61539DA9A38BF5F7222C8A380C2AAF2C8CC075820BAE6B7B63C4F107216465DF1EFE817A10F3AE9D0AF4FB827DEAD + D188351CF5FE6D6465766963654B6579496E666FA1696465766963654B6579A4010220012158205B6FD9E2B13EB0E5687C2668281BA2 + E00B74A4AD878FB89125F3489C5F6E4BED225820B5C87386D05EBFEE6FE1DA7AF3530E8E659A70154F91C594D0F953D176430F9D6F64 + 6967657374416C676F726974686D675348412D32353658404DDD44CCC2761226F02391F45A23189A7C53A5B9ADA080A23DED77A93988 + DC5AF59A67EC936F594AF86AE188CDC667573E377DB23657618B9FB6A24F01BF99A06A6E616D65537061636573A17765752E6575726F + 70612E65632E657564692E7069642E3188D818586CA46672616E646F6D5820A0A2A777A503F660A157C7B09008F606D42D353CC5A86D + CB51051F02EDA1D4AA686469676573744944006C656C656D656E7456616C7565D903EC6A313936352D30312D303171656C656D656E74 + 4964656E7469666965726A62697274685F64617465D818586FA46672616E646F6D5820F41ABFAFE91CFBA92E78C249C4C92F63C60D23 + 47B3B74D21C2A3FEFEA2DF9569686469676573744944016C656C656D656E7456616C7565D903EC6A323032342D31302D323371656C65 + 6D656E744964656E7469666965726D69737375616E63655F64617465D8185865A46672616E646F6D5820105A81F2B0F3835025EC5C8A + FC1C5C55D08AC7F91BB175E858F03FE1E8BA5395686469676573744944026C656C656D656E7456616C7565666A617669657271656C65 + 6D656E744964656E7469666965726A676976656E5F6E616D65D818586DA46672616E646F6D5820C58F218133297B5413268A51F4245D + 06F497E189BBCD63FF5B8F04D5B75321DE686469676573744944036C656C656D656E7456616C7565D903EC6A323032352D30312D3231 + 71656C656D656E744964656E7469666965726B6578706972795F64617465D8185860A46672616E646F6D58202B27AC89B308044EA57A + 26CE02388C005C34E415F0E8E1F259D51999915E958B686469676573744944046C656C656D656E7456616C7565F571656C656D656E74 + 4964656E7469666965726B6167655F6F7665725F3138D8185866A46672616E646F6D582051BFD38D542495BE08327AC2064A05846836 + B35FBAC4F0C467DE0338072DA30B686469676573744944056C656C656D656E7456616C75656647617263696171656C656D656E744964 + 656E7469666965726B66616D696C795F6E616D65D8185866A46672616E646F6D58203DA715C789DE91788ACD9A6A919CE82DB180C1F7 + D3AEB41AE81A0F0027349C29686469676573744944066C656C656D656E7456616C756562455571656C656D656E744964656E74696669 + 65726F69737375696E675F636F756E747279D8185875A46672616E646F6D58207A17BCCBCA82615A3CB1321343CB963287482156C2C8 + E8563C35B8E0A3825CF0686469676573744944076C656C656D656E7456616C75656F54657374205049442069737375657271656C656D + 656E744964656E7469666965727169737375696E675F617574686F72697479 """.trimIndent().replace("\n", "") val parsed = IssuerSigned.deserialize(input.decodeToByteArray(Base16())) From 0c2e11fdff050188fb92ab17e97660d7cf88118c Mon Sep 17 00:00:00 2001 From: Christian Kollmann Date: Tue, 29 Oct 2024 16:17:09 +0100 Subject: [PATCH 4/4] Cleanup code --- conventions-vclib/src/main/kotlin/VcLibConventions.kt | 1 - .../at/asitplus/wallet/lib/iso/IssuerSignedListSerializer.kt | 5 ++--- .../wallet/lib/iso/NamespacedIssuerSignedListSerializer.kt | 4 ---- 3 files changed, 2 insertions(+), 8 deletions(-) diff --git a/conventions-vclib/src/main/kotlin/VcLibConventions.kt b/conventions-vclib/src/main/kotlin/VcLibConventions.kt index aec5fc02..c954e1a9 100644 --- a/conventions-vclib/src/main/kotlin/VcLibConventions.kt +++ b/conventions-vclib/src/main/kotlin/VcLibConventions.kt @@ -50,7 +50,6 @@ inline fun KotlinDependencyHandler.commonImplementationDependencies() { implementation(project.ktor("http")) implementation(project.napier()) implementation(project.ktor("utils")) - //project.AspVersions.versions["obor"] = VcLibVersions.obor implementation("net.orandja.obor:obor:${VcLibVersions.obor}") } diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedListSerializer.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedListSerializer.kt index 9ad0fa51..54db30b6 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedListSerializer.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/IssuerSignedListSerializer.kt @@ -80,9 +80,8 @@ open class IssuerSignedListSerializer(private val namespace: String) : KSerializ } val readBytes = decoder.decodeSerializableValue(ByteArraySerializer()) val item = Cbor.decodeFromByteArray(readBytes) as CborMap - - val elementID = - (item.first { (it.key as CborText).value == PROP_ELEMENT_ID }.value as CborText).value + val elementIdItem = item.first { (it.key as CborText).value == PROP_ELEMENT_ID } + val elementID = (elementIdItem.value as CborText).value entries += ByteStringWrapper( IssuerSignedItem.deserialize(item.cbor, namespace, elementID).getOrThrow(), item.cbor ) diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/NamespacedIssuerSignedListSerializer.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/NamespacedIssuerSignedListSerializer.kt index bec5c147..87bf354e 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/NamespacedIssuerSignedListSerializer.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/NamespacedIssuerSignedListSerializer.kt @@ -32,7 +32,6 @@ object NamespacedIssuerSignedListSerializer : KSerializer { @@ -41,11 +40,9 @@ object NamespacedIssuerSignedListSerializer : KSerializer