Skip to content

Commit

Permalink
wallet: Add functionality to request identity documents. (#704)
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
davidz25 authored Aug 16, 2024
1 parent 493908a commit d8222f5
Show file tree
Hide file tree
Showing 29 changed files with 1,684 additions and 34 deletions.
4 changes: 2 additions & 2 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ camera = "1.3.3"
compose-material3 = "1.2.1"
compose-material-icons-extended = "1.6.7"
androidx-navigation = "2.7.7"
code-scanner = "2.1.0"
code-scanner = "2.3.2"
ktor = "2.3.10"
javax-servlet-api = "4.0.1"
androidx-lifecycle = "2.2.0"
Expand Down Expand Up @@ -95,7 +95,7 @@ compose-material3 = { module = "androidx.compose.material3:material3", version.r
compose-material-icons-extended = { module = "androidx.compose.material:material-icons-extended", version.ref = "compose-material-icons-extended" }
androidx-navigation-runtime = { group = "androidx.navigation", name = "navigation-runtime-ktx", version.ref = "androidx-navigation" }
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "androidx-navigation" }
code-scanner = { module = " com.budiyev.android:code-scanner", version.ref = "code-scanner" }
code-scanner = { module = "com.github.yuriy-budiyev:code-scanner", version.ref = "code-scanner" }
ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" }
ktor-client-core = { module = "io.ktor:ktor-client-core", version.ref = "ktor" }
ktor-client-java = { module = "io.ktor:ktor-client-java", version.ref = "ktor" }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -758,6 +758,59 @@ object DrivingLicense {
AAMVA_NAMESPACE,
null
)
.addSampleRequest(
"US Transportation",
mapOf(
Pair(MDL_NAMESPACE, listOf(
"sex",
"portrait",
"given_name",
"issue_date",
"expiry_date",
"family_name",
"document_number",
"issuing_authority",
)),
Pair(AAMVA_NAMESPACE, listOf(
"DHS_compliance",
"EDL_credential"
))
),
)
.addSampleRequest(
"Age Over 21 + Portrait",
mapOf(
Pair(MDL_NAMESPACE, listOf(
"age_over_21",
"portrait"
))
),
)
.addSampleRequest(
"Mandatory Data Elements",
mapOf(
Pair(MDL_NAMESPACE, listOf(
"family_name",
"given_name",
"birth_date",
"issue_date",
"expiry_date",
"issuing_country",
"issuing_authority",
"document_number",
"portrait",
"driving_privileges",
"un_distinguishing_sign",
))
)
)
.addSampleRequest(
"All Data Elements",
mapOf(
Pair(MDL_NAMESPACE, listOf()),
Pair(AAMVA_NAMESPACE, listOf())
)
)
.build()
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,36 @@ object EUPersonalID {
EUPID_NAMESPACE,
SampleData.ISSUING_COUNTRY.toDataItem()
)
.addSampleRequest(
displayName = "Age Over 18",
mdocDataElements = mapOf(
Pair(EUPID_NAMESPACE, listOf(
"age_over_18",
))
),
)
.addSampleRequest(
displayName = "Mandatory Data Elements",
mdocDataElements = mapOf(
Pair(EUPID_NAMESPACE, listOf(
"family_name",
"given_name",
"birth_date",
"age_over_18",
"issuance_date",
"expiry_date",
"issuing_authority",
"issuing_country"
))
)
)
.addSampleRequest(
displayName = "All Data Elements",
mdocDataElements = mapOf(
Pair(EUPID_NAMESPACE, listOf(
))
)
)
.build()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,12 +32,14 @@ import com.android.identity.cbor.DataItem
* no more than one paragraph.
*
* @param displayName the name suitable for display of the Document Type.
* @param sampleRequests sample [DocumentWellKnownRequest] for the Document Type.
* @param mdocDocumentType metadata of an mDoc Document Type (optional).
* @param vcDocumentType metadata of a W3C VC Document Type (optional).
*
*/
class DocumentType private constructor(
val displayName: String,
val sampleRequests: List<DocumentWellKnownRequest>,
val mdocDocumentType: MdocDocumentType?,
val vcDocumentType: VcDocumentType?
) {
Expand All @@ -54,6 +56,8 @@ class DocumentType private constructor(
var mdocBuilder: MdocDocumentType.Builder? = null,
var vcBuilder: VcDocumentType.Builder? = null
) {
private val sampleRequests = mutableListOf<DocumentWellKnownRequest>()

/**
* Initialize the [mdocBuilder].
*
Expand Down Expand Up @@ -182,9 +186,48 @@ class DocumentType private constructor(
?: throw Exception("The VC Document Type was not initialized")
}

/**
* Adds a sample request to the document.
*
* TODO: Add support for VC claims as well.
*
* @param displayName a short name explaining the request
* @param mdocDataElements the mdoc data elements in the request, per namespace. If
* the list of a namespace is empty, all defined data elements will be included.
*/
fun addSampleRequest(
displayName: String,
mdocDataElements: Map<String, List<String>>?
) = apply {
val mdocRequest = if (mdocDataElements == null) {
null
} else {
val nsRequests = mutableListOf<MdocNamespaceRequest>()
for ((namespace, dataElements) in mdocDataElements) {
val mdocNsBuilder = mdocBuilder!!.namespaces[namespace]!!
val deList = if (dataElements.isEmpty()) {
mdocNsBuilder.dataElements.values.toList()
} else {
val list = mutableListOf<MdocDataElement>()
for (dataElement in dataElements) {
list.add(mdocNsBuilder.dataElements[dataElement]!!)
}
list
}
nsRequests.add(MdocNamespaceRequest(namespace, deList))
}
MdocRequest(mdocBuilder!!.docType, nsRequests)
}
sampleRequests.add(DocumentWellKnownRequest(displayName, mdocRequest))
}

/**
* Build the [DocumentType].
*/
fun build() = DocumentType(displayName, mdocBuilder?.build(), vcBuilder?.build())
fun build() = DocumentType(
displayName,
sampleRequests,
mdocBuilder?.build(),
vcBuilder?.build())
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,24 @@ class DocumentTypeRepository {
_documentTypes.find {
it.mdocDocumentType?.docType?.equals(mdocDocType) ?: false
}

/**
* Gets the first [DocumentType] in [documentTypes] with a given mdoc namespace.
*
* @param mdocNamespace the mdoc namespace name.
* @return the [DocumentType] or null if not found.
*/
fun getDocumentTypeForMdocNamespace(mdocNamespace: String): DocumentType? {
for (documentType in _documentTypes) {
if (documentType.mdocDocumentType == null) {
continue
}
for ((nsName, _) in documentType.mdocDocumentType.namespaces) {
if (nsName == mdocNamespace) {
return documentType
}
}
}
return null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.android.identity.documenttype


/**
* Class representing a well-known document request.
*
* @param displayName a short string with the name of the request, short enough to be used
* for a button. For example "Age Over 21 and Portrait" or "Full mDL".
*/
data class DocumentWellKnownRequest(
val displayName: String,
val mdocRequest: MdocRequest?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.android.identity.documenttype

/**
* A class representing a request for data elements in a namespace.
*
* @param namespace the namespace.
* @param dataElementsToRequest the data elements to request.
*/
data class MdocNamespaceRequest(
val namespace: String,
val dataElementsToRequest: List<MdocDataElement>
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.android.identity.documenttype

/**
* A class representing a request for a particular set of namespaces and data elements
* for a particular document type.
*
* @param docType the mdoc doctype.
* @param namespacesToRequest the namespaces to request.
*/
data class MdocRequest(
val docType: String,
val namespacesToRequest: List<MdocNamespaceRequest>
)
6 changes: 3 additions & 3 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ dependencyResolutionManagement {
}
}
mavenCentral()
jcenter() {
content {
includeGroup("com.budiyev.android")
maven("https://jitpack.io") {
mavenContent {
includeGroup("com.github.yuriy-budiyev")
}
}
}
Expand Down
3 changes: 2 additions & 1 deletion wallet/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ android {

defaultConfig {
applicationId = "com.android.identity_credential.wallet"
minSdk = 27
minSdk = 29
targetSdk = libs.versions.android.targetSdk.get().toInt()
versionCode = projectVersionCode
versionName = projectVersionName
Expand Down Expand Up @@ -131,6 +131,7 @@ dependencies {
implementation(libs.androidx.material)
implementation(libs.face.detection)
implementation(libs.zxing.core)
implementation(libs.code.scanner)

implementation(files("../third-party/play-services-identity-credentials-0.0.1-eap01.aar"))
implementation(libs.bundles.google.play.services)
Expand Down
16 changes: 9 additions & 7 deletions wallet/src/customized/assets/webview/about.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
This is a customized flavor of the OWF Identity Credential Wallet.

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

Identity data in this application can be shared in-person to any
This application supports provisioning, presentation, and reading
of real-world identity using the ISO/IEC 18013-5:2021 mdoc and
IETF SD-JWT credential formats.

Identity data in this application can shared in-person to any
ISO/IEC 18013-5:2021 compliant mdoc/mDL reader using NFC or QR
engagement.
engagement. The application also supports reader functionality
for mdoc/mDLs.

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

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


14 changes: 7 additions & 7 deletions wallet/src/main/assets/webview/about.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
Wallet version __placeholder__{appinfo=version}

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

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

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

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


Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,8 @@ class MainActivity : FragmentActivity() {
permissionTracker = permissionTracker,
sharedPreferences = application.sharedPreferences,
qrEngagementViewModel = qrEngagementViewModel,
documentModel = application.documentModel
documentModel = application.documentModel,
readerModel = application.readerModel,
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ class PresentationActivity : FragmentActivity() {
// See if we recognize the reader/verifier
var trustPoint: TrustPoint? = null
if (docRequest.readerAuthenticated) {
val result = walletApp.trustManager.verify(
val result = walletApp.readerTrustManager.verify(
docRequest.readerCertificateChain!!.javaX509Certificates,
customValidators = emptyList() // not needed for reader auth
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.android.identity_credential.wallet

import android.graphics.Bitmap
import com.android.identity.documenttype.MdocDataElement

data class ReaderDataElement(
// Null if the data element isn't known
val mdocDataElement: MdocDataElement?,

val value: ByteArray,

// Only set DocumentAttributeType.Picture
val bitmap: Bitmap?,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.android.identity_credential.wallet

import kotlinx.datetime.Instant

data class ReaderDocument(
val docType: String,
val msoValidFrom: Instant,
val msoValidUntil: Instant,
val msoSigned: Instant,
val msoExpectedUpdate: Instant?,
val namespaces: List<ReaderNamespace>,
val infoTexts: List<String>,
val warningTexts: List<String>,
)
Loading

0 comments on commit d8222f5

Please sign in to comment.