Aceptación en persona de credenciales digitales (sin conexión)

En esta guía, se explica cómo las partes de confianza (RP) y los desarrolladores de lectores pueden implementar la verificación presencial (sin conexión) de las credenciales digitales presentadas desde la Billetera de Google según el estándar internacional ISO/IEC 18013-5.

Las credenciales digitales de la Billetera de Google se pueden verificar de forma segura en entornos físicos (como terminales de punto de venta, lugares de eventos, puertas de transporte público, lectores de fuerzas del orden y apps de lectores móviles) sin requerir una conexión a Internet activa en el momento de la presentación.

Descripción general de la presentación sin conexión (ISO/IEC 18013-5)

El estándar ISO/IEC 18013-5 define un protocolo interoperable y estandarizado para la presentación sin conexión entre un titular (el dispositivo móvil del usuario que ejecuta la Billetera de Google) y un lector o verificador (una terminal física o una app complementaria para dispositivos móviles).

El flujo de presentación se produce en fases distintas:

  1. Interacción del dispositivo: El lector y la billetera establecen el contacto inicial a través de NFC Tap (entrega estática o negociada) o escaneo de código QR. Durante esta fase, se intercambian los metadatos de participación del dispositivo y la clave pública efímera del lector (EReaderKey).
  2. Conexión de transporte de datos: Se negocia un canal Bluetooth de bajo consumo (BLE) seguro y encriptado (con el lector actuando en modo de cliente central o servidor periférico).
  3. Solicitud del dispositivo: El lector transmite un DeviceRequest codificado en CBOR que especifica el tipo de documento solicitado (como org.iso.18013.5.1.mDL) y los espacios de nombres y elementos de datos específicos solicitados.
  4. Consentimiento del usuario y autenticación del dispositivo: La Billetera de Google le solicita al usuario que revise los elementos de datos solicitados y confirme el uso compartido mediante la autenticación biométrica o el bloqueo de pantalla.
  5. Respuesta del dispositivo y verificación criptográfica: La billetera envía una DeviceResponse codificada en CBOR que contiene el objeto de seguridad móvil (MSO) firmado y los elementos de datos firmados por el dispositivo. El lector verifica las firmas criptográficas en comparación con los certificados raíz de confianza.

El SDK de código abierto de Multipaz

Para implementar una aplicación de lector o verificador, Google recomienda usar Multipaz, un SDK de Kotlin Multiplatform (KMP) de código abierto desarrollado originalmente por Google y aportado a la OpenWallet Foundation (OWF).

Multipaz proporciona una implementación lista para producción de los protocolos de lector y billetera ISO/IEC 18013-5, canalizaciones de verificación criptográfica, codificación y decodificación de CBOR, y esquemas de tipo de documento extensibles.

Cómo integrar Multipaz en tu app de lector

En los siguientes pasos, se muestra cómo integrar el SDK de Multipaz en una aplicación de lector de Android.

Paso 1: Agrega dependencias

Las bibliotecas de Multipaz se publican en Maven Central. Agrega los módulos necesarios al archivo build.gradle.kts de tu aplicación:

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

Paso 2: Configura los permisos de Android

La verificación presencial requiere permisos de hardware para la interacción de NFC, el escaneo de la cámara (para la interacción de QR) y el transporte de datos de Bluetooth de bajo consumo. Agrega los siguientes permisos a tu 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>

Paso 3: Inicializa el motor de verificación

Usa las abstracciones de conexión VerificationHelper o Reader de Multipaz para controlar los eventos de interacción y administrar el ciclo de vida de la comunicación 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()
    }
}

Paso 4: Construye el DeviceRequest

Especifica el tipo de documento y los elementos de datos individuales que tu aplicación necesita verificar. Siempre aplica el principio de divulgación mínima (p.ej., solicita solo age_over_21 en lugar de birth_date completa cuando verificas la edad):

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

Cómo admitir tipos de documentos adicionales (DocTypes)

Multipaz admite credenciales estandarizadas listas para usar y proporciona una arquitectura extensible para solicitar tipos de documentos personalizados o específicos del dominio.

1. Tipos de documentos estándar integrados

La biblioteca multipaz-doctypes proporciona modelos de esquema predefinidos para credenciales estándar:

Identificador del tipo de documento Estándar o alcance Espacio de nombres típico Elementos comunes
org.iso.18013.5.1.mDL Licencia de conducir digital 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 Datos de identificación de personas de la Billetera de identidad digital de la 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 Pase de ID de la Billetera de Google o IDs de prueba com.google.wallet.idcard.1 given_name, family_name, birth_date, document_number, portrait

2. Cómo solicitar tipos de documentos personalizados

Para solicitar tipos de documentos personalizados, define la cadena docType de destino y las asignaciones de espacio de nombres correspondientes cuando construyas el 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)
        )
    )
)

Verificación criptográfica y administración de confianza

Recibir una carga útil de respuesta es solo el primer paso. Los lectores deben realizar una verificación criptográfica de cuatro pasos para validar la autenticidad y la integridad de la credencial presentada.

Paso de verificación Destino de verificación
1. Autenticación de la entidad emisora Verifica IssuerAuth (COSE_Sign1) en comparación con los certificados raíz de IACA de confianza.
2. Verificación del período de validez Asegúrate de que validFrom ≤ hora actual ≤ validUntil.
3. Verificación de la integridad de los datos Calcula los resúmenes SHA-256 de los elementos que se muestran y compáralos con ValueDigests de MSO.
4. Autenticación en dispositivos Verifica la firma o el MAC DeviceSigned con la DeviceKey vinculada a la SessionTranscript.

1. La canalización de verificación de 4 pasos

  1. Autenticación de la entidad emisora (IssuerAuth):
    • La autoridad emisora (IssuerAuth payload) firma el objeto de seguridad móvil (MSO).
    • El lector verifica la firma COSE_Sign1 con el certificado del firmante del documento y se asegura de que la cadena de certificados llegue hasta un certificado raíz de autoridad certificadora (AC) de la entidad emisora (IACA) de confianza.
  2. Verificación del período de validez:
    • El lector verifica las marcas de tiempo validityInfo.validFrom y validityInfo.validUntil en el MSO en comparación con el reloj actual del lector para asegurarse de que la credencial no haya vencido.
  3. Verificación de la integridad de los datos (ValueDigests):
    • Para cada IssuerSignedItem recibido, el lector calcula su resumen (p.ej., SHA-256) y verifica que coincida con la entrada de hash correspondiente en el diccionario ValueDigests del MSO.
  4. Autenticación en dispositivos (DeviceSigned):
    • El lector valida que el dispositivo que presenta la credencial tenga la clave privada correspondiente a la DeviceKey publicada dentro del MSO firmado.
    • Para ello, se verifica la DeviceAuth (ya sea DeviceSignature o DeviceMac) en la SessionTranscript, se vincula la sesión a la clave efímera del lector y se evitan los ataques de reproducción y de intermediario.

2. Administra los certificados raíz de IACA de confianza

Los lectores de producción deben mantener un almacén de confianza local y seguro que contenga certificados raíz de IACA de confianza:

  • Certificados de IACA de producción: Descarga y configura certificados raíz de autoridades emisoras oficiales. Consulta nuestra lista de entidades emisoras admitidas y certificados de IACA.
  • AAMVA VICAL: En el caso de las jurisdicciones de EE.UU., los sistemas de lectores pueden integrarse con el servicio de la Lista de autoridades certificadoras verificadas (VICAL) de la Asociación Estadounidense de Administradores de Vehículos Motorizados (AAMVA) para sincronizar automáticamente los anclajes de confianza estatales.
  • Raíces de pruebas de la zona de pruebas: Cuando realices pruebas con credenciales de la zona de pruebas, asegúrate de que el lector confíe en la raíz de IACA de la zona de pruebas de 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
}

Autenticación del lector (recomendada)

La autenticación del lector permite que la aplicación del lector demuestre criptográficamente su identidad a la Billetera de Google firmando la estructura ReaderAuthentication con un certificado de lector X.509 autorizado.

  • Por qué se recomienda: La autenticación del lector permite que tu app o terminal de lector le presente una identidad de confianza al usuario. Si bien es opcional para los atributos públicos básicos (p.ej., verificar age_over_21), se recomienda para las integraciones de lectores aumentar la confianza del usuario y puede ser legal o requerida por la política cuando se solicitan atributos sensibles (como el número de Seguro Social completo, la dirección residencial o los endosos estatales específicos).
  • Cómo funciona: El lector incluye su cadena de certificados y firma la transcripción de la sesión. La Billetera de Google muestra la identidad verificada y el nombre de la organización del lector al usuario en la pantalla de consentimiento antes de la publicación de los datos.

Herramientas de prueba y desarrollo

Para acelerar la integración, usa las siguientes herramientas para desarrolladores y las implementaciones de referencia:

  1. Apps de referencia de Multipaz:
    • Clona el repositorio de Multipaz y ejecuta la app de ejemplo de Android IdentityReader para probar los flujos de verificación física.
  2. Crea un ID de prueba en la Billetera de Google:
  3. Pruebas de verificadores basados en la Web:
    • Usa verifier.multipaz.org para inspeccionar solicitudes CBOR, explorar consultas de reclamos y probar presentaciones basadas en la Web de W3C o ISO 18013-7.

Solución de problemas y diagnóstico de campo

En la siguiente tabla, se enumeran los problemas comunes que se producen durante la verificación sin conexión y las resoluciones recomendadas:

Problema o síntoma Causa raíz Resolución recomendada
Tiempo de espera agotado de la conexión BLE o falla en la conexión
  • Interferencia de RF en entornos de alta densidad
  • Incompatibilidad entre el modo periférico y el central en hardware de lector específico
  • Tiempos de espera agotados de escaneo
  • Asegúrate de que el lector admita los modos de cliente central BLE y servidor periférico.
  • Ajusta la ventana y el intervalo de escaneo de BLE para realizar un escaneo agresivo durante la participación activa.
  • Verifica que la negociación del tamaño de MTU se complete correctamente.
Falla o se interrumpe la participación de NFC Tap El usuario aleja el dispositivo móvil de la antena del lector antes de que se transfiera por completo el registro de entrega de BLE.
  • Proporciona comentarios visuales, de audio o hápticos inmediatos en la terminal en cuanto comience la participación de NFC.
  • Indica a los usuarios que sostengan el teléfono de forma estable contra el objetivo de NFC hasta que se establezca la conexión BLE.
UNTRUSTED_ISSUER o falla en la cadena de certificados El certificado del firmante del documento no se encadena a ningún certificado de IACA de confianza en el almacén de confianza local del lector.
  • Verifica que el certificado raíz de la entidad emisora esté cargado en el almacén de confianza del lector.
  • Si realizas pruebas en la zona de pruebas, verifica que esté cargada la raíz de IACA de la zona de pruebas de Google.
  • Asegúrate de que las listas de certificados de IACA (p.ej., AAMVA VICAL) se actualicen periódicamente.
INVALID_VALIDITY_INFO o MSO vencido
  • El reloj del sistema del lector no está sincronizado.
  • Venció la firma del MSO.
  • Asegúrate de que el dispositivo lector sincronice su hora del sistema con regularidad a través de NTP.
  • Pídele al usuario que abra la Billetera de Google mientras esté conectado a Internet para actualizar los tokens de credenciales.
DEVICE_AUTHENTICATION_FAILED No coincide la transcripción de la sesión entre el lector y la billetera, o la firma del dispositivo efímero no es válida.
  • Asegúrate de que los bytes sin procesar exactos de DeviceEngagementBytes y EReaderKeyBytes se conserven en la estructura SessionTranscript sin volver a codificarlos.
Falla de permisos de Android 12 y versiones posteriores La aplicación intentó escanear o publicar a través de BLE sin permisos de tiempo de ejecución.
  • Verifica y solicita BLUETOOTH_SCAN, BLUETOOTH_CONNECT y BLUETOOTH_ADVERTISE en el tiempo de ejecución antes de iniciar las sesiones del lector.

Lineamientos de UX y privacidad para lectores presenciales

Cuando diseñes lectores físicos y apps complementarias, ten en cuenta lo siguiente:

  • Practica la divulgación selectiva en la IU: Solo muestra la decisión o el atributo mínimo requerido al operador (p.ej., muestra una marca de verificación verde destacada y "Edad verificada: 21 años o más" en lugar de mostrar la fecha de nacimiento, la dirección y el número de licencia completos del usuario).
  • Indicadores claros de interacción física: Etiqueta claramente la zona objetivo de NFC y muestra indicadores visuales (como animaciones o barras de progreso) que muestren cada etapa: Presionar o escanear → Conectando → Verificando → Completado.
  • Control de datos efímeros: No almacenes ni registres elementos de datos personales recibidos de la billetera, a menos que lo exija explícitamente la ley aplicable y se divulgue a través de intentToRetain = true.