Integrare l'app Holder con Credential Manager

L'API Credential Manager Holder consente all'app holder (chiamata anche "wallet") per Android di gestire e presentare le credenziali digitali ai verificatori.

Immagine che mostra l'interfaccia utente delle credenziali digitali in Credential Manager
Figura 1. L'interfaccia utente del selettore di credenziali digitali.

Concetti principali

Prima di utilizzare l'API Holder, è importante acquisire familiarità con i seguenti concetti.

Formati delle credenziali

Le credenziali possono essere memorizzate nelle app di archiviazione in diversi formati. Questi formati sono specifiche su come deve essere rappresentata una credenziale e ognuno contiene le seguenti informazioni sulla credenziale:

  • Tipo:la categoria, ad esempio una laurea o una patente di guida digitale.
  • Proprietà:attributi come nome e cognome.
  • Codifica:il modo in cui è strutturata la credenziale, ad esempio SD-JWT o mdoc
  • Validità:metodo per verificare crittograficamente l'autenticità della credenziale.

Ogni formato delle credenziali esegue la codifica e la convalida in modo leggermente diverso, ma a livello funzionale sono uguali.

Il registro supporta due formati:

Un verificatore può effettuare una richiesta OpenID4VP per SD-JWT e mdoc quando utilizza Credential Manager. La scelta varia in base al caso d'uso e al settore.

Registrazione dei metadati delle credenziali

Gestore delle credenziali non memorizza direttamente le credenziali di un titolare, ma i metadati delle credenziali. Un'app titolare deve prima registrare i metadati delle credenziali con Credential Manager utilizzando RegistryManager. Questa procedura di registrazione crea un record del registro che ha due scopi principali:

  • Corrispondenza:i metadati delle credenziali registrate vengono utilizzati per la corrispondenza con le future richieste di verifica.
  • Display:gli elementi della UI personalizzati vengono mostrati all'utente nell'interfaccia del selettore delle credenziali.

Utilizzerai la classe OpenId4VpRegistry per registrare le tue credenziali digitali, in quanto supporta i formati delle credenziali mdoc e SD-JWT. I verificatori invieranno richieste OpenID4VP per richiedere queste credenziali.

Registrare le credenziali dell'app

Per utilizzare l'API Credential Manager Holder, aggiungi le seguenti dipendenze allo script di build del modulo dell'app:

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

}

Crea RegistryManager

Crea un'istanza RegistryManager e registra una richiesta OpenId4VpRegistry.

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

Crea una richiesta OpenId4VpRegistry

Come accennato in precedenza, dovrai registrare un OpenId4VpRegistry per gestire una richiesta OpenID4VP da un verificatore. Supporremo che tu abbia caricato alcuni tipi di dati locali con le credenziali del tuo wallet (ad esempio sdJwtsFromStorage). Ora li convertirai negli equivalenti Jetpack DigitalCredentialEntry in base al loro formato: SdJwtEntry o MdocEntry per SD-JWT o mdoc, rispettivamente.

Aggiungere Sd-JWT al registro

Mappa ogni credenziale SD-JWT locale a un SdJwtEntry per il registro:

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
}

Aggiungere mdocs al Registro di sistema

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

  • Un metodo per configurare il campo id consiste nel registrare un identificatore delle credenziali criptato, in modo che solo tu possa decriptare il valore.
  • I campi di visualizzazione dell'interfaccia utente per entrambi i formati devono essere localizzati.

Registrare le credenziali

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

Ora siamo pronti a registrare le tue credenziali in Credential Manager.

try {
    val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
    // Handle failure
}

Ora hai registrato le tue credenziali in Gestore delle credenziali.

Gestione dei metadati delle app

I metadati che l'app titolare registra con Credential Manager hanno le seguenti proprietà:

  • Persistenza: le informazioni vengono salvate localmente e persistono tra i riavvii.
  • Archiviazione isolata: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 basati su chiavi:i record del registro di ogni app sono identificati da una id, che consente di identificare, aggiornare o eliminare 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 registro viene chiamato più volte con lo stesso id, l'ultima chiamata sovrascrive tutti i record precedenti. Per aggiornare, registra di nuovo senza dover prima cancellare il vecchio record.

(Facoltativo) Crea un matcher

Un matcher è un binario Wasm che Credential Manager esegue in una sandbox per filtrare le credenziali registrate in base a una richiesta di verifica in entrata.

  • Matcher predefinito: la classe OpenId4VpRegistry include automaticamente il matcher OpenId4VP predefinito (OpenId4VpDefaults.DEFAULT_MATCHER) quando lo istanzi. 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 logica di corrispondenza propria.

Gestire una credenziale selezionata

Quando un utente seleziona una credenziale, l'app titolare deve gestire la richiesta. Devi definire un'attività che ascolti il filtro androidx.credentials.registry.provider.action.GET_CREDENTIAL intent. Il nostro portafoglio di esempio mostra questa procedura.

L'intent avvia l'attività con la richiesta del verificatore e l'origine della chiamata, che estrai con la funzione PendingIntentHandler.retrieveProviderGetCredentialRequest. Questo restituisce un ProviderGetCredentialRequest contenente tutte le informazioni associate alla richiesta di verifica. Esistono tre componenti chiave:

  • L'app di chiamate:l'app che ha effettuato la richiesta, recuperabile con getCallingAppInfo.
  • La credenziale selezionata:informazioni sul candidato scelto dall'utente, recuperate tramite selectedCredentialSet extension method; corrisponderà all'ID credenziale registrato.
  • Richieste specifiche: la richiesta specifica effettuata dal verificatore, recuperata dal metodo getCredentialOptions. Per un flusso di richiesta di credenziali digitali, puoi aspettarti di trovare un singolo GetDigitalCredentialOption in questo elenco.

Nella maggior parte dei casi, il verificatore invia una richiesta di presentazione delle credenziali digitali, che puoi elaborare con il seguente codice di esempio:

request.credentialOptions.forEach { option ->
    if (option is GetDigitalCredentialOption) {
        Log.i(TAG, "Got DC request: ${option.requestJson}")
        processRequest(option.requestJson)
    }
}

Un esempio è visibile nel portafoglio campione.

Controllare l'identità del verificatore

  1. Estrai ProviderGetCredentialRequest dall'intent:
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
  1. Controlla l'origine privilegiata:le app privilegiate (come i browser web) possono effettuare chiamate per conto di altri verificatori impostando il parametro origine. Per recuperare questa origine, devi passare un elenco di chiamanti privilegiati e attendibili (una lista consentita in formato JSON) all'API CallingAppInfo di getOrigin().
val origin = request?.callingAppInfo?.getOrigin(
    privilegedAppsJson // Your allow list JSON
)

Se l'origine non è vuota:l'origine viene restituita se packageName e le impronte dei certificati ottenute da signingInfo corrispondono a quelle di un'app trovata nell'allowlist passata all'API getOrigin(). Una volta ottenuto il valore dell'origine, l'app del fornitore deve considerarlo una chiamata privilegiata e impostare questa origine nella risposta OpenID4VP, anziché calcolarla utilizzando la firma dell'app chiamante.

Gestore delle password di Google utilizza un elenco consentito disponibile pubblicamente per le chiamate a getOrigin(). In qualità di fornitore di credenziali, puoi utilizzare questo elenco o fornire il tuo in formato JSON descritto dall'API. Spetta al fornitore selezionare l'elenco da utilizzare. Per ottenere l'accesso privilegiato con i fornitori di credenziali di terze parti, consulta la documentazione fornita dalla terza parte.

Se l'origine è vuota,la richiesta di verifica proviene da un'app per Android. L'origine dell'app da inserire nella risposta OpenID4VP deve essere calcolata come 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"

Eseguire il rendering della UI del supporto

Quando viene selezionata una credenziale, viene richiamata l'app titolare, guidando l'utente nell'interfaccia utente dell'app. Esistono due modi standard per gestire questo flusso di lavoro:

  • Se è necessaria un'autenticazione utente aggiuntiva per rilasciare la credenziale, utilizza l'API BiometricPrompt. Ciò è 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, si riducono al minimo i clic degli utenti e si offre un'esperienza più fluida.

Restituisce la risposta delle credenziali

Quando l'app titolare è pronta a inviare il risultato, completa l'attività con la risposta della credenziale:

PendingIntentHandler.setGetCredentialResponse(
    resultData,
    GetCredentialResponse(DigitalCredential(response.responseJson))
)
setResult(RESULT_OK, resultData)
finish()

Se esiste un'eccezione, puoi inviare anche l'eccezione delle credenziali:

PendingIntentHandler.setGetCredentialException(
    resultData,
    GetCredentialUnknownException() // Configure the proper exception
)
setResult(RESULT_OK, resultData)
finish()

Fai riferimento all'app di esempio per un esempio completo di restituzione della risposta delle credenziali nel contesto.