Skip to content

Commit

Permalink
Conditionally add iss claim to proof (#339)
Browse files Browse the repository at this point in the history
  • Loading branch information
babisRoutis authored Nov 11, 2024
1 parent f797015 commit 2b9c33b
Show file tree
Hide file tree
Showing 6 changed files with 96 additions and 16 deletions.
31 changes: 27 additions & 4 deletions src/main/kotlin/eu/europa/ec/eudi/openid4vci/AuthorizeIssuance.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ data class AuthorizationRequestPrepared(
val dpopNonce: Nonce?,
) : java.io.Serializable

enum class Grant : java.io.Serializable {
AuthorizationCode,
PreAuthorizedCodeGrant,
}

/**
* Sealed hierarchy of states describing an authorized issuance request. These states hold an access token issued by the
* authorization server that protects the credential issuer.
Expand All @@ -50,9 +55,22 @@ sealed interface AuthorizedRequest : java.io.Serializable {
val refreshToken: RefreshToken?
val credentialIdentifiers: Map<CredentialConfigurationIdentifier, List<CredentialIdentifier>>?
val timestamp: Instant

/**
* Authorization server-provided DPoP Nonce, if any
*/
val authorizationServerDpopNonce: Nonce?

/**
* Protected resource-provided DPoP Nonce, if any
*/
val resourceServerDpopNonce: Nonce?

/**
* The Grant through which the authorization was obtained
*/
val grant: Grant

fun isAccessTokenExpired(at: Instant): Boolean = accessToken.isExpired(timestamp, at)
fun isRefreshTokenExpiredOrMissing(at: Instant): Boolean = refreshToken?.isExpired(timestamp, at) ?: true

Expand All @@ -65,13 +83,14 @@ sealed interface AuthorizedRequest : java.io.Serializable {
*/
fun withCNonce(cNonce: CNonce): ProofRequired =
ProofRequired(
accessToken,
refreshToken,
accessToken = accessToken,
refreshToken = refreshToken,
cNonce = cNonce,
credentialIdentifiers,
timestamp,
credentialIdentifiers = credentialIdentifiers,
timestamp = timestamp,
authorizationServerDpopNonce = authorizationServerDpopNonce,
resourceServerDpopNonce = resourceServerDpopNonce,
grant = grant,
)

fun withRefreshedAccessToken(
Expand Down Expand Up @@ -111,6 +130,7 @@ sealed interface AuthorizedRequest : java.io.Serializable {
* @param timestamp the point in time of the authorization (when tokens were issued)
* @param authorizationServerDpopNonce Nonce value for DPoP provided by the Authorization Server
* @param resourceServerDpopNonce Nonce value for DPoP provided by the Resource Server
* @param grant the Grant through which the authorization was obtained
*/
data class NoProofRequired(
override val accessToken: AccessToken,
Expand All @@ -119,6 +139,7 @@ sealed interface AuthorizedRequest : java.io.Serializable {
override val timestamp: Instant,
override val authorizationServerDpopNonce: Nonce?,
override val resourceServerDpopNonce: Nonce?,
override val grant: Grant,
) : AuthorizedRequest

/**
Expand All @@ -132,6 +153,7 @@ sealed interface AuthorizedRequest : java.io.Serializable {
* @param timestamp the point in time of the authorization (when tokens were issued)
* @param authorizationServerDpopNonce Nonce value for DPoP provided by the Authorization Server
* @param resourceServerDpopNonce Nonce value for DPoP provided by the Resource Server
* @param grant the Grant through which the authorization was obtained
*/
data class ProofRequired(
override val accessToken: AccessToken,
Expand All @@ -141,6 +163,7 @@ sealed interface AuthorizedRequest : java.io.Serializable {
override val timestamp: Instant,
override val authorizationServerDpopNonce: Nonce?,
override val resourceServerDpopNonce: Nonce?,
override val grant: Grant,
) : AuthorizedRequest
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ data class AuthorizedTransaction(
timestamp = authorizedRequest.timestamp,
authorizationServerDpopNonce = authorizedRequest.authorizationServerDpopNonce,
resourceServerDpopNonce = authorizedRequest.resourceServerDpopNonce,
grant = authorizedRequest.grant,
)
},
transactionId = transactionId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ internal class AuthorizeIssuanceImpl(
credConfigIdsAsAuthDetails,
dpopNonce,
).getOrThrow()
authorizedRequest(credentialOffer, tokenResponse, newDpopNonce)
authorizedRequest(credentialOffer, tokenResponse, newDpopNonce, Grant.AuthorizationCode)
}

override suspend fun authorizeWithPreAuthorizationCode(
Expand All @@ -119,7 +119,7 @@ internal class AuthorizeIssuanceImpl(
credConfigIdsAsAuthDetails,
dpopNonce = null,
).getOrThrow()
authorizedRequest(credentialOffer, tokenResponse, newDpopNonce)
authorizedRequest(credentialOffer, tokenResponse, newDpopNonce, Grant.PreAuthorizedCodeGrant)
}
}

Expand Down Expand Up @@ -150,6 +150,7 @@ private fun authorizedRequest(
offer: CredentialOffer,
tokenResponse: TokenResponse,
newDpopNonce: Nonce?,
grant: Grant,
): AuthorizedRequest {
val offerRequiresProofs = offer.credentialConfigurationIdentifiers.any {
val credentialConfiguration = offer.credentialIssuerMetadata.credentialConfigurationsSupported[it]
Expand All @@ -166,6 +167,7 @@ private fun authorizedRequest(
timestamp,
authorizationServerDpopNonce = newDpopNonce,
resourceServerDpopNonce = null,
grant = grant,
)

else ->
Expand All @@ -176,6 +178,7 @@ private fun authorizedRequest(
timestamp,
authorizationServerDpopNonce = newDpopNonce,
resourceServerDpopNonce = null,
grant = grant,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ private interface CheckPopSigner<POP_SIGNER : PopSigner> {

internal abstract class ProofBuilder<POP_SIGNER : PopSigner, out PROOF : Proof>(
val clock: Clock,
val iss: ClientId,
val iss: ClientId?,
val aud: CredentialIssuerId,
val nonce: CNonce,
val popSigner: POP_SIGNER,
Expand All @@ -42,7 +42,7 @@ internal abstract class ProofBuilder<POP_SIGNER : PopSigner, out PROOF : Proof>(
operator fun invoke(
proofTypesSupported: ProofTypesSupported,
clock: Clock,
iss: ClientId,
iss: ClientId?,
aud: CredentialIssuerId,
nonce: CNonce,
popSigner: PopSigner,
Expand All @@ -54,12 +54,33 @@ internal abstract class ProofBuilder<POP_SIGNER : PopSigner, out PROOF : Proof>(
}
}
}
operator fun invoke(
proofTypesSupported: ProofTypesSupported,
clock: Clock,
client: Client,
grant: Grant,
aud: CredentialIssuerId,
nonce: CNonce,
popSigner: PopSigner,
): ProofBuilder<*, *> =
invoke(proofTypesSupported, clock, iss(client, grant), aud, nonce, popSigner)

private fun iss(client: Client, grant: Grant): ClientId? {
val useIss = when (grant) {
Grant.AuthorizationCode -> true
Grant.PreAuthorizedCodeGrant -> when (client) {
is Client.Attested -> true
is Client.Public -> false
}
}
return client.id.takeIf { useIss }
}
}
}

internal class JwtProofBuilder(
clock: Clock,
iss: ClientId,
iss: ClientId?,
aud: CredentialIssuerId,
nonce: CNonce,
popSigner: PopSigner.Jwt,
Expand All @@ -86,7 +107,7 @@ internal class JwtProofBuilder(

private fun claimSet(): JWTClaimsSet =
JWTClaimsSet.Builder().apply {
issuer(iss)
iss?.let { issuer(it) }
audience(aud.toString())
claim("nonce", nonce.value)
issueTime(Date.from(clock.instant()))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ internal class RequestIssuanceImpl(
//
// Update state
//
val updatedAuthorizedRequest = this.withCNonceFrom(outcome).withResourceServerDpopNonce(newResourceServerDpopNonce)
val updatedAuthorizedRequest =
this.withCNonceFrom(outcome).withResourceServerDpopNonce(newResourceServerDpopNonce)

//
// Retry on invalid proof if we begin from NoProofRequired and issuer
Expand Down Expand Up @@ -104,15 +105,18 @@ internal class RequestIssuanceImpl(
}
}
}
popSigners.map { proofFactory(it, cNonce) }
popSigners.map { proofFactory(it, cNonce, grant) }
}
}

private fun proofFactory(proofSigner: PopSigner, cNonce: CNonce): ProofFactory = { credentialSupported ->
val iss = config.client.id
private fun proofFactory(
proofSigner: PopSigner,
cNonce: CNonce,
grant: Grant,
): ProofFactory = { credentialSupported ->
val aud = credentialOffer.credentialIssuerMetadata.credentialIssuerIdentifier
val proofTypesSupported = credentialSupported.proofTypesSupported
ProofBuilder(proofTypesSupported, config.clock, iss, aud, cNonce, proofSigner).build()
ProofBuilder(proofTypesSupported, config.clock, config.client, grant, aud, cNonce, proofSigner).build()
}

private suspend fun buildRequest(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,31 @@ data class AccessTokenTO(
}
}

@Serializable
enum class GrantTO {
@SerialName("authorization_code")
AuthorizationCode,

@SerialName("urn:ietf:params:oauth:grant-type:pre-authorized_code")
PreAuthorizedCodeGrant,

;

fun toGrant(): Grant =
when (this) {
AuthorizationCode -> Grant.AuthorizationCode
PreAuthorizedCodeGrant -> Grant.PreAuthorizedCodeGrant
}

companion object {
fun fromGrant(grant: Grant): GrantTO =
when (grant) {
Grant.AuthorizationCode -> AuthorizationCode
Grant.PreAuthorizedCodeGrant -> PreAuthorizedCodeGrant
}
}
}

@Serializable
data class DeferredIssuanceStoredContextTO(
@Required @SerialName("credential_issuer") val credentialIssuerId: String,
Expand All @@ -120,7 +145,8 @@ data class DeferredIssuanceStoredContextTO(
@SerialName("transaction_id") val transactionId: String,
@SerialName("access_token") val accessToken: AccessTokenTO,
@SerialName("refresh_token") val refreshToken: RefreshTokenTO? = null,
@SerialName("authorization_timestamp") val authorizationTimestamp: Long,
@SerialName("authorization_timestamGrantTO.fromGrant(grant)p") val authorizationTimestamp: Long,
@SerialName("grant") val grant: GrantTO,
) {

fun toDeferredIssuanceStoredContext(
Expand Down Expand Up @@ -165,6 +191,7 @@ data class DeferredIssuanceStoredContextTO(
timestamp = Instant.ofEpochSecond(authorizationTimestamp),
authorizationServerDpopNonce = null,
resourceServerDpopNonce = null,
grant = grant.toGrant(),
),
transactionId = TransactionId(transactionId),
),
Expand Down Expand Up @@ -203,6 +230,7 @@ data class DeferredIssuanceStoredContextTO(
accessToken = AccessTokenTO.from(authorizedTransaction.authorizedRequest.accessToken),
refreshToken = authorizedTransaction.authorizedRequest.refreshToken?.let { RefreshTokenTO.from(it) },
authorizationTimestamp = authorizedTransaction.authorizedRequest.timestamp.epochSecond,
grant = GrantTO.fromGrant(authorizedTransaction.authorizedRequest.grant),
)
}

Expand Down

0 comments on commit 2b9c33b

Please sign in to comment.