Skip to content

Commit

Permalink
Update credential request and deferred credential request to draft 15 (
Browse files Browse the repository at this point in the history
  • Loading branch information
vafeini committed Feb 21, 2025
1 parent 7ce5489 commit 02420e4
Show file tree
Hide file tree
Showing 15 changed files with 342 additions and 636 deletions.
29 changes: 0 additions & 29 deletions src/main/kotlin/eu/europa/ec/eudi/openid4vci/Issuance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,6 @@ import com.nimbusds.jose.JWSAlgorithm
import com.nimbusds.jose.JWSSigner
import com.nimbusds.jose.crypto.factories.DefaultJWSSignerFactory
import com.nimbusds.jose.jwk.JWK
import eu.europa.ec.eudi.openid4vci.internal.ClaimSetSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.JsonObject

/**
Expand Down Expand Up @@ -99,20 +97,6 @@ sealed interface SubmissionOutcome : java.io.Serializable {
data class Failed(val error: CredentialIssuanceError) : SubmissionOutcome
}

/**
* Interface to model the set of specific claims that need to be included in the issued credential.
* This set of claims is modeled differently depending on the credential format.
*/
sealed interface ClaimSet

@Serializable(with = ClaimSetSerializer::class)
class MsoMdocClaimSet(claims: List<Pair<Namespace, ClaimName>>) :
ClaimSet,
List<Pair<Namespace, ClaimName>> by claims

@Serializable
data class GenericClaimSet(val claims: List<ClaimName>) : ClaimSet

/**
* Sealed interface to model the payload of an issuance request. Issuance can be requested by providing the credential configuration
* identifier and a claim set ot by providing a credential identifier retrieved from token endpoint while authorizing an issuance request.
Expand All @@ -136,12 +120,9 @@ sealed interface IssuanceRequestPayload {
* Credential configuration based request payload.
*
* @param credentialConfigurationIdentifier The credential configuration identifier
* @param claimSet Optional parameter to specify the specific set of claims that are requested to be included in the
* credential to be issued.
*/
data class ConfigurationBased(
override val credentialConfigurationIdentifier: CredentialConfigurationIdentifier,
val claimSet: ClaimSet? = null,
) : IssuanceRequestPayload
}

Expand All @@ -152,16 +133,6 @@ typealias AuthorizedRequestAnd<T> = Pair<AuthorizedRequest, T>
*/
interface RequestIssuance {

@Deprecated(
message = "Method deprecated and will be removed in a future release",
replaceWith = ReplaceWith("request(requestPayload, popSigner?.let(::listOf).orEmpty()"),
)
suspend fun AuthorizedRequest.requestSingle(
requestPayload: IssuanceRequestPayload,
popSigner: PopSigner?,
): Result<AuthorizedRequestAnd<SubmissionOutcome>> =
request(requestPayload, popSigner?.let(::listOf).orEmpty())

/**
* Places a request to the credential issuance endpoint.
* Method will attempt to automatically retry submission in case
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,12 @@
package eu.europa.ec.eudi.openid4vci.internal

import eu.europa.ec.eudi.openid4vci.*
import eu.europa.ec.eudi.openid4vci.CredentialIssuanceError.InvalidIssuanceRequest

internal sealed interface CredentialType {
data class MsoMdocDocType(val doctype: String, val claimSet: MsoMdocClaimSet?) : CredentialType

data class SdJwtVcType(val type: String, val claims: GenericClaimSet?) : CredentialType

data class W3CSignedJwtType(val type: List<String>, val claims: GenericClaimSet?) : CredentialType
}

internal sealed interface CredentialConfigurationReference {
data class ById(val credentialIdentifier: CredentialIdentifier) : CredentialConfigurationReference
data class ByFormat(val credential: CredentialType) : CredentialConfigurationReference
data class ByCredentialId(val credentialIdentifier: CredentialIdentifier) : CredentialConfigurationReference
data class ByCredentialConfigurationId(
val credentialConfigurationId: CredentialConfigurationIdentifier,
) : CredentialConfigurationReference
}

/**
Expand All @@ -41,129 +34,26 @@ internal data class CredentialIssuanceRequest(
) {

companion object {
internal fun byId(
internal fun byCredentialId(
credentialIdentifier: CredentialIdentifier,
proofs: List<Proof>,
responseEncryptionSpec: IssuanceResponseEncryptionSpec?,
): CredentialIssuanceRequest =
CredentialIssuanceRequest(
CredentialConfigurationReference.ById(credentialIdentifier),
CredentialConfigurationReference.ByCredentialId(credentialIdentifier),
proofs,
responseEncryptionSpec,
)

internal fun formatBased(
credentialConfiguration: CredentialConfiguration,
claimSet: ClaimSet?,
internal fun byCredentialConfigurationId(
credentialConfigurationId: CredentialConfigurationIdentifier,
proofs: List<Proof>,
responseEncryptionSpec: IssuanceResponseEncryptionSpec?,
): CredentialIssuanceRequest {
val cd = when (credentialConfiguration) {
is MsoMdocCredential -> msoMdoc(credentialConfiguration, claimSet.ensureClaimSet())
is SdJwtVcCredential -> sdJwtVc(credentialConfiguration, claimSet.ensureClaimSet())
is W3CSignedJwtCredential -> w3cSignedJwt(credentialConfiguration, claimSet.ensureClaimSet())
is W3CJsonLdSignedJwtCredential -> error("Format $FORMAT_W3C_JSONLD_SIGNED_JWT not supported")
is W3CJsonLdDataIntegrityCredential -> error("Format $FORMAT_W3C_JSONLD_DATA_INTEGRITY not supported")
}

return CredentialIssuanceRequest(
CredentialConfigurationReference.ByFormat(cd),
): CredentialIssuanceRequest =
CredentialIssuanceRequest(
CredentialConfigurationReference.ByCredentialConfigurationId(credentialConfigurationId),
proofs,
responseEncryptionSpec,
)
}
}
}

private inline fun <reified C : ClaimSet> ClaimSet?.ensureClaimSet(): C? =
if (this != null) {
ensure(this is C) { InvalidIssuanceRequest("Invalid Claim Set provided for issuance") }
this
} else null

private fun msoMdoc(
credentialConfiguration: MsoMdocCredential,
claimSet: MsoMdocClaimSet?,
): CredentialType.MsoMdocDocType {
fun MsoMdocClaimSet.validate() {
if (isNotEmpty()) {
val supportedClaims = credentialConfiguration.claims
ensure(supportedClaims.isNotEmpty()) {
InvalidIssuanceRequest(
"Issuer does not support claims for credential [MsoMdoc-${credentialConfiguration.docType}]",
)
}

// TODO [d15]: Remove when requests are adapted to d15
// forEach { (nameSpace, claimName) ->
// val supportedClaimNames = supportedClaims[nameSpace]
// ensureNotNull(supportedClaimNames) {
// InvalidIssuanceRequest("Namespace $nameSpace not supported by issuer")
// }
// ensure(claimName in supportedClaimNames) {
// InvalidIssuanceRequest("Requested claim name $claimName is not supported by issuer")
// }
// }
}
}

val validClaimSet = claimSet?.apply { validate() }
return CredentialType.MsoMdocDocType(
doctype = credentialConfiguration.docType,
claimSet = validClaimSet,
)
}

private fun sdJwtVc(
credentialConfiguration: SdJwtVcCredential,
claimSet: GenericClaimSet?,
): CredentialType.SdJwtVcType {
fun GenericClaimSet.validate() {
if (claims.isNotEmpty()) {
val supportedClaims = credentialConfiguration.claims
ensure(supportedClaims.isNotEmpty()) {
InvalidIssuanceRequest(
"Issuer does not support claims for credential " +
"[$FORMAT_SD_JWT_VC-${credentialConfiguration.type}]",
)
}
// TODO [d15]: Remove when requests are adapted to d15
// ensure(supportedClaims.keys.containsAll(claims)) {
// InvalidIssuanceRequest("Claim names requested are not supported by issuer")
// }
}
}

val validClaimSet = claimSet?.apply { validate() }
return CredentialType.SdJwtVcType(
type = credentialConfiguration.type,
claims = validClaimSet,
)
}

private fun w3cSignedJwt(
credentialConfiguration: W3CSignedJwtCredential,
claimSet: GenericClaimSet?,
): CredentialType.W3CSignedJwtType {
fun GenericClaimSet.validate() {
if (claims.isNotEmpty()) {
val supportedClaims = credentialConfiguration.claims
ensure(supportedClaims.isNotEmpty()) {
InvalidIssuanceRequest(
"Issuer does not support claims for credential " +
"[$FORMAT_W3C_SIGNED_JWT-${credentialConfiguration.credentialDefinition.type}]",
)
}
// TODO [d15]: Remove when requests are adapted to d15
// ensure(supportedClaims.keys.containsAll(claims)) {
// InvalidIssuanceRequest("Claim names requested are not supported by issuer")
// }
}
}

val validClaimSet = claimSet?.apply { validate() }
return CredentialType.W3CSignedJwtType(
type = credentialConfiguration.credentialDefinition.type,
claims = validClaimSet,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -138,17 +138,20 @@ internal class RequestIssuanceImpl(

return when (requestPayload) {
is IssuanceRequestPayload.ConfigurationBased -> {
CredentialIssuanceRequest.formatBased(
credentialCfg,
requestPayload.claimSet,
CredentialIssuanceRequest.byCredentialConfigurationId(
requestPayload.credentialConfigurationIdentifier,
proofs,
responseEncryptionSpec,
)
}

is IssuanceRequestPayload.IdentifierBased -> {
requestPayload.ensureAuthorized(authorizationDetails)
CredentialIssuanceRequest.byId(requestPayload.credentialIdentifier, proofs, responseEncryptionSpec)
CredentialIssuanceRequest.byCredentialId(
requestPayload.credentialIdentifier,
proofs,
responseEncryptionSpec,
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,28 +90,6 @@ internal object ProofSerializer : KSerializer<Proof> {
}
}

internal object ClaimSetSerializer : KSerializer<MsoMdocClaimSet> {

val internal = serializer<Map<Namespace, Map<ClaimName, JsonObject>>>()
override val descriptor: SerialDescriptor = internal.descriptor

override fun deserialize(decoder: Decoder): MsoMdocClaimSet = internal.deserialize(decoder).asMsoMdocClaimSet()

override fun serialize(encoder: Encoder, value: MsoMdocClaimSet) {
internal.serialize(encoder, value.toJson())
}

private fun Map<Namespace, Map<ClaimName, JsonObject>>.asMsoMdocClaimSet() =
flatMap { (nameSpace, cs) -> cs.map { (claimName, _) -> nameSpace to claimName } }
.let(::MsoMdocClaimSet)

private fun MsoMdocClaimSet.toJson(): Map<Namespace, Map<ClaimName, JsonObject>> =
groupBy { (nameSpace, _) -> nameSpace }
.mapValues { (_, vs) -> vs.associate { (_, claimName) -> claimName to emptyJsonObject } }

private val emptyJsonObject = JsonObject(emptyMap())
}

@OptIn(ExperimentalSerializationApi::class)
internal object GrantedAuthorizationDetailsSerializer :
KSerializer<Map<CredentialConfigurationIdentifier, List<CredentialIdentifier>>> {
Expand Down
Loading

0 comments on commit 02420e4

Please sign in to comment.