Transfert d'identifiants

Les API de transfert d'identifiants du gestionnaire d'identifiants permettent de transférer de manière sécurisée les identifiants utilisateur entre les fournisseurs d'identifiants sur le même appareil. Ce guide explique comment les fournisseurs d'identifiants sur Android peuvent s'intégrer aux API fournies par la androidx.credentials:providerevents bibliothèque. Cette fonctionnalité est compatible avec les mots de passe, les clés d'accès, les informations d'adresse et les champs personnalisés à l'aide du format d'échange d'identifiants FIDO (CXF) standardisé.

Concepts fondamentaux

Le framework de transfert d'identifiants facilite le transfert d'identifiants peer-to-peer sur le même appareil sans exposer les identifiants bruts à l'OS Android ni aux applications non authentifiées.

Le framework définit deux rôles principaux :

  • Exportateur (fournisseur source) : fournisseur d'identifiants qui détient actuellement les identifiants utilisateur. Il pré-enregistre les métadonnées sur les comptes exportables disponibles (ExportEntry) auprès du système et répond aux demandes de transfert lorsqu'il est sélectionné par l'utilisateur.
  • Importateur (fournisseur client ou assistant de configuration) : fournisseur d'identifiants ou assistant de configuration qui lance une demande d'importation (ImportCredentialsRequest) spécifiant les types d'identifiants et d'extensions qu'il peut recevoir.

Compatibilité avec les versions d'Android

L'API de transfert d'identifiants fonctionne sur les appareils équipés d'Android 8 (niveau d'API 26) ou version ultérieure.

Ajouter des dépendances

Ajoutez la dépendance androidx.credentials:providerevents à build.gradle ou build.gradle.kts de votre module :

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

Instancier la classe requise

Créez une instance de ProviderEventsManager requise.

val providerEventsManager = ProviderEventsManager.create(context)

Implémenter l'exportateur

Pour permettre aux utilisateurs d'exporter des identifiants de votre application vers d'autres fournisseurs d'identifiants sur l'appareil, implémentez le rôle d'exportateur.

Enregistrer les entrées d'exportation

Lorsque votre fournisseur d'identifiants change (par exemple, lorsqu'un utilisateur se connecte, ajoute des identifiants ou modifie des comptes), enregistrez ou mettez à jour vos éléments ExportEntry à l'aide de ProviderEventsManager.registerExport().

Chaque ExportEntry nécessite les éléments suivants :

  • id: identifiant de chaîne secret généré de manière aléatoire qui représente de manière unique cette entrée d'exportation. Vous devez conserver cet ID de manière sécurisée ; vous en aurez besoin ultérieurement pour vérifier les demandes de transfert entrantes.
  • accountDisplayName : libellé de compte facultatif (par exemple, "Personal Account").
  • userDisplayName : identifiant principal de l'utilisateur (par exemple, "alice@example.com").
  • icon: icône Bitmap représentant le fournisseur ou le compte (mise à l'échelle automatique au format PNG 32x32 par la bibliothèque).
  • supportedCredentialTypes : ensemble de constantes de chaîne de CredentialTypes représentant les types contenus dans cette entrée.

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

Déclarer l'activité de l'exportateur dans le fichier manifeste

Lorsque l'utilisateur sélectionne votre ExportEntry dans l'interface utilisateur du sélecteur système, le système lance votre Activity de gestion désignée. Déclarez cette activité avec l'action d'intent obligatoire et le schéma d'URI de contenu :

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

Gérer l'intent de transfert dans votre activité

Dans votre CredentialExportActivity, utilisez IntentHandler.retrieveProviderImportCredentialsRequest(intent) pour analyser la requête, vérifier l'application appelante et credId, effectuer toute authentification biométrique requise et réécrire la charge utile FIDO CXF dans l'URI de contenu fourni :

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

Exceptions d'enregistrement et d'effacement d'exportation

Toutes les exceptions de ce module sont des sous-classes de ImportCredentialsException, RegisterExportException ou ClearExportException.

Implémenter l'importateur

Pour importer des identifiants dans votre application (par exemple, lors de l'intégration ou de l'importation de fournisseurs), implémentez le rôle d'importateur et lancez le flux en appelant ProviderEventsManager.importCredentials().

Construire la requête d'importation et lancer le flux

Spécifiez les CredentialTypes et KnownExtensions compatibles avec votre importateur :

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

Exceptions de flux d'importation

Toutes les exceptions de ce module sont des sous-classes de ImportCredentialsException, RegisterExportException ou ClearExportException.

Types d'identifiants et extensions compatibles

L'objet androidx.credentials.providerevents.transfer.CredentialTypes définit des constantes de chaîne standardisées correspondant aux types d'éléments FIDO CXF :

Constante Valeur (type cxf) Description
CREDENTIAL_TYPE_BASIC_AUTH "basic-auth" Identifiants de connexion (nom d'utilisateur et mot de passe).
CREDENTIAL_TYPE_PUBLIC_KEY "passkey" Identifiants de clé publique de clé d'accès FIDO2 ou WebAuthn.
CREDENTIAL_TYPE_ADDRESS "address" Informations sur l'adresse postale ou de livraison pour le remplissage automatique des formulaires.
CREDENTIAL_TYPE_API_KEY "api-key" Clés et jetons d'accès à l'API.
CREDENTIAL_TYPE_CREDIT_CARD "credit-card" Informations de paiement par carte de crédit et de débit.
CREDENTIAL_TYPE_CUSTOM_FIELDS "custom-fields" Groupements personnalisés ou champs définis par l'utilisateur.
CREDENTIAL_TYPE_DRIVERS_LICENSE "drivers-license" Informations sur le permis de conduire.
CREDENTIAL_TYPE_FILE "file" Métadonnées et références d'espace réservé pour les fichiers binaires.
CREDENTIAL_TYPE_GENERATED_PASSWORD "generated-password" Mots de passe sécurisés générés par une machine.
CREDENTIAL_TYPE_IDENTITY_DOCUMENT "identity-document" Références de carte nationale d'identité, de numéro de sécurité sociale, de numéro d'identification fiscale ou de passeport.
CREDENTIAL_TYPE_ITEM_REFERENCE "item-reference" Liens logiques pointant vers un autre élément de la charge utile.
CREDENTIAL_TYPE_NOTE "note" Notes sécurisées définies par l'utilisateur (chaîne UTF-8).
CREDENTIAL_TYPE_PASSPORT "passport" Informations sur le document de voyage (passeport).
CREDENTIAL_TYPE_PERSON_NAME "person-name" Informations sur l'identité et le nom d'une personne.
CREDENTIAL_TYPE_SSH_KEY "ssh-key" Paires de clés publique et privée SSH.
CREDENTIAL_TYPE_TOTP "totp" Secrets de mot de passe à usage unique basé sur le temps (2FA).
CREDENTIAL_TYPE_WIFI "wifi" SSID et phrases secrètes du réseau Wi-Fi.

Correspondances WASM (WebAssembly) personnalisées (avancé)

Par défaut, l'appel de RegisterExportRequest.create(context, entries) regroupe le credential_transfer_matcher.wasm par défaut du système à partir des éléments de la bibliothèque, qui filtre les entrées uniquement en fonction de l'intersection de supportedCredentialTypes.

Si votre fournisseur d'identifiants nécessite une logique de correspondance complexe (par exemple, des vérifications de capacités dynamiques ou un filtrage conditionnel basé sur des champs personnalisés), vous pouvez compiler votre propre module WebAssembly correspondant à l'API de transfert d'identifiants WASM et transmettre directement le tableau d'octets bruts :

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