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,其中包含與驗證工具要求相關的所有資訊。其中包含三個主要元件:
- 通話應用程式:發出要求的應用程式,可透過
getCallingAppInfo擷取。您可以使用這項功能檢查驗證者身分。 - 所選憑證:使用者選擇的候選憑證相關資訊,透過
selectedCredentialSet extension method擷取;這會與您註冊的憑證 ID 相符。 - 特定要求:驗證者提出的特定要求,從
getCredentialOptions方法擷取。如果是數位憑證要求流程,這份清單中應該會顯示單一GetDigitalCredentialOption。
您可以使用下列程式碼處理顯示要求:
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()
如需在情境中傳回憑證回應的完整範例,請參閱範例應用程式。