使用持有者應用程式出示憑證

Credential Manager Holder API 可讓 Android 持有者 (也稱為「錢包」) 應用程式管理數位憑證,並向驗證者出示。本指南說明如何向 Credential Manager 註冊持有者應用程式的憑證,以及如何在使用者選取憑證時處理提供要求。Holder API 支援 Android 6 (API 級別 23) 以上版本。

新增依附元件

如要使用 Credential Manager Holder API,請將下列依附元件新增至應用程式模組的 build.gradle 指令碼:

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

註冊憑證

首先,建立 RegistryManager 執行個體,用於註冊 OpenId4VpRegistry 要求。

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

建構 OpenId4VpRegistry 要求

您必須註冊 OpenId4VpRegistry,才能處理驗證工具的 OpenID4VP 要求。假設您已載入含有錢包憑證的本機資料類型 (例如 sdJwtsFromStorage 和 mdocsFromStorage),則必須將這些類型轉換為對應的 Jetpack DigitalCredentialEntry:SdJwtEntry 和 MdocEntry。

將 Sd-JWT 新增至登錄檔

將每個本機 SD-JWT 憑證對應至登錄檔的 SdJwtEntry:

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
}

將 mdoc 新增至 Registry

將本機 mdoc 憑證對應至 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
}

程式碼重點

  • 加密 ID:設定 ID 欄位的方法之一,是註冊加密憑證 ID,這樣只有您能解密該值。
  • 本地化:兩種格式的使用者介面顯示欄位都應本地化。

合併並註冊要求

合併轉換後的項目,並向 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.
)

選用:內嵌核發

如果使用者沒有所要求類型的現有憑證,您可以顯示將憑證新增至持有者的選項。方法是在 OpenId4VpRegistry 中新增包含支援憑證的 inlineIssuanceEntries 清單:

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

使用 CredentialManager 註冊憑證。

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

應用程式中繼資料管理

持有者應用程式向CredentialManager註冊的中繼資料具有下列屬性:

  • 持續性:資訊會儲存在本機,重新啟動後仍會保留。
  • 獨立儲存空間:每個應用程式的登錄記錄都會分開儲存,也就是說,應用程式無法變更其他應用程式的登錄記錄。
  • 鍵控更新:每個應用程式的註冊記錄都由 id 鍵控,可重新識別、更新或刪除記錄。
  • 更新中繼資料:建議您在應用程式變更或首次載入時,更新保存的中繼資料。如果在同一個 id 下多次呼叫登錄檔,最新的呼叫會覆寫所有先前的記錄。如要更新,請重新註冊,不必先清除舊記錄。

選用:建立比對器

比對器是 Wasm 二進位檔,Credential Manager 會在沙箱中執行,根據傳入的驗證器要求篩選已註冊的憑證。

  • 預設比對器:OpenId4VpRegistry 類別在例項化時,會自動包含預設的 OpenId4VP 比對器 (OpenId4VpDefaults.DEFAULT_MATCHER)。對於所有標準 OpenID4VP 用途,程式庫會為您處理比對作業。
  • 自訂比對器:只有在支援需要專屬比對邏輯的非標準通訊協定時,才需要實作自訂比對器。

處理所選憑證

使用者選取憑證後,您的皮夾應用程式需要處理要求。您需要定義一個 Activity,用於監聽 androidx.credentials.registry.provider.action.GET_CREDENTIAL 意圖篩選器。我們的錢包範例會示範這項程序。

意圖會啟動活動,並附帶驗證器要求和呼叫來源,您可以使用 PendingIntentHandler.retrieveProviderGetCredentialRequest 函式擷取這些資訊。這會傳回 ProviderGetCredentialRequest,其中包含與驗證工具要求相關的所有資訊。其中包含三個主要元件:

您可以使用下列程式碼處理顯示要求:

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

如需範例,請參閱錢包範例。

檢查驗證者身分

首先,從意圖中擷取 ProviderGetCredentialRequest:

val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)

檢查具有特殊權限的應用程式:具有特殊權限的應用程式 (例如網路瀏覽器) 可設定來源參數,代表其他驗證工具發出呼叫。如要擷取這個來源,請將具有特殊權限且受信任的呼叫端清單 (JSON 格式的許可清單) 傳遞至 CallingAppInfo getOrigin API。

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

如果 origin 不是空白:如果 packageName 和憑證指紋與許可清單中的應用程式相符,系統就會傳回來源。請將此視為具特殊權限的呼叫,並在 OpenID4VP 回應中設定這個來源,而不是使用呼叫應用程式的簽章來計算來源。

如果 origin 為空白:驗證器要求來自 Android 應用程式。對應至 OpenID4VP 回應的來源應計算為 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"

算繪持有者 UI

選取憑證後,系統會叫用持有者應用程式,引導使用者操作應用程式 UI。處理這項工作流程有兩種標準做法:

  • 如果需要額外的使用者驗證才能釋出憑證,請使用 BiometricPrompt API。範例中會說明這一點。
  • 否則,許多錢包會選擇以靜默方式傳回,方法是轉譯空白活動,立即將資料傳回呼叫應用程式。這樣可以盡量減少使用者點擊次數,提供更流暢的體驗。

傳回憑證回應

當皮夾應用程式準備好傳回結果時,請使用憑證回應完成活動:

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

如有例外狀況,您也可以傳送憑證例外狀況:

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

如需在情境中傳回憑證回應的完整範例,請參閱範例應用程式。