Akceptacja dokumentów cyfrowych w formie fizycznej (offline)

Z tego przewodnika dowiesz się, jak strony uzależnione (RP) i deweloperzy czytników mogą wdrożyć weryfikację stacjonarną (offline) cyfrowych dokumentów tożsamości przedstawianych z Portfela Google zgodnie z międzynarodowym standardem ISO/IEC 18013-5.

Cyfrowe dokumenty tożsamości w Portfelu Google można bezpiecznie weryfikować w środowiskach fizycznych (takich jak terminale płatnicze, miejsca wydarzeń, bramki transportu publicznego, czytniki organów ścigania i aplikacje do odczytu mobilnego) bez konieczności aktywnego połączenia z internetem w momencie ich okazywania.

Omówienie prezentacji offline (ISO/IEC 18013-5)

Norma ISO/IEC 18013-5 definiuje ustandaryzowany, interoperacyjny protokół prezentacji offline między posiadaczem (urządzeniem mobilnym użytkownika z Portfelem Google) a czytnikiem / weryfikatorem (terminalem fizycznym lub towarzyszącą aplikacją mobilną).

Proces prezentacji składa się z kilku etapów:

  1. Zaangażowanie urządzenia: czytnik i portfel nawiązują początkowe połączenie przez zbliżenie NFC (statyczne lub negocjowane przekazanie) lub zeskanowanie kodu QR. Na tym etapie wymieniane są metadane zaangażowania urządzenia i tymczasowy klucz publiczny czytnika (EReaderKey).
  2. Połączenie do przesyłania danych: negocjowany jest bezpieczny, szyfrowany kanał Bluetooth Low Energy (BLE) (czytnik działa w trybie Central Client lub Peripheral Server).
  3. Żądanie urządzenia: czytnik przesyła zakodowany w formacie CBOR obiekt DeviceRequest, który określa żądany typ dokumentu (np. org.iso.18013.5.1.mDL) oraz konkretne żądane przestrzenie nazw i elementy danych.
  4. Zgoda użytkownika i uwierzytelnianie urządzenia: Portfel Google prosi użytkownika o sprawdzenie żądanych elementów danych i potwierdzenie udostępnienia za pomocą uwierzytelniania biometrycznego lub blokady ekranu.
  5. Odpowiedź urządzenia i weryfikacja kryptograficzna: portfel odsyła zakodowany w formacie CBOR obiekt DeviceResponse zawierający podpisany obiekt zabezpieczeń mobilnych (MSO) i podpisane przez urządzenie elementy danych. Czytnik weryfikuje podpisy kryptograficzne na podstawie zaufanych certyfikatów głównych.

Pakiet SDK Multipaz Open-Source

Aby wdrożyć aplikację czytnika lub weryfikatora, Google zaleca użycie Multipaz, czyli pakietu SDK Kotlin Multiplatform (KMP) o otwartym kodzie źródłowym, który został pierwotnie opracowany przez Google i przekazany do OpenWallet Foundation (OWF).

Multipaz zapewnia gotową do wykorzystania w środowisku produkcyjnym implementację protokołów czytnika i portfela ISO/IEC 18013-5, potoki weryfikacji kryptograficznej, kodowanie i dekodowanie CBOR oraz rozszerzalne schematy typów dokumentów.

Integracja Multipaz z aplikacją czytnika

Poniższe kroki pokazują, jak zintegrować pakiet SDK Multipaz z aplikacją czytnika na Androida.

Krok 1. Dodaj zależności

Biblioteki Multipaz są publikowane w Maven Central. Dodaj wymagane moduły do pliku build.gradle.kts aplikacji:

// 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")
}

Krok 2. Skonfiguruj uprawnienia na Androidzie

Weryfikacja stacjonarna wymaga uprawnień sprzętowych do korzystania z NFC, skanowania aparatem (w przypadku kodów QR) i przesyłania danych przez Bluetooth Low Energy. Dodaj do pliku AndroidManifest.xml te uprawnienia:

<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>

Krok 3. Zainicjuj mechanizm weryfikacji

Użyj abstrakcji połączenia VerificationHelper lub czytnika Multipaz, aby obsługiwać zdarzenia zaangażowania i zarządzać cyklem życia komunikacji 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()
    }
}

Krok 4. Utwórz DeviceRequest

Określ typ dokumentu i poszczególne elementy danych, które aplikacja musi zweryfikować. Zawsze stosuj zasadę minimalnego ujawniania informacji (np. proś tylko o age_over_21, a nie o pełne birth_date podczas weryfikacji wieku):

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)
    }
}

Obsługa dodatkowych typów dokumentów (DocTypes)

Multipaz obsługuje standardowe dane uwierzytelniające i zapewnia rozszerzalną architekturę do żądania niestandardowych lub specyficznych dla domeny typów dokumentów.

1. Wbudowane standardowe typy dokumentów

Biblioteka multipaz-doctypes udostępnia wstępnie zdefiniowane modele schematów dla standardowych danych logowania:

Identyfikator typu dokumentu Standard / zakres Typowa przestrzeń nazw Typowe elementy
org.iso.18013.5.1.mDL cyfrowe prawo jazdy zgodne z normą 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 Dane identyfikacyjne osoby w cyfrowym portfelu tożsamości UE (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 Cyfrowy dokument tożsamości w Portfelu Google / dokumenty testowe com.google.wallet.idcard.1 given_name, family_name, birth_date, document_number, portrait

2. Prośba o niestandardowe typy dokumentów

Aby poprosić o niestandardowe typy dokumentów, podczas tworzenia DeviceRequest zdefiniuj ciąg znaków docType i odpowiednie mapowania przestrzeni nazw:

// 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)
        )
    )
)

Weryfikacja kryptograficzna i zarządzanie zaufaniem

Otrzymanie ładunku odpowiedzi to dopiero pierwszy krok. Czytniki muszą przeprowadzić 4-etapową weryfikację kryptograficzną, aby potwierdzić autentyczność i integralność przedstawionego dokumentu.

Krok weryfikacji Cel weryfikacji
1. Uwierzytelnianie wydawcy Weryfikacja certyfikatu IssuerAuth (COSE_Sign1) pod kątem zaufanych certyfikatów głównych IACA
2. Sprawdzanie okresu ważności Sprawdź, czy validFrom ≤ bieżąca godzina ≤ validUntil
3. Sprawdzanie integralności danych Oblicz skróty SHA-256 zwróconych elementów i dopasuj je do MSO ValueDigests
4. Uwierzytelnianie urządzenia Weryfikuj podpis DeviceSigned lub MAC za pomocą DeviceKey powiązanego z SessionTranscript

1. 4-etapowy proces weryfikacji

  1. Uwierzytelnianie wystawcy (IssuerAuth):
    • Obiekt zabezpieczeń mobilnych (MSO) jest podpisany przez organ wydający (IssuerAuth ładunek).
    • Czytnik weryfikuje podpis COSE_Sign1 za pomocą certyfikatu osoby podpisującej dokument i sprawdza, czy łańcuch certyfikatów prowadzi do zaufanego certyfikatu głównego urzędu certyfikacji wydającego dokumenty (IACA).
  2. Weryfikacja okna ważności:
    • Czytnik sprawdza sygnatury czasowe validityInfo.validFrom i validityInfo.validUntil w obiekcie MSO względem bieżącego zegara czytnika, aby upewnić się, że dokument nie wygasł.
  3. Weryfikacja integralności danych (ValueDigests):
    • Dla każdego otrzymanego elementu IssuerSignedItem czytnik oblicza jego skrót (np. SHA-256) i sprawdza, czy jest on zgodny z odpowiednim wpisem skrótu w słowniku ValueDigests MSO.
  4. Uwierzytelnianie urządzenia (DeviceSigned):
    • Czytnik sprawdza, czy urządzenie, które przedstawia dane uwierzytelniające, ma klucz prywatny odpowiadający kluczowi DeviceKey opublikowanemu w podpisanym obiekcie MSO.
    • Odbywa się to przez weryfikację DeviceAuth (DeviceSignature lub DeviceMac) przez SessionTranscript, powiązanie sesji z kluczem efemerycznym czytnika i zapobieganie atakom typu „replay” i „man-in-the-middle”.

2. Zarządzanie zaufanymi certyfikatami głównymi IACA

Czytniki produkcyjne muszą utrzymywać bezpieczny, lokalny magazyn zaufania zawierający zaufane certyfikaty główne IACA:

  • Produkcyjne certyfikaty IACA: pobierz i skonfiguruj certyfikaty główne od oficjalnych urzędów certyfikacji. Zapoznaj się z listą obsługiwanych wydawców i certyfikatów IACA.
  • AAMVA VICAL: w przypadku jurysdykcji w Stanach Zjednoczonych systemy czytników mogą integrować się z usługą Verified Issuer Certificate Authority List (VICAL) organizacji American Association of Motor Vehicle Administrators (AAMVA), aby automatycznie synchronizować kotwice zaufania stanu.
  • Certyfikaty główne do testowania w piaskownicy: podczas testowania z użyciem danych logowania w piaskownicy upewnij się, że czytnik ufa certyfikatowi głównemu IACA piaskownicy 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
}

Uwierzytelnianie czytnika (zalecane)

Uwierzytelnianie czytnika umożliwia aplikacji czytnika kryptograficzne potwierdzenie swojej tożsamości w Portfelu Google przez podpisanie struktury ReaderAuthentication za pomocą autoryzowanego certyfikatu czytnika X.509.

  • Dlaczego jest to zalecane: uwierzytelnianie czytnika umożliwia aplikacji czytnika lub terminalowi przedstawienie użytkownikowi zaufanej tożsamości. Chociaż jest to opcjonalne w przypadku podstawowych atrybutów publicznych (np.weryfikacji age_over_21), zdecydowanie zalecamy to w przypadku integracji czytników, aby zwiększyć zaufanie użytkowników. Może to być wymagane przez prawo lub zasady, gdy prosisz o atrybuty poufne (takie jak pełny numer PESEL, adres zamieszkania lub określone potwierdzenia stanu).
  • Jak to działa: czytnik dołącza swój łańcuch certyfikatów i podpisuje transkrypcję sesji. Przed udostępnieniem danych Portfel Google wyświetla użytkownikowi na ekranie zgody zweryfikowaną tożsamość i nazwę organizacji czytnika.

Narzędzia do testowania i programowania

Aby przyspieszyć integrację, użyj tych narzędzi dla programistów i przykładowych implementacji:

  1. Przykładowe aplikacje Multipaz:
    • Skopiuj repozytorium Multipaz i uruchom przykładową aplikację IdentityReader na Androida, aby przetestować procesy weryfikacji fizycznej.
  2. Tworzenie dokumentu tożsamości w Portfelu Google:
  3. Testowanie weryfikatora internetowego:
    • Użyj strony verifier.multipaz.org, aby sprawdzać żądania CBOR, przeglądać zapytania dotyczące uprawnień i testować prezentacje internetowe W3C / ISO 18013-7.

Rozwiązywanie problemów i diagnostyka w terenie

W tabeli poniżej znajdziesz typowe problemy występujące podczas weryfikacji offline i zalecane rozwiązania:

Problem / objaw Główna przyczyna Zalecane rozwiązanie
Przekroczenie limitu czasu połączenia BLE / błąd połączenia
  • Zakłócenia częstotliwości radiowej w środowiskach o dużej gęstości.
  • Niezgodność trybu urządzenia peryferyjnego i centralnego na określonym sprzęcie czytnika.
  • Przekroczenie limitu czasu skanowania.
  • Upewnij się, że czytnik obsługuje tryby BLE Central Client i Peripheral Server.
  • Dostosuj okno i interwał skanowania BLE, aby zapewnić agresywne skanowanie podczas aktywnego zaangażowania.
  • Sprawdź, czy negocjacje rozmiaru MTU zakończyły się powodzeniem.
Niepowodzenie lub spadek zaangażowania po zbliżeniu urządzenia NFC Użytkownik odsuwa urządzenie mobilne od anteny czytnika, zanim rekord przekazania BLE zostanie w pełni przesłany.
  • Zapewnij natychmiastową reakcję wizualną, dźwiękową lub haptyczną na terminalu, gdy tylko rozpocznie się interakcja NFC.
  • Poproś użytkowników, aby trzymali telefon nieruchomo przy celu NFC, dopóki nie zostanie nawiązane połączenie BLE.
UNTRUSTED_ISSUER / Błąd łańcucha certyfikatów Certyfikat osoby podpisującej dokument nie jest powiązany z żadnym zaufanym certyfikatem IACA w lokalnym magazynie zaufania czytnika.
  • Sprawdź, czy certyfikat główny wydawcy jest wczytany do magazynu zaufania czytnika.
  • Jeśli testujesz w piaskownicy, sprawdź, czy certyfikat Google Sandbox IACA Root jest wczytany.
  • Zadbaj o to, aby listy certyfikatów IACA (np. AAMVA VICAL) były okresowo aktualizowane.
INVALID_VALIDITY_INFO / Expired MSO
  • Zegar systemowy czytnika nie jest zsynchronizowany.
  • Podpis MSO wygasł.
  • Upewnij się, że czytnik regularnie synchronizuje czas systemowy za pomocą protokołu NTP.
  • Poproś użytkownika o otwarcie Portfela Google, gdy jest połączony z internetem, aby odświeżyć tokeny danych uwierzytelniających.
DEVICE_AUTHENTICATION_FAILED Niezgodność transkrypcji sesji między czytnikiem a portfelem lub nieprawidłowy tymczasowy podpis urządzenia.
  • Upewnij się, że dokładne nieprzetworzone bajty DeviceEngagementBytes i EReaderKeyBytes są zachowywane w strukturze SessionTranscript bez ponownego kodowania.
Awaria uprawnień na Androidzie 12 lub nowszym Aplikacja próbowała skanować lub rozgłaszać przez BLE bez uprawnień w czasie działania.
  • Przed rozpoczęciem sesji czytnika sprawdź i poproś o uprawnienia BLUETOOTH_SCAN, BLUETOOTH_CONNECT i BLUETOOTH_ADVERTISE w czasie działania.

Wytyczne dotyczące UX i prywatności w przypadku czytników zbliżeniowych

Podczas projektowania czytników fizycznych i aplikacji towarzyszących:

  • Stosuj selektywne ujawnianie informacji w interfejsie: wyświetlaj operatorowi tylko decyzję lub minimalny wymagany atrybut (np. widoczny zielony znacznik wyboru i komunikat „Wiek powyżej 21 lat został zweryfikowany” zamiast pełnej daty urodzenia, adresu i numeru prawa jazdy użytkownika).
  • Wyraźne wskaźniki interakcji fizycznej: wyraźnie oznacz obszar docelowy NFC i wyświetlaj wskazówki wizualne (np. animacje lub paski postępu) pokazujące każdy etap: Kliknij / Zeskanuj → Łączenie → Weryfikacja → Gotowe.
  • Postępowanie z danymi tymczasowymi: nie przechowuj ani nie rejestruj elementów danych osobowych otrzymanych z portfela, chyba że jest to wyraźnie wymagane przez obowiązujące przepisy i ujawnione w intentToRetain = true.