Trasferimento delle credenziali

Le API di trasferimento delle credenziali di Credential Manager consentono il trasferimento sicuro delle credenziali utente tra i fornitori di credenziali sullo stesso dispositivo. Questa guida spiega in dettaglio come i fornitori di credenziali su Android possono integrarsi con le API fornite dalla androidx.credentials:providerevents libreria. Questa funzionalità supporta password, passkey, informazioni sull'indirizzo e campi personalizzati utilizzando il formato di scambio delle credenziali FIDO (CXF) standardizzato.

Concetti principali

Il framework di trasferimento delle credenziali facilita il trasferimento delle credenziali peer-to-peer sullo stesso dispositivo senza esporre le credenziali non elaborate al sistema operativo Android o alle app non autenticate.

Il framework definisce due ruoli principali:

  • Esportatore (fornitore di origine): un fornitore di credenziali che attualmente detiene le credenziali utente. Pre-registra i metadati relativi agli account esportabili disponibili (ExportEntry) con il sistema e risponde alle richieste di trasferimento quando selezionate dall'utente.
  • Importatore (fornitore client o procedura guidata di configurazione): un fornitore di credenziali o una procedura guidata di configurazione che avvia una richiesta di importazione (ImportCredentialsRequest) specificando i tipi di credenziali e di estensioni che può ricevere.

Compatibilità con la versione di Android

L'API di trasferimento delle credenziali funziona sui dispositivi con Android 8 (livello API 26) e versioni successive.

Aggiungi dipendenze

Aggiungi la dipendenza androidx.credentials:providerevents a build.gradle o build.gradle.kts del modulo:

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

Crea un'istanza della classe richiesta

Crea un'istanza di ProviderEventsManager richiesta.

val providerEventsManager = ProviderEventsManager.create(context)

Implementa l'esportatore

Per consentire agli utenti di esportare le credenziali dalla tua app ad altri fornitori di credenziali sul dispositivo, implementa il ruolo di esportatore.

Registra le voci di esportazione

Quando il fornitore di credenziali cambia (ad esempio, un utente esegue l'accesso, aggiunge credenziali o modifica gli account), registra o aggiorna gli elementi ExportEntry utilizzando ProviderEventsManager.registerExport().

Ogni ExportEntry richiede:

  • id: un identificatore di stringa segreto generato in modo casuale che rappresenta in modo univoco questa voce di esportazione. Devi conservare questo ID in modo sicuro; ti servirà in un secondo momento per verificare le richieste di trasferimento in entrata.
  • accountDisplayName: etichetta dell'account facoltativa (ad esempio, "Personal Account").
  • userDisplayName: l'identificatore principale dell'utente (ad esempio, "alice@example.com").
  • icon: un'icona Bitmap che rappresenta il fornitore o l'account (la libreria la ridimensiona automaticamente a PNG 32x32).
  • supportedCredentialTypes: un insieme di costanti stringa di CredentialTypes che rappresentano i tipi contenuti in questa voce.

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

Dichiara l'attività dell'esportatore nel file manifest

Quando l'utente seleziona ExportEntry nell'interfaccia utente del selettore di sistema, il sistema avvia l'Activity di gestione designata. Dichiara questa attività con l'azione intent obbligatoria e lo schema URI contenuto:

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

Gestisci l'intent di trasferimento nella tua attività

In CredentialExportActivity, utilizza IntentHandler.retrieveProviderImportCredentialsRequest(intent) per analizzare la richiesta, verificare l'app di chiamate e credId, eseguire l'autenticazione biometrica richiesta e scrivere il payload FIDO CXF nell'URI contenuto fornito:

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

Eccezioni di registrazione ed eliminazione dell'esportazione

Tutte le eccezioni in questo modulo sono sottoclassi di ImportCredentialsException, RegisterExportException o ClearExportException.

Implementa l'importatore

Per importare le credenziali nella tua app (ad esempio, durante l'onboarding o l'importazione del fornitore ), implementa il ruolo di importatore e avvia il flusso chiamando ProviderEventsManager.importCredentials().

Crea la richiesta di importazione e avvia il flusso

Specifica quali CredentialTypes e KnownExtensions supporta l'importatore:

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

Eccezioni del flusso di importazione

Tutte le eccezioni in questo modulo sono sottoclassi di ImportCredentialsException, RegisterExportException o ClearExportException.

Tipi di credenziali ed estensioni supportati

L'oggetto androidx.credentials.providerevents.transfer.CredentialTypes definisce costanti stringa standardizzate corrispondenti ai tipi di elementi FIDO CXF:

Costante Valore (tipo cxf) Descrizione
CREDENTIAL_TYPE_BASIC_AUTH "basic-auth" Credenziali di accesso con nome utente e password.
CREDENTIAL_TYPE_PUBLIC_KEY "passkey" Credenziali della chiave pubblica della passkey FIDO2 o WebAuthn.
CREDENTIAL_TYPE_ADDRESS "address" Informazioni sull'indirizzo postale o di spedizione per il completamento automatico dei moduli.
CREDENTIAL_TYPE_API_KEY "api-key" Chiavi e token di accesso alle API.
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" Dati di pagamento con carta di credito e di debito.
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" Raggruppamenti personalizzati o campi definiti dall'utente.
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" Dettagli della patente di guida.
CREDENTIAL_TYPE_FILE "file" Metadati e riferimenti ai segnaposto per i file binari.
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" Password sicure generate automaticamente.
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" Riferimenti a carte di identità nazionali, SSN, TIN o passaporti.
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" Link logici che rimandano a un altro elemento nel payload.
CREDENTIAL_TYPE_NOTE "note" Note sicure definite dall'utente (stringa UTF-8).
CREDENTIAL_TYPE_PASSPORT "passport" Dettagli del documento di viaggio del passaporto.
CREDENTIAL_TYPE_PERSON_NAME "person-name" Dettagli sull'identità e sul nome della persona.
CREDENTIAL_TYPE_SSH_KEY "ssh-key" Coppie di chiavi pubblica e privata SSH.
CREDENTIAL_TYPE_TOTP "totp" Segreti della password monouso basata sul tempo (2FA).
CREDENTIAL_TYPE_WIFI "wifi" SSID e passphrase della rete Wi-Fi.

Matcher WASM (WebAssembly) personalizzati (avanzati)

Per impostazione predefinita, se chiami RegisterExportRequest.create(context, entries), viene incluso credential_transfer_matcher.wasm predefinito del sistema dagli asset della libreria, che filtra le voci in base alla sola intersezione di supportedCredentialTypes.

Se il fornitore di credenziali richiede una logica di corrispondenza complessa (ad esempio, controlli dinamici delle funzionalità o filtri condizionali basati su campi personalizzati), puoi compilare il tuo modulo WebAssembly corrispondente all'API di trasferimento delle credenziali WASM e passare direttamente l'array di byte non elaborati:

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