Transferência de credenciais

As APIs de transferência de credenciais do Credential Manager permitem a transferência segura de credenciais do usuário entre provedores de credenciais no mesmo dispositivo. Este guia detalha como os provedores de credenciais no Android podem se integrar às APIs fornecidas pela androidx.credentials:providerevents biblioteca. Esse recurso oferece suporte a senhas, chaves de acesso, informações de endereço e campos personalizados usando o formato de troca de credenciais FIDO (CXF, na sigla em inglês) padronizado.

Principais conceitos

A estrutura de transferência de credenciais facilita a transferência de credenciais ponto a ponto no mesmo dispositivo sem expor credenciais brutas ao SO Android ou a apps não autenticados.

A estrutura define duas funções principais:

  • Exportador (provedor de origem) : um provedor de credenciais que atualmente tem credenciais do usuário. Ele pré-registra metadados sobre contas exportáveis disponíveis (ExportEntry) com o sistema e responde a solicitações de transferência quando selecionado pelo usuário.
  • Importador (provedor de cliente ou assistente de configuração): um provedor de credenciais ou assistente de configuração que inicia uma solicitação de importação (ImportCredentialsRequest) especificando os tipos de credenciais e extensões que ele pode receber.

Compatibilidade com a versão do Android

A API Credentials Transfer funciona em dispositivos com o Android 8 (nível 26 da API) e versões mais recentes.

Adicionar dependências

Adicione a dependência androidx.credentials:providerevents ao build.gradle ou build.gradle.kts do módulo:

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

Instanciar a classe necessária

Crie uma instância da ProviderEventsManager necessária.

val providerEventsManager = ProviderEventsManager.create(context)

Implementar o exportador

Para permitir que os usuários exportem credenciais do seu app para outros provedores de credenciais no dispositivo, implemente a função de exportador.

Registrar entradas de exportação

Quando o provedor de credenciais muda (por exemplo, um usuário faz login, adiciona credenciais ou modifica contas), registre ou atualize os itens ExportEntry usando ProviderEventsManager.registerExport().

Cada ExportEntry exige:

  • id: um identificador de string secreto e gerado aleatoriamente que representa exclusivamente essa entrada de exportação. É necessário manter esse ID em segurança ; ele será necessário mais tarde para verificar as solicitações de transferência recebidas.
  • accountDisplayName: rótulo da conta opcional (por exemplo, "Personal Account").
  • userDisplayName: o identificador principal do usuário (por exemplo, "alice@example.com").
  • icon: um ícone Bitmap que representa o provedor ou a conta (escalonado automaticamente para PNG de 32 x 32 pela biblioteca).
  • supportedCredentialTypes: um conjunto de constantes de string de CredentialTypes que representam os tipos que essa entrada contém.

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

Declarar a atividade do exportador no arquivo de manifesto

Quando o usuário seleciona ExportEntry na interface do seletor do sistema, o sistema inicia a Activity de processamento designada. Declare essa Activity com a ação da intent e o esquema de URI de conteúdo obrigatórios:

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

Processar a intent de transferência na sua atividade

Na CredentialExportActivity, use IntentHandler.retrieveProviderImportCredentialsRequest(intent) para analisar a solicitação, verificar o app de chamada e o credId, realizar qualquer autenticação biométrica necessária e gravar o payload do FIDO CXF de volta no URI de conteúdo fornecido:

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

Exceções de registro e limpeza de exportação

Todas as exceções nesse módulo são subclasses de ImportCredentialsException, RegisterExportException ou ClearExportException.

Implementar o importador

Para importar credenciais para seu app (por exemplo, durante a integração ou a importação do provedor ), implemente a função de importador e inicie o fluxo chamando ProviderEventsManager.importCredentials().

Criar a solicitação de importação e iniciar o fluxo

Especifique quais CredentialTypes e KnownExtensions o importador oferece suporte:

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

Exceções de fluxo de importação

Todas as exceções nesse módulo são subclasses de ImportCredentialsException, RegisterExportException ou ClearExportException.

Tipos e extensões de credenciais compatíveis

O objeto androidx.credentials.providerevents.transfer.CredentialTypes define constantes de string padronizadas correspondentes aos tipos de item FIDO CXF:

Constante Valor (tipo cxf) Descrição
CREDENTIAL_TYPE_BASIC_AUTH "basic-auth" Credenciais de login de nome de usuário e senha.
CREDENTIAL_TYPE_PUBLIC_KEY "passkey" Credenciais de chave pública de chave de acesso FIDO2 ou WebAuthn.
CREDENTIAL_TYPE_ADDRESS "address" Informações de endereço postal ou de envio para preenchimento automático de formulários.
CREDENTIAL_TYPE_API_KEY "api-key" Chaves e tokens de acesso à API.
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" Informações de pagamento com cartão de crédito e débito.
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" Agrupamentos personalizados ou campos definidos pelo usuário.
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" Detalhes da carteira de habilitação.
CREDENTIAL_TYPE_FILE "file" Metadados e referências de marcador para arquivos binários.
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" Senhas seguras geradas por máquina.
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" Documentos de identidade nacional, SSN, TIN ou referências de passaporte.
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" Links lógicos que apontam para outro item no payload.
CREDENTIAL_TYPE_NOTE "note" Notas seguras definidas pelo usuário (string UTF-8).
CREDENTIAL_TYPE_PASSPORT "passport" Detalhes do documento de viagem do passaporte.
CREDENTIAL_TYPE_PERSON_NAME "person-name" Detalhes de identidade e nome da pessoa.
CREDENTIAL_TYPE_SSH_KEY "ssh-key" Pares de chaves SSH públicas e privadas.
CREDENTIAL_TYPE_TOTP "totp" Secrets de senha única baseada em tempo (2FA).
CREDENTIAL_TYPE_WIFI "wifi" SSID e senhas de rede Wi-Fi.

Matchers WASM (WebAssembly) personalizados (avançado)

Por padrão, chamar RegisterExportRequest.create(context, entries) agrupa o credential_transfer_matcher.wasm padrão do sistema dos recursos da biblioteca, que filtra as entradas com base na interseção de supportedCredentialTypes.

Se o provedor de credenciais exigir uma lógica de correspondência complexa (por exemplo, verificações de capacidade dinâmica ou filtragem condicional com base em campos personalizados), você poderá compilar seu próprio módulo WebAssembly que corresponda à API de transferência de credenciais WASM e transmitir a matriz de bytes bruta diretamente:

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