Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add implementation of /.well-known/jwt-issuer #48

Merged
merged 3 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docker-compose/haproxy/haproxy.conf
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,21 @@
*/
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
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 {
Expand All @@ -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 =
Expand All @@ -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 })
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -173,6 +174,7 @@ class IssueMsoMdocPid(
private val validateProof = ValidateProof(credentialIssuerId)
override val supportedCredential: CredentialMetaData
get() = PidMsoMdocV1
override val publicKey: JWK? = null

context(Raise<IssueCredentialError>)
override suspend fun invoke(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,10 +224,11 @@ class IssueSdJwtVcPid(
) : IssueSpecificCredential<JsonElement> {

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<IssueCredentialError>)
override suspend fun invoke(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -27,6 +28,7 @@ import org.slf4j.LoggerFactory
interface IssueSpecificCredential<out T> {

val supportedCredential: CredentialMetaData
val publicKey: JWK?

context(Raise<IssueCredentialError>)
suspend operator fun invoke(
Expand Down