Skip to content

Commit

Permalink
Fix issues with Credential Response Encryption.
Browse files Browse the repository at this point in the history
1. RequestedResponseEncryption.Required now accepts JWK with keyUse null as well. When keyUse is null, the JWK can be used for all purposes, including encryption.
2. Properly verify during issuance that RequestedResponseEncryption is supported by the issuers configured CredentialResponseEncryption. Previously when CredentialResponseEncryption was set to Required, but the CredentialRequestTO contained no CredentialResponseEncryptionTO, the issuance would continue and an unencrypted credential would be issued, even though the issuer was configured to require encryption.
  • Loading branch information
dzarras committed Aug 23, 2024
1 parent ead571f commit 903c30c
Show file tree
Hide file tree
Showing 3 changed files with 305 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,8 @@ sealed interface RequestedResponseEncryption {
) : RequestedResponseEncryption {
init {
require(!encryptionJwk.isPrivate) { "encryptionJwk must not contain a private key" }
require(KeyUse.ENCRYPTION == encryptionJwk.keyUse) {
// When keyUse is null, the JWK can be used for all purposes, including but not limited to ENCRYPTION.
require(encryptionJwk.keyUse == null || encryptionJwk.keyUse == KeyUse.ENCRYPTION) {
"encryptionJwk cannot be used for encryption"
}
require(encryptionAlgorithm in JWEAlgorithm.Family.ASYMMETRIC) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,7 @@ class IssueCredential(
when (unresolvedRequest) {
is UnresolvedCredentialRequest.ByFormat ->
unresolvedRequest.credentialRequest to null

is UnresolvedCredentialRequest.ByCredentialIdentifier ->
resolve(unresolvedRequest) to unresolvedRequest.credentialIdentifier
}
Expand Down Expand Up @@ -370,8 +371,8 @@ private fun CredentialRequestTO.toDomain(
supported: CredentialResponseEncryption,
): UnresolvedCredentialRequest {
val proof = ensureNotNull(proof) { MissingProof }.toDomain()
val credentialResponseEncryption =
credentialResponseEncryption?.toDomain(supported) ?: RequestedResponseEncryption.NotRequired
val credentialResponseEncryption = credentialResponseEncryption?.toDomain() ?: RequestedResponseEncryption.NotRequired
credentialResponseEncryption.ensureIsSupported(supported)

fun credentialRequestByFormat(format: FormatTO): UnresolvedCredentialRequest.ByFormat =
when (format) {
Expand Down Expand Up @@ -459,54 +460,63 @@ private fun ProofTo.toDomain(): UnvalidatedProof = when (type) {
}

/**
* Gets the [RequestedResponseEncryption] that corresponds to the provided values.
* Verifies this [RequestedResponseEncryption] is supported by the provided [CredentialResponseEncryption], otherwise
* raises an [InvalidEncryptionParameters].
*/
context(Raise<InvalidEncryptionParameters>)
private fun CredentialResponseEncryptionTO.toDomain(supported: CredentialResponseEncryption): RequestedResponseEncryption.Required =
withError({ InvalidEncryptionParameters(it) }) {
fun RequestedResponseEncryption.ensureIsSupported() {
when (supported) {
is CredentialResponseEncryption.NotSupported -> {
if (this is RequestedResponseEncryption.Required) {
// credential response encryption not supported by issuer but required by client
raise(IllegalArgumentException("credential response encryption is not supported"))
}
}

is CredentialResponseEncryption.Optional -> {
if (this is RequestedResponseEncryption.Required) {
// credential response encryption supported by issuer and required by client
// ensure provided parameters are supported
if (encryptionAlgorithm !in supported.parameters.algorithmsSupported) {
raise(IllegalArgumentException("jwe encryption algorithm '${encryptionAlgorithm.name}' is not supported"))
}
if (encryptionMethod !in supported.parameters.methodsSupported) {
raise(IllegalArgumentException("jwe encryption method '${encryptionMethod.name}' is not supported"))
}
}
private fun RequestedResponseEncryption.ensureIsSupported(supported: CredentialResponseEncryption) {
try {
when (supported) {
is CredentialResponseEncryption.NotSupported -> {
if (this is RequestedResponseEncryption.Required) {
// credential response encryption not supported by issuer but required by client
throw IllegalArgumentException("credential response encryption is not supported")
}
}

is CredentialResponseEncryption.Required -> {
if (this !is RequestedResponseEncryption.Required) {
// credential response encryption required by issuer but not required by client
raise(IllegalArgumentException("credential response encryption is required"))
}

is CredentialResponseEncryption.Optional -> {
if (this is RequestedResponseEncryption.Required) {
// credential response encryption supported by issuer and required by client
// ensure provided parameters are supported
if (encryptionAlgorithm !in supported.parameters.algorithmsSupported) {
raise(IllegalArgumentException("jwe encryption algorithm '${encryptionAlgorithm.name}' is not supported"))
throw IllegalArgumentException("jwe encryption algorithm '${encryptionAlgorithm.name}' is not supported")
}
if (encryptionMethod !in supported.parameters.methodsSupported) {
raise(IllegalArgumentException("jwe encryption method '${encryptionMethod.name}' is not supported"))
throw IllegalArgumentException("jwe encryption method '${encryptionMethod.name}' is not supported")
}
}
}
}

RequestedResponseEncryption.Required(Json.encodeToString(key), algorithm, method)
.bind()
.also { it.ensureIsSupported() }
is CredentialResponseEncryption.Required -> {
if (this !is RequestedResponseEncryption.Required) {
// credential response encryption required by issuer but not required by client
throw IllegalArgumentException("credential response encryption is required")
}

// ensure provided parameters are supported
if (encryptionAlgorithm !in supported.parameters.algorithmsSupported) {
throw IllegalArgumentException("jwe encryption algorithm '${encryptionAlgorithm.name}' is not supported")
}
if (encryptionMethod !in supported.parameters.methodsSupported) {
throw IllegalArgumentException("jwe encryption method '${encryptionMethod.name}' is not supported")
}
}
}
} catch (error: Exception) {
raise(InvalidEncryptionParameters(error))
}
}

/**
* Gets the [RequestedResponseEncryption] that corresponds to the provided values.
*/
context(Raise<InvalidEncryptionParameters>)
private fun CredentialResponseEncryptionTO.toDomain(): RequestedResponseEncryption.Required =
RequestedResponseEncryption.Required(
Json.encodeToString(key),
algorithm,
method,
).getOrElse { raise(InvalidEncryptionParameters(it)) }

fun CredentialResponse<JsonElement>.toTO(nonce: CNonce): IssueCredentialResponse.PlainTO =
when (this) {
Expand Down
Loading

0 comments on commit 903c30c

Please sign in to comment.