Przenoszenie certyfikatów

Interfejsy API do przenoszenia danych logowania w Menedżerze danych logowania umożliwiają bezpieczne przenoszenie danych logowania użytkownika między dostawcami danych logowania na tym samym urządzeniu. Z tego przewodnika dowiesz się jak dostawcy danych logowania na Androidzie mogą integrować się z interfejsami API udostępnianymi przez androidx.credentials:providerevents bibliotekę. Ta funkcja obsługuje hasła, klucze dostępu, informacje o adresie i pola niestandardowe za pomocą standardowego formatu wymiany danych logowania FIDO (CXF).

Podstawowe pojęcia

Platforma przenoszenia danych logowania ułatwia przenoszenie danych logowania między urządzeniami na tym samym urządzeniu bez ujawniania surowych danych logowania systemowi Android ani nieuwierzytelnionym aplikacjom.

Platforma definiuje 2 główne role:

  • Eksporter (dostawca źródłowy): dostawca danych logowania, który obecnie przechowuje dane logowania użytkownika. Wstępnie rejestruje w systemie metadane dotyczące dostępnych kont, które można wyeksportować (ExportEntry), i odpowiada na żądania przeniesienia , gdy użytkownik je wybierze.
  • Importer (dostawca klienta lub kreator konfiguracji): dostawca danych logowania lub kreator konfiguracji, który inicjuje żądanie importu (ImportCredentialsRequest), określając, jakie typy danych logowania i rozszerzeń może odbierać.

Zgodność z wersją Androida

Interfejs Credentials Transfer API działa na urządzeniach z Androidem 8 (poziom API 26) lub nowszym.

Dodawanie zależności

Dodaj zależność androidx.credentials:providerevents do pliku build.gradle lub build.gradle.kts modułu:

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

Tworzenie instancji wymaganej klasy

Utwórz instancję wymaganej klasy ProviderEventsManager.

val providerEventsManager = ProviderEventsManager.create(context)

Implementowanie eksportera

Aby umożliwić użytkownikom eksportowanie danych logowania z Twojej aplikacji do innych dostawców danych logowania na urządzeniu, zaimplementuj rolę eksportera.

Rejestrowanie wpisów eksportu

Gdy dostawca danych logowania się zmieni (np. użytkownik się zaloguje, doda dane logowania lub zmodyfikuje konta), zarejestruj lub zaktualizuj elementy ExportEntry za pomocą ProviderEventsManager.registerExport().

Każdy element ExportEntry wymaga:

  • id: tajny, losowo wygenerowany ciąg znaków, który jednoznacznie reprezentuje ten wpis eksportu. Musisz bezpiecznie przechowywać ten identyfikator ; będzie on potrzebny później do weryfikowania przychodzących żądań przeniesienia.
  • accountDisplayName: opcjonalna etykieta konta (np. "Personal Account").
  • userDisplayName: główny identyfikator użytkownika (np. "alice@example.com").
  • icon: ikona Bitmap reprezentująca dostawcę lub konto (biblioteka automatycznie skaluje ją do formatu PNG o wymiarach 32 x 32).
  • supportedCredentialTypes: zbiór stałych ciągów znaków z CredentialTypes reprezentujących typy danych logowania, które zawiera ten wpis.

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

Deklarowanie aktywności eksportera w pliku manifestu

Gdy użytkownik wybierze Twój element ExportEntry w interfejsie selektora systemu, system uruchomi wyznaczoną Activity obsługi. Zadeklaruj tę aktywność za pomocą obowiązkowej akcji intencji i schematu identyfikatora URI treści:

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

Obsługa intencji przeniesienia w aktywności

W CredentialExportActivity użyj IntentHandler.retrieveProviderImportCredentialsRequest(intent), aby przeanalizować żądanie, zweryfikować aplikację wywołującą i credId, przeprowadzić wymaganą uwierzytelnianie biometryczne i zapisać ładunek FIDO CXF z powrotem do podanego identyfikatora URI treści:

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

Wyjątki rejestracji eksportu i czyszczenia

Wszystkie wyjątki w tym module są podklasami ImportCredentialsException, RegisterExportException, or ClearExportException.

Implementowanie importera

Aby importować dane logowania do aplikacji (np. podczas wdrażania lub importowania dostawcy ), zaimplementuj rolę importera i zainicjuj proces, wywołując ProviderEventsManager.importCredentials().

Tworzenie żądania importu i uruchamianie procesu

Określ, które CredentialTypes i KnownExtensions są obsługiwane przez importera:

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

Wyjątki procesu importu

Wszystkie wyjątki w tym module są podklasami ImportCredentialsException, RegisterExportException, or ClearExportException.

Obsługiwane typy danych logowania i rozszerzenia

Obiekt androidx.credentials.providerevents.transfer.CredentialTypes definiuje standardowe stałe ciągi znaków odpowiadające typom elementów FIDO CXF:

Stała Wartość (typ cxf) Opis
CREDENTIAL_TYPE_BASIC_AUTH "basic-auth" Dane logowania z nazwą użytkownika i hasłem.
CREDENTIAL_TYPE_PUBLIC_KEY "passkey" Dane logowania z kluczem publicznym FIDO2 lub WebAuthn.
CREDENTIAL_TYPE_ADDRESS "address" Informacje o adresie pocztowym lub dostawy do automatycznego wypełniania formularzy.
CREDENTIAL_TYPE_API_KEY "api-key" Klucze i tokeny dostępu do interfejsu API.
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" Informacje o płatnościach kartą kredytową lub debetową.
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" Niestandardowe grupowania lub pola zdefiniowane przez użytkownika.
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" Szczegóły prawa jazdy.
CREDENTIAL_TYPE_FILE "file" Metadane i odniesienia do plików binarnych.
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" Bezpieczne hasła generowane przez maszynę.
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" Dowody osobiste, numery SSN, numery TIN lub paszporty.
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" Logiczne linki wskazujące inny element w ładunku.
CREDENTIAL_TYPE_NOTE "note" Bezpieczne notatki zdefiniowane przez użytkownika (ciąg znaków UTF-8).
CREDENTIAL_TYPE_PASSPORT "passport" Szczegóły dokumentu podróży – paszportu.
CREDENTIAL_TYPE_PERSON_NAME "person-name" Dane osobowe i informacje o nazwie.
CREDENTIAL_TYPE_SSH_KEY "ssh-key" Pary kluczy publicznych i prywatnych SSH.
CREDENTIAL_TYPE_TOTP "totp" Tajne kody jednorazowe oparte na czasie (2FA).
CREDENTIAL_TYPE_WIFI "wifi" Identyfikator SSID i hasła sieci Wi-Fi.

Niestandardowe dopasowania WASM (WebAssembly) (zaawansowane)

Domyślnie wywołanie RegisterExportRequest.create(context, entries) powoduje powiązanie domyślnego dla systemu pliku credential_transfer_matcher.wasm z zasobów biblioteki, który filtruje wpisy wyłącznie na podstawie przecięcia supportedCredentialTypes.

Jeśli dostawca danych logowania wymaga złożonej logiki dopasowywania (np. dynamicznych kontroli możliwości lub filtrowania warunkowego na podstawie pól niestandardowych), możesz skompilować własny moduł WebAssembly zgodny z interfejsem API przenoszenia danych logowania WASM i przekazać bezpośrednio surową tablicę bajtów:

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