Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix issuer signed item deserialization #159

Merged
merged 4 commits into from
Oct 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions conventions-vclib/src/main/kotlin/VcLibConventions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ inline fun KotlinDependencyHandler.commonImplementationDependencies() {
implementation(project.ktor("http"))
implementation(project.napier())
implementation(project.ktor("utils"))
implementation("net.orandja.obor:obor:${VcLibVersions.obor}")
}

/**
Expand Down
1 change: 1 addition & 0 deletions conventions-vclib/src/main/kotlin/VcLibVersions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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")

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[versions]
kmmresult = "1.8.0!!"
1 change: 0 additions & 1 deletion vck/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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`}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -42,6 +46,7 @@ data class IssuerSigned private constructor(
vckCborSerializer.decodeFromByteArray<IssuerSigned>(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]:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.descriptors.buildClassSerialDescriptor
import kotlinx.serialization.encoding.*

open class IssuerSignedItemSerializer(private val namespace: String) : KSerializer<IssuerSignedItem> {
open class IssuerSignedItemSerializer(private val namespace: String, private val elementIdentifier: String) :
KSerializer<IssuerSignedItem> {

override val descriptor: SerialDescriptor = buildClassSerialDescriptor("IssuerSignedItem") {
element(PROP_DIGEST_ID, Long.serializer().descriptor)
Expand All @@ -35,17 +36,12 @@ open class IssuerSignedItemSerializer(private val namespace: String) : KSerializ
}

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) {
Expand Down Expand Up @@ -82,15 +78,15 @@ 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 elementValue: Any? = null
decoder.decodeStructure(descriptor) {
while (true) {
val name = decodeStringElement(descriptor, 0)
Expand All @@ -99,33 +95,35 @@ 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")

PROP_ELEMENT_VALUE -> elementValue = decodeAnything(index, elementIdentifier)
}
if (index == 3) break
if (random != null && elementValue != null) break
}
}
return IssuerSignedItem(
digestId = digestId,
random = random,
random = random!!,
elementIdentifier = elementIdentifier,
elementValue = elementValue
elementValue = elementValue!!,
)
}

private fun CompositeDecoder.decodeAnything(index: Int, elementIdentifier: String): Any {
if (namespace.isBlank()) Napier.w { "This decoder is not namespace-aware! Unspeakable things may happen…" }
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 {
elementIdentifier?.let {
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"
}
?: 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
Expand Down
Original file line number Diff line number Diff line change
@@ -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",
Expand Down Expand Up @@ -73,8 +79,12 @@ 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<CborObject>(readBytes) as CborMap
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
)
}
}
return IssuerSignedList(entries)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ object NamespacedIssuerSignedListSerializer : KSerializer<Map<String, IssuerSign
override fun serialize(encoder: Encoder, value: String) {
encoder.encodeString(value).also { key = value }
}

}

inner class IssuerSignedListSerializer internal constructor() : KSerializer<IssuerSignedList> {
Expand All @@ -41,17 +40,14 @@ object NamespacedIssuerSignedListSerializer : KSerializer<Map<String, IssuerSign
override fun deserialize(decoder: Decoder): IssuerSignedList =
decoder.decodeSerializableValue(IssuerSignedListSerializer(key))


override fun serialize(encoder: Encoder, value: IssuerSignedList) {
encoder.encodeSerializableValue(IssuerSignedListSerializer(key), value)
}

}
}

override fun serialize(encoder: Encoder, value: Map<String, IssuerSignedList>) =
NamespacedMapEntryDeserializer().let {
MapSerializer(it.namespaceSerializer, it.itemSerializer).serialize(encoder, value)
}

}
Loading
Loading