Este guia explica como as partes confiáveis (RPs, na sigla em inglês) podem integrar tecnicamente a API Digital Credentials para solicitar e validar Carteiras de Habilitação para Dispositivos Móveis (mDLs) e passes de identificação da Carteira do Google em apps Android e na Web.
Processo de registro e pré-requisitos
Antes de entrar em produção, é necessário registrar formalmente o aplicativo da parte confiável no Google.
- Teste no sandbox: você pode começar o desenvolvimento imediatamente usando nosso ambiente de sandbox e criando um ID de teste. Não é necessário aceitar os Termos de Serviço para testes.
- Enviar o formulário de entrada:preencha o formulário de integração da RP. A integração geralmente leva de 3 a 5 dias úteis. O nome e o logotipo do produto serão mostrados na tela de permissão voltada ao usuário para ajudar a identificar quem está solicitando os dados.
- Aceitar os Termos de Serviço:é necessário assinar os Termos de Serviço antes de entrar em produção.
Para perguntas sobre como acompanhar seu aplicativo ou testar em regiões não aceitas, consulte as Perguntas frequentes.
Formatos e recursos aceitos
A Carteira do Google aceita documentos de identificação digitais baseados em mdoc ISO.
- Credenciais aceitas:confira as credenciais e os atributos aceitos.
- Protocolos aceitos: OpenID4VP (versão 1.0).
- SDK mínimo do Android:Android 9 (nível 28 da API) e versões mais recentes.
- Compatibilidade com navegadores:para uma lista completa de navegadores que oferecem suporte à API Digital Credentials, consulte a página de suporte do ecossistema.
- Perguntas frequentes:para perguntas sobre o suporte a países e o cronograma de novas regiões, consulte as Perguntas frequentes sobre credenciais e dados.
Formatar a solicitação
Para solicitar credenciais de qualquer carteira, é necessário formatar a solicitação usando o OpenID4VP. É possível solicitar credenciais específicas ou várias credenciais em um único objeto dcql_query.
Exemplo de solicitação JSON
Confira um exemplo de solicitação requestJson de mdoc para receber credenciais de identidade de qualquer carteira em um dispositivo Android ou na Web.
{
"requests" : [
{
"protocol": "openid4vp-v1-signed",
"data": {<signed_credential_request>} // This is an object, shouldn't be a string.
}
]
}
Solicitar criptografia
O client_metadata contém a chave pública de criptografia para cada solicitação.
É necessário armazenar chaves privadas para cada solicitação e usá-las para autenticar e autorizar o token recebido do app Carteira.
O parâmetro credential_request em requestJson contém os seguintes campos.
Qualificações específicas
{
"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
]
}
}
}
}
Qualquer credencial qualificada
Confira o exemplo de solicitação para mDL e passe de identificação. O usuário pode continuar com qualquer um deles.
{
"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
],
"isserauth_alg_values": [
-7
]
}
}
}
}
É possível solicitar qualquer número de atributos aceitos de qualquer credencial de identidade armazenada na Carteira do Google.
Solicitações assinadas
Solicitações assinadas (solicitações de autorização protegidas por JWT) encapsulam sua solicitação de apresentação verificável em um JSON Web Token (JWT) assinado criptograficamente usando sua infraestrutura de PKI, garantindo a integridade da solicitação e comprovando sua identidade na Carteira do Google.
Pré-requisitos
Antes de implementar as mudanças de código para a solicitação assinada, verifique se você tem:
- Chave privada:é necessário ter uma chave privada (por exemplo,
ES256de curva elíptica) para assinar a solicitação gerenciada no servidor. - Certificado:é necessário ter um certificado X.509 padrão derivado do par de chaves.
- Registro:verifique se o certificado público está registrado na Carteira do Google. Entre em contato com nossa equipe de suporte em
wallet-identity-rp-support@google.com.
Lógica de construção de solicitação
Para criar uma solicitação, é necessário usar a chave privada e incluir o payload em um 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
Acionar a API
Toda a solicitação de API precisa ser gerada no lado do servidor. Dependendo da plataforma, você vai transmitir o JSON gerado para as APIs nativas.
No app (Android)
Para solicitar credenciais de identidade dos seus apps Android, siga estas etapas:
Atualizar dependências
No build.gradle do projeto, atualize as dependências para usar o Credential Manager (Beta):
dependencies {
implementation("androidx.credentials:credentials:1.5.0-beta01")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}
Configurar o Credential Manager
Para configurar e inicializar um objeto CredentialManager, adicione uma lógica parecida
com esta:
// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)
Solicitar atributos de identidade
Em vez de especificar parâmetros individuais para solicitações de identidade, o app fornece todos eles juntos como uma string JSON no CredentialOption.
O Gerenciador de credenciais transmite essa string JSON para as carteiras digitais disponíveis sem examinar o conteúdo. Cada carteira é responsável por:
- Analisar a string JSON para entender a solicitação de identidade.
- Determinar quais das credenciais armazenadas, se houver, atendem à solicitação.
Recomendamos que os parceiros criem as solicitações no servidor, mesmo para integrações de apps Android.
Você vai usar o requestJson do formato de solicitação
como o request na chamada de função 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)
}
}
Processar a resposta da credencial
Depois de receber uma resposta da carteira, verifique se ela foi bem-sucedida e contém a resposta 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}")
}
}
A resposta credentialJson contém um identityToken criptografado (JWT), definido pelo W3C. O app Carteira é responsável por criar essa resposta.
Exemplo:
{
"protocol" : "openid4vp-v1-signed",
"data" : {
<encrpted_response>
}
}
Você vai transmitir essa resposta de volta ao servidor para validar a autenticidade dela. Confira as etapas para validar a resposta da credencial.
Web
Para solicitar credenciais de identidade usando a API Digital Credentials no Chrome ou em outros navegadores aceitos, faça a seguinte solicitação.
const credentialResponse = await navigator.credentials.get({
digital : {
requests : [
{
protocol: "openid4vp-v1-signed",
data: {<credential_request>} // This is an object, shouldn't be a string.
}
]
}
})
Envie a resposta dessa API de volta ao servidor para validar a credencial.
Validar a resposta
Quando a carteira retornar o identityToken criptografado (JWT), será necessário realizar uma validação do lado do servidor rigorosa antes de confiar nos dados.
Descriptografar a resposta
Use a chave privada correspondente à chave pública enviada no client_metadata da solicitação para descriptografar o JWE. Isso gera um vp_token.
Exemplo do 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 vai resultar em um JSON vp_token contendo a
credencial.
{
"vp_token":
{
"cred1": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
}
}
Criar a transcrição da sessão
A próxima etapa é criar o SessionTranscript da ISO/IEC 18013-5:2021 com uma estrutura de transferência específica do Android ou da Web:
SessionTranscript = [ null, // DeviceEngagementBytes not available null, // EReaderKeyBytes not available [ "OpenID4VPDCAPIHandover", AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web ] ]Para transferências do Android e da Web, é necessário usar o mesmo valor de uso único usado para gerar
credential_request.Transferência do 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()
Transferência do 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()
Usando o
SessionTranscript, a resposta do dispositivo precisa ser validada de acordo com a cláusula 9 da ISO/IEC 18013-5:2021. Isso inclui várias etapas, como:Verificar o certificado do emissor do estado. Consulte os certificados IACA do emissor aceito.
Verificar a assinatura do MSO (seção 9.1.2 da 18013-5)
Calcular e verificar
ValueDigestspara elementos de dados (seção 9.1.2 da 18013-5)Verificar a assinatura
deviceSignature(seção 9.1.3 da 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
}
Verificação de idade que preserva a privacidade (ZKP)
Para oferecer suporte a provas de conhecimento zero (por exemplo, verificar se um usuário tem mais de 18 anos sem ver a data de nascimento exata), mude o formato da solicitação para mso_mdoc_zk e forneça a configuração zk_system_type necessária.
Para uma visão geral do que é ZKP e dos recursos dela, consulte as Perguntas frequentes.
...
"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
{
...
Você vai receber uma prova de conhecimento zero criptografada da carteira. É possível validar essa prova em relação aos certificados IACA dos emissores usando a biblioteca longfellow-zk do Google.
O serviço de verificador contém um servidor baseado em Docker pronto para implantação que permite validar a resposta em relação a determinados certificados IACA do emissor.
É possível modificar o certs.pem para gerenciar os certificados de emissor IACA em que você quer confiar.
Recursos e suporte
- Perguntas frequentes:para perguntas frequentes sobre integração técnica, consulte as Perguntas frequentes sobre identidade e credenciais digitais.
- Implementação de referência: confira nossa implementação de referência de verificadores de identidade no GitHub.
- Site de teste:teste o fluxo de ponta a ponta em verifier.multipaz.org.
- Especificação OpenID4VP:confira a especificação técnica do openID4VP.
- Suporte:para receber ajuda com a depuração ou tirar dúvidas durante a integração, entre em contato com
wallet-identity-rp-support@google.com.