Interfejs Credential Manager Holder API umożliwia aplikacji posiadacza na Androidzie (nazywanej też „portfelem”) zarządzanie cyfrowymi dokumentami tożsamości i przedstawianie ich weryfikatorom. Z tego przewodnika dowiesz się, jak zarejestrować dane logowania aplikacji posiadacza w Menedżerze danych logowania i jak obsłużyć prośbę o prezentację, gdy użytkownik wybierze dane logowania. Interfejs Holder API jest obsługiwany na Androidzie 6 (poziom interfejsu API 23) i nowszych wersjach.
Dodawanie zależności
Aby używać interfejsu Credential Manager Holder API, dodaj te zależności do skryptu build.gradle modułu aplikacji:
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"
}
Rejestrowanie danych logowania
Najpierw utwórz instancję RegistryManager, której użyjesz do zarejestrowania żądania OpenId4VpRegistry.
// Create the registry manager
val registryManager = RegistryManager.create(context)
Tworzenie żądania OpenId4VpRegistry
Aby obsługiwać żądania OpenID4VP od weryfikatorów, musisz zarejestrować OpenId4VpRegistry. Jeśli masz typy danych lokalnych wczytane z danymi logowania do portfela (np. sdJwtsFromStorage i mdocsFromStorage), musisz przekonwertować je na ich odpowiedniki w Jetpacku DigitalCredentialEntry: SdJwtEntry i MdocEntry.
Dodawanie tokenów SD-JWT do rejestru
Zmapuj każde lokalne dane uwierzytelniające SD-JWT na SdJwtEntry rejestru:
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
}
Dodawanie dokumentów mdoc do rejestru
Zmapuj lokalne dane uwierzytelniające mdoc na typ 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
}
Najważniejsze informacje o kodzie
- Zaszyfrowane identyfikatory: jedną z metod konfigurowania pola
IDjest zarejestrowanie zaszyfrowanego identyfikatora danych logowania, dzięki czemu tylko Ty możesz odszyfrować wartość. - Lokalizacja: pola wyświetlane w interfejsie w przypadku obu formatów powinny być zlokalizowane.
Łączenie i rejestrowanie prośby
Połącz przekonwertowane wpisy i zarejestruj żądanie za pomocą 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.
)
Opcjonalnie: wydawanie wbudowane
Jeśli użytkownik nie ma dostępnych danych uwierzytelniających żądanego typu, możesz wyświetlić opcję dodania danych uwierzytelniających do aplikacji przechowującej dokumenty. Aby to zrobić, dodaj do pliku OpenId4VpRegistry listę inlineIssuanceEntries zawierającą obsługiwane dane logowania:
val openidRegistryRequest = OpenId4VpRegistry(
credentialEntries = credentialEntries,
inlineIssuanceEntries = 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"
)
Zarejestruj swoje dane logowania w CredentialManager.
try {
val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
// Handle failure
}
Zarządzanie metadanymi aplikacji
Metadane, które aplikacja przechowująca rejestruje w CredentialManager, mają te właściwości:
- Trwałość: informacje są zapisywane lokalnie i zachowywane po ponownym uruchomieniu.
- Odizolowane miejsce na dane: wpisy rejestru każdej aplikacji są przechowywane oddzielnie, co oznacza, że jedna aplikacja nie może zmieniać wpisów rejestru innej aplikacji.
- Aktualizacje z kluczem: rekordy rejestru każdej aplikacji są kluczowane za pomocą
id, co umożliwia reidentyfikację, aktualizowanie i usuwanie rekordów. - Aktualizowanie metadanych: dobrą praktyką jest aktualizowanie zapisanych metadanych przy każdej zmianie lub pierwszym wczytaniu aplikacji. Jeśli rejestr jest wywoływany wielokrotnie w ramach tego samego
id, ostatnie wywołanie zastępuje wszystkie poprzednie rekordy. Aby zaktualizować, zarejestruj się ponownie bez konieczności wcześniejszego czyszczenia starego rekordu.
Opcjonalnie: utwórz dopasowanie
Dopasowanie to plik binarny Wasm, który Menedżer danych logowania uruchamia w piaskownicy, aby filtrować zarejestrowane dane logowania pod kątem przychodzącego żądania weryfikatora.
- Dopasowanie domyślne: klasa
OpenId4VpRegistryautomatycznie uwzględnia domyślne dopasowanieOpenId4VP(OpenId4VpDefaults.DEFAULT_MATCHER) podczas tworzenia instancji. W przypadku wszystkich standardowych przypadków użycia OpenID4VP biblioteka obsługuje dopasowywanie. - Niestandardowy moduł dopasowujący: niestandardowy moduł dopasowujący należy wdrożyć tylko wtedy, gdy obsługujesz niestandardowy protokół, który wymaga własnej logiki dopasowywania.
Obsługa wybranych danych logowania
Gdy użytkownik wybierze dokument, aplikacja musi obsłużyć żądanie.
Musisz zdefiniować aktywność, która nasłuchuje filtra intencji androidx.credentials.registry.provider.action.GET_CREDENTIAL.
Ten proces jest przedstawiony w przykładowym portfelu.
Intencja uruchamia aktywność z żądaniem weryfikatora i wywołującym źródłem, które wyodrębniasz za pomocą funkcji PendingIntentHandler.retrieveProviderGetCredentialRequest. Zwraca on obiekt ProviderGetCredentialRequest zawierający wszystkie informacje powiązane z żądaniem weryfikatora. Składa się z 3 kluczowych elementów:
- Aplikacja do obsługi połączeń: aplikacja, która wysłała żądanie, możliwa do pobrania za pomocą
getCallingAppInfo. Możesz go użyć do sprawdzenia tożsamości weryfikatora. - Wybrane dane logowania: informacje o tym, którego kandydata wybrał użytkownik, pobrane za pomocą
selectedCredentialSet extension method. Będą one zgodne z zarejestrowanym identyfikatorem danych logowania. - Konkretne żądania: konkretne żądanie przesłane przez weryfikatora, pobrane za pomocą metody
getCredentialOptions. W przypadku procesu żądania cyfrowych danych uwierzytelniających na tej liście znajdziesz pojedynczy elementGetDigitalCredentialOption.
Żądanie prezentacji możesz przetworzyć za pomocą tego kodu:
request.credentialOptions.forEach { option ->
if (option is GetDigitalCredentialOption) {
Log.i(TAG, "Got DC request: ${option.requestJson}")
processRequest(option.requestJson)
}
}
Przykład znajdziesz w przykładowym portfelu.
Sprawdzanie tożsamości weryfikatora
Najpierw wyodrębnij ProviderGetCredentialRequest z intencji:
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
Sprawdź, czy nie ma aplikacji uprzywilejowanych: aplikacje uprzywilejowane (np. przeglądarki internetowe) mogą wykonywać połączenia w imieniu innych weryfikatorów, ustawiając parametr origin. Aby pobrać to źródło, przekaż listę uprzywilejowanych i zaufanych rozmówców (listę dozwolonych w formacie JSON) do interfejsu API CallingAppInfo getOrigin.
val origin = request?.callingAppInfo?.getOrigin(
privilegedAppsJson // Your allow list JSON
)
Jeśli pole origin nie jest puste: źródło jest zwracane, jeśli packageName i odciski cyfrowe certyfikatu pasują do aplikacji na liście dozwolonych. Potraktuj to jako wywołanie uprzywilejowane i ustaw to źródło w odpowiedzi OpenID4VP zamiast obliczać źródło za pomocą podpisu aplikacji wywołującej.
Jeśli pole origin jest puste: żądanie weryfikatora pochodzi z aplikacji na Androida. Pochodzenie zmapowane na odpowiedź OpenID4VP powinno być obliczane jako android:apk-key-hash:<encoded SHA 256 fingerprint>.
val appSigningInfo =
request?.callingAppInfo?.signingInfoCompat?.signingCertificateHistory?.firstOrNull()?.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val certHash =
Base64.encodeToString(md.digest(requireNotNull(appSigningInfo)), Base64.NO_WRAP or Base64.NO_PADDING)
val computedOrigin = "android:apk-key-hash:$certHash"
Renderowanie interfejsu posiadacza
Po wybraniu dokumentu tożsamości wywoływana jest aplikacja, która prowadzi użytkownika przez interfejs. Można to zrobić na 2 standardowe sposoby:
- Jeśli do udostępnienia danych uwierzytelniających wymagane jest dodatkowe uwierzytelnienie użytkownika, użyj interfejsu BiometricPrompt API. Zostało to przedstawione w przykładzie.
- W przeciwnym razie wiele portfeli decyduje się na cichy zwrot, renderując pustą aktywność, która natychmiast przekazuje dane z powrotem do aplikacji wywołującej. Minimalizuje to liczbę kliknięć użytkownika i zapewnia płynniejsze działanie.
Zwracanie odpowiedzi z danymi logowania
Gdy aplikacja przechowująca będzie gotowa do odesłania wyniku, zakończ aktywność z odpowiedzią dotyczącą danych uwierzytelniających:
PendingIntentHandler.setGetCredentialResponse(
resultData,
GetCredentialResponse(DigitalCredential(response.responseJson))
)
setResult(RESULT_OK, resultData)
finish()
Jeśli wystąpi wyjątek, możesz wysłać wyjątek dotyczący danych logowania:
PendingIntentHandler.setGetCredentialException(
resultData,
GetCredentialUnknownException() // Configure the proper exception
)
setResult(RESULT_OK, resultData)
finish()
Pełny przykład zwracania odpowiedzi z danymi logowania w kontekście znajdziesz w przykładowej aplikacji.