Anmeldedaten mit Ihrer Inhaber-App vorlegen

Mit der Credential Manager Holder API kann Ihre Android-Inhaber-App (auch „Wallet“ genannt) digitale Berechtigungsnachweise verwalten und Prüfern präsentieren. In diesem Leitfaden wird beschrieben, wie Sie die Anmeldedaten Ihrer Inhaber-App im Credential Manager registrieren und wie Sie eine Präsentationsanfrage verarbeiten, wenn ein Nutzer Anmeldedaten auswählt. Die Holder API wird ab Android 6 (API-Level 23) unterstützt.

Abhängigkeiten hinzufügen

Wenn Sie die Credential Manager Holder API verwenden möchten, fügen Sie dem build.gradle-Script Ihres App-Moduls die folgenden Abhängigkeiten hinzu:

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

Anmeldedaten registrieren

Erstellen Sie zuerst eine RegistryManager-Instanz, mit der Sie eine OpenId4VpRegistry-Anfrage registrieren.

// Create the registry manager
val registryManager = RegistryManager.create(context)

OpenId4VpRegistry-Anfrage erstellen

Sie müssen einen OpenId4VpRegistry registrieren, um OpenID4VP-Anfragen von Prüfern zu verarbeiten. Angenommen, Sie haben lokale Datentypen mit Ihren Wallet-Anmeldedaten geladen (z. B. sdJwtsFromStorage und mdocsFromStorage). Dann müssen Sie sie in die entsprechenden Jetpack-DigitalCredentialEntry-Äquivalente umwandeln: SdJwtEntry und MdocEntry.

SD-JWTs zur Registry hinzufügen

Ordnen Sie jeden lokalen SD-JWT-Berechtigungsnachweis einem SdJwtEntry für die Registry zu:

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
}

mDocs zur Registry hinzufügen

Ordnen Sie Ihre lokalen mdoc-Anmeldedaten dem Jetpack-Typ MdocEntry zu:

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
}

Wichtige Punkte zum Code

  • Verschlüsselte IDs: Eine Methode zum Konfigurieren des Felds ID besteht darin, eine verschlüsselte Anmeldedaten-ID zu registrieren, sodass nur Sie den Wert entschlüsseln können.
  • Lokalisierung: Die UI-Anzeigefelder für beide Formate sollten lokalisiert werden.

Anfrage kombinieren und registrieren

Kombinieren Sie die konvertierten Einträge und registrieren Sie die Anfrage mit dem 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.
)

Optional: Inline-Ausstellung

Wenn für einen Nutzer keine vorhandenen Anmeldedaten eines angeforderten Typs verfügbar sind, können Sie eine Option zum Hinzufügen von Anmeldedaten zu Ihrem Inhaber anzeigen. Fügen Sie dazu Ihrer OpenId4VpRegistry eine inlineIssuanceEntries-Liste mit den unterstützten Anmeldedaten hinzu:

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

Registrieren Sie Ihre Anmeldedaten bei CredentialManager.

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

Verwaltung von App-Metadaten

Die Metadaten, die Ihre Inhaber-App bei CredentialManager registriert, haben die folgenden Attribute:

  • Persistenz: Die Informationen werden lokal gespeichert und bleiben auch nach einem Neustart erhalten.
  • Isolierter Speicher: Die Registry-Einträge jeder App werden separat gespeichert. Das bedeutet, dass eine App die Registry-Einträge einer anderen App nicht ändern kann.
  • Schlüsselbasierte Aktualisierungen: Die Registrierungseinträge jeder App sind mit einer id versehen, sodass Einträge re-identifiziert, aktualisiert oder gelöscht werden können.
  • Metadaten aktualisieren: Es empfiehlt sich, die gespeicherten Metadaten immer dann zu aktualisieren, wenn sich die App ändert oder zum ersten Mal geladen wird. Wenn eine Registry mehrmals unter derselben id aufgerufen wird, überschreibt der letzte Aufruf alle vorherigen Einträge. Wenn Sie die Registrierung aktualisieren möchten, registrieren Sie sich einfach noch einmal. Sie müssen den alten Eintrag nicht löschen.

Optional: Matcher erstellen

Ein Matcher ist eine Wasm-Binärdatei, die der Credential Manager in einer Sandbox ausführt, um Ihre registrierten Ausweisdokumente anhand einer eingehenden Anfrage des Verifizierers zu filtern.

  • Standard-Matcher: Die Klasse OpenId4VpRegistry enthält beim Instanziieren automatisch den Standard-Matcher OpenId4VP (OpenId4VpDefaults.DEFAULT_MATCHER). Bei allen Standard-OpenID4VP-Anwendungsfällen übernimmt die Bibliothek den Abgleich für Sie.
  • Benutzerdefinierter Matcher: Sie würden einen benutzerdefinierten Matcher nur implementieren, wenn Sie ein nicht standardmäßiges Protokoll unterstützen, das eine eigene Abgleichslogik erfordert.

Ausgewählte Anmeldedaten verarbeiten

Wenn ein Nutzer einen Berechtigungsnachweis auswählt, muss Ihre Inhaber-App die Anfrage verarbeiten. Sie müssen eine Aktivität definieren, die auf den Intent-Filter androidx.credentials.registry.provider.action.GET_CREDENTIAL reagiert. In unserem Beispiel-Wallet wird dieses Verfahren veranschaulicht.

Der Intent startet Ihre Aktivität mit der Verifier-Anfrage und dem aufrufenden Ursprung, die Sie mit derPendingIntentHandler.retrieveProviderGetCredentialRequest-Funktion extrahieren. Dadurch wird ein ProviderGetCredentialRequest mit allen Informationen zurückgegeben, die mit der Anfrage des Verifizierers verknüpft sind. Es gibt drei Hauptkomponenten:

Sie können die Präsentationsanfrage mit dem folgenden Code verarbeiten:

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

Ein Beispiel hierfür finden Sie im Beispiel-Wallet.

Identität des Prüfers überprüfen

Extrahieren Sie zuerst die ProviderGetCredentialRequest aus dem Intent:

val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)

Nach privilegierten Apps suchen: Privilegierte Apps (z. B. Webbrowser) können im Namen anderer Prüfer Aufrufe ausführen, indem sie den Parameter „origin“ festlegen. Um diesen Ursprung abzurufen, übergeben Sie eine Liste privilegierter und vertrauenswürdiger Aufrufer (eine Zulassungsliste im JSON-Format) an die getOrigin-API von CallingAppInfo.

val origin = request?.callingAppInfo?.getOrigin(
    privilegedAppsJson // Your allow list JSON
)

Wenn origin nicht leer ist: Der Ursprung wird zurückgegeben, wenn die packageName und die Zertifikat-Fingerabdrücke mit einer App auf Ihrer Zulassungsliste übereinstimmen. Betrachten Sie dies als privilegierten Aufruf und legen Sie diesen Ursprung in der OpenID4VP-Antwort fest, anstatt den Ursprung mithilfe der Signatur der Anruf-App zu berechnen.

Wenn origin leer ist: Die Anfrage des Verifizierers stammt von einer Android-App. Der Ursprung, der der OpenID4VP-Antwort zugeordnet ist, sollte als android:apk-key-hash:<encoded SHA 256 fingerprint> berechnet werden.

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"

Inhaber-UI rendern

Wenn ein Ausweis ausgewählt wird, wird die Inhaber-App aufgerufen und der Nutzer durch die Benutzeroberfläche der App geführt. Es gibt zwei Standardmethoden, um diesen Workflow zu verarbeiten:

  • Wenn für die Freigabe der Anmeldedaten eine zusätzliche Nutzerauthentifizierung erforderlich ist, verwende die BiometricPrompt API. Dies wird im Beispiel veranschaulicht.
  • Andernfalls entscheiden sich viele Wallets für eine unauffällige Rückgabe, indem sie eine leere Aktivität rendern, die die Daten sofort an die aufrufende App zurückgibt. Dadurch werden die Klicks der Nutzer minimiert und die Nutzung wird nahtloser.

Anmeldedatenantwort zurückgeben

Sobald Ihre Inhaber-App bereit ist, das Ergebnis zurückzusenden, beenden Sie die Aktivität mit der Antwort auf die Anmeldedaten:

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

Wenn eine Ausnahme vorliegt, können Sie die Ausnahme für die Anmeldedaten auf ähnliche Weise senden:

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

Ein vollständiges Beispiel für die Rückgabe der Antwort mit den Anmeldedaten im Kontext finden Sie in der Beispiel-App.