Questa guida spiega come le parti di supporto (RP) e gli sviluppatori di lettori possono implementare la verifica di persona (offline) delle credenziali digitali presentate da Google Wallet in conformità allo standard internazionale ISO/IEC 18013-5.
Le credenziali digitali in Google Wallet possono essere verificate in modo sicuro in ambienti fisici (come terminali dei punti vendita, sedi di eventi, varchi di transito, lettori delle forze dell'ordine e app di lettura mobile) senza richiedere una connessione a internet attiva al momento della presentazione.
Panoramica della presentazione offline (ISO/IEC 18013-5)
Lo standard ISO/IEC 18013-5 definisce un protocollo standardizzato e interoperabile per la presentazione offline tra un titolare (il dispositivo mobile dell'utente su cui è in esecuzione Google Wallet) e un lettore / verificatore (un terminale fisico o un'app mobile complementare).
Il flusso della presentazione si svolge in fasi distinte:
- Interazione con il dispositivo:il lettore e il wallet stabiliscono il contatto iniziale tramite NFC Tap (Static o Negotiated Handover) o scansione del codice QR. Durante questa fase, vengono scambiati i metadati di engagement del dispositivo e la chiave pubblica temporanea del lettore (
EReaderKey). - Connessione per il trasporto dei dati: viene negoziato un canale Bluetooth Low Energy (BLE) sicuro e criptato (con il lettore che agisce in modalità Central Client o Peripheral Server).
- Richiesta del dispositivo:il lettore trasmette un
DeviceRequestcon codifica CBOR che specifica il tipo di documento richiesto (ad esempioorg.iso.18013.5.1.mDL) e gli spazi dei nomi e gli elementi di dati specifici richiesti. - Consenso dell'utente e autenticazione del dispositivo:Google Wallet chiede all'utente di esaminare gli elementi dei dati richiesti e di confermare la condivisione utilizzando l'autenticazione biometrica o il blocco schermo.
- Risposta del dispositivo e verifica crittografica: il wallet invia un
DeviceResponsecon codifica CBOR contenente l'oggetto di sicurezza mobile (MSO) firmato e gli elementi di dati firmati dal dispositivo. Il lettore verifica le firme crittografiche rispetto ai certificati radice attendibili.
SDK open source Multipaz
Per implementare un'applicazione di lettura o verifica, Google consiglia di utilizzare Multipaz, un SDK Kotlin Multiplatform (KMP) open source originariamente sviluppato da Google e donato alla OpenWallet Foundation (OWF).
Multipaz fornisce un'implementazione pronta per la produzione dei protocolli di lettura e wallet ISO/IEC 18013-5, pipeline di verifica crittografica, codifica/decodifica CBOR e schemi di tipi di documenti estensibili.
Integrazione di Multipaz nell'app di lettura
I passaggi seguenti mostrano come integrare l'SDK Multipaz in un'applicazione di lettura Android.
Passaggio 1: aggiungi le dipendenze
Le librerie Multipaz sono pubblicate su Maven Central. Aggiungi i moduli richiesti al file build.gradle.kts della tua applicazione:
// build.gradle.kts
dependencies {
// Core Multipaz library (protocol engine, CBOR, crypto)
implementation("org.multipaz:multipaz:0.100.0")
// Android-specific platform bindings (NFC, BLE, Keystore)
implementation("org.multipaz:multipaz-android:0.100.0")
// Standardized document types (mDL, EU PID, etc.)
implementation("org.multipaz:multipaz-doctypes:0.100.0")
}
Passaggio 2: configura le autorizzazioni Android
La verifica di persona richiede autorizzazioni hardware per l'interazione NFC, la scansione della fotocamera (per l'interazione QR) e il trasporto dei dati Bluetooth Low Energy. Aggiungi le seguenti autorizzazioni al tuo AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- NFC Engagement -->
<uses-permission android:name="android.permission.NFC" />
<uses-feature android:name="android.hardware.nfc" android:required="false" />
<!-- Camera for QR Code Engagement -->
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false" />
<!-- Bluetooth Low Energy Transport (Android 12+) -->
<uses-permission android:name="android.permission.BLUETOOTH_SCAN"
android:usesPermissionFlags="neverForLocation" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<!-- Legacy Bluetooth Permissions for Android 11 and lower -->
<uses-permission android:name="android.permission.BLUETOOTH"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"
android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"
android:maxSdkVersion="30" />
</manifest>
Passaggio 3: inizializza il motore di verifica
Utilizza le astrazioni di connessione VerificationHelper o Reader di Multipaz per gestire gli eventi di engagement e il ciclo di vita della comunicazione BLE:
import org.multipaz.verification.VerificationHelper
import org.multipaz.cbor.Cbor
import org.multipaz.crypto.Crypto
class ReaderManager(private val context: android.content.Context) {
private var verificationHelper: VerificationHelper? = null
fun startListeningForEngagement() {
verificationHelper = VerificationHelper.Builder(
context = context,
listener = object : VerificationHelper.Listener {
override fun onDeviceConnected() {
// BLE channel established, send request
sendDeviceRequest()
}
override fun onResponseReceived(deviceResponseBytes: ByteArray) {
// Process and verify the received credential payload
handleDeviceResponse(deviceResponseBytes)
}
override fun onError(error: Throwable) {
// Handle transport or protocol errors
}
override fun onDeviceDisconnected(transportTransportSpecificTermination: Boolean) {
// Connection closed
}
}
).build()
}
}
Passaggio 4: crea la richiesta DeviceRequest
Specifica il tipo di documento e i singoli elementi di dati che la tua applicazione deve verificare. Applica sempre il principio della divulgazione minima (ad es. richiedendo solo age_over_21 anziché birth_date per la verifica dell'età):
fun sendDeviceRequest() {
// Specify the DocType and requested elements
val docType = "org.iso.18013.5.1.mDL"
val namespace = "org.iso.18013.5.1"
// Map of requested elements: elementName -> intentToRetain
val requestedElements = mapOf(
"family_name" to false,
"given_name" to false,
"age_over_21" to false,
"portrait" to false,
"driving_privileges" to false
)
// Build ISO/IEC 18013-5 DeviceRequest structure
val deviceRequestBytes = verificationHelper?.buildDeviceRequest(
docType = docType,
itemsToRequest = mapOf(namespace to requestedElements)
)
if (deviceRequestBytes != null) {
verificationHelper?.sendDeviceRequest(deviceRequestBytes)
}
}
Supporto di tipi di documenti aggiuntivi (DocType)
Multipaz supporta le credenziali standardizzate out-of-the-box e fornisce un'architettura estensibile per richiedere tipi di documenti personalizzati o specifici del dominio.
1. Tipi di documenti standard integrati
La libreria multipaz-doctypes fornisce modelli di schema predefiniti per le credenziali standard:
| Identificatore del tipo di documento | Standard / Ambito | Spazio dei nomi tipico | Elementi comuni |
|---|---|---|---|
org.iso.18013.5.1.mDL |
Patente di guida digitale ISO/IEC 18013-5 | org.iso.18013.5.1 |
family_name, given_name, birth_date, issue_date, expiry_date, issuing_authority, document_number, portrait, driving_privileges, age_over_18, age_over_21 |
eu.europa.ec.eudi.pid.1 |
Dati di identificazione della persona del Portafoglio europeo di identità digitale (EUDIW) | eu.europa.ec.eudi.pid.1 |
family_name, first_name, birth_date, nationality, issuing_country, issuing_authority, personal_administrative_number |
com.google.wallet.idcard.1 |
Pass ID di Google Wallet / ID di test | com.google.wallet.idcard.1 |
given_name, family_name, birth_date, document_number, portrait |
2. Richiesta di tipi di documenti personalizzati
Per richiedere tipi di documenti personalizzati, definisci la stringa docType di destinazione e le mappature degli spazi dei nomi corrispondenti durante la creazione di DeviceRequest:
// Example: Requesting a custom event ticket credential
val customDocType = "com.example.events.ticket"
val customNamespace = "com.example.events.ticket.1"
val customRequestedItems = mapOf(
"ticket_id" to false,
"event_name" to false,
"seat_section" to false,
"vip_access" to false
)
val multiDocRequestBytes = verificationHelper?.buildMultiDocDeviceRequest(
documents = listOf(
DocumentRequest(
docType = customDocType,
namespaces = mapOf(customNamespace to customRequestedItems)
)
)
)
Verifica crittografica e gestione dei trust
La ricezione di un payload di risposta è solo il primo passaggio. I lettori devono eseguire una verifica crittografica in quattro passaggi per convalidare l'autenticità e l'integrità della credenziale presentata.
| Passaggio di verifica | Target di verifica |
|---|---|
| 1. Autenticazione dell'emittente | Verifica IssuerAuth (COSE_Sign1) rispetto ai certificati radice IACA attendibili |
| 2. Controllo della finestra di validità | Assicurati che validFrom ≤ ora corrente ≤ validUntil |
| 3. Controllo dell'integrità dei dati | Calcola i digest SHA-256 degli elementi restituiti e confrontali con l'MSO ValueDigests |
| 4. Autenticazione del dispositivo | Verifica la firma o il MAC DeviceSigned utilizzando DeviceKey associato a SessionTranscript |
1. La pipeline di verifica in 4 passaggi
- Autenticazione dell'emittente (
IssuerAuth):- Il Mobile Security Object (MSO) è firmato dall'autorità di emissione (payload
IssuerAuth). - Il lettore verifica la firma
COSE_Sign1utilizzando il certificato del firmatario del documento e si assicura che il certificato sia collegato a un certificato radice di un'autorità di certificazione di emissione (IACA) attendibile.
- Il Mobile Security Object (MSO) è firmato dall'autorità di emissione (payload
- Verifica della finestra di validità:
- Il lettore controlla i timestamp
validityInfo.validFromevalidityInfo.validUntilnell'MSO rispetto all'orologio corrente del lettore per assicurarsi che la credenziale non sia scaduta.
- Il lettore controlla i timestamp
- Verifica dell'integrità dei dati (
ValueDigests):- Per ogni
IssuerSignedItemricevuto, il lettore calcola il relativo digest (ad es. SHA-256) e verifica che corrisponda alla voce hash corrispondente nel dizionarioValueDigestsdell'MSO.
- Per ogni
- Autenticazione del dispositivo (
DeviceSigned):- Il lettore verifica che il dispositivo che presenta la credenziale sia in possesso della chiave privata corrispondente alla
DeviceKeypubblicata all'interno dell'MSO firmato. - Questo viene fatto verificando il
DeviceAuth(DeviceSignatureoDeviceMac) tramite ilSessionTranscript, associando la sessione alla chiave temporanea del lettore e impedendo attacchi di replay e man-in-the-middle.
- Il lettore verifica che il dispositivo che presenta la credenziale sia in possesso della chiave privata corrispondente alla
2. Gestione dei certificati radice IACA attendibili
I lettori di produzione devono mantenere un archivio di attendibilità locale sicuro contenente i certificati radice IACA attendibili:
- Certificati IACA di produzione: scarica e configura i certificati radice dalle autorità di emissione ufficiali. Consulta il nostro elenco di emittenti e certificati IACA supportati.
- AAMVA VICAL: per le giurisdizioni degli Stati Uniti, i sistemi di lettura possono integrarsi con il servizio Verified Issuer Certificate Authority List (VICAL) dell'American Association of Motor Vehicle Administrators (AAMVA) per sincronizzare automaticamente i trust anchor statali.
- Radici di test della sandbox:quando esegui i test con le credenziali della sandbox, assicurati che il lettore consideri attendibile la radice IACA della sandbox di Google.
import org.multipaz.crypto.X509Cert
// Configure trusted IACA certificates in the trust store
val trustedCertificates = mutableListOf<X509Cert>()
// Add official state IACA certificates
trustedCertificates.add(X509Cert.fromPem(sampleStateIacaPem))
// Add Google Sandbox IACA root certificate for testing
trustedCertificates.add(X509Cert.fromPem(googleSandboxIacaPem))
val verifier = MultipazVerifier(trustStore = trustedCertificates)
val verificationResult = verifier.verify(deviceResponseBytes, sessionTranscript)
if (verificationResult.isIssuerAuthorized && verificationResult.isDeviceAuthenticated) {
// Credential is valid and authentic
} else {
// Reject presentation: cryptographic validation failed
}
Autenticazione del lettore (consigliata)
L'autenticazione del lettore consente all'applicazione di lettura di dimostrare crittograficamente la propria identità a Google Wallet firmando la struttura ReaderAuthentication utilizzando un certificato del lettore X.509 autorizzato.
- Perché è consigliata: l'autenticazione del lettore consente all'app o al terminale di lettura di presentare un'identità attendibile all'utente. Sebbene sia facoltativo per gli attributi pubblici di base (ad es. la verifica di
age_over_21), è vivamente consigliato per le integrazioni dei lettori per aumentare la fiducia degli utenti e potrebbe essere richiesto dalla legge o dalle norme quando si richiedono attributi sensibili (come il numero di previdenza sociale completo, l'indirizzo di residenza o approvazioni statali specifiche). - Come funziona: il lettore include la propria catena di certificati e firma la trascrizione della sessione. Google Wallet mostra l'identità verificata e il nome dell'organizzazione del lettore all'utente nella schermata per il consenso prima del rilascio dei dati.
Strumenti di test e sviluppo
Per accelerare l'integrazione, utilizza i seguenti strumenti per sviluppatori e implementazioni di riferimento:
- App di riferimento Multipaz:
- Clona il repository Multipaz ed esegui l'app di esempio Android
IdentityReaderper testare i flussi di verifica fisica.
- Clona il repository Multipaz ed esegui l'app di esempio Android
- Crea un ID di test in Google Wallet:
- Segui la nostra guida Crea un pass ID di test in Google Wallet per eseguire il provisioning di una credenziale di test simulata in Google Wallet utilizzando il simulatore di passaporto elettronico Utopia.
- Test del verificatore basato sul web:
- Utilizza verifier.multipaz.org per ispezionare le richieste CBOR, esplorare le query di rivendicazione e testare le presentazioni basate sul web W3C / ISO 18013-7.
Risoluzione dei problemi e diagnostica sul campo
La tabella seguente elenca i problemi comuni riscontrati durante la verifica offline e le soluzioni consigliate:
| Problema / sintomo | Causa principale | Risoluzione consigliata |
|---|---|---|
| Timeout della connessione BLE / Connessione non riuscita |
|
|
| Il coinvolgimento tramite tocco NFC non va a buon fine o si interrompe | L'utente allontana il dispositivo mobile dall'antenna del lettore prima che il record di trasferimento BLE venga trasferito completamente. |
|
UNTRUSTED_ISSUER / Errore della catena di certificati |
Il certificato del firmatario del documento non è collegato a nessun certificato IACA attendibile nell'archivio di attendibilità locale del lettore. |
|
INVALID_VALIDITY_INFO / MSO scaduta |
|
|
DEVICE_AUTHENTICATION_FAILED |
Mancata corrispondenza della trascrizione della sessione tra il lettore e Wallet o firma del dispositivo temporanea non valida. |
|
| Arresto anomalo delle autorizzazioni di Android 12 e versioni successive | L'applicazione ha tentato di eseguire la scansione o l'annuncio tramite BLE senza autorizzazioni di runtime. |
|
Linee guida UX e indicazioni sulla privacy per i lettori di persona
Quando progetti lettori fisici e app complementari:
- Pratica la divulgazione selettiva nell'interfaccia utente:mostra all'operatore solo la decisione o l'attributo minimo richiesto (ad es. mostra un segno di spunta verde ben visibile e "Età 21+ verificata" anziché visualizzare la data di nascita completa, l'indirizzo e il numero di patente dell'utente).
- Indicatori chiari di interazione fisica:etichetta chiaramente la zona target NFC e mostra indicatori visivi (come animazioni o barre di avanzamento) che mostrano ogni fase: Tocca / Scansiona → Connessione → Verifica → Completato.
- Gestione dei dati temporanei: non memorizzare o registrare elementi di dati personali ricevuti dal wallet, a meno che non sia esplicitamente richiesto dalla legge vigente e divulgato tramite
intentToRetain = true.