Android 持有人 (也稱為「錢包」) 應用程式可透過 Credential Manager Holder API 管理數位憑證,並向驗證者出示。
核心概念
使用 Holder API 前,請務必先熟悉下列概念。
憑證格式
憑證可以不同格式儲存在持有者應用程式中。這些格式是憑證的呈現方式規格,每種格式都包含憑證的下列資訊:
- 類型:類別,例如大學學位或行動駕照。
- 屬性:例如姓名。
- 編碼:憑證的結構方式,例如 SD-JWT 或 mdoc
- 效力:以加密編譯方式驗證憑證真實性的方法。
每種憑證格式的編碼和驗證方式略有不同,但功能相同。
登錄檔支援兩種格式:
- SD-JWT:符合 IETF SD-JWT 型可驗證憑證 (SD-JWT VC) 規格。
- 行動裝置文件或 mdoc:符合 ISO/IEC 18013-5:2021 規格。
使用憑證管理工具時,驗證者可以對 SD-JWT 和 mdoc 提出 OpenID4VP 要求。選擇方式會因用途和產業而異。
註冊憑證中繼資料
憑證管理工具不會直接儲存持有人憑證,而是儲存憑證的中繼資料。持有人應用程式必須先使用 RegistryManager 向 Credential Manager 註冊憑證中繼資料。這個註冊程序會建立登錄記錄,主要有兩個用途:
- 比對:系統會使用已註冊的憑證中繼資料,比對日後的驗證者要求。
- 顯示:系統會在憑證選取器介面上向使用者顯示自訂 UI 元素。
您將使用 OpenId4VpRegistry 類別註冊數位憑證,因為這個類別同時支援 mdoc 和 SD-JWT 憑證格式。驗證者會傳送 OpenID4VP 要求,要求提供這些憑證。
註冊應用程式的憑證
如要使用 Credential Manager Holder API,請將下列依附元件新增至應用程式模組的建構指令碼:
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") }
建立 RegistryManager
建立 RegistryManager 例項,並向該例項註冊 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
}
建構 OpenId4VpRegistry 要求
如先前所述,您需要註冊 OpenId4VpRegistry,才能處理驗證方的 OpenID4VP 要求。我們假設您已載入一些含有錢包憑證的本機資料類型 (例如 sdJwtsFromStorage)。現在,您將根據這些資料的格式 (SD-JWT 為 SdJwtEntry,mdoc 為 MdocEntry),將其轉換為 Jetpack DigitalCredentialEntry 對等項目。
將 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 新增至登錄檔
將本機 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,這樣只有您能解密值。 - 兩種格式的 UI 顯示欄位都應本地化。
註冊憑證
合併轉換後的項目,並向 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.
)
現在,我們已準備好向 CredentialManager 註冊憑證。
try {
val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
// Handle failure
}
您現在已向 Credential Manager 註冊憑證。
管理應用程式中繼資料
持有者應用程式向 CredentialManager 註冊的中繼資料具有下列屬性:
- 持續性:資訊會儲存在本機,並在重新啟動後保留。
- 獨立儲存:每個應用程式的登錄記錄都會分開儲存,也就是說,一個應用程式無法變更另一個應用程式的登錄記錄。
- 以鍵值為依據的更新:每個應用程式的登錄記錄都會以
id為鍵值,方便重新識別、更新或刪除記錄。 - 更新中繼資料:建議您在應用程式變更或首次載入時,更新保存的中繼資料。如果在同一個
id下多次呼叫登錄檔,系統會以最後一次呼叫覆寫所有先前的記錄。如要更新,請重新註冊,不必先清除舊記錄。
選用:建立比對器
比對器是 Credential Manager 在沙箱中執行的 Wasm 二進位檔,可根據傳入的驗證器要求篩選已註冊的憑證。
- 預設比對器:您例項化
OpenId4VpRegistry類別時,系統會自動加入預設OpenId4VP比對器 (OpenId4VpDefaults.DEFAULT_MATCHER)。對於所有標準 OpenID4VP 用途,程式庫都會為您處理比對作業。 - 自訂比對器:只有在支援非標準通訊協定時,才需要導入自訂比對器,因為這類通訊協定需要專屬的比對邏輯。
處理所選憑證
使用者選取憑證時,持有者應用程式必須處理要求。您需要定義會監聽 androidx.credentials.registry.provider.action.GET_CREDENTIAL 意圖篩選器的 Activity。我們的範例錢包會示範這項程序。
Intent 會啟動活動,並傳送驗證器要求和呼叫來源,您可以使用 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
)
如果來源不是空白:如果 packageName 和從 signingInfo 取得的憑證指紋,與傳遞至 getOrigin() API 的許可清單所列應用程式相符,系統就會傳回來源。取得來源值後,提供者應用程式應將此視為具特殊權限的呼叫,在 OpenID4VP 回應中設定這個來源,不會使用呼叫應用程式的簽章來計算來源。
Google 密碼管理工具針對 getOrigin() 的呼叫採用公開發布的許可清單。憑證提供者可以使用這份清單,或以 API 描述的 JSON 格式自行提供清單。提供者可自行選擇要使用的清單。如要取得第三方憑證提供者的特殊權限存取權,請參閱第三方提供的說明文件。
如果來源為空白,驗證者要求來自 Android 應用程式。應用程式來源應計算為 android:apk-key-hash:<encoded SHA 256 fingerprint>,並放入 OpenID4VP 回應中。
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"
算繪 Holder 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()
如需在結構定義中傳回憑證回應的完整範例,請參閱範例應用程式。