A API Credential Manager Holder permite que seu app de titular do Android (também chamado de "carteira") gerencie e apresente credenciais digitais aos verificadores. Este guia descreve como registrar as credenciais do app de armazenamento no Gerenciador de credenciais e como processar uma solicitação de apresentação quando um usuário seleciona uma credencial. A API Holder pode ser usada no Android 6 (nível 23 da API) e versões mais recentes.
Adicionar dependências
Para usar a API Credential Manager Holder, adicione as seguintes dependências ao script build.gradle do módulo do app:
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"
}
Registrar suas credenciais
Primeiro, crie uma instância RegistryManager que será usada para registrar uma
solicitação OpenId4VpRegistry.
// Create the registry manager
val registryManager = RegistryManager.create(context)
Criar uma solicitação OpenId4VpRegistry
É necessário registrar um OpenId4VpRegistry para processar solicitações OpenID4VP de
verificadores. Supondo que você tenha tipos de dados locais carregados com as credenciais da carteira (por exemplo, sdJwtsFromStorage e mdocsFromStorage), é necessário
convertê-los nos equivalentes do Jetpack DigitalCredentialEntry:
SdJwtEntry e MdocEntry.
Adicionar Sd-JWTs ao registro
Associe cada credencial SD-JWT local a um SdJwtEntry para o registro:
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
}
Adicionar mdocs ao registro
Mapeie suas credenciais mdoc locais para o tipo MdocEntry do Jetpack:
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
}
Pontos principais sobre o código
- IDs criptografados: um método de configuração do campo
IDé registrar um identificador de credencial criptografado para que só você possa descriptografar o valor. - Localização: os campos de exibição da interface para os dois formatos precisam ser localizados.
Combinar e registrar sua solicitação
Combine as entradas convertidas e registre a solicitação com o
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.
)
Opcional: emissão inline
Se não houver credenciais do tipo solicitado disponíveis para um usuário,
você poderá mostrar uma opção para adicionar uma credencial ao titular. Para fazer isso, adicione uma
lista inlineIssuanceEntries com as credenciais aceitas ao seu
OpenId4VpRegistry:
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"
)
Registre suas credenciais com o CredentialManager.
try {
val response = registryManager.registerCredentials(openidRegistryRequest)
} catch (e: Exception) {
// Handle failure
}
Gerenciamento de metadados de apps
Os metadados que o app de carteira registra com o CredentialManager têm as seguintes
propriedades:
- Persistência: as informações são salvas localmente e mantidas após as reinicializações.
- Armazenamento em silos: os registros de cada app são armazenados separadamente, ou seja, um app não pode mudar os registros de outro.
- Atualizações com chave: os registros de cada app são identificados por um
id, o que permite reidentificar, atualizar ou excluir registros. - Atualização de metadados: é uma boa prática atualizar os metadados persistentes
sempre que o app for alterado ou carregado pela primeira vez. Se um registro for chamado várias
vezes no mesmo
id, a chamada mais recente vai substituir todos os registros anteriores. Para atualizar, registre novamente sem precisar limpar o registro antigo primeiro.
Opcional: criar um correspondente
Um matcher é um binário Wasm que o Gerenciador de credenciais executa em um sandbox para filtrar suas credenciais registradas em relação a uma solicitação de verificador recebida.
- Matcher padrão: a classe
OpenId4VpRegistryinclui automaticamente o matcherOpenId4VPpadrão (OpenId4VpDefaults.DEFAULT_MATCHER) quando você o instancia. Para todos os casos de uso padrão do OpenID4VP, a biblioteca processa a correspondência para você. - Correspondência personalizada: você só implementará uma correspondência personalizada se estiver oferecendo suporte a um protocolo não padrão que exija uma lógica de correspondência própria.
Processar uma credencial selecionada
Quando um usuário seleciona uma credencial, o app de carteira precisa processar a solicitação.
Você precisa definir uma atividade que detecte o
filtro de intent androidx.credentials.registry.provider.action.GET_CREDENTIAL.
Nossa carteira de exemplo demonstra esse procedimento.
A intent inicia sua atividade com a solicitação do Verificador e a origem da chamada,
que você extrai com a
função
PendingIntentHandler.retrieveProviderGetCredentialRequest. Isso retorna um ProviderGetCredentialRequest
com todas as informações associadas à solicitação do verificador. Há
três componentes principais:
- O app de ligação: o app que fez a solicitação, recuperável com
getCallingAppInfo. Você pode usar isso para verificar a identidade do verificador. - A credencial selecionada: informações sobre qual candidato o usuário
escolheu, recuperadas pelo
selectedCredentialSet extension method. Isso vai corresponder ao ID da credencial que você registrou. - Solicitações específicas: a solicitação específica feita pelo verificador, recuperada
do método
getCredentialOptions. Para um fluxo de solicitação de credenciais digitais, você pode encontrar um únicoGetDigitalCredentialOptionnessa lista.
Você pode processar a solicitação de apresentação com o seguinte código:
request.credentialOptions.forEach { option ->
if (option is GetDigitalCredentialOption) {
Log.i(TAG, "Got DC request: ${option.requestJson}")
processRequest(option.requestJson)
}
}
Confira um exemplo disso na carteira de exemplo.
Verificar a identidade do verificador
Primeiro, extraia o ProviderGetCredentialRequest da intent:
val request = PendingIntentHandler.retrieveProviderGetCredentialRequest(intent)
Verificar se há apps privilegiados: apps privilegiados (como navegadores da Web) podem fazer chamadas em
nome de outros verificadores definindo o parâmetro de origem. Para recuperar essa
origem, transmita uma lista de autores de chamadas privilegiados e confiáveis (uma lista de permissões no formato
JSON) para a API getOrigin CallingAppInfo.
val origin = request?.callingAppInfo?.getOrigin(
privilegedAppsJson // Your allow list JSON
)
Se origin não estiver vazio: a origem será retornada se o packageName e
as impressões digitais do certificado corresponderem a um app na sua lista de permissões. Considere isso uma
chamada privilegiada e defina essa origem na resposta do OpenID4VP em vez de
calculá-la usando a assinatura do app de chamada.
Se origin estiver vazio: a solicitação do verificador é de um app Android. A origem
mapeada para a resposta do OpenID4VP precisa ser calculada como
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"
Renderizar a interface do titular
Quando uma credencial é selecionada, o app de armazenamento é invocado, guiando o usuário pela interface do app. Há duas maneiras padrão de lidar com esse fluxo de trabalho:
- Se for necessária uma autenticação adicional do usuário para liberar a credencial, use a API BiometricPrompt. Isso é demonstrado no exemplo.
- Caso contrário, muitas carteiras optam por um retorno silencioso, renderizando uma atividade vazia que transmite imediatamente os dados de volta ao app de chamada. Isso minimiza os cliques do usuário e oferece uma experiência mais integrada.
Retornar a resposta da credencial
Quando o app de carteira estiver pronto para enviar o resultado de volta, encerre a atividade com a resposta da credencial:
PendingIntentHandler.setGetCredentialResponse(
resultData,
GetCredentialResponse(DigitalCredential(response.responseJson))
)
setResult(RESULT_OK, resultData)
finish()
Se houver uma exceção, você poderá enviar a exceção de credencial de maneira semelhante:
PendingIntentHandler.setGetCredentialException(
resultData,
GetCredentialUnknownException() // Configure the proper exception
)
setResult(RESULT_OK, resultData)
finish()
Consulte o app de exemplo para ver um exemplo completo de como retornar a resposta da credencial no contexto.