Z tego przewodnika dowiesz się, jak strony ufające mogą technicznie zintegrować interfejs Digital Credentials API, aby żądać i weryfikować mobilne prawa jazdy (mDL) oraz cyfrowe dokumenty tożsamości z Portfela Google w aplikacjach na Androida i w internecie.
.Proces rejestracji i wymagania wstępne
Zanim zaczniesz korzystać z aplikacji strony ufającej w środowisku produkcyjnym, musisz ją formalnie zarejestrować w Google.
- Testowanie w piaskownicy: możesz od razu rozpocząć tworzenie aplikacji, korzystając z naszego środowiska piaskownicy i tworząc testowy dokument tożsamości. Do testowania nie jest wymagana akceptacja Warunków korzystania z usługi.
- Przesłanie formularza rejestracji: wypełnij formularz rejestracji strony ufającej. Rejestracja trwa zwykle 3–5 dni roboczych. Nazwa i logo Twojej usługi będą wyświetlane na ekranie zgody widocznym dla użytkowników, aby ułatwić im zidentyfikowanie, kto prosi o ich dane.
- Akceptacja Warunków korzystania z usługi: zanim zaczniesz korzystać z usługi, musisz zaakceptować Warunki korzystania z usługi.
Jeśli masz pytania dotyczące śledzenia aplikacji lub testowania w regionach, które nie są obsługiwane, zapoznaj się z najczęstszymi pytaniami.
Obsługiwane formaty i funkcje
Portfel Google obsługuje cyfrowe dokumenty tożsamości oparte na ISO mdoc.
- Obsługiwane dane logowania: możesz sprawdzić obsługiwane dane logowania i atrybuty.
- Obsługiwane protokoły: OpenID4VP (wersja 1.0).
- Minimalny Android SDK: Android 9 (poziom interfejsu API 28) i nowszy.
- Obsługa przeglądarek: pełną listę przeglądarek obsługujących interfejs Digital Credentials API znajdziesz na stronie Obsługa ekosystemu.
- Najczęstsze pytania: jeśli masz pytania dotyczące obsługi krajów i harmonogramu wdrażania nowych regionów, zapoznaj się z najczęstszymi pytaniami dotyczącymi danych logowania i danych.
Formatowanie żądania
Aby poprosić o dane logowania z dowolnego portfela, musisz sformatować żądanie za pomocą OpenID4VP. Możesz poprosić o konkretne dane logowania lub o wiele danych logowania w jednym obiekcie dcql_query.
Przykład żądania JSON
Oto przykład żądania requestJson mdoc, które umożliwia uzyskanie danych logowania z dowolnego portfela na urządzeniu z Androidem lub w internecie.
{
"requests" : [
{
"protocol": "openid4vp-v1-signed",
"data": {<signed_credential_request>} // This is an object, shouldn't be a string.
}
]
}
Szyfrowanie żądania
Parametr client_metadata zawiera klucz publiczny szyfrowania dla każdego żądania.
Musisz przechowywać klucze prywatne dla każdego żądania i używać ich do uwierzytelniania i autoryzowania tokena otrzymanego z aplikacji Portfel.
Parametr credential_request w requestJson zawiera te pola:
Konkretny certyfikat
{
"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
]
}
}
}
}
Dowolny kwalifikujący się certyfikat
Oto przykładowe żądanie dotyczące zarówno mobilnego prawa jazdy, jak i cyfrowego dokumentu tożsamości. Użytkownik może wybrać dowolną z tych opcji.
{
"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
]
}
}
}
}
Możesz poprosić o dowolną liczbę obsługiwanych atrybutów z dowolnych danych logowania przechowywanych w Portfelu Google.
Podpisane żądania
Podpisane żądania (żądania autoryzacji zabezpieczone za pomocą JWT) zawierają żądanie prezentacji weryfikowalnej w tokenie sieciowym JSON (JWT) podpisanym kryptograficznie przy użyciu infrastruktury PKI. Dzięki temu zapewniają integralność żądania i potwierdzają Twoją tożsamość w Portfelu Google.
Wymagania wstępne
Zanim wprowadzisz zmiany w kodzie dotyczące podpisanych żądań, upewnij się, że masz:
- Klucz prywatny: potrzebujesz klucza prywatnego (np.
ES256krzywej eliptycznej) do podpisywania żądania, którym zarządzasz na serwerze. - Certyfikat: potrzebujesz standardowego certyfikatu X.509 pochodzącego z pary kluczy.
- Rejestracja: upewnij się, że Twój certyfikat publiczny jest zarejestrowany w Portfelu Google. Skontaktuj się z naszym zespołem pomocy pod adresem
wallet-identity-rp-support@google.com.
Logika tworzenia żądania
Aby utworzyć żądanie, musisz użyć klucza prywatnego i zawinąć ładunek w 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
Wywoływanie interfejsu API
Całe żądanie do interfejsu API powinno być generowane po stronie serwera. W zależności od platformy wygenerowany kod JSON przekażesz do natywnych interfejsów API.
W aplikacji (Android)
Aby poprosić o dane logowania w aplikacjach na Androida, wykonaj te czynności:
Aktualizowanie zależności
W pliku build.gradle projektu zaktualizuj zależności, aby używać Menedżera danych logowania (wersja beta):
dependencies {
implementation("androidx.credentials:credentials:1.5.0-beta01")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}
Konfigurowanie Menedżera danych logowania
Aby skonfigurować i zainicjować obiekt CredentialManager, dodaj logikę podobną
do tej:
// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)
Żądanie atrybutów tożsamości
Zamiast określać poszczególne parametry żądań tożsamości, aplikacja udostępnia je wszystkie razem jako ciąg JSON w CredentialOption.
Menedżer danych logowania przekazuje ten ciąg JSON do dostępnych portfeli cyfrowych bez sprawdzania jego zawartości. Każdy portfel jest odpowiedzialny za:
- Parsowanie ciągu JSON w celu zrozumienia żądania tożsamości.
- analizowanie ciągu JSON w celu zrozumienia żądania tożsamości;
Zalecamy, aby partnerzy tworzyli żądania na serwerze, nawet w przypadku integracji z aplikacjami na Androida.
Użyjesz requestJson z formatu żądania
jako request w wywołaniu funkcji 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)
}
}
Obsługa odpowiedzi dotyczącej danych logowania
Gdy otrzymasz odpowiedź z portfela, sprawdzisz, czy jest ona prawidłowa i czy zawiera odpowiedź 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}")
}
}
Odpowiedź credentialJson zawiera zaszyfrowany identityToken (JWT) zdefiniowany przez W3C. Za utworzenie tej odpowiedzi odpowiada aplikacja Portfel.
Przykład:
{
"protocol" : "openid4vp-v1-signed",
"data" : {
<encrpted_response>
}
}
Przekażesz tę odpowiedź z powrotem na serwer, aby sprawdzić jej autentyczność. Kroki weryfikacji odpowiedzi dotyczącej danych logowania znajdziesz tutaj.
Sieć
Aby poprosić o dane logowania za pomocą interfejsu Digital Credentials API w Chrome lub innych obsługiwanych przeglądarkach, wyślij to żądanie.
const credentialResponse = await navigator.credentials.get({
digital : {
requests : [
{
protocol: "openid4vp-v1-signed",
data: {<credential_request>} // This is an object, shouldn't be a string.
}
]
}
})
Wyślij odpowiedź z tego interfejsu API z powrotem na serwer, aby zweryfikować odpowiedź dotyczącą danych logowania.
Weryfikowanie odpowiedzi
Gdy portfel zwróci zaszyfrowany token identityToken (JWT), przed zaufaniem do danych musisz przeprowadzić dokładną weryfikację po stronie serwera.
Odszyfrowywanie odpowiedzi
Aby odszyfrować JWE, użyj klucza prywatnego odpowiadającego kluczowi publicznemu wysłanemu w client_metadata żądania. Spowoduje to wygenerowanie vp_token.
Przykład w Pythonie:
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 spowoduje wygenerowanie kodu JSON vp_token zawierającego dane logowania
{
"vp_token":
{
"cred1": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
}
}
Tworzenie transkrypcji sesji
Następnym krokiem jest utworzenie SessionTranscript na podstawie normy ISO/IEC 18013-5:2021 z strukturą przekazywania danych specyficzną dla Androida lub internetu:
SessionTranscript = [ null, // DeviceEngagementBytes not available null, // EReaderKeyBytes not available [ "OpenID4VPDCAPIHandover", AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web ] ]W przypadku przekazywania danych zarówno na Androidzie, jak i w internecie musisz użyć tego samego nonce, którego użyto do wygenerowania
credential_request.Przekazywanie danych na Androidzie
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()
Przekazywanie danych w przeglądarce
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()
Za pomocą
SessionTranscriptnależy zweryfikować odpowiedź urządzenia zgodnie z klauzulą 9 normy ISO/IEC 18013-5:2021.Ta weryfikacja obejmuje kilka kroków:
Sprawdzenie certyfikatu wystawcy: wyodrębnij łańcuch certyfikatów podpisywania wystawcy z
issuerAuthi zweryfikuj go za pomocą zaufanych certyfikatów głównych IACA. Zapoznaj się z obsługiwanymi certyfikatami IACA wystawcy.Weryfikacja podpisu MSO (sekcja 9.1.2 normy 18013-5)
Obliczanie i sprawdzanie
ValueDigestsdla elementów danych (sekcja 9.1.2 normy 18013-5)Weryfikacja podpisu
deviceSignature(sekcja 9.1.3 normy 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
}
Weryfikacja wieku chroniąca prywatność (ZKP)
Aby obsługiwać dowody z wiedzą zerową (np. weryfikowanie, czy użytkownik ma ukończone 18 lat, bez sprawdzania dokładnej daty urodzenia), zmień format żądania na mso_mdoc_zk i podaj wymaganą konfigurację zk_system_type.
Ogólne omówienie ZKP i jego możliwości znajdziesz w najczęstszych pytaniach.
...
"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
{
...
Z portfela otrzymasz zaszyfrowany dowód z wiedzą zerową. Możesz zweryfikować ten dowód za pomocą certyfikatów IACA wystawców, korzystając z biblioteki longfellow-zk Google.
Usługa verifier-service zawiera gotowy do wdrożenia serwer oparty na Dockerze, który umożliwia weryfikowanie odpowiedzi za pomocą określonych certyfikatów IACA wystawcy.
Możesz zmodyfikować plik certs.pem aby zarządzać certyfikatami IACA wystawcy którym chcesz zaufać.
Zasoby i pomoc
- Najczęstsze pytania: najczęstsze pytania dotyczące integracji technicznej znajdziesz w najczęstszych pytaniach dotyczących tożsamości i danych logowania.
- Implementacja referencyjna: zapoznaj się z naszą implementacją referencyjną weryfikatorów tożsamości w GitHubie.
- Witryna testowa: wypróbuj kompleksowy proces na stronie verifier.multipaz.org.
- Specyfikacja OpenID4VP: zapoznaj się ze specyfikacją techniczną OpenID4VP.
- Pomoc: jeśli podczas integracji masz pytania lub potrzebujesz pomocy w debugowaniu, skontaktuj się z nami pod adresem
wallet-identity-rp-support@google.com.