將 Holder 應用程式與 Credential Manager 整合

Android 持有人 (也稱為「錢包」) 應用程式可透過 Credential Manager Holder API 管理數位憑證,並向驗證者出示。

圖片:顯示憑證管理工具中的數位憑證使用者介面
圖 1. 數位憑證選取器使用者介面。

核心概念

使用 Holder API 前,請務必先熟悉下列概念。

憑證格式

憑證可以不同格式儲存在持有者應用程式中。這些格式是憑證的呈現方式規格,每種格式都包含憑證的下列資訊:

  • 類型:類別,例如大學學位或行動駕照。
  • 屬性:例如姓名。
  • 編碼:憑證的結構方式,例如 SD-JWT 或 mdoc
  • 效力:以加密編譯方式驗證憑證真實性的方法。

每種憑證格式的編碼和驗證方式略有不同,但功能相同。

登錄檔支援兩種格式:

使用憑證管理工具時,驗證者可以對 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,內含與驗證者要求相關的所有資訊。主要有三個元件:

驗證者最常提出數位憑證出示要求,您可以透過下列程式碼範例處理這類要求:

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

如需相關範例,請參閱範例錢包

檢查驗證者身分

  1. 從意圖中擷取 ProviderGetCredentialRequest
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
  1. 檢查具有特殊權限的來源:具有特殊權限的應用程式 (例如網路瀏覽器) 可設定來源參數,代表其他驗證者發出呼叫。如要擷取這個來源,您必須將具有特殊權限且受信任的呼叫端清單 (JSON 格式的允許清單) 傳入 CallingAppInfogetOrigin() 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()

如需在結構定義中傳回憑證回應的完整範例,請參閱範例應用程式