Skip to content

Commit b4ce259

Browse files
suzannajiwanikdeus
authored andcommitted
Pull Format from Input Descriptors
Updated OpenID4VP flow to determine format from input_descriptors field when the format cannot be determined from the client_metadata. Tested manually against verifiers which have malformed client_metadata. Signed-off-by: Suzanna Jiwani <suzannaj@google.com>
1 parent cd4c01f commit b4ce259

File tree

2 files changed

+58
-25
lines changed

2 files changed

+58
-25
lines changed

wallet/src/main/java/com/android/identity_credential/wallet/presentation/OpenID4VPPresentationActivity.kt

Lines changed: 55 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -462,7 +462,8 @@ class OpenID4VPPresentationActivity : FragmentActivity() {
462462

463463
val uri = Uri.parse(authRequest)
464464
val authorizationRequest = getAuthorizationRequest(uri, httpClient)
465-
val presentationSubmission = createPresentationSubmission(authorizationRequest)
465+
val credentialFormat = getFormat(authorizationRequest.clientMetadata, authorizationRequest.presentationDefinition)
466+
val presentationSubmission = createPresentationSubmission(authorizationRequest, credentialFormat)
466467
val inputDescriptors =
467468
authorizationRequest.presentationDefinition["input_descriptors"]!!.jsonArray
468469

@@ -479,10 +480,8 @@ class OpenID4VPPresentationActivity : FragmentActivity() {
479480
//
480481
val inputDescriptorObj = inputDescriptors[0].jsonObject
481482
val docType = inputDescriptorObj["id"]!!.toString().run { substring(1, this.length - 1) }
482-
// Get the requested format from the client metadata.
483-
val credentialFormat = getFormat(authorizationRequest.clientMetadata)
484-
val documentRequest = formatAsDocumentRequest(inputDescriptorObj)
485483

484+
val documentRequest = formatAsDocumentRequest(inputDescriptorObj)
486485
val document = firstMatchingDocument(credentialFormat, docType)
487486
?: run { throw NoMatchingDocumentException("No matching credentials in wallet for " +
488487
"docType $docType and credentialFormat $credentialFormat") }
@@ -894,42 +893,75 @@ private class TimeChecks : JWTClaimsSetVerifier<SecurityContext> {
894893
}
895894
}
896895

897-
private fun getFormat(clientMetadata: JsonObject): CredentialFormat {
898-
// Search for these strings, in order.
896+
private fun getFormat(
897+
clientMetadata: JsonObject,
898+
presentationDefinition: JsonObject
899+
): CredentialFormat {
900+
// Since we only return a single format, we order acceptedFormats such that preferred formats
901+
// are first.
899902
val acceptedFormats = listOf(
900903
"mso_mdoc" to CredentialFormat.MDOC_MSO,
901904
"jwt_vc" to CredentialFormat.SD_JWT_VC,
902905
"vc+sd-jwt" to CredentialFormat.SD_JWT_VC)
903-
clientMetadata["vp_formats"]?.let {
904-
val vpFormats = it.jsonObject.keys
905-
for ((format, credentialFormat) in acceptedFormats) {
906-
if (vpFormats.contains(format)) {
907-
return credentialFormat
906+
907+
// If the clientMetadata has a exactly one format, return that.
908+
if (clientMetadata.containsKey("vp_formats")) {
909+
val vpFormats = clientMetadata["vp_formats"]!!.jsonObject.keys
910+
if (vpFormats.size == 1) {
911+
for ((format, credentialFormat) in acceptedFormats) {
912+
if (vpFormats.contains(format)) {
913+
return credentialFormat
914+
}
908915
}
916+
throw IllegalArgumentException("No supported formats found in: ${vpFormats.sorted().joinToString()}")
909917
}
910-
throw IllegalArgumentException("No supported formats found in: ${vpFormats.sorted().joinToString()}")
911918
}
912-
throw IllegalArgumentException("No vp_formats found in client_metadata")
919+
920+
// If clientMetadata has 0 or 2+ formats, use presentation_definition -> input_descriptors
921+
// -> format.
922+
val inputDescriptors = presentationDefinition["input_descriptors"]!!.jsonArray
923+
val vpFormats = HashSet<String>()
924+
925+
for (inputDescriptor: JsonElement in inputDescriptors) {
926+
val inputDescriptorObj = inputDescriptor.jsonObject
927+
val formatName = inputDescriptorObj["format"]?.jsonObject?.keys?.first()
928+
if (formatName != null) {
929+
vpFormats.add(formatName)
930+
}
931+
}
932+
933+
for ((format, credentialFormat) in acceptedFormats) {
934+
if (vpFormats.contains(format)) {
935+
return credentialFormat
936+
}
937+
}
938+
throw IllegalArgumentException("No vp_formats found in client_metadata and no supported " +
939+
"formats found in input_descriptors: ${vpFormats.sorted().joinToString()}")
913940
}
914941

915-
private val validFormats = setOf("mso_mdoc", "jwt_vc", "vc+sd-jwt")
942+
internal fun createPresentationSubmission(
943+
authRequest: AuthorizationRequest,
944+
credentialFormat: CredentialFormat
945+
): PresentationSubmission {
946+
val vpFormats = when (credentialFormat) {
947+
CredentialFormat.MDOC_MSO -> setOf("jwt_vc", "vc+sd-jwt")
948+
CredentialFormat.SD_JWT_VC -> setOf("mso_mdoc")
949+
}
916950

917-
internal fun createPresentationSubmission(authRequest: AuthorizationRequest): PresentationSubmission {
918951
val descriptorMaps = ArrayList<DescriptorMap>()
919952
val inputDescriptors = authRequest.presentationDefinition["input_descriptors"]!!.jsonArray
920953

921954
for (inputDescriptor: JsonElement in inputDescriptors) {
922955
val inputDescriptorObj = inputDescriptor.jsonObject
923956
val docType = inputDescriptorObj["id"]!!.toString().run { substring(1, this.length - 1) }
924-
val formatName = inputDescriptorObj["format"]?.jsonObject?.keys?.first() ?: "mso_mdoc"
925-
if (!validFormats.contains(formatName)) {
926-
throw IllegalArgumentException("Unexpected format ($formatName) in input_descriptors.")
957+
val formatName = inputDescriptorObj["format"]?.jsonObject?.keys?.first()
958+
if (formatName != null && vpFormats.contains(formatName)) {
959+
descriptorMaps.add(DescriptorMap(
960+
id = docType,
961+
format = formatName,
962+
path = "$"
963+
))
927964
}
928-
descriptorMaps.add(DescriptorMap(
929-
id = docType,
930-
format = formatName,
931-
path = "$"
932-
))
933965
}
934966

935967
return PresentationSubmission(

wallet/src/test/java/com/android/identity_credential/wallet/OpenID4VPTest.kt

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.android.identity_credential.wallet
22

33
import com.android.identity.document.DocumentRequest
4+
import com.android.identity.issuance.CredentialFormat
45
import com.android.identity_credential.wallet.presentation.DescriptorMap
56
import com.android.identity_credential.wallet.presentation.createPresentationSubmission
67
import com.android.identity_credential.wallet.presentation.formatAsDocumentRequest
@@ -84,7 +85,7 @@ class OpenID4VPTest {
8485
"\"id_token_signed_response_alg\":\"RS256\"}"),
8586
authRequest.clientMetadata!!)
8687

87-
val presentationSubmission = createPresentationSubmission(authRequest)
88+
val presentationSubmission = createPresentationSubmission(authRequest, CredentialFormat.MDOC_MSO)
8889
Assert.assertEquals("32f54163-7166-48f1-93d8-ff217bdb0653",
8990
presentationSubmission.definitionId)
9091
val descriptorMaps = presentationSubmission.descriptorMaps
@@ -142,7 +143,7 @@ class OpenID4VPTest {
142143
"\"vp_formats\":{\"mso_mdoc\":{\"alg\":[\"ES256\",\"ES384\",\"ES512\",\"EdDSA\",\"ESB256\",\"ESB320\",\"ESB384\",\"ESB512\"]}}}\n"),
143144
authRequest.clientMetadata!!)
144145

145-
val presentationSubmission = createPresentationSubmission(authRequest)
146+
val presentationSubmission = createPresentationSubmission(authRequest, CredentialFormat.MDOC_MSO)
146147
val descriptorMaps = presentationSubmission.descriptorMaps
147148
for (descriptorMap: DescriptorMap in descriptorMaps) {
148149
Assert.assertEquals("org.iso.18013.5.1.mDL ", descriptorMap.id)

0 commit comments

Comments
 (0)