Questa guida spiega come le parti che si affidano (RP) possono integrare tecnicamente l'API Credenziali digitali per richiedere e convalidare le credenziali di identità digitale nelle app Android e sul web.
Procedura di registrazione e prerequisiti
Prima di pubblicare l'app in produzione, devi registrare formalmente la tua applicazione della parte che si affida con Google. Utilizziamo una procedura di firma del certificato in cui Google firma il tuo certificato per stabilire l'attendibilità.
- Test nella sandbox: puoi iniziare subito lo sviluppo senza inviare un modulo di acquisizione. Puoi eseguire i test direttamente utilizzando le chiavi di test pre-attendibili e i metadati di esempio pubblicati nella pagina Modalità sandbox.
- Registra il flusso end-to-end: una volta completati i test e dopo aver eseguito un'integrazione funzionante nella sandbox, registra un video end-to-end che mostri l'intero flusso di integrazione.
- Invia il modulo di acquisizione e accetta i Termini di servizio: compila e invia il modulo di onboarding della parte che si affida.
Il modulo richiede le seguenti informazioni:
- La richiesta di firma del certificato (CSR) di produzione.
- Le risorse di visualizzazione: URL del logo, nome visualizzato, URL delle Norme sulla privacy e URL dei Termini di servizio.
- Il video end-to-end dell'integrazione della sandbox.
Una volta approvato, Google ti fornirà un certificato firmato e i metadati univoci codificati in Base64URL (gw_rp_metadata_bytes).
Dettagli dell'integrazione tecnica
Le sezioni seguenti illustrano i dettagli dell'integrazione tecnica per le parti che si affidano che si integrano direttamente con l'API Credenziali digitali (inclusi la formattazione delle richieste, la crittografia delle richieste, l'attivazione dell'API, la convalida delle risposte e l'implementazione delle prove a conoscenza zero).
Formati e funzionalità supportati
Google Wallet supporta i documenti di identità digitali basati su ISO mdoc.
- Credenziali supportate: puoi consultare le credenziali e gli attributi supportati.
- Protocolli supportati: OpenID4VP (versione 1.0).
- SDK Android minimo: Android 9 (livello API 28) e versioni successive.
- Supporto del browser: per un elenco completo dei browser che supportano l'API Credenziali digitali, consulta la pagina Supporto dell'ecosistema.
- Domande frequenti: per domande sul supporto dei paesi e sulla tempistica per le nuove regioni, consulta le domande frequenti su credenziali e dati.
Formattare la richiesta
Per richiedere le credenziali da qualsiasi wallet, devi formattare la richiesta utilizzando OpenID4VP. Puoi richiedere credenziali specifiche o più credenziali in un singolo oggetto dcql_query.
Esempio di richiesta JSON
Ecco un esempio di richiesta requestJson mdoc per ottenere le credenziali di identità da qualsiasi wallet su un dispositivo Android o sul web.
{
"requests" : [
{
"protocol": "openid4vp-v1-signed",
"data": {<signed_credential_request>} // This is an object, shouldn't be a string.
}
]
}
Crittografia delle richieste
client_metadata contiene la chiave pubblica di crittografia per ogni richiesta.
Dovrai memorizzare le chiavi private per ogni richiesta e utilizzarle per autenticare e autorizzare il token che ricevi dall'app Wallet.
Metadati OpenID4VP integrati
Quando formatti la richiesta di credenziali, devi includere il campo gw_rp_metadata_bytes all'interno dell'oggetto client_metadata (come mostrato nel codice di richiesta di esempio riportato di seguito). Questo campo contiene i metadati della parte che si affida codificati in Base64URL richiesti da Google Wallet per verificare la tua identità e mostrare il tuo branding all'utente.
Il parametro credential_request in requestJson contiene i seguenti campi.
Qualifica specifica
{
"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
]
}
},
"gw_rp_metadata_bytes": "<base64url encoded metadata string>"
}
}
Qualsiasi credenziale idonea
Ecco la richiesta di esempio sia per la patente di guida digitale sia per il pass ID. L'utente può procedere con una delle due.
{
"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
],
"issuerauth_alg_values": [
-7
]
}
},
"gw_rp_metadata_bytes": "<base64url encoded metadata string>"
}
}
Puoi richiedere un numero qualsiasi di attributi supportati da qualsiasi credenziale di identità memorizzata in Google Wallet.
Richieste firmate
Richieste firmate (richieste di autorizzazione protette da JWT) incapsulano la tua richiesta di presentazione verificabile all'interno di un token web JSON (JWT) firmato crittograficamente utilizzando la tua infrastruttura PKI, garantendo l'integrità della richiesta e dimostrando la tua identità a Google Wallet.
Prerequisiti
Prima di implementare le modifiche al codice per la richiesta firmata, assicurati di avere:
- Chiave privata: ti serve una chiave privata (ad es. curva ellittica
ES256) per firmare la richiesta gestita nel tuo server. - Certificato: ti serve un certificato X.509 standard derivato dalla tua coppia di chiavi.
- Registrazione: assicurati che il tuo certificato pubblico sia registrato in Google Wallet.
Logica di creazione delle richieste
Per creare una richiesta, devi utilizzare la chiave privata e racchiudere il payload in 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
Attivare l'API
L'intera richiesta API deve essere generata lato server. A seconda della piattaforma, passerai il JSON generato alle API della piattaforma.
In-app (Android)
Per richiedere le credenziali di identità dalle tue app Android, segui questi passaggi:
Aggiornare le dipendenze
Nel file build.gradle del progetto, aggiorna le dipendenze per utilizzare Gestore delle credenziali (beta):
dependencies {
implementation("androidx.credentials:credentials:1.5.0-beta01")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}
Configurare il Gestore delle credenziali
Per configurare e inizializzare un oggetto CredentialManager, aggiungi una logica simile
alla seguente:
// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)
Richiedere gli attributi di identità
Anziché specificare singoli parametri per le richieste di identità, l'app li fornisce tutti insieme come stringa JSON all'interno di CredentialOption.
Il Gestore delle credenziali passa questa stringa JSON ai wallet digitali disponibili senza esaminarne i contenuti. Ogni wallet è quindi responsabile di:
- Analizzare la stringa JSON per comprendere la richiesta di identità.
- Determinare quali delle credenziali memorizzate, se presenti, soddisfano la richiesta.
Consigliamo ai partner di creare le richieste sul server anche per le integrazioni delle app per Android.
Utilizzerai requestJson da Formato della richiesta
come request nella chiamata di funzione 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)
}
}
Gestire la risposta delle credenziali
Una volta ricevuta una risposta dal wallet, verificherai se la risposta è riuscita e contiene la risposta 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 risposta credentialJson contiene un identityToken (JWT) criptato, definito dal W3C. L'app Wallet è responsabile della creazione di questa risposta.
Esempio:
{
"protocol" : "openid4vp-v1-signed",
"data" : {
<encrpted_response>
}
}
Passerai questa risposta al server per verificarne l'autenticità. Puoi trovare i passaggi per convalidare la risposta delle credenziali
Web
Per richiedere le credenziali di identità utilizzando l'API Credenziali digitali su Chrome o altri browser supportati, effettua la seguente richiesta.
const credentialResponse = await navigator.credentials.get({
digital : {
requests : [
{
protocol: "openid4vp-v1-signed",
data: {<credential_request>} // This is an object, shouldn't be a string.
}
]
}
})
Invia la risposta di questa API al tuo server per convalidare la risposta delle credenziali
Convalidare la risposta
Una volta che il wallet restituisce identityToken (JWT) criptato, devi eseguire una convalida lato server rigorosa prima di considerare attendibili i dati.
Decriptare la risposta
Utilizza la chiave privata corrispondente alla chiave pubblica inviata in client_metadata della richiesta per decriptare il JWE. In questo modo si ottiene un vp_token.
Esempio 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 genererà un JSON vp_token contenente la
credenziale
{
"vp_token":
{
"cred1": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
}
}
Creare la trascrizione della sessione
Il passaggio successivo consiste nel creare SessionTranscript da ISO/IEC 18013-5:2021 con una struttura di trasferimento specifica per Android o web:
SessionTranscript = [ null, // DeviceEngagementBytes not available null, // EReaderKeyBytes not available [ "OpenID4VPDCAPIHandover", AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web ] ]Per i trasferimenti Android e web, dovrai utilizzare lo stesso nonce che hai utilizzato per generare
credential_request.Trasferimento 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()
Trasferimento del browser
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()
Utilizzando
SessionTranscript, la risposta del dispositivo deve essere convalidata in base alla clausola 9 di ISO/IEC 18013-5:2021.Questa convalida include diversi passaggi:
Controlla il certificato dell'emittente: estrai la catena di certificati di firma dell'emittente da
issuerAuthe convalidala rispetto ai certificati radice IACA attendibili. Consulta i certificati IACA dell'emittente supportati.Verifica la firma MSO (sezione 9.1.2 di 18013-5)
Calcola e controlla
ValueDigestsper gli elementi di dati (sezione 9.1.2 di 18013-5)Verifica la firma
deviceSignature(sezione 9.1.3 di 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
}
Verifica dell'età che tutela la privacy (ZKP)
Per supportare le prove a conoscenza zero (ad es. verificare che un utente abbia più di 18 anni senza visualizzare la data di nascita esatta), modifica il formato della richiesta in mso_mdoc_zk e fornisci la configurazione zk_system_type richiesta.
Per una panoramica generale di cos'è ZKP e delle sue funzionalità, consulta le domande frequenti.
...
"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
{
...
Riceverai una prova a conoscenza zero criptata dal wallet. Puoi convalidare questa prova rispetto ai certificati IACA degli emittenti utilizzando la libreria longfellow-zk di Google.
Il servizio di verifica contiene un server basato su Docker pronto per il deployment che ti consente di convalidare la risposta rispetto a determinati certificati IACA dell'emittente.
Puoi modificare certs.pem per gestire i certificati dell'emittente IACA di cui vuoi fidarti.
Risorse e assistenza
- Domande frequenti: per le domande frequenti sull'integrazione tecnica, consulta le domande frequenti su identità e credenziali digitali.
- Implementazione di riferimento: consulta la nostra implementazione di riferimento dei verificatori di identità su GitHub.
- Sito web di test: prova il flusso end-to-end all'indirizzo verifier.multipaz.org.
- Specifica OpenID4VP: consulta la specifica tecnica per openID4VP.
- Assistenza: per ricevere assistenza per il debug o se hai domande durante l'integrazione, contatta
wallet-identity-rp-support@google.com.