Прием цифровых удостоверений личности при личном взаимодействии (офлайн)

В этом руководстве рассказывается, как полагающиеся стороны и разработчики считывающих устройств могут реализовать офлайн-проверку цифровых удостоверений личности, предъявляемых из Google Кошелька, в соответствии с международным стандартом ISO/IEC 18013-5.

Цифровые удостоверения личности в Google Кошельке можно безопасно проверять в физической среде (например, на терминалах оплаты, в местах проведения мероприятий, на турникетах, считывателях правоохранительных органов и в мобильных приложениях для считывания), не требуя активного подключения к интернету во время предъявления.

Обзор офлайн-презентации (ISO/IEC 18013-5)

Стандарт ISO/IEC 18013-5 определяет стандартизированный, совместимый протокол для офлайн-презентации между держателем (мобильным устройством пользователя с Google Кошельком) и считывателем / верификатором (физическим терминалом или сопутствующим мобильным приложением).

Процесс презентации состоит из нескольких этапов:

  1. Взаимодействие устройств. Терминал и кошелек устанавливают первоначальный контакт через NFC (статическая или согласованная передача) или сканирование QR-кода. На этом этапе происходит обмен метаданными взаимодействия устройства и временным открытым ключом считывателя (EReaderKey).
  2. Подключение для передачи данных. Устанавливается защищенный зашифрованный канал Bluetooth с низким энергопотреблением (BLE). При этом считыватель работает в режиме центрального клиента или периферийного сервера.
  3. Запрос устройства. Считыватель передает закодированный в CBOR DeviceRequest, указывая тип запрашиваемого документа (например, org.iso.18013.5.1.mDL), а также конкретные пространства имен и элементы данных.
  4. Согласие пользователя и аутентификация устройства. Google Кошелек предлагает пользователю проверить запрашиваемые элементы данных и подтвердить передачу с помощью биометрической аутентификации или блокировки экрана.
  5. Ответ устройства и криптографическая проверка. Кошелек отправляет обратно объект DeviceResponse, закодированный в формате CBOR и содержащий подписанный объект MSO и элементы данных, подписанные устройством. Считыватель проверяет криптографические подписи по доверенным корневым сертификатам.

Multipaz Open-Source SDK

Для реализации приложения для считывания или проверки Google рекомендует использовать Multipaz – мультиплатформенный SDK Kotlin с открытым исходным кодом, изначально разработанный Google и переданный OpenWallet Foundation (OWF).

Multipaz предоставляет готовую к использованию реализацию протоколов считывателя и кошелька ISO/IEC 18013-5, конвейеры криптографической проверки, кодирование/декодирование CBOR и расширяемые схемы типов документов.

Как интегрировать Multipaz в приложение для считывания

Ниже описано, как интегрировать Multipaz SDK в приложение для считывания на Android.

Шаг 1. Добавьте зависимости

Библиотеки Multipaz опубликованы в Maven Central. Добавьте необходимые модули в файл build.gradle.kts приложения:

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

Шаг 2. Настройте разрешения Android

Для личной проверки требуются разрешения на использование NFC, камеры (для сканирования QR-кодов) и Bluetooth с низким энергопотреблением. Добавьте в файл 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>

Шаг 3. Инициализируйте механизм проверки

Используйте абстракции Multipaz VerificationHelper или Reader connection, чтобы обрабатывать события взаимодействия и управлять жизненным циклом связи 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()
    }
}

Шаг 4. Создайте DeviceRequest

Укажите тип документа и отдельные элементы данных, которые нужно проверить в приложении. Всегда применяйте принцип минимального раскрытия информации (например, запрашивайте только age_over_21, а не полную birth_date при проверке возраста):

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

Поддержка дополнительных типов документов (DocType)

Multipaz поддерживает стандартные удостоверения личности и предоставляет расширяемую архитектуру для запроса документов определенных типов.

1. Встроенные стандартные типы документов

Библиотека multipaz-doctypes предоставляет стандартные модели схем для стандартных учетных данных:

Идентификатор типа документа Стандарт / область применения Типичное пространство имен Общие элементы
org.iso.18013.5.1.mDL Цифровое водительское удостоверение 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 Идентификационные данные лица в цифровом кошельке ЕС (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 Цифровое удостоверение личности в Google Кошельке / тестовые удостоверения личности com.google.wallet.idcard.1 given_name, family_name, birth_date, document_number, portrait

2. Как запросить собственные типы документов

Чтобы запросить пользовательские типы документов, определите целевую строку docType и соответствующие сопоставления пространства имен при создании 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)
        )
    )
)

Криптографическая проверка и управление доверием

Получение полезной нагрузки ответа – это только первый шаг. Считыватели должны выполнять четырехэтапную криптографическую проверку, чтобы подтвердить подлинность и целостность предъявленного удостоверения.

Этап проверки Цель проверки
1. Issuer Authentication Проверьте IssuerAuth (COSE_Sign1) по доверенным корневым сертификатам IACA.
2. Проверка срока действия Убедитесь, что validFrom ≤ текущее время ≤ validUntil.
3. Проверка целостности данных Вычислите дайджесты SHA-256 возвращенных элементов и сопоставьте их с MSO ValueDigests.
4. Аутентификация устройства Проверьте подпись DeviceSigned или MAC-адрес, используя DeviceKey, связанный с SessionTranscript.

1. Четыре этапа проверки

  1. Аутентификация эмитента (IssuerAuth):
    • Объект мобильной безопасности (MSO) подписывается органом, выдавшим удостоверение (полезная нагрузка IssuerAuth).
    • Терминал проверяет подпись COSE_Sign1 с помощью сертификата подписи документа и убеждается, что сертификат связан с доверенным корневым сертификатом центра сертификации, выдавшего документ (IACA).
  2. Проверка срока действия:
    • Считыватель проверяет временные метки validityInfo.validFrom и validityInfo.validUntil в MSO, чтобы убедиться, что срок действия удостоверения не истек.
  3. Проверка целостности данных (ValueDigests):
    • Для каждого полученного элемента IssuerSignedItem считывающее устройство вычисляет дайджест (например, SHA-256) и проверяет, соответствует ли он записи хеша в словаре ValueDigests MSO.
  4. Аутентификация устройства (DeviceSigned):
    • Считыватель проверяет, что устройство, предоставляющее удостоверение, содержит закрытый ключ, соответствующий DeviceKey, опубликованному в подписанном MSO.
    • Для этого проверяется DeviceAuth (DeviceSignature или DeviceMac) по SessionTranscript, сеанс привязывается к эфемерному ключу считывателя и предотвращаются атаки с повторной передачей и атаки посредника.

2. Управление доверенными корневыми сертификатами IACA

В рабочих считывателях должно быть защищенное локальное хранилище доверия, содержащее доверенные корневые сертификаты IACA:

  • Рабочие сертификаты IACA. Скачайте и настройте корневые сертификаты от официальных центров сертификации. Ознакомьтесь со списком поддерживаемых эмитентов и сертификатов IACA.
  • AAMVA VICAL. В США системы считывания могут интегрироваться с сервисом VICAL (Verified Issuer Certificate Authority List) Американской ассоциации администраторов автотранспорта (AAMVA), чтобы автоматически синхронизировать якоря доверия штатов.
  • Корневые сертификаты для тестирования в изолированной среде. При тестировании с использованием учетных данных изолированной среды убедитесь, что считыватель доверяет корневому сертификату Google Sandbox IACA Root.
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
}

Аутентификация читателей (рекомендуется)

Аутентификация считывающего устройства позволяет приложению для считывания криптографически подтвердить свою личность в Google Кошельке, подписав структуру ReaderAuthentication с помощью авторизованного сертификата считывающего устройства X.509.

  • Почему это рекомендуется. Аутентификация считывающего устройства позволяет вашему приложению или терминалу подтвердить свою надежность пользователю. Для базовых общедоступных атрибутов (например, для подтверждения age_over_21) это необязательно, но настоятельно рекомендуется для интеграции со считывающими устройствами, чтобы повысить доверие пользователей. Кроме того, это может требоваться по закону или правилам при запросе конфиденциальных атрибутов (например, полного номера социального страхования, адреса проживания или определенных государственных подтверждений).
  • Как это работает. Считывающее устройство включает цепочку сертификатов и подписывает расшифровку сеанса. Перед передачей данных Google Кошелек показывает пользователю подтвержденную личность и название организации считывающего устройства на окне запроса доступа.

Инструменты для тестирования и разработки

Чтобы ускорить интеграцию, используйте следующие инструменты разработчика и эталонные реализации:

  1. Приложения Multipaz Reference:
    • Клонируйте репозиторий Multipaz и запустите пример приложения для Android IdentityReader, чтобы протестировать процессы физической проверки.
  2. Как создать тестовое удостоверение личности в Google Кошельке
  3. Тестирование веб-верификатора
    • Используйте verifier.multipaz.org, чтобы проверять запросы CBOR, изучать запросы на получение данных и тестировать веб-презентации W3C / ISO 18013-7.

Устранение неполадок и диагностика на месте

В таблице ниже перечислены распространенные проблемы, возникающие при офлайн-проверке, и рекомендуемые способы их решения.

Проблема / признак Основная причина Рекомендуемое решение
Истекло время ожидания подключения BLE / Не удалось подключиться
  • Радиопомехи в средах с высокой плотностью.
  • Несовместимость периферийного и центрального режимов на определенном оборудовании считывателя.
  • Время ожидания сканирования.
  • Убедитесь, что считыватель поддерживает режимы BLE Central Client и Peripheral Server.
  • Настройте окно и интервал сканирования BLE для агрессивного сканирования во время активного взаимодействия.
  • Убедитесь, что согласование размера MTU завершено успешно.
Сбои или прерывания при бесконтактной оплате NFC Пользователь убирает мобильное устройство от антенны считывателя до того, как запись о передаче BLE будет полностью передана.
  • Обеспечьте немедленный визуальный, звуковой или виброотклик на терминале, как только начнется взаимодействие по NFC.
  • Попросите пользователей держать телефон неподвижно у NFC-метки, пока не будет установлено BLE-подключение.
UNTRUSTED_ISSUER / Ошибка цепочки сертификатов Сертификат подписавшего документ не связан ни с одним доверенным сертификатом IACA в локальном хранилище доверия считывателя.
  • Убедитесь, что корневой сертификат издателя загружен в хранилище доверия устройства чтения.
  • Если вы проводите тестирование в тестовой среде, убедитесь, что загружен корневой сертификат Google Sandbox IACA Root.
  • Периодически обновляйте списки сертификатов IACA (например, AAMVA VICAL).
INVALID_VALIDITY_INFO / Expired MSO
  • Системные часы устройства чтения не синхронизированы.
  • Срок действия подписи MSO истек.
  • Убедитесь, что системное время на считывающем устройстве регулярно синхронизируется по протоколу NTP.
  • Попросите пользователя открыть Google Кошелек, подключившись к интернету, чтобы обновить токены удостоверения.
DEVICE_AUTHENTICATION_FAILED Расшифровка сеанса не совпадает в считывателе и Кошельке, или подпись временного устройства недействительна.
  • Убедитесь, что точные необработанные байты DeviceEngagementBytes и EReaderKeyBytes сохранены в структуре SessionTranscript без перекодирования.
Сбой, связанный с разрешениями, в Android 12 и более поздних версиях Приложение пыталось выполнить сканирование или передать данные по BLE без динамических разрешений.
  • Проверьте и запросите разрешения BLUETOOTH_SCAN, BLUETOOTH_CONNECT и BLUETOOTH_ADVERTISE во время выполнения, прежде чем запускать сеансы считывания.

Рекомендации по UX и правилам конфиденциальности для личных читателей

При разработке физических считывающих устройств и сопутствующих приложений:

  • Выборочное раскрытие информации в интерфейсе. Показывайте оператору только решение или минимально необходимый атрибут. Например, вместо полной даты рождения, адреса и номера удостоверения личности пользователя можно показать заметную зеленую галочку и надпись Возраст 21+ подтвержден.
  • Понятные индикаторы физического взаимодействия. Четко обозначьте целевую зону NFC и показывайте визуальные подсказки (например, анимацию или индикаторы выполнения), отражающие каждый этап: Нажмите / Отсканируйте → Подключение → Проверка → Готово.
  • Обработка временных данных. Не храните и не регистрируйте элементы персональных данных, полученные из кошелька, если это не требуется действующим законодательством и не указано в intentToRetain = true.