دمج تطبيق "حامل المستندات" مع "إدارة بيانات الاعتماد"

تتيح واجهة برمجة التطبيقات Credential Manager Holder API لتطبيق حامل الشهادة (المعروف أيضًا باسم "المحفظة") على Android إدارة الشهادات الرقمية وتقديمها إلى جهات التحقّق.

صورة تعرض واجهة مستخدم بيانات الاعتماد الرقمية في "مدير بيانات الاعتماد"
الشكل 1. واجهة مستخدم أداة اختيار بيانات الاعتماد الرقمية

المفاهيم الأساسية

من المهم التعرّف على المفاهيم التالية قبل استخدام Holder API.

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

يمكن تخزين المستندات في تطبيقات حامل المستندات بتنسيقات مختلفة. هذه التنسيقات هي مواصفات لكيفية عرض بيانات الاعتماد، ويحتوي كل تنسيق على المعلومات التالية حول بيانات الاعتماد:

  • النوع: هو الفئة، مثل شهادة جامعية أو رخصة قيادة على جهاز جوّال.
  • الخصائص: سمات مثل الاسم الأول واسم العائلة
  • الترميز: الطريقة التي يتم بها تنظيم بيانات الاعتماد، مثل SD-JWT أو mdoc
  • الصلاحية: هي طريقة للتحقّق من صحة بيانات الاعتماد باستخدام التشفير.

تختلف طريقة الترميز والتحقّق من الصحة قليلاً بين تنسيقات بيانات الاعتماد، ولكنها تتشابه من الناحية الوظيفية.

يتوافق السجلّ مع تنسيقَين:

يمكن لخدمة التحقّق إرسال طلب OpenID4VP للحصول على SD-JWT وmdocs عند استخدام Credential Manager. ويختلف الاختيار حسب حالة الاستخدام والمجال الذي تم اختياره.

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

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

  • المطابقة: يتم استخدام البيانات الوصفية لبيانات الاعتماد المسجّلة للمطابقة مع طلبات التحقّق المستقبلية.
  • العرض: يتم عرض عناصر واجهة المستخدم المخصّصة للمستخدم في واجهة اختيار بيانات الاعتماد.

ستستخدم فئة OpenId4VpRegistry لتسجيل بيانات الاعتماد الرقمية، لأنّها تتوافق مع تنسيقات بيانات اعتماد mdoc وSD-JWT. سيرسل المدقّقون طلبات OpenID4VP لطلب بيانات الاعتماد هذه.

تسجيل بيانات اعتماد تطبيقك

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

Groovy

dependencies {
    // Use to implement credentials registrys

    implementation "androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha04"
    implementation "androidx.credentials.registry:registry-digitalcredentials-preview:1.0.0-alpha04"
    implementation "androidx.credentials.registry:registry-provider:1.0.0-alpha04"
    implementation "androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha04"

}

Kotlin

dependencies {
    // Use to implement credentials registrys

    implementation("androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha04")
    implementation("androidx.credentials.registry:registry-digitalcredentials-preview:1.0.0-alpha04")
    implementation("androidx.credentials.registry:registry-provider:1.0.0-alpha04")
    implementation("androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha04")

}

إنشاء RegistryManager

أنشئ مثيلاً من RegistryManager وسجِّل طلب OpenId4VpRegistry معه.

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

// The guide covers how to build this out later
val registryRequest = OpenId4VpRegistry(credentialEntries, id)

try {
    registryManager.registerCredentials(registryRequest)
} catch (e: Exception) {
    // Handle exceptions
}

إنشاء طلب OpenId4VpRegistry

كما ذكرنا سابقًا، عليك تسجيل OpenId4VpRegistry للتعامل مع طلب OpenID4VP من جهة التحقّق. سنفترض أنّ لديك بعض أنواع البيانات المحلية المحمَّلة مع بيانات اعتماد محفظتك (على سبيل المثال، sdJwtsFromStorage). عليك الآن تحويلها إلى ما يعادلها في Jetpack DigitalCredentialEntry استنادًا إلى تنسيقها، أي SdJwtEntry أو MdocEntry لـ SD-JWT أو mdoc على التوالي.

إضافة رموز 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.
)

الآن، نحن جاهزون لتسجيل بيانات اعتمادك باستخدام CredentialManager.

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

لقد سجّلت بيانات اعتمادك الآن في "إدارة بيانات الاعتماد".

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

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

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

اختياري: إنشاء أداة مطابقة

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

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

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

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

يُطلق الغرض نشاطك مع طلب Verifier ومصدر الاتصال، الذي تستخرجه باستخدام الدالة 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)
    }
}

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

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

  1. استخراج ProviderGetCredentialRequest من الغرض:
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
  1. التحقّق من مصدر مميّز: يمكن للتطبيقات المميّزة (مثل متصفّحات الويب) إجراء مكالمات نيابةً عن جهات التحقّق الأخرى من خلال ضبط مَعلمة المصدر. لاسترداد هذا المصدر، يجب تمرير قائمة بالجهات الطالبة المميّزة والموثوق بها (قائمة مسموح بها بتنسيق JSON) إلى واجهة برمجة التطبيقات getOrigin() الخاصة بـ CallingAppInfo.
val origin = request?.callingAppInfo?.getOrigin(
    privilegedAppsJson // Your allow list JSON
)

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

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

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

val appSigningInfo = request?.callingAppInfo?.signingInfoCompat?.signingCertificateHistory[0]?.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val certHash = Base64.encodeToString(md.digest(appSigningInfo), Base64.NO_WRAP or Base64.NO_PADDING)
return "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()

راجِع تطبيق العيّنة للحصول على مثال كامل حول عرض ردّ بيانات الاعتماد في السياق.