diff --git a/README.md b/README.md index 7e92e27..48eb72b 100644 --- a/README.md +++ b/README.md @@ -212,28 +212,53 @@ Description: URI to use when generating Credential Offers. Default value: `openid-credential-offer://` Variable: `ISSUER_SIGNING_KEY` -Description: Whether to generate a new, or use an existing key-pair for signing. +Description: Whether to generate a new, or use an existing key-pair for signing verifiable credentials. Possible values: `GenerateRandom`, `LoadFromKeystore` Default value: `GenerateRandom` Variable: `ISSUER_SIGNING_KEY_KEYSTORE` -Description: Location of the keystore from which to load the key-pair for signing. Uses Spring Resource URL syntax. +Description: Location of the keystore from which to load the key-pair for signing verifiable credentials. Uses Spring Resource URL syntax. Default value: N/A Variable: `ISSUER_SIGNING_KEY_KEYSTORE_TYPE` -Description: Type of the keystore from which to load the key-pair for signing. +Description: Type of the keystore from which to load the key-pair for signing verifiable credentials. Default value: N/A Variable: `ISSUER_SIGNING_KEY_KEYSTORE_PASSWORD` -Description: Password of the keystore from which to load the key-pair for signing. +Description: Password of the keystore from which to load the key-pair for signing verifiable credentials. Default value: N/A Variable: `ISSUER_SIGNING_KEY_ALIAS` -Description: Alias of the key-pair for signing. +Description: Alias of the key-pair for signing verifiable credentials. Default value: N/A Variable: `ISSUER_SIGNING_KEY_PASSWORD` -Description: Password of the key-pair for signing. +Description: Password of the key-pair for signing verifiable credentials. +Default value: N/A + +Variable: `ISSUER_METADATA_SIGNING_KEY` +Description: Whether to generate a new, or use an existing key-pair for signing metadata. +Possible values: `GenerateRandom`, `LoadFromKeystore` +Default value: `GenerateRandom` + +Variable: `ISSUER_METADATA_SIGNING_KEY_KEYSTORE` +Description: Location of the keystore from which to load the key-pair for signing metadata. Uses Spring Resource URL syntax. +Default value: N/A + +Variable: `ISSUER_METADATA_SIGNING_KEY_KEYSTORE_TYPE` +Description: Type of the keystore from which to load the key-pair for signing metadata. +Default value: N/A + +Variable: `ISSUER_METADATA_SIGNING_KEY_KEYSTORE_PASSWORD` +Description: Password of the keystore from which to load the key-pair for signing metadata. +Default value: N/A + +Variable: `ISSUER_METADATA_SIGNING_KEY_ALIAS` +Description: Alias of the key-pair for signing metadata. +Default value: N/A + +Variable: `ISSUER_METADATA_SIGNING_KEY_PASSWORD` +Description: Password of the key-pair for signing metadata. Default value: N/A Variable: `ISSUER_KEYCLOAK_SERVER_URL` 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 6be350c..b006792 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt @@ -233,6 +233,26 @@ fun beans(clock: Clock) = beans { encryptionKey } + // + // Signed metadata signing key + // + bean(name = "metadata-signing-key") { + when (env.getProperty("issuer.metadata.signing-key")) { + null, KeyOption.GenerateRandom -> { + log.info("Generating random signing key for metadata") + ECKeyGenerator(Curve.P_256) + .keyID("issuer-kid-1") + .keyUse(KeyUse.SIGNATURE) + .generate() + } + + KeyOption.LoadFromKeystore -> { + log.info("Loading signing key and certificate for metadata from keystore") + loadJwkFromKeystore(env, "issuer.metadata.signing-key") + } + } + } + // // Adapters (out ports) // @@ -349,7 +369,7 @@ fun beans(clock: Clock) = beans { GenerateSignedMetadataWithNimbus( clock = ref(), credentialIssuerId = ref().id, - signingKey = ref(), + signingKey = ref(name = "metadata-signing-key"), ) } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/GenerateSignedMetadataWithNimbus.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/GenerateSignedMetadataWithNimbus.kt index 7ede211..5970dfd 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/GenerateSignedMetadataWithNimbus.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/jose/GenerateSignedMetadataWithNimbus.kt @@ -15,18 +15,17 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.out.jose -import com.nimbusds.jose.JWSHeader -import com.nimbusds.jose.JWSObject -import com.nimbusds.jose.JWSSigner -import com.nimbusds.jose.Payload +import com.nimbusds.jose.* import com.nimbusds.jose.crypto.ECDSASigner +import com.nimbusds.jose.crypto.Ed25519Signer +import com.nimbusds.jose.crypto.RSASSASigner +import com.nimbusds.jose.jwk.* import com.nimbusds.jose.util.JSONObjectUtils -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.CredentialIssuerId import eu.europa.ec.eudi.pidissuer.port.out.jose.GenerateSignedMetadata import kotlinx.serialization.encodeToString import kotlinx.serialization.json.* +import org.bouncycastle.jce.provider.BouncyCastleProvider import java.time.Clock /** @@ -35,8 +34,12 @@ import java.time.Clock internal class GenerateSignedMetadataWithNimbus( private val clock: Clock, private val credentialIssuerId: CredentialIssuerId, - private val signingKey: IssuerSigningKey, + private val signingKey: JWK, ) : GenerateSignedMetadata { + init { + require(signingKey is AsymmetricJWK) { "only asymmetric keys are supported" } + require(signingKey.isPrivate) { "a private key is required for signing metadata" } + } override fun invoke(metadata: JsonObject): String { val payload = (metadata - "signed_metadata").buildUpon { @@ -60,12 +63,39 @@ private fun Map.buildUpon(builder: JsonObjectBuilder.() -> private fun JsonObject.toPayload(): Payload = Payload(JSONObjectUtils.parse(Json.encodeToString(this))) -private val IssuerSigningKey.jwsHeader: JWSHeader +private val JWK.signingAlgorithm: JWSAlgorithm + get() = when (this) { + is ECKey -> when (curve) { + Curve.P_256 -> JWSAlgorithm.ES256 + Curve.P_256K, Curve.SECP256K1 -> JWSAlgorithm.ES256K + Curve.P_384 -> JWSAlgorithm.ES384 + Curve.P_521 -> JWSAlgorithm.ES512 + else -> error("unsupported curve '$curve' for ECKey") + } + is RSAKey -> JWSAlgorithm.RS256 + is OctetKeyPair -> when (curve) { + Curve.Ed25519 -> JWSAlgorithm.Ed25519 + else -> error("unsupported curve '$curve' for OctetKeyPair") + } + else -> error("unsupported key type '$javaClass'") + } + +private val JWK.jwsHeader: JWSHeader get() = JWSHeader.Builder(signingAlgorithm) - .jwk(key.toPublicJWK()) - .keyID(key.keyID) - .x509CertChain(key.x509CertChain) + .jwk(toPublicJWK()) + .keyID(keyID) + .x509CertChain(x509CertChain) .build() -private val IssuerSigningKey.jwsSigner: JWSSigner - get() = ECDSASigner(key) +private val JWK.jwsSigner: JWSSigner + get() = when (this) { + is ECKey -> ECDSASigner(this) + .apply { + if (Curve.P_256K == curve || Curve.SECP256K1 == curve) { + jcaContext.provider = BouncyCastleProvider() + } + } + is RSAKey -> RSASSASigner(this) + is OctetKeyPair -> Ed25519Signer(this) + else -> error("unsupported key type '$javaClass'") + } diff --git a/src/main/resources/application-keystore.properties b/src/main/resources/application-keystore.properties index a392b30..af61441 100644 --- a/src/main/resources/application-keystore.properties +++ b/src/main/resources/application-keystore.properties @@ -7,3 +7,9 @@ issuer.signing-key.keystore.type=JKS issuer.signing-key.keystore.password=sd-jwt issuer.signing-key.alias=sd-jwt-issuance issuer.signing-key.password=sd-jwt-issuance +issuer.metadata.signing-key=LoadFromKeystore +issuer.metadata.signing-key.keystore=classpath:sd-jwt.jks +issuer.metadata.signing-key.keystore.type=JKS +issuer.metadata.signing-key.keystore.password=sd-jwt +issuer.metadata.signing-key.alias=sd-jwt-issuance +issuer.metadata.signing-key.password=sd-jwt-issuance diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8e1a1ae..79b2c5b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -44,6 +44,7 @@ issuer.dpop.nonce.enabled=false issuer.dpop.nonce.expiration=PT5M issuer.credentialEndpoint.batchIssuance.enabled=true issuer.credentialEndpoint.batchIssuance.batchSize=10 +issuer.metadata.signing-key=GenerateRandom issuer.metadata.display[0].name=Digital Credentials Issuer issuer.metadata.display[0].locale=en issuer.metadata.display[0].logo.uri=https://eudiw.dev/ic-logo.svg