الخطوة 3: التكامل كجهة تسجيل جهات التحقّق

يشرح هذا الدليل كيفية إعداد جهات تسجيل أدوات التحقّق (VR)، أي مقدّمي خدمات إثبات الهوية ومجمّعيها والمنصات التي تتحقّق من هوية المستخدمين نيابةً عن عدة عملاء نهائيين (الجهات المعتمِدة النهائية)، لمرجع التصديق (CA) وترميز البيانات الوصفية لـ CBOR ودمج واجهة Digital Credentials API.

آلية عمل عملية دمج "أداة التحقّق" مع "أمين السجلّ"

بصفتك "مسؤول التحقّق"، أنت تعمل كـ "هيئة إصدار شهادات" (CA) خاصة بك، وتوقّع طلبات الهوية لـ "الجهات المعتمدة النهائية" (RP) التي تديرها. اختَر أي خطوة للانتقال إلى تفاصيل تنفيذها:

إرسال مرجع التصديق الجذري في وضع الحماية قبل الاختبار في وضع الحماية

على عكس "الجهات المعتمِدة المباشرة"، توقّع "جهات التحقّق المسجِّلة" طلبات وضع الحماية باستخدام مرجع تصديق جذر لوضع الحماية الخاص بها:

تسجيل مراجع التصديق الجذر أولاً

أرسِل نموذج تسجيل جهة التحقّق لكي تضيف Google شهادة الجذر لمرجع التصديق في وضع الحماية إلى مخزن شهادات الجذر الموثوق بها في وضع الحماية.

توقيع شهادات Leaf

إصدار شهادات غير مخوّلة للتصديق خاصة بالطرف المستنِد النهائي مع معرّف OID‏ 1.3.6.1.4.1.11129.10.1 مرتبط بشهادة CA الجذر في وضع الحماية

التحقّق من صحة الردود

التحقّق من صحة issuerAuth مقارنةً بشهادة Utopia Sandbox أثناء اختبار وضع الحماية


الخطوة أ: ضبط ملف تعريف شهادة CA والبيانات الوصفية لـ CBOR

1. ملف تعريف الشهادة

يجب توقيع الطلبات باستخدام شهادات X.509 v3 غير المخوّلة للتصديق (المرتبطة بمرجع التصديق الجذري المسجَّل في البيئة التجريبية أو بيئة الإنتاج) التي تم إنشاؤها باستخدام P-256 / ECDSA وتحتوي على إضافة Google مخصّصة:

  • معرّف الكائن (OID) للإضافة المخصّصة: 1.3.6.1.4.1.11129.10.1
  • الأهمية: غير بالغة الأهمية
  • المحتوى: تجزئة SHA-256 لـ RelyingPartyMetadataBytes، بترميز ASN.1 OCTET STRING.
  • الموضوع (الاسم المميّز): يجب أن يكون فريدًا على مستوى العالم لكلّ "طرف موثوق به" نهائي في المراحل اللاحقة توقّع له.

تنطبق على الشهادات غير المخوّلة للتصديق فقط: تنطبق متطلبات الإضافة المخصّصة والموضوع هذه فقط على شهادات الكيان النهائي (غير المخوّلة للتصديق) التي تمثّل الجهات الموثوقة النهائية في المراحل اللاحقة. لا تتطلّب مراجع التصديق الجذر في بيئة الاختبار والإنتاج، وأي مراجع تصديق وسيطة تستخدمها، إضافة معرّف الكائن (OID) المخصّص من Google، ولا تحتاج إلى موضوع خاص بالطرف المعتمد النهائي.

التسلسل الهرمي لشهادة "أمين تسجيل جهة التحقّق" من "مرجع التصديق الجذر" و"مرجع التصديق الوسيط" الاختياري إلى شهادة "الجهة المعتمدة النهائية" غير المخوّلة للتصديق والبيانات الوصفية لـ CBOR

2. مخطط البيانات الوصفية (CBOR)

يجب ترميز البيانات الوصفية لكل جهة موثوقة نهائية تابعة (والعلامة التجارية المجمّعة الاختيارية) بتنسيق CBOR:

; in CDDL for CBOR encoding
; schemaVersion = "v1"

RelyingPartyMetadataBytes = #6.24(bstr .cbor RelyingPartyMetadata)


RelyingPartyMetadata = {
  "schema_version": tstr,
  "display": DisplayInfo,
  "aggregator_info": DisplayInfo  ; Optional: include to show your branding alongside the RP
}

DisplayInfo = {
  "display_name": tstr,
  "logo_uri": tstr,             ; See brand guidelines link in following paragraph
  "privacy_policy_uri": tstr
}

يجب أن يتّبع logo_uri إرشادات العلامة التجارية في "محفظة Google".

3- دمج البيانات الوصفية لبروتوكول OpenID4VP

When formatting your signed OpenID4VP credential request in Step B, include the Base64URL-encoded RelyingPartyMetadataBytes in the gw_rp_metadata_bytes field inside the client_metadata object, and include your End-RP leaf certificate chain in the JWS x5c header.


التنسيقات وبيانات الاعتماد والإمكانات المتوافقة

تتيح "محفظة Google" استخدام مستندات التعريف الرقمية المستندة إلى ISO mdoc عبر OpenID4VP (الإصدار 1.0) على الإصدار 9 من Android (مستوى واجهة برمجة التطبيقات 28) والإصدارات الأحدث وعلى مستوى المتصفّحات المتوافقة.

بيانات الاعتماد والسمات المتوافقة

يمكنك البحث عن معرّفات doctype_value (رخصة القيادة على الأجهزة الجوّالة، وبطاقة الهوية الرقمية، ومستند التعريف مع صورة) ومسارات طلبات السمات التي يمكنك طلبها من "محفظة Google".

جهات الإصدار المتوافقة وشهادات IACA

نزِّل شهادات الجذر الرسمية الصادرة عن "الرابطة الدولية لمسؤولي إصدار الشهادات" (IACA) للولايات الأمريكية وجهات الإصدار الدولية وجهة إصدار Sandbox Utopia للتحقّق من issuerAuth.


الخطوة ب: تنسيق الطلب وتوقيعه (من جهة الخادم)

لطلب بيانات الاعتماد من أي محفظة، يجب تنسيق طلبك باستخدام OpenID4VP على خادمك. يمكنك طلب بيانات اعتماد معيّنة أو بيانات اعتماد متعددة في عنصر dcql_query واحد.

مثال على طلب JSON

في ما يلي نموذج لطلب mdoc requestJson للحصول على بيانات اعتماد الهوية من أي محفظة على جهاز 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": {
          // 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>"
  }
}

أي بيانات اعتماد مؤهَّلة

في ما يلي مثال على طلب رخصة 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 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".

الطلبات الموقَّعة

الطلبات الموقَّعة (طلبات التفويض الآمنة المستندة إلى JWT) تغلّف طلب العرض القابل للتحقّق داخل رمز ويب مميّز بتنسيق JSON (JWT) موقَّع بطريقة مشفّرة باستخدام البنية الأساسية للمفاتيح العامة (PKI)، ما يضمن سلامة الطلب وإثبات هويتك لـ "محفظة Google".

المتطلبات الأساسية

قبل تنفيذ تغييرات الرمز للطلبات الموقَّعة، تأكَّد من توفّر ما يلي:

  • المفتاح الخاص: تحتاج إلى مفتاح خاص (على سبيل المثال، Elliptic Curve ES256) لتوقيع الطلب، وتتم إدارته على خادمك. لاختبار وضع الحماية بصفتك جهة معتمِدة، استخدِم المفتاح الخاص لاختبار وضع الحماية.
  • الشهادة: تحتاج إلى شهادة معيار X.509 مشتقة من مفتاحَي التشفير.
  • التسجيل: تأكَّد من أنّ شهادتك العامة (أو شهادة الجذر لمرجع التصديق لجهات التحقّق من التسجيل) معتمَدة من "محفظة Google".

منطق إنشاء الطلبات

عند إنشاء طلب OpenID4VP وتوقيعه، يجب الانتباه جيدًا إلى تنسيق الترميز الدقيق المتوقّع لكل حقل، لأنّ الخلط بين base64 وbase64url أو استخدام الحشو (=) سيؤدي إلى تعذُّر التحقّق من صحة التوقيع أو المصدر:

الحقل الترميز / التنسيق المطلوب الأحرف والحشو
client_id ‏(x509_hash:<hash>) base64url بدون حشو يستخدم الأبجدية الآمنة لعنوان URL (-، _)، ويتم حذف اللاحقة =
expected_origins ‏(android:apk-key-hash:<hash>) ترميز Base64 العادي بدون حشو يستخدم الأبجدية العادية (+، /)، ويتم تجريد اللاحقة =
expected_origins ‏(https://...) عنوان URL بأحرف صغيرة بدون شَرطات مائلة لاحقة مثال: https://example.com (بدون اللاحقة /)
رأس JWS واحد (x5c) Standard base64 مع مساحة متروكة يستخدم الأبجدية العادية (+، /)، ويحتفظ بحشو = اللاحق

لإنشاء طلب، استخدِم مفتاحك الخاص وأدرِج الحمولة في 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()

            # Important: Use URL-safe Base64 (base64url: '-', '_') without padding ('=') for client_id
            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
            # Important: Use standard Base64 (not URL-safe) without padding for android:apk-key-hash:
            # origin = f"android:apk-key-hash:{base64.b64encode(apk_cert_hash).decode('utf-8').rstrip('=')}"
            # Format for web origin (lowercase URL without trailing slash): origin = "https://example.com"
            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.
            # Important: Use standard Base64 ('+', '/') WITH padding ('=') for x5c
            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" : {
    <encrypted_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 (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 والتحقّق من صحة DeviceResponse

  1. أنشئ SessionTranscript من ISO/IEC 18013-5:2021 باستخدام بنية تسليم خاصة بنظام Android أو الويب:
SessionTranscript = [
  null,                // DeviceEngagementBytes not available
  null,                // EReaderKeyBytes not available
  [
    "OpenID4VPDCAPIHandover",
    AndroidHandoverDataBytes   // BrowserHandoverDataBytes for Web
  ]
]

بالنسبة إلى عمليات التسليم على كل من Android والويب، استخدِم nonce نفسه الذي استخدمته لإنشاء credential_request.

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()

Browser Handover

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.

تتضمّن عملية التحقّق هذه أربع خطوات:

  1. التحقّق من شهادة جهة الإصدار: استخرِج سلسلة شهادات التوقيع الخاصة بجهة الإصدار من issuerAuth وتحقَّق من صحتها من خلال مقارنتها بشهادات الجذر الموثوق بها الصادرة عن "هيئة إصدار الشهادات الدولية" (IACA). يُرجى الرجوع إلى جهات الإصدار المعتمَدة وشهادات IACA (وإضافة شهادة Utopia Sandbox عند الاختبار باستخدام مستند تعريف تجريبي في وضع الحماية).
  2. التحقّق من توقيع MSO (القسم 9.1.2 من معيار ISO/IEC 18013-5)
  3. احتساب ValueDigests والتحقّق منه لعناصر البيانات (القسم 9.1.2 من معيار ISO/IEC 18013-5)
  4. التحقّق من توقيع deviceSignature (القسم 9.1.3 من معيار ISO/IEC 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 المطلوبة.

للحصول على نظرة عامة شاملة على ZKP وإمكاناته، يُرجى الاطّلاع على الأسئلة الشائعة.

  ...
  "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 التي تريد الوثوق بها.


حِزم تطوير البرامج (SDK) وعمليات التنفيذ المرجعية والدعم

استخدِم مكتباتنا المفتوحة المصدر والخادم المرجعي وأداة التحقّق من الويب لتسريع عملية التنفيذ:

المراجع / المكتبة ما يوفّره رابط
Identity Verifiers Reference Implementation نموذج لخادم وعميل يوضّحان كيفية إنشاء طلب OpenID4VP وتوقيع JWS وفك تشفير JWE والتحقّق من صحة استجابة mdoc. مستودع GitHub
حزمة تطوير البرامج (SDK) لـ Multipaz مكتبة مفتوحة المصدر (OpenWallet Foundation) تتضمّن DeviceResponseParser لفك ترميز CBOR وإعادة إنشاء SessionTranscript والتحقّق من صحة توقيع ISO/IEC 18013-5/7 openwallet-foundation/multipaz
مكتبة Longfellow ZKP وخدمة Verifier مكتبة مفتوحة المصدر وخادم مستند إلى Docker للتحقّق من صحة بيانات العمر والسمات المستندة إلى "إثبات المعرفة الصفرية" (mso_mdoc_zk). google/longfellow-zk
أداة التحقّق التفاعلية من الويب أداة التحقّق من الويب المستضافة لاختبار طلبات W3C Digital Credentials API / OpenID4VP في "محفظة Google". verifier.multipaz.org
مواصفات OpenID4VP 1.0 المواصفات الرسمية لبروتوكول OpenID for Verifiable Presentations 1.0. مواصفات OpenID4VP
الأسئلة الشائعة ودعم المطوّرين إجابات عن الأسئلة الشائعة حول الدمج الفني، أو يمكنك إرسال رسالة إلكترونية إلى wallet-identity-rp-support@google.com للحصول على مساعدة في تصحيح الأخطاء. الأسئلة الشائعة حول الدمج الفني

الخطوات التالية