انتقال اعتبارنامه

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

مفاهیم اصلی

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

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

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

سازگاری با نسخه اندروید

رابط برنامه‌نویسی کاربردی انتقال اعتبارنامه‌ها (Credentials Transfer API) روی دستگاه‌هایی که اندروید ۸ (سطح API 26) و بالاتر را اجرا می‌کنند، کار می‌کند.

وابستگی‌ها را اضافه کنید

وابستگی androidx.credentials:providerevents به build.gradle یا build.gradle.kts ماژول خود اضافه کنید:

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

Instantiate the required class

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

val providerEventsManager = ProviderEventsManager.create(context)

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

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

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

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

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

  • id : یک شناسه رشته‌ای مخفی و تصادفی که منحصراً نمایانگر این ورودی خروجی است. شما باید این شناسه را به طور ایمن ذخیره کنید ؛ بعداً برای تأیید درخواست‌های انتقال ورودی به آن نیاز خواهید داشت.
  • accountDisplayName : برچسب حساب اختیاری (برای مثال، "Personal Account" ).
  • userDisplayName : شناسه اصلی کاربر (برای مثال، "alice@example.com" ).
  • icon : یک آیکون Bitmap که نشان‌دهنده‌ی ارائه‌دهنده یا حساب کاربری است (به‌طور خودکار توسط کتابخانه به PNG 32x32 مقیاس‌بندی می‌شود).
  • 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 مربوط به مدیریت تعیین‌شده شما را اجرا می‌کند. این activity را با intent اجباری action و content URI scheme تعریف کنید:

<activity
    android:name="com.example.CredentialExportActivity"
    android:exported="true"
    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) برای تجزیه درخواست، تأیید برنامه فراخوانی و credId ، انجام هرگونه احراز هویت بیومتریک مورد نیاز و نوشتن payload FIDO CXF در URI محتوای ارائه شده استفاده کنید:

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

استثنائات ثبت و ترخیص صادرات

All exceptions in this module are subclasses of ImportCredentialsException , RegisterExportException , or 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 هستند.

Supported credential types and extensions

شیء 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" کلیدها و توکن‌های دسترسی API.
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" اطلاعات پرداخت با کارت اعتباری و بدهی.
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" Custom groupings or user-defined fields.
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" جزئیات گواهینامه رانندگی.
CREDENTIAL_TYPE_FILE "file" ارجاعات فراداده و جای‌نگهدار برای فایل‌های باینری.
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" Machine-generated secure passwords.
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" National ID cards, SSN, TIN, or passport references.
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" پیوندهای منطقی که به مورد دیگری در payload اشاره می‌کنند.
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" رمزهای عبور یکبار مصرف (2FA) مبتنی بر زمان.
CREDENTIAL_TYPE_WIFI "wifi" SSID و رمز عبور شبکه Wi-Fi.

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

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

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

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