تقديم بيانات الاعتماد باستخدام تطبيق المحفظة

تتيح واجهة برمجة التطبيقات Credential Manager Holder API لتطبيق الحافظة (المعروف أيضًا باسم "المحفظة") على Android إدارة بيانات الاعتماد الرقمية وتقديمها إلى جهات التحقّق. يوضّح هذا الدليل كيفية تسجيل بيانات اعتماد تطبيق الحامل في Credential Manager وكيفية التعامل مع طلب العرض عندما يختار المستخدم بيانات اعتماد. تتوفّر واجهة برمجة التطبيقات Holder API على الإصدار Android 6 (المستوى 23 من واجهة برمجة التطبيقات) والإصدارات الأحدث.

إضافة التبعيات

لاستخدام واجهة Credential Manager Holder API، أضِف التبعيات التالية إلى النص البرمجي build.gradle الخاص بوحدة تطبيقك:

dependencies {
    // Used to implement credential registries
    implementation "androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha05"
    implementation "androidx.credentials.registry:registry-digitalcredentials-openid:1.0.0-alpha05"
    implementation "androidx.credentials.registry:registry-digitalcredentials-sdjwtvc:1.0.0-alpha05"
    implementation "androidx.credentials.registry:registry-provider:1.0.0-alpha05"
    implementation "androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha05"
}

تسجيل بيانات الاعتماد

أولاً، أنشِئ مثيل RegistryManager ستستخدمه لتسجيل طلب OpenId4VpRegistry.

// Create the registry manager
val registryManager = RegistryManager.create(context)

إنشاء طلب OpenId4VpRegistry

يجب تسجيل OpenId4VpRegistry لمعالجة طلبات OpenID4VP من جهات التحقّق. إذا كانت لديك أنواع بيانات محلية تم تحميلها باستخدام بيانات اعتماد محفظتك (مثل sdJwtsFromStorage وmdocsFromStorage)، عليك تحويلها إلى ما يعادلها من Jetpack DigitalCredentialEntry: SdJwtEntry وMdocEntry.

إضافة Sd-JWT إلى السجلّ

اربط كل بيانات اعتماد SD-JWT محلية بـ SdJwtEntry للسجلّ:

fun mapToSdJwtEntries(sdJwtsFromStorage: List<StoredSdJwtEntry>): List<SdJwtEntry> {
    val list = mutableListOf<SdJwtEntry>()

    for (sdJwt in sdJwtsFromStorage) {
        list.add(
            SdJwtEntry(
                verifiableCredentialType = sdJwt.getVCT(),
                claims = sdJwt.getClaimsList(),
                entryDisplayPropertySet = sdJwt.toDisplayProperties(),
                id = sdJwt.getId() // Make sure this cannot be readily guessed
            )
        )
    }
    return list
}

إضافة مستندات mdoc إلى السجلّ

يمكنك ربط بيانات اعتماد mdoc المحلية بنوع Jetpack‏ MdocEntry:

fun mapToMdocEntries(mdocsFromStorage: List<StoredMdocEntry>): List<MdocEntry> {
    val list = mutableListOf<MdocEntry>()

    for (mdoc in mdocsFromStorage) {
        list.add(
            MdocEntry(
                docType = mdoc.retrieveDocType(),
                fields = mdoc.getFields(),
                entryDisplayPropertySet = mdoc.toDisplayProperties(),
                id = mdoc.getId() // Make sure this cannot be readily guessed
            )
        )
    }
    return list
}

نقاط رئيسية حول الرمز

  • المعرّفات المشفّرة: تتمثل إحدى طرق ضبط الحقل ID في تسجيل معرّف بيانات اعتماد مشفّر، بحيث لا يمكن لأحد سواك فك تشفير القيمة.
  • الأقلمة: يجب أقلمة حقول عرض واجهة المستخدم لكلا التنسيقَين.

دمج طلبك وتسجيله

اجمع الإدخالات المحوَّلة وسجِّل الطلب لدى RegistryManager:

val credentialEntries =
    mapToSdJwtEntries(sdJwtsFromStorage) + mapToMdocEntries(mdocsFromStorage)

val openidRegistryRequest = OpenId4VpRegistry(
    credentialEntries = credentialEntries,
    id = "my-wallet-openid-registry-v1" // A stable, unique ID to identify your registry record.
)

اختياري: الإصدار المضمّن

إذا لم تتوفّر بيانات اعتماد حالية من النوع المطلوب للمستخدم، يمكنك عرض خيار لإضافة بيانات اعتماد إلى تطبيق المحفظة. لإجراء ذلك، أضِف قائمة inlineIssuanceEntries تحتوي على بيانات الاعتماد المتوافقة إلى OpenId4VpRegistry:

val openidRegistryRequest = OpenId4VpRegistry(
    credentialEntries = credentialEntries,
    inlineIssuanceEntries = emptyList(),
                listOf(
                MdocInlineIssuanceEntry(
                    id = "Issuance",
                    display = InlineIssuanceEntry.InlineIssuanceDisplayProperties(
                        subtitle = "Mobile Drivers License, State Id, and Others",
                    ),
                    supportedMdocs = setOf(
                        MdocInlineIssuanceEntry.SupportedMdoc(
                        "eu.europa.ec.eudi.pid.1"
                        ),
                        MdocInlineIssuanceEntry.SupportedMdoc(
                        "org.iso.18013.5.1.mDL1"
                        ),
                    )
                ),
                SdJwtInlineIssuanceEntry(
                    id = "sd-jwt-issuance",
                    display = InlineIssuanceEntry.InlineIssuanceDisplayProperties(
                        subtitle = "Mobile Drivers License, State Id, and Others",
                    ),
                    supportedSdJwts = setOf(
                        SdJwtInlineIssuanceEntry.SupportedSdJwt("urn:openid:interop:id:1")
                    )
                )
            ),
    id = "my-wallet-openid-registry-v1"
)

سجِّل بيانات الاعتماد باستخدام CredentialManager.

try {
    val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
    // Handle failure
}

إدارة البيانات الوصفية للتطبيق

تتضمّن البيانات الوصفية التي يسجّلها تطبيق المحفظة في CredentialManager السمات التالية:

  • الاستمرارية: يتم حفظ المعلومات محليًا، وتظل محفوظة بعد إعادة التشغيل.
  • التخزين المنعزل: يتم تخزين سجلّات التسجيل لكل تطبيق بشكل منفصل، ما يعني أنّ أحد التطبيقات لا يمكنه تغيير سجلّات التسجيل لتطبيق آخر.
  • التعديلات المستندة إلى مفاتيح: يتم تحديد سجلّات التسجيل لكل تطبيق من خلال id، ما يسمح بإعادة تحديد السجلّات أو تعديلها أو حذفها.
  • تعديل البيانات الوصفية: من الممارسات الجيدة تعديل البيانات الوصفية المحفوظة كلما تغيّر تطبيقك أو تم تحميله لأول مرة. إذا تم استدعاء سجلّ عدة مرات ضمن id نفسه، سيؤدي الاستدعاء الأخير إلى استبدال جميع السجلات السابقة. لتعديل البيانات، أعِد التسجيل بدون الحاجة إلى محو السجلّ القديم أولاً.

اختياري: إنشاء مطابِق

أداة المطابقة هي ملف ثنائي بتنسيق Wasm يشغّله Credential Manager في وضع الحماية لفلترة بيانات الاعتماد المسجّلة وفقًا لطلب "جهة التحقّق" الوارد.

  • المطابِق التلقائي: تتضمّن الفئة OpenId4VpRegistry تلقائيًا المطابِق التلقائي OpenId4VP (OpenId4VpDefaults.DEFAULT_MATCHER) عند إنشاء مثيل لها. في جميع حالات الاستخدام العادية لبروتوكول OpenID4VP، تتولّى المكتبة عملية المطابقة نيابةً عنك.
  • المطابق المخصّص: لن تنفّذ مطابقًا مخصّصًا إلا إذا كنت تستخدم بروتوكولاً غير عادي يتطلب منطق المطابقة الخاص به.

التعامل مع بيانات اعتماد محدّدة

عندما يختار المستخدم مستند تعريف، يجب أن يتعامل تطبيق المحفظة مع الطلب. عليك تحديد النشاط الذي يتتبّع intent filter androidx.credentials.registry.provider.action.GET_CREDENTIAL. يوضّح نموذج المحفظة هذا الإجراء.

يؤدي الغرض إلى تشغيل نشاطك باستخدام طلب "أداة التحقّق" ومصدر الاتصال، والذي يمكنك استخراجه باستخدام PendingIntentHandler.retrieveProviderGetCredentialRequest الدالة. يؤدي ذلك إلى عرض ProviderGetCredentialRequest يحتوي على جميع المعلومات المرتبطة بطلب جهة التحقّق. هناك ثلاثة مكونات رئيسية:

  • تطبيق الاتصال: التطبيق الذي أرسل الطلب، ويمكن استرداده باستخدام getCallingAppInfo. ويمكنك استخدام هذا الرمز للتحقّق من هوية المُدقِّق.
  • بيانات الاعتماد المحدّدة: معلومات حول بيانات الاعتماد التي اختارها المستخدم، ويتم استردادها من خلال selectedCredentialSet extension method، وستتطابق مع رقم تعريف بيانات الاعتماد الذي سجّلته.
  • الطلبات المحدّدة: الطلب المحدّد الذي قدّمه المدقّق، والذي تم استرداده من خلال طريقة getCredentialOptions. بالنسبة إلى مسار طلب "بيانات الاعتماد الرقمية"، من المتوقّع العثور على GetDigitalCredentialOption واحد في هذه القائمة.

يمكنك معالجة طلب العرض باستخدام الرمز التالي:

request.credentialOptions.forEach { option ->
    if (option is GetDigitalCredentialOption) {
        Log.i(TAG, "Got DC request: ${option.requestJson}")
        processRequest(option.requestJson)
    }
}

يمكنك الاطّلاع على مثال على ذلك في نموذج المحفظة.

التحقّق من هوية جهة التحقّق

أولاً، استخرِج ProviderGetCredentialRequest من الغرض:

val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)

التحقّق من التطبيقات المميّزة: يمكن للتطبيقات المميّزة (مثل متصفّحات الويب) إجراء طلبات نيابةً عن أدوات التحقّق الأخرى من خلال ضبط مَعلمة المصدر. لاسترداد هذا المصدر، عليك تمرير قائمة بالمتصلين الموثوق بهم والذين لديهم امتيازات (قائمة مسموح بها بتنسيق JSON) إلى واجهة برمجة التطبيقات CallingAppInfo getOrigin.

val origin = request?.callingAppInfo?.getOrigin(
    privilegedAppsJson // Your allow list JSON
)

إذا لم تكن السمة origin فارغة: يتم عرض المصدر إذا كانت السمة packageName والملفات المرجعية للشهادة تتطابق مع تطبيق في القائمة المسموح بها. يُرجى اعتبار هذا الطلب مميزًا وضبط هذا المصدر في استجابة OpenID4VP بدلاً من احتساب المصدر باستخدام توقيع تطبيق الاتصال.

إذا كان الحقل origin فارغًا: يعني ذلك أنّ طلب التحقّق وارد من تطبيق Android. يجب احتساب المصدر المرتبط باستجابة OpenID4VP على النحو التالي: android:apk-key-hash:<encoded SHA 256 fingerprint>.

val appSigningInfo =
    request?.callingAppInfo?.signingInfoCompat?.signingCertificateHistory?.get(0)?.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val certHash =
    Base64.encodeToString(md.digest(appSigningInfo), Base64.NO_WRAP or Base64.NO_PADDING)
val computedOrigin = "android:apk-key-hash:$certHash"

عرض واجهة المستخدم الخاصة بحامل البطاقة

عند اختيار مستند تعريف، يتم استدعاء تطبيق المحفظة، ما يوجّه المستخدم من خلال واجهة المستخدم الخاصة بالتطبيق. هناك طريقتان عاديتان للتعامل مع سير العمل هذا:

  • إذا كانت هناك حاجة إلى مصادقة إضافية للمستخدم من أجل إصدار بيانات الاعتماد، استخدِم BiometricPrompt API. ويمكنك الاطّلاع على مثال على ذلك في النموذج.
  • في المقابل، تختار العديد من المحافظ العودة إلى التطبيق بدون إشعار المستخدم من خلال عرض نشاط فارغ يُعيد البيانات على الفور إلى التطبيق الذي طلبها، ما يقلّل من عدد النقرات التي يجريها المستخدم ويوفّر تجربة أكثر سلاسة.

عرض الردّ الخاص ببيانات الاعتماد

عندما يصبح تطبيق المحفظة جاهزًا لإرسال النتيجة، يمكنك إنهاء النشاط باستخدام الردّ الذي يتضمّن بيانات الاعتماد:

PendingIntentHandler.setGetCredentialResponse(
    resultData,
    GetCredentialResponse(DigitalCredential(response.responseJson))
)
setResult(RESULT_OK, resultData)
finish()

إذا كان هناك استثناء، يمكنك إرسال استثناء بيانات الاعتماد بشكل مشابه:

PendingIntentHandler.setGetCredentialException(
    resultData,
    GetCredentialUnknownException() // Configure the proper exception
)
setResult(RESULT_OK, resultData)
finish()

يُرجى الرجوع إلى نموذج التطبيق للاطّلاع على مثال كامل على عرض الاستجابة لبيانات الاعتماد في السياق.