Ce guide explique comment les parties utilisatrices peuvent intégrer techniquement l'API Digital Credentials pour demander et valider des permis de conduire mobiles (mDL) et des pass d'identité depuis Google Wallet sur les applications Android et le Web.
Processus d'inscription et prérequis
Avant de passer en production, vous devez enregistrer officiellement votre application de partie utilisatrice auprès de Google.
- Test dans l'environnement de bac à sable : vous pouvez commencer le développement immédiatement à l'aide de notre environnement de bac à sable et en créant un ID de test. L'acceptation des conditions d'utilisation n'est pas requise pour les tests.
- Envoyer le formulaire de participation : remplissez le formulaire d'intégration des parties utilisatrices. L'intégration prend généralement entre trois et cinq jours ouvrés. Le nom et le logo de votre produit s'affichent sur l'écran de consentement destiné aux utilisateurs pour les aider à identifier qui demande leurs données.
- Accepter les conditions d'utilisation : vous devez signer les conditions d'utilisation avant de passer en production.
Pour toute question concernant le suivi de votre application ou les tests dans des régions non prises en charge, consultez les questions fréquentes.
Formats et fonctionnalités compatibles
Google Wallet est compatible avec les pièces d'identité numériques basées sur ISO mdoc.
- Identifiants compatibles : vous pouvez consulter les identifiants et les attributs compatibles.
- Protocoles compatibles : OpenID4VP (version 1.0).
- SDK Android minimal : Android 9 (niveau d'API 28) et versions ultérieures.
- Navigateurs compatibles : pour obtenir la liste complète des navigateurs compatibles avec l'API Digital Credentials, consultez la page Compatibilité de l'écosystème.
- Questions fréquentes : pour toute question concernant la compatibilité par pays et le calendrier des nouvelles régions, consultez les questions fréquentes sur les identifiants et les données.
Mise en forme de la requête
Pour demander des identifiants à partir d'un portefeuille, vous devez mettre en forme votre requête à l'aide d'OpenID4VP. Vous pouvez demander des identifiants spécifiques ou plusieurs identifiants dans un seul objet dcql_query.
Exemple de requête JSON
Voici un exemple de requête requestJson mdoc permettant d'obtenir des identifiants à partir d'un portefeuille sur un appareil Android ou sur le Web.
{
"requests" : [
{
"protocol": "openid4vp-v1-signed",
"data": {<signed_credential_request>} // This is an object, shouldn't be a string.
}
]
}
Chiffrement des requêtes
Le client_metadata contient la clé publique de chiffrement pour chaque requête.
Vous devez stocker les clés privées pour chaque requête et les utiliser pour authentifier et autoriser le jeton que vous recevez de l'application Wallet.
Le paramètre credential_request dans requestJson contient les champs suivants.
Identifiant spécifique
{
"response_type": "vp_token",
"response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
"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"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.18013.5.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.18013.5.1",
"age_over_18"
],
"intent_to_retain": false
}
]
}
]
},
"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", // This is required
"alg" : "ECDH-ES", // This is required
}
]
},
"vp_formats_supported": {
"mso_mdoc": {
"deviceauth_alg_values": [
-7
],
"issuerauth_alg_values": [
-7
]
}
}
}
}
Tout identifiant éligible
Voici l'exemple de requête pour le permis de conduire mobile et le pass d'identité. L'utilisateur peut choisir l'un ou l'autre.
{
"response_type": "vp_token",
"response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
"nonce": "1234",
"dcql_query": {
"credentials": [
{
"id": "mdl-request",
"format": "mso_mdoc",
"meta": {
"doctype_value": "org.iso.18013.5.1.mDL"
},
"claims": [
{
"path": [
"org.iso.18013.5.1",
"family_name"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.18013.5.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.18013.5.1",
"age_over_18"
],
"intent_to_retain": false
}
]
},
{ // Credential type 2
"id": "id_pass-request",
"format": "mso_mdoc",
"meta": {
"doctype_value": "com.google.wallet.idcard.1"
},
"claims": [
{
"path": [
"org.iso.18013.5.1",
"family_name"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.18013.5.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.18013.5.1",
"age_over_18"
],
"intent_to_retain": false
}
]
}
]
credential_sets : [
{
"options": [
[ "mdl-request" ],
[ "id_pass-request" ]
]
}
]
},
"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", // This is required
"alg" : "ECDH-ES", // This is required
}
]
},
"vp_formats_supported": {
"mso_mdoc": {
"deviceauth_alg_values": [
-7
],
"isserauth_alg_values": [
-7
]
}
}
}
}
Vous pouvez demander n'importe quel nombre d'attributs compatibles à partir de n'importe quel identifiant stocké dans Google Wallet.
Requêtes signées
Requêtes signées (requêtes d'autorisation sécurisées par JWT) encapsulent votre requête de présentation vérifiable dans un jeton Web JSON (JWT) signé de manière cryptographique à l'aide de votre infrastructure PKI, ce qui garantit l'intégrité de la requête et prouve votre identité à Google Wallet.
Prérequis
Avant d'implémenter les modifications de code pour les requêtes signées, assurez-vous de disposer des éléments suivants :
- Clé privée : vous avez besoin d'une clé privée (par exemple,
ES256à courbe elliptique) pour signer la requête gérée sur votre serveur. - Certificat : vous avez besoin d'un certificat X.509 standard dérivé de votre paire de clés.
- Inscription : assurez-vous que votre certificat public est enregistré auprès de Google Wallet. Contactez notre équipe d'assistance à l'adresse
wallet-identity-rp-support@google.com.
Logique de construction des requêtes
Pour construire une requête, vous devez utiliser votre clé privée et encapsuler la charge utile dans un JWS.
def construct_openid4vp_request(
doctypes: list[str],
requested_fields: list[dict],
nonce_base64: str,
jwe_encryption_public_jwk: jwk.JWK,
is_zkp_request: bool,
is_signed_request: bool,
state: dict,
origin: str
) -> dict:
# ... [Existing logic to build 'presentation_definition' and basic 'request_payload'] ...
# ------------------------------------------------------------------
# SIGNED REQUEST IMPLEMENTATION (JAR)
# ------------------------------------------------------------------
if is_signed_request:
try:
# 1. Load the Verifier's Certificate
# We must load the PEM string into a cryptography x509 object
verifier_cert_obj = x509.load_pem_x509_certificate(
CERTIFICATE.encode('utf-8'),
backend=default_backend()
)
# 2. Calculate Client ID (x509_hash)
# We calculate the SHA-256 hash of the DER-encoded certificate.
cert_der = verifier_cert_obj.public_bytes(serialization.Encoding.DER)
verifier_fingerprint_bytes = hashlib.sha256(cert_der).digest()
# Create a URL-safe Base64 hash (removing padding '=')
verifier_fingerprint_b64 = base64.urlsafe_b64encode(verifier_fingerprint_bytes).decode('utf-8').rstrip("=")
# Format the client_id as required by the spec
client_id = f'x509_hash:{verifier_fingerprint_b64}'
# 3. Update Request Payload with JAR specific fields
request_payload["client_id"] = client_id
# Explicitly set expected origins to prevent relay attacks
# Format for android origin: origin = android:apk-key-hash:<base64SHA256_ofAppSigningCert>
# Format for web origin: origin = <origin_url>
if origin:
request_payload["expected_origins"] = [origin]
# 4. Create Signed JWT (JWS)
# Load the signing private key
signing_key = jwk.JWK.from_pem(PRIVATE_KEY.encode('utf-8'))
# Initialize JWS with the JSON payload
jws_token = jws.JWS(json.dumps(request_payload).encode('utf-8'))
# Construct the JOSE Header
# 'x5c' (X.509 Certificate Chain) is critical: it allows the wallet
# to validate your key against the one registered in the console.
x5c_value = base64.b64encode(cert_der).decode('utf-8')
protected_header = {
"alg": "ES256", # Algorithm (e.g., ES256 or RS256)
"typ": "oauth-authz-req+jwt", # Standard type for JAR
"kid": "1", # Key ID
"x5c": [x5c_value] # Embed the certificate
}
# Sign the token
jws_token.add_signature(
key=signing_key,
alg=None,
protected=json_encode(protected_header)
)
# 5. Return the Request Object
# Instead of returning the raw JSON, we return the signed JWT string
# under the 'request' key.
return {"request": jws_token.serialize(compact=True)}
except Exception as e:
print(f"Error signing OpenID4VP request: {e}")
return None
# ... [Fallback for unsigned requests] ...
return request_payload
Déclencher l'API
L'ensemble de la requête API doit être généré côté serveur. Selon la plate-forme, vous transmettrez le JSON généré aux API natives.
Dans l'application (Android)
Pour demander des identifiants à partir de vos applications Android, procédez comme suit :
Mettre à jour les dépendances
Dans le fichier build.gradle de votre projet, mettez à jour vos dépendances pour utiliser le Gestionnaire d'identifiants (bêta) :
dependencies {
implementation("androidx.credentials:credentials:1.5.0-beta01")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}
Configurer le Gestionnaire d'identifiants
Pour configurer et initialiser un objet CredentialManager, ajoutez une logique semblable
à celle-ci :
// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)
Demander des attributs d'identité
Au lieu de spécifier des paramètres individuels pour les requêtes d'identité, l'application les fournit tous ensemble sous forme de chaîne JSON dans CredentialOption.
Le Gestionnaire d'identifiants transmet cette chaîne JSON aux portefeuilles numériques disponibles sans examiner son contenu. Chaque portefeuille est ensuite responsable des éléments suivants :
- Analyser la chaîne JSON pour comprendre la requête d’identité.
- Déterminer si l'un de ses identifiants stockés répond à la requête.
Nous recommandons aux partenaires de créer leurs requêtes sur le serveur, même pour les intégrations d'applications Android.
Vous utiliserez le requestJson du format de requête
comme request dans l'appel de fonction GetDigitalCredentialOption().
// 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)
}
}
Gérer la réponse d'identifiant
Une fois que vous avez reçu une réponse du portefeuille, vous vérifiez si elle a abouti et si elle contient la réponse credentialJson.
// 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}")
}
}
La réponse credentialJson contient un identityToken (JWT) chiffré, défini par le W3C. L'application Wallet est responsable de la création de cette réponse.
Exemple :
{
"protocol" : "openid4vp-v1-signed",
"data" : {
<encrpted_response>
}
}
Vous renverrez cette réponse au serveur pour valider son authenticité. Vous trouverez les étapes à suivre pour valider la réponse d'identifiant.
Web
Pour demander des identifiants à l'aide de l'API Digital Credentials sur Chrome ou d'autres navigateurs compatibles, effectuez la requête suivante.
const credentialResponse = await navigator.credentials.get({
digital : {
requests : [
{
protocol: "openid4vp-v1-signed",
data: {<credential_request>} // This is an object, shouldn't be a string.
}
]
}
})
Renvoyez la réponse de cette API à votre serveur pour valider la réponse d'identifiant.
Valider la réponse
Une fois que le portefeuille renvoie le identityToken (JWT) chiffré, vous devez effectuer une validation stricte côté serveur avant de faire confiance aux données.
Déchiffrer la réponse
Utilisez la clé privée correspondant à la clé publique envoyée dans le client_metadata de la requête pour déchiffrer le JWE. Vous obtenez ainsi un vp_token.
Exemple Python :
from jwcrypto import jwe, jwk
# Retrieve the Private Key from Datastore
reader_private_jwk = jwk.JWK.from_json(jwe_private_key_json_str)
# Save public key thumbprint for session transcript
encryption_public_jwk_thumbprint = reader_private_jwk.thumbprint()
# 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 génère un JSON vp_token contenant l'
identifiant.
{
"vp_token":
{
"cred1": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
}
}
Créer la transcription de la session
L'étape suivante consiste à créer le SessionTranscript à partir de la norme ISO/CEI 18013-5:2021 avec une structure de transfert spécifique à Android ou au Web :
SessionTranscript = [ null, // DeviceEngagementBytes not available null, // EReaderKeyBytes not available [ "OpenID4VPDCAPIHandover", AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web ] ]Pour les transferts Android et Web, vous devez utiliser le même nonce que celui utilisé pour générer
credential_request.Transfert Android
AndroidHandoverData = [ origin, // "android:apk-key-hash:<base64SHA256_ofAppSigningCert>", nonce, // nonce that was used to generate credential request, encryption_public_jwk_thumbprint, // Encryption public key (JWK) Thumbprint ] AndroidHandoverDataBytes = hashlib.sha256(cbor2.dumps(AndroidHandoverData)).digest()
Transfert de navigateur
BrowserHandoverData =[ origin, // Origin URL nonce, // nonce that was used to generate credential request encryption_public_jwk_thumbprint, // Encryption public key (JWK) Thumbprint ] BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()
À l'aide du
SessionTranscript, la réponse de l'appareil doit être validée conformément à la clause 9 de la norme ISO/CEI 18013-5:2021. Cela inclut plusieurs étapes, telles que les suivantes :Vérifier le certificat de l'émetteur de l'État. Consultez les certificats IACA de l'émetteur compatible.
Vérifier la signature MSO (section 9.1.2 de la norme 18013-5)
Calculer et vérifier les
ValueDigestspour les éléments de données (section 9.1.2 de la norme 18013-5)Vérifier la signature
deviceSignature(section 9.1.3 de la norme 18013-5)
{
"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
}
Vérification de l'âge préservant la confidentialité (ZKP)
Pour prendre en charge les preuves à divulgation nulle d'information (par exemple, vérifier qu'un utilisateur a plus de 18 ans sans voir sa date de naissance exacte), remplacez le format de votre requête par mso_mdoc_zk et fournissez la configuration zk_system_type requise.
Pour obtenir une présentation générale de la ZKP et de ses fonctionnalités, consultez les questions fréquentes.
...
"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": "f88a39e561ec0be02bb3dfe38fb609ad154e98decbbe632887d850fc612fea6f", // This will differ if you need more than 1 attribute.
"num_attributes": 1, // number of attributes (in claims) this has can support
"version": 5,
"block_enc_hash": 4096,
"block_enc_sig": 2945,
}
{
"system": "longfellow-libzk-v1",
"circuit_hash": "137e5a75ce72735a37c8a72da1a8a0a5df8d13365c2ae3d2c2bd6a0e7197c7c6", // This will differ if you need more than 1 attribute.
"num_attributes": 1, // number of attributes (in claims) this has can support
"version": 6,
"block_enc_hash": 4096,
"block_enc_sig": 2945,
}
],
"verifier_message": "challenge"
},
"claims": [{
...
"client_metadata": {
"jwks": {
"keys": [ // sample request encryption key
{
...
Vous recevrez une preuve à divulgation nulle d'information chiffrée du portefeuille. Vous pouvez valider cette preuve par rapport aux certificats IACA des émetteurs à l'aide de la bibliothèque longfellow-zk de Google.
Le verifier-service contient un serveur basé sur Docker prêt à être déployé qui vous permet de valider la réponse par rapport à certains certificats IACA d'émetteurs.
Vous pouvez modifier le fichier certs.pem pour gérer les certificats IACA d'émetteurs auxquels vous souhaitez faire confiance.
Ressources et assistance
- Questions fréquentes : pour obtenir des réponses aux questions fréquentes sur l'intégration technique, consultez les questions fréquentes sur l'identité et les identifiants numériques.
- Mise en œuvre de référence : consultez notre mise en œuvre de référence des vérificateurs d'identité sur GitHub.
- Site Web de test : essayez le flux de bout en bout sur verifier.multipaz.org.
- Spécification OpenID4VP : consultez la spécification technique d'OpenID4VP.
- Assistance : pour obtenir de l'aide sur le débogage ou pour toute question lors de l'intégration, contactez
wallet-identity-rp-support@google.com.