Accettazione di credenziali digitali di persona (offline)

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:

  1. 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).
  2. 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).
  3. Richiesta del dispositivo:il lettore trasmette un DeviceRequest con codifica CBOR che specifica il tipo di documento richiesto (ad esempio org.iso.18013.5.1.mDL) e gli spazi dei nomi e gli elementi di dati specifici richiesti.
  4. 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.
  5. Risposta del dispositivo e verifica crittografica: il wallet invia un DeviceResponse con 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

  1. Autenticazione dell'emittente (IssuerAuth):
    • Il Mobile Security Object (MSO) è firmato dall'autorità di emissione (payload IssuerAuth).
    • Il lettore verifica la firma COSE_Sign1 utilizzando 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.
  2. Verifica della finestra di validità:
    • Il lettore controlla i timestamp validityInfo.validFrom e validityInfo.validUntil nell'MSO rispetto all'orologio corrente del lettore per assicurarsi che la credenziale non sia scaduta.
  3. Verifica dell'integrità dei dati (ValueDigests):
    • Per ogni IssuerSignedItem ricevuto, il lettore calcola il relativo digest (ad es. SHA-256) e verifica che corrisponda alla voce hash corrispondente nel dizionario ValueDigests dell'MSO.
  4. Autenticazione del dispositivo (DeviceSigned):
    • Il lettore verifica che il dispositivo che presenta la credenziale sia in possesso della chiave privata corrispondente alla DeviceKey pubblicata all'interno dell'MSO firmato.
    • Questo viene fatto verificando il DeviceAuth (DeviceSignature o DeviceMac) tramite il SessionTranscript, associando la sessione alla chiave temporanea del lettore e impedendo attacchi di replay e man-in-the-middle.

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:

  1. App di riferimento Multipaz:
    • Clona il repository Multipaz ed esegui l'app di esempio Android IdentityReader per testare i flussi di verifica fisica.
  2. 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.
  3. 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
  • Interferenze RF in ambienti ad alta densità.
  • Incompatibilità tra la modalità periferica e quella centrale su hardware di lettura specifici.
  • Timeout di scansione.
  • Assicurati che il lettore supporti sia la modalità BLE Central Client che la modalità Peripheral Server.
  • Regola la finestra e l'intervallo di scansione BLE per una scansione aggressiva durante il coinvolgimento attivo.
  • Verifica che la negoziazione della dimensione MTU venga completata correttamente.
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.
  • Fornisci un feedback visivo/audio/aptico immediato sul terminale non appena inizia l'interazione NFC.
  • Chiedi agli utenti di tenere lo smartphone fermo contro il target NFC finché non viene stabilita la connessione BLE.
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.
  • Verifica che il certificato radice dell'emittente sia caricato nell'archivio di attendibilità del lettore.
  • Se esegui il test in sandbox, verifica che sia caricato il certificato Google Sandbox IACA Root.
  • Assicurati che gli elenchi dei certificati IACA (ad es. AAMVA VICAL) vengano aggiornati periodicamente.
INVALID_VALIDITY_INFO / MSO scaduta
  • L'orologio di sistema del lettore non è sincronizzato.
  • La firma MSO è scaduta.
  • Assicurati che il dispositivo di lettura sincronizzi regolarmente l'ora di sistema tramite NTP.
  • Chiedi all'utente di aprire Google Wallet mentre è connesso a internet per aggiornare i token delle credenziali.
DEVICE_AUTHENTICATION_FAILED Mancata corrispondenza della trascrizione della sessione tra il lettore e Wallet o firma del dispositivo temporanea non valida.
  • Assicurati che i byte non elaborati esatti di DeviceEngagementBytes e EReaderKeyBytes vengano conservati nella struttura SessionTranscript senza nuova codifica.
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.
  • Controlla e richiedi BLUETOOTH_SCAN, BLUETOOTH_CONNECT e BLUETOOTH_ADVERTISE in fase di runtime prima di avviare le sessioni di lettura.

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.