diff --git a/signum b/signum index 2ae952abc..67298aeeb 160000 --- a/signum +++ b/signum @@ -1 +1 @@ -Subproject commit 2ae952abc6cf3a9dda1d21ff0bb405558618c408 +Subproject commit 67298aeeb7be70680eab15bc21fbc4ee2fa89260 diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/CredentialIssuer.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/CredentialIssuer.kt index 2260f4086..a6241bb61 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/CredentialIssuer.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidvci/CredentialIssuer.kt @@ -24,6 +24,7 @@ import at.asitplus.wallet.lib.data.vckJsonSerializer import com.benasher44.uuid.uuid4 import io.github.aakira.napier.Napier import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArray +import kotlinx.serialization.builtins.ByteArraySerializer /** * Server implementation to issue credentials using OID4VCI. @@ -98,8 +99,7 @@ class CredentialIssuer( credentialIssuer = publicContext, configurationIds = credentialSchemes.flatMap { it.toCredentialIdentifier() }, grants = CredentialOfferGrants( - authorizationCode = - CredentialOfferGrantsAuthCode( + authorizationCode = CredentialOfferGrantsAuthCode( // TODO remember this state, for subsequent requests from the Wallet issuerState = uuid4().toString(), authorizationServer = authorizationService.publicContext @@ -219,7 +219,7 @@ class CredentialIssuer( * Removed in OID4VCI Draft 14, kept here for a bit of backwards-compatibility */ private suspend fun String.validateCwtProof(): CryptoPublicKey { - val coseSigned = CoseSigned.deserialize(decodeToByteArray(Base64UrlStrict)).getOrNull() + val coseSigned = CoseSigned.deserialize(ByteArraySerializer(), decodeToByteArray(Base64UrlStrict)).getOrNull() ?: throw OAuth2Exception(Errors.INVALID_PROOF) .also { Napier.w("client did provide invalid proof: $this") } val cwt = coseSigned.payload?.let { CborWebToken.deserialize(it).getOrNull() } diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt index 2591eb2dc..8c6d0c5d0 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/IssuerAgent.kt @@ -119,6 +119,7 @@ class IssuerAgent( namespacedItems = mapOf(credential.scheme.isoNamespace!! to credential.issuerSignedItems), issuerAuth = coseService.createSignedCose( payload = mso, + serializer = MobileSecurityObject.serializer(), addKeyId = false, addCertificate = true, ).getOrThrow(), diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt index 1bcee5e74..23ea33d78 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Validator.kt @@ -24,6 +24,7 @@ import io.matthewnelson.encoding.base16.Base16 import io.matthewnelson.encoding.base64.Base64 import io.matthewnelson.encoding.core.Decoder.Companion.decodeToByteArrayOrNull import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString +import kotlinx.serialization.builtins.ByteArraySerializer import kotlinx.serialization.json.buildJsonObject @@ -295,17 +296,14 @@ class Validator( throw IllegalArgumentException("issuerKey") } - if (verifierCoseService.verifyCose(issuerAuth, issuerKey).isFailure) { + if (verifierCoseService.verifyCose(issuerAuth, issuerKey, MobileSecurityObject.serializer()).isFailure) { Napier.w("IssuerAuth not verified: $issuerAuth") throw IllegalArgumentException("issuerAuth") } - val mso: MobileSecurityObject? = issuerSigned.issuerAuth.getTypedPayload(MobileSecurityObject.serializer()).onFailure { - throw IllegalArgumentException("mso", it) - Napier.w("MSO could not be decoded", it) - }.getOrNull()?.value + val mso: MobileSecurityObject? = issuerSigned.issuerAuth.payload if (mso == null) { - Napier.w("MSO is null: ${issuerAuth.payload?.encodeToString(Base16(strict = true))}") + Napier.w("MSO is null: $issuerAuth") throw IllegalArgumentException("mso") } @@ -314,13 +312,14 @@ class Validator( throw IllegalArgumentException("mso.docType") } val walletKey = mso.deviceKeyInfo.deviceKey + val deviceSignature = doc.deviceSigned.deviceAuth.deviceSignature ?: run { Napier.w("DeviceSignature is null: ${doc.deviceSigned.deviceAuth}") throw IllegalArgumentException("deviceSignature") } - if (verifierCoseService.verifyCose(deviceSignature, walletKey).isFailure) { - Napier.w("DeviceSignature not verified") + if (verifierCoseService.verifyCose(deviceSignature, walletKey, ByteArraySerializer()).isFailure) { + Napier.w("DeviceSignature not verified: ${doc.deviceSigned.deviceAuth}") throw IllegalArgumentException("deviceSignature") } @@ -471,7 +470,7 @@ class Validator( it.serialize().encodeToString(Base16(strict = true)) ) } - val result = verifierCoseService.verifyCose(it.issuerAuth, issuerKey) + val result = verifierCoseService.verifyCose(it.issuerAuth, issuerKey, MobileSecurityObject.serializer()) if (result.isFailure) { Napier.w("ISO: Could not verify credential", result.exceptionOrNull()) return Verifier.VerifyCredentialResult.InvalidStructure( diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifiablePresentationFactory.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifiablePresentationFactory.kt index 3bbe04e37..ba051b3be 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifiablePresentationFactory.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifiablePresentationFactory.kt @@ -15,6 +15,7 @@ import at.asitplus.wallet.lib.jws.JwsService import at.asitplus.wallet.lib.jws.SdJwtSigned import io.github.aakira.napier.Napier import kotlinx.datetime.Clock +import kotlinx.serialization.builtins.ByteArraySerializer import kotlinx.serialization.json.JsonElement class VerifiablePresentationFactory( @@ -57,6 +58,7 @@ class VerifiablePresentationFactory( ): Holder.CreatePresentationResult.DeviceResponse { val deviceSignature = coseService.createSignedCose( payload = challenge.encodeToByteArray(), + serializer = ByteArraySerializer(), addKeyId = false ).getOrElse { Napier.w("Could not create DeviceAuth for presentation", it) diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseService.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseService.kt index 2aa3eac26..6e9d4b522 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseService.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/cbor/CoseService.kt @@ -4,7 +4,6 @@ import at.asitplus.KmmResult import at.asitplus.catching import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.cosef.* -import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper import at.asitplus.signum.indispensable.pki.X509Certificate import at.asitplus.signum.indispensable.toX509SignatureAlgorithm import at.asitplus.signum.supreme.asKmmResult @@ -12,11 +11,8 @@ import at.asitplus.signum.supreme.sign.Verifier import at.asitplus.wallet.lib.agent.CryptoService import at.asitplus.wallet.lib.agent.DefaultVerifierCryptoService import at.asitplus.wallet.lib.agent.VerifierCryptoService -import at.asitplus.wallet.lib.iso.MobileSecurityObject -import at.asitplus.wallet.lib.iso.vckCborSerializer -import at.asitplus.wallet.lib.iso.wrapInCborTag import io.github.aakira.napier.Napier -import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.KSerializer /** * Creates and parses COSE objects. @@ -35,10 +31,11 @@ interface CoseService { * @param addKeyId whether to set [CoseHeader.kid] in [protectedHeader] * @param addCertificate whether to set [CoseHeader.certificateChain] in [unprotectedHeader] */ - suspend fun

createSignedCose( + suspend fun

createSignedCose( protectedHeader: CoseHeader? = null, unprotectedHeader: CoseHeader? = null, payload: P? = null, + serializer: KSerializer

, addKeyId: Boolean = true, addCertificate: Boolean = false, ): KmmResult> @@ -46,7 +43,11 @@ interface CoseService { interface VerifierCoseService { - fun verifyCose(coseSigned: CoseSigned<*>, signer: CoseKey): KmmResult + fun

verifyCose( + coseSigned: CoseSigned

, + signer: CoseKey, + serializer: KSerializer

, + ): KmmResult } @@ -54,50 +55,24 @@ class DefaultCoseService(private val cryptoService: CryptoService) : CoseService override val algorithm: CoseAlgorithm = cryptoService.keyMaterial.signatureAlgorithm.toCoseAlgorithm().getOrThrow() - override suspend fun

createSignedCose( + override suspend fun

createSignedCose( protectedHeader: CoseHeader?, unprotectedHeader: CoseHeader?, payload: P?, + serializer: KSerializer

, addKeyId: Boolean, addCertificate: Boolean, ): KmmResult> = catching { protectedHeader.withAlgorithmAndKeyId(addKeyId).let { coseHeader -> - payload.asCosePayload().let { cosePayload -> - CoseSigned( - protectedHeader = coseHeader, - unprotectedHeader = unprotectedHeader.withCertificateIfExists(addCertificate), - payload = cosePayload, - signature = calcSignature(coseHeader, cosePayload) - ) - } + CoseSigned

( + protectedHeader = coseHeader, + unprotectedHeader = unprotectedHeader.withCertificateIfExists(addCertificate), + payload = payload, + signature = calcSignature(coseHeader, payload, serializer) + ) } } - /** - * Encodes [this] as payload for [createSignedCose], i.e. encodes it into a byte array. - * + [ByteArray] is processed as it is - * + [ByteStringWrapper] is wrapped in Tag(24) - * + [MobileSecurityObject] is wrapped as [ByteStringWrapper] and wrapped in Tag(24) - * + null is processed as it is - * - * If other complex data classes need to be serialized (other than [MobileSecurityObject]), - * extend this method in the same fashion - */ - @Throws(NotImplementedError::class) - private fun

P.asCosePayload(): ByteArray? = when (this) { - is ByteArray -> this - is ByteStringWrapper<*> -> vckCborSerializer - .encodeToByteArray(this) - .wrapInCborTag(24) - - is MobileSecurityObject -> vckCborSerializer - .encodeToByteArray(ByteStringWrapper(this) as ByteStringWrapper) - .wrapInCborTag(24) - - is Nothing? -> null - else -> throw NotImplementedError() - } - private suspend fun CoseHeader?.withCertificateIfExists(addCertificate: Boolean): CoseHeader? = if (addCertificate) { withCertificate(cryptoService.keyMaterial.getCertificate()) @@ -122,16 +97,18 @@ class DefaultCoseService(private val cryptoService: CryptoService) : CoseService this?.copy(algorithm = coseAlgorithm) ?: CoseHeader(algorithm = coseAlgorithm) - private suspend fun calcSignature( + @Throws(Throwable::class) + private suspend fun

calcSignature( protectedHeader: CoseHeader, - payload: ByteArray?, + payload: P?, + serializer: KSerializer

, ): CryptoSignature.RawByteEncodable = - cryptoService.sign(CoseSigned.prepareCoseSignatureInput(protectedHeader, payload)) - .asKmmResult().getOrElse { + CoseSigned.prepareCoseSignatureInput

(protectedHeader, payload, serializer).let { signatureInput -> + cryptoService.sign(signatureInput).asKmmResult().getOrElse { Napier.w("No signature from native code", it) throw it } - + } } class DefaultVerifierCoseService( @@ -141,9 +118,16 @@ class DefaultVerifierCoseService( /** * Verifiers the signature of [coseSigned] by using [signer]. */ - override fun verifyCose(coseSigned: CoseSigned<*>, signer: CoseKey) = catching { - val signatureInput = CoseSigned.prepareCoseSignatureInput(coseSigned.protectedHeader.value, coseSigned.payload) - + override fun

verifyCose( + coseSigned: CoseSigned

, + signer: CoseKey, + serializer: KSerializer

, + ) = catching { + val signatureInput = CoseSigned.prepareCoseSignatureInput( + protectedHeader = coseSigned.protectedHeader.value, + payload = coseSigned.payload, + serializer = serializer + ) val algorithm = coseSigned.protectedHeader.value.algorithm ?: throw IllegalArgumentException("Algorithm not specified") val publicKey = signer.toCryptoPublicKey().getOrElse { ex -> diff --git a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/SdJwtVerificationTest.kt b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/SdJwtVerificationTest.kt index b7d34b5f0..217b8e86d 100644 --- a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/SdJwtVerificationTest.kt +++ b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/agent/SdJwtVerificationTest.kt @@ -129,7 +129,7 @@ class SdJwtVerificationTest : FreeSpec({ } } """.trimIndent() - println(reconstructed) + reconstructed shouldBe vckJsonSerializer.parseToJsonElement(expected) } diff --git a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/CoseServiceTest.kt b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/CoseServiceTest.kt index 50a6f6239..3078d4c56 100644 --- a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/CoseServiceTest.kt +++ b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/CoseServiceTest.kt @@ -9,8 +9,12 @@ import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.nulls.shouldNotBeNull import io.kotest.matchers.shouldBe import kotlinx.datetime.Clock +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.builtins.ByteArraySerializer +import kotlinx.serialization.builtins.NothingSerializer import kotlin.random.Random +@OptIn(ExperimentalSerializationApi::class) class CoseServiceTest : FreeSpec({ lateinit var cryptoService: CryptoService @@ -29,21 +33,25 @@ class CoseServiceTest : FreeSpec({ } "signed object with bytes can be verified" { + val parameterSerializer = ByteArraySerializer() val signed = coseService.createSignedCose( unprotectedHeader = CoseHeader(algorithm = CoseAlgorithm.ES256), payload = randomPayload, + serializer = parameterSerializer, ).getOrThrow() signed.payload shouldBe randomPayload signed.signature.shouldNotBeNull() - val parsed = CoseSigned.deserialize(signed.serialize()).getOrThrow() + val parsed = CoseSigned.deserialize(parameterSerializer, signed.serialize(parameterSerializer)).getOrThrow() + parsed shouldBe signed - val result = verifierCoseService.verifyCose(parsed, coseKey) + val result = verifierCoseService.verifyCose(parsed, coseKey, parameterSerializer) result.isSuccess shouldBe true } "signed object with MSO payload can be verified" { + val parameterSerializer = MobileSecurityObject.serializer() val mso = MobileSecurityObject( version = "1.0", digestAlgorithm = "SHA-256", @@ -62,29 +70,34 @@ class CoseServiceTest : FreeSpec({ val signed = coseService.createSignedCose( protectedHeader = CoseHeader(algorithm = CoseAlgorithm.ES256), payload = mso, + serializer = parameterSerializer ).getOrThrow() - signed.getTypedPayload(MobileSecurityObject.serializer()).getOrThrow().shouldNotBeNull().value shouldBe mso + signed.payload shouldBe mso signed.signature.shouldNotBeNull() - val parsed = CoseSigned.deserialize(signed.serialize()).getOrThrow() + val parsed = CoseSigned.deserialize(parameterSerializer,signed.serialize(parameterSerializer)).getOrThrow() + parsed shouldBe signed - val result = verifierCoseService.verifyCose(parsed, coseKey) + val result = verifierCoseService.verifyCose(parsed, coseKey, parameterSerializer) result.isSuccess shouldBe true } "signed object without payload can be verified" { + val parameterSerializer = NothingSerializer() val signed = coseService.createSignedCose( unprotectedHeader = null, payload = null, + serializer = parameterSerializer ).getOrThrow() signed.payload shouldBe null signed.signature.shouldNotBeNull() - val parsed = CoseSigned.deserialize(signed.serialize()).getOrThrow() + val parsed = CoseSigned.deserialize(parameterSerializer,signed.serialize(parameterSerializer)).getOrThrow() + parsed shouldBe signed - val result = verifierCoseService.verifyCose(parsed, coseKey) + val result = verifierCoseService.verifyCose(parsed, coseKey, parameterSerializer) result.isSuccess shouldBe true } diff --git a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/DeviceSignedItemSerializationTest.kt b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/DeviceSignedItemSerializationTest.kt index d062acffc..66e95e261 100644 --- a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/DeviceSignedItemSerializationTest.kt +++ b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/cbor/DeviceSignedItemSerializationTest.kt @@ -1,8 +1,8 @@ package at.asitplus.wallet.lib.cbor +import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.cosef.CoseHeader import at.asitplus.signum.indispensable.cosef.CoseSigned -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 @@ -47,9 +47,9 @@ class DeviceSignedItemSerializationTest : FreeSpec({ key = elementId, value = Random.nextBytes(32), ) - val protectedHeader = ByteStringWrapper(CoseHeader(), CoseHeader().serialize()) - val issuerAuth = CoseSigned(protectedHeader, null, null, byteArrayOf()) - val deviceAuth = CoseSigned(protectedHeader, null, null, byteArrayOf()) + val protectedHeader = CoseHeader() + val issuerAuth = CoseSigned(protectedHeader, null, null, CryptoSignature.RSAorHMAC(byteArrayOf())) + val deviceAuth = CoseSigned(protectedHeader, null, null, CryptoSignature.RSAorHMAC(byteArrayOf())) val doc = Document( docType = uuid4().toString(), 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 a2a0ac6ec..0f5de7f07 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 @@ -1,5 +1,6 @@ package at.asitplus.wallet.lib.cbor +import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.cosef.CoseHeader import at.asitplus.signum.indispensable.cosef.CoseSigned import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper @@ -45,8 +46,8 @@ class IssuerSignedItemSerializationTest : FreeSpec({ elementIdentifier = elementId, elementValue = Random.nextBytes(32), ) - val protectedHeader = ByteStringWrapper(CoseHeader(), CoseHeader().serialize()) - val issuerAuth = CoseSigned(protectedHeader, null, null, byteArrayOf()) + val protectedHeader = CoseHeader() + val issuerAuth = CoseSigned(protectedHeader, null, null, CryptoSignature.RSAorHMAC(byteArrayOf())) val doc = Document( docType = uuid4().toString(), issuerSigned = IssuerSigned.fromIssuerSignedItems( diff --git a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/IsoProcessTest.kt b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/IsoProcessTest.kt index 4d8472739..326a08f8d 100644 --- a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/IsoProcessTest.kt +++ b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/IsoProcessTest.kt @@ -19,6 +19,7 @@ import io.kotest.matchers.shouldBe import io.matthewnelson.encoding.base16.Base16 import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.datetime.Clock +import kotlinx.serialization.builtins.ByteArraySerializer import kotlin.random.Random class IsoProcessTest : FreeSpec({ @@ -55,9 +56,7 @@ class Wallet { issuerAuth.payload.shouldNotBeNull() val mso = document.issuerSigned.issuerAuth - .getTypedPayload(MobileSecurityObject.serializer()) - .getOrThrow()?.value - .shouldNotBeNull() + .payload.shouldNotBeNull() val mdlItems = document.issuerSigned.namespaces?.get(ConstantIndex.AtomicAttribute2023.isoNamespace) .shouldNotBeNull() @@ -91,6 +90,7 @@ class Wallet { deviceAuth = DeviceAuth( deviceSignature = coseService.createSignedCose( payload = null, + serializer = ByteArraySerializer(), addKeyId = false ).getOrThrow() ) @@ -142,6 +142,7 @@ class Issuer { ), issuerAuth = coseService.createSignedCose( payload = mso, + serializer = MobileSecurityObject.serializer(), addKeyId = false, addCertificate = true, ).getOrThrow() @@ -183,6 +184,7 @@ class Verifier { readerAuth = coseService.createSignedCose( unprotectedHeader = CoseHeader(), payload = null, + serializer = ByteArraySerializer(), addKeyId = false, ).getOrThrow() ) @@ -196,19 +198,16 @@ class Verifier { doc.errors.shouldBeNull() val issuerSigned = doc.issuerSigned val issuerAuth = issuerSigned.issuerAuth - verifierCoseService.verifyCose(issuerAuth, issuerKey).isSuccess shouldBe true + verifierCoseService.verifyCose(issuerAuth, issuerKey, MobileSecurityObject.serializer()).isSuccess shouldBe true issuerAuth.payload.shouldNotBeNull() - val mso = issuerAuth - .getTypedPayload(MobileSecurityObject.serializer()) - .getOrThrow()?.value - .shouldNotBeNull() + val mso = issuerAuth.payload.shouldNotBeNull() mso.docType shouldBe ConstantIndex.AtomicAttribute2023.isoDocType val mdlItems = mso.valueDigests[ConstantIndex.AtomicAttribute2023.isoNamespace].shouldNotBeNull() val walletKey = mso.deviceKeyInfo.deviceKey val deviceSignature = doc.deviceSigned.deviceAuth.deviceSignature.shouldNotBeNull() - verifierCoseService.verifyCose(deviceSignature, walletKey).isSuccess shouldBe true + verifierCoseService.verifyCose(deviceSignature, walletKey, ByteArraySerializer()).isSuccess shouldBe true val namespaces = issuerSigned.namespaces.shouldNotBeNull() val issuerSignedItems = namespaces[ConstantIndex.AtomicAttribute2023.isoNamespace].shouldNotBeNull() diff --git a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/Tag24SerializationTest.kt b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/Tag24SerializationTest.kt index fdcf47887..72f935396 100644 --- a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/Tag24SerializationTest.kt +++ b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/iso/Tag24SerializationTest.kt @@ -1,6 +1,7 @@ package at.asitplus.wallet.lib.iso import at.asitplus.catching +import at.asitplus.signum.indispensable.CryptoSignature import at.asitplus.signum.indispensable.cosef.* import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapper import at.asitplus.signum.indispensable.cosef.io.ByteStringWrapperSerializer @@ -9,6 +10,7 @@ import at.asitplus.wallet.lib.agent.EphemeralKeyWithSelfSignedCert import at.asitplus.wallet.lib.agent.Issuer import at.asitplus.wallet.lib.agent.IssuerAgent import at.asitplus.wallet.lib.data.ConstantIndex +import io.kotest.assertions.withClue import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.maps.shouldNotBeEmpty @@ -47,10 +49,10 @@ class Tag24SerializationTest : FreeSpec({ ), deviceAuth = DeviceAuth( deviceSignature = CoseSigned( - protectedHeader = ByteStringWrapper(CoseHeader()), + protectedHeader = CoseHeader(), unprotectedHeader = null, payload = byteArrayOf(), - rawSignature = byteArrayOf() + signature = CryptoSignature.RSAorHMAC(byteArrayOf()) ) ) @@ -98,11 +100,13 @@ class Tag24SerializationTest : FreeSpec({ ).getOrThrow().shouldBeInstanceOf() issuedCredential.issuerSigned.namespaces!!.shouldNotBeEmpty() - val numberOfClaims = issuedCredential.issuerSigned.namespaces!!.entries.fold(0) { acc, entry -> + val numberOfClaims = issuedCredential.issuerSigned.namespaces.entries.fold(0) { acc, entry -> acc + entry.value.entries.size } val serialized = issuedCredential.issuerSigned.serialize().encodeToString(Base16(true)) - "D818".toRegex().findAll(serialized).toList().shouldHaveSize(numberOfClaims + 1) + withClue(serialized) { + "D818".toRegex().findAll(serialized).toList().shouldHaveSize(numberOfClaims + 1) + } // add 1 for MSO in IssuerAuth } @@ -116,18 +120,18 @@ class Tag24SerializationTest : FreeSpec({ validityInfo = ValidityInfo(Clock.System.now(), Clock.System.now(), Clock.System.now()) ) val serializedMso = mso.serializeForIssuerAuth() - val input = CoseSigned>( - protectedHeader = ByteStringWrapper(CoseHeader()), + val input = CoseSigned( + protectedHeader = CoseHeader(), unprotectedHeader = null, - payload = serializedMso, - rawSignature = byteArrayOf() + payload = mso, + signature = CryptoSignature.RSAorHMAC(byteArrayOf()) ) val serialized = vckCborSerializer.encodeToByteArray(input) serialized.encodeToString(Base16(true)).shouldContainOnlyOnce("D818") serializedMso.encodeToString(Base16(true)).shouldStartWith("D818") - vckCborSerializer.decodeFromByteArray>(serialized) shouldBe input + vckCborSerializer.decodeFromByteArray>(serialized) shouldBe input MobileSecurityObject.deserializeFromIssuerAuth(serializedMso).getOrThrow() shouldBe mso } @@ -167,10 +171,10 @@ private fun deviceKeyInfo() = DeviceKeyInfo(CoseKey(CoseKeyType.EC2, keyParams = CoseKeyParams.EcYBoolParams(CoseEllipticCurve.P256))) private fun issuerAuth() = CoseSigned( - protectedHeader = ByteStringWrapper(CoseHeader()), + protectedHeader = CoseHeader(), unprotectedHeader = null, - payload = byteArrayOf(), - rawSignature = byteArrayOf() + payload = null, + signature = CryptoSignature.RSAorHMAC(byteArrayOf()) ) private fun issuerSignedItem() = IssuerSignedItem(0u, Random.nextBytes(16), "identifier", "value") diff --git a/vck/src/jvmTest/kotlin/at/asitplus/wallet/lib/cbor/CoseServiceJvmTest.kt b/vck/src/jvmTest/kotlin/at/asitplus/wallet/lib/cbor/CoseServiceJvmTest.kt index d8382ce34..1346e91be 100644 --- a/vck/src/jvmTest/kotlin/at/asitplus/wallet/lib/cbor/CoseServiceJvmTest.kt +++ b/vck/src/jvmTest/kotlin/at/asitplus/wallet/lib/cbor/CoseServiceJvmTest.kt @@ -7,7 +7,6 @@ import at.asitplus.signum.supreme.HazardousMaterials import at.asitplus.signum.supreme.hazmat.jcaPrivateKey import at.asitplus.signum.supreme.sign.EphemeralKey import at.asitplus.wallet.lib.agent.DefaultCryptoService -import at.asitplus.wallet.lib.agent.EphemeralKeyWithSelfSignedCert import at.asitplus.wallet.lib.agent.EphemeralKeyWithoutCert import com.authlete.cbor.CBORByteArray import com.authlete.cbor.CBORDecoder @@ -21,6 +20,7 @@ import io.kotest.matchers.shouldBe import io.kotest.matchers.types.shouldBeInstanceOf import io.matthewnelson.encoding.base16.Base16 import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString +import kotlinx.serialization.builtins.ByteArraySerializer import java.security.interfaces.ECPrivateKey import java.security.interfaces.ECPublicKey @@ -87,10 +87,11 @@ class CoseServiceJvmTest : FreeSpec({ "Signed object from int. library can be verified with int. library" { val signed = coseService.createSignedCose( payload = randomPayload.encodeToByteArray(), + serializer = ByteArraySerializer(), ).getOrThrow() withClue("$sigAlgo: Signature: ${signed.signature.encodeToTlv().toDerHexString()}") { - verifierCoseService.verifyCose(signed, cryptoService.keyMaterial.publicKey.toCoseKey().getOrThrow()) + verifierCoseService.verifyCose(signed, cryptoService.keyMaterial.publicKey.toCoseKey().getOrThrow(), ByteArraySerializer()) .isSuccess shouldBe true } } @@ -110,7 +111,7 @@ class CoseServiceJvmTest : FreeSpec({ extLibVerifier.verify(extLibCoseSign1) shouldBe true // Parsing to our structure verifying payload - val coseSigned = CoseSigned.deserialize(extLibCoseSign1.encode()).getOrThrow() + val coseSigned = CoseSigned.deserialize(ByteArraySerializer(), extLibCoseSign1.encode()).getOrThrow() coseSigned.payload shouldBe randomPayload.encodeToByteArray() val parsedDefLengthSignature = coseSigned.signature as CryptoSignature.EC.DefiniteLength val parsedSig = parsedDefLengthSignature.rawByteArray.encodeToString(Base16()) @@ -125,15 +126,16 @@ class CoseServiceJvmTest : FreeSpec({ val signed = coseService.createSignedCose( protectedHeader = CoseHeader(algorithm = coseAlgorithm), payload = randomPayload.encodeToByteArray(), + serializer = ByteArraySerializer(), addCertificate = false, addKeyId = false, ).getOrThrow() - val signedSerialized = signed.serialize().encodeToString(Base16()) + val signedSerialized = signed.serialize(ByteArraySerializer()).encodeToString(Base16()) val extLibSerialized = extLibCoseSign1.encode().encodeToString(Base16()) signedSerialized.length shouldBe extLibSerialized.length - withClue("$sigAlgo: Signature: ${parsedSig}") { - verifierCoseService.verifyCose(coseSigned, coseKey).isSuccess shouldBe true + withClue("$sigAlgo: Signature: $parsedSig") { + verifierCoseService.verifyCose(coseSigned, coseKey, ByteArraySerializer()).isSuccess shouldBe true } } @@ -141,11 +143,12 @@ class CoseServiceJvmTest : FreeSpec({ val coseSigned = coseService.createSignedCose( protectedHeader = CoseHeader(algorithm = coseAlgorithm), payload = randomPayload.encodeToByteArray(), + serializer = ByteArraySerializer(), addCertificate = false, addKeyId = false, ).getOrThrow() - val parsed = CBORDecoder(byteArrayOf(0xD2.toByte()) + coseSigned.serialize()).next() + val parsed = CBORDecoder(byteArrayOf(0xD2.toByte()) + coseSigned.serialize(ByteArraySerializer())).next() .shouldBeInstanceOf() val parsedCoseSign1 = parsed.tagContent .shouldBeInstanceOf()