Yeterlilik belgesi aktarımı

Kimlik Bilgisi Yöneticisi'nin Kimlik Bilgisi Aktarımı API'leri, kimlik bilgisi sağlayıcılar arasında kullanıcı kimlik bilgilerinin aynı cihazda güvenli bir şekilde aktarılmasını sağlar. Bu kılavuzda, Android'deki kimlik bilgisi sağlayıcıların androidx.credentials:providerevents kitaplığı tarafından sağlanan API'lerle nasıl entegre olabileceği ayrıntılı olarak açıklanmaktadır. Bu özellik, standartlaştırılmış FIDO Kimlik Bilgisi Değişim Biçimi (CXF) kullanılarak şifreleri, geçiş anahtarlarını, adres bilgilerini ve özel alanları destekler.

Temel kavramlar

Kimlik bilgisi aktarımı çerçevesi, aynı cihazda eşler arası kimlik bilgisi aktarımını kolaylaştırır. Bu işlem sırasında, ham kimlik bilgileri Android işletim sistemine veya kimliği doğrulanmamış uygulamalara gösterilmez.

Çerçeve, iki temel rol tanımlar:

  • Dışa aktarıcı (kaynak sağlayıcı): Şu anda kullanıcı kimlik bilgilerini barındıran bir kimlik bilgisi sağlayıcı. Dışa aktarılabilir hesaplarla (ExportEntry) ilgili meta verileri sistemle önceden kaydeder ve kullanıcı tarafından seçildiğinde aktarım isteklerine yanıt verir.
  • İçe aktarıcı (istemci sağlayıcı veya kurulum sihirbazı): Hangi tür kimlik bilgilerini ve uzantıları alabileceğini belirten bir içe aktarma isteği (ImportCredentialsRequest) başlatan bir kimlik bilgisi sağlayıcı veya kurulum sihirbazı.

Android sürümü uyumluluğu

Credentials Transfer API, Android 8 (API düzeyi 26) ve sonraki sürümlerin yüklü olduğu cihazlarda çalışır.

Bağımlılık ekleme

Modülünüzün build.gradle veya build.gradle.kts dosyasına androidx.credentials:providerevents bağımlılığını ekleyin:

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

Gerekli sınıfı oluşturun

Gerekli ProviderEventsManager örneğini oluşturun.

val providerEventsManager = ProviderEventsManager.create(context)

Dışa aktarıcıyı uygulama

Kullanıcıların kimlik bilgilerini uygulamanızdan cihazdaki diğer kimlik bilgisi sağlayıcılarına aktarmasına izin vermek için dışa aktarıcı rolünü uygulayın.

Dışa aktarma girişlerini kaydetme

Kimlik bilgisi sağlayıcınız değiştiğinde (örneğin, bir kullanıcı oturum açtığında, kimlik bilgisi eklediğinde veya hesapları değiştirdiğinde) ExportEntry öğelerinizi ProviderEventsManager.registerExport() kullanarak kaydedin veya güncelleyin.

Her ExportEntry için gerekenler:

  • id: Bu dışa aktarma girişini benzersiz şekilde temsil eden gizli, rastgele oluşturulmuş bir dize tanımlayıcı. Bu kimliği güvenli bir şekilde saklamanız gerekir. Daha sonra gelen transfer isteklerini doğrulamak için bu kimliğe ihtiyacınız olacaktır.
  • accountDisplayName: İsteğe bağlı hesap etiketi (örneğin, "Personal Account").
  • userDisplayName: Kullanıcının birincil tanımlayıcısı (örneğin, "alice@example.com").
  • icon: Sağlayıcıyı veya hesabı temsil eden bir Bitmap simgesi (kitaplık tarafından otomatik olarak 32x32 PNG'ye ölçeklendirilir).
  • supportedCredentialTypes: Bu girişin hangi türleri içerdiğini gösteren, CredentialTypes kaynağından alınan bir dizi dize sabiti.

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

Manifest dosyasında dışa aktarma etkinliğini bildirin

Kullanıcı, sistem seçici kullanıcı arayüzünde ExportEntry öğenizi seçtiğinde sistem, belirlenen işleme Activity öğenizi başlatır. Bu etkinliği zorunlu amaç işlemi ve içerik URI şemasıyla bildirin.

Yetkisiz çağırmalara ve confused-deputy saldırılarına karşı koruma sağlamak için Android 14 (API düzeyi 34) ve sonraki sürümlerde android:requireContentUriPermissionFromCaller="write" beyan edin. Bu, Android işletim sistemine, etkinliğin başlamasına izin vermeden önce başlatmayı yapan arayanın hedef içerik URI'sine yazma izni olup olmadığını doğrulamasını söyler. Bu durum, aşağıdaki örnekte gösterilmiştir:

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

Etkinliğinizdeki aktarım amacını işleme

CredentialExportActivity içinde, isteği ayrıştırmak, çağıran bileşeni ve istek meta verilerini doğrulamak, gerekli biyometrik kimlik doğrulamayı gerçekleştirmek ve FIDO CXF yükünü sağlanan içerik URI'sine geri yazmak için IntentHandler.retrieveProviderImportCredentialsRequest(intent) kullanın.

İsteğin kimliğini doğrulama

Etkinliğiniz dışa aktarıldığı için aşağıdaki katmanların her birini doğrulayarak kimlik bilgisi dışa aktarma akışını koruyun:

  1. credId oturum nonce'unu doğrulayın (birincil savunma): ExportEntry cihazınızı kaydederken kriptografik olarak rastgele, yüksek entropili bir id (nonce) oluşturun ve bunu kısa süreli bir oturumla ilişkilendirin. Etkinliğinizde, request.credId değerinin sağlayıcınız tarafından daha önce verilmiş etkin ve süresi dolmamış bir oturum tek seferlik rastgele sayısıyla eşleştiğini doğrulayın. Üçüncü taraf uygulamaları bu tek seferlik rastgele sayıyı tahmin edemediğinden, intent'in sistem seçicideki gerçek bir kullanıcı seçimine karşılık gelmesi sağlanır.
  2. Doğrulama Activity.getCallingPackage() (arayan kontrolü): Kimlik bilgisi seçici, etkinliğinizi startActivityForResult kullanarak başlattığı için etkinliğiniz, kullanıcı arayüzünü başlatan arayanın güvenilir sistem kimlik bilgisi seçici çerçevesi (Google Play Hizmetleri'nin yüklü olduğu Android cihazlarda "com.google.android.gms") olduğunu doğrulamak için getCallingPackage() öğesini inceleyebilir. Arayan paket tanınmıyorsa isteği reddedin.
  3. İncele request.callingAppInfo (hedef uygulama bilgileri): callingAppInfo, içe aktarılan kimlik bilgilerini alacak hedef uygulamayı tanımlar. Bu bilgileri, şeffaflık için uygun gördüğünüz şekilde (ör. onay kullanıcı arayüzünüzde hedef uygulama adını ve simgesini gösterme) veya hedef uygulamanın güvenilir olup olmadığını doğrulamak için kullanabilirsiniz.

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

Kayıt ve gümrükleme istisnalarını dışa aktarma

Bu modüldeki tüm istisnalar ImportCredentialsException, RegisterExportException veya ClearExportException alt sınıflarıdır.

İçe aktarıcıyı uygulama

Kimlik bilgilerini uygulamanıza aktarmak için (ör. ilk katılım veya sağlayıcı içe aktarma sırasında) içe aktarıcı rolünü uygulayın ve ProviderEventsManager.importCredentials() işlevini çağırarak akışı başlatın.

İçe aktarma isteğini ve başlatma akışını oluşturma

İçe aktarıcınızın hangi CredentialTypes ve KnownExtensions biçimlerini desteklediğini belirtin:

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

İçe aktarma akışı istisnaları

Bu modüldeki tüm istisnalar ImportCredentialsException, RegisterExportException veya ClearExportException alt sınıflarıdır.

Desteklenen kimlik bilgisi türleri ve uzantıları

androidx.credentials.providerevents.transfer.CredentialTypes nesnesi, FIDO CXF öğe türlerine karşılık gelen standartlaştırılmış dize sabitlerini tanımlar:

Sabit Değer (cxf türü) Açıklama
CREDENTIAL_TYPE_BASIC_AUTH "basic-auth" Kullanıcı adı ve şifre giriş bilgileri.
CREDENTIAL_TYPE_PUBLIC_KEY "passkey" FIDO2 veya WebAuthn geçiş anahtarı ortak anahtar kimlik bilgileri.
CREDENTIAL_TYPE_ADDRESS "address" Formları otomatik doldurmak için posta veya kargo adresi bilgileri.
CREDENTIAL_TYPE_API_KEY "api-key" API erişim anahtarları ve jetonları
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" Kredi ve banka kartı ödeme bilgileri
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" Özel gruplandırmalar veya kullanıcı tanımlı alanlar.
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" Sürücü belgesi ayrıntıları
CREDENTIAL_TYPE_FILE "file" İkili dosyalar için meta veriler ve yer tutucu referansları.
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" Makine tarafından oluşturulan güvenli şifreler.
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" Resmi kimlik kartları, SSN, TIN veya pasaport referansları.
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" Yükteki başka bir öğeyi işaret eden mantıksal bağlantılar.
CREDENTIAL_TYPE_NOTE "note" Kullanıcı tanımlı güvenli notlar (UTF-8 dizesi).
CREDENTIAL_TYPE_PASSPORT "passport" Pasaport seyahat belgesi ayrıntıları
CREDENTIAL_TYPE_PERSON_NAME "person-name" Kişi kimliği ve adlandırma ayrıntıları
CREDENTIAL_TYPE_SSH_KEY "ssh-key" SSH ortak ve özel anahtar çiftleri.
CREDENTIAL_TYPE_TOTP "totp" Zaman tabanlı tek kullanımlık şifre (2FA) sırları.
CREDENTIAL_TYPE_WIFI "wifi" Kablosuz ağ SSID'si ve geçiş ifadeleri.

Özel WASM (WebAssembly) eşleştiriciler (Gelişmiş)

Varsayılan olarak, RegisterExportRequest.create(context, entries) çağrısı, girişleri yalnızca supportedCredentialTypes kesişimine göre filtreleyen kitaplık öğelerinden sistem varsayılanı credential_transfer_matcher.wasm öğesini paketler.

Kimlik bilgisi sağlayıcınız karmaşık eşleştirme mantığı (ör. dinamik özellik kontrolleri veya özel alanlara göre koşullu filtreleme) gerektiriyorsa WASM kimlik bilgisi aktarımı API'siyle eşleşen kendi WebAssembly modülünüzü derleyebilir ve ham bayt dizisini doğrudan iletebilirsiniz:

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