Bu kılavuzda, güvenen tarafların (RP'ler) Android uygulamalarında ve web'de dijital kimlik bilgilerini istemek ve doğrulamak için Digital Credentials API'yi teknik olarak nasıl entegre edebileceği açıklanmaktadır.
Kayıt Süreci ve Ön Koşullar
Üretimde yayınlanmadan önce, güvenen taraf uygulamanızı Google'a resmi olarak kaydetmeniz gerekir. Güven oluşturmak için Google'ın sertifikanızı imzaladığı bir sertifika imzalama süreci kullanırız.
- Özel Korumalı Alan'da test etme: Kabul formu göndermeden hemen geliştirmeye başlayabilirsiniz. Korumalı Alan Modu sayfasında yayınlanan önceden güvenilen test anahtarlarını ve örnek meta verileri kullanarak doğrudan test yapabilirsiniz.
- Uçtan uca akışı kaydetme: Testi tamamlayıp sanal ortamda çalışan bir entegrasyon oluşturduktan sonra, entegrasyon akışınızın tamamını gösteren uçtan uca bir video kaydedin.
- İlk Katılım Formunu Gönderin ve Hizmet Şartları'nı Kabul Edin: Güvenen Taraf İlk Katılım Formu'nu doldurup gönderin.
Formda aşağıdaki bilgiler gereklidir:
- Üretim sertifikası imzalama isteğiniz (CSR).
- Görüntüleme öğeleriniz: Logo URL'si, görünen ad, gizlilik politikası URL'si ve hizmet şartları URL'si.
- Korumalı alan entegrasyonunuzun uçtan uca videosu.
Onaylandıktan sonra Google size imzalı bir sertifika ve benzersiz Base64URL kodlu meta verilerinizi (gw_rp_metadata_bytes) sağlar.
Teknik Entegrasyon Ayrıntıları
Aşağıdaki bölümlerde, doğrudan Digital Credentials API ile entegrasyon yapan güvenen taraflara yönelik teknik entegrasyon ayrıntıları (istek biçimlendirme, istek şifreleme, API'yi tetikleme, yanıtları doğrulama ve sıfır bilgi kanıtlarını uygulama dahil) ele alınmaktadır.
Desteklenen Biçimler ve Özellikler
Google Cüzdan, ISO mdoc tabanlı dijital kimlikleri destekler.
- Desteklenen kimlik bilgileri: Desteklenen kimlik bilgilerini ve özellikleri inceleyebilirsiniz.
- Desteklenen Protokoller: OpenID4VP (Sürüm 1.0).
- Minimum Android SDK: Android 9 (API düzeyi 28) ve sonraki sürümler.
- Tarayıcı Desteği: Digital Credentials API'yi destekleyen tarayıcıların kapsamlı bir listesi için Ecosystem Support sayfasını inceleyin.
- SSS: Ülke desteği ve yeni bölgeler için zaman çizelgesiyle ilgili sorularınız varsa Kimlik Bilgileri ve Veriler SSS başlıklı makaleyi inceleyin.
İsteği Biçimlendirme
Herhangi bir cüzdandan kimlik bilgisi istemek için isteğinizi OpenID4VP kullanarak biçimlendirmeniz gerekir. Tek bir dcql_query nesnede belirli kimlik bilgileri veya birden fazla kimlik bilgisi isteyebilirsiniz.
JSON İsteği Örneği
Android cihazdaki veya web'deki herhangi bir cüzdandan kimlik bilgisi almak için kullanılan mdoc requestJson isteğinin bir örneğini aşağıda bulabilirsiniz.
{
"requests" : [
{
"protocol": "openid4vp-v1-signed",
"data": {<signed_credential_request>} // This is an object, shouldn't be a string.
}
]
}
Şifreleme isteğinde bulunma
client_metadata, her istek için şifreleme ortak anahtarını içerir.
Her istek için özel anahtarları saklamanız ve bunları, cüzdan uygulamasından aldığınız jetonun kimliğini doğrulamak ve yetkilendirmek için kullanmanız gerekir.
Entegre OpenID4VP meta verileri
Kimlik bilgisi isteğinizi biçimlendirirken gw_rp_metadata_bytes alanını client_metadata nesnesine eklemeniz gerekir (aşağıdaki örnek istek kodunda gösterildiği gibi). Bu alan, kimliğinizi doğrulamak ve markanızı kullanıcıya göstermek için Google Cüzdan'ın gerektirdiği Base64URL kodlu güvenen taraf meta verilerini içerir.
requestJson içindeki credential_request parametresi aşağıdaki alanları içerir.
Belirli yeterlilik belgesi
{
"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": {
// Use org.iso.18013.5.1.mDL for mDL,
// com.google.wallet.idcard.1 for ID pass, or
// org.iso.23220.photoid.1 for ID pass (using photo ID document type)
"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
}
]
}
]
},
"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>"
}
}
Uygun Kimlik Bilgileri
Mobil sürücü belgesi, dijital kimlik ve dijital kimlik (fotoğraflı kimlik belgesi türü kullanılarak) için örnek istek aşağıda verilmiştir. Kullanıcı bunlardan biriyle devam edebilir.
{
"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 pass
"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 type 3: ID pass (using photo ID document type)
"id": "photo_id-request",
"format": "mso_mdoc",
"meta": {
"doctype_value": "org.iso.23220.photoid.1"
},
"claims": [
{
"path": [
"org.iso.23220.1",
"family_name"
],
"intent_to_retain": false // set this to true if you are saving the value of the field
},
{
"path": [
"org.iso.23220.1",
"given_name"
],
"intent_to_retain": false
},
{
"path": [
"org.iso.23220.1",
"age_over_18"
],
"intent_to_retain": false
}
]
}
]
credential_sets : [
{
"options": [
[ "mdl-request" ],
[ "id_pass-request" ],
[ "photo_id-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>"
}
}
Google Cüzdan'da saklanan kimlik bilgilerinden istediğiniz sayıda desteklenen özelliği isteyebilirsiniz.
İmzalı İstekler
İmzalı İstekler (JWT ile Güvenli Yetkilendirme İstekleri), PKI altyapınızı kullanarak kriptografik olarak imzalanmış bir JSON Web Token (JWT) içinde doğrulanabilir sunum isteğinizi kapsar. Bu sayede istek bütünlüğü sağlanır ve Google Cüzdan'a kimliğiniz kanıtlanır.
Ön koşullar
İmzalı istek için kod değişikliklerini uygulamadan önce şunlardan emin olun:
- Özel anahtar: Sunucunuzda yönetilen isteği imzalamak için özel bir anahtara (ör. eliptik eğri
ES256) ihtiyacınız vardır. - Sertifika: Anahtar çiftinizden türetilmiş standart bir X.509 sertifikasına ihtiyacınız vardır.
- Kayıt: Genel sertifikanızın Google Cüzdan'a kaydedildiğinden emin olun.
İstek oluşturma mantığı
İstek oluşturmak için özel anahtarınızı kullanmanız ve yükü JWS'ye sarmalamanız gerekir.
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
API'yi tetikleme
API isteğinin tamamı sunucu tarafında oluşturulmalıdır. Platforma bağlı olarak, oluşturulan JSON'u platform API'lerine iletirsiniz.
Uygulama İçi (Android)
Android uygulamalarınızdan kimlik bilgisi istemek için aşağıdaki adımları uygulayın:
Bağımlılıkları güncelleme
Projenizin build.gradle dosyasında, bağımlılıklarınızı Kimlik Bilgisi Yöneticisi'ni (beta) kullanacak şekilde güncelleyin:
dependencies {
implementation("androidx.credentials:credentials:1.5.0-beta01")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}
Kimlik Bilgisi Yöneticisi'ni yapılandırma
Bir CredentialManager nesnesini yapılandırmak ve başlatmak için aşağıdakine benzer bir mantık ekleyin:
// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)
İstek kimliği özellikleri
Uygulama, kimlik istekleri için parametreleri tek tek belirtmek yerine CredentialOption içinde bir JSON dizesi olarak birlikte sağlar.
Kimlik bilgisi yöneticisi, bu JSON dizesini içeriğini incelemeden kullanılabilir dijital cüzdanlara iletir. Her cüzdan daha sonra şunlardan sorumludur:
- Kimlik isteğini anlamak için JSON dizesini ayrıştırma.
- Kayıtlı kimlik bilgilerinden hangisinin (varsa) isteği karşıladığını belirleme
İş ortaklarının, Android uygulaması entegrasyonları için bile isteklerini sunucuda oluşturmalarını öneririz.
İstek Biçimi'ndeki requestJson öğesini GetDigitalCredentialOption() işlev çağrısında request olarak kullanacaksınız.
// 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)
}
}
Kimlik bilgisi yanıtını işleme
Cüzdandan yanıt aldıktan sonra yanıtın başarılı olup olmadığını ve credentialJson yanıtını içerip içermediğini doğrulayın.
// 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}")
}
}
credentialJson yanıtı, W3C tarafından tanımlanan şifrelenmiş bir identityToken (JWT) içerir. Bu yanıtı oluşturmaktan Cüzdan uygulaması sorumludur.
Örnek:
{
"protocol" : "openid4vp-v1-signed",
"data" : {
<encrpted_response>
}
}
Bu yanıtı, geçerliliğini doğrulamak için sunucuya geri iletirsiniz. Kimlik bilgisi yanıtını doğrulama adımlarını
Web
Chrome'da veya desteklenen diğer tarayıcılarda Digital Credentials API'yi kullanarak kimlik bilgisi istemek için aşağıdaki isteği gönderin.
const credentialResponse = await navigator.credentials.get({
digital : {
requests : [
{
protocol: "openid4vp-v1-signed",
data: {<credential_request>} // This is an object, shouldn't be a string.
}
]
}
})
Kimlik bilgisi yanıtını doğrulamak için bu API'den gelen yanıtı sunucunuza geri gönderin.
Yanıtı Doğrulama
Cüzdan, şifrelenmiş identityToken (JWT) döndürdüğünde verilere güvenmeden önce sıkı bir sunucu tarafı doğrulama işlemi yapmanız gerekir.
Yanıtın şifresini çözme
JWE'nin şifresini çözmek için isteğin client_metadata bölümünde gönderilen ortak anahtara karşılık gelen özel anahtarı kullanın. Bu işlem vp_token ile sonuçlanır.
Python örneği:
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, kimlik bilgisini içeren bir vp_token JSON'a neden olur.
{
"vp_token":
{
"cred1": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
}
}
Oturum transkriptini oluşturma
Bir sonraki adım, Android'e veya web'e özel bir devretme yapısıyla ISO/IEC 18013-5:2021'den SessionTranscript oluşturmaktır:
SessionTranscript = [ null, // DeviceEngagementBytes not available null, // EReaderKeyBytes not available [ "OpenID4VPDCAPIHandover", AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web ] ]Hem Android hem de web devretme işlemleri için
credential_requestoluştururken kullandığınız nonce'ı kullanmanız gerekir.Android Handover
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()
Tarayıcı Aktarımı
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()
SessionTranscriptkullanılarak Cihaz Yanıtı, ISO/IEC 18013-5:2021 madde 9'a göre doğrulanmalıdır.Bu doğrulama işlemi birkaç adımdan oluşur:
Yayıncı Sertifikasını Kontrol Edin: Yayıncının imza sertifikası zincirini
issuerAuthdosyasından çıkarın ve güvenilir IACA kök sertifikalarına göre doğrulayın. Desteklenen veren kuruluşun IACA sertifikalarına bakın.MSO imzasını doğrulama (18013-5 Bölüm 9.1.2)
Veri öğeleri (18013-5 Bölüm 9.1.2) için
ValueDigestsdeğerini hesaplayın ve kontrol edin.deviceSignatureimzasını doğrulama (18013-5 Bölüm 9.1.3)
{
"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
}
Gizliliği korumaya yönelik yaş doğrulama (ZKP)
Sıfır bilgi kanıtlarını desteklemek için (ör. kullanıcının tam doğum tarihini görmeden 18 yaşından büyük olduğunu doğrulama) istek biçiminizi mso_mdoc_zk olarak değiştirin ve gerekli zk_system_type yapılandırmasını sağlayın.
ZKP'nin ne olduğu ve özellikleri hakkında genel bir bakış için SSS bölümüne bakın.
...
"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
{
...
Cüzdandan şifrelenmiş bir sıfır bilgi kanıtı alırsınız. Google'ın longfellow-zk kitaplığını kullanarak bu kanıtı IACA sertifikası veren kuruluşlara karşı doğrulayabilirsiniz.
Doğrulayıcı hizmeti, yanıtı belirli veren IACA sertifikalarına göre doğrulamanıza olanak tanıyan, dağıtıma hazır, Docker tabanlı bir sunucu içerir.
Güvenmek istediğiniz IACA veren sertifikalarını yönetmek için certs.pem dosyasını değiştirebilirsiniz.
Kaynaklar ve Destek
- SSS: Teknik entegrasyonla ilgili sık sorulan sorular için Dijital Kimlik ve Kimlik Bilgileri SSS bölümüne bakın.
- Referans Uygulama: GitHub'daki Kimlik Doğrulayıcılar Referans Uygulamamıza göz atın.
- Test Web Sitesi: verifier.multipaz.org adresinde baştan sona süreci deneyin.
- OpenID4VP Spesifikasyonu: OpenID4VP'nin teknik özelliklerine göz atın.
- Destek: Entegrasyon sırasında hata ayıklama yardımı veya sorularınız için
wallet-identity-rp-support@google.comile iletişime geçin.