コード例 #1
0
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // do not allow screenshots of passphrase input
    // to prevent "too easy" passphrase theft by root apps
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB) {
      getWindow()
          .setFlags(WindowManager.LayoutParams.FLAG_SECURE, WindowManager.LayoutParams.FLAG_SECURE);
    }

    CryptoInputParcel cryptoInputParcel = getIntent().getParcelableExtra(EXTRA_CRYPTO_INPUT);
    if (cryptoInputParcel == null) {
      cryptoInputParcel = new CryptoInputParcel();
      getIntent().putExtra(EXTRA_CRYPTO_INPUT, cryptoInputParcel);
    }

    // this activity itself has no content view (see manifest)
    RequiredInputParcel requiredInput = getIntent().getParcelableExtra(EXTRA_REQUIRED_INPUT);
    if (requiredInput.mType != RequiredInputType.PASSPHRASE) {
      return;
    }

    // handle empty passphrases by directly returning an empty crypto input parcel
    try {
      CanonicalizedSecretKeyRing pubRing =
          new ProviderHelper(this).getCanonicalizedSecretKeyRing(requiredInput.getMasterKeyId());
      // use empty passphrase for empty passphrase
      if (pubRing.getSecretKey(requiredInput.getSubKeyId()).getSecretKeyType()
          == SecretKeyType.PASSPHRASE_EMPTY) {
        // also return passphrase back to activity
        Intent returnIntent = new Intent();
        cryptoInputParcel.mPassphrase = new Passphrase("");
        returnIntent.putExtra(RESULT_CRYPTO_INPUT, cryptoInputParcel);
        setResult(RESULT_OK, returnIntent);
        finish();
      }
    } catch (NotFoundException e) {
      Log.e(Constants.TAG, "Key not found?!", e);
      setResult(RESULT_CANCELED);
      finish();
    }
  }
コード例 #2
0
  /** Signs and/or encrypts data based on parameters of class */
  public PgpSignEncryptResult execute(
      PgpSignEncryptInputParcel input,
      CryptoInputParcel cryptoInput,
      InputData inputData,
      OutputStream outputStream) {

    int indent = 0;
    OperationLog log = new OperationLog();

    log.add(LogType.MSG_PSE, indent);
    indent += 1;

    boolean enableSignature = input.getSignatureMasterKeyId() != Constants.key.none;
    boolean enableEncryption =
        ((input.getEncryptionMasterKeyIds() != null && input.getEncryptionMasterKeyIds().length > 0)
            || input.getSymmetricPassphrase() != null);
    boolean enableCompression = (input.getCompressionId() != CompressionAlgorithmTags.UNCOMPRESSED);

    Log.d(
        Constants.TAG,
        "enableSignature:"
            + enableSignature
            + "\nenableEncryption:"
            + enableEncryption
            + "\nenableCompression:"
            + enableCompression
            + "\nenableAsciiArmorOutput:"
            + input.isEnableAsciiArmorOutput()
            + "\nisHiddenRecipients:"
            + input.isHiddenRecipients());

    // add additional key id to encryption ids (mostly to do self-encryption)
    if (enableEncryption && input.getAdditionalEncryptId() != Constants.key.none) {
      input.setEncryptionMasterKeyIds(
          Arrays.copyOf(
              input.getEncryptionMasterKeyIds(), input.getEncryptionMasterKeyIds().length + 1));
      input.getEncryptionMasterKeyIds()[input.getEncryptionMasterKeyIds().length - 1] =
          input.getAdditionalEncryptId();
    }

    ArmoredOutputStream armorOut = null;
    OutputStream out;
    if (input.isEnableAsciiArmorOutput()) {
      armorOut = new ArmoredOutputStream(new BufferedOutputStream(outputStream, 1 << 16));
      if (input.getVersionHeader() != null) {
        armorOut.setHeader("Version", input.getVersionHeader());
      }
      // if we have a charset, put it in the header
      if (input.getCharset() != null) {
        armorOut.setHeader("Charset", input.getCharset());
      }
      out = armorOut;
    } else {
      out = outputStream;
    }

    /* Get keys for signature generation for later usage */
    CanonicalizedSecretKey signingKey = null;
    if (enableSignature) {

      updateProgress(R.string.progress_extracting_signature_key, 0, 100);

      try {
        // fetch the indicated master key id (the one whose name we sign in)
        CanonicalizedSecretKeyRing signingKeyRing =
            mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId());

        // fetch the specific subkey to sign with, or just use the master key if none specified
        signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());

        // Make sure we are allowed to sign here!
        if (!signingKey.canSign()) {
          log.add(LogType.MSG_PSE_ERROR_KEY_SIGN, indent);
          return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
        }

        switch (signingKey.getSecretKeyType()) {
          case DIVERT_TO_CARD:
          case PASSPHRASE_EMPTY:
            {
              if (!signingKey.unlock(new Passphrase())) {
                throw new AssertionError(
                    "PASSPHRASE_EMPTY/DIVERT_TO_CARD keyphrase not unlocked with empty passphrase."
                        + " This is a programming error!");
              }
              break;
            }

          case PIN:
          case PATTERN:
          case PASSPHRASE:
            {
              Passphrase localPassphrase = cryptoInput.getPassphrase();
              if (localPassphrase == null) {
                try {
                  localPassphrase =
                      getCachedPassphrase(signingKeyRing.getMasterKeyId(), signingKey.getKeyId());
                } catch (PassphraseCacheInterface.NoSecretKeyException ignored) {
                }
              }
              if (localPassphrase == null) {
                log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
                return new PgpSignEncryptResult(
                    log,
                    RequiredInputParcel.createRequiredSignPassphrase(
                        signingKeyRing.getMasterKeyId(),
                        signingKey.getKeyId(),
                        cryptoInput.getSignatureTime()),
                    cryptoInput);
              }
              if (!signingKey.unlock(localPassphrase)) {
                log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
                return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
              }
              break;
            }

          case GNU_DUMMY:
            {
              log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
              return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
            }
          default:
            {
              throw new AssertionError("Unhandled SecretKeyType! (should not happen)");
            }
        }

      } catch (ProviderHelper.NotFoundException e) {
        log.add(LogType.MSG_PSE_ERROR_SIGN_KEY, indent);
        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
      } catch (PgpGeneralException e) {
        log.add(LogType.MSG_PSE_ERROR_UNLOCK, indent);
        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
      }

      // Use preferred hash algo
      int requestedAlgorithm = input.getSignatureHashAlgorithm();
      ArrayList<Integer> supported = signingKey.getSupportedHashAlgorithms();
      if (requestedAlgorithm == PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) {
        // get most preferred
        input.setSignatureHashAlgorithm(supported.get(0));
      } else if (!supported.contains(requestedAlgorithm)) {
        log.add(LogType.MSG_PSE_ERROR_HASH_ALGO, indent);
        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
      }
    }
    updateProgress(R.string.progress_preparing_streams, 2, 100);

    /* Initialize PGPEncryptedDataGenerator for later usage */
    PGPEncryptedDataGenerator cPk = null;
    if (enableEncryption) {

      // Use preferred encryption algo
      int algo = input.getSymmetricEncryptionAlgorithm();
      if (algo == PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED) {
        // get most preferred
        // TODO: get from recipients
        algo = PgpConstants.sPreferredSymmetricAlgorithms.get(0);
      }
      // has Integrity packet enabled!
      JcePGPDataEncryptorBuilder encryptorBuilder =
          new JcePGPDataEncryptorBuilder(algo)
              .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
              .setWithIntegrityPacket(true);

      cPk = new PGPEncryptedDataGenerator(encryptorBuilder);

      if (input.getSymmetricPassphrase() != null) {
        // Symmetric encryption
        log.add(LogType.MSG_PSE_SYMMETRIC, indent);

        JcePBEKeyEncryptionMethodGenerator symmetricEncryptionGenerator =
            new JcePBEKeyEncryptionMethodGenerator(input.getSymmetricPassphrase().getCharArray());
        cPk.addMethod(symmetricEncryptionGenerator);
      } else {
        log.add(LogType.MSG_PSE_ASYMMETRIC, indent);

        // Asymmetric encryption
        for (long id : input.getEncryptionMasterKeyIds()) {
          try {
            CanonicalizedPublicKeyRing keyRing =
                mProviderHelper.getCanonicalizedPublicKeyRing(KeyRings.buildUnifiedKeyRingUri(id));
            Set<Long> encryptSubKeyIds = keyRing.getEncryptIds();
            for (Long subKeyId : encryptSubKeyIds) {
              CanonicalizedPublicKey key = keyRing.getPublicKey(subKeyId);
              cPk.addMethod(key.getPubKeyEncryptionGenerator(input.isHiddenRecipients()));
              log.add(
                  LogType.MSG_PSE_KEY_OK,
                  indent + 1,
                  KeyFormattingUtils.convertKeyIdToHex(subKeyId));
            }
            if (encryptSubKeyIds.isEmpty()) {
              log.add(
                  LogType.MSG_PSE_KEY_WARN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id));
              if (input.isFailOnMissingEncryptionKeyIds()) {
                return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
              }
            }
          } catch (ProviderHelper.NotFoundException e) {
            log.add(
                LogType.MSG_PSE_KEY_UNKNOWN, indent + 1, KeyFormattingUtils.convertKeyIdToHex(id));
            if (input.isFailOnMissingEncryptionKeyIds()) {
              return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
            }
          }
        }
      }
    }

    /* Initialize signature generator object for later usage */
    PGPSignatureGenerator signatureGenerator = null;
    if (enableSignature) {
      updateProgress(R.string.progress_preparing_signature, 4, 100);

      try {
        boolean cleartext =
            input.isCleartextSignature() && input.isEnableAsciiArmorOutput() && !enableEncryption;
        signatureGenerator =
            signingKey.getDataSignatureGenerator(
                input.getSignatureHashAlgorithm(),
                cleartext,
                cryptoInput.getCryptoData(),
                cryptoInput.getSignatureTime());
      } catch (PgpGeneralException e) {
        log.add(LogType.MSG_PSE_ERROR_NFC, indent);
        return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
      }
    }

    ProgressScaler progressScaler = new ProgressScaler(mProgressable, 8, 95, 100);
    PGPCompressedDataGenerator compressGen = null;
    OutputStream pOut;
    OutputStream encryptionOut = null;
    BCPGOutputStream bcpgOut;

    ByteArrayOutputStream detachedByteOut = null;
    ArmoredOutputStream detachedArmorOut = null;
    BCPGOutputStream detachedBcpgOut = null;

    try {

      if (enableEncryption) {
        /* actual encryption */
        updateProgress(R.string.progress_encrypting, 8, 100);
        log.add(enableSignature ? LogType.MSG_PSE_SIGCRYPTING : LogType.MSG_PSE_ENCRYPTING, indent);
        indent += 1;

        encryptionOut = cPk.open(out, new byte[1 << 16]);

        if (enableCompression) {
          log.add(LogType.MSG_PSE_COMPRESSING, indent);
          compressGen = new PGPCompressedDataGenerator(input.getCompressionId());
          bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
        } else {
          bcpgOut = new BCPGOutputStream(encryptionOut);
        }

        if (enableSignature) {
          signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);
        }

        PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
        char literalDataFormatTag;
        if (input.isCleartextSignature()) {
          literalDataFormatTag = PGPLiteralData.UTF8;
        } else {
          literalDataFormatTag = PGPLiteralData.BINARY;
        }
        pOut =
            literalGen.open(
                bcpgOut,
                literalDataFormatTag,
                inputData.getOriginalFilename(),
                new Date(),
                new byte[1 << 16]);

        long alreadyWritten = 0;
        int length;
        byte[] buffer = new byte[1 << 16];
        InputStream in = inputData.getInputStream();
        while ((length = in.read(buffer)) > 0) {
          pOut.write(buffer, 0, length);

          // update signature buffer if signature is requested
          if (enableSignature) {
            signatureGenerator.update(buffer, 0, length);
          }

          alreadyWritten += length;
          if (inputData.getSize() > 0) {
            long progress = 100 * alreadyWritten / inputData.getSize();
            progressScaler.setProgress((int) progress, 100);
          }
        }

        literalGen.close();
        indent -= 1;

      } else if (enableSignature
          && input.isCleartextSignature()
          && input.isEnableAsciiArmorOutput()) {
        /* cleartext signature: sign-only of ascii text */

        updateProgress(R.string.progress_signing, 8, 100);
        log.add(LogType.MSG_PSE_SIGNING_CLEARTEXT, indent);

        // write -----BEGIN PGP SIGNED MESSAGE-----
        armorOut.beginClearText(input.getSignatureHashAlgorithm());

        InputStream in = inputData.getInputStream();
        final BufferedReader reader = new BufferedReader(new InputStreamReader(in));

        // update signature buffer with first line
        processLine(reader.readLine(), armorOut, signatureGenerator);

        // TODO: progress: fake annealing?
        while (true) {
          String line = reader.readLine();

          // end cleartext signature with newline, see http://tools.ietf.org/html/rfc4880#section-7
          if (line == null) {
            armorOut.write(NEW_LINE);
            break;
          }

          armorOut.write(NEW_LINE);

          // update signature buffer with input line
          signatureGenerator.update(NEW_LINE);
          processLine(line, armorOut, signatureGenerator);
        }

        armorOut.endClearText();

        pOut = new BCPGOutputStream(armorOut);
      } else if (enableSignature && input.isDetachedSignature()) {
        /* detached signature */

        updateProgress(R.string.progress_signing, 8, 100);
        log.add(LogType.MSG_PSE_SIGNING_DETACHED, indent);

        InputStream in = inputData.getInputStream();

        // handle output stream separately for detached signatures
        detachedByteOut = new ByteArrayOutputStream();
        OutputStream detachedOut = detachedByteOut;
        if (input.isEnableAsciiArmorOutput()) {
          detachedArmorOut =
              new ArmoredOutputStream(new BufferedOutputStream(detachedOut, 1 << 16));
          if (input.getVersionHeader() != null) {
            detachedArmorOut.setHeader("Version", input.getVersionHeader());
          }

          detachedOut = detachedArmorOut;
        }
        detachedBcpgOut = new BCPGOutputStream(detachedOut);

        long alreadyWritten = 0;
        int length;
        byte[] buffer = new byte[1 << 16];
        while ((length = in.read(buffer)) > 0) {
          // no output stream is written, no changed to original data!

          signatureGenerator.update(buffer, 0, length);

          alreadyWritten += length;
          if (inputData.getSize() > 0) {
            long progress = 100 * alreadyWritten / inputData.getSize();
            progressScaler.setProgress((int) progress, 100);
          }
        }

        pOut = null;
      } else if (enableSignature && !input.isCleartextSignature() && !input.isDetachedSignature()) {
        /* sign-only binary (files/data stream) */

        updateProgress(R.string.progress_signing, 8, 100);
        log.add(LogType.MSG_PSE_SIGNING, indent);

        InputStream in = inputData.getInputStream();

        if (enableCompression) {
          compressGen = new PGPCompressedDataGenerator(input.getCompressionId());
          bcpgOut = new BCPGOutputStream(compressGen.open(out));
        } else {
          bcpgOut = new BCPGOutputStream(out);
        }

        signatureGenerator.generateOnePassVersion(false).encode(bcpgOut);

        PGPLiteralDataGenerator literalGen = new PGPLiteralDataGenerator();
        pOut =
            literalGen.open(
                bcpgOut,
                PGPLiteralData.BINARY,
                inputData.getOriginalFilename(),
                new Date(),
                new byte[1 << 16]);

        long alreadyWritten = 0;
        int length;
        byte[] buffer = new byte[1 << 16];
        while ((length = in.read(buffer)) > 0) {
          pOut.write(buffer, 0, length);

          signatureGenerator.update(buffer, 0, length);

          alreadyWritten += length;
          if (inputData.getSize() > 0) {
            long progress = 100 * alreadyWritten / inputData.getSize();
            progressScaler.setProgress((int) progress, 100);
          }
        }

        literalGen.close();
      } else {
        pOut = null;
        // TODO: Is this log right?
        log.add(LogType.MSG_PSE_CLEARSIGN_ONLY, indent);
      }

      if (enableSignature) {
        updateProgress(R.string.progress_generating_signature, 95, 100);
        try {
          if (detachedBcpgOut != null) {
            signatureGenerator.generate().encode(detachedBcpgOut);
          } else {
            signatureGenerator.generate().encode(pOut);
          }
        } catch (NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded e) {
          // this secret key diverts to a OpenPGP card, throw exception with hash that will be
          // signed
          log.add(LogType.MSG_PSE_PENDING_NFC, indent);
          return new PgpSignEncryptResult(
              log,
              RequiredInputParcel.createNfcSignOperation(
                  signingKey.getRing().getMasterKeyId(),
                  signingKey.getKeyId(),
                  e.hashToSign,
                  e.hashAlgo,
                  cryptoInput.getSignatureTime()),
              cryptoInput);
        }
      }

      // closing outputs
      // NOTE: closing needs to be done in the correct order!
      if (encryptionOut != null) {
        if (compressGen != null) {
          compressGen.close();
        }

        encryptionOut.close();
      }
      // Note: Closing ArmoredOutputStream does not close the underlying stream
      if (armorOut != null) {
        armorOut.close();
      }
      // Note: Closing ArmoredOutputStream does not close the underlying stream
      if (detachedArmorOut != null) {
        detachedArmorOut.close();
      }
      // Also closes detachedBcpgOut
      if (detachedByteOut != null) {
        detachedByteOut.close();
      }
      if (out != null) {
        out.close();
      }
      if (outputStream != null) {
        outputStream.close();
      }

    } catch (SignatureException e) {
      log.add(LogType.MSG_PSE_ERROR_SIG, indent);
      return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
    } catch (PGPException e) {
      log.add(LogType.MSG_PSE_ERROR_PGP, indent);
      return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
    } catch (IOException e) {
      log.add(LogType.MSG_PSE_ERROR_IO, indent);
      return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
    }

    updateProgress(R.string.progress_done, 100, 100);

    log.add(LogType.MSG_PSE_OK, indent);
    PgpSignEncryptResult result = new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_OK, log);
    if (detachedByteOut != null) {
      try {
        detachedByteOut.flush();
        detachedByteOut.close();
      } catch (IOException e) {
        // silently catch
      }
      result.setDetachedSignature(detachedByteOut.toByteArray());
    }
    return result;
  }
コード例 #3
0
    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
      final Activity activity = getActivity();

      ContextThemeWrapper theme = ThemeChanger.getDialogThemeWrapper(activity);

      mRequiredInput = getArguments().getParcelable(EXTRA_REQUIRED_INPUT);

      CustomAlertDialogBuilder alert = new CustomAlertDialogBuilder(theme);

      // No title, see http://www.google.com/design/spec/components/dialogs.html#dialogs-alerts
      // alert.setTitle()

      if (mRequiredInput.mType == RequiredInputType.BACKUP_CODE) {
        LayoutInflater inflater = LayoutInflater.from(theme);
        View view = inflater.inflate(R.layout.passphrase_dialog_backup_code, null);
        alert.setView(view);

        mBackupCodeEditText = new EditText[4];
        mBackupCodeEditText[0] = (EditText) view.findViewById(R.id.backup_code_1);
        mBackupCodeEditText[1] = (EditText) view.findViewById(R.id.backup_code_2);
        mBackupCodeEditText[2] = (EditText) view.findViewById(R.id.backup_code_3);
        mBackupCodeEditText[3] = (EditText) view.findViewById(R.id.backup_code_4);
        setupEditTextFocusNext(mBackupCodeEditText);

        AlertDialog dialog = alert.create();
        dialog.setButton(
            DialogInterface.BUTTON_POSITIVE,
            activity.getString(R.string.btn_unlock),
            (DialogInterface.OnClickListener) null);
        return dialog;
      }

      long subKeyId = mRequiredInput.getSubKeyId();

      LayoutInflater inflater = LayoutInflater.from(theme);
      mLayout = (ViewAnimator) inflater.inflate(R.layout.passphrase_dialog, null);
      alert.setView(mLayout);

      mPassphraseText = (TextView) mLayout.findViewById(R.id.passphrase_text);
      mPassphraseEditText = (EditText) mLayout.findViewById(R.id.passphrase_passphrase);

      View vTimeToLiveLayout = mLayout.findViewById(R.id.remember_layout);
      vTimeToLiveLayout.setVisibility(mRequiredInput.mSkipCaching ? View.GONE : View.VISIBLE);

      mTimeToLiveSpinner = (CacheTTLSpinner) mLayout.findViewById(R.id.ttl_spinner);

      alert.setNegativeButton(
          android.R.string.cancel,
          new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int id) {
              dialog.cancel();
            }
          });

      String userId;
      CanonicalizedSecretKey.SecretKeyType keyType =
          CanonicalizedSecretKey.SecretKeyType.PASSPHRASE;

      String message;
      String hint;
      if (mRequiredInput.mType == RequiredInputType.PASSPHRASE_SYMMETRIC) {
        message = getString(R.string.passphrase_for_symmetric_encryption);
        hint = getString(R.string.label_passphrase);
      } else {
        try {
          ProviderHelper helper = new ProviderHelper(activity);
          mSecretRing =
              helper.getCanonicalizedSecretKeyRing(
                  KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId));
          // yes the inner try/catch block is necessary, otherwise the final variable
          // above can't be statically verified to have been set in all cases because
          // the catch clause doesn't return.
          try {
            String mainUserId = mSecretRing.getPrimaryUserIdWithFallback();
            KeyRing.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId);
            if (mainUserIdSplit.name != null) {
              userId = mainUserIdSplit.name;
            } else {
              userId = getString(R.string.user_id_no_name);
            }
          } catch (PgpKeyNotFoundException e) {
            userId = null;
          }

          keyType = mSecretRing.getSecretKey(subKeyId).getSecretKeyType();
          switch (keyType) {
            case PASSPHRASE:
              message = getString(R.string.passphrase_for, userId);
              hint = getString(R.string.label_passphrase);
              break;
            case PIN:
              message = getString(R.string.pin_for, userId);
              hint = getString(R.string.label_pin);
              break;
            case DIVERT_TO_CARD:
              message = getString(R.string.yubikey_pin_for, userId);
              hint = getString(R.string.label_pin);
              break;
              // special case: empty passphrase just returns the empty passphrase
            case PASSPHRASE_EMPTY:
              finishCaching(new Passphrase(""));
            default:
              throw new AssertionError("Unhandled SecretKeyType (should not happen)");
          }

        } catch (ProviderHelper.NotFoundException e) {
          alert.setTitle(R.string.title_key_not_found);
          alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId()));
          alert.setPositiveButton(
              android.R.string.ok,
              new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int which) {
                  dismiss();
                }
              });
          alert.setCancelable(false);
          return alert.create();
        }
      }

      mPassphraseText.setText(message);
      mPassphraseEditText.setHint(hint);

      // Hack to open keyboard.
      // This is the only method that I found to work across all Android versions
      // http://turbomanage.wordpress.com/2012/05/02/show-soft-keyboard-automatically-when-edittext-receives-focus/
      // Notes: * onCreateView can't be used because we want to add buttons to the dialog
      //        * opening in onActivityCreated does not work on Android 4.4
      mPassphraseEditText.setOnFocusChangeListener(
          new View.OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
              mPassphraseEditText.post(
                  new Runnable() {
                    @Override
                    public void run() {
                      if (getActivity() == null || mPassphraseEditText == null) {
                        return;
                      }
                      InputMethodManager imm =
                          (InputMethodManager)
                              getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
                      imm.showSoftInput(mPassphraseEditText, InputMethodManager.SHOW_IMPLICIT);
                    }
                  });
            }
          });
      mPassphraseEditText.requestFocus();

      mPassphraseEditText.setImeActionLabel(
          getString(android.R.string.ok), EditorInfo.IME_ACTION_DONE);
      mPassphraseEditText.setOnEditorActionListener(this);

      if ((keyType == CanonicalizedSecretKey.SecretKeyType.DIVERT_TO_CARD
              && Preferences.getPreferences(activity).useNumKeypadForYubiKeyPin())
          || keyType == CanonicalizedSecretKey.SecretKeyType.PIN) {
        mPassphraseEditText.setInputType(InputType.TYPE_CLASS_NUMBER);
        mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());
      } else {
        mPassphraseEditText.setRawInputType(
            InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD);
      }

      mPassphraseEditText.setTransformationMethod(PasswordTransformationMethod.getInstance());

      AlertDialog dialog = alert.create();
      dialog.setButton(
          DialogInterface.BUTTON_POSITIVE,
          activity.getString(R.string.btn_unlock),
          (DialogInterface.OnClickListener) null);

      return dialog;
    }