Descripción general
En esta sección, se describe el proceso paso a paso para que los registradores de verificadores se integren con el servicio de identidad de la Billetera de Google.
Como registrador de verificadores (por ejemplo, una empresa de IDV que realiza la verificación en nombre de otras entidades), actúas como tu propia autoridad certificadora (AC) y firmas solicitudes de identidad para las partes finales receptoras (RPs) que administras.
Proceso de integración
Paso 1: Envía el formulario de admisión y los certificados raíz, y acepta las Condiciones del Servicio
Completa y envía el Formulario de admisión para la integración del registrador de verificadores. En este formulario, proporcionarás tus certificados raíz de zona de pruebas y de producción. Cuando envíes este formulario de admisión para la integración, también aceptarás formalmente las Condiciones del Servicio para el registrador de verificadores de la Billetera de Google.
Paso 2: Confianza y pruebas de la zona de pruebas
Después de enviar el formulario de admisión, Google agrega tu certificado raíz de la zona de pruebas al almacén de confianza de la zona de pruebas de la Billetera de Google y te notifica. Luego, puedes comenzar a probar tu integración en la zona de pruebas con certificados firmados por tu raíz de la zona de pruebas.
Paso 3: Graba una demostración de video de extremo a extremo
Cuando se completen las pruebas de la zona de pruebas, graba demostraciones de video de extremo a extremo del flujo de verificación para tu parte receptora inicial (primera) y envíalas a Google.
- Requisitos de video:
- Graba demostraciones para los flujos alojados por el verificador (autoalojados) y alojados por el comerciante (alojados por la RP) , según corresponda.
- Usa recursos de visualización reales del comerciante (nombre, logotipo, URL de las Condiciones del Servicio) y recursos de visualización del agregador en los videos.
- Demuestra claramente la interfaz de usuario y las pantallas que inician el flujo de verificación.
Paso 4: Aprobación y confianza de la raíz de producción
Cuando reciba tus demostraciones de video de extremo a extremo, Google activará el proceso de revisión y aprobación de videos y, en paralelo, iniciará el proceso de confianza del certificado raíz de producción. Una vez que se completen y aprueben ambos procesos, podrás comenzar a lanzar el servicio para tus RPs finales.
Paso 5: Integración continua de la RP final
Para cada RP final que firmes, debes hacer lo siguiente:
- Informa a Google: Usa el Formulario de integración del cliente del registrador de verificadores para notificar a Google sobre la nueva RP y su caso de uso previsto.
- Configura los metadatos: Completa la información de visualización de la RP (nombre, logotipo, URL de la Política de Privacidad) y establece un nombre distintivo (sujeto) único a nivel global en su certificado.
Especificaciones técnicas
A. Perfil de certificado
Las solicitudes deben estar firmadas por certificados X.509 v3 estándar generados con P-256 / ECDSA y que contengan una extensión personalizada de Google:
- OID de extensión personalizada:
1.3.6.1.4.1.11129.10.1 - Importancia: No crítica
- Contenido: Un hash SHA256 de
RelyingPartyMetadataBytes, codificado en unOCTET STRINGASN.1
B. Esquema de metadatos (CBOR)
Los metadatos deben estar codificados en formato CBOR.
; in CDDL for CBOR encoding
; schemaVersion = "v1"
RelyingPartyMetadataBytes = #6.24(bstr .cbor RelyingPartyMetadata)
RelyingPartyMetadata = {
"schema_version": tstr,
"display": DisplayInfo,
"aggregator_info": DisplayInfo ; Optional: include to show your branding alongside the RP
}
DisplayInfo = {
"display_name": tstr,
"logo_uri": tstr, ; See brand guidelines link in following paragraph
"privacy_policy_uri": tstr
}
El logo_uri debe seguir los Lineamientos de la marca de la Billetera de Google.
C. Integración de OpenID4VP
Cuando formatees tu solicitud de credenciales OpenID4VP firmada, incluye los metadatos codificados en base64url en el campo gw_rp_metadata_bytes dentro del objeto client_metadata (como se muestra en el código de solicitud de ejemplo en la siguiente sección).
Cumplimiento y revocación
- Supervisión de abusos: Google supervisa la actividad maliciosa de la RP y te notificará sobre cualquier abuso detectado.
- Revocación rápida: Debes revocar rápidamente los certificados de las RPs abusivas y publicar una Lista de revocación de certificados (CRL) actualizada.
- Auditoría: Google mantiene registros anonimizados para garantizar que las solicitudes de la RP coincidan con sus casos de uso registrados.
Próximos pasos
Para comenzar tu integración como registrador de verificadores, completa y envía el Formulario de admisión para la integración del registrador de verificadores. Para integrar clientes finales posteriores, usa el Formulario de integración del cliente del registrador de verificadores.
Para obtener respuestas a preguntas frecuentes sobre la integración, consulta las Preguntas frecuentes sobre credenciales y documentos de identidad digitales.
Detalles de la integración del registrador de verificadores
En la siguiente sección, se abarcan los detalles técnicos de la integración para los registradores de verificadores que se integran con la API de Digital Credentials (incluidos el formato de solicitud, el encriptado de solicitudes, la activación de la API, la validación de respuestas y la implementación de pruebas de conocimiento cero).
Formatos y capacidades compatibles
La Billetera de Google admite IDs digitales basados en **mdoc ISO**.
- Credenciales compatibles: Puedes consultar las credenciales y los atributos compatibles.
- Protocolos compatibles: OpenID4VP (versión 1.0).
- SDK de Android mínimo: Android 9 (nivel de API 28) y versiones posteriores
- Navegadores compatibles: Para obtener una lista completa de los navegadores que admiten la API de Digital Credentials, consulta la página Compatibilidad con el ecosistema.
- Preguntas frecuentes: Si tienes preguntas sobre la compatibilidad con países y el cronograma para las regiones nuevas, consulta las Preguntas frecuentes sobre credenciales y datos.
Formatea la solicitud
Para solicitar credenciales de cualquier billetera, debes formatear tu solicitud con OpenID4VP. Puedes solicitar credenciales específicas o varias credenciales en un solo objeto dcql_query.
Ejemplo de solicitud JSON
A continuación, se muestra un ejemplo de una solicitud requestJson de mdoc para obtener credenciales de identidad de cualquier billetera en un dispositivo Android o en la Web.
{
"requests" : [
{
"protocol": "openid4vp-v1-signed",
"data": {<signed_credential_request>} // This is an object, shouldn't be a string.
}
]
}
Encriptado de solicitudes
El client_metadata contiene la clave pública de encriptado para cada solicitud.
Deberás almacenar claves privadas para cada solicitud y usarlas para autenticar y autorizar el token que recibes de la app de la billetera.
Metadatos de OpenID4VP integrados
Cuando formatees tu solicitud de credenciales, debes incluir el campo gw_rp_metadata_bytes dentro del objeto client_metadata (como se muestra en el código de solicitud de ejemplo a continuación). Este campo contiene los metadatos de la parte receptora codificados en Base64URL que requiere la Billetera de Google para verificar tu identidad y mostrar tu marca al usuario.
El parámetro credential_request en requestJson contiene los siguientes campos.
Credencial específica
{
"response_type": "vp_token",
"response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
"nonce": "1234",
"dcql_query": {
"credentials": [
{
"id": "cred1",
"format": "mso_mdoc",
"meta": {
"doctype_value": "org.iso.18013.5.1.mDL" // this is for mDL. Use com.google.wallet.idcard.1 for ID pass
},
"claims": [
{
"path": [
"org.iso.18013.5.1",
"family_name"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.18013.5.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.18013.5.1",
"age_over_18"
],
"intent_to_retain": false
}
]
}
]
},
"client_metadata": {
"jwks": {
"keys": [ // sample request encryption key
{
"kty": "EC",
"crv": "P-256",
"x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
"y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ",
"use": "enc",
"kid" : "1", // This is required
"alg" : "ECDH-ES", // This is required
}
]
},
"vp_formats_supported": {
"mso_mdoc": {
"deviceauth_alg_values": [
-7
],
"issuerauth_alg_values": [
-7
]
}
},
"gw_rp_metadata_bytes": "<base64url encoded metadata string>"
}
}
Cualquier credencial apta
A continuación, se muestra la solicitud de ejemplo para el pase de identificación y la mDL. El usuario puede continuar con cualquiera de ellos.
{
"response_type": "vp_token",
"response_mode": "dc_api.jwt", // change this to dc_api if you want to demo with a non encrypted response.
"nonce": "1234",
"dcql_query": {
"credentials": [
{
"id": "mdl-request",
"format": "mso_mdoc",
"meta": {
"doctype_value": "org.iso.18013.5.1.mDL"
},
"claims": [
{
"path": [
"org.iso.18013.5.1",
"family_name"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.18013.5.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.18013.5.1",
"age_over_18"
],
"intent_to_retain": false
}
]
},
{ // Credential type 2
"id": "id_pass-request",
"format": "mso_mdoc",
"meta": {
"doctype_value": "com.google.wallet.idcard.1"
},
"claims": [
{
"path": [
"org.iso.18013.5.1",
"family_name"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.18013.5.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.18013.5.1",
"age_over_18"
],
"intent_to_retain": false
}
]
}
]
credential_sets : [
{
"options": [
[ "mdl-request" ],
[ "id_pass-request" ]
]
}
]
},
"client_metadata": {
"jwks": {
"keys": [ // sample request encryption key
{
"kty": "EC",
"crv": "P-256",
"x": "pDe667JupOe9pXc8xQyf_H03jsQu24r5qXI25x_n1Zs",
"y": "w-g0OrRBN7WFLX3zsngfCWD3zfor5-NLHxJPmzsSvqQ",
"use": "enc",
"kid" : "1", // This is required
"alg" : "ECDH-ES", // This is required
}
]
},
"vp_formats_supported": {
"mso_mdoc": {
"deviceauth_alg_values": [
-7
],
"issuerauth_alg_values": [
-7
]
}
},
"gw_rp_metadata_bytes": "<base64url encoded metadata string>"
}
}
Puedes solicitar cualquier cantidad de atributos admitidos de cualquier credencial de identidad almacenada en la Billetera de Google.
Solicitudes firmadas
Solicitudes firmadas (solicitudes de autorización protegidas por JWT) encapsulan tu solicitud de presentación verificable dentro de un token web JSON (JWT) firmado de forma criptográfica con tu infraestructura de PKI, lo que garantiza la integridad de la solicitud y demuestra tu identidad a la Billetera de Google.
Requisitos previos
Antes de implementar los cambios de código para la solicitud firmada, asegúrate de tener lo siguiente:
- Clave privada: Necesitas una clave privada (p.ej.,
ES256de curva elíptica) para firmar la solicitud que se administra en tu servidor. - Certificado: Necesitas un certificado X.509 estándar derivado de tu par de claves.
- Registro: Asegúrate de que tu certificado público esté registrado en la Billetera de Google.
Lógica de construcción de solicitudes
Para crear una solicitud, debes usar tu clave privada y ajustar la carga útil en un JWS.
def construct_openid4vp_request(
doctypes: list[str],
requested_fields: list[dict],
nonce_base64: str,
jwe_encryption_public_jwk: jwk.JWK,
is_zkp_request: bool,
is_signed_request: bool,
state: dict,
origin: str
) -> dict:
# ... [Existing logic to build 'presentation_definition' and basic 'request_payload'] ...
# ------------------------------------------------------------------
# SIGNED REQUEST IMPLEMENTATION (JAR)
# ------------------------------------------------------------------
if is_signed_request:
try:
# 1. Load the Verifier's Certificate
# We must load the PEM string into a cryptography x509 object
verifier_cert_obj = x509.load_pem_x509_certificate(
CERTIFICATE.encode('utf-8'),
backend=default_backend()
)
# 2. Calculate Client ID (x509_hash)
# We calculate the SHA-256 hash of the DER-encoded certificate.
cert_der = verifier_cert_obj.public_bytes(serialization.Encoding.DER)
verifier_fingerprint_bytes = hashlib.sha256(cert_der).digest()
# Create a URL-safe Base64 hash (removing padding '=')
verifier_fingerprint_b64 = base64.urlsafe_b64encode(verifier_fingerprint_bytes).decode('utf-8').rstrip("=")
# Format the client_id as required by the spec
client_id = f'x509_hash:{verifier_fingerprint_b64}'
# 3. Update Request Payload with JAR specific fields
request_payload["client_id"] = client_id
# Explicitly set expected origins to prevent relay attacks
# Format for android origin: origin = android:apk-key-hash:<base64SHA256_ofAppSigningCert>
# Format for web origin: origin = <origin_url>
if origin:
request_payload["expected_origins"] = [origin]
# 4. Create Signed JWT (JWS)
# Load the signing private key
signing_key = jwk.JWK.from_pem(PRIVATE_KEY.encode('utf-8'))
# Initialize JWS with the JSON payload
jws_token = jws.JWS(json.dumps(request_payload).encode('utf-8'))
# Construct the JOSE Header
# 'x5c' (X.509 Certificate Chain) is critical: it allows the wallet
# to validate your key against the one registered in the console.
x5c_value = base64.b64encode(cert_der).decode('utf-8')
protected_header = {
"alg": "ES256", # Algorithm (e.g., ES256 or RS256)
"typ": "oauth-authz-req+jwt", # Standard type for JAR
"kid": "1", # Key ID
"x5c": [x5c_value] # Embed the certificate
}
# Sign the token
jws_token.add_signature(
key=signing_key,
alg=None,
protected=json_encode(protected_header)
)
# 5. Return the Request Object
# Instead of returning the raw JSON, we return the signed JWT string
# under the 'request' key.
return {"request": jws_token.serialize(compact=True)}
except Exception as e:
print(f"Error signing OpenID4VP request: {e}")
return None
# ... [Fallback for unsigned requests] ...
return request_payload
Activa la API
Toda la solicitud a la API debe generarse del lado del servidor. Según la plataforma, pasarás el JSON generado a las APIs de la plataforma.
Conversiones (Android)
Para solicitar credenciales de identidad desde tus apps para Android, sigue estos pasos:
Actualiza las dependencias
En el archivo build.gradle de tu proyecto, actualiza las dependencias para usar el Administrador de credenciales (versión beta):
dependencies {
implementation("androidx.credentials:credentials:1.5.0-beta01")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}
Cómo configurar el Administrador de credenciales
Para configurar e inicializar un objeto CredentialManager, agrega una lógica similar
a la siguiente:
// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)
Solicita atributos de identidad
En lugar de especificar parámetros individuales para las solicitudes de identidad, la app los proporciona todos juntos como una cadena JSON dentro de CredentialOption.
El Administrador de credenciales pasa esta cadena JSON a las billeteras digitales disponibles sin examinar su contenido. Cada billetera es responsable de lo siguiente:
- Analizar la cadena JSON para comprender la solicitud de identidad
- Determinar cuáles de sus credenciales almacenadas, si las hay, satisfacen la solicitud
Recomendamos a los socios que creen sus solicitudes en el servidor, incluso para las integraciones de apps para Android.
Usarás el requestJson del formato de solicitud
como el request en la llamada a la función GetDigitalCredentialOption().
// The request in the JSON format to conform with
// the JSON-ified Digital Credentials API request definition.
val requestJson = generateRequestFromServer()
val digitalCredentialOption =
GetDigitalCredentialOption(requestJson = requestJson)
// Use the option from the previous step to build the `GetCredentialRequest`.
val getCredRequest = GetCredentialRequest(
listOf(digitalCredentialOption)
)
coroutineScope.launch {
try {
val result = credentialManager.getCredential(
context = activityContext,
request = getCredRequest
)
verifyResult(result)
} catch (e : GetCredentialException) {
handleFailure(e)
}
}
Administra la respuesta de credenciales
Una vez que recibas una respuesta de la billetera, verificarás si la respuesta es correcta y contiene la respuesta credentialJson.
// Handle the successfully returned credential.
fun verifyResult(result: GetCredentialResponse) {
val credential = result.credential
when (credential) {
is DigitalCredential -> {
val responseJson = credential.credentialJson
validateResponseOnServer(responseJson) // make a server call to validate the response
}
else -> {
// Catch any unrecognized credential type here.
Log.e(TAG, "Unexpected type of credential ${credential.type}")
}
}
}
// Handle failure.
fun handleFailure(e: GetCredentialException) {
when (e) {
is GetCredentialCancellationException -> {
// The user intentionally canceled the operation and chose not
// to share the credential.
}
is GetCredentialInterruptedException -> {
// Retry-able error. Consider retrying the call.
}
is NoCredentialException -> {
// No credential was available.
}
else -> Log.w(TAG, "Unexpected exception type ${e::class.java}")
}
}
La respuesta credentialJson contiene un identityToken (JWT) encriptado, definido por el W3C. La app de la Billetera es responsable de crear esta respuesta.
Ejemplo:
{
"protocol" : "openid4vp-v1-signed",
"data" : {
<encrpted_response>
}
}
Pasarás esta respuesta al servidor para validar su autenticidad. Puedes encontrar los pasos para validar la respuesta de credenciales
Web
Para solicitar credenciales de identidad con la API de Digital Credentials en Chrome o en otros navegadores compatibles, realiza la siguiente solicitud.
const credentialResponse = await navigator.credentials.get({
digital : {
requests : [
{
protocol: "openid4vp-v1-signed",
data: {<credential_request>} // This is an object, shouldn't be a string.
}
]
}
})
Envía la respuesta de esta API a tu servidor para validar la respuesta de credenciales
Valida la respuesta
Una vez que la billetera muestre el identityToken (JWT) encriptado, debes realizar una validación estricta del lado del servidor antes de confiar en los datos.
Desencripta la respuesta
Usa la clave privada correspondiente a la clave pública enviada en el client_metadata de la solicitud para desencriptar el JWE. Esto genera un vp_token.
Ejemplo de Python:
from jwcrypto import jwe, jwk
# Retrieve the Private Key from Datastore
reader_private_jwk = jwk.JWK.from_json(jwe_private_key_json_str)
# Save public key thumbprint for session transcript
encryption_public_jwk_thumbprint = reader_private_jwk.thumbprint()
# Decrypt the JWE encrypted response from Google Wallet
jwe_object = jwe.JWE()
jwe_object.deserialize(encrypted_jwe_response_from_wallet)
jwe_object.decrypt(reader_private_jwk)
decrypted_payload_bytes = jwe_object.payload
decrypted_data = json.loads(decrypted_payload_bytes)
decrypted_data generará un JSON vp_token que contiene la
credencial.
{
"vp_token":
{
"cred1": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
}
}
Crea la transcripción de la sesión.
El siguiente paso es crear el SessionTranscript de ISO/IEC 18013-5:2021 con una estructura de transferencia específica para Android o la Web:
SessionTranscript = [ null, // DeviceEngagementBytes not available null, // EReaderKeyBytes not available [ "OpenID4VPDCAPIHandover", AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web ] ]Para las transferencias de Android y la Web, deberás usar el mismo nonce que usaste para generar
credential_request.Transferencia de Android
AndroidHandoverData = [ origin, // "android:apk-key-hash:<base64SHA256_ofAppSigningCert>", nonce, // nonce that was used to generate credential request, encryption_public_jwk_thumbprint, // Encryption public key (JWK) Thumbprint ] AndroidHandoverDataBytes = hashlib.sha256(cbor2.dumps(AndroidHandoverData)).digest()
Transferencia del navegador
BrowserHandoverData =[ origin, // Origin URL nonce, // nonce that was used to generate credential request encryption_public_jwk_thumbprint, // Encryption public key (JWK) Thumbprint ] BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()
Con el
SessionTranscript, se debe validar la respuesta del dispositivo según la cláusula 9 de ISO/IEC 18013-5:2021.Esta validación incluye varios pasos:
Verifica el certificado del emisor: Extrae la cadena de certificados de firma del emisor de
issuerAuthy valídala con los certificados raíz de la IACA de confianza. Consulta los certificados de la IACA del emisor admitidos.Verifica la firma de MSO (sección 9.1.2 de 18013-5).
Calcula y verifica
ValueDigestspara los elementos de datos (sección 9.1.2 de 18013-5).Verifica la firma
deviceSignature(sección 9.1.3 de 18013-5).
{
"version": "1.0",
"documents": [
{
"docType": "org.iso.18013.5.1.mDL",
"issuerSigned": {
"nameSpaces": {...}, // contains data elements
"issuerAuth": [...] // COSE_Sign1 w/ issuer PK, mso + sig
},
"deviceSigned": {
"nameSpaces": 24(<< {} >>), // empty
"deviceAuth": {
"deviceSignature": [...] // COSE_Sign1 w/ device signature
}
}
}
],
"status": 0
}
Verificación de edad que preserva la privacidad (ZKP)
Para admitir pruebas de conocimiento cero (p.ej., verificar que un usuario tenga más de 18 años sin ver su fecha de nacimiento exacta), cambia el formato de tu solicitud a mso_mdoc_zk y proporciona la configuración zk_system_type requerida.
Para obtener una descripción general de qué es ZKP y sus capacidades, consulta las Preguntas frecuentes.
...
"dcql_query": {
"credentials": [{
"id": "cred1",
"format": "mso_mdoc_zk",
"meta": {
"doctype_value": "org.iso.18013.5.1.mDL"
"zk_system_type": [
{
"system": "longfellow-libzk-v1",
"circuit_hash": "f88a39e561ec0be02bb3dfe38fb609ad154e98decbbe632887d850fc612fea6f", // This will differ if you need more than 1 attribute.
"num_attributes": 1, // number of attributes (in claims) this has can support
"version": 5,
"block_enc_hash": 4096,
"block_enc_sig": 2945,
}
{
"system": "longfellow-libzk-v1",
"circuit_hash": "137e5a75ce72735a37c8a72da1a8a0a5df8d13365c2ae3d2c2bd6a0e7197c7c6", // This will differ if you need more than 1 attribute.
"num_attributes": 1, // number of attributes (in claims) this has can support
"version": 6,
"block_enc_hash": 4096,
"block_enc_sig": 2945,
}
],
"verifier_message": "challenge"
},
"claims": [{
...
"client_metadata": {
"jwks": {
"keys": [ // sample request encryption key
{
...
Recibirás una prueba de conocimiento cero encriptada de la billetera. Puedes validar esta prueba con los certificados de la IACA de los emisores con la biblioteca longfellow-zk de Google.
El servicio de verificador contiene un servidor basado en Docker listo para la implementación que te permite validar la respuesta con ciertos certificados de la IACA del emisor.
Puedes modificar certs.pem para administrar los certificados de entidad emisora de la IACA en los que deseas confiar.
Asistencia y recursos
- Preguntas frecuentes: Para obtener respuestas a preguntas frecuentes sobre la integración técnica, consulta las Preguntas frecuentes sobre credenciales y documentos de identidad digitales.
- Implementación de referencia: Consulta nuestra implementación de referencia de verificadores de identidad en GitHub.
- Sitio web de prueba: Prueba el flujo de extremo a extremo en verifier.multipaz.org.
- Especificación de OpenID4VP: Consulta la especificación técnica de openID4VP.
- Asistencia: Si necesitas ayuda para depurar o tienes preguntas durante la integración, comunícate con
wallet-identity-rp-support@google.com.