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

When an application requests identity attributes, the following process occurs:

  1. Credential Discovery: The application queries available wallets to identify credentials that can satisfy the request. Android then presents a system UI selector, displaying the information to be shared. This allows the user to make an informed decision about which credential to use.

  2. User Selection and Wallet Interaction: The user selects a credential, and Android invokes the corresponding wallet app to complete the transaction. The wallet app may present its own consent screen or require biometric confirmation.

  3. Outcome: If the user consents, the selected identity credentials are shared with the requesting application. If the user declines, an error is returned.

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.

{
    "providers": [{
      "protocol": "openid4vp",
      "request": "<credential_request>"
    }]
}

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",
  "origin": "https://www.website.com", // omit this parameter for app
  "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 auth key
        {
          "kty": "EC",
          "crv": "P-256",
          "x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
          "y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ"
        }
      ]
    },
    "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: {
              providers: [{
                  protocol: "openid4vp",
                  request: "<credential_request>"
              }]
          },
      })

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.

    Example:

      TinkConfig.register();
    
      // Sample Private Key JWK
      String privateKeysetJson = '{"crv":"P-256","d":"evjMOTTqWeTKKOrtWxq9kO_a5rHW_ja_wrT6eH7VPzs","kty":"EC","x":"C2Ka4HcNelUIxMjscNRq1GjFamP-fskGjvR6aY9Ac_Q","y":"a1tXuHhBnsGaaFyNzpqADY_Cp39Q56L2VLuADRsWncE"}';
    
      // Read the private keyset for Hybrid Decryption
      KeysetHandle privateKeysetHandle = CleartextKeysetHandle.read(
              JsonKeysetReader.withString(privateKeysetJson));
      HybridDecrypt decrypt = privateKeysetHandle.getPrimitive(HybridDecrypt.class);
    
      // Split the JWE string by the '.' delimiter
      String[] parts = jweString.split("\\.");
      if (parts.length != 5) {
          throw new IllegalArgumentException("Invalid JWE format (expected 5 parts)");
      }
    
      // The encrypted data is typically in the second part (index 1)
      byte[] ciphertext = Base64.getUrlDecoder().decode(parts[1]);
    
      // Associated data (AAD) is often the third part (index 2)
      byte[] associatedData = Base64.getUrlDecoder().decode(parts[2]);
    
      // Decrypt the ciphertext
      byte[] decryptedPayloadBytes = decrypt.decrypt(ciphertext, associatedData);
      String decryptedJsonResponse = new String(decryptedPayloadBytes, StandardCharsets.UTF_8);
    
    

    decrypted_json_response 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
      [
        "OID4VPDCAPIHandover",
        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-origin:",
          clientId,           // "android:apk-key-hash:",
          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:"
          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.