diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ea5d317..95a4d8fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ Release 5.1.0: - Update JsonPath4K to 2.4.0 - Fix XCF export with transitive dependencies +Release 5.0.1: + - Fix verifiable presentation of ISO credentials to contain `DeviceResponse` instead of a `Document` + - Data classes for verification result of ISO structures now may contain more than one document + Release 5.0.0: - Remove `OidcSiopWallet.newDefaultInstance()` and replace it with a constructor - Remove `OidcSiopVerifier.newInstance()` methods and replace them with constructors diff --git a/gradle.properties b/gradle.properties index 503ce19e..d24a93ac 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,7 +12,7 @@ kotlin.mpp.enableCInteropCommonization=true kotlin.mpp.stability.nowarn=true kotlin.native.ignoreDisabledTargets=true -artifactVersion = 5.1.0-SNAPSHOT +artifactVersion = 5.0.1-SNAPSHOT jdk.version=17 diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt index 45061096..4fe74039 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopVerifier.kt @@ -481,7 +481,7 @@ class OidcSiopVerifier private constructor( /** * Successfully decoded and validated the response from the Wallet (ISO credential) */ - data class SuccessIso(val document: IsoDocumentParsed, val state: String?) : + data class SuccessIso(val documents: Collection, val state: String?) : AuthnResponseResult() } @@ -656,7 +656,7 @@ class OidcSiopVerifier private constructor( .also { Napier.i("VP success: $this") } is Verifier.VerifyPresentationResult.SuccessIso -> - AuthnResponseResult.SuccessIso(document, state) + AuthnResponseResult.SuccessIso(documents, state) .also { Napier.i("VP success: $this") } is Verifier.VerifyPresentationResult.SuccessSdJwt -> diff --git a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt index d91639c9..584bef16 100644 --- a/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt +++ b/vck-openid/src/commonMain/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWallet.kt @@ -12,6 +12,7 @@ import at.asitplus.openid.OpenIdConstants.SCOPE_OPENID import at.asitplus.openid.OpenIdConstants.URN_TYPE_JWK_THUMBPRINT import at.asitplus.openid.OpenIdConstants.VP_TOKEN import at.asitplus.signum.indispensable.CryptoPublicKey +import at.asitplus.signum.indispensable.io.Base64UrlStrict import at.asitplus.signum.indispensable.josef.JsonWebKey import at.asitplus.signum.indispensable.josef.JsonWebKeySet import at.asitplus.signum.indispensable.josef.JwsSigned @@ -26,7 +27,6 @@ import at.asitplus.wallet.lib.oidc.helper.PresentationFactory import at.asitplus.wallet.lib.oidc.helpers.AuthorizationResponsePreparationState import at.asitplus.wallet.lib.oidvci.OAuth2Exception import io.github.aakira.napier.Napier -import io.matthewnelson.encoding.base16.Base16 import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString import kotlinx.datetime.Clock import kotlinx.serialization.json.JsonPrimitive @@ -285,9 +285,8 @@ class OidcSiopWallet( private fun Holder.CreatePresentationResult.toJsonPrimitive() = when (this) { is Holder.CreatePresentationResult.Signed -> JsonPrimitive(jws) is Holder.CreatePresentationResult.SdJwt -> JsonPrimitive(sdJwt) - is Holder.CreatePresentationResult.Document -> JsonPrimitive( - document.serialize().encodeToString(Base16(strict = true)) - ) + is Holder.CreatePresentationResult.DeviceResponse -> + JsonPrimitive(deviceResponse.serialize().encodeToString(Base64UrlStrict)) } private fun List.singleOrArray() = if (size == 1) { diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt index 70c2f286..08b006f4 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopIsoProtocolTest.kt @@ -165,7 +165,7 @@ class OidcSiopIsoProtocolTest : FreeSpec({ val result = verifierSiop.validateAuthnResponseFromPost(authnResponse.params.formUrlEncode()) result.shouldBeInstanceOf() - val document = result.document + val document = result.documents.first() document.validItems.shouldNotBeEmpty() document.validItems.shouldBeSingleton() @@ -217,5 +217,5 @@ private suspend fun runProcess( val result = verifierSiop.validateAuthnResponse(authnResponse.url) result.shouldBeInstanceOf() - return result.document + return result.documents.first() } diff --git a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWalletScopeSupportTest.kt b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWalletScopeSupportTest.kt index 67b87622..7b209640 100644 --- a/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWalletScopeSupportTest.kt +++ b/vck-openid/src/commonTest/kotlin/at/asitplus/wallet/lib/oidc/OidcSiopWalletScopeSupportTest.kt @@ -157,7 +157,7 @@ class OidcSiopWalletScopeSupportTest : FreeSpec({ val result = verifierSiop.validateAuthnResponse(authnResponse.url) result.shouldBeInstanceOf() - result.document.validItems.shouldNotBeEmpty() + result.documents.first().validItems.shouldNotBeEmpty() } } }) diff --git a/vck/src/androidMain/kotlin/at/asitplus/wallet/lib/agent/CryptoService.android.kt b/vck/src/androidMain/kotlin/at/asitplus/wallet/lib/agent/CryptoService.android.kt index 41962809..99e9f8c8 100644 --- a/vck/src/androidMain/kotlin/at/asitplus/wallet/lib/agent/CryptoService.android.kt +++ b/vck/src/androidMain/kotlin/at/asitplus/wallet/lib/agent/CryptoService.android.kt @@ -2,10 +2,15 @@ package at.asitplus.wallet.lib.agent import at.asitplus.KmmResult import at.asitplus.KmmResult.Companion.wrap +import at.asitplus.signum.indispensable.getJcaPublicKey import at.asitplus.signum.indispensable.josef.* +import at.asitplus.signum.supreme.HazardousMaterials +import at.asitplus.signum.supreme.hazmat.jcaPrivateKey +import java.security.Security import javax.crypto.Cipher +import javax.crypto.KeyAgreement import javax.crypto.Mac -import javax.crypto.spec.GCMParameterSpec +import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec actual open class PlatformCryptoShim actual constructor(actual val keyMaterial: KeyMaterial) { @@ -18,18 +23,13 @@ actual open class PlatformCryptoShim actual constructor(actual val keyMaterial: algorithm: JweEncryption ): KmmResult = runCatching { val jcaCiphertext = Cipher.getInstance(algorithm.jcaName).also { + it.init( + Cipher.ENCRYPT_MODE, + SecretKeySpec(key, algorithm.jcaKeySpecName), + IvParameterSpec(iv) + ) if (algorithm.isAuthenticatedEncryption) { - it.init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(key, algorithm.jcaKeySpecName), - GCMParameterSpec(algorithm.ivLengthBits, iv) - ) it.updateAAD(aad) - } else { - it.init( - Cipher.ENCRYPT_MODE, - SecretKeySpec(key, algorithm.jcaKeySpecName), - ) } }.doFinal(input) if (algorithm.isAuthenticatedEncryption) { @@ -41,7 +41,6 @@ actual open class PlatformCryptoShim actual constructor(actual val keyMaterial: } }.wrap() - actual open suspend fun decrypt( key: ByteArray, iv: ByteArray, @@ -50,34 +49,43 @@ actual open class PlatformCryptoShim actual constructor(actual val keyMaterial: authTag: ByteArray, algorithm: JweEncryption ): KmmResult = runCatching { + val wholeInput = input + if (algorithm.isAuthenticatedEncryption) authTag else byteArrayOf() Cipher.getInstance(algorithm.jcaName).also { + it.init( + Cipher.DECRYPT_MODE, + SecretKeySpec(key, algorithm.jcaKeySpecName), + IvParameterSpec(iv) + ) if (algorithm.isAuthenticatedEncryption) { - it.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(key, algorithm.jcaKeySpecName), - GCMParameterSpec(algorithm.ivLengthBits, iv) - ) it.updateAAD(aad) - } else { - it.init( - Cipher.DECRYPT_MODE, - SecretKeySpec(key, algorithm.jcaKeySpecName), - ) } - }.doFinal(input + authTag) + }.doFinal(wholeInput) }.wrap() actual open fun performKeyAgreement( ephemeralKey: EphemeralKeyHolder, recipientKey: JsonWebKey, algorithm: JweAlgorithm - ): KmmResult { - return KmmResult.success("sharedSecret-${algorithm.identifier}".encodeToByteArray()) - } + ): KmmResult = runCatching { + val jvmKey = recipientKey.toCryptoPublicKey().getOrThrow().getJcaPublicKey().getOrThrow() + KeyAgreement.getInstance(algorithm.jcaName).also { + @OptIn(HazardousMaterials::class) + it.init(ephemeralKey.key.jcaPrivateKey) + it.doPhase(jvmKey, true) + }.generateSecret() + }.wrap() - actual open fun performKeyAgreement(ephemeralKey: JsonWebKey, algorithm: JweAlgorithm): KmmResult { - return KmmResult.success("sharedSecret-${algorithm.identifier}".encodeToByteArray()) - } + actual open fun performKeyAgreement( + ephemeralKey: JsonWebKey, + algorithm: JweAlgorithm + ): KmmResult = runCatching { + val publicKey = ephemeralKey.toCryptoPublicKey().getOrThrow().getJcaPublicKey().getOrThrow() + KeyAgreement.getInstance(algorithm.jcaName).also { + @OptIn(HazardousMaterials::class) + it.init(keyMaterial.getUnderLyingSigner().jcaPrivateKey) + it.doPhase(publicKey, true) + }.generateSecret() + }.wrap() actual open fun hmac( key: ByteArray, diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Holder.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Holder.kt index 89d9b288..277c3c4b 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Holder.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Holder.kt @@ -1,15 +1,11 @@ package at.asitplus.wallet.lib.agent import at.asitplus.KmmResult +import at.asitplus.dif.* import at.asitplus.jsonpath.core.NodeList import at.asitplus.jsonpath.core.NormalizedJsonPath import at.asitplus.wallet.lib.data.ConstantIndex import at.asitplus.wallet.lib.data.VerifiablePresentation -import at.asitplus.dif.ConstraintField -import at.asitplus.dif.FormatHolder -import at.asitplus.dif.InputDescriptor -import at.asitplus.dif.PresentationDefinition -import at.asitplus.dif.PresentationSubmission import at.asitplus.wallet.lib.iso.IssuerSigned import kotlinx.serialization.Serializable @@ -189,9 +185,9 @@ interface Holder { data class SdJwt(val sdJwt: String) : CreatePresentationResult() /** - * [document] contains a valid ISO 18013 [Document] with [IssuerSigned] and [DeviceSigned] structures + * [deviceResponse] contains a valid ISO 18013 [DeviceResponse] with [Document] and [DeviceSigned] structures */ - data class Document(val document: at.asitplus.wallet.lib.iso.Document) : + data class DeviceResponse(val deviceResponse: at.asitplus.wallet.lib.iso.DeviceResponse) : CreatePresentationResult() } 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 df166014..d43a40d8 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 @@ -240,13 +240,32 @@ class Validator( ) } + /** + * Validates an ISO device response, equivalent of a Verifiable Presentation + */ + @Throws(IllegalArgumentException::class) + fun verifyDeviceResponse(deviceResponse: DeviceResponse, challenge: String): Verifier.VerifyPresentationResult { + if (deviceResponse.status != 0U) { + throw IllegalArgumentException("status") + .also { Napier.w("Status invalid: ${deviceResponse.status}") } + } + if (deviceResponse.documents == null) { + throw IllegalArgumentException("documents") + .also { Napier.w("No documents: $deviceResponse") } + } + return Verifier.VerifyPresentationResult.SuccessIso( + documents = deviceResponse.documents.map { verifyDocument(it, challenge) } + ) + } + /** * Validates an ISO document, equivalent of a Verifiable Presentation */ - fun verifyDocument(doc: Document, challenge: String): Verifier.VerifyPresentationResult { + @Throws(IllegalArgumentException::class) + fun verifyDocument(doc: Document, challenge: String): IsoDocumentParsed { val docSerialized = doc.serialize().encodeToString(Base16(strict = true)) if (doc.errors != null) { - return Verifier.VerifyPresentationResult.InvalidStructure(docSerialized) + throw IllegalArgumentException("errors") .also { Napier.w("Document has errors: ${doc.errors}") } } val issuerSigned = doc.issuerSigned @@ -254,36 +273,36 @@ class Validator( val issuerKey = issuerAuth.unprotectedHeader?.certificateChain?.let { X509Certificate.decodeFromDerOrNull(it)?.publicKey?.toCoseKey()?.getOrNull() - } ?: return Verifier.VerifyPresentationResult.InvalidStructure(docSerialized) + } ?: throw IllegalArgumentException("issuerKey") .also { Napier.w("Got no issuer key in $issuerAuth") } if (verifierCoseService.verifyCose(issuerAuth, issuerKey).isFailure) { - return Verifier.VerifyPresentationResult.InvalidStructure(docSerialized) + throw IllegalArgumentException("issuerAuth") .also { Napier.w("IssuerAuth not verified: $issuerAuth") } } val mso = issuerSigned.getIssuerAuthPayloadAsMso().getOrNull() - ?: return Verifier.VerifyPresentationResult.InvalidStructure(docSerialized) + ?: throw IllegalArgumentException("mso") .also { Napier.w("MSO is null: ${issuerAuth.payload?.encodeToString(Base16(strict = true))}") } if (mso.docType != doc.docType) { - return Verifier.VerifyPresentationResult.InvalidStructure(docSerialized) + throw IllegalArgumentException("mso.docType") .also { Napier.w("Invalid MSO docType '${mso.docType}' does not match Doc docType '${doc.docType}") } } val walletKey = mso.deviceKeyInfo.deviceKey val deviceSignature = doc.deviceSigned.deviceAuth.deviceSignature - ?: return Verifier.VerifyPresentationResult.InvalidStructure(docSerialized) + ?: throw IllegalArgumentException("deviceSignature") .also { Napier.w("DeviceSignature is null: ${doc.deviceSigned.deviceAuth}") } if (verifierCoseService.verifyCose(deviceSignature, walletKey).isFailure) { - return Verifier.VerifyPresentationResult.InvalidStructure(docSerialized) + throw IllegalArgumentException("deviceSignature") .also { Napier.w("DeviceSignature not verified") } } val deviceSignaturePayload = deviceSignature.payload - ?: return Verifier.VerifyPresentationResult.InvalidStructure(docSerialized) + ?: throw IllegalArgumentException("challenge") .also { Napier.w("DeviceSignature does not contain challenge") } if (!deviceSignaturePayload.contentEquals(challenge.encodeToByteArray())) { - return Verifier.VerifyPresentationResult.InvalidStructure(docSerialized) + throw IllegalArgumentException("challenge") .also { Napier.w("DeviceSignature does not contain correct challenge") } } @@ -298,9 +317,7 @@ class Validator( } } } - return Verifier.VerifyPresentationResult.SuccessIso( - IsoDocumentParsed(mso = mso, validItems = validItems, invalidItems = invalidItems) - ) + return IsoDocumentParsed(mso = mso, validItems = validItems, invalidItems = invalidItems) } /** 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 1946a654..b214af4e 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 @@ -55,7 +55,7 @@ class VerifiablePresentationFactory( challenge: String, credential: SubjectCredentialStore.StoreEntry.Iso, requestedClaims: Collection - ): Holder.CreatePresentationResult.Document { + ): Holder.CreatePresentationResult.DeviceResponse { val deviceSignature = coseService.createSignedCose( payload = challenge.encodeToByteArray(), addKeyId = false ).getOrElse { @@ -99,20 +99,25 @@ class VerifiablePresentationFactory( ?: throw PresentationException("Attribute not available in credential: $['$namespace']['$attributeName']") } } - - return Holder.CreatePresentationResult.Document( - Document( - docType = credential.scheme.isoDocType!!, - issuerSigned = IssuerSigned.fromIssuerSignedItems( - namespacedItems = disclosedItems, - issuerAuth = credential.issuerSigned.issuerAuth - ), - deviceSigned = DeviceSigned( - namespaces = ByteStringWrapper(DeviceNameSpaces(mapOf())), - deviceAuth = DeviceAuth( - deviceSignature = deviceSignature + return Holder.CreatePresentationResult.DeviceResponse( + DeviceResponse( + version = "1.0", + documents = arrayOf( + Document( + docType = credential.scheme.isoDocType!!, + issuerSigned = IssuerSigned.fromIssuerSignedItems( + namespacedItems = disclosedItems, + issuerAuth = credential.issuerSigned.issuerAuth + ), + deviceSigned = DeviceSigned( + namespaces = ByteStringWrapper(DeviceNameSpaces(mapOf())), + deviceAuth = DeviceAuth( + deviceSignature = deviceSignature + ) + ) ) - ) + ), + status = 0U, ) ) } diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Verifier.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Verifier.kt index 692da584..6358b103 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Verifier.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/Verifier.kt @@ -4,11 +4,7 @@ import at.asitplus.signum.indispensable.CryptoPublicKey import at.asitplus.signum.indispensable.josef.JwsSigned import at.asitplus.signum.indispensable.josef.jwkId import at.asitplus.signum.indispensable.josef.toJsonWebKey -import at.asitplus.wallet.lib.data.IsoDocumentParsed -import at.asitplus.wallet.lib.data.SelectiveDisclosureItem -import at.asitplus.wallet.lib.data.VerifiableCredentialJws -import at.asitplus.wallet.lib.data.VerifiableCredentialSdJwt -import at.asitplus.wallet.lib.data.VerifiablePresentationParsed +import at.asitplus.wallet.lib.data.* import at.asitplus.wallet.lib.iso.IssuerSigned @@ -53,7 +49,7 @@ interface Verifier { val isRevoked: Boolean ) : VerifyPresentationResult() - data class SuccessIso(val document: IsoDocumentParsed) : VerifyPresentationResult() + data class SuccessIso(val documents: Collection) : VerifyPresentationResult() data class InvalidStructure(val input: String) : VerifyPresentationResult() data class NotVerified(val input: String, val challenge: String) : VerifyPresentationResult() } diff --git a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifierAgent.kt b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifierAgent.kt index 3c224fb8..85ac66f7 100644 --- a/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifierAgent.kt +++ b/vck/src/commonMain/kotlin/at/asitplus/wallet/lib/agent/VerifierAgent.kt @@ -1,8 +1,10 @@ package at.asitplus.wallet.lib.agent +import at.asitplus.signum.indispensable.io.Base64UrlStrict import at.asitplus.signum.indispensable.josef.JwsSigned import at.asitplus.wallet.lib.data.AtomicAttribute2023 import at.asitplus.wallet.lib.data.VerifiablePresentationParsed +import at.asitplus.wallet.lib.iso.DeviceResponse import at.asitplus.wallet.lib.iso.Document import at.asitplus.wallet.lib.jws.SdJwtSigned import io.github.aakira.napier.Napier @@ -35,21 +37,36 @@ class VerifierAgent private constructor( /** * Verifies a presentation of some credentials that a holder issued with that [challenge] we sent before. */ - override fun verifyPresentation(it: String, challenge: String): Verifier.VerifyPresentationResult { - val sdJwtSigned = runCatching { SdJwtSigned.parse(it) }.getOrNull() + override fun verifyPresentation(input: String, challenge: String): Verifier.VerifyPresentationResult { + val sdJwtSigned = runCatching { SdJwtSigned.parse(input) }.getOrNull() if (sdJwtSigned != null) { - return validator.verifyVpSdJwt(it, challenge, keyMaterial.publicKey) + return validator.verifyVpSdJwt(input, challenge, keyMaterial.publicKey) } - val jwsSigned = JwsSigned.deserialize(it).getOrNull() + val jwsSigned = JwsSigned.deserialize(input).getOrNull() if (jwsSigned != null) { - return validator.verifyVpJws(it, challenge, keyMaterial.publicKey) + return validator.verifyVpJws(input, challenge, keyMaterial.publicKey) } - val document = it.decodeToByteArrayOrNull(Base16(strict = true)) + val document = input.decodeToByteArrayOrNull(Base16(false)) ?.let { bytes -> Document.deserialize(bytes).getOrNull() } if (document != null) { - return validator.verifyDocument(document, challenge) + val verifiedDocument = runCatching { + validator.verifyDocument(document, challenge) + }.getOrElse { + return Verifier.VerifyPresentationResult.InvalidStructure(input) + } + return Verifier.VerifyPresentationResult.SuccessIso(listOf(verifiedDocument)) } - return Verifier.VerifyPresentationResult.InvalidStructure(it) + val deviceResponse = input.decodeToByteArrayOrNull(Base64UrlStrict) + ?.let { bytes -> DeviceResponse.deserialize(bytes).getOrNull() } + if (deviceResponse != null) { + val result = runCatching { + validator.verifyDeviceResponse(deviceResponse, challenge) + }.getOrElse { + return Verifier.VerifyPresentationResult.InvalidStructure(input) + } + return result + } + return Verifier.VerifyPresentationResult.InvalidStructure(input) .also { Napier.w("Could not verify presentation, unknown format: $it") } }