ارائه کردن اعتبارنامه‌ها با برنامه دارنده

Credential Manager Holder API به برنامه دارنده Android شما (که «کیف پول» نیز نامیده می‌شود) امکان می‌دهد اطلاعات اعتباری دیجیتال را مدیریت کند و به درستی‌سنج‌ها ارائه دهد. این راهنما نحوه ثبت اطلاعات کاربری برنامه دارنده شما با Credential Manager و نحوه رسیدگی به درخواست ارائه زمانی که کاربر یک اطلاعات کاربری را انتخاب می‌کند، شرح می‌دهد. Holder API در Android 6 (سطح میانای برنامه‌سازی کاربردی ۲۳) و بالاتر پشتیبانی می‌شود.

افزودن وابستگی‌ها

برای استفاده از 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

برای رسیدگی به درخواست‌های OpenID4VP ازسوی درستی‌سنج‌ها، باید OpenId4VpRegistry ثبت کنید. با فرض اینکه انواع داده‌های محلی را با اطلاعات کاربری کیف پولتان بار کرده‌اید (مثلاً 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 است که «مدیر اطلاعات کاربری» آن را در محیط امن اجرا می‌کند تا اطلاعات کاربری ثبت‌شده شما را دربرابر درخواست «درستی‌سنج» ورودی فیلتر کند.

  • تطبیق‌دهنده پیش‌فرض: کلاس OpenId4VpRegistry به‌طور خودکار تطبیق‌دهنده پیش‌فرض OpenId4VP (OpenId4VpDefaults.DEFAULT_MATCHER) را هنگام نمونه‌سازی آن دربرمی‌گیرد. برای همه موارد استفاده استاندارد OpenID4VP، کتابخانه مطابقت را برای شما انجام می‌دهد.
  • تطابق‌دهنده سفارشی: فقط درصورتی تطابق‌دهنده سفارشی را پیاده‌سازی می‌کنید که از پروتکل غیراستانداردی پشتیبانی کنید که به منطق تطابق خاص خود نیاز دارد.

رسیدگی به اعتبارنامه انتخاب‌شده

وقتی کاربر اطلاعات کاربری را انتخاب می‌کند، برنامه دارنده شما باید درخواست را مدیریت کند. باید «فعالیتی» تعریف کنید که به فیلتر قصد 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 API ارسال کنید.

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

برای مشاهده نمونه کامل برگرداندن پاسخ اطلاعات کاربری در بافت، به برنامه نمونه مراجعه کنید.