Trình giấy tờ uỷ nhiệm bằng ứng dụng của người sở hữu

API Trình quản lý thông tin xác thực cho người sở hữu cho phép ứng dụng của người sở hữu (còn gọi là "ví") trên Android quản lý và xuất trình thông tin xác thực kỹ thuật số cho bên xác minh. Hướng dẫn này mô tả cách đăng ký thông tin xác thực của ứng dụng chủ sở hữu với Trình quản lý thông tin xác thực và cách xử lý yêu cầu trình bày khi người dùng chọn một thông tin xác thực. Holder API được hỗ trợ trên Android 6 (cấp độ API 23) trở lên.

Thêm phần phụ thuộc

Để sử dụng API Trình quản lý thông tin xác thực, hãy thêm các phần phụ thuộc sau vào tập lệnh build.gradle của mô-đun ứng dụng:

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

Đăng ký thông tin đăng nhập

Trước tiên, hãy tạo một thực thể RegistryManager mà bạn sẽ dùng để đăng ký yêu cầu OpenId4VpRegistry.

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

Tạo yêu cầu OpenId4VpRegistry

Bạn cần đăng ký một OpenId4VpRegistry để xử lý các yêu cầu OpenID4VP từ người xác minh. Giả sử bạn đã tải các loại dữ liệu địa phương bằng thông tin xác thực ví (ví dụ: sdJwtsFromStorage và mdocsFromStorage), bạn phải chuyển đổi các loại dữ liệu đó thành các loại dữ liệu tương đương trong Jetpack DigitalCredentialEntry: SdJwtEntry và MdocEntry.

Thêm Sd-JWT vào sổ đăng ký

Liên kết từng thông tin xác thực SD-JWT cục bộ với một SdJwtEntry cho sổ đăng ký:

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
}

Thêm mdoc vào Sổ đăng ký

Ánh xạ thông tin xác thực mdoc cục bộ của bạn vào loại 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
}

Các điểm chính về mã

  • Mã nhận dạng đã mã hoá: Một phương thức định cấu hình trường ID là đăng ký một giá trị nhận dạng thông tin xác thực đã mã hoá, nhờ đó chỉ bạn mới có thể giải mã giá trị này.
  • Bản địa hoá: Các trường hiển thị giao diện người dùng cho cả hai định dạng phải được bản địa hoá.

Kết hợp và đăng ký yêu cầu của bạn

Kết hợp các mục đã chuyển đổi và đăng ký yêu cầu bằng 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.
)

Không bắt buộc: Phát hành nội tuyến

Nếu người dùng không có thông tin xác thực thuộc loại được yêu cầu, bạn có thể hiển thị một lựa chọn để thêm thông tin xác thực vào ví của người dùng. Để thực hiện việc này, hãy thêm danh sách inlineIssuanceEntries chứa thông tin xác thực được hỗ trợ vào OpenId4VpRegistry:

val openidRegistryRequest = OpenId4VpRegistry(
    credentialEntries = credentialEntries,
    inlineIssuanceEntries = 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"
)

Đăng ký thông tin xác thực của bạn bằng CredentialManager.

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

Quản lý siêu dữ liệu ứng dụng

Siêu dữ liệu mà ứng dụng lưu trữ của bạn đăng ký với CredentialManager có các thuộc tính sau:

  • Tính ổn định: Thông tin được lưu trên thiết bị và vẫn được giữ lại sau khi khởi động lại.
  • Bộ nhớ riêng biệt: Bản ghi sổ đăng ký của mỗi ứng dụng được lưu trữ riêng biệt, nghĩa là một ứng dụng không thể thay đổi bản ghi sổ đăng ký của một ứng dụng khác.
  • Bản cập nhật có khoá: Bản ghi trong sổ đăng ký của mỗi ứng dụng được khoá bằng một id, cho phép xác định lại, cập nhật hoặc xoá bản ghi.
  • Cập nhật siêu dữ liệu: Bạn nên cập nhật siêu dữ liệu được duy trì bất cứ khi nào ứng dụng của bạn thay đổi hoặc được tải lần đầu. Nếu một sổ đăng ký được gọi nhiều lần trong cùng một id, thì lệnh gọi mới nhất sẽ ghi đè tất cả bản ghi trước đó. Để cập nhật, hãy đăng ký lại mà không cần xoá bản ghi cũ trước.

Không bắt buộc: Tạo trình so khớp

Trình so khớp là một tệp nhị phân Wasm mà Trình quản lý thông tin xác thực chạy trong hộp cát để lọc thông tin xác thực đã đăng ký của bạn theo yêu cầu của Trình xác minh sắp tới.

  • Trình so khớp mặc định: Lớp OpenId4VpRegistry tự động bao gồm trình so khớp OpenId4VP mặc định (OpenId4VpDefaults.DEFAULT_MATCHER) khi bạn tạo thực thể cho lớp đó. Đối với tất cả các trường hợp sử dụng OpenID4VP tiêu chuẩn, thư viện sẽ xử lý việc so khớp cho bạn.
  • Trình so khớp tuỳ chỉnh: Bạn chỉ triển khai trình so khớp tuỳ chỉnh nếu đang hỗ trợ một giao thức không chuẩn yêu cầu logic so khớp riêng.

Xử lý thông tin xác thực đã chọn

Khi người dùng chọn một thông tin xác thực, ứng dụng lưu trữ của bạn cần xử lý yêu cầu đó. Bạn cần xác định một Hoạt động lắng nghe bộ lọc ý định androidx.credentials.registry.provider.action.GET_CREDENTIAL. Ví mẫu của chúng tôi minh hoạ quy trình này.

Ý định này sẽ khởi chạy hoạt động của bạn bằng yêu cầu của Trình xác minh và nguồn gốc gọi, mà bạn trích xuất bằng PendingIntentHandler.retrieveProviderGetCredentialRequest hàm. Thao tác này sẽ trả về một ProviderGetCredentialRequest chứa tất cả thông tin liên quan đến yêu cầu của trình xác minh. Có ba thành phần chính:

  • Ứng dụng gọi điện: Ứng dụng đưa ra yêu cầu, có thể truy xuất bằng getCallingAppInfo. Bạn có thể sử dụng thông tin này để kiểm tra danh tính của người xác minh.
  • Thông tin xác thực đã chọn: Thông tin về thông tin xác thực mà người dùng đã chọn, được truy xuất thông qua selectedCredentialSet extension method; thông tin này sẽ khớp với mã thông tin xác thực mà bạn đã đăng ký.
  • Yêu cầu cụ thể: Yêu cầu cụ thể do trình xác minh đưa ra, được truy xuất từ phương thức getCredentialOptions. Đối với quy trình yêu cầu Giấy tờ tuỳ thân kỹ thuật số, bạn có thể thấy một GetDigitalCredentialOption duy nhất trong danh sách này.

Bạn có thể xử lý yêu cầu trình bày bằng mã sau:

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

Bạn có thể xem ví dụ về vấn đề này trong ví mẫu.

Kiểm tra danh tính của người xác minh

Trước tiên, hãy trích xuất ProviderGetCredentialRequest từ ý định:

val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)

Kiểm tra các ứng dụng có đặc quyền: Các ứng dụng có đặc quyền (như trình duyệt web) có thể thay mặt cho các trình xác minh khác thực hiện lệnh gọi bằng cách đặt tham số nguồn gốc. Để truy xuất nguồn gốc này, hãy truyền danh sách phương thức gọi có đặc quyền và đáng tin cậy (danh sách cho phép ở định dạng JSON) đến API CallingAppInfo getOrigin.

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

Nếu origin không trống: Nguồn gốc được trả về nếu packageName và dấu vân tay chứng chỉ khớp với một ứng dụng trong danh sách cho phép của bạn. Hãy coi đây là một lệnh gọi đặc quyền và đặt nguồn gốc này trên phản hồi OpenID4VP thay vì tính toán nguồn gốc bằng chữ ký của ứng dụng gọi.

Nếu origin trống: Yêu cầu của trình xác minh là từ một ứng dụng Android. Nguồn gốc được liên kết với phản hồi OpenID4VP phải được tính là android:apk-key-hash:<encoded SHA 256 fingerprint>.

val appSigningInfo =
    request?.callingAppInfo?.signingInfoCompat?.signingCertificateHistory?.firstOrNull()?.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
val certHash =
    Base64.encodeToString(md.digest(requireNotNull(appSigningInfo)), Base64.NO_WRAP or Base64.NO_PADDING)
val computedOrigin = "android:apk-key-hash:$certHash"

Hiển thị giao diện người dùng của chủ thẻ

Khi một thông tin xác thực được chọn, ứng dụng lưu trữ sẽ được gọi, hướng dẫn người dùng thông qua giao diện người dùng của ứng dụng. Có hai cách tiêu chuẩn để xử lý quy trình này:

  • Nếu cần xác thực thêm người dùng để phát hành thông tin xác thực, hãy sử dụng BiometricPrompt API. Điều này được minh hoạ trong mẫu.
  • Nếu không, nhiều ví sẽ chọn trả về tự động bằng cách hiển thị một hoạt động trống để ngay lập tức chuyển dữ liệu trở lại ứng dụng gọi. Điều này giúp giảm thiểu số lượt nhấp của người dùng và mang lại trải nghiệm liền mạch hơn.

Trả về phản hồi thông tin xác thực

Sau khi ứng dụng lưu trữ sẵn sàng gửi lại kết quả, hãy hoàn tất hoạt động bằng phản hồi thông tin xác thực:

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

Nếu có ngoại lệ, bạn có thể gửi ngoại lệ thông tin xác thực tương tự:

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

Tham khảo ứng dụng mẫu để xem ví dụ đầy đủ về cách trả về phản hồi thông tin xác thực theo bối cảnh.