Aceptación en línea de credenciales digitales

Los IDs digitales se pueden aceptar en los flujos en la aplicación y web. Para aceptar credenciales de la Billetera de Google, deberás hacer lo siguiente:

  1. Realiza la integración a través de la app o la Web siguiendo las instrucciones proporcionadas.
  2. Completa este formulario para solicitar y aceptar las condiciones del servicio de aceptación de credenciales de la Billetera de Google.

Requisitos previos

Para probar la presentación de IDs de forma digital, primero debes inscribirte en el programa beta público con la cuenta de prueba que quieras (debe ser una cuenta de Gmail). Luego, proporciona los siguientes detalles a tu contacto de Google designado.

  • Vínculo a las Condiciones del Servicio
  • Logotipo
  • Sitio web
  • IDs de paquetes de aplicaciones (para integraciones de apps para Android)
    • Incluye compilaciones de depuración o de desarrollador
  • Firma de la app
    • $ $ANDROID_SDK/build-tools/$BUILD_TOOLS_VERSION/apksigner verify --print-certs -v $APK
  • El ID de Gmail que se usó para unirse a la versión beta pública

Formatos de credenciales compatibles

Existen varios estándares propuestos que definen el formato de datos de los documentos de identidad digital, y dos de ellos están ganando mucha tracción en la industria:

  1. mdocs: Es definido por ISO.
  2. Credenciales verificables de W3C: definidas por W3C.

Si bien el Administrador de credenciales de Android admite ambos formatos, por el momento, la Billetera de Google solo admite IDs digitales basados en mdoc.

Credenciales admitidas

La Billetera de Google admite 2 tipos de credenciales:

  1. Licencia de Conducir Digital (mDL)
  2. Pase de ID

Puedes solicitar cualquiera de las credenciales en tu flujo con un solo cambio de parámetro.

Experiencia del usuario

Cuando una aplicación solicita atributos de identidad, se produce el siguiente proceso:

  1. Descubrimiento de credenciales: La aplicación consulta las billeteras disponibles para identificar las credenciales que pueden satisfacer la solicitud. Luego, Android presenta un selector de IU del sistema que muestra la información que se compartirá. Esto permite que el usuario tome una decisión fundamentada sobre cuál credencial usar.

  2. Selección del usuario y la interacción con la billetera: El usuario selecciona una credencial y Android invoca la app de la billetera correspondiente para completar la transacción. Es posible que la app de la billetera presente su propia pantalla de consentimiento o requiera una confirmación biométrica.

  3. Resultado: Si el usuario da su consentimiento, las credenciales de identidad seleccionadas se comparten con la aplicación solicitante. Si el usuario rechaza la solicitud, se muestra un error.

Formato de solicitud para solicitar credenciales de ID desde la billetera

Este es un ejemplo de una solicitud de requestJson de mdoc para obtener credenciales de identidad desde cualquier billetera en un dispositivo Android o la Web.

{
    "providers": [{
      "protocol": "openid4vp",
      "request": "<credential_request>"
    }]
}

Solicitar encriptación

client_metadata contiene la clave pública de encriptación 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.

El parámetro credential_request en requestJson consistiría en los siguientes campos.

{
  "response_type": "vp_token",
  "response_mode": "dc_api.jwt",
  "nonce": "1234",
  "origin": "https://www.website.com", // omit this parameter for app
  "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"
            ]
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "given_name"
            ]
          },
          {
            "path": [
              "org.iso.18013.5.1",
              "age_over_18"
            ]
          }
        ]
      }
    ]
  },
  "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",
          "alg" : "ECDH-ES",
        }
      ]
    },
    "authorization_encrypted_response_alg": "ECDH-ES",
    "authorization_encrypted_response_enc": "A128GCM"
  }
}

Puedes solicitar cualquier cantidad de atributos compatibles desde cualquier credencial de identidad almacenada en la Billetera de Google.

En la app

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 (beta):

dependencies {
    implementation("androidx.credentials:credentials:1.5.0-beta01")
    // optional - needed for credentials support from play services, for devices running Android 13 and below.
    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, que consiste en 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)
    }
}

Verifica y valida la respuesta

Una vez que recibas una respuesta de la billetera, verificarás si la respuesta es exitosa 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, que define el W3C. La app de Wallet es responsable de crear esta respuesta.

Ejemplo:

"response" : {
  <encrpted_response>
}

Volverás a pasar esta respuesta al servidor para validar su autenticidad. Puedes encontrar los pasos para validar la respuesta de la credencial.

Web

Para solicitar credenciales de identidad con la API de Digital Credentials en Chrome, deberás registrarte en la prueba de origen de la API de Digital Credentials.

const credentialResponse = await navigator.credentials.get({
          digital: {
              providers: [{
                  protocol: "openid4vp",
                  request: "<credential_request>"
              }]
          },
      })

Envía la respuesta de esta API a tu servidor para validar la respuesta de la credencial.

Pasos para validar la respuesta de la credencial

Cuando recibas el identityToken encriptado de tu app o sitio web, deberás realizar varias validaciones antes de confiar en la respuesta.

  1. Cómo desencriptar una respuesta con una clave privada

    El primer paso es desencriptar el token con la clave privada guardada y obtener una respuesta JSON.

    Ejemplo:

      TinkConfig.register();
    
      // Sample Private Key JWK
      String privateKeysetJson = '{"crv":"P-256","d":"evjMOTTqWeTKKOrtWxq9kO_a5rHW_ja_wrT6eH7VPzs","kty":"EC","x":"C2Ka4HcNelUIxMjscNRq1GjFamP-fskGjvR6aY9Ac_Q","y":"a1tXuHhBnsGaaFyNzpqADY_Cp39Q56L2VLuADRsWncE"}';
    
      // Read the private keyset for Hybrid Decryption
      KeysetHandle privateKeysetHandle = CleartextKeysetHandle.read(
              JsonKeysetReader.withString(privateKeysetJson));
      HybridDecrypt decrypt = privateKeysetHandle.getPrimitive(HybridDecrypt.class);
    
      // Split the JWE string by the '.' delimiter
      String[] parts = jweString.split("\\.");
      if (parts.length != 5) {
          throw new IllegalArgumentException("Invalid JWE format (expected 5 parts)");
      }
    
      // The encrypted data is typically in the second part (index 1)
      byte[] ciphertext = Base64.getUrlDecoder().decode(parts[1]);
    
      // Associated data (AAD) is often the third part (index 2)
      byte[] associatedData = Base64.getUrlDecoder().decode(parts[2]);
    
      // Decrypt the ciphertext
      byte[] decryptedPayloadBytes = decrypt.decrypt(ciphertext, associatedData);
      String decryptedJsonResponse = new String(decryptedPayloadBytes, StandardCharsets.UTF_8);
    
    

    decrypted_json_response generará un JSON vp_token que contendrá la credencial.

    {
      "vp_token":
      {
        "cred1": "<credential_token>"
      }
    }
    
  2. Crea la transcripción de la sesión

    El siguiente paso es crear el SessionTranscript de la norma 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
      [
        "OID4VPDCAPIHandover",
        AndroidHandoverDataBytes   // BrowserHandoverDataBytes for Web
      ]
    ]
    

    Para las transferencias web y de Android, deberás usar el mismo nonce que usaste para generar credential_request.

    Transferencia de Android

        AndroidHandoverData = [
          origin,             // "android:apk-key-hash:<base64SHA256_ofAppSigningCert>",
          clientId,           // "android-origin:<app_package_name>",
          nonce,              // nonce that was used to generate credential request
        ]
    
        AndroidHandoverDataBytes = hashlib.sha256(cbor2.dumps(AndroidHandoverData)).digest()
        

    Transferencia del navegador

        BrowserHandoverData =[
          origin,               // Origin URL
          clientId,             // "web-origin:<origin>"
          nonce,               //  nonce that was used to generate credential request
        ]
    
        BrowserHandoverDataBytes = hashlib.sha256(cbor2.dumps(BrowserHandoverData)).digest()
        

    Con SessionTranscript, DeviceResponse se debe validar de acuerdo con el artículo 9 de la norma ISO/IEC 18013-5:2021. Esto incluye varios pasos, como los siguientes:

  3. Verifica el certificado de la entidad emisora estatal. Consulta los certificados de la IACA de las entidades emisoras compatibles.

  4. Verifica la firma del MSO (Sección 9.1.2 del estándar 18013-5)

  5. Calcula y verifica los ValueDigests para los elementos de datos (Sección 9.1.2 de la norma 18013-5)

  6. Verifica la firma de deviceSignature (Sección 9.1.3 de la norma 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
}

Prueba tu solución

Para probar tu solución, compila y ejecuta nuestra aplicación para Android de contenedor de referencias de código abierto. Estos son los pasos para compilar y ejecutar la app del contenedor de referencia:

  • Clona el repositorio de apps de referencia
  • Abre el proyecto en Android Studio.
  • Compila y ejecuta el destino appholder en tu dispositivo Android o emulador.