diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt index 8ae750b1..bd1cfc90 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt @@ -362,8 +362,8 @@ fun beans(clock: Clock) = beans { // CNonce // bean { DefaultGenerateCNonce(clock = clock, expiresIn = Duration.ofMinutes(5L)) } - bean { EncryptCNonceWithNimbus(issuerPublicUrl, ref("cnonce-encryption-key")) } - bean { DecryptCNonceWithNimbus(issuerPublicUrl, ref("cnonce-encryption-key")) } + bean { EncryptCNonceWithNimbus(issuerPublicUrl, ref(), ref("cnonce-encryption-key")) } + bean { DecryptCNonceWithNimbus(issuerPublicUrl, ref("cnonce-encryption-key"), ref()) } // // Credentials diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/DecryptCNonceWithNimbus.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/DecryptCNonceWithNimbus.kt index 4c63e924..b5748b06 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/DecryptCNonceWithNimbus.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/DecryptCNonceWithNimbus.kt @@ -20,16 +20,17 @@ import com.nimbusds.jose.EncryptionMethod import com.nimbusds.jose.JOSEObjectType import com.nimbusds.jose.JWEAlgorithm import com.nimbusds.jose.crypto.factories.DefaultJWEDecrypterFactory +import com.nimbusds.jose.crypto.factories.DefaultJWSVerifierFactory import com.nimbusds.jose.jwk.JWKSet import com.nimbusds.jose.jwk.RSAKey import com.nimbusds.jose.jwk.source.ImmutableJWKSet -import com.nimbusds.jose.proc.DefaultJOSEObjectTypeVerifier -import com.nimbusds.jose.proc.JWEDecryptionKeySelector -import com.nimbusds.jose.proc.SecurityContext +import com.nimbusds.jose.proc.* import com.nimbusds.jwt.EncryptedJWT import com.nimbusds.jwt.JWTClaimsSet import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier import com.nimbusds.jwt.proc.DefaultJWTProcessor +import eu.europa.ec.eudi.pidissuer.adapter.out.IssuerSigningKey +import eu.europa.ec.eudi.pidissuer.adapter.out.signingAlgorithm import eu.europa.ec.eudi.pidissuer.domain.CNonce import eu.europa.ec.eudi.pidissuer.domain.CredentialIssuerId import eu.europa.ec.eudi.pidissuer.port.out.jose.DecryptCNonce @@ -42,6 +43,7 @@ import java.time.Duration internal class DecryptCNonceWithNimbus( private val issuer: CredentialIssuerId, private val decryptionKey: RSAKey, + private val signingKey: IssuerSigningKey, ) : DecryptCNonce { init { require(decryptionKey.isPrivate) { "a private key is required for decryption" } @@ -49,16 +51,28 @@ internal class DecryptCNonceWithNimbus( private val processor = DefaultJWTProcessor() .apply { - jweTypeVerifier = DefaultJOSEObjectTypeVerifier(JOSEObjectType("cnonce+jwt")) + val jcaProvider = BouncyCastleProvider() + val typeVerifier = DefaultJOSEObjectTypeVerifier(JOSEObjectType("cnonce+jwt")) + + jweTypeVerifier = typeVerifier jweKeySelector = JWEDecryptionKeySelector( JWEAlgorithm.RSA_OAEP_512, EncryptionMethod.XC20P, ImmutableJWKSet(JWKSet(decryptionKey)), ) - jweDecrypterFactory = DefaultJWEDecrypterFactory() - .apply { - jcaContext.provider = BouncyCastleProvider() - } + jweDecrypterFactory = DefaultJWEDecrypterFactory().apply { + jcaContext.provider = jcaProvider + } + + jwsTypeVerifier = typeVerifier + jwsKeySelector = JWSVerificationKeySelector( + signingKey.signingAlgorithm, + ImmutableJWKSet(JWKSet(signingKey.key)), + ) + jwsVerifierFactory = DefaultJWSVerifierFactory().apply { + jcaContext.provider = jcaProvider + } + jwtClaimsSetVerifier = DefaultJWTClaimsVerifier( issuer.externalForm, JWTClaimsSet.Builder() diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/EncryptCNonceWithNimbus.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/EncryptCNonceWithNimbus.kt index ad945af0..cb39312c 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/EncryptCNonceWithNimbus.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/EncryptCNonceWithNimbus.kt @@ -19,10 +19,17 @@ import com.nimbusds.jose.EncryptionMethod import com.nimbusds.jose.JOSEObjectType import com.nimbusds.jose.JWEAlgorithm import com.nimbusds.jose.JWEHeader +import com.nimbusds.jose.JWEObject +import com.nimbusds.jose.JWSHeader +import com.nimbusds.jose.Payload import com.nimbusds.jose.crypto.RSAEncrypter +import com.nimbusds.jose.crypto.factories.DefaultJWSSignerFactory import com.nimbusds.jose.jwk.RSAKey import com.nimbusds.jwt.EncryptedJWT import com.nimbusds.jwt.JWTClaimsSet +import com.nimbusds.jwt.SignedJWT +import eu.europa.ec.eudi.pidissuer.adapter.out.IssuerSigningKey +import eu.europa.ec.eudi.pidissuer.adapter.out.signingAlgorithm import eu.europa.ec.eudi.pidissuer.domain.CNonce import eu.europa.ec.eudi.pidissuer.domain.CredentialIssuerId import eu.europa.ec.eudi.pidissuer.port.out.jose.EncryptCNonce @@ -34,17 +41,20 @@ import java.util.* */ internal class EncryptCNonceWithNimbus( private val issuer: CredentialIssuerId, + private val signingKey: IssuerSigningKey, encryptionKey: RSAKey, ) : EncryptCNonce { - private val encrypter = RSAEncrypter(encryptionKey) - .apply { - jcaContext.provider = BouncyCastleProvider() - } + private val jcaProvider = BouncyCastleProvider() + private val signer = run { + val factory = DefaultJWSSignerFactory().apply { jcaContext.provider = jcaProvider } + factory.createJWSSigner(signingKey.key, signingKey.signingAlgorithm) + } + private val encrypter = RSAEncrypter(encryptionKey).apply { jcaContext.provider = jcaProvider } - override suspend fun invoke(cnonce: CNonce): String = - EncryptedJWT( - JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_512, EncryptionMethod.XC20P) + override suspend fun invoke(cnonce: CNonce): String { + val signedJwt = SignedJWT( + JWSHeader.Builder(signingKey.signingAlgorithm) .type(JOSEObjectType("cnonce+jwt")) .build(), JWTClaimsSet.Builder() @@ -57,7 +67,20 @@ internal class EncryptCNonceWithNimbus( expirationTime(Date.from((cnonce.activatedAt + cnonce.expiresIn))) } .build(), + ).apply { + sign(signer) + } + + val encryptedJwt = JWEObject( + JWEHeader.Builder(JWEAlgorithm.RSA_OAEP_512, EncryptionMethod.XC20P) + .type(JOSEObjectType("cnonce+jwt")) + .contentType("JWT") + .build(), + Payload(signedJwt), ).apply { encrypt(encrypter) - }.serialize() + } + + return encryptedJwt.serialize() + } } diff --git a/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProofTest.kt b/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProofTest.kt index 0b1c5f64..05e5dcdc 100644 --- a/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProofTest.kt +++ b/src/test/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/ValidateJwtProofTest.kt @@ -24,13 +24,19 @@ import com.nimbusds.jose.JOSEObjectType import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.JWSHeader import com.nimbusds.jose.crypto.RSASSASigner +import com.nimbusds.jose.jwk.Curve +import com.nimbusds.jose.jwk.ECKey import com.nimbusds.jose.jwk.RSAKey +import com.nimbusds.jose.jwk.gen.ECKeyGenerator import com.nimbusds.jose.jwk.gen.RSAKeyGenerator import com.nimbusds.jose.util.Base64 import com.nimbusds.jose.util.Base64URL import com.nimbusds.jose.util.X509CertChainUtils import com.nimbusds.jwt.JWTClaimsSet import com.nimbusds.jwt.SignedJWT +import com.nimbusds.oauth2.sdk.id.Issuer +import com.nimbusds.oauth2.sdk.util.X509CertificateUtils +import eu.europa.ec.eudi.pidissuer.adapter.out.IssuerSigningKey import eu.europa.ec.eudi.pidissuer.adapter.out.mdl.MobileDrivingLicenceV1 import eu.europa.ec.eudi.pidissuer.domain.* import eu.europa.ec.eudi.pidissuer.loadResource @@ -41,6 +47,7 @@ import java.security.cert.X509Certificate import java.time.Clock import java.util.* import kotlin.test.* +import kotlin.time.Duration.Companion.days import kotlin.time.Duration.Companion.minutes import kotlin.time.toJavaDuration @@ -48,9 +55,22 @@ internal class ValidateJwtProofTest { private val issuer = CredentialIssuerId.unsafe("https://eudi.ec.europa.eu/issuer") private val clock = Clock.systemDefaultZone() - private val key = RSAKeyGenerator(4096, false).generate() - private val encryptCNonce = EncryptCNonceWithNimbus(issuer, key) - private val decryptCNonce = DecryptCNonceWithNimbus(issuer, key) + private val signingKey = run { + val key = ECKeyGenerator(Curve.P_256).keyID("signing-key-0").generate() + val issuedAt = clock.instant() + val expiresAt = issuedAt + 365.days.toJavaDuration() + val x509 = X509CertificateUtils.generateSelfSigned( + Issuer(issuer.externalForm), + Date.from(issuedAt), + Date.from(expiresAt), + key.toECPublicKey(), + key.toECPrivateKey(), + ) + IssuerSigningKey(ECKey.Builder(key).x509CertChain(listOf(Base64.encode(x509.encoded))).build()) + } + private val encryptionKey = RSAKeyGenerator(4096, false).generate() + private val encryptCNonce = EncryptCNonceWithNimbus(issuer, signingKey, encryptionKey) + private val decryptCNonce = DecryptCNonceWithNimbus(issuer, encryptionKey, signingKey) private val validateJwtProof = ValidateJwtProof(issuer, decryptCNonce) private val credentialConfiguration = MobileDrivingLicenceV1.copy( proofTypesSupported = ProofTypesSupported(