Skip to content

Commit d8222f5

Browse files
authored
wallet: Add functionality to request identity documents. (#704)
Also add "sample requests" to our repository for well-known doctypes in the identity-doctypes library. Also upgrade to the latest version of https://github.com/yuriy-budiyev/code-scanner Test: Manually tested Test: ./gradlew check Test: ./gradlew connectedCheck Signed-off-by: David Zeuthen <zeuthen@google.com>
1 parent 493908a commit d8222f5

File tree

29 files changed

+1684
-34
lines changed

29 files changed

+1684
-34
lines changed

gradle/libs.versions.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ camera = "1.3.3"
3232
compose-material3 = "1.2.1"
3333
compose-material-icons-extended = "1.6.7"
3434
androidx-navigation = "2.7.7"
35-
code-scanner = "2.1.0"
35+
code-scanner = "2.3.2"
3636
ktor = "2.3.10"
3737
javax-servlet-api = "4.0.1"
3838
androidx-lifecycle = "2.2.0"
@@ -95,7 +95,7 @@ compose-material3 = { module = "androidx.compose.material3:material3", version.r
9595
compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose-material-icons-extended" }
9696
androidx-navigation-runtime = { group = "androidx.navigation", name = "navigation-runtime-ktx", version.ref = "androidx-navigation" }
9797
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation" }
98-
code-scanner = { module = " com.budiyev.android:code-scanner", version.ref = "code-scanner" }
98+
code-scanner = { module = "com.github.yuriy-budiyev:code-scanner", version.ref = "code-scanner" }
9999
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
100100
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
101101
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }

identity-doctypes/src/main/java/com/android/identity/documenttype/knowntypes/DrivingLicense.kt

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -758,6 +758,59 @@ object DrivingLicense {
758758
AAMVA_NAMESPACE,
759759
null
760760
)
761+
.addSampleRequest(
762+
"US Transportation",
763+
mapOf(
764+
Pair(MDL_NAMESPACE, listOf(
765+
"sex",
766+
"portrait",
767+
"given_name",
768+
"issue_date",
769+
"expiry_date",
770+
"family_name",
771+
"document_number",
772+
"issuing_authority",
773+
)),
774+
Pair(AAMVA_NAMESPACE, listOf(
775+
"DHS_compliance",
776+
"EDL_credential"
777+
))
778+
),
779+
)
780+
.addSampleRequest(
781+
"Age Over 21 + Portrait",
782+
mapOf(
783+
Pair(MDL_NAMESPACE, listOf(
784+
"age_over_21",
785+
"portrait"
786+
))
787+
),
788+
)
789+
.addSampleRequest(
790+
"Mandatory Data Elements",
791+
mapOf(
792+
Pair(MDL_NAMESPACE, listOf(
793+
"family_name",
794+
"given_name",
795+
"birth_date",
796+
"issue_date",
797+
"expiry_date",
798+
"issuing_country",
799+
"issuing_authority",
800+
"document_number",
801+
"portrait",
802+
"driving_privileges",
803+
"un_distinguishing_sign",
804+
))
805+
)
806+
)
807+
.addSampleRequest(
808+
"All Data Elements",
809+
mapOf(
810+
Pair(MDL_NAMESPACE, listOf()),
811+
Pair(AAMVA_NAMESPACE, listOf())
812+
)
813+
)
761814
.build()
762815
}
763816
}

identity-doctypes/src/main/java/com/android/identity/documenttype/knowntypes/EUPersonalID.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,36 @@ object EUPersonalID {
302302
EUPID_NAMESPACE,
303303
SampleData.ISSUING_COUNTRY.toDataItem()
304304
)
305+
.addSampleRequest(
306+
displayName = "Age Over 18",
307+
mdocDataElements = mapOf(
308+
Pair(EUPID_NAMESPACE, listOf(
309+
"age_over_18",
310+
))
311+
),
312+
)
313+
.addSampleRequest(
314+
displayName = "Mandatory Data Elements",
315+
mdocDataElements = mapOf(
316+
Pair(EUPID_NAMESPACE, listOf(
317+
"family_name",
318+
"given_name",
319+
"birth_date",
320+
"age_over_18",
321+
"issuance_date",
322+
"expiry_date",
323+
"issuing_authority",
324+
"issuing_country"
325+
))
326+
)
327+
)
328+
.addSampleRequest(
329+
displayName = "All Data Elements",
330+
mdocDataElements = mapOf(
331+
Pair(EUPID_NAMESPACE, listOf(
332+
))
333+
)
334+
)
305335
.build()
306336
}
307337
}

identity/src/commonMain/kotlin/com/android/identity/documenttype/DocumentType.kt

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ import com.android.identity.cbor.DataItem
3232
* no more than one paragraph.
3333
*
3434
* @param displayName the name suitable for display of the Document Type.
35+
* @param sampleRequests sample [DocumentWellKnownRequest] for the Document Type.
3536
* @param mdocDocumentType metadata of an mDoc Document Type (optional).
3637
* @param vcDocumentType metadata of a W3C VC Document Type (optional).
3738
*
3839
*/
3940
class DocumentType private constructor(
4041
val displayName: String,
42+
val sampleRequests: List<DocumentWellKnownRequest>,
4143
val mdocDocumentType: MdocDocumentType?,
4244
val vcDocumentType: VcDocumentType?
4345
) {
@@ -54,6 +56,8 @@ class DocumentType private constructor(
5456
var mdocBuilder: MdocDocumentType.Builder? = null,
5557
var vcBuilder: VcDocumentType.Builder? = null
5658
) {
59+
private val sampleRequests = mutableListOf<DocumentWellKnownRequest>()
60+
5761
/**
5862
* Initialize the [mdocBuilder].
5963
*
@@ -182,9 +186,48 @@ class DocumentType private constructor(
182186
?: throw Exception("The VC Document Type was not initialized")
183187
}
184188

189+
/**
190+
* Adds a sample request to the document.
191+
*
192+
* TODO: Add support for VC claims as well.
193+
*
194+
* @param displayName a short name explaining the request
195+
* @param mdocDataElements the mdoc data elements in the request, per namespace. If
196+
* the list of a namespace is empty, all defined data elements will be included.
197+
*/
198+
fun addSampleRequest(
199+
displayName: String,
200+
mdocDataElements: Map<String, List<String>>?
201+
) = apply {
202+
val mdocRequest = if (mdocDataElements == null) {
203+
null
204+
} else {
205+
val nsRequests = mutableListOf<MdocNamespaceRequest>()
206+
for ((namespace, dataElements) in mdocDataElements) {
207+
val mdocNsBuilder = mdocBuilder!!.namespaces[namespace]!!
208+
val deList = if (dataElements.isEmpty()) {
209+
mdocNsBuilder.dataElements.values.toList()
210+
} else {
211+
val list = mutableListOf<MdocDataElement>()
212+
for (dataElement in dataElements) {
213+
list.add(mdocNsBuilder.dataElements[dataElement]!!)
214+
}
215+
list
216+
}
217+
nsRequests.add(MdocNamespaceRequest(namespace, deList))
218+
}
219+
MdocRequest(mdocBuilder!!.docType, nsRequests)
220+
}
221+
sampleRequests.add(DocumentWellKnownRequest(displayName, mdocRequest))
222+
}
223+
185224
/**
186225
* Build the [DocumentType].
187226
*/
188-
fun build() = DocumentType(displayName, mdocBuilder?.build(), vcBuilder?.build())
227+
fun build() = DocumentType(
228+
displayName,
229+
sampleRequests,
230+
mdocBuilder?.build(),
231+
vcBuilder?.build())
189232
}
190233
}

identity/src/commonMain/kotlin/com/android/identity/documenttype/DocumentTypeRepository.kt

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,24 @@ class DocumentTypeRepository {
5353
_documentTypes.find {
5454
it.mdocDocumentType?.docType?.equals(mdocDocType) ?: false
5555
}
56+
57+
/**
58+
* Gets the first [DocumentType] in [documentTypes] with a given mdoc namespace.
59+
*
60+
* @param mdocNamespace the mdoc namespace name.
61+
* @return the [DocumentType] or null if not found.
62+
*/
63+
fun getDocumentTypeForMdocNamespace(mdocNamespace: String): DocumentType? {
64+
for (documentType in _documentTypes) {
65+
if (documentType.mdocDocumentType == null) {
66+
continue
67+
}
68+
for ((nsName, _) in documentType.mdocDocumentType.namespaces) {
69+
if (nsName == mdocNamespace) {
70+
return documentType
71+
}
72+
}
73+
}
74+
return null
75+
}
5676
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.android.identity.documenttype
2+
3+
4+
/**
5+
* Class representing a well-known document request.
6+
*
7+
* @param displayName a short string with the name of the request, short enough to be used
8+
* for a button. For example "Age Over 21 and Portrait" or "Full mDL".
9+
*/
10+
data class DocumentWellKnownRequest(
11+
val displayName: String,
12+
val mdocRequest: MdocRequest?,
13+
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.android.identity.documenttype
2+
3+
/**
4+
* A class representing a request for data elements in a namespace.
5+
*
6+
* @param namespace the namespace.
7+
* @param dataElementsToRequest the data elements to request.
8+
*/
9+
data class MdocNamespaceRequest(
10+
val namespace: String,
11+
val dataElementsToRequest: List<MdocDataElement>
12+
)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.android.identity.documenttype
2+
3+
/**
4+
* A class representing a request for a particular set of namespaces and data elements
5+
* for a particular document type.
6+
*
7+
* @param docType the mdoc doctype.
8+
* @param namespacesToRequest the namespaces to request.
9+
*/
10+
data class MdocRequest(
11+
val docType: String,
12+
val namespacesToRequest: List<MdocNamespaceRequest>
13+
)

settings.gradle.kts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ dependencyResolutionManagement {
2525
}
2626
}
2727
mavenCentral()
28-
jcenter() {
29-
content {
30-
includeGroup("com.budiyev.android")
28+
maven("https://jitpack.io") {
29+
mavenContent {
30+
includeGroup("com.github.yuriy-budiyev")
3131
}
3232
}
3333
}

wallet/build.gradle.kts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ android {
2626

2727
defaultConfig {
2828
applicationId = "com.android.identity_credential.wallet"
29-
minSdk = 27
29+
minSdk = 29
3030
targetSdk = libs.versions.android.targetSdk.get().toInt()
3131
versionCode = projectVersionCode
3232
versionName = projectVersionName
@@ -131,6 +131,7 @@ dependencies {
131131
implementation(libs.androidx.material)
132132
implementation(libs.face.detection)
133133
implementation(libs.zxing.core)
134+
implementation(libs.code.scanner)
134135

135136
implementation(files("../third-party/play-services-identity-credentials-0.0.1-eap01.aar"))
136137
implementation(libs.bundles.google.play.services)

wallet/src/customized/assets/webview/about.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
This is a customized flavor of the OWF Identity Credential Wallet.
22

3-
This application supports provisioning and presentation of real-world
4-
identity using the ISO/IEC 18013-5:2021 mdoc credential format.
3+
Wallet version __placeholder__{appinfo=version}
54

6-
Identity data in this application can be shared in-person to any
5+
This application supports provisioning, presentation, and reading
6+
of real-world identity using the ISO/IEC 18013-5:2021 mdoc and
7+
IETF SD-JWT credential formats.
8+
9+
Identity data in this application can shared in-person to any
710
ISO/IEC 18013-5:2021 compliant mdoc/mDL reader using NFC or QR
8-
engagement.
11+
engagement. The application also supports reader functionality
12+
for mdoc/mDLs.
913

1014
Identity data can also be shared to other Android applications or
11-
websites using a preview version of the
15+
websites using OpenID4VP and a preview version of the
1216
[Digital Identities API](https://wicg.github.io/digital-identities/)
1317
API being worked on in the
1418
[W3C WICG Digital Identities group](https://github.com/WICG/digital-identities).
@@ -17,5 +21,3 @@ API being worked on in the
1721

1822
* [OWF Identity Credential Home Page](https://github.com/openwallet-foundation-labs/identity-credential)
1923
* [IACA certificate](https://github.com/openwallet-foundation-labs/identity-credential/blob/main/wallet/src/main/res/raw/iaca_certificate.pem)
20-
21-
Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
Wallet version __placeholder__{appinfo=version}
22

3-
This application supports provisioning and presentation of real-world
4-
identity using the ISO/IEC 18013-5:2021 mdoc credential format.
3+
This application supports provisioning, presentation, and reading
4+
of real-world identity using the ISO/IEC 18013-5:2021 mdoc and
5+
IETF SD-JWT credential formats.
56

6-
Identity data in this application can be shared in-person to any
7+
Identity data in this application can shared in-person to any
78
ISO/IEC 18013-5:2021 compliant mdoc/mDL reader using NFC or QR
8-
engagement.
9+
engagement. The application also supports reader functionality
10+
for mdoc/mDLs.
911

1012
Identity data can also be shared to other Android applications or
11-
websites using a preview version of the
13+
websites using OpenID4VP and a preview version of the
1214
[Digital Identities API](https://wicg.github.io/digital-identities/)
1315
API being worked on in the
1416
[W3C WICG Digital Identities group](https://github.com/WICG/digital-identities).
@@ -17,5 +19,3 @@ API being worked on in the
1719

1820
* [OWF Identity Credential Home Page](https://github.com/openwallet-foundation-labs/identity-credential)
1921
* [IACA certificate](https://github.com/openwallet-foundation-labs/identity-credential/blob/main/wallet/src/main/res/raw/iaca_certificate.pem)
20-
21-

wallet/src/main/java/com/android/identity_credential/wallet/MainActivity.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ class MainActivity : FragmentActivity() {
9090
permissionTracker = permissionTracker,
9191
sharedPreferences = application.sharedPreferences,
9292
qrEngagementViewModel = qrEngagementViewModel,
93-
documentModel = application.documentModel
93+
documentModel = application.documentModel,
94+
readerModel = application.readerModel,
9495
)
9596
}
9697
}

wallet/src/main/java/com/android/identity_credential/wallet/PresentationActivity.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ class PresentationActivity : FragmentActivity() {
344344
// See if we recognize the reader/verifier
345345
var trustPoint: TrustPoint? = null
346346
if (docRequest.readerAuthenticated) {
347-
val result = walletApp.trustManager.verify(
347+
val result = walletApp.readerTrustManager.verify(
348348
docRequest.readerCertificateChain!!.javaX509Certificates,
349349
customValidators = emptyList() // not needed for reader auth
350350
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.android.identity_credential.wallet
2+
3+
import android.graphics.Bitmap
4+
import com.android.identity.documenttype.MdocDataElement
5+
6+
data class ReaderDataElement(
7+
// Null if the data element isn't known
8+
val mdocDataElement: MdocDataElement?,
9+
10+
val value: ByteArray,
11+
12+
// Only set DocumentAttributeType.Picture
13+
val bitmap: Bitmap?,
14+
)
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.android.identity_credential.wallet
2+
3+
import kotlinx.datetime.Instant
4+
5+
data class ReaderDocument(
6+
val docType: String,
7+
val msoValidFrom: Instant,
8+
val msoValidUntil: Instant,
9+
val msoSigned: Instant,
10+
val msoExpectedUpdate: Instant?,
11+
val namespaces: List<ReaderNamespace>,
12+
val infoTexts: List<String>,
13+
val warningTexts: List<String>,
14+
)

0 commit comments

Comments
 (0)