защищенное подтверждение Android

Чтобы подтверждать намерения пользователей при выполнении важных транзакций, например платежей, на поддерживаемых устройствах с Android 9 (уровень API 28) или более поздней версии можно использовать защищенное подтверждение Android. При использовании этого рабочего процесса приложение показывает пользователю запрос на подтверждение короткого заявления, подтверждающего его намерение выполнить транзакцию.

Если пользователь принимает заявление, ваше приложение может использовать ключ из Android Keystore для подписания сообщения, показанного в диалоговом окне. Подпись с высокой долей вероятности указывает на то, что пользователь ознакомился с заявлением и согласился с ним.

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

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

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

  1. Сгенерируйте асимметричный ключ подписи, используя класс KeyGenParameterSpec.Builder. При создании ключа передайте true в setUserConfirmationRequired(). Также вызовите setAttestationChallenge(), передав подходящее значение запроса, предоставленное проверяющей стороной.

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

  3. Отправьте сведения о транзакции на свой сервер, чтобы он сгенерировал и вернул большой двоичный объект (BLOB) с дополнительными данными. Дополнительные данные могут включать данные, которые необходимо подтвердить, или подсказки для синтаксического анализа, например локаль строки подсказки.

    Для повышения безопасности BLOB-объект должен содержать криптографический одноразовый код для защиты от атак повторного воспроизведения и для устранения неоднозначности транзакций.

  4. Настройте объект ConfirmationCallback, который сообщает приложению, что пользователь принял запрос в диалоговом окне подтверждения.

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

    Если пользователь подтвердит запрос, будет вызван обратный вызов onConfirmed(). Объект BLOB dataThatWasConfirmed – это структура данных CBOR, которая содержит текст запроса, показанный пользователю, и дополнительные данные, переданные в конструктор ConfirmationPrompt. Используйте ранее созданный ключ, чтобы подписать BLOB-объект dataThatWasConfirmed, а затем передайте этот объект вместе с подписью и сведениями о транзакции обратно в веб-приложение.

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

    1. Проверьте подпись сообщения и цепочку сертификатов аттестации ключа подписи.
    2. Убедитесь, что в сертификате аттестации установлен флаг TRUSTED_CONFIRMATION_REQUIRED, который указывает, что для ключа подписи требуется подтверждение доверенного пользователя. Если ключ подписи – это ключ RSA, убедитесь, что у него нет свойства PURPOSE_ENCRYPT или PURPOSE_DECRYPT.
    3. Проверьте extraData, чтобы убедиться, что это сообщение с подтверждением относится к новому запросу и ещё не обработано. Этот шаг защищает от атак повторного воспроизведения.
    4. Проанализируйте promptText, чтобы получить информацию о подтвержденном действии или запросе. Помните, что promptText – это единственная часть сообщения, которую пользователь действительно подтвердил. Полагающаяся сторона не должна предполагать, что данные, которые нужно подтвердить, в extraData соответствуют promptText.
  5. Чтобы показать диалоговое окно, добавьте логику, аналогичную приведенной в следующем фрагменте кода:

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

Дополнительные ресурсы

Дополнительную информацию о защищенном подтверждении Android можно найти в следующих ресурсах:

Блоги