L'API Credential Manager Holder permet à votre application Android de détenteur (également appelée "portefeuille") de gérer et de présenter des identifiants numériques aux outils de validation.
Concepts fondamentaux
Il est important de vous familiariser avec les concepts suivants avant d'utiliser l'API Holder.
Formats des identifiants
Les identifiants peuvent être stockés dans des applications de portefeuille sous différents formats. Ces formats sont des spécifications sur la façon dont un identifiant doit être représenté. Chacun d'eux contient les informations suivantes sur l'identifiant :
- Type : catégorie, par exemple diplôme universitaire ou permis de conduire mobile.
- Propriétés : attributs tels que le prénom et le nom.
- Encodage : la façon dont l'identifiant est structuré, par exemple SD-JWT ou mdoc
- Validité : méthode permettant de vérifier l'authenticité des identifiants de manière cryptographique.
Chaque format d'identifiant effectue l'encodage et la validation de manière légèrement différente, mais ils sont fonctionnellement identiques.
Le registre est compatible avec deux formats :
- SD-JWT : conforme à la spécification IETF SD-JWT-based Verifiable Credentials (SD-JWT VC).
- Les documents mobiles ou mdocs sont conformes à la spécification ISO/IEC 18013-5:2021.
Un vérificateur peut effectuer une requête OpenID4VP pour SD-JWT et mdocs lorsqu'il utilise Credential Manager. Le choix varie en fonction du cas d'utilisation et du secteur.
Enregistrement des métadonnées des identifiants
Le Gestionnaire d'identifiants ne stocke pas directement les identifiants d'un titulaire, mais plutôt les métadonnées des identifiants. Une application de portefeuille doit d'abord enregistrer les métadonnées des identifiants auprès du Gestionnaire d'identifiants à l'aide de RegistryManager. Ce processus d'enregistrement crée un enregistrement dans le registre, qui remplit deux fonctions clés :
- Mise en correspondance : les métadonnées d'identifiants enregistrées sont utilisées pour établir une correspondance avec les futures demandes de validation.
- Affichage : des éléments d'UI personnalisés sont présentés à l'utilisateur dans l'interface du sélecteur d'identifiants.
Vous utiliserez la classe OpenId4VpRegistry pour enregistrer vos identifiants numériques, car elle est compatible avec les formats d'identifiants mdoc et SD-JWT. Les validateurs enverront des requêtes OpenID4VP pour demander ces identifiants.
Enregistrer les identifiants de votre application
Pour utiliser l'API Credential Manager Holder, ajoutez les dépendances suivantes au script de compilation du module de votre application :
Groovy
dependencies { // Use to implement credentials registrys implementation "androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha04" implementation "androidx.credentials.registry:registry-digitalcredentials-preview:1.0.0-alpha04" implementation "androidx.credentials.registry:registry-provider:1.0.0-alpha04" implementation "androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha04" }
Kotlin
dependencies { // Use to implement credentials registrys implementation("androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha04") implementation("androidx.credentials.registry:registry-digitalcredentials-preview:1.0.0-alpha04") implementation("androidx.credentials.registry:registry-provider:1.0.0-alpha04") implementation("androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha04") }
Créer le RegistryManager
Créez une instance RegistryManager et enregistrez une requête OpenId4VpRegistry avec celle-ci.
// Create the registry manager
val registryManager = RegistryManager.create(context)
// The guide covers how to build this out later
val registryRequest = OpenId4VpRegistry(credentialEntries, id)
try {
registryManager.registerCredentials(registryRequest)
} catch (e: Exception) {
// Handle exceptions
}
Créer une requête OpenId4VpRegistry
Comme indiqué précédemment, vous devrez enregistrer un OpenId4VpRegistry pour gérer une demande OpenID4VP d'un vérificateur. Nous partons du principe que vous avez chargé certains types de données locales avec vos identifiants Wallet (par exemple, sdJwtsFromStorage). Vous allez maintenant les convertir en équivalents Jetpack DigitalCredentialEntry en fonction de leur format : SdJwtEntry ou MdocEntry pour SD-JWT ou mdoc, respectivement.
Ajouter des Sd-JWT au registre
Associez chaque identifiant SD-JWT local à un SdJwtEntry pour le registre :
fun mapToSdJwtEntries(sdJwtsFromStorage: List<StoredSdJwtEntry>): List<SdJwtEntry> {
val list = mutableListOf<SdJwtEntry>()
for (sdJwt in sdJwtsFromStorage) {
list.add(
SdJwtEntry(
verifiableCredentialType = sdJwt.getVCT(),
claims = sdJwt.getClaimsList(),
entryDisplayPropertySet = sdJwt.toDisplayProperties(),
id = sdJwt.getId() // Make sure this cannot be readily guessed
)
)
}
return list
}
Ajouter des fichiers mdocs au registre
Mappez vos identifiants mdoc locaux dans le type Jetpack MdocEntry :
fun mapToMdocEntries(mdocsFromStorage: List<StoredMdocEntry>): List<MdocEntry> {
val list = mutableListOf<MdocEntry>()
for (mdoc in mdocsFromStorage) {
list.add(
MdocEntry(
docType = mdoc.retrieveDocType(),
fields = mdoc.getFields(),
entryDisplayPropertySet = mdoc.toDisplayProperties(),
id = mdoc.getId() // Make sure this cannot be readily guessed
)
)
}
return list
}
Points clés concernant le code
- Pour configurer le champ
id, vous pouvez enregistrer un identifiant d'identifiant chiffré afin que vous seul puissiez déchiffrer la valeur. - Les champs d'affichage de l'UI pour les deux formats doivent être localisés.
Enregistrer vos identifiants
Combinez vos entrées converties et enregistrez la requête avec RegistryManager :
val credentialEntries = mapToSdJwtEntries(sdJwtsFromStorage) + mapToMdocEntries(mdocsFromStorage)
val openidRegistryRequest = OpenId4VpRegistry(
credentialEntries = credentialEntries,
id = "my-wallet-openid-registry-v1" // A stable, unique ID to identify your registry record.
)
Nous sommes maintenant prêts à enregistrer vos identifiants avec CredentialManager.
try {
val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
// Handle failure
}
Vous avez maintenant enregistré vos identifiants avec Credential Manager.
Gestion des métadonnées de l'application
Les métadonnées que votre application de portefeuille enregistre auprès de CredentialManager présentent les propriétés suivantes :
- Persistance : les informations sont enregistrées localement et persistent après les redémarrages.
- Stockage cloisonné : les enregistrements du registre de chaque application sont stockés séparément, ce qui signifie qu'une application ne peut pas modifier les enregistrements du registre d'une autre application.
- Mises à jour avec clé : les enregistrements du registre de chaque application sont associés à un
id, ce qui permet de réidentifier, de mettre à jour ou de supprimer des enregistrements. - Mise à jour des métadonnées : nous vous recommandons de mettre à jour les métadonnées persistantes chaque fois que votre application change ou est chargée pour la première fois. Si un registre est appelé plusieurs fois sous le même
id, le dernier appel écrase tous les enregistrements précédents. Pour mettre à jour l'enregistrement, réenregistrez-le sans avoir à effacer l'ancien enregistrement au préalable.
Facultatif : Créer un outil de mise en correspondance
Un comparateur est un binaire Wasm que le Gestionnaire d'identifiants exécute dans un bac à sable pour filtrer vos identifiants enregistrés par rapport à une demande de validation entrante.
- Comparateur par défaut : la classe
OpenId4VpRegistryinclut automatiquement le comparateurOpenId4VPpar défaut (OpenId4VpDefaults.DEFAULT_MATCHER) lorsque vous l'instanciez. Pour tous les cas d'utilisation OpenID4VP standards, la bibliothèque gère la mise en correspondance pour vous. - Matcher personnalisé : vous n'implémenterez un matcher personnalisé que si vous utilisez un protocole non standard qui nécessite sa propre logique de correspondance.
Gérer un identifiant sélectionné
Lorsqu'un utilisateur sélectionne un identifiant, votre application de portefeuille doit traiter la demande.
Vous devrez définir une activité qui écoute le filtre d'intent androidx.credentials.registry.provider.action.GET_CREDENTIAL.
Notre exemple de portefeuille illustre cette procédure.
L'intent lance votre activité avec la requête de l'outil de validation et l'origine de l'appel, que vous extrayez avec la fonction PendingIntentHandler.retrieveProviderGetCredentialRequest. Cette méthode renvoie un ProviderGetCredentialRequest contenant toutes les informations associées à la demande de l'outil de validation. Il se compose de trois éléments clés :
- L'application appelante : application qui a effectué la requête, récupérable avec
getCallingAppInfo. - L'identifiant sélectionné : informations sur le candidat choisi par l'utilisateur, récupérées via
selectedCredentialSet extension method. Il doit correspondre à l'ID d'identifiant que vous avez enregistré. - Demandes spécifiques : demande spécifique formulée par le validateur, récupérée à partir de la méthode
getCredentialOptions. Pour un flux de demande d'identifiants numériques, vous devriez trouver un seulGetDigitalCredentialOptiondans cette liste.
Le plus souvent, l'outil de validation envoie une demande de présentation d'identifiant numérique. Vous pouvez la traiter avec l'exemple de code suivant :
request.credentialOptions.forEach { option ->
if (option is GetDigitalCredentialOption) {
Log.i(TAG, "Got DC request: ${option.requestJson}")
processRequest(option.requestJson)
}
}
Vous trouverez un exemple de cela dans l'exemple de portefeuille.
Vérifier l'identité du valideur
- Extrayez
ProviderGetCredentialRequestde l'intent :
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
- Rechercher une origine privilégiée : les applications privilégiées (comme les navigateurs Web) peuvent passer des appels au nom d'autres validateurs en définissant le paramètre d'origine. Pour récupérer cette origine, vous devez transmettre une liste d'appelants privilégiés et approuvés (une liste d'autorisation au format JSON) à l'API
getOrigin()deCallingAppInfo.
val origin = request?.callingAppInfo?.getOrigin(
privilegedAppsJson // Your allow list JSON
)
Si l'origine n'est pas vide : l'origine est renvoyée si packageName et les empreintes du certificat obtenues à partir de signingInfo correspondent à celles d'une application trouvée dans la liste d'autorisation transmise à l'API getOrigin(). Une fois la valeur d'origine obtenue, l'application du fournisseur doit considérer cet appel comme privilégié et définir cette origine sur la réponse OpenID4VP, au lieu de calculer l'origine à l'aide de la signature de l'application appelante.
Le Gestionnaire de mots de passe de Google utilise une liste d'autorisation ouverte pour les appels vers getOrigin(). En tant que fournisseur d'identifiants, vous pouvez utiliser cette liste ou fournir la vôtre au format JSON décrit par l'API. C'est au fournisseur de sélectionner la liste à utiliser. Pour obtenir un accès privilégié avec des fournisseurs d'identifiants tiers, reportez-vous à la documentation fournie par le tiers.
Si l'origine est vide,la demande de validation provient d'une application Android. L'origine de l'application à inclure dans la réponse OpenID4VP doit être calculée comme suit : android:apk-key-hash:<encoded SHA 256 fingerprint>.
val appSigningInfo = request?.callingAppInfo?.signingInfoCompat?.signingCertificateHistory[0]?.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val certHash = Base64.encodeToString(md.digest(appSigningInfo), Base64.NO_WRAP or Base64.NO_PADDING)
return "android:apk-key-hash:$certHash"
Afficher l'UI du support
Lorsqu'un identifiant est sélectionné, l'application du détenteur est appelée, guidant l'utilisateur dans l'interface utilisateur de l'application. Il existe deux façons standards de gérer ce workflow :
- Si une authentification utilisateur supplémentaire est nécessaire pour libérer l'identifiant, utilisez l'API BiometricPrompt. C'est ce que montre l'exemple.
- Sinon, de nombreux portefeuilles optent pour un retour silencieux en affichant une activité vide qui renvoie immédiatement les données à l'application appelante. Cela minimise les clics de l'utilisateur et offre une expérience plus fluide.
Renvoyer la réponse d'identifiant
Une fois que votre application de portefeuille est prête à renvoyer le résultat, terminez l'activité avec la réponse d'identifiant :
PendingIntentHandler.setGetCredentialResponse(
resultData,
GetCredentialResponse(DigitalCredential(response.responseJson))
)
setResult(RESULT_OK, resultData)
finish()
En cas d'exception, vous pouvez également envoyer l'exception d'identifiant :
PendingIntentHandler.setGetCredentialException(
resultData,
GetCredentialUnknownException() // Configure the proper exception
)
setResult(RESULT_OK, resultData)
finish()
Consultez l'application exemple pour obtenir un exemple complet de renvoi de la réponse d'identifiant dans le contexte.