diff --git a/docker-compose/haproxy/haproxy.conf b/docker-compose/haproxy/haproxy.conf index ab3f6565..b78600d2 100755 --- a/docker-compose/haproxy/haproxy.conf +++ b/docker-compose/haproxy/haproxy.conf @@ -20,11 +20,13 @@ defaults frontend all_http_frontend bind 0.0.0.0:80 use_backend keycloak-backend if { path_beg /idp } + use_backend pid-issuer-metadata if { path /.well-known/jwt-issuer/pid-issuer } use_backend pid-issuer-backend if { path_beg /pid-issuer } frontend all_https_frontend bind 0.0.0.0:443 ssl crt /etc/ssl/certs/localhost.tls.pem use_backend keycloak-backend if { path_beg /idp } + use_backend pid-issuer-metadata if { path /.well-known/jwt-issuer/pid-issuer } use_backend pid-issuer-backend if { path_beg /pid-issuer } backend keycloak-backend @@ -33,6 +35,9 @@ backend keycloak-backend option forwarded proto host by by_port for server server1 keycloak:8080 cookie server1 +backend pid-issuer-metadata + http-request return status 200 content-type application/json lf-string "{\"issuer\":\"https://localhost/pid-issuer/\",\"jwks_uri\":\"https://localhost/pid-issuer/public_keys.jwks\"}" + backend pid-issuer-backend balance roundrobin cookie SERVERUSED insert indirect nocache 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 97724e9d..157326df 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/PidIssuerApplication.kt @@ -240,7 +240,7 @@ fun beans(clock: Clock) = beans { // Routes // bean { - val metaDataApi = MetaDataApi(ref()) + val metaDataApi = MetaDataApi(ref(), ref()) val walletApi = WalletApi(ref(), ref(), ref()) val issuerApi = IssuerApi(ref()) metaDataApi.route.and(issuerApi.route).and(walletApi.route) @@ -270,6 +270,8 @@ fun beans(clock: Clock) = beans { authorize(WalletApi.DEFERRED_ENDPOINT, hasAnyAuthority(*scopes.toTypedArray())) authorize(MetaDataApi.WELL_KNOWN_OPENID_CREDENTIAL_ISSUER, permitAll) authorize(MetaDataApi.WELL_KNOWN_JWKS, permitAll) + authorize(MetaDataApi.WELL_KNOWN_JWT_ISSUER, permitAll) + authorize(MetaDataApi.PUBLIC_KEYS, permitAll) authorize(IssuerApi.CREDENTIALS_OFFER, permitAll) authorize(anyExchange, denyAll) } diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/MetaDataApi.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/MetaDataApi.kt index 6a7e0243..459cc974 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/MetaDataApi.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/input/web/MetaDataApi.kt @@ -15,7 +15,12 @@ */ package eu.europa.ec.eudi.pidissuer.adapter.input.web +import com.nimbusds.jose.jwk.JWKSet +import eu.europa.ec.eudi.pidissuer.domain.CredentialIssuerMetaData import eu.europa.ec.eudi.pidissuer.port.input.GetCredentialIssuerMetaData +import kotlinx.serialization.json.Json +import kotlinx.serialization.json.JsonPrimitive +import kotlinx.serialization.json.buildJsonObject import org.springframework.http.MediaType import org.springframework.web.reactive.function.server.ServerResponse import org.springframework.web.reactive.function.server.bodyValueAndAwait @@ -23,7 +28,8 @@ import org.springframework.web.reactive.function.server.coRouter import org.springframework.web.reactive.function.server.json class MetaDataApi( - val getCredentialIssuerMetaData: GetCredentialIssuerMetaData, + private val getCredentialIssuerMetaData: GetCredentialIssuerMetaData, + private val credentialIssuerMetaData: CredentialIssuerMetaData, ) { val route = coRouter { @@ -33,6 +39,12 @@ class MetaDataApi( GET(WELL_KNOWN_JWKS, accept(MediaType.APPLICATION_JSON)) { _ -> handleGetJwtIssuerJwkSet() } + GET(WELL_KNOWN_JWT_ISSUER, accept(MediaType.APPLICATION_JSON)) { + handleGetJwtIssuer() + } + GET(PUBLIC_KEYS, accept(MediaType.APPLICATION_JSON)) { + handleGetJwtIssuerJwks() + } } private suspend fun handleGetClientIssuerMetaData(): ServerResponse = @@ -41,9 +53,28 @@ class MetaDataApi( private suspend fun handleGetJwtIssuerJwkSet(): ServerResponse = TODO() + private suspend fun handleGetJwtIssuer(): ServerResponse = + ServerResponse.ok() + .json() + .bodyValueAndAwait( + buildJsonObject { + put("issuer ", JsonPrimitive(credentialIssuerMetaData.id.externalForm)) + put("jwks ", Json.parseToJsonElement(credentialIssuerMetaData.jwtIssuerJwks.toString(true))) + }, + ) + + private suspend fun handleGetJwtIssuerJwks(): ServerResponse = + ServerResponse.ok() + .json() + .bodyValueAndAwait(credentialIssuerMetaData.jwtIssuerJwks.toString(true)) + companion object { const val WELL_KNOWN_OPENID_CREDENTIAL_ISSUER = "/.well-known/openid-credential-issuer" - const val WELL_KNOWN_JWKS = "/.well-known/jwks.json" + const val WELL_KNOWN_JWT_ISSUER = "/.well-known/jwt-issuer" + const val PUBLIC_KEYS = "/public_keys.jwks" } } + +private val CredentialIssuerMetaData.jwtIssuerJwks: JWKSet + get() = JWKSet(specificCredentialIssuers.mapNotNull { it.publicKey }) diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt index 324712ce..1f71afa4 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueMsoMdocPid.kt @@ -20,6 +20,7 @@ import arrow.core.raise.Raise import arrow.core.raise.withError import com.nimbusds.jose.JWSAlgorithm import com.nimbusds.jose.jwk.ECKey +import com.nimbusds.jose.jwk.JWK import eu.europa.ec.eudi.pidissuer.adapter.out.jose.ValidateProof import eu.europa.ec.eudi.pidissuer.domain.* import eu.europa.ec.eudi.pidissuer.port.input.AuthorizationContext @@ -173,6 +174,7 @@ class IssueMsoMdocPid( private val validateProof = ValidateProof(credentialIssuerId) override val supportedCredential: CredentialMetaData get() = PidMsoMdocV1 + override val publicKey: JWK? = null context(Raise) override suspend fun invoke( diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt index 79dbbdf5..dfd373b3 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/adapter/out/pid/IssueSdJwtVcPid.kt @@ -224,10 +224,11 @@ class IssueSdJwtVcPid( ) : IssueSpecificCredential { private val log = LoggerFactory.getLogger(IssueSdJwtVcPid::class.java) + private val validateProof = ValidateProof(credentialIssuerId) override val supportedCredential: CredentialMetaData get() = PidSdJwtVcV1 - - private val validateProof = ValidateProof(credentialIssuerId) + override val publicKey: JWK + get() = issuerKey.toPublicJWK() context(Raise) override suspend fun invoke( diff --git a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/out/IssueSpecificCredential.kt b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/out/IssueSpecificCredential.kt index 8c43a1c2..5c345405 100644 --- a/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/out/IssueSpecificCredential.kt +++ b/src/main/kotlin/eu/europa/ec/eudi/pidissuer/port/out/IssueSpecificCredential.kt @@ -16,6 +16,7 @@ package eu.europa.ec.eudi.pidissuer.port.out import arrow.core.raise.Raise +import com.nimbusds.jose.jwk.JWK import eu.europa.ec.eudi.pidissuer.domain.* import eu.europa.ec.eudi.pidissuer.port.input.AuthorizationContext import eu.europa.ec.eudi.pidissuer.port.input.IssueCredentialError @@ -27,6 +28,7 @@ import org.slf4j.LoggerFactory interface IssueSpecificCredential { val supportedCredential: CredentialMetaData + val publicKey: JWK? context(Raise) suspend operator fun invoke(