L'API Credential Manager Holder consente alla tua app di gestione Android (chiamata anche "wallet") di gestire e presentare le credenziali digitali ai verificatori. Questa guida descrive come registrare le credenziali della tua app titolare con Gestore delle credenziali e come gestire una richiesta di presentazione quando un utente seleziona una credenziale. L'API Holder è supportata su Android 6 (livello API 23) e versioni successive.
Aggiungi dipendenze
Per utilizzare l'API Holder di Gestore delle credenziali, aggiungi le seguenti dipendenze allo script build.gradle del modulo dell'app:
dependencies {
// Used to implement credential registries
implementation "androidx.credentials.registry:registry-digitalcredentials-mdoc:1.0.0-alpha05"
implementation "androidx.credentials.registry:registry-digitalcredentials-openid:1.0.0-alpha05"
implementation "androidx.credentials.registry:registry-digitalcredentials-sdjwtvc:1.0.0-alpha05"
implementation "androidx.credentials.registry:registry-provider:1.0.0-alpha05"
implementation "androidx.credentials.registry:registry-provider-play-services:1.0.0-alpha05"
}
Registra le tue credenziali
Innanzitutto, crea un'istanza RegistryManager che utilizzerai per registrare una richiesta OpenId4VpRegistry.
// Create the registry manager
val registryManager = RegistryManager.create(context)
Crea una richiesta OpenId4VpRegistry
Devi registrare un OpenId4VpRegistry per gestire le richieste OpenID4VP dei verificatori. Supponendo che tu abbia tipi di dati locali caricati con le credenziali del tuo wallet (ad es. sdJwtsFromStorage e mdocsFromStorage), devi convertirli nei loro equivalenti Jetpack DigitalCredentialEntry: SdJwtEntry e MdocEntry.
Aggiungi SD-JWT al registry
Mappa ogni credenziale SD-JWT locale a un SdJwtEntry per il registry:
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
}
Aggiungi mdoc al Registry
Mappa le tue credenziali mdoc locali nel tipo 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
}
Punti chiave sul codice
- ID criptati: un metodo per configurare il campo
IDconsiste nel registrare un identificatore di credenziali criptato, in modo che solo tu possa decriptare il valore. - Localizzazione: i campi di visualizzazione della UI per entrambi i formati devono essere localizzati.
Combina e registra la tua richiesta
Combina le voci convertite e registra la richiesta con 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.
)
(Facoltativo) Emissione in linea
Se per un utente non sono disponibili credenziali esistenti di un tipo richiesto, puoi visualizzare un'opzione per aggiungere una credenziale al tuo holder. Per farlo, aggiungi un elenco inlineIssuanceEntries contenente le credenziali supportate al tuo OpenId4VpRegistry:
val openidRegistryRequest = OpenId4VpRegistry(
credentialEntries = credentialEntries,
inlineIssuanceEntries = emptyList(),
listOf(
MdocInlineIssuanceEntry(
id = "Issuance",
display = InlineIssuanceEntry.InlineIssuanceDisplayProperties(
subtitle = "Mobile Drivers License, State Id, and Others",
),
supportedMdocs = setOf(
MdocInlineIssuanceEntry.SupportedMdoc(
"eu.europa.ec.eudi.pid.1"
),
MdocInlineIssuanceEntry.SupportedMdoc(
"org.iso.18013.5.1.mDL1"
),
)
),
SdJwtInlineIssuanceEntry(
id = "sd-jwt-issuance",
display = InlineIssuanceEntry.InlineIssuanceDisplayProperties(
subtitle = "Mobile Drivers License, State Id, and Others",
),
supportedSdJwts = setOf(
SdJwtInlineIssuanceEntry.SupportedSdJwt("urn:openid:interop:id:1")
)
)
),
id = "my-wallet-openid-registry-v1"
)
Registra le tue credenziali con Gestore delle credenziali.
try {
val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
// Handle failure
}
Gestione dei metadati delle app
I metadati registrati dall'app del titolare con CredentialManager hanno le seguenti proprietà:
- Persistenza: le informazioni vengono salvate localmente e persistono anche dopo i riavvii.
- Archiviazione in silo: i record del registro di ogni app vengono archiviati separatamente, il che significa che un'app non può modificare i record del registro di un'altra app.
- Aggiornamenti con chiave: i record del registry di ogni app sono associati a una chiave
id, che consente di identificare, aggiornare o eliminare nuovamente i record. - Aggiornamento dei metadati: è buona norma aggiornare i metadati persistenti
ogni volta che l'app cambia o viene caricata per la prima volta. Se un registry viene chiamato più volte con lo stesso
id, l'ultima chiamata sovrascrive tutti i record precedenti. Per eseguire l'aggiornamento, registrati di nuovo senza dover prima cancellare il record precedente.
(Facoltativo) Crea un matcher
Un matcher è un file binario Wasm che Gestore delle credenziali esegue in una sandbox per filtrare le credenziali registrate in base a una richiesta di verifica in arrivo.
- Matcher predefinito: la classe
OpenId4VpRegistryinclude automaticamente il matcherOpenId4VPpredefinito (OpenId4VpDefaults.DEFAULT_MATCHER) quando ne crei un'istanza. Per tutti i casi d'uso standard di OpenID4VP, la libreria gestisce la corrispondenza per te. - Matcher personalizzato: implementa un matcher personalizzato solo se supporti un protocollo non standard che richiede una propria logica di corrispondenza.
Gestisci una credenziale selezionata
Quando un utente seleziona una credenziale, l'app di archiviazione deve gestire la richiesta.
Dovrai definire un'attività che ascolta il filtro per intent androidx.credentials.registry.provider.action.GET_CREDENTIAL.
Il nostro wallet di esempio mostra questa procedura.
L'intent avvia la tua attività con la richiesta di Verifier e l'origine della chiamata, che estrai con la
PendingIntentHandler.retrieveProviderGetCredentialRequest
funzione. Viene restituito un oggetto ProviderGetCredentialRequest
contenente tutte le informazioni associate alla richiesta del verificatore. I componenti chiave sono tre:
- L'app chiamante: l'app che ha effettuato la richiesta, recuperabile con
getCallingAppInfo. Puoi utilizzarlo per verificare l'identità del verificatore. - La credenziale selezionata: informazioni sul candidato scelto dall'utente, recuperate tramite
selectedCredentialSet extension method; corrisponderà all'ID credenziale che hai registrato. - Richieste specifiche: la richiesta specifica effettuata dal verificatore, recuperata dal metodo
getCredentialOptions. Per un flusso di richiesta di credenziali digitali, dovresti trovare un singoloGetDigitalCredentialOptionin questo elenco.
Puoi elaborare la richiesta di presentazione con il seguente codice:
request.credentialOptions.forEach { option ->
if (option is GetDigitalCredentialOption) {
Log.i(TAG, "Got DC request: ${option.requestJson}")
processRequest(option.requestJson)
}
}
Un esempio è disponibile nel wallet di esempio.
Controlla l'identità del verificatore
Innanzitutto, estrai ProviderGetCredentialRequest dall'intent:
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
Controlla la presenza di app con privilegi: le app con privilegi (come i browser web) possono effettuare chiamate per conto di altri verificatori impostando il parametro di origine. Per recuperare questa origine, passa un elenco di chiamanti con privilegi e attendibili (una lista consentita in formato JSON) all'API getOrigin CallingAppInfo.
val origin = request?.callingAppInfo?.getOrigin(
privilegedAppsJson // Your allow list JSON
)
Se origin non è vuoto: l'origine viene restituita se packageName e i fingerprint del certificato corrispondono a un'app nella tua lista consentita. Considera questa una chiamata con privilegi e imposta questa origine sulla risposta OpenID4VP anziché calcolarla utilizzando la firma dell'app chiamante.
Se origin è vuoto: la richiesta del verificatore proviene da un'app per Android. L'origine mappata alla risposta OpenID4VP deve essere calcolata come android:apk-key-hash:<encoded SHA 256 fingerprint>.
val appSigningInfo =
request?.callingAppInfo?.signingInfoCompat?.signingCertificateHistory?.get(0)?.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val certHash =
Base64.encodeToString(md.digest(appSigningInfo), Base64.NO_WRAP or Base64.NO_PADDING)
val computedOrigin = "android:apk-key-hash:$certHash"
Eseguire il rendering della UI del titolare
Quando viene selezionata una credenziale, viene richiamata l'app del titolare, che guida l'utente attraverso la sua UI. Esistono due modi standard per gestire questo flusso di lavoro:
- Se è necessaria un'ulteriore autenticazione dell'utente per rilasciare la credenziale, utilizza l'API BiometricPrompt. Questo è dimostrato nell'esempio.
- In caso contrario, molti wallet optano per un ritorno silenzioso eseguendo il rendering di un'attività vuota che restituisce immediatamente i dati all'app chiamante. In questo modo, i clic dell'utente vengono ridotti al minimo e l'esperienza è più fluida.
Restituisci la risposta della credenziale
Una volta che l'app del titolare è pronta per inviare il risultato, termina l'attività con la risposta della credenziale:
PendingIntentHandler.setGetCredentialResponse(
resultData,
GetCredentialResponse(DigitalCredential(response.responseJson))
)
setResult(RESULT_OK, resultData)
finish()
Se si verifica un'eccezione, puoi inviare l'eccezione della credenziale in modo simile:
PendingIntentHandler.setGetCredentialException(
resultData,
GetCredentialUnknownException() // Configure the proper exception
)
setResult(RESULT_OK, resultData)
finish()
Consulta l'app di esempio per un esempio completo di restituzione della risposta della credenziale nel contesto.