Para ajudar a confirmar as intenções dos usuários quando eles iniciarem uma transação sensível, como fazer um pagamento, os dispositivos com suporte que executam o Android 9 (nível 28 da API) ou versões mais recentes permitem o uso da Confirmação protegida pelo Android. Ao usar esse fluxo de trabalho, o app mostra uma solicitação ao usuário pedindo que ele aprove uma breve declaração que reafirma a intenção de concluir a transação sensível.
Se o usuário aceitar a declaração, seu app poderá usar uma chave do Android Keystore para assinar a mensagem mostrada na caixa de diálogo. A assinatura indica, com confiança muito alta, que o usuário conferiu a declaração e concordou com ela.
Atenção: a Confirmação protegida pelo Android não oferece um canal de informações seguro ao usuário. O app não pode presumir nenhuma garantia de confidencialidade além daquelas que a plataforma Android oferece. E, principalmente, não use esse fluxo de trabalho para exibir informações sensíveis que você normalmente não mostraria no dispositivo do usuário.
Depois que o usuário confirma a mensagem, a integridade dela é garantida, mas seu app ainda precisa usar a criptografia de dados em trânsito para proteger a confidencialidade da mensagem assinada.
Para oferecer suporte à confirmação de alta confiabilidade do usuário no seu app, faça o seguinte:
Gere uma chave de assinatura assimétrica usando a classe
KeyGenParameterSpec.Builder
. Ao criar a chave, transmitatrue
parasetUserConfirmationRequired()
. Além disso, chamesetAttestationChallenge()
, transmitindo um valor de desafio adequado fornecido pela parte confiável.Registre a chave recém-gerada e o certificado de atestado dela junto à parte confiável envolvida.
Envie os detalhes da transação ao seu servidor e faça com que ele gere e retorne um objeto grande binário (BLOB) de dados extras. Dados extras podem incluir os dados a serem confirmados ou dicas de análise, como a localidade da string da solicitação.
Para uma implementação mais segura, o BLOB precisa conter um valor de uso único criptográfico para proteção contra ataques de repetição e para eliminar a ambiguidade de transações.
Configure o objeto
ConfirmationCallback
, que informa ao app quando o usuário aceitou a solicitação mostrada em uma caixa de diálogo de confirmação:Kotlin
class MyConfirmationCallback : ConfirmationCallback() { override fun onConfirmed(dataThatWasConfirmed: ByteArray?) { super.onConfirmed(dataThatWasConfirmed) // Sign dataThatWasConfirmed using your generated signing key. // By completing this process, you generate a signed statement. } override fun onDismissed() { super.onDismissed() // Handle case where user declined the prompt in the // confirmation dialog. } override fun onCanceled() { super.onCanceled() // Handle case where your app closed the dialog before the user // responded to the prompt. } override fun onError(e: Exception?) { super.onError(e) // Handle the exception that the callback captured. } }
Java
public class MyConfirmationCallback extends ConfirmationCallback { @Override public void onConfirmed(@NonNull byte[] dataThatWasConfirmed) { super.onConfirmed(dataThatWasConfirmed); // Sign dataThatWasConfirmed using your generated signing key. // By completing this process, you generate a signed statement. } @Override public void onDismissed() { super.onDismissed(); // Handle case where user declined the prompt in the // confirmation dialog. } @Override public void onCanceled() { super.onCanceled(); // Handle case where your app closed the dialog before the user // responded to the prompt. } @Override public void onError(Throwable e) { super.onError(e); // Handle the exception that the callback captured. } }
Se o usuário aprovar a caixa de diálogo, o callback de
onConfirmed()
será chamado. O BLOBdataThatWasConfirmed
é uma estrutura de dados CBOR (link em inglês) que contém, entre outros detalhes, o texto da solicitação mostrada ao usuário, além dos dados extras que você transmitiu no builderConfirmationPrompt
. Use a chave criada anteriormente para assinar o BLOBdataThatWasConfirmed
e, em seguida, transmita esse BLOB com os detalhes da assinatura e da transação para a parte confiável.Para aproveitar ao máximo a garantia de segurança oferecida pela Confirmação protegida pelo Android, a parte confiável precisa realizar as etapas abaixo ao receber uma mensagem assinada:
- Verificar a assinatura na mensagem, bem como a cadeia de certificados de atestado da chave de assinatura.
- Verificar se o certificado de atestado tem a
flag
TRUSTED_CONFIRMATION_REQUIRED
definida, o que indica que a chave de assinatura exige a confirmação de um usuário confiável. Se a chave de assinatura for RSA, ela não pode ter as propriedadesPURPOSE_ENCRYPT
ouPURPOSE_DECRYPT
. - Verificar
extraData
para ter certeza de que essa mensagem de confirmação pertence a uma nova solicitação e ainda não foi processada. Essa etapa oferece proteção contra ataques de repetição. - Analisar se o
promptText
contém informações sobre a ação ou solicitação confirmada. Lembre-se de quepromptText
é a única parte da mensagem realmente confirmada pelo usuário. A parte confiável nunca pode presumir que os dados a serem confirmados incluídos emextraData
correspondem aopromptText
.
Adicione uma lógica parecida com a mostrada no snippet de código abaixo para mostrar a caixa de diálogo:
Kotlin
// This data structure varies by app type. This is an example. data class ConfirmationPromptData(val sender: String, val receiver: String, val amount: String) val myExtraData: ByteArray = byteArrayOf() val myDialogData = ConfirmationPromptData("Ashlyn", "Jordan", "$500") val threadReceivingCallback = Executor { runnable -> runnable.run() } val callback = MyConfirmationCallback() val dialog = ConfirmationPrompt.Builder(context) .setPromptText("${myDialogData.sender}, send ${myDialogData.amount} to ${myDialogData.receiver}?") .setExtraData(myExtraData) .build() dialog.presentPrompt(threadReceivingCallback, callback)
Java
// This data structure varies by app type. This is an example. class ConfirmationPromptData { String sender, receiver, amount; ConfirmationPromptData(String sender, String receiver, String amount) { this.sender = sender; this.receiver = receiver; this.amount = amount; } }; final int MY_EXTRA_DATA_LENGTH = 100; byte[] myExtraData = new byte[MY_EXTRA_DATA_LENGTH]; ConfirmationPromptData myDialogData = new ConfirmationPromptData("Ashlyn", "Jordan", "$500"); Executor threadReceivingCallback = Runnable::run; MyConfirmationCallback callback = new MyConfirmationCallback(); ConfirmationPrompt dialog = (new ConfirmationPrompt.Builder(getApplicationContext())) .setPromptText("${myDialogData.sender}, send ${myDialogData.amount} to ${myDialogData.receiver}?") .setExtraData(myExtraData) .build(); dialog.presentPrompt(threadReceivingCallback, callback);
Outros recursos
Para mais informações sobre a Confirmação protegida pelo Android, consulte os recursos abaixo.
Blogs
- Confirmação protegida pelo Android: como levar a segurança das transações para o próximo nível (em inglês)