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

رابط‌های برنامه‌نویسی کاربردی (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")
}

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

یک نمونه از 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 اعلان کنید.

برای محافظت در برابر فراخوانی‌های غیرمجاز و حملات confusion-deputy، در اندروید ۱۴ (سطح API ۳۴) و بالاتر، android:requireContentUriPermissionFromCaller="write" تعریف کنید. این به سیستم عامل اندروید دستور می‌دهد تا قبل از اجازه دادن به شروع فعالیت، تأیید کند که فراخواننده‌ای که راه‌اندازی را آغاز می‌کند، مجوزهای نوشتن را در URI محتوای مقصد دارد، همانطور که در مثال زیر نشان داده شده است:

<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) برای تجزیه درخواست، تأیید مؤلفه فراخوانی و ابرداده درخواست، انجام هرگونه احراز هویت بیومتریک مورد نیاز و نوشتن payload FIDO CXF در URI محتوای ارائه شده استفاده کنید.

درخواست را تأیید کنید

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

  1. تأیید عدم وجود (nonce) در session credId (دفاع اولیه): هنگام ثبت ExportEntry خود، یک id (nonce) با آنتروپی بالا و تصادفی رمزنگاری شده ایجاد کنید و آن را با یک session کوتاه مدت مرتبط کنید. در activity خود، تأیید کنید که request.credId با یک عدم وجود session فعال و منقضی نشده که قبلاً توسط ارائه دهنده شما صادر شده است، مطابقت داشته باشد. از آنجا که برنامه‌های شخص ثالث نمی‌توانند این عدم وجود را حدس بزنند، این امر تضمین می‌کند که intent با یک انتخاب کاربر معتبر در انتخابگر سیستم مطابقت دارد.
  2. تأیید Activity.getCallingPackage() (بررسی فراخوانی‌کننده): از آنجا که انتخابگر اعتبارنامه، فعالیت شما را با استفاده از startActivityForResult راه‌اندازی می‌کند، فعالیت شما می‌تواند getCallingPackage() بررسی کند تا تأیید کند که فراخوانی‌کننده‌ی فوری که رابط کاربری را راه‌اندازی می‌کند، چارچوب انتخابگر اعتبارنامه‌ی سیستم مورد اعتماد است (در دستگاه‌های اندروید با سرویس‌های گوگل پلی، "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" کلیدها و توکن‌های دسترسی API.
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" کارت شناسایی ملی، شماره تأمین اجتماعی، شماره شناسایی ملی (TIN) یا گذرنامه.
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)