يشرح هذا الدليل كيف يمكن للجهات المستندة (RPs) دمج واجهة برمجة التطبيقات Digital Credentials API من الناحية الفنية لطلب بيانات اعتماد الهوية الرقمية والتحقّق من صحتها على مستوى تطبيقات Android والويب.
عملية التسجيل والمتطلبات الأساسية
قبل نشر تطبيقك في بيئة الإنتاج، عليك تسجيل تطبيق الجهة المستندة رسميًا لدى Google. نستخدم عملية توقيع الشهادات حيث توقِّع Google شهادتك لإنشاء الثقة.
- الاختبار في وضع الحماية: يمكنك بدء التطوير على الفور بدون إرسال نموذج الإعداد. يمكنك إجراء الاختبار مباشرةً باستخدام مفاتيح الاختبار الموثوق بها مسبقًا والبيانات الوصفية النموذجية المنشورة على صفحة "وضع الحماية".
- تسجيل المسار من البداية إلى النهاية: بعد إكمال الاختبار والحصول على عملية دمج تعمل في وضع الحماية، سجِّل فيديو من البداية إلى النهاية يعرض مسار الدمج بالكامل.
- إرسال نموذج الإعداد وقبول بنود الخدمة: املأ نموذج إعداد الجهة المستندة وأرسِله.
يتطلّب النموذج المعلومات التالية:
- طلب توقيع الشهادة (CSR) لبيئة الإنتاج
- مواد العرض التي تظهر للمستخدمين: عنوان URL للشعار واسم العرض وعنوان URL لسياسة الخصوصية وعنوان URL لبنود الخدمة
- الفيديو من البداية إلى النهاية لعملية الدمج في وضع الحماية
بعد الموافقة، ستزوّدك Google بشهادة موقَّعة والبيانات الوصفية الفريدة التي تم ترميزها باستخدام Base64URL (gw_rp_metadata_bytes).
تفاصيل عملية الدمج الفني
تتناول الأقسام التالية تفاصيل عملية الدمج الفني للجهات المستندة التي تدمج مباشرةً مع واجهة برمجة التطبيقات Digital Credentials API (بما في ذلك تنسيق الطلب وتشفيره وتفعيل واجهة برمجة التطبيقات والتحقّق من صحة الردود وتنفيذ إثباتات عدم المعرفة).
التنسيقات والإمكانات المتوافقة
تتيح "محفظة Google" استخدام مستندات التعريف الرقمية المستندة إلى معيار ISO mdoc.
- بيانات الاعتماد المتوافقة: يمكنك الاطّلاع على بيانات الاعتماد والسمات المتوافقة.
- البروتوكولات المتوافقة: OpenID4VP (الإصدار 1.0).
- الحد الأدنى من حزمة تطوير البرامج (SDK) لنظام Android: Android 9 (المستوى 28 من واجهة برمجة التطبيقات) والإصدارات الأحدث
- المتصفّحات المتوافقة: للحصول على قائمة شاملة بالمتصفّحات التي تتيح استخدام واجهة برمجة التطبيقات Digital Credentials API، يُرجى الرجوع إلى صفحة دعم النظام المتكامل.
- الأسئلة الشائعة: للتعرّف على الأسئلة حول البلدان التي تتيح استخدام واجهة برمجة التطبيقات والجدول الزمني للمناطق الجديدة، يُرجى الاطّلاع على الأسئلة الشائعة حول بيانات الاعتماد والبيانات.
تنسيق الطلب
لطلب بيانات اعتماد من أي محفظة، عليك تنسيق طلبك باستخدام 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 على المفتاح العام للتشفير لكل طلب.
عليك تخزين المفاتيح الخاصة لكل طلب واستخدامها للمصادقة على الرمز المميّز الذي تتلقّاه من تطبيق المحفظة ومنحه الإذن.
البيانات الوصفية المدمجة لـ OpenID4VP
عند تنسيق طلب بيانات الاعتماد، عليك تضمين الحقل gw_rp_metadata_bytes داخل العنصر client_metadata (كما هو موضّح في نموذج رمز الطلب أدناه). يحتوي هذا الحقل على البيانات الوصفية للجهة المستندة التي تم ترميزها باستخدام Base64URL والتي تطلبها "محفظة Google" للتحقّق من هويتك وعرض علامتك التجارية للمستخدم.
تحتوي المَعلمة 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
]
}
},
"gw_rp_metadata_bytes": "<base64url encoded metadata string>"
}
}
أي بيانات اعتماد مؤهَّلة
في ما يلي مثال على الطلب لكلّ من رخصة القيادة الرقمية (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
],
"issuerauth_alg_values": [
-7
]
}
},
"gw_rp_metadata_bytes": "<base64url encoded metadata string>"
}
}
يمكنك طلب أي عدد من السمات المتوافقة من أي بيانات اعتماد هوية مخزَّنة في "محفظة Google".
الطلبات الموقَّعة
الطلبات الموقَّعة (طلبات التفويض الآمنة باستخدام رموز JSON المميّزة للويب) تغلّف طلب العرض القابل للتحقّق داخل رمز JSON المميّز للويب (JWT) الذي تم توقيعه باستخدام التشفير باستخدام البنية الأساسية للمفاتيح العامة (PKI)، ما يضمن سلامة الطلب ويثبت هويتك لدى "محفظة Google".
المتطلبات الأساسية
قبل تنفيذ تغييرات الرمز للطلب الموقَّع، تأكَّد من توفّر ما يلي:
- مفتاح خاص: تحتاج إلى مفتاح خاص (مثل Elliptic Curve
ES256) لتوقيع الطلب الذي تتم إدارته على خادمك. - شهادة: تحتاج إلى شهادة X.509 عادية مستمدة من زوج المفاتيح.
- التسجيل: تأكَّد من تسجيل شهادتك العامة في "محفظة Google".
منطق إنشاء الطلب
لإنشاء طلب، عليك استخدام مفتاحك الخاص وتضمين الحمولة في توقيع JSON على الويب (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 (رمز JSON المميّز للويب) مشفّر، كما حدّده اتحاد شبكة الويب العالمية (W3C). تطبيق "محفظة Google" هو المسؤول عن إنشاء هذا الردّ.
مثال:
{
"protocol" : "openid4vp-v1-signed",
"data" : {
<encrpted_response>
}
}
ستعيد هذا الردّ إلى الخادم للتحقّق من صحته. يمكنك العثور على خطوات التحقّق من صحة استجابة بيانات الاعتماد
الويب
لطلب بيانات اعتماد الهوية باستخدام واجهة برمجة التطبيقات Digital Credentials API على 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 (رمز JSON المميّز للويب) المشفّر، عليك إجراء عملية التحقّق على صعيد الخادم بشكل صارم قبل الوثوق بالبيانات.
فك تشفير الردّ
استخدِم المفتاح الخاص المقابل للمفتاح العام الذي تم إرساله في 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 باستخدام بنية تسليم خاصة بنظام Android أو الويب:
SessionTranscript = [ null, // DeviceEngagementBytes not available null, // EReaderKeyBytes not available [ "OpenID4VPDCAPIHandover", AndroidHandoverDataBytes // BrowserHandoverDataBytes for Web ] ]بالنسبة إلى عمليات التسليم على Android والويب، عليك استخدام الرقم العشوائي نفسه الذي استخدمته لإنشاء
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.يتضمّن هذا التحقّق عدة خطوات:
التحقّق من شهادة جهة الإصدار: استخرِج سلسلة شهادات التوقيع الخاصة بجهة الإصدار من
issuerAuthوتحقَّق من صحتها مقابل شهادات IACA الجذر الموثوق بها. يُرجى الرجوع إلى شهادات 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
}
التحقّق من العمر مع الحفاظ على الخصوصية (إثبات عدم المعرفة)
لإتاحة إثباتات عدم المعرفة (مثل التحقّق من أنّ عمر المستخدم يتخطّى 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.