Menyajikan kredensial dengan aplikasi pemegang Anda

Credential Manager Holder API memungkinkan aplikasi pemegang (juga disebut "dompet") Android Anda mengelola dan menampilkan kredensial digital kepada pemverifikasi. Panduan ini menjelaskan cara mendaftarkan kredensial aplikasi pemegang Anda ke Credential Manager dan cara menangani permintaan presentasi saat pengguna memilih kredensial. Holder API didukung di Android 6 (API level 23) dan yang lebih tinggi.

Menambahkan dependensi

Untuk menggunakan Credential Manager Holder API, tambahkan dependensi berikut ke skrip build.gradle modul aplikasi Anda:

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

Mendaftarkan kredensial Anda

Pertama, buat instance RegistryManager yang akan Anda gunakan untuk mendaftarkan permintaan OpenId4VpRegistry.

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

Membuat permintaan OpenId4VpRegistry

Anda harus mendaftarkan OpenId4VpRegistry untuk menangani permintaan OpenID4VP dari pemverifikasi. Dengan asumsi Anda memiliki jenis data lokal yang dimuat dengan kredensial wallet (misalnya, sdJwtsFromStorage dan mdocsFromStorage), Anda harus mengonversinya menjadi setara Jetpack DigitalCredentialEntry: SdJwtEntry dan MdocEntry.

Menambahkan SD-JWT ke registry

Petakan setiap kredensial SD-JWT lokal ke SdJwtEntry untuk registry:

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
}

Menambahkan mdoc ke Registry

Petakan kredensial mdoc lokal Anda ke jenis 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
}

Poin penting tentang kode

  • ID Terenkripsi: Salah satu metode untuk mengonfigurasi kolom ID adalah dengan mendaftarkan ID kredensial terenkripsi, sehingga hanya Anda yang dapat mendekripsi nilainya.
  • Pelokalan: Kolom tampilan UI untuk kedua format harus dilokalkan.

Menggabungkan dan mendaftarkan permintaan Anda

Gabungkan entri yang dikonversi dan daftarkan permintaan dengan 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.
)

Opsional: Penerbitan inline

Jika tidak ada kredensial yang ada dari jenis yang diminta yang tersedia untuk pengguna, Anda dapat menampilkan opsi untuk menambahkan kredensial ke pemegang Anda. Untuk melakukannya, tambahkan daftar inlineIssuanceEntries yang berisi kredensial yang didukung ke OpenId4VpRegistry Anda:

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

Daftarkan kredensial Anda dengan CredentialManager.

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

Pengelolaan metadata aplikasi

Metadata yang didaftarkan aplikasi pemegang Anda ke CredentialManager memiliki properti berikut:

  • Persistensi: Informasi disimpan secara lokal, dan tetap ada setelah perangkat dimulai ulang.
  • Penyimpanan Terpisah: Setiap data registry aplikasi disimpan secara terpisah, yang berarti satu aplikasi tidak dapat mengubah data registry aplikasi lain.
  • Update dengan Kunci: Setiap data registry aplikasi diberi kunci dengan id, yang memungkinkan identifikasi ulang, update, atau penghapusan data.
  • Memperbarui Metadata: Sebaiknya perbarui metadata yang dipertahankan setiap kali aplikasi Anda berubah atau pertama kali dimuat. Jika registry dipanggil beberapa kali dalam id yang sama, panggilan terbaru akan menimpa semua data sebelumnya. Untuk memperbarui, daftar ulang tanpa perlu menghapus data lama terlebih dahulu.

Opsional: Membuat matcher

Matcher adalah biner Wasm yang dijalankan Credential Manager di sandbox untuk memfilter kredensial terdaftar Anda terhadap permintaan Verifier yang masuk.

  • Pencocok default: Class OpenId4VpRegistry otomatis menyertakan pencocok OpenId4VP default (OpenId4VpDefaults.DEFAULT_MATCHER) saat Anda membuat instance-nya. Untuk semua kasus penggunaan OpenID4VP standar, library akan menangani pencocokan untuk Anda.
  • Pencocok kustom: Anda hanya akan menerapkan pencocok kustom jika Anda mendukung protokol non-standar yang memerlukan logika pencocokannya sendiri.

Menangani kredensial yang dipilih

Saat pengguna memilih kredensial, aplikasi pemegang Anda harus menangani permintaan tersebut. Anda harus menentukan Aktivitas yang memproses filter intent androidx.credentials.registry.provider.action.GET_CREDENTIAL. Wallet contoh kami menunjukkan prosedur ini.

Intent meluncurkan aktivitas Anda dengan permintaan Verifier dan origin panggilan, yang Anda ekstrak dengan PendingIntentHandler.retrieveProviderGetCredentialRequest fungsi. Tindakan ini akan menampilkan ProviderGetCredentialRequest yang berisi semua informasi yang terkait dengan permintaan pemverifikasi. Ada tiga komponen utama:

Anda dapat memproses permintaan presentasi dengan kode berikut:

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

Contohnya dapat dilihat di contoh wallet.

Memeriksa identitas pemverifikasi

Pertama, ekstrak ProviderGetCredentialRequest dari intent:

val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)

Periksa aplikasi dengan hak istimewa: Aplikasi dengan hak istimewa (seperti browser web) dapat melakukan panggilan atas nama pemverifikasi lain dengan menetapkan parameter origin. Untuk mengambil origin ini, teruskan daftar pemanggil dengan hak istimewa dan tepercaya (daftar yang diizinkan dalam format JSON) ke CallingAppInfo getOrigin API.

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

Jika origin tidak kosong: Origin akan ditampilkan jika packageName dan sidik jari sertifikat cocok dengan aplikasi dalam daftar yang diizinkan. Anggap ini sebagai panggilan dengan hak istimewa dan tetapkan origin ini pada respons OpenID4VP, bukan menghitung origin menggunakan tanda tangan aplikasi panggilan.

Jika origin kosong: Permintaan verifikator berasal dari aplikasi Android. Origin yang dipetakan ke respons OpenID4VP harus dihitung sebagai 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"

Merender UI Pemegang

Saat kredensial dipilih, aplikasi pemegang akan dipanggil, yang memandu pengguna melalui UI aplikasi. Ada dua cara standar untuk menangani alur kerja ini:

  • Jika autentikasi pengguna tambahan diperlukan untuk merilis kredensial, gunakan BiometricPrompt API. Hal ini ditunjukkan dalam contoh.
  • Jika tidak, banyak dompet memilih pengembalian otomatis dengan merender aktivitas kosong yang langsung meneruskan data kembali ke aplikasi panggilan. Hal ini meminimalkan klik pengguna dan memberikan pengalaman yang lebih lancar.

Menampilkan respons kredensial

Setelah aplikasi pemegang Anda siap mengirimkan kembali hasilnya, selesaikan aktivitas dengan respons kredensial:

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

Jika ada pengecualian, Anda juga dapat mengirim pengecualian kredensial:

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

Lihat aplikasi contoh untuk mengetahui contoh lengkap menampilkan respons kredensial dalam konteks.