Skip to content

Commit

Permalink
Add: Support multiple verifiable presentations in the verifier
Browse files Browse the repository at this point in the history
  • Loading branch information
acrusage-iaik committed Apr 9, 2024
1 parent f6b666a commit cb43a2b
Show file tree
Hide file tree
Showing 5 changed files with 407 additions and 37 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import io.github.aakira.napier.Napier
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.JsonElement

/**
* Contents of an OIDC Authentication Response.
Expand Down Expand Up @@ -41,7 +42,7 @@ data class AuthenticationResponseParameters(
* format is already represented as a JSON object or a JSON string.
*/
@SerialName("vp_token")
val vpToken: String? = null,
val vpToken: JsonElement? = null,

/**
* OID4VP: REQUIRED. The presentation_submission element as defined in DIF.PresentationExchange. It contains
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ import at.asitplus.wallet.lib.oidvci.decodeFromUrlQuery
import at.asitplus.wallet.lib.oidvci.encodeToParameters
import com.benasher44.uuid.uuid4
import io.github.aakira.napier.Napier
import io.ktor.http.*
import io.ktor.http.URLBuilder
import io.ktor.http.Url
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.datetime.Clock
Expand Down Expand Up @@ -450,23 +451,19 @@ class OidcSiopVerifier(
val descriptors = presentationSubmission.descriptorMap
?: return AuthnResponseResult.ValidationError("presentation_submission", params.state)
.also { Napier.w("presentation_submission contains no descriptors") }
val vp = params.vpToken // TODO: this may be an array of verifiable presentations
val verifiablePresentation = params.vpToken
?: return AuthnResponseResult.ValidationError("vp_token is null", params.state)
.also { Napier.w("No VP in response") }

// descriptors use JsonPaths to describe the resulting input descriptor match locations
// -> it seems fitting to compile the vpToken to a jsonObject
// making use of assumption:50c2c2bc-df25-4e9d-9890-67bde5a0e677
val verifiablePresentations = jsonSerializer.parseToJsonElement(vp)

val validationResults = descriptors.map { descriptor ->
val cumulativeJsonPath = try {
descriptor.cumulativeJsonPath
} catch (exception: InvalidJsonPathException) {
return AuthnResponseResult.ValidationError("presentation_submission", params.state)
}
Napier.d("matching jsonPath: $cumulativeJsonPath")
val relatedPresentation =
verifiablePresentations.matchJsonPath(cumulativeJsonPath).entries.first().value
verifiablePresentation.matchJsonPath(cumulativeJsonPath).entries.first().value

val format = descriptor.format
val result = when (format) {
Expand Down Expand Up @@ -541,4 +538,4 @@ private val PresentationSubmissionDescriptor.cumulativeJsonPath: String
descriptorIterator = descriptorIterator.nestedPath
}
return cummulativeJsonPath
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import at.asitplus.crypto.datatypes.jws.JwsSigned
import at.asitplus.crypto.datatypes.jws.toJsonWebKey
import at.asitplus.wallet.lib.agent.CryptoService
import at.asitplus.wallet.lib.agent.Holder
import at.asitplus.wallet.lib.data.dif.ClaimFormatEnum
import at.asitplus.wallet.lib.data.dif.PresentationDefinition
import at.asitplus.wallet.lib.jws.DefaultJwsService
import at.asitplus.wallet.lib.jws.DefaultVerifierJwsService
Expand All @@ -28,12 +29,12 @@ import at.asitplus.wallet.lib.oidvci.formUrlEncode
import io.github.aakira.napier.Napier
import io.ktor.http.URLBuilder
import io.ktor.http.Url
import io.ktor.util.encodeBase64
import io.ktor.util.flattenEntries
import io.matthewnelson.encoding.base16.Base16
import io.matthewnelson.encoding.core.Encoder.Companion.encodeToString
import kotlinx.coroutines.runBlocking
import kotlinx.datetime.Clock
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.add
import kotlinx.serialization.json.buildJsonArray
import kotlin.time.Duration.Companion.seconds
Expand Down Expand Up @@ -81,7 +82,8 @@ class OidcSiopWallet(
clientId: String = "https://wallet.a-sit.at/",
jwkSetRetriever: (String) -> JsonWebKeySet? = { null },
requestObjectCandidateRetriever: RequestObjectCandidateRetriever = { listOf() },
presentationDefinitionRetriever: PresentationDefinitionRetriever = object : PresentationDefinitionRetriever {
presentationDefinitionRetriever: PresentationDefinitionRetriever = object :
PresentationDefinitionRetriever {
override suspend fun retrieveFromUrl(url: Url): PresentationDefinition? {
return null
}
Expand Down Expand Up @@ -301,18 +303,6 @@ class OidcSiopWallet(
.also { Napier.w("response_type is not specified") }
if (!params.responseType.contains(VP_TOKEN) && params.presentationDefinition == null)
return KmmResult.failure<AuthenticationResponseParameters>(OAuth2Exception(Errors.INVALID_REQUEST))
.also { Napier.w("vp_token not requested") }
if (params.clientMetadata.vpFormats != null) {
if (params.clientMetadata.vpFormats.jwtVp?.algorithms?.contains(jwsService.algorithm.identifier) != true)
return KmmResult.failure<AuthenticationResponseParameters>(OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED))
.also { Napier.w("Incompatible JWT algorithms") }
if (params.clientMetadata.vpFormats.jwtSd?.algorithms?.contains(jwsService.algorithm.identifier) != true)
return KmmResult.failure<AuthenticationResponseParameters>(OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED))
.also { Napier.w("Incompatible JWT algorithms") }
if (params.clientMetadata.vpFormats.msoMdoc?.algorithms?.contains(jwsService.algorithm.identifier) != true)
return KmmResult.failure<AuthenticationResponseParameters>(OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED))
.also { Napier.w("Incompatible JWT algorithms") }
}
if (params.nonce == null)
return KmmResult.failure<AuthenticationResponseParameters>(OAuth2Exception(Errors.INVALID_REQUEST))
.also { Napier.w("nonce is null") }
Expand Down Expand Up @@ -346,7 +336,7 @@ class OidcSiopWallet(
retriever.deriveFromScope(it)
}
}
} ?: throw OAuth2Exception(Errors.INVALID_REQUEST)
} ?: throw OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED)
.also { Napier.d("No valid presentation definition has been found for request $params") }

val presentationSubmissionContainer = holder.createPresentation(
Expand All @@ -357,6 +347,24 @@ class OidcSiopWallet(
?: return KmmResult.failure<AuthenticationResponseParameters>(OAuth2Exception(Errors.USER_CANCELLED))
.also { Napier.w("Could not create presentation") }

params.clientMetadata.vpFormats?.let { supportedFormats ->
presentationSubmissionContainer.presentationSubmission.descriptorMap?.mapIndexed { index, descriptor ->
val isMissingFormatSupport = when (descriptor.format) {
ClaimFormatEnum.JWT -> supportedFormats.jwt?.algorithms?.contains(jwsService.algorithm.identifier) != true
ClaimFormatEnum.JWT_VC -> supportedFormats.jwtVc?.algorithms?.contains(jwsService.algorithm.identifier) != true
ClaimFormatEnum.JWT_VP -> supportedFormats.jwtVp?.algorithms?.contains(jwsService.algorithm.identifier) != true
ClaimFormatEnum.JWT_SD -> supportedFormats.jwtSd?.algorithms?.contains(jwsService.algorithm.identifier) != true
ClaimFormatEnum.MSO_MDOC -> supportedFormats.msoMdoc?.algorithms?.contains(jwsService.algorithm.identifier) != true
else -> true
}

if (isMissingFormatSupport) {
return KmmResult.failure<AuthenticationResponseParameters>(
OAuth2Exception(Errors.REGISTRATION_VALUE_NOT_SUPPORTED)
).also { Napier.w("Incompatible JWT algorithms for claim format ${descriptor.format}: $presentationDefinition") }
}
}
}

return KmmResult.success(
AuthenticationResponseParameters(
Expand All @@ -372,12 +380,12 @@ class OidcSiopWallet(
)
}
}.let {
if (it.size == 1) it[0]
if (it.size == 1) JsonPrimitive(it[0])
else buildJsonArray {
for (value in it) {
add(value)
}
}.toString() // making use of assumption:416dc455-ebb7-4b74-86e4-b63dc8cfe279
}
},
presentationSubmission = presentationSubmissionContainer.presentationSubmission,
)
Expand All @@ -402,11 +410,4 @@ interface PresentationDefinitionRetriever {
* Implementations need to match the scope to a known presentation definition.
*/
suspend fun deriveFromScope(scope: String): PresentationDefinition?
}

private fun String.encodeBase64Url(): String {
return this.encodeBase64()
.replace('+', Char(0x2B))
.replace('/', Char(0x2F))
.replace('=', Char(0x3D))
}
Loading

0 comments on commit cb43a2b

Please sign in to comment.