Online Acceptance of Digital Credentials

Digital IDs can be accepted both in-app and web flows. To accept credentials from Google Wallet you will need to:

  1. Integrate using app or web following the provided instructions and
  2. Fill out this form to request and agree to the terms of service of accepting credentials from Google Wallet.

Prerequisites

To test presentation of IDs digitally, you must first enroll in the public beta program using the intended test account (this needs to be a gmail account). Subsequently, furnish the ensuing details to your designated Google contact.

  • Terms of Service link
  • Logo
  • Website
  • App package IDs (for Android app integrations)
    • Including dev / debug builds
  • App signature
    • $ $ANDROID_SDK/build-tools/$BUILD_TOOLS_VERSION/apksigner verify --print-certs -v $APK
  • Gmail ID that was used to join the public beta

Supported Credential Formats

Several proposed standards exist that define the data format of digital identity documents, with two gaining significant industry traction:

  1. mdocs - defined by ISO.
  2. w3c Verifiable Credentials - defined by the w3c.

While the Android Credential Manager supports both the formats, Google Wallet supports only mdoc based Digital IDs at the moment.

Supported Credentials

Google Wallet supports 2 credential types:

  1. Mobile Drivers License (mDL)
  2. ID pass

You can request either credential in your flow with a single parameter change.

User experience

This section talks about the recommended online presentation flow. The flow shows presentation of age to an app for alcohol delivery, but UX is similar for web as well as other types of presentations.

User prompted to verify age in app or website User sees available eligible credentials User sees confirmation page in Google Wallet User Authenticates to confirm sharing Data sent to app or website
User prompted to verify age in app or website User sees available eligible credentials User sees confirmation page in Google Wallet User Authenticates to confirm sharing Data sent to app or website

Key Notes

  1. The app or website has flexibility in how they create the entry point to the API. As shown in Step 1, we recommend showing a generic button such as "Verify with digital ID" since overtime we expect options beyond Google Wallet to be available through the API.
  2. The selector screen in step 2 is rendered by Android. Eligible credentials are determined by a match between the registration logic provided by each Wallet and the request sent by the relying party
  3. Step 3 is rendered by Google Wallet. Google Wallet will show the name, logo and privacy policy the developer provides on this screen.

Add a digital ID flow

In the case the user does not have a credential we recommend providing a link next to the "Verify with digital ID" button which will deep-link out to Google Wallet to allow the user to add a digital ID.

User prompted to verify age in app or website User taken to Google Wallet to get a digital ID
User prompted to verify age in app or website User taken to Google Wallet to get a digital ID

No digital ID available

If the user selects the "Verify with digital ID" option without having a digital ID they will be shown this error message.

User prompted to verify age in app or website User shown an error is they don't have a digital ID
User prompted to verify age in app or website User shown an error is they don't have a digital ID

The API does not support a feature to silently learn whether the user has any available digital IDs to preserve the user's privacy. Hence we recommend including the onboarding link option as shown.

Request Format for requesting ID credentials from wallet

Here is a sample of an mdoc requestJson request to get Identity credentials from any wallet on an Android device or web.

{
      "requests" : [
        {
          "protocol": "openid4vp",
          "data": {<credential_request>} // This is an object, shouldn't be a string.
        }
      ]
}

Request Encryption

The client_metadata contains the encryption public key for each request. you'll have to store private keys for each request and use it to authenticate and authorize the token that you receive from the wallet app.

The credential_request parameter in requestJson would consist the following fields.

{
  "response_type": "vp_token",
  "response_mode": "dc_api.jwt",
  "nonce": "1234",
  "dcql_query": {
    "credentials": [
      {
        "id": "cred1",
        "format": "mso_mdoc",
        "meta": {
          "doctype_value": "org.iso.18013.5.1.mDL"  // this is for mDL. Use com.google.wallet.idcard.1 for ID pass
        },
        "claims": [
          {
            "path": [
              "org.iso.18013.5.1",
              "family_name"
            ]
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ]
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ]
          }
        ]
      }
    ]
  },
  "client_metadata": {
    "jwks": {
      "keys": [ // sample request encryption key
        {
          "kty": "EC",
          "crv": "P-256",
          "x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
          "y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ",
          "use": "enc",
          "kid" : "1",
          "alg" : "ECDH-ES",
        }
      ]
    },
    "authorization_encrypted_response_alg": "ECDH-ES",
    "authorization_encrypted_response_enc": "A128GCM"
  }
}

You can request for any number of supported attributes from any identity credential stored in Google Wallet.

In App

To request identity credentials from your Android apps follow these steps:

Update dependencies

In your project's build.gradle, update your dependencies to use Credential Manager (beta):

dependencies {
    implementation("androidx.credentials:credentials:1.5.0-beta01")
    // optional - needed for credentials support from play services, for devices running Android 13 and below.
    implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}

Configure the Credential Manager

To configure and initialize a CredentialManager object, add logic similar to the following:

// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)

Request Identity attributes

Instead of specifying individual parameters for identity requests, the app provides them all together as a JSON string within the CredentialOption. The Credential Manager passes this JSON string along to the available digital wallets without examining its contents. Each wallet is then responsible for: - Parsing the JSON string to understand the identity request. - Determining which of its stored credentials, if any, satisfy the request.

We recommend partners to create their requests on the server even for android app integrations.

you'll use the requestJson from Request Format consisting the request in GetDigitalCredentialOption() function call

// The request in the JSON format to conform with
// the JSON-ified Digital Credentials API request definition.
val requestJson = generateRequestFromServer()
val digitalCredentialOption =
    GetDigitalCredentialOption(requestJson = requestJson)

// Use the option from the previous step to build the `GetCredentialRequest`.
val getCredRequest = GetCredentialRequest(
    listOf(digitalCredentialOption)
)

coroutineScope.launch {
    try {
        val result = credentialManager.getCredential(
            context = activityContext,
            request = getCredRequest
        )
        verifyResult(result)
    } catch (e : GetCredentialException) {
        handleFailure(e)
    }
}

Verify and Validate the response

Once you get a response back from the wallet, you will verify if the response is successful and contains the credentialJson response.

// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
    val credential = result.credential
    when (credential) {
        is DigitalCredential -> {
            val responseJson = credential.credentialJson
            validateResponseOnServer(responseJson) // make a server call to validate the response
        }
        else -> {
            // Catch any unrecognized credential type here.
            Log.e(TAG, "Unexpected type of credential ${credential.type}")
        }
    }
}

// Handle failure.
fun handleFailure(e: GetCredentialException) {
  when (e) {
        is GetCredentialCancellationException -> {
            // The user intentionally canceled the operation and chose not
            // to share the credential.
        }
        is GetCredentialInterruptedException -> {
            // Retry-able error. Consider retrying the call.
        }
        is NoCredentialException -> {
            // No credential was available.
        }
        else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
    }
}

The credentialJson response contain an encrypted identityToken (JWT), defined by the W3C. The Wallet app is responsible for crafting this response.

Example:

"response" : {
  <encrpted_response>
}

You'll pass this response back to the server to validate it's authenticity. You can find the steps to validate credential response.

Web

To request Identity Credentials using the Digital Credentials API on Chrome, you'll need to sign up for the Digital Credentials API origin trial.

const credentialResponse = await navigator.credentials.get({
          digital : {
          requests : [
            {
              protocol: "openid4vp",
              data: {<credential_request>} // This is an object, shouldn't be a string.
            }
          ]
        }
      })

Send the response from this api back to your server to validate credential response

Steps to Validate credential response

Upon receiving the encrypted identityToken from your app or website there are multiple validations you'll need to perform before trusting the response.

  1. Decrypt response using private key

    The first step is to decrypt the token using the saved private key and get a response JSON.

    Python Example:

    from jwcrypto import jwe, jwk
    
    # Retrieve the Private Key from Datastore
    reader_private_jwk = jwk.JWK.from_json(jwe_private_key_json_str)
    
    # Decrypt the JWE encrypted response from Google Wallet
    jwe_object = jwe.JWE()
    jwe_object.deserialize(encrypted_jwe_response_from_wallet)
    jwe_object.decrypt(reader_private_jwk)
    decrypted_payload_bytes = jwe_object.payload
    decrypted_data = json.loads(decrypted_payload_bytes)
    

    decrypted_data will result in a vp_token JSON containing the credential

    {
      "vp_token":
      {
        "cred1": "<credential_token>"
      }
    }
    
  2. Create the session transcript

    Next step is to create the SessionTranscript from ISO/IEC 18013-5:2021 with an Android or Web specific Handover structure:

    SessionTranscript = [
      null,                // DeviceEngagementBytes not available
      null,                // EReaderKeyBytes not available
      [
        "OpenID4VPDCAPIHandover",
        AndroidHandoverDataBytes   // BrowserHandoverDataBytes for Web
      ]
    ]
    

    For both Android / web handovers you'll need to use the same nonce that you used to generate credential_request.

    Android Handover

        AndroidHandoverData = [
          origin,             // "android:apk-key-hash:<base64SHA256_ofAppSigningCert>",
          clientId,           // "android-origin:<app_package_name>",
          nonce,              // nonce that was used to generate credential request
        ]
    
        AndroidHandoverDataBytes = hashlib.sha256(cbor2.dumps(AndroidHandoverData)).digest()
        

    Browser Handover

        BrowserHandoverData =[
          origin,               // Origin URL
          clientId,             // "web-origin:<origin>"
          nonce,               //  nonce that was used to generate credential request
        ]
    
        BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()
        

    Using the SessionTranscript the DeviceResponse must be validated according to ISO/IEC 18013-5:2021 clause 9. This includes several steps, such as:

  3. Check State Issuer Cert. Checkout the supported issuer's IACA certs

  4. Verify MSO signature (18013-5 Section 9.1.2)

  5. Calculate and check ValueDigests for Data Elements (18013-5 Section 9.1.2)

  6. Verify deviceSignature signature (18013-5 Section 9.1.3)

{
  "version": "1.0",
  "documents": [
    {
      "docType": "org.iso.18013.5.1.mDL",
      "issuerSigned": {
        "nameSpaces": {...}, // contains data elements
        "issuerAuth": [...]  // COSE_Sign1 w/ issuer PK, mso + sig
      },
      "deviceSigned": {
        "nameSpaces": 24(<< {} >>), // empty
        "deviceAuth": {
          "deviceSignature": [...] // COSE_Sign1 w/ device signature
        }
      }
    }
  ],
  "status": 0
}

Test your solution

To test your solution, build and run our open source reference holder Android application. Here are the steps to build and run the reference holder app:

  • Clone the reference apps repository
  • Open the project on Android Studio
  • Build and run the appholder target on your Android device or emulator.

Zero Knowledge Proof (ZKP) based verification

Zero-Knowledge Proof (ZKP) is a cryptographic method that allows an individual (the prover) to prove to a verifier that they possess a certain piece of identity information or meet a specific criterion (e.g., are over 18, hold a valid credential) without revealing the actual underlying data itself. Essentially, it's a way to confirm the truth of a statement about one's identity while keeping the sensitive details private.

Digital identity systems that rely on direct sharing of identity data often require users to share excessive personal information, increasing the risk of data breaches and identity theft. ZKPs offer a paradigm shift, enabling verification with minimal disclosure.

Key Concepts of ZKPs in Digital Identity:

  • Prover: The individual trying to prove an aspect of their identity.
  • Verifier: The entity requesting proof of an identity attribute.
  • The Proof: A cryptographic protocol that allows the prover to convince the verifier of the truth of their claim without revealing the secret information.

Core Properties of Zero-Knowledge Proofs:

  • Completeness: If the statement is true and both the prover and verifier are honest, the verifier will be convinced.
  • Soundness: If the statement is false, a dishonest prover cannot (with very high probability) convince an honest verifier that it is true.
  • Zero-Knowledge: The verifier learns nothing beyond the fact that the statement is true. No actual data from the prover's identity is exposed.

To get a Zero Knowledge proof back from Google Wallet, you need change the request format to mso_mdoc_zk and add zk_system_type to your Request

  ...
  "dcql_query": {
    "credentials": [{
      "id": "cred1",
      "format": "mso_mdoc_zk",
      "meta": {
        "doctype_value": "org.iso.18013.5.1.mDL"
        "zk_system_type": [
         {
          "system": "longfellow-libzk-v1",
           "circuit_hash": "2b8e0c49b08eb1801b9bd7a82aa9eb3736a7519fc2b409asdhj1237034", // This will differ if you need more than 1 attribute.
           "num_attributes": 1,
           "version": 1
         }
       ],
       "verifier_message": "challenge"
      },
     "claims": [{
         ...