WICG / digital-credentials

Digital Credentials, like driver's licenses
https://wicg.github.io/digital-credentials/
Other
67 stars 8 forks source link

[HOWTO] Try the Prototype API in Chrome/Android #36

Closed samuelgoto closed 6 months ago

samuelgoto commented 8 months ago

Identity Credentials Quick Start Guide

Sept 2023

This HOWTO will guide you through to with with the prototype of the API in Chrome and Android. It is a bit cumbersome at the moment because it is in its early stage of incubation (e.g. protected by flags). If you run into problems, feel free to ping @leecam or @samuelgoto on slack.

Test device setup

  1. Enable the Identity Credential API on Android
    1. Enroll your Android device in the Google Play Services Beta program as per instructions here:
      https://developers.google.com/android/guides/beta-program
    2. Once Google Play Services has updated, ensure the version is greater or equal to 23.40.xx. This can be found by going to do Settings->All apps->Google Play Services
  2. Enable the Identity Credential API in Chrome
    1. Install the chrome canary build https://play.google.com/store/apps/details?id=com.chrome.canary&hl=en_US&gl=US
    2. Open chrome and go to chrome://flags, search for digital credentials and enable that flag

Testing the setup

You should now be able to test the Identity Credential with our sample apps to verify your setup:

  1. Install the sample wallet app here. It will appear in the launcher as IC Purse
    adb install -t <path-to-apk>
  2. Install the sample reader app here. It will appear in the launcher as App Verifier
    adb install -t <path-to-apk>
  3. Launch the App Holder app and provision a new mDL
    1. Tap the menu button and select Add Self Signed Document
    2. This document will now be available for presentment to native apps and websites
  4. Launch the App Verifier app and tap Request via Credential Manager. This should invoke the Credential Selector UX showing the available documents that match the request. At this point you see the mDL you provisioned above.
  5. Select the mDL. The Verifier app will now show the information it received.
  6. You can also test via the web.
    1. Navigate Chrome Canary to www.identitycredential.dev and ensure you also request the mDL successfully.

You can use this website and reader app to test and verify your wallet application. You should see your credentials alongside credentials from our sample wallet.

If you got this far, you should have something that looks more or less like the following:

https://www.youtube.com/watch?v=mZeSVNK0jlw

Verifier API

You can build a website that uses the JS API to request documents on the web.

// Gets a CBOR with specific fields out of mobile driver's license as an mdoc
const {response} = await navigator.credentials.get({
  identity: {
    providers: [{
      holder: {
        selector: {
          format: ["mdoc"],
          retention: {days: 90},
          doctype: "org.iso.18013.5.1.mDL",
          fields: [
            "org.iso.18013.5.1.document_number",
            "org.iso.18013.5.1.portrait",
            "org.iso.18013.5.1.driving_privileges",
            "org.iso.18013.5.1.aamva.organ_donor",
          ],
        },
        params: {
          nonce: "gf69kepV+m5tGxUIsFtLi6pwg=",
          readerPublicKey: "ftl+VEHPB17r2 ... Nioc9QZ7X/6w...",
        }
      }
    }],
  }
});

You can also build a native app verifier by calling the following API:

TODO(@leecam): write the instructions on how to use the CredMan API for requesting digital credentials

Holder API

Building the sample wallet app

You can build our samples as a starting point and have a play.

  1. Setup a local maven repo. Download the local maven repo from here
    # cd ~/.m2/repository
    # unzip idsdk.zip
  2. Check out the sample code The code for the sample apps is here https://github.com/google/identity-credential/tree/android-credential-manager Make sure you use the android-credential-manager branch
  3. Load the project in Android Studio
  4. You can build the App Holder app and install it on your device

You should now be able to use the use the demo apps as before

Setting up the SDK in your app

Note: Remember to only use the two app package names you shared with us. We allow-listed them to use this API while the API is still under development so that we can control backwards incompatible breaking changes.

  1. You need to add the local maven repo to your settings.gradle file.
  2. You can do this by adding mavenLocal() to the repositories section.
  3. Next add the SDK dependency to your build.gradle.
    implementation 'com.google.android.gms:play-services-identity-credentials:0.0.1-eap01'

e.g https://github.com/google/identity-credential/blob/android-credential-manager/appholder/build.gradle#L80

Where to look in the sample wallet

The credential registration happens here: https://github.com/google/identity-credential/blob/android-credential-manager/appholder/src/main/java/com/android/mdl/app/document/DocumentManager.kt#L88

The Provider API

Note: This API will be provided as part of the Credential Manager Jetpack Library. Unfortunately we can’t share this with you directly quite yet. Instead you’ll use some slightly lower-level APIs. Jetpack just provides more developer friendly wrappers over the API you’ll be using today. It's still fairly straightforward but note that when this API is released the public API will be exposed via Cred Man.

The incoming request parameters are provided to your wallet app as a JSON string. This JSON string is provided by the calling RP application. The specification of this JSON is currently being defined by the W3C, but this API doesn’t concern itself with its contents. It is the responsibility of your wallet app to parse this request and form the response.

Chrome and our test apps provide the JSON in the following form. This is just a simple request format to demonstrate the API, this will likely evolve in the W3C working group. This is the RedBox in David’s ISO presentation.

{
  "providers": [
    {
      "responseFormat": "mdoc",
      "selector": {
        "fields": [
          {
            "name": "doctype",
            "equal": "org.iso.18013.5.1.mDL"
          },
          {
            "name": "org.iso.18013.5.1.portrait"
          },
          {
            "name": "org.iso.18013.5.1.family_name"
          },
          {
            "name": "org.iso.18013.5.1.given_name"
          },
          {
            "name": "org.iso.18013.5.1.document_number"
          },
          {
            "name": "org.iso.18013.5.1.expiry_date"
          },
          {
            "name": "org.iso.18013.5.1.issue_date"
          },
          {
            "name": "org.iso.18013.5.1.age_over_18"
          },
          {
            "name": "org.iso.18013.5.1.aamva.DHS_compliance"
          },
          {
            "name": "org.iso.18013.5.1.aamva.EDL_credential"
          }
        ]
      },
      "params": {
        "nonce": "ZWuRXfcV7-iRkZH4puWnRA==",
        "requesterIdentity": "BOHjVu78FDoBZdxmSn6EOfcC1Eam8c9XCKjblABWpt4="
      }
    }
  ]
}

The provider API allows you to define the matching logic used by your wallet to decide which documents/credentials to show in Credential Selection UI for a given json request.

This matcher logic is defined as a wasm module that you register with the system as follows

val registrationRequest = RegistrationRequest(
      credentials = yourMetaData,  // A binary blob that we pass to your matcher
      matcher = yourMatcherBinary, // The wasm module 
      type = "com.credman.IdentityCredential" // has to set to this
    )

val client = IdentityCredentialManager.Companion.getClient(context)
client.registerCredentials(registrationRequest)

Android will execute your wasm matcher in a sandbox upon receiving a request from an RP application or website. The matcher binary will be provided with the credential data blob you provide as part of registration, the incoming request json from the calling RP and the calling app information (calling packagename or origin). The matcher's job is to parse the incoming request and to populate the entries in the selector UX.

As per above, we will provide more developer friendly APIs in jetpack towards the end of the year. This includes default matchers and helper classes. So most wallets won’t need to deal with writing their own matcher unless they have some complex matching logic or want to support a new credential type.

For this proof of concept you can use the matcher from our demo app. You can place it in your assets folder in your app. You can find it here: https://github.com/google/identity-credential/tree/android-credential-manager/appholder/src/main/assets

There are 3 helper classes that you can copy and paste into your wallet app, they can be found here: https://github.com/google/identity-credential/tree/android-credential-manager/appholder/src/main/java/com/android/mdl/app/credman

These helpers use the provided matcher and build up the credential data in a structure the matcher understands.

You can use these helpers to register a simple credential as follows:

val fields = mutableListOf<IdentityCredentialField>()
// Add the doc type field
fields.add(IdentityCredentialField(
name = "doctype",
       value = "fakedoc",
       displayName = "Document Type",
       displayValue = "Fake doc type"
))

// Add a name field
fields.add(IdentityCredentialField(
name = "firstname",  // field name is required for matching the fields in the json
       value = "Erika",  // the vaule is optional.
       displayName = "First Name", // required to show the matched fields in the selector
       displayValue = "Erika"  // the vaule is optional.
))

// Create the entry
val entry = listOf(IdentityCredentialEntry(
id = 1, // will be passed to your app if the credential is picked by the user
       format = "mdoc",
                title = "Erika's's Driving License",
                subtitle = "California DMV",
                icon = BitmapFactory.decodeResource(context.resources, R.mylogo),
                fields = fields.toList(),
                disclaimer = null,
                warning = null,
 ))

// Create the registry with the list of entries
val registry = IdentityCredentialRegistry(listOf(entry))
// Register using the stock matcher 
val client = IdentityCredentialManager.Companion.getClient(context)
client.registerCredentials(registry.toRegistrationRequest(context))

Once you implement the above registration flow, you can test the web app, your credentials should appear in the selector (assuming they have the required fields to be considered a match).

Invocation

This API attempts to provide a huge amount of flexibility for the wallet application. The goal is to just handle credential selection and wallet invocation. All Android requires is that the wallet (via the matcher) provides enough information about the credential and the requested attributes that we can render a selector. This information allows the user to make an informed choice about which document to proceed with.

Once a credential is selected by the user Android will intent into the wallet app, where it can show its own UI to gather consent from the user, e.g by showing a biometric prompt. Our sample app doesn’t show any UI, but we suggest your app shows at least a biometric prompt.

You need to add a new Activity to your app with the following intent handler in the manifest com.google.android.gms.identitycredentials.action.GET_CREDENTIALS

Here is an example:

<activity
            android:label="@string/app_name"
            android:name=".GetCredentialActivity"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="androidx.identitycredentials.action.GET_CREDENTIALS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

Android will invoke this Activity if your credential is selected by the user. You should use it to obtain user consent and form the response.

Again helper libs will do most of this heavy lifting in the future but for now you’ll be exposed to a bit of the plumbing.

Our sample Activity is here: https://github.com/google/identity-credential/blob/android-credential-manager/appholder/src/main/java/com/android/mdl/app/GetCredentialActivity.kt

In your onCreate method you should obtain the request:

// The JSON from the calling app
val request = extractGetCredentialRequest(intent)

// the credential ID of the selected credential (registered above)
val credentialId = intent.getLongExtra(EXTRA_CREDENTIAL_ID, -1)

// The calling app info
val callingAppInfo = extractCallingAppInfo(intent)

You should parse the request and generate the response for the selected credential.

The response is provided as a ByteArray. Our demo apps place a base64 encoded string of encrypted response in this byte array. (at some point we’ll change this to a string)

This string is passed directly back to the calling app. Again Android does not concern itself with the format of the request or the response. We leave it to the wallet to understand the format of the request and generate the response.

The response is provided as follows (again helpers in the future will hide some of these gory details):

val bundle = Bundle()
// you need to generate the encodedCredentialDocument
bundle.putByteArray("identityToken", Base64.encodeToString(encodedCredentialDocument, Base64.NO_WRAP or Base64.URL_SAFE).toByteArray())
val credentialResponse = com.google.android.gms.identitycredentials.Credential("type", bundle)
val response = GetCredentialResponse(credentialResponse)
val resultData = Intent()
setGetCredentialResponse(resultData, response)
setResult(RESULT_OK, resultData)
finish()

How to generate the response

The best way is probably to look at the sample code :)

The logic starts here: https://github.com/google/identity-credential/blob/android-credential-manager/appholder/src/main/java/com/android/mdl/app/GetCredentialActivity.kt#L151

Much of the heavy lifting is performed in this class: https://github.com/google/identity-credential/blob/android-credential-manager/identity-android/src/main/java/com/android/identity/android/mdoc/util/CredmanUtil.kt

Its a standard mdoc device response CBOR, encrypted with HPKE. The main change is the session transcript, which is generated here: https://github.com/google/identity-credential/blob/android-credential-manager/appholder/src/main/java/com/android/mdl/app/GetCredentialActivity.kt#L158

Note: We always set the calling package name to "com.android.mdl.appreader" in our sample apps, so you’ll need to do this too until we fix this hack.

QZHelen commented 8 months ago

Install the latest sample wallet app and sample reader app that up to date with the latest github commit.

TallTed commented 8 months ago

I suggest making this How-To (and any others) into git-managed .md docs akin to a repo's README.md, or maybe into a wiki page, if you link to it from the README and similar, so it's more discoverable than GitHub wiki pages tend to be, not to mention any not-really-an-issue [HOWTO] comment(s).

This will make it much easier for others to contribute, and you for to apply their contributions, to your How-To and other docs, than working through any issue's comments.

samuelgoto commented 8 months ago

Yeah, I'm open to moving this to anything that makes the collaboration easier/faster.

I recently ran into a github feature that allows anyone to edit a comment, even when it is authored by someone else. @QZHelen can you give that a try to update the links?

The benefit of issues (over markdown files) is that (a) you can have a comment thread where people can contribute to the discussion and (b) you can edit directly in the UI (as opposed to git clone and PR-ing), which I think substantially decreases the collaboration costs.

Again, happy to move this to wherever is most effective, but I wanted to give github issues a try before moving somewhere else.

TallTed commented 8 months ago

@samuelgoto —

Let's say, I want to suggest a change to a paragraph in your "initial comment". Here, I must quote that paragraph, and then provide my suggestion as both "markdown escaped" and "markdown processed", so you can see both my desired end-result and the explicit content to be pasted into your "initial comment". This must be repeated for each paragraph (or other reasonably-sized block). If I make several suggestions, they must either be blended into one comment (making each one harder to track), or split into multiple comments (making each one harder than that to track.).

If the article were a managed markdown document, I would use GitHub's web interface (virtually the only way I interact with GitHub) to edit the document — and GitHub would automatically handle cloning the repo and starting a new branch in my clone — and saving my changes in that browser-based editor would automatically lead to creation of a PR. Anyone with comments on my suggestions can make them on the PR, and after comments are resolved, the PR can be merged into the document.

About GitHub's "feature that allows anyone to edit a comment" — How would you track individual contributions, or discussions about them? My experience of comment edits is that you can see any version, but you cannot see the diffs between those versions, neither save-by-save nor version_x-vs-version_y. Also, there's no announcement/notification of such edits of comments. If you must go in a direction like this, I would push for using GitHub's wiki — though I generally advise against this tool, because while it lacks many features commonly found in other wikis (e.g., comparison of arbitrary versions of a given wiki page, such as seeing all changes made since my own last edit) — because it's at least slightly easier to see when a change happens, via RSS feed, though not via GitHub's normal notifications.

All of which can be summed up as: I strongly advise using a Git-managed (markdown or other format) document for collaborative editing on GitHub.

timcappalli commented 6 months ago

These instructions have moved to the Wiki: https://github.com/WICG/identity-credential/wiki/HOWTO%3A-Try-the-Prototype-API-in-Chrome-Android