From e0586107e893baa12f437482c4b5c3972ae03baf Mon Sep 17 00:00:00 2001 From: Dimitris Zarras <138439389+dzarras@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:51:47 +0200 Subject: [PATCH] Include the 'x5c' header claim in issued SD-JWT-VCs only when the configured Signing Key has a Certificate that contains the Issuer as a SAN DNS name or SAN URI. (#252) --- .../pidissuer/adapter/out/IssuerSigningKey.kt | 5 +++++ .../adapter/out/pid/EncodePidInSdJwtVc.kt | 22 ++++++++++++++++++- 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/IssuerSigningKey.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/IssuerSigningKey.kt index 39c3db2c..31b1f19c 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/IssuerSigningKey.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/IssuerSigningKey.kt @@ -20,8 +20,10 @@ import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.jwk.Curve import com.nimbusds.jose.jwk.ECKey import com.nimbusds.jose.util.X509CertChainUtils +import com.nimbusds.jose.util.X509CertUtils import id.walt.mdoc.COSECryptoProviderKeyInfo import id.walt.mdoc.SimpleCOSECryptoProvider +import java.security.cert.X509Certificate @JvmInline value class IssuerSigningKey(val key: ECKey) { @@ -62,3 +64,6 @@ internal fun IssuerSigningKey.cryptoProvider(): SimpleCOSECryptoProvider { ), ) } + +internal val IssuerSigningKey.certificate: X509Certificate + get() = X509CertUtils.parse(key.x509CertChain.first().decode()) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt index 54feda89..8565ffbc 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/EncodePidInSdJwtVc.kt @@ -23,6 +23,7 @@ import com.nimbusds.jose.jwk.ECKey import com.nimbusds.jose.jwk.JWK import com.nimbusds.jwt.SignedJWT import eu.europa.ec.eudi.pidissuer.adapter.out.IssuerSigningKey +import eu.europa.ec.eudi.pidissuer.adapter.out.certificate import eu.europa.ec.eudi.pidissuer.adapter.out.oauth.* import eu.europa.ec.eudi.pidissuer.adapter.out.signingAlgorithm import eu.europa.ec.eudi.pidissuer.domain.CredentialIssuerId @@ -30,12 +31,16 @@ import eu.europa.ec.eudi.pidissuer.domain.SdJwtVcType import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError.Unexpected import eu.europa.ec.eudi.sdjwt.* +import eu.europa.ec.eudi.sdjwt.vc.sanOfDNSName +import eu.europa.ec.eudi.sdjwt.vc.sanOfUniformResourceIdentifier import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json import kotlinx.serialization.json.JsonElement import kotlinx.serialization.json.add import kotlinx.serialization.json.buildJsonArray import org.slf4j.LoggerFactory +import java.net.URL +import java.security.cert.X509Certificate import java.time.Clock import java.time.Instant import java.time.ZonedDateTime @@ -65,11 +70,20 @@ class EncodePidInSdJwtVc( // SD-JWT VC requires no decoys val sdJwtFactory = SdJwtFactory(hashAlgorithm = hashAlgorithm, fallbackMinimumDigests = null) val signer = ECDSASigner(issuerSigningKey.key) + val x509CertChain = run { + val certificate = issuerSigningKey.certificate + if (certificate.containsSanUri(credentialIssuerId.value) || certificate.containsSanDns(credentialIssuerId.value)) { + issuerSigningKey.key.x509CertChain + } else { + null + } + } + SdJwtIssuer.nimbus(sdJwtFactory, signer, issuerSigningKey.signingAlgorithm) { // TODO: This will change to dc+sd-jwt in a future release type(JOSEObjectType("vc+sd-jwt")) keyID(issuerSigningKey.key.keyID) - x509CertChain(issuerSigningKey.key.x509CertChain) + x509CertChain(x509CertChain) } } @@ -220,3 +234,9 @@ private object Printer { return str } } + +private fun X509Certificate.containsSanDns(url: URL): Boolean = + url.host in sanOfDNSName().getOrDefault(emptyList()) + +private fun X509Certificate.containsSanUri(url: URL): Boolean = + url.toExternalForm() in sanOfUniformResourceIdentifier().getOrDefault(emptyList())