Интегрируйте приложение Holder с Credential Manager.

API Credential Manager Holder позволяет вашему приложению-держателю (также называемому «кошельком») для Android управлять цифровыми учетными данными и представлять их проверяющим.

Изображение, демонстрирующее пользовательский интерфейс цифровых учетных данных в Диспетчере учетных данных.
Рисунок 1. Пользовательский интерфейс выбора цифровых учетных данных.

Основные концепции

Перед использованием API Holder важно ознакомиться со следующими понятиями.

Форматы учетных данных

В приложениях-владельцах учетные данные могут храниться в различных форматах. Эти форматы представляют собой спецификации того, как должны быть представлены учетные данные, и каждый из них содержит следующую информацию об учетных данных:

  • Тип: Категория, например, диплом университета или водительское удостоверение.
  • Свойства: Атрибуты, такие как имя и фамилия.
  • Кодировка: способ структурирования учетных данных, например, SD-JWT или mdoc.
  • Проверка достоверности: Метод криптографической проверки подлинности учетных данных.

В каждом формате учетных данных кодирование и проверка выполняются немного по-разному, но функционально они одинаковы.

Реестр поддерживает два формата:

При использовании Credential Manager верификатор может отправить запрос OpenID4VP для SD-JWT и mdocs. Выбор зависит от сценария использования и отраслевых предпочтений.

Регистрация метаданных учетных данных

Менеджер учетных данных не хранит учетные данные владельца напрямую, а лишь их метаданные . Приложение-владелец должно сначала зарегистрировать метаданные учетных данных в Менеджере учетных данных с помощью RegistryManager . Этот процесс регистрации создает запись в реестре, которая выполняет две ключевые функции:

  • Сопоставление: Зарегистрированные метаданные учетных данных используются для сопоставления с будущими запросами проверяющих.
  • Отображение: Настраиваемые элементы пользовательского интерфейса отображаются пользователю в интерфейсе выбора учетных данных.

Для регистрации цифровых учетных данных вы будете использовать класс OpenId4VpRegistry , поскольку он поддерживает как формат mdoc, так и SD-JWT. Верификаторы будут отправлять запросы OpenID4VP для получения этих учетных данных.

Зарегистрируйте учетные данные своего приложения.

Для использования API Credential Manager Holder добавьте следующие зависимости в скрипт сборки вашего модуля приложения:

классный

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"

}

Котлин

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 ). Теперь вам нужно будет преобразовать их в наши эквиваленты Jetpack DigitalCredentialEntry в зависимости от их формата — SdJwtEntry или MdocEntry для SD-JWT или mdoc соответственно.

Добавить 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
}

Добавить файлы mdocs в реестр

Привяжите локальные учетные данные 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 — регистрация зашифрованного идентификатора учетных данных, благодаря чему расшифровать значение сможете только вы.
  • Поля пользовательского интерфейса для обоих форматов должны быть локализованы.

Зарегистрируйте свои учетные данные

Объедините преобразованные записи и зарегистрируйте запрос в 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
}

Вы зарегистрировали свои учетные данные в Менеджере учетных данных.

Управление метаданными приложений

Метаданные, которые ваше приложение-держатель регистрирует в CredentialManager, имеют следующие свойства:

  • Сохранение данных: информация сохраняется локально и не удаляется после перезагрузки.
  • Изолированное хранилище: записи реестра каждого приложения хранятся отдельно, это означает, что одно приложение не может изменять записи реестра другого приложения.
  • Обновления по ключу: записи реестра каждого приложения имеют id , что позволяет повторно идентифицировать, обновлять или удалять записи.
  • Обновление метаданных: Рекомендуется обновлять сохраненные метаданные при каждом изменении или первой загрузке приложения. Если реестр вызывается несколько раз под одним и тем же id , последний вызов перезапишет все предыдущие записи. Для обновления выполните повторную регистрацию, не удаляя предварительно старую запись.

Необязательно: Создать сопоставитель

Сопоставитель — это исполняемый файл Wasm, который Credential Manager запускает в изолированной среде для фильтрации зарегистрированных учетных данных в соответствии с входящим запросом Verifier.

  • Сопоставитель по умолчанию: Класс OpenId4VpRegistry автоматически включает в себя стандартный сопоставитель OpenId4VP ( OpenId4VpDefaults.DEFAULT_MATCHER ) при его создании. Для всех стандартных сценариев использования OpenID4VP библиотека обрабатывает сопоставление автоматически.
  • Пользовательский сопоставитель: Пользовательский сопоставитель следует реализовывать только в том случае, если вы поддерживаете нестандартный протокол, требующий собственной логики сопоставления.

Обработайте выбранные учетные данные

Когда пользователь выбирает учетные данные, ваше приложение-держатель должно обработать запрос. Вам потребуется определить Activity, которая будет прослушивать фильтр намерения androidx.credentials.registry.provider.action.GET_CREDENTIAL . Наш пример кошелька демонстрирует эту процедуру .

Интент запускает вашу активность с запросом Verifier и источником вызова, которые вы извлекаете с помощью функции PendingIntentHandler.retrieveProviderGetCredentialRequest . Эта функция возвращает объект ProviderGetCredentialRequest , содержащий всю информацию, связанную с запросом Verifier. Есть три ключевых компонента:

  • Приложение, совершившее звонок: приложение, отправившее запрос, можно получить с помощью getCallingAppInfo .
  • Выбранные учетные данные: информация о том, какого кандидата выбрал пользователь, полученная с помощью selectedCredentialSet extension method ; она будет соответствовать идентификатору учетных данных, которые вы зарегистрировали.
  • Конкретные запросы: конкретный запрос, сделанный проверяющим, извлекаемый из метода getCredentialOptions . Для потока запросов цифровых учетных данных вы можете ожидать найти в этом списке только один GetDigitalCredentialOption .

Чаще всего проверяющий отправляет запрос на предъявление цифровых учетных данных, который можно обработать с помощью следующего примера кода:

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. Проверка наличия привилегированного источника: Привилегированные приложения (например, веб-браузеры) могут совершать вызовы от имени других верификаторов, установив параметр origin. Чтобы получить этот источник, необходимо передать список привилегированных и доверенных вызывающих сторон (разрешенный список в формате JSON) в API getOrigin() класса CallingAppInfo .
val origin = request?.callingAppInfo?.getOrigin(
    privilegedAppsJson // Your allow list JSON
)

Если поле origin не пустое: значение origin возвращается, если packageName и отпечатки сертификатов, полученные из signingInfo совпадают с таковыми у приложения, найденного в списке разрешенных приложений, переданном в API getOrigin() . После получения значения origin приложение-провайдер должно рассматривать это как привилегированный вызов и установить это значение origin в ответе OpenID4VP, вместо того чтобы вычислять origin с помощью подписи вызывающего приложения.

Google Password Manager использует общедоступный список разрешенных вызовов функции getOrigin() . В качестве поставщика учетных данных вы можете использовать этот список или предоставить свой собственный в формате JSON, описанном в API. Выбор используемого списка остается за поставщиком. Для получения привилегированного доступа к учетным данным сторонних поставщиков обратитесь к документации, предоставленной этой третьей стороной.

Если поле origin пустое, запрос на проверку исходит от приложения Android. Исходное состояние приложения, которое необходимо указать в ответе OpenID4VP, должно вычисляться как android:apk-key-hash:<encoded SHA 256 fingerprint> .

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"

Отобразить пользовательский интерфейс держателя

При выборе учетных данных запускается приложение, содержащее эти данные, которое направляет пользователя по пользовательскому интерфейсу приложения. Существует два стандартных способа обработки этого процесса:

  • Если для предоставления учетных данных требуется дополнительная аутентификация пользователя, используйте API BiometricPrompt . Это продемонстрировано в примере .
  • В противном случае, многие кошельки выбирают скрытую отправку данных, создавая пустую активность, которая немедленно передает данные обратно в вызывающее приложение. Это минимизирует количество кликов пользователя и обеспечивает более удобный интерфейс.

Верните ответ с учетными данными.

Как только ваше приложение будет готово отправить результат обратно, завершите операцию, отправив ответ с учетными данными:

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

Для получения полного примера возврата ответа с учетными данными в контексте обратитесь к демонстрационному приложению .