يوضِّح هذا الدليل كيف يمكن للجهات المستندة (RPs) دمج واجهة برمجة التطبيقات "بيانات الاعتماد الرقمية" من الناحية الفنية لطلب "رُخص القيادة على الأجهزة الجوّالة" (mDL) و"الهويات الرقمية" والتحقّق من صحتها من "محفظة Google" على مستوى تطبيقات Android والويب.
عملية التسجيل والمتطلبات الأساسية
قبل نشر تطبيقك الذي يمثل جهة مستندة في بيئة الإنتاج، عليك تسجيله رسميًا لدى Google.
- الاختبار في بيئة Sandbox: يمكنك بدء عملية التطوير على الفور باستخدام بيئة Sandbox و إنشاء هوية اختبار. ليس عليك قبول بنود الخدمة لإجراء الاختبار.
- إرسال نموذج جمع المعلومات: املأ نموذج إعداد الجهة المستندة. تستغرِق عملية الانضمام عادةً من 3 إلى 5 أيام عمل. سيظهر اسم منتجك وشعاره على شاشة طلب الموافقة التي يراها المستخدمون لمساعدتهم في تحديد الجهة التي تطلب بياناتهم.
- قبول بنود الخدمة: يجب توقيع بنود الخدمة قبل نشر التطبيق.
للإجابة عن الأسئلة حول تتبُّع طلبك أو إجراء الاختبار من مناطق غير متوافقة، يُرجى الاطّلاع على الأسئلة الشائعة.
التنسيقات والإمكانات المتوافقة
تتيح "محفظة Google" استخدام الهويات الرقمية المستندة إلى معيار ISO mdoc.
- بيانات الاعتماد المتوافقة: يمكنك الاطّلاع على بيانات الاعتماد والسمات المتوافقة.
- البروتوكولات المتوافقة: OpenID4VP (الإصدار 1.0).
- الحد الأدنى من حزمة تطوير البرامج (SDK) لنظام التشغيل Android: Android 9 (المستوى 28 من واجهة برمجة التطبيقات) والإصدارات الأحدث
- المتصفحات المتوافقة: للحصول على قائمة شاملة بالمتصفحات التي تتيح استخدام واجهة برمجة التطبيقات "بيانات الاعتماد الرقمية"، يُرجى الرجوع إلى صفحة دعم النظام الأساسي.
- الأسئلة الشائعة: للإجابة عن الأسئلة حول البلدان المتوافقة والجدول الزمني للمناطق الجديدة، يُرجى الاطّلاع على الأسئلة الشائعة حول بيانات الاعتماد والبيانات.
تهيئة الطلب
لطلب بيانات الاعتماد من أي محفظة، عليك تهيئة طلبك باستخدام OpenID4VP. يمكنك طلب بيانات اعتماد محدّدة أو بيانات اعتماد متعددة في عنصر dcql_query واحد.
مثال على طلب JSON
في ما يلي نموذج لطلب requestJson من mdoc للحصول على بيانات اعتماد الهوية من أي محفظة على جهاز Android أو على الويب.
{
"requests" : [
{
"protocol": "openid4vp-v1-signed",
"data": {<signed_credential_request>} // This is an object, shouldn't be a string.
}
]
}
تشفير الطلب
تحتوي client_metadata على المفتاح العام للتشفير لكل طلب.
عليك تخزين المفاتيح الخاصة لكل طلب واستخدامها لمصادقة الرمز المميّز الذي تتلقّاه من تطبيق المحفظة ومنحه الإذن.
تحتوي المَعلمة credential_request في requestJson على الحقول التالية.
بيانات اعتماد محدّدة
{
"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
]
}
}
}
}
أي بيانات اعتماد مؤهّلة
في ما يلي مثال على طلب كل من "رخصة القيادة الرقمية (mDL)" و"الهويّة الرقمية". يمكن للمستخدم المتابعة باستخدام أي منهما.
{
"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
]
}
}
}
}
يمكنك طلب أي عدد من السمات المتوافقة من أي بيانات اعتماد هوية مخزّنة في "محفظة Google".
الطلبات الموقَّعة
الطلبات الموقَّعة (طلبات منح الإذن الآمنة باستخدام JWT) تغلّف طلب العرض القابل للتحقّق داخل رمز JSON المميّز للويب (JWT) الموقَّع باستخدام التشفير باستخدام البنية الأساسية للمفاتيح العامة (PKI)، ما يضمن سلامة الطلب ويثبت هويتك لدى "محفظة Google".
المتطلبات الأساسية
قبل تنفيذ تغييرات الرموز للطلب الموقَّع، تأكَّد من توفّر ما يلي:
- مفتاح خاص: تحتاج إلى مفتاح خاص (مثل Elliptic Curve
ES256) لتوقيع الطلب الذي تتم إدارته على خادمك. - شهادة: تحتاج إلى شهادة X.509 عادية مستمدة من زوج المفاتيح.
- التسجيل: تأكَّد من تسجيل شهادتك العامة في "محفظة Google". يمكنك التواصل مع فريق الدعم على عنوان البريد الإلكتروني
wallet-identity-rp-support@google.com
منطق إنشاء الطلب
لإنشاء طلب، عليك استخدام مفتاحك الخاص وتضمين الحمولة في 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
تفعيل واجهة برمجة التطبيقات
يجب إنشاء طلب بيانات من واجهة برمجة التطبيقات بالكامل على جهة الخادم. استنادًا إلى النظام الأساسي، ستمرِّر JSON الذي تم إنشاؤه إلى واجهات برمجة التطبيقات الأصلية.
داخل التطبيق (Android)
لطلب بيانات اعتماد الهوية من تطبيقات Android، اتّبِع الخطوات التالية:
تعديل التبعيات
في ملف build.gradle لمشروعك، عدِّل التبعيات لاستخدام Credential Manager (الإصدار التجريبي):
dependencies {
implementation("androidx.credentials:credentials:1.5.0-beta01")
implementation("androidx.credentials:credentials-play-services-auth:1.5.0-beta01")
}
ضبط Credential Manager
لضبط كائن CredentialManager وتهيئته، أضِف منطقًا مشابهًا
لما يلي:
// Use your app or activity context to instantiate a client instance of CredentialManager.
val credentialManager = CredentialManager.create(context)
طلب سمات الهوية
بدلاً من تحديد مَعلمات فردية لطلبات الهوية، يقدّم التطبيق جميعها معًا كسلسلة JSON ضمن CredentialOption.
يمرِّر Credential Manager سلسلة JSON هذه إلى المحافظ الرقمية المتاحة بدون فحص محتوياتها. بعد ذلك، تكون كل محفظة مسؤولة عن:
- تحليل سلسلة JSON لفهم طلب الهوية.
- تحديد بيانات الاعتماد المخزّنة التي تستوفي الطلب، إن وُجدت.
ننصح الشركاء بإنشاء طلباتهم على الخادم حتى لعمليات الدمج في تطبيقات Android.
ستستخدم requestJson من تنسيق الطلب
كـ request في استدعاء الدالة 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)
}
}
التعامل مع استجابة بيانات الاعتماد
بعد تلقّي ردّ من المحفظة، ستتحقّق مما إذا كان الردّ ناجحًا ويحتوي على استجابة 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}")
}
}
تحتوي استجابة credentialJson على identityToken (JWT) مشفّر، كما حدّده اتحاد شبكة الويب العالمية (W3C). تطبيق "محفظة Google" هو المسؤول عن إنشاء هذه الاستجابة.
مثال:
{
"protocol" : "openid4vp-v1-signed",
"data" : {
<encrpted_response>
}
}
ستعيد هذه الاستجابة إلى الخادم للتحقّق من صحتها. يمكنك الاطّلاع على خطوات التحقّق من صحة استجابة بيانات الاعتماد
الويب
لطلب بيانات اعتماد الهوية باستخدام واجهة برمجة التطبيقات "بيانات الاعتماد الرقمية" على Chrome أو غيره من المتصفحات المتوافقة، أرسِل الطلب التالي.
const credentialResponse = await navigator.credentials.get({
digital : {
requests : [
{
protocol: "openid4vp-v1-signed",
data: {<credential_request>} // This is an object, shouldn't be a string.
}
]
}
})
أرسِل الردّ من واجهة برمجة التطبيقات هذه إلى خادمك للتحقّق من صحة استجابة بيانات الاعتماد
التحقّق من صحة الردّ
بعد أن تعرض المحفظة identityToken (JWT) المشفّر، عليك إجراء عملية التحقّق على صعيد الخادم بشكل صارم قبل الوثوق بالبيانات.
فك تشفير الردّ
استخدِم المفتاح الخاص الذي يتطابق مع المفتاح العام المُرسَل في client_metadata للطلب لفك تشفير JWE. سينتج عن ذلك vp_token.
مثال على 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 إلى JSON vp_token يحتوي على بيانات الاعتماد
.
{
"vp_token":
{
"cred1": ["<base64UrlNoPadding_encoded_credential>"] // This applies to OpenID4VP 1.0 spec.
}
}
إنشاء نص الجلسة
الخطوة التالية هي إنشاء SessionTranscript من ISO/IEC 18013-5:2021 باستخدام بنية Handover خاصة بنظام Android أو الويب:
SessionTranscript = [ null, // DeviceEngagementBytes not available null, // EReaderKeyBytes not available [ "OpenID4VPDCAPIHandover", AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web ] ]بالنسبة إلى عمليات النقل على Android والويب، عليك استخدام قيمة nonce نفسها التي استخدمتها لإنشاء
credential_request.عملية النقل على 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()
عملية النقل على المتصفح
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()
باستخدام
SessionTranscript، يجب التحقّق من صحة استجابة الجهاز وفقًا للبند 9 من ISO/IEC 18013-5:2021. يتضمّن ذلك عدة خطوات، مثل:التحقّق من شهادة جهة إصدار الحالة يُرجى الرجوع إلى شهادات IACA لجهات الإصدار المتوافقة.
التحقّق من توقيع MSO (القسم 9.1.2 من 18013-5)
حساب
ValueDigestsوالتحقّق منها لعناصر البيانات (القسم 9.1.2 من 18013-5)التحقّق من توقيع
deviceSignature(القسم 9.1.3 من 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
}
التحقّق من العمر مع الحفاظ على الخصوصية (ZKP)
لإتاحة استخدام إثباتات عدم المعرفة (مثل التحقّق من أنّ عمر المستخدم يتخطّى 18 عامًا بدون الاطّلاع على تاريخ ميلاده الدقيق)، غيِّر تنسيق طلبك إلى mso_mdoc_zk وقدِّم إعداد zk_system_type المطلوب.
للحصول على نظرة عامة على مفهوم إثباتات عدم المعرفة وإمكاناتها، يُرجى الاطّلاع على الأسئلة الشائعة.
...
"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
{
...
ستتلقّى من المحفظة إثباتًا مشفّرًا لعدم المعرفة. يمكنك التحقّق من صحة هذا الإثبات مقابل شهادات IACA لجهات الإصدار باستخدام مكتبة longfellow-zk من Google.
تحتوي خدمة التحقّق من صحة البيانات على خادم مستند إلى Docker وجاهز للنشر يتيح لك التحقّق من صحة الردّ مقابل شهادات IACA معيّنة لجهات الإصدار.
يمكنك تعديل ملف certs.pem لإدارة شهادات IACA لجهات الإصدار التي تريد الوثوق بها.
المراجع والدعم
- الأسئلة الشائعة: للإجابة عن الأسئلة الشائعة حول عملية الدمج الفني، يُرجى الاطّلاع على الأسئلة الشائعة حول الهوية الرقمية وبيانات الاعتماد الرقمية.
- التنفيذ المرجعي: يمكنك الاطّلاع على التنفيذ المرجعي للجهات التي تتحقّق من صحة الهوية على GitHub.
- الموقع الإلكتروني للاخت1بار: يمكنك تجربة عملية النقل من البداية إلى النهاية على verifier.multipaz.org.
- مواصفات OpenID4VP: يمكنك الاطّلاع على المواصفات الفنية لـ openID4VP.
- الدعم: للحصول على مساعدة في تحديد الأخطاء أو للإجابة عن الأسئلة أثناء عملية الدمج، يُرجى التواصل مع
wallet-identity-rp-support@google.com.