Skip to content

Commit

Permalink
Actually verify deserialized values from ISO examples
Browse files Browse the repository at this point in the history
  • Loading branch information
nodh committed Jul 24, 2023
1 parent c7dae46 commit 37405c6
Show file tree
Hide file tree
Showing 6 changed files with 240 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -250,6 +254,7 @@ data class IssuerSignedList(

companion object {
fun withItems(list: List<IssuerSignedItem>) = IssuerSignedList(
// TODO verify serialization of this
list.map { ByteStringWrapper(it, cborSerializer.encodeToByteArray(it).wrapInCborTag(24)) }
)
}
Expand Down Expand Up @@ -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()
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<MobileSecurityObject>(it)
}.getOrElse {
Expand Down Expand Up @@ -226,3 +241,21 @@ data class ValidityInfo(
}
}
}


object ByteStringWrapperMobileSecurityObjectSerializer : KSerializer<ByteStringWrapper<MobileSecurityObject>> {

override val descriptor: SerialDescriptor =
PrimitiveSerialDescriptor("ByteStringWrapperMobileSecurityObjectSerializer", PrimitiveKind.STRING)

override fun serialize(encoder: Encoder, value: ByteStringWrapper<MobileSecurityObject>) {
val bytes = cborSerializer.encodeToByteArray(value.value)
encoder.encodeSerializableValue(ByteArraySerializer(), bytes)
}

override fun deserialize(decoder: Decoder): ByteStringWrapper<MobileSecurityObject> {
val bytes = decoder.decodeSerializableValue(ByteArraySerializer())
return ByteStringWrapper(cborSerializer.decodeFromByteArray(bytes), bytes)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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" {
Expand Down Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
),
Expand Down Expand Up @@ -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]
Expand Down
Loading

0 comments on commit 37405c6

Please sign in to comment.