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

issuance JWT-property and well-known URL fixes #825

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ object Issuer {
suspend fun W3CVC.mergingJwtIssue(
issuerKey: Key,
issuerDid: String?,
issuerKid: String? = null,
subjectDid: String,

mappings: JsonObject,
Expand All @@ -79,7 +78,7 @@ object Issuer {
w3cVc.signJws(
issuerKey = issuerKey,
issuerDid = issuerDid,
issuerKid = issuerKid,
issuerKid = getKidHeader(issuerKey, issuerDid),
subjectDid = subjectDid,
additionalJwtHeader = additionalJwtHeader.toMutableMap().apply {
put("typ", "JWT".toJsonElement())
Expand Down Expand Up @@ -114,7 +113,7 @@ object Issuer {
).run {
w3cVc.signSdJwt(
issuerKey = issuerKey,
issuerKeyId = issuerDid ?: issuerKey.getKeyId(),
issuerKeyId = getKidHeader(issuerKey, issuerDid),
subjectDid = subjectDid,
disclosureMap = disclosureMap,
additionalJwtHeaders = additionalJwtHeaders.toMutableMap().apply {
Expand Down Expand Up @@ -186,4 +185,17 @@ object Issuer {

return IssuanceInformation(vc, jwtRes)
}

@JvmBlocking
@JvmAsync
@JsPromise
@JsExport.Ignore
suspend fun getKidHeader(issuerKey: Key, issuerDid: String? = null): String {
return if(!issuerDid.isNullOrEmpty()) {
if (issuerDid.startsWith("did:key"))
issuerDid + "#" + issuerDid.removePrefix("did:key:")
else
issuerDid + "#" + issuerKey.getKeyId()
} else issuerKey.getKeyId()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package id.walt.oid4vc

import cbor.Cbor
import id.walt.credentials.issuance.Issuer.getKidHeader
import id.walt.credentials.issuance.Issuer.mergingJwtIssue
import id.walt.credentials.issuance.Issuer.mergingSdJwtIssue
import id.walt.credentials.issuance.dataFunctions
Expand Down Expand Up @@ -124,9 +125,11 @@ object OpenID4VCI {
appendPathSegments(".well-known", "oauth-authorization-server")
}.buildString()

fun getJWTIssuerProviderMetadataUrl(baseUrl: String) = URLBuilder(baseUrl).apply {
fun getJWTVCIssuerProviderMetadataUrl(issUrl: String) = Url(issUrl).let { URLBuilder(it.protocolWithAuthority).apply {
appendPathSegments(".well-known", "jwt-vc-issuer")
}.buildString()
if(it.fullPath.isNotEmpty())
appendPathSegments(it.fullPath.substringAfter("/"))
}.buildString() }

suspend fun resolveCIProviderMetadata(credOffer: CredentialOffer) = http.get(getCIProviderMetadataUrl(credOffer)).bodyAsText().let {
OpenIDProviderMetadata.fromJSONString(it)
Expand Down Expand Up @@ -385,8 +388,8 @@ object OpenID4VCI {

suspend fun generateSdJwtVC(credentialRequest: CredentialRequest,
credentialData: JsonObject, dataMapping: JsonObject?,
selectiveDisclosure: SDMap?, vct: String,
issuerDid: String?, issuerKid: String?, x5Chain: List<String>?,
selectiveDisclosure: SDMap?, vct: String, issuerId: String,
issuerDid: String?, x5Chain: List<String>?,
issuerKey: Key): String {
val proofHeader = credentialRequest.proof?.jwt?.let { JwtUtils.parseJWTHeader(it) } ?: throw CredentialError(
credentialRequest, CredentialErrorCode.invalid_or_missing_proof, message = "Proof must be JWT proof"
Expand Down Expand Up @@ -417,13 +420,12 @@ object OpenID4VCI {
?: throw IllegalArgumentException("Either holderKey or holderDid must be given")

val defaultPayloadProperties = defaultPayloadProperties(
issuerDid ?: issuerKid ?: issuerKey.getKeyId(),
cnf, vct, null, null, null, null)
issuerId, cnf, vct, null, null, null, null)
val undisclosedPayload = sdPayload.undisclosedPayload.plus(defaultPayloadProperties).let { JsonObject(it) }
val fullPayload = sdPayload.fullPayload.plus(defaultPayloadProperties).let { JsonObject(it) }

val headers = mapOf(
"kid" to issuerKid,
"kid" to getKidHeader(issuerKey, issuerDid),
"typ" to SD_JWT_VC_TYPE_HEADER
).plus(x5Chain?.let {
mapOf("x5c" to JsonArray(it.map { cert -> cert.toJsonElement() }))
Expand All @@ -438,7 +440,7 @@ object OpenID4VCI {

suspend fun generateW3CJwtVC(credentialRequest: CredentialRequest,
credentialData: JsonObject, dataMapping: JsonObject?,
selectiveDisclosure: SDMap?, issuerDid: String?, issuerKid: String?,
selectiveDisclosure: SDMap?, issuerDid: String?,
x5Chain: List<String>?, issuerKey: Key): String {
val proofHeader = credentialRequest.proof?.jwt?.let { JwtUtils.parseJWTHeader(it) } ?: throw CredentialError(
credentialRequest, CredentialErrorCode.invalid_or_missing_proof, message = "Proof must be JWT proof"
Expand All @@ -452,7 +454,6 @@ object OpenID4VCI {
true -> vc.mergingJwtIssue(
issuerKey = issuerKey,
issuerDid = issuerDid,
issuerKid = issuerKid,
subjectDid = holderDid ?: "",
mappings = dataMapping ?: JsonObject(emptyMap()),
additionalJwtHeader = additionalJwtHeaders,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import id.walt.oid4vc.requests.TokenRequest
import id.walt.oid4vc.responses.CredentialResponse
import id.walt.oid4vc.responses.TokenResponse
import id.walt.oid4vc.util.randomUUID
import id.walt.sdjwt.JWTVCIssuerMetadata
import id.walt.sdjwt.SDJwtVC
import id.walt.verifier.lspPotential.LspPotentialVerificationInterop
import io.ktor.client.*
Expand Down Expand Up @@ -248,11 +249,11 @@ class LspPotentialIssuance(val client: HttpClient) {

// ### get issuer metadata, steps 7-10
val providerMetadataUri = OpenID4VCI.getCIProviderMetadataUrl(parsedOffer.credentialIssuer)
val jwtIssuerMetadataUri = OpenID4VCI.getJWTIssuerProviderMetadataUrl(parsedOffer.credentialIssuer)
val jwtIssuerMetadataUri = OpenID4VCI.getJWTVCIssuerProviderMetadataUrl(parsedOffer.credentialIssuer)
val oAuthMetadataUri = OpenID4VCI.getOAuthProviderMetadataUrl(parsedOffer.credentialIssuer)
val providerMetadata = client.get(providerMetadataUri).bodyAsText().let { OpenIDProviderMetadata.fromJSONString(it) }
val oauthMetadata = client.get(oAuthMetadataUri).body<OpenIDProviderMetadata>()
val jwtIssuerMetadata = client.get(jwtIssuerMetadataUri).body<OpenIDProviderMetadata>()
val jwtIssuerMetadata = client.get(jwtIssuerMetadataUri).body<JWTVCIssuerMetadata>()
assertNotNull(providerMetadata.credentialConfigurationsSupported)
assertNotNull(providerMetadata.credentialEndpoint)
assertNotNull(jwtIssuerMetadata.issuer)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -224,23 +224,15 @@ open class CIProvider(
val resolvedIssuerKey = KeyManager.resolveSerializedKey(request.issuerKey)

request.run {
var issuerKid = issuerDid ?: resolvedIssuerKey.getKeyId()
if(!issuerDid.isNullOrEmpty()) {
if (issuerDid.startsWith("did:key") && issuerDid.length == 186) // EBSI conformance corner case when issuer uses did:key instead of did:ebsi and no trust framework is defined
issuerKid = issuerDid + "#" + issuerDid.removePrefix("did:key:")
else if (issuerDid.startsWith("did:ebsi"))
issuerKid = issuerDid + "#" + resolvedIssuerKey.getKeyId()
}

val holderKeyJWK = JWKKey.importJWK(holderKey.toString()).getOrNull()?.exportJWKObject()?.plus("kid" to JWKKey.importJWK(holderKey.toString()).getOrThrow().getKeyId())?.toJsonObject()

when (credentialFormat) {
CredentialFormat.sd_jwt_vc -> OpenID4VCI.generateSdJwtVC(credentialRequest, vc, request.mapping,
request.selectiveDisclosure, vct = metadata.credentialConfigurationsSupported?.get(request.credentialConfigurationId)?.vct ?: throw ConfigurationException(
ConfigException("No vct configured for given credential configuration id: ${request.credentialConfigurationId}")
), issuerDid, issuerKid, request.x5Chain, resolvedIssuerKey).toString()
), issuerDid ?: baseUrl, issuerDid, request.x5Chain, resolvedIssuerKey).toString()
else -> OpenID4VCI.generateW3CJwtVC(credentialRequest, vc, request.mapping, request.selectiveDisclosure,
issuerDid, issuerKid, request.x5Chain, resolvedIssuerKey)
issuerDid, request.x5Chain, resolvedIssuerKey)
}
}.also { log.debug { "Respond VC: $it" } }
}))
Expand Down
Loading