diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/KeyAttestationJWT.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/KeyAttestationJWT.kt index 2a48bece..8c481aad 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/KeyAttestationJWT.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/domain/KeyAttestationJWT.kt @@ -27,7 +27,7 @@ import com.nimbusds.jose.jwk.JWK import com.nimbusds.jose.jwk.RSAKey import com.nimbusds.jwt.SignedJWT import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.first +import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch data class KeyAttestationJWT private constructor( @@ -131,4 +131,4 @@ internal suspend fun signatureVerifiedByKey(vararg tasks: suspend () -> JWK?): J tasks.forEach { launch { send(it()) } } - }.first { it != null } + }.firstOrNull { it != null } diff --git a/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/WalletApiTest.kt b/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/WalletApiTest.kt index c175704e..d765631e 100644 --- a/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/WalletApiTest.kt +++ b/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/WalletApiTest.kt @@ -825,6 +825,39 @@ internal class WalletApiEncryptionOptionalKeyAttestationsRequiredTest : BaseWall assertEquals("Invalid proof JWT: Key Attestation 'nonce' does not match JWT Proof 'nonce'", response.errorDescription) } + @Test + internal fun `issuance with jwt proof that contains key attestation fails when proof is signed with non-attested key`() = + runTest { + val authentication = dPoPTokenAuthentication(clock = clock) + val cNonce = generateNonce(clock.now(), 5L.minutes) + val nonAttestedProofKey = ECKeyGenerator(Curve.P_256).generate() + val attestedKey = ECKeyGenerator(Curve.P_256).generate() + val keyAttestationJwt = keyAttestationJWT(proofSigningKey = attestedKey) + + val proofs = jwtProof(credentialIssuerMetadata.id, clock, cNonce, nonAttestedProofKey) { + customParam("key_attestation", keyAttestationJwt.serialize()) + }.toJwtProofs() + + val response = client() + .mutateWith(mockAuthentication(authentication)) + .post() + .uri(WalletApi.CREDENTIAL_ENDPOINT) + .contentType(MediaType.APPLICATION_JSON) + .bodyValue(requestByCredentialIdentifier(proofs)) + .accept(MediaType.APPLICATION_JSON) + .exchange() + .expectStatus().isBadRequest() + .expectBody() + .returnResult() + .let { assertNotNull(it.responseBody) } + + assertEquals(CredentialErrorTypeTo.INVALID_PROOF, response.type) + assertEquals( + "Invalid proof JWT: Key attestation does not contain a key that verifies the jwt proof signature", + response.errorDescription, + ) + } + @Test fun `issuance with attestation proof (without 'exp' claim) is successful`() = runTest { val authentication = dPoPTokenAuthentication(clock = clock) @@ -1527,7 +1560,7 @@ private suspend fun keyAttestationJWT( clock: Clock = Clock.System, expiresAt: Instant = clock.now() + 1.days, includeExpiresAt: Boolean = true, - extraKeys: () -> List, + extraKeys: () -> List = { emptyList() }, ): SignedJWT { val keyAttestationSigningKey = loadECKey("key-attestation-key.pem") val signer = ECDSASigner(keyAttestationSigningKey)