انتقال اطلاعات اعتباری

‫Credentials Transfer API در Credential Manager امکان انتقال ایمن اطلاعات اعتباری کاربر را در همان دستگاه بین ارائه‌دهندگان اطلاعات اعتباری فراهم می‌کند. این راهنما جزئیات نحوه ادغام ارائه‌دهندگان اطلاعات کاربری در Android با میاناهای برنامه‌سازی کاربردی ارائه‌شده توسط کتابخانه androidx.credentials:providerevents را شرح می‌دهد. این ویژگی از گذرواژه‌ها، گذرکلیدها، اطلاعات نشانی، و فیلدهای سفارشی بااستفاده از قالب استانداردشده تبادل اطلاعات کاربری FIDO (CXF) پشتیبانی می‌کند.

مفاهیم اصلی

چارچوب انتقال اطلاعات کاربری، انتقال همتابه‌همتای اطلاعات کاربری را در همان دستگاه تسهیل می‌کند، بدون اینکه اطلاعات کاربری خام را درمعرض سیستم‌عامل Android یا برنامه‌های تأییدنشده قرار دهد.

این چارچوب دو نقش اصلی را تعریف می‌کند:

  • صادرکننده (ارائه‌دهنده منبع): ارائه‌دهنده اطلاعات اعتباری که درحال‌حاضر اطلاعات اعتباری کاربر را دراختیار دارد. این سرویس فراداده مربوط به حساب‌های قابل‌صادر شدن دردسترس (ExportEntry) را در سیستم پیش‌ثبت می‌کند و وقتی کاربر آن را انتخاب می‌کند، به درخواست‌های انتقال پاسخ می‌دهد.
  • واردکننده (ارائه‌دهنده کارخواه یا دستیار راه‌اندازی): ارائه‌دهنده اطلاعات اعتباری یا دستیار راه‌اندازی که درخواست وارد کردن را آغاز می‌کند (ImportCredentialsRequest) و مشخص می‌کند چه نوع اطلاعات اعتباری و افزونه‌هایی را می‌تواند دریافت کند.

سازگاری نسخه Android

Credentials Transfer API در دستگاه‌های دارای Android 8 (سطح میانای برنامه‌سازی کاربردی ۲۶) و بالاتر کار می‌کند.

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

وابستگی androidx.credentials:providerevents را به build.gradle یا build.gradle.kts واحدتان اضافه کنید:

dependencies {
    implementation("androidx.credentials:providerevents:1.0.0-alpha06")
}

نمونه‌سازی کلاس موردنیاز

نمونه‌ای از ProviderEventsManager موردنیاز ایجاد کنید.

val providerEventsManager = ProviderEventsManager.create(context)

پیاده‌سازی صادرکننده

برای اینکه کاربران بتوانند اطلاعات اعتباری را از برنامه شما به دیگر ارائه‌دهندگان اطلاعات اعتباری در دستگاه صادر کنند، نقش صادرکننده را پیاده‌سازی کنید.

ثبت ورودی‌های صادرشده

وقتی ارائه‌دهنده اطلاعات کاربری شما تغییر می‌کند (برای مثال، کاربری به سیستم وارد می‌شود، اطلاعات کاربری اضافه می‌کند، یا حساب‌ها را اصلاح می‌کند)، موارد ExportEntry خود را بااستفاده از ProviderEventsManager.registerExport() ثبت یا به‌روز کنید.

هر ExportEntry به این موارد نیاز دارد:

  • id: شناسه رشته‌ای مخفی و تصادفی که به‌طور منحصربه‌فردی این ورودی صادرشده را نشان می‌دهد. باید این شناسه را به‌طور ایمن حفظ کنید؛ بعداً برای تأیید درخواست‌های انتقال ورودی به آن نیاز خواهید داشت.
  • accountDisplayName: برچسب حساب اختیاری (برای مثال، "Personal Account").
  • userDisplayName: شناسه اصلی کاربر (برای مثال، "alice@example.com").
  • icon: نماد Bitmap که نشان‌دهنده ارائه‌دهنده یا حساب است (به‌طور خودکار توسط کتابخانه به PNG‏ ۳۲×۳۲ مقیاس‌بندی می‌شود).
  • supportedCredentialTypes: مجموعه‌ای از ثابت‌های رشته‌ای از CredentialTypes که نشان‌دهنده نوع این ورودی است.

suspend fun registerMyProviderForExport(
    providerEventsManager: ProviderEventsManager,
    providerIcon: Bitmap,
    // Randomly generated and stored in encrypted storage
    secretEntryId: String
) {
    val entry = ExportEntry(
        id = secretEntryId,
        accountDisplayName = "MyProvider Personal",
        userDisplayName = "alice@example.com",
        icon = providerIcon,
        supportedCredentialTypes = setOf(
            CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH, // Passwords
            CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY, // Passkeys
            CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
            CredentialTypes.CREDENTIAL_TYPE_CREDIT_CARD
        )
    )

    // RegisterExportRequest.create() attaches the default WASM matcher from assets
    val request = RegisterExportRequest.create(context, listOf(entry))

    try {
        val response = providerEventsManager.registerExport(request)
        // Registration successful
    } catch (e: Exception) {
        // Handle registration exceptions (e.g., RegisterExportProviderConfigurationException)
    }
}

اعلام فعالیت صادرکننده در فایل مانیفست

وقتی کاربر ExportEntry شما را در واسط کاربر انتخابگر سیستم انتخاب می‌کند، سیستم Activity رسیدگی تعیین‌شده شما را راه‌اندازی می‌کند. این فعالیت را با کنش هدف اجباری و طرح‌واره شناسه منبع یکسان محتوا اعلام کنید.

برای محافظت دربرابر فراخوانی‌های غیرمجاز و حملات «معاون سردرگم»، android:requireContentUriPermissionFromCaller="write" را در Android 14 (سطح ای‌پی‌آی ۳۴) و بالاتر اعلام کنید. این کار به سیستم‌عامل Android دستور می‌دهد تا قبل‌از اجازه دادن به شروع فعالیت، تأیید کند که تماس‌گیرنده آغازکننده راه‌اندازی، اجازه‌های نوشتن در شناسه منبع یکسان محتوای مقصد را دارد، همان‌طور که در مثال زیر نشان داده شده است:

<activity
    android:name="com.example.CredentialExportActivity"
    android:exported="true"
    android:requireContentUriPermissionFromCaller="write"
    android:label="@string/export_activity_label">
    <intent-filter>
        <!-- This intent action is required for Credential Manager to invoke this activity -->
        <action android:name="androidx.identitycredentials.action.IMPORT_CREDENTIALS" />
        <category android:name="android.intent.category.DEFAULT" />
        <data android:scheme="content" />
    </intent-filter>
</activity>

مدیریت کردن هدف انتقال در فعالیت

در CredentialExportActivity، از IntentHandler.retrieveProviderImportCredentialsRequest(intent) برای تجزیه کردن درخواست، درستی‌سنجی مؤلفه فراخواننده و فراداده درخواست، انجام هرگونه اصالت‌سنجی زیست‌سنجشی لازم، و نوشتن بارداده FIDO CXF در شناسه منبع یکسان محتوای ارائه‌شده استفاده کنید.

اصالت‌سنجی درخواست

چون فعالیتتان صادر می‌شود، با درستی‌سنجی هریک از لایه‌های زیر، از جریان صادر کردن اطلاعات کاربری محافظت کنید:

  1. درستی‌سنجی کردن credId نانس جلسه (دفاع اولیه): هنگام ثبت کردن ExportEntry، یک id (نانس) با آنتروپی بالا و به‌صورت تصادفی رمزنگاری‌شده تولید کنید و آن را با یک جلسه کوتاه‌مدت مرتبط کنید. در فعالیتتان، تأیید کنید که request.credId با یک نانس جلسه فعال و منقضی‌نشده که قبلاً توسط ارائه‌دهنده شما صادر شده است مطابقت دارد. چون برنامه‌های طرف سوم نمی‌توانند این عدد یک‌بارمصرف را حدس بزنند، این کار تضمین می‌کند که قصد با انتخاب کاربر معتبر در انتخابگر سیستم مطابقت دارد.
  2. درستی‌سنجی Activity.getCallingPackage() (بررسی فراخواننده): چون انتخابگر اطلاعات کاربری فعالیت شما را بااستفاده از startActivityForResult راه‌اندازی می‌کند، فعالیت شما می‌تواند getCallingPackage() را بررسی کند تا تأیید کند که فراخواننده فوری راه‌اندازی‌کننده میانای کاربر، چارچوب انتخابگر اطلاعات کاربری سیستم مورداعتماد است (در دستگاه‌های Android دارای خدمات Google Play، "com.google.android.gms"). اگر بسته فراخواننده ناشناخته است، درخواست را رد کنید.
  3. بازرسی request.callingAppInfo (اطلاعات برنامه هدف): callingAppInfo برنامه مقصدی را که اعتبارنامه‌های واردشده را دریافت خواهد کرد شناسایی می‌کند. می‌توانید از این اطلاعات برای شفاف‌سازی (مثلاً نمایش نام و نماد برنامه مقصد در میانای تأیید) یا تأیید اینکه برنامه مقصد قابل‌اعتماد است یا نه استفاده کنید.

class CredentialExportActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        // 1. Extract the transfer request from the incoming Intent
        val request: ProviderImportCredentialsRequest? =
            IntentHandler.retrieveProviderImportCredentialsRequest(intent)

        if (request == null) {
            finishWithError()
            return
        }

        // 2. Validate CallingAppInfo and secret `credId`
        val callingAppPackage = request.callingAppInfo.packageName
        val receivedCredId = request.credId
        if (!verifySecretEntryId(receivedCredId) || !isTrustedImporter(callingAppPackage)) {
            // Secret ID mismatch or untrusted caller -> abort
            sendExceptionAndFinish(ImportCredentialsNoExportOptionException("Unauthorized request"))
            return
        }

        // 3. Optional: Prompt user for Biometric / PIN authentication before exporting
        authenticateUserThenExport(request)
    }

    private fun authenticateUserThenExport(request: ProviderImportCredentialsRequest) {
        // ... Biometric prompt logic ...
        // Once authenticated, generate the FIDO CXF JSON string matching the requested types
        val cxfJsonPayload = buildFidoCxfJsonPayload(
            requestedTypes = request.request.credentialTypes,
            requestedExtensions = request.request.knownExtensions
        )

        val response = ImportCredentialsResponse(cxfJsonPayload)

        // 4. Write the JSON payload to the Content URI and set Activity result
        IntentHandler.setImportCredentialsResponse(
            context = this,
            uri = request.uri,
            intent = intent,
            response = response
        )
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    private fun sendExceptionAndFinish(exception: androidx.credentials.providerevents.exception.ImportCredentialsException) {
        IntentHandler.setImportCredentialsException(intent, exception)
        setResult(Activity.RESULT_OK, intent)
        finish()
    }

    private fun verifySecretEntryId(credentialId: String): Boolean {
        // Check if credentialId matches what you stored when calling RegisterExportRequest
        return credentialId == getStoredSecretEntryId()
    }

    private fun isTrustedImporter(packageName: String): Boolean {
        // Implement any specific allowlisting / caller checks if required
        return true
    }

    private fun finishWithError() {
        setResult(Activity.RESULT_CANCELED)
        finish()
    }
}

صادر کردن ثبت‌نام و پاک کردن استثناها

همه استثناهای این ماژول زیرکلاس‌های ImportCredentialsException،‏ RegisterExportException، یا ClearExportException هستند.

پیاده‌سازی واردکننده

برای وارد کردن اطلاعات کاربری به برنامه‌تان (برای مثال، درطول آماده‌سازی یا وارد کردن ارائه‌دهنده)، نقش واردکننده را پیاده‌سازی کنید و جریان را با فراخوانی ProviderEventsManager.importCredentials() آغاز کنید.

ساختن درخواست وارد کردن و راه‌اندازی جریان

مشخص کنید واردکننده‌تان از کدام CredentialTypes و KnownExtensions پشتیبانی می‌کند:

suspend fun startCredentialImport(
    activityContext: Context,
    providerEventsManager: ProviderEventsManager
) {
    val importRequest = ImportCredentialsRequest(
        credentialTypes = setOf(
            CredentialTypes.CREDENTIAL_TYPE_BASIC_AUTH,
            CredentialTypes.CREDENTIAL_TYPE_PUBLIC_KEY,
            CredentialTypes.CREDENTIAL_TYPE_ADDRESS,
            CredentialTypes.CREDENTIAL_TYPE_NOTE
        ),
        knownExtensions = setOf(
            KnownExtensions.KNOWN_EXTENSION_SHARED
        )
    )

    try {
        // Launches the system Selector UI; suspends until user selects a provider and completes transfer
        val response = providerEventsManager.importCredentials(activityContext, importRequest)

        // 1. Inspect the source exporter's package info
        val exporterPackageName = response.callingAppInfo.packageName

        // 2. Parse the FIDO CXF JSON string
        val cxfJsonString = response.response.responseJson
        parseAndSaveImportedCredentials(cxfJsonString)
    } catch (e: ImportCredentialsException) {
        // Handle specific import exceptions (e.g., ImportCredentialsCancellationException)
        handleImportFailure(e)
    }
}

private fun parseAndSaveImportedCredentials(cxfJsonString: String) {
    val rootJson = JSONObject(cxfJsonString)
    // Parse according to FIDO Credential Exchange Format (CXF v1.0) specification:
    // https://fidoalliance.org/specs/cx/cxf-v1.0-ps-20250814.html
}

// Helper function to make it compile
private fun handleImportFailure(e: ImportCredentialsException) {}

وارد کردن استثناهای جریان

همه استثناهای این ماژول زیرکلاس‌های ImportCredentialsException،‏ RegisterExportException، یا ClearExportException هستند.

انواع اعتبارنامه و افزونه‌های پشتیبانی‌شده

شیء androidx.credentials.providerevents.transfer.CredentialTypes ثابت‌های رشته‌ای استانداردشده مربوط به انواع مورد FIDO CXF را تعریف می‌کند:

ثابت مقدار (نوع cxf) شرح
CREDENTIAL_TYPE_BASIC_AUTH "basic-auth" اعتبارنامه ورود به سیستم نام کاربری و گذرواژه.
CREDENTIAL_TYPE_PUBLIC_KEY "passkey" اعتبارنامه‌های کلید عمومی گذرکلید FIDO2 یا WebAuthn.
CREDENTIAL_TYPE_ADDRESS "address" اطلاعات نشانی پستی یا ارسال کالا برای تکمیل خودکار فرم.
CREDENTIAL_TYPE_API_KEY "api-key" کلیدها و کدهای دسترسی میانای برنامه‌سازی کاربردی.
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" اطلاعات پرداخت کارت اعتباری و نقدی.
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" گروه‌بندی‌های سفارشی یا فیلدهای تعریف‌شده توسط کاربر.
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" جزئیات گواهینامه رانندگی.
CREDENTIAL_TYPE_FILE "file" فراداده و مراجع جای‌بان برای فایل‌های دودویی.
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" گذرواژه‌های ایمن تولیدشده توسط ماشین.
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" کارت شناسایی ملی، شماره تأمین اجتماعی، شماره شناسایی مؤدی، یا ارجاعات گذرنامه.
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" پیوندهای منطقی که به مورد دیگری در بار داده اشاره می‌کنند.
CREDENTIAL_TYPE_NOTE "note" یادداشت‌های امن تعریف‌شده توسط کاربر (رشته UTF-8).
CREDENTIAL_TYPE_PASSPORT "passport" جزئیات سند سفر گذرنامه.
CREDENTIAL_TYPE_PERSON_NAME "person-name" جزئیات هویت و نام‌گذاری شخص.
CREDENTIAL_TYPE_SSH_KEY "ssh-key" جفت کلیدهای عمومی و خصوصی SSH.
CREDENTIAL_TYPE_TOTP "totp" رمزهای گذرواژه یک‌بارمصرف زمان‌محور (احراز هویت دومرحله‌ای).
CREDENTIAL_TYPE_WIFI "wifi" شناسه SSID و گذرعبارت‌های شبکه Wi-Fi.

تطبیق‌دهنده‌های سفارشی WASM (WebAssembly) (پیشرفته)

به‌طور پیش‌فرض، فراخوانی RegisterExportRequest.create(context, entries)، credential_transfer_matcher.wasm پیش‌فرض سیستم را از دارایی‌های کتابخانه بسته‌بندی می‌کند که ورودی‌ها را صرفاً براساس تقاطع supportedCredentialTypes فیلتر می‌کند.

اگر ارائه‌دهنده اطلاعات کاربری شما به منطق تطبیق پیچیده نیاز دارد (برای مثال، بررسی‌های قابلیت پویا یا فیلتر کردن شرطی براساس فیلدهای سفارشی)، می‌توانید ماژول WebAssembly خودتان را که با WASM credential transfer API مطابقت دارد کامپایل کنید و آرایه بایت خام را مستقیماً ارسال کنید:

// Loading a custom WASM matcher
val customMatcherBytes = context.assets.open("my_custom_matcher.wasm").use { it.readBytes() }
val customRequest = RegisterExportRequest(
    entries = myEntries,
    exportMatcher = customMatcherBytes
)
providerEventsManager.registerExport(customRequest)