diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/DeviceRequest.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/DeviceRequest.kt index 54a2fe2e0..a36268cad 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/DeviceRequest.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/DeviceRequest.kt @@ -28,6 +28,7 @@ import kotlinx.serialization.encoding.Encoder import kotlinx.serialization.encoding.decodeStructure import kotlinx.serialization.encoding.encodeCollection import kotlinx.serialization.encoding.encodeStructure +import okio.ByteString.Companion.toByteString /** * Part of the ISO/IEC 18013-5:2021 standard: Data structure for mdoc request (8.3.2.1.2.1) @@ -227,6 +228,9 @@ data class IssuerSigned( val issuerAuth: CoseSigned, ) { + fun getIssuerAuthPayloadAsMso() = issuerAuth.payload?.stripCborTag(24) + ?.let { cborSerializer.decodeFromByteArray(ByteStringWrapperMobileSecurityObjectSerializer, it).value } + fun serialize() = cborSerializer.encodeToByteArray(this) companion object { @@ -250,6 +254,7 @@ data class IssuerSignedList( companion object { fun withItems(list: List) = IssuerSignedList( + // TODO verify serialization of this list.map { ByteStringWrapper(it, cborSerializer.encodeToByteArray(it).wrapInCborTag(24)) } ) } @@ -514,4 +519,4 @@ fun ByteArray.stripCborTag(tag: Byte) = this.dropWhile { it == 0xd8.toByte() }.d fun ByteArray.wrapInCborTag(tag: Byte) = byteArrayOf(0xd8.toByte()) + byteArrayOf(tag) + this -fun ByteArray.sha256(): ByteArray = this//toByteString().sha256().toByteArray() +fun ByteArray.sha256(): ByteArray = toByteString().sha256().toByteArray() diff --git a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileSecurityObject.kt b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileSecurityObject.kt index 539665693..804b0b8e6 100644 --- a/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileSecurityObject.kt +++ b/vclib/src/commonMain/kotlin/at/asitplus/wallet/lib/iso/MobileSecurityObject.kt @@ -11,6 +11,7 @@ import kotlinx.serialization.KSerializer import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import kotlinx.serialization.builtins.ByteArraySerializer +import kotlinx.serialization.cbor.ByteStringWrapper import kotlinx.serialization.decodeFromByteArray import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor @@ -45,7 +46,21 @@ data class MobileSecurityObject( fun serialize() = cborSerializer.encodeToByteArray(this) + fun serializeForIssuerAuth() = + cborSerializer.encodeToByteArray(ByteStringWrapperMobileSecurityObjectSerializer, ByteStringWrapper(this)) + .wrapInCborTag(24) + companion object { + fun deserializeFromIssuerAuth(it: ByteArray) = kotlin.runCatching { + cborSerializer.decodeFromByteArray( + ByteStringWrapperMobileSecurityObjectSerializer, + it.stripCborTag(24) + ).value + }.getOrElse { + Napier.w("deserialize failed", it) + null + } + fun deserialize(it: ByteArray) = kotlin.runCatching { cborSerializer.decodeFromByteArray(it) }.getOrElse { @@ -226,3 +241,21 @@ data class ValidityInfo( } } } + + +object ByteStringWrapperMobileSecurityObjectSerializer : KSerializer> { + + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("ByteStringWrapperMobileSecurityObjectSerializer", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: ByteStringWrapper) { + val bytes = cborSerializer.encodeToByteArray(value.value) + encoder.encodeSerializableValue(ByteArraySerializer(), bytes) + } + + override fun deserialize(decoder: Decoder): ByteStringWrapper { + val bytes = decoder.decodeSerializableValue(ByteArraySerializer()) + return ByteStringWrapper(cborSerializer.decodeFromByteArray(bytes), bytes) + } + +} diff --git a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/CborSerializationTest.kt b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/CborSerializationTest.kt index 86aca4244..2a22664db 100644 --- a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/CborSerializationTest.kt +++ b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/CborSerializationTest.kt @@ -2,15 +2,25 @@ package at.asitplus.wallet.lib.iso import at.asitplus.wallet.lib.cbor.CoseSigned import at.asitplus.wallet.lib.data.jsonSerializer +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DOC_TYPE_MDL +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.DOCUMENT_NUMBER +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.DRIVING_PRIVILEGES +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.EXPIRY_DATE +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.FAMILY_NAME +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.ISSUE_DATE +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.PORTRAIT +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.NAMESPACE_MDL import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.collections.shouldContain import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import io.kotest.matchers.string.shouldContain import io.ktor.utils.io.core.toByteArray import io.matthewnelson.component.encoding.base16.decodeBase16ToArray import io.matthewnelson.component.encoding.base16.encodeBase16 +import kotlinx.datetime.Instant import kotlinx.datetime.LocalDate import kotlinx.serialization.encodeToString @@ -128,10 +138,25 @@ class CborSerializationTest : FreeSpec({ 9bb7f80bf """.trimIndent().replace("\n", "").uppercase() - val deserialized = DeviceRequest.deserialize(input.decodeBase16ToArray()!!) - deserialized.shouldNotBeNull() + val deviceRequest = DeviceRequest.deserialize(input.decodeBase16ToArray()!!) + deviceRequest.shouldNotBeNull() + println(deviceRequest) + + deviceRequest.version shouldBe "1.0" + val docRequest = deviceRequest.docRequests.first() + docRequest.shouldNotBeNull() - println(deserialized) + docRequest.itemsRequest.value.docType shouldBe DOC_TYPE_MDL + val itemsRequestList = docRequest.itemsRequest.value.namespaces[NAMESPACE_MDL] + itemsRequestList.shouldNotBeNull() + itemsRequestList.findItem(FAMILY_NAME) shouldBe true + itemsRequestList.findItem(DOCUMENT_NUMBER) shouldBe true + itemsRequestList.findItem(DRIVING_PRIVILEGES) shouldBe true + itemsRequestList.findItem(EXPIRY_DATE) shouldBe true + itemsRequestList.findItem(PORTRAIT) shouldBe false + + docRequest.readerAuth.shouldNotBeNull() + docRequest.readerAuth?.unprotectedHeader?.certificateChain?.shouldNotBeNull() } // From ISO/IEC 18013-5:2021(E), D4.1.2, page 116 @@ -298,10 +323,60 @@ class CborSerializationTest : FreeSpec({ 806a07f8b5388a332d92c189a7bf293ee1f543405ae6824d6673746174757300 """.trimIndent().replace("\n", "").uppercase() - val deserialized = DeviceResponse.deserialize(input.decodeBase16ToArray()!!) - deserialized.shouldNotBeNull() + val deviceResponse = DeviceResponse.deserialize(input.decodeBase16ToArray()!!) + deviceResponse.shouldNotBeNull() - println(deserialized) + println(deviceResponse) + + deviceResponse.version shouldBe "1.0" + val document = deviceResponse.documents?.get(0) + document.shouldNotBeNull() + document.docType shouldBe DOC_TYPE_MDL + val issuerSignedList = document.issuerSigned.namespaces?.get(NAMESPACE_MDL) + issuerSignedList.shouldNotBeNull() + issuerSignedList.findItem(0U).elementIdentifier shouldBe FAMILY_NAME + issuerSignedList.findItem(0U).elementValue.string shouldBe "Doe" + issuerSignedList.findItem(3U).elementIdentifier shouldBe ISSUE_DATE + issuerSignedList.findItem(3U).elementValue.string shouldBe "2019-10-20" + issuerSignedList.findItem(4U).elementIdentifier shouldBe EXPIRY_DATE + issuerSignedList.findItem(4U).elementValue.string shouldBe "2024-10-20" + issuerSignedList.findItem(7U).elementIdentifier shouldBe DOCUMENT_NUMBER + issuerSignedList.findItem(7U).elementValue.string shouldBe "123456789" + issuerSignedList.findItem(8U).elementIdentifier shouldBe PORTRAIT + issuerSignedList.findItem(8U).elementValue.bytes.shouldNotBeNull() + issuerSignedList.findItem(9U).elementIdentifier shouldBe DRIVING_PRIVILEGES + val drivingPrivilege = issuerSignedList.findItem(9U).elementValue.drivingPrivilege + drivingPrivilege.shouldNotBeNull() + drivingPrivilege shouldContain DrivingPrivilege( + vehicleCategoryCode = "A", + issueDate = LocalDate.parse("2018-08-09"), + expiryDate = LocalDate.parse("2024-10-20") + ) + drivingPrivilege shouldContain DrivingPrivilege( + vehicleCategoryCode = "B", + issueDate = LocalDate.parse("2017-02-23"), + expiryDate = LocalDate.parse("2024-10-20") + ) + val mso = document.issuerSigned.getIssuerAuthPayloadAsMso() + mso.shouldNotBeNull() + mso.version shouldBe "1.0" + mso.digestAlgorithm shouldBe "SHA-256" + mso.docType shouldBe DOC_TYPE_MDL + mso.validityInfo.signed shouldBe Instant.parse("2020-10-01T13:30:02Z") + mso.validityInfo.validFrom shouldBe Instant.parse("2020-10-01T13:30:02Z") + mso.validityInfo.validUntil shouldBe Instant.parse("2021-10-01T13:30:02Z") + val valueDigestList = mso.valueDigests[NAMESPACE_MDL] + valueDigestList.shouldNotBeNull() + valueDigestList.findItem(0U) shouldBe "75167333B47B6C2BFB86ECCC1F438CF57AF055371AC55E1E359E20F254ADCEBF" + .decodeBase16ToArray() + valueDigestList.findItem(1U) shouldBe "67E539D6139EBD131AEF441B445645DD831B2B375B390CA5EF6279B205ED4571" + .decodeBase16ToArray() + val valueDigestListUs = mso.valueDigests[NAMESPACE_MDL + ".US"] + valueDigestListUs.shouldNotBeNull() + valueDigestListUs.findItem(0U) shouldBe "D80B83D25173C484C5640610FF1A31C949C1D934BF4CF7F18D5223B15DD4F21C" + .decodeBase16ToArray() + valueDigestListUs.findItem(1U) shouldBe "4D80E1E2E4FB246D97895427CE7000BB59BB24C8CD003ECF94BF35BBD2917E34" + .decodeBase16ToArray() } "Driving Privilege" { @@ -444,18 +519,42 @@ class CborSerializationTest : FreeSpec({ 044b890ad85aa53f129134775d733754d7cb7a413766aeff13cb2e """.trimIndent().replace("\n", "").uppercase() - val deserialized = CoseSigned.deserialize(input.decodeBase16ToArray()!!) - deserialized.shouldNotBeNull() + val coseSigned = CoseSigned.deserialize(input.decodeBase16ToArray()!!) + coseSigned.shouldNotBeNull() + println(coseSigned) - println(deserialized) - // NOTE: deserialized.payload is a tagged CBOR bytestring with Tag 24 = 0xD818 - // TODO How to deserialize a tagged byte string? - val payload = deserialized.payload + val payload = coseSigned.payload payload.shouldNotBeNull() - val stripped = payload.drop(5).toByteArray() - val parsed = MobileSecurityObject.deserialize(stripped) - parsed.shouldNotBeNull() - println(parsed) + val mso = MobileSecurityObject.deserializeFromIssuerAuth(payload) + mso.shouldNotBeNull() + println(mso) + mso.version shouldBe "1.0" + mso.digestAlgorithm shouldBe "SHA-256" + mso.docType shouldBe DOC_TYPE_MDL + mso.validityInfo.signed shouldBe Instant.parse("2020-10-01T13:30:02Z") + mso.validityInfo.validFrom shouldBe Instant.parse("2020-10-01T13:30:02Z") + mso.validityInfo.validUntil shouldBe Instant.parse("2021-10-01T13:30:02Z") + val valueDigestList = mso.valueDigests[NAMESPACE_MDL] + valueDigestList.shouldNotBeNull() + valueDigestList.findItem(0U) shouldBe "75167333B47B6C2BFB86ECCC1F438CF57AF055371AC55E1E359E20F254ADCEBF" + .decodeBase16ToArray() + valueDigestList.findItem(1U) shouldBe "67E539D6139EBD131AEF441B445645DD831B2B375B390CA5EF6279B205ED4571" + .decodeBase16ToArray() + val valueDigestListUs = mso.valueDigests["$NAMESPACE_MDL.US"] + valueDigestListUs.shouldNotBeNull() + valueDigestListUs.findItem(0U) shouldBe "D80B83D25173C484C5640610FF1A31C949C1D934BF4CF7F18D5223B15DD4F21C" + .decodeBase16ToArray() + valueDigestListUs.findItem(1U) shouldBe "4D80E1E2E4FB246D97895427CE7000BB59BB24C8CD003ECF94BF35BBD2917E34" + .decodeBase16ToArray() } }) + +private fun ItemsRequestList.findItem(key: String) = + entries.first { it.key == key }.value + +private fun ValueDigestList.findItem(digestId: UInt) = + entries.first { it.key == digestId }.value + +private fun IssuerSignedList.findItem(digestId: UInt) = + entries.first { it.value.digestId == digestId }.value diff --git a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/IsoMdocTest.kt b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/IsoMdocTest.kt index 84676139d..400263d6b 100644 --- a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/IsoMdocTest.kt +++ b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/IsoMdocTest.kt @@ -75,7 +75,7 @@ class Wallet { println("Wallet stored IssuerAuth: $issuerAuth") val issuerAuthPayload = issuerAuth.payload issuerAuthPayload.shouldNotBeNull() - val mso = MobileSecurityObject.deserialize(issuerAuthPayload.stripCborTag(24)) + val mso = document.issuerSigned.getIssuerAuthPayloadAsMso() mso.shouldNotBeNull() val mdlItems = document.issuerSigned.namespaces?.get(NAMESPACE_MDL) mdlItems.shouldNotBeNull() @@ -192,7 +192,7 @@ class Issuer { issuerAuth = coseService.createSignedCose( protectedHeader = CoseHeader(algorithm = CoseAlgorithm.ES256), unprotectedHeader = null, // TODO transport issuer certificate - payload = mso.serialize().wrapInCborTag(24), + payload = mso.serializeForIssuerAuth(), addKeyId = false, ).getOrThrow() ), @@ -252,7 +252,7 @@ class Verifier { verifierCoseService.verifyCose(issuerAuth, issuerKey).getOrThrow().shouldBe(true) val issuerAuthPayload = issuerAuth.payload issuerAuthPayload.shouldNotBeNull() - val mso = MobileSecurityObject.deserialize(issuerAuthPayload.stripCborTag(24)) + val mso = issuerSigned.getIssuerAuthPayloadAsMso() mso.shouldNotBeNull() mso.docType shouldBe DOC_TYPE_MDL val mdlItems = mso.valueDigests[NAMESPACE_MDL] diff --git a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/JsonSerializationTest.kt b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/JsonSerializationTest.kt index f77e95509..ffacbbdab 100644 --- a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/JsonSerializationTest.kt +++ b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/JsonSerializationTest.kt @@ -1,11 +1,22 @@ package at.asitplus.wallet.lib.iso +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DOC_TYPE_MDL +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.DOCUMENT_NUMBER +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.DRIVING_PRIVILEGES +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.EXPIRY_DATE +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.FAMILY_NAME +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.ISSUE_DATE +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.DataElements.PORTRAIT +import at.asitplus.wallet.lib.iso.IsoDataModelConstants.NAMESPACE_MDL import at.asitplus.wallet.lib.jws.JwsSigned import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.nulls.shouldNotBeNull -import io.kotest.matchers.shouldNotBe +import io.kotest.matchers.shouldBe +import kotlinx.datetime.LocalDate class JsonSerializationTest : FreeSpec({ @@ -35,13 +46,55 @@ class JsonSerializationTest : FreeSpec({ } """.trimIndent() - val deserialized = ServerRequest.deserialize(input) - deserialized.shouldNotBeNull() - println(deserialized) + val serverRequest = ServerRequest.deserialize(input) + serverRequest.shouldNotBeNull() + println(serverRequest) + + serverRequest.version shouldBe "1.0" + val docRequest = serverRequest.docRequests[0] + docRequest.docType shouldBe DOC_TYPE_MDL + val itemRequests = docRequest.namespaces[NAMESPACE_MDL] + itemRequests.shouldNotBeNull() + itemRequests[FAMILY_NAME] shouldBe true + itemRequests[DOCUMENT_NUMBER] shouldBe true + itemRequests[DRIVING_PRIVILEGES] shouldBe true + itemRequests[ISSUE_DATE] shouldBe true + itemRequests[EXPIRY_DATE] shouldBe true + itemRequests[PORTRAIT] shouldBe false } // from ISO/IEC 18013-5:2021(E), D4.2.1.2, page 121 "Server Response" { + /** + * Payload in JWS is: + * { + * "doctype": "org.iso.18013.5.1.mDL", + * "namespaces": { + * "org.iso.18013.5.1": { + * "family_name": "Doe", + * "given_name": "Jane", + * "issue_date": "2019-10-20", + * "expiry_date": "2024-10-20", + * "document_number": "123456789", + * "portrait": "_9j_4AAQSkZJRgABAQEAkACQAAD_2wBDABMNDhEODBMRDxEVFBMXHTAfHRoaHToqLCMwRT1JR0Q9Q0FMVm1dTFFoUkFDX4JgaHF1e3x7SlyGkIV3j214e3b_2wBDARQVFR0ZHTgfHzh2T0NPdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnb_wAARCAAYAGQDASIAAhEBAxEB_8QAGwAAAwEAAwEAAAAAAAAAAAAAAAUGBAECAwf_xAAyEAABAwMDAgUCAwkAAAAAAAABAgMEAAURBhIhEzEUFVFhcSJBB4GhFjVCUnORssHx_8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH_xAAaEQEBAQADAQAAAAAAAAAAAAAAAUERITFh_9oADAMBAAIRAxEAPwClu94i2iMpx9aSvH0NA_Us-w_3Xnp-8-dwlyOh0NrhRt37s8A5zgetK9R6fjLbuN0dUtbvSyhPZKSABn37Ufh_-5X_AOuf8U0hXeZq8InORLfb3py2iQooOO3fGAePet1i1BHvTbmxCmXWuVoUc4HqDUlbkzJ1_mu6dcEUEEqLpBBBPpg9_wBPWvXTS0tM3mMtC_H9FZK92RxkEfOTTC-mr2tUl10Qbc9KZa5W6FYAHrwDx84p3Z7vHvEPxEfcnadq0q7pNTehun5PcN2O_wBXxt_7XhoZhUqDdY5UUodQlG7GcEhQzQN7zrCLbX0sx20zF_x7XMBPtnByacXG4MW2CuVJJCEjsOST9gKgdVWeNZlw2Y24lSVFa1HJUcivoT6o6Y48WWg2eD1cY_WmGpn9tykIddtL6IqzhLu7v8cYP96qYz6JUdt9o5bcSFJPsai9YRpaoqJDLzCrQgp6bTJAxxjPAx-p70ya1VAgWqApUd9KHWyEIbAVt2nbjJIpg36ivosbDTnQ66nFFITv24wO_Y0lja88RJaZ8u29RYTnr5xk4_lrm-so1KxAkx5keMjnaiSoJUSVAdhn0rHc3rrpm5x1KuTs1t3koXnBweRgk4-RSe9lXlFcA5GaKJyz3KJ4-3vxd_T6qCndjOPyrJp-zeSQlx-v19zhXu2bccAYxk-lFFFLJOjk-MXJt1wegledwSM9_sCCOPat1i05GswcUlannnBtUtQxx6AUUUC5_RSes6YNxeiMu8LaCSQR6dxx85p3ZrRHs0ToR9ysnctau6jRRQYdQ6b88eZc8V0OkCMdPdnP5imVxtzFyhKiyQShX3HdJ9RRRT4J0aIUUJYcuz6oqVZDO3gfHOM9_tVPDitQorcdhO1tsYAoooF190_GvaEFxSmnkcJcTzx6EfcVhiaPSma3JuM96epvG1Kxgcdgck5HtRRSClooooP_2Q", + * "driving_privileges": [ + * { + * "vehicle_category_code": "A", + * "issue_date": "2018-08-09", + * "expiry_date": "2024-10-20" + * }, + * { + * "vehicle_category_code": "B", + * "issue_date": "2017-02-23", + * "expiry_date": "2024-10-20" + * } + * ] + * } + * }, + * "iat": 1609855200, + * "exp": 1609855320 + * } + */ val input = """ { "version": "1.0", @@ -89,17 +142,35 @@ class JsonSerializationTest : FreeSpec({ } """.trimIndent() - val deserialized = ServerResponse.deserialize(input) - deserialized.shouldNotBeNull() - println(deserialized) + val serverResponse = ServerResponse.deserialize(input) + serverResponse.shouldNotBeNull() + println(serverResponse) - val payload = deserialized.documents.first() + val payload = serverResponse.documents.first() val jws = JwsSigned.parse(payload) jws.shouldNotBeNull() - val mdl = MobileDrivingLicenceJws.deserialize(jws.payload.decodeToString()) - mdl.shouldNotBeNull() - println(mdl) - } + val mdlJws = MobileDrivingLicenceJws.deserialize(jws.payload.decodeToString()) + mdlJws.shouldNotBeNull() + println(mdlJws) + mdlJws.doctype shouldBe DOC_TYPE_MDL + val mdl = mdlJws.namespaces.mdl + mdl.familyName shouldBe "Doe" + mdl.givenName shouldBe "Jane" + mdl.issueDate shouldBe LocalDate.parse("2019-10-20") + mdl.expiryDate shouldBe LocalDate.parse("2024-10-20") + mdl.licenceNumber shouldBe "123456789" + mdl.drivingPrivileges shouldHaveSize 2 + mdl.drivingPrivileges shouldContain DrivingPrivilege( + vehicleCategoryCode = "A", + issueDate = LocalDate.parse("2018-08-09"), + expiryDate = LocalDate.parse("2024-10-20") + ) + mdl.drivingPrivileges shouldContain DrivingPrivilege( + vehicleCategoryCode = "B", + issueDate = LocalDate.parse("2017-02-23"), + expiryDate = LocalDate.parse("2024-10-20") + ) + } }) diff --git a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/JwsSerializationTest.kt b/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/JwsSerializationTest.kt deleted file mode 100644 index 72a728a86..000000000 --- a/vclib/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/JwsSerializationTest.kt +++ /dev/null @@ -1,49 +0,0 @@ -package at.asitplus.wallet.lib.iso - -import at.asitplus.wallet.lib.jws.JwsSigned -import io.kotest.core.spec.style.FreeSpec -import io.kotest.matchers.nulls.shouldNotBeNull - -class JwsSerializationTest : FreeSpec({ - - "mDL as JWS" { - // from ISO/IEC 18013-5:2021 - val input = """ { - "doctype": "org.iso.18013.5.1.mDL", - "namespaces": { - "org.iso.18013.5.1": { - "family_name": "Doe", - "given_name": "Jane", - "issue_date": "2019-10-20", - "expiry_date": "2024-10-20", - "document_number": "123456789", - "portrait": "_9j_4AAQSkZJRgABAQEAkACQAAD_2wBDABMNDhEODBMRDxEVFBMXHTAfHRoaHToqLCMwRT1JR0Q9Q0FMVm1dTFFoUkFDX4JgaHF1e3x7SlyGkIV3j214e3b_2wBDARQVFR0ZHTgfHzh2T0NPdnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnZ2dnb_wAARCAAYAGQDASIAAhEBAxEB_8QAGwAAAwEAAwEAAAAAAAAAAAAAAAUGBAECAwf_xAAyEAABAwMDAgUCAwkAAAAAAAABAgMEAAURBhIhEzEUFVFhcSJBB4GhFjVCUnORssHx_8QAFQEBAQAAAAAAAAAAAAAAAAAAAAH_xAAaEQEBAQADAQAAAAAAAAAAAAAAAUERITFh_9oADAMBAAIRAxEAPwClu94i2iMpx9aSvH0NA_Us-w_3Xnp-8-dwlyOh0NrhRt37s8A5zgetK9R6fjLbuN0dUtbvSyhPZKSABn37Ufh_-5X_AOuf8U0hXeZq8InORLfb3py2iQooOO3fGAePet1i1BHvTbmxCmXWuVoUc4HqDUlbkzJ1_mu6dcEUEEqLpBBBPpg9_wBPWvXTS0tM3mMtC_H9FZK92RxkEfOTTC-mr2tUl10Qbc9KZa5W6FYAHrwDx84p3Z7vHvEPxEfcnadq0q7pNTehun5PcN2O_wBXxt_7XhoZhUqDdY5UUodQlG7GcEhQzQN7zrCLbX0sx20zF_x7XMBPtnByacXG4MW2CuVJJCEjsOST9gKgdVWeNZlw2Y24lSVFa1HJUcivoT6o6Y48WWg2eD1cY_WmGpn9tykIddtL6IqzhLu7v8cYP96qYz6JUdt9o5bcSFJPsai9YRpaoqJDLzCrQgp6bTJAxxjPAx-p70ya1VAgWqApUd9KHWyEIbAVt2nbjJIpg36ivosbDTnQ66nFFITv24wO_Y0lja88RJaZ8u29RYTnr5xk4_lrm-so1KxAkx5keMjnaiSoJUSVAdhn0rHc3rrpm5x1KuTs1t3koXnBweRgk4-RSe9lXlFcA5GaKJyz3KJ4-3vxd_T6qCndjOPyrJp-zeSQlx-v19zhXu2bccAYxk-lFFFLJOjk-MXJt1wegledwSM9_sCCOPat1i05GswcUlannnBtUtQxx6AUUUC5_RSes6YNxeiMu8LaCSQR6dxx85p3ZrRHs0ToR9ysnctau6jRRQYdQ6b88eZc8V0OkCMdPdnP5imVxtzFyhKiyQShX3HdJ9RRRT4J0aIUUJYcuz6oqVZDO3gfHOM9_tVPDitQorcdhO1tsYAoooF190_GvaEFxSmnkcJcTzx6EfcVhiaPSma3JuM96epvG1Kxgcdgck5HtRRSClooooP_2Q", - "driving_privileges": [ - { - "vehicle_category_code": "A", - "issue_date": "2018-08-09", - "expiry_date": "2024-10-20" - }, - { - "vehicle_category_code": "B", - "issue_date": "2017-02-23", - "expiry_date": "2024-10-20" - } - ], - "birth_date": "1970-01-01", - "issuing_country": "AT", - "issuing_authority": "LPD Steiermark", - "un_distinguishing_sign": "AT" - } - }, - "iat": 1609855200, - "exp": 1609855320 - } - """.trimIndent() - - val mdlJws = MobileDrivingLicenceJws.deserialize(input) - - mdlJws.shouldNotBeNull() - println(mdlJws.namespaces.mdl) - } -})