From f56c54c4c6959f0f5bddc075f9eae7ccf0c58006 Mon Sep 17 00:00:00 2001 From: Christian Kollmann Date: Tue, 29 Oct 2024 10:53:34 +0100 Subject: [PATCH] Verify JWS validation with out-of-band keys --- CHANGELOG.md | 1 + .../asitplus/wallet/lib/agent/IssuerAgent.kt | 1 + .../wallet/lib/agent/VerifierAgent.kt | 9 ++++++--- .../asitplus/wallet/lib/jws/JwsServiceTest.kt | 20 +++++++++++++++++++ 4 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7ebafc6..5481d06c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,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) + - Add `publicKeyLookup` function to `DefaultVerifierJwsService` to provide valid keys for JWS objects out-of-band (e.g. when they're not included in the header of the JWS) - OID4VCI: - `WalletService` supports building multiple authorization details to request a token for more than one credential - Remove `buildAuthorizationDetails(RequestOptions)` for `WalletService`, please migrate to `buildScope(RequestOptions)` 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 9aec2c91..fbee17b3 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 @@ -52,6 +52,7 @@ class IssuerAgent( constructor( keyMaterial: KeyMaterial = EphemeralKeyWithoutCert(), issuerCredentialStore: IssuerCredentialStore = InMemoryIssuerCredentialStore(), + validator: Validator = Validator(), ) : this( validator = Validator(), issuerCredentialStore = issuerCredentialStore, 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 85ac66f7..220191a6 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 @@ -20,12 +20,15 @@ class VerifierAgent private constructor( override val keyMaterial: KeyMaterial, ) : Verifier { - constructor(keyPairAdapter: KeyMaterial) : this( - validator = Validator(), + constructor( + keyPairAdapter: KeyMaterial, + validator: Validator = Validator() + ) : this( + validator = validator, keyMaterial = keyPairAdapter, ) - constructor(): this( + constructor() : this( validator = Validator(), keyMaterial = EphemeralKeyWithoutCert(), ) diff --git a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/jws/JwsServiceTest.kt b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/jws/JwsServiceTest.kt index ca7e6ce9..c9010bf8 100644 --- a/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/jws/JwsServiceTest.kt +++ b/vck/src/commonTest/kotlin/at/asitplus/wallet/lib/jws/JwsServiceTest.kt @@ -101,6 +101,26 @@ class JwsServiceTest : FreeSpec({ verifierJwsService.verifyJwsObject(signed) shouldBe false } + "signed object without public key in header can not be verified" { + val payload = randomPayload.encodeToByteArray() + val header = JwsHeader(algorithm = JwsAlgorithm.ES256) + val signed = jwsService.createSignedJws(header, payload).getOrThrow() + + verifierJwsService = DefaultVerifierJwsService() + verifierJwsService.verifyJwsObject(signed) shouldBe false + } + + "signed object without public key in header, but retrieved out-of-band can be verified" { + val payload = randomPayload.encodeToByteArray() + val header = JwsHeader(algorithm = JwsAlgorithm.ES256) + val signed = jwsService.createSignedJws(header, payload).getOrThrow() + val validKey = cryptoService.keyMaterial.jsonWebKey + + val publicKeyLookup: PublicKeyLookup = { setOf(validKey) } + verifierJwsService = DefaultVerifierJwsService(publicKeyLookup = publicKeyLookup) + verifierJwsService.verifyJwsObject(signed) shouldBe true + } + "encrypted object can be decrypted" { val stringPayload = vckJsonSerializer.encodeToString(randomPayload) val encrypted = jwsService.encryptJweObject(