API Credential Manager Holder позволяет вашему приложению-держателю (также называемому «кошельком») для Android управлять цифровыми учетными данными и представлять их проверяющим.
Основные концепции
Перед использованием API Holder важно ознакомиться со следующими понятиями.
Форматы учетных данных
В приложениях-владельцах учетные данные могут храниться в различных форматах. Эти форматы представляют собой спецификации того, как должны быть представлены учетные данные, и каждый из них содержит следующую информацию об учетных данных:
- Тип: Категория, например, диплом университета или водительское удостоверение.
- Свойства: Атрибуты, такие как имя и фамилия.
- Кодировка: способ структурирования учетных данных, например, SD-JWT или mdoc.
- Проверка достоверности: Метод криптографической проверки подлинности учетных данных.
В каждом формате учетных данных кодирование и проверка выполняются немного по-разному, но функционально они одинаковы.
Реестр поддерживает два формата:
- SD-JWT: соответствует спецификации IETF SD-JWT-based Verifiable Credentials (SD-JWT VC) .
- Мобильные документы (mdocs): соответствуют спецификации ISO/IEC 18013-5:2021 .
При использовании 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)
}
}
Пример этого можно увидеть в образце кошелька .
Проверьте личность проверяющего.
- Выделите объект
ProviderGetCredentialRequestиз интента:
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
- Проверка наличия привилегированного источника: Привилегированные приложения (например, веб-браузеры) могут совершать вызовы от имени других верификаторов, установив параметр 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()
Для получения полного примера возврата ответа с учетными данными в контексте обратитесь к демонстрационному приложению .