Confirmação protegida pelo Android

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:

  1. Gere uma chave de assinatura assimétrica usando a classe KeyGenParameterSpec.Builder. Ao criar a chave, transmita true para setUserConfirmationRequired(). Além disso, chame setAttestationChallenge(), transmitindo um valor de desafio adequado fornecido pela parte confiável.

  2. Registre a chave recém-gerada e o certificado de atestado dela junto à parte confiável envolvida.

  3. 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.

  4. 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 BLOB dataThatWasConfirmed é 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 builder ConfirmationPrompt. Use a chave criada anteriormente para assinar o BLOB dataThatWasConfirmed 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:

    1. Verificar a assinatura na mensagem, bem como a cadeia de certificados de atestado da chave de assinatura.
    2. 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 propriedades PURPOSE_ENCRYPT ou PURPOSE_DECRYPT.
    3. 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.
    4. Analisar se o promptText contém informações sobre a ação ou solicitação confirmada. Lembre-se de que promptText é 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 em extraData correspondem ao promptText.
  5. 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