@Override
  public void onActivityCreated(Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);

    // set displayed values
    if (getArguments() != null) {
      if (getArguments().containsKey(ARG_QUERY)) {
        String query = getArguments().getString(ARG_QUERY);
        mQueryEditText.setText(query, TextView.BufferType.EDITABLE);

        Log.d(Constants.TAG, "query: " + query);
      }

      if (getArguments().containsKey(ARG_KEYSERVER)) {
        String keyserver = getArguments().getString(ARG_KEYSERVER);
        int keyserverPos = mServerAdapter.getPosition(keyserver);
        mServerSpinner.setSelection(keyserverPos);

        Log.d(Constants.TAG, "keyserver: " + keyserver);
      }

      if (getArguments().getBoolean(ARG_DISABLE_QUERY_EDIT, false)) {
        mQueryEditText.setEnabled(false);
      }
    }
  }
  private void loadData(Bundle savedInstanceState, Uri appUri) {
    mAppSettings = new ApiDataAccessObject(this).getApiAppSettings(appUri);

    // get application name and icon from package manager
    String appName;
    Drawable appIcon = null;
    PackageManager pm = getApplicationContext().getPackageManager();
    try {
      ApplicationInfo ai = pm.getApplicationInfo(mAppSettings.getPackageName(), 0);

      appName = (String) pm.getApplicationLabel(ai);
      appIcon = pm.getApplicationIcon(ai);
    } catch (PackageManager.NameNotFoundException e) {
      // fallback
      appName = mAppSettings.getPackageName();
    }
    mAppNameView.setText(appName);
    mAppIconView.setImageDrawable(appIcon);

    Uri accountsUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ACCOUNTS).build();
    Log.d(Constants.TAG, "accountsUri: " + accountsUri);
    Uri allowedKeysUri = appUri.buildUpon().appendPath(KeychainContract.PATH_ALLOWED_KEYS).build();
    Log.d(Constants.TAG, "allowedKeysUri: " + allowedKeysUri);
    startListFragments(savedInstanceState, accountsUri, allowedKeysUri);
  }
  @Override
  public void onDestroy() {
    super.onDestroy();
    Log.d(Constants.TAG, "PassphraseCacheService, onDestroy()");

    unregisterReceiver(mIntentReceiver);
  }
  private void showPassphraseDialog(final long secretKeyId) {
    // Message is received after passphrase is cached
    Handler returnHandler =
        new Handler() {
          @Override
          public void handleMessage(Message message) {
            if (message.what == PassphraseDialogFragment.MESSAGE_OKAY) {
              startSigning();
            }
          }
        };

    // Create a new Messenger for the communication back
    Messenger messenger = new Messenger(returnHandler);

    try {
      PassphraseDialogFragment passphraseDialog =
          PassphraseDialogFragment.newInstance(this, messenger, secretKeyId);

      passphraseDialog.show(getSupportFragmentManager(), "passphraseDialog");
    } catch (PgpGeneralException e) {
      Log.d(Constants.TAG, "No passphrase for this secret key!");
      // send message to handler to start certification directly
      returnHandler.sendEmptyMessage(PassphraseDialogFragment.MESSAGE_OKAY);
    }
  }
  @Override
  public void onCreate() {
    super.onCreate();
    mContext = this;
    Log.d(Constants.TAG, "PassphraseCacheService, onCreate()");

    registerReceiver();
  }
  public static void clearCachedPassphrases(Context context) {
    Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase()");

    Intent intent = new Intent(context, PassphraseCacheService.class);
    intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);

    context.startService(intent);
  }
 private void updateService() {
   if (mPassphraseCache.size() > 0) {
     startForeground(Constants.Notification.PASSPHRASE_CACHE, getNotification());
   } else {
     // stop whole service if no cached passphrases remaining
     Log.d(
         Constants.TAG,
         "PassphraseCacheService: No passphrases remaining in memory, stopping service!");
     stopForeground(true);
   }
 }
  public static void clearCachedPassphrase(Context context, long masterKeyId, long subKeyId) {
    Log.d(Constants.TAG, "PassphraseCacheService.clearCachedPassphrase() for " + masterKeyId);

    Intent intent = new Intent(context, PassphraseCacheService.class);
    intent.setAction(ACTION_PASSPHRASE_CACHE_CLEAR);

    intent.putExtra(EXTRA_KEY_ID, masterKeyId);
    intent.putExtra(EXTRA_SUBKEY_ID, subKeyId);

    context.startService(intent);
  }
 @Override
 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
   Log.d(Constants.TAG, "clicked id: " + id);
   long masterKeyId = getMasterKeyId(position);
   if (mSelectedItems.contains(masterKeyId)) {
     mSelectedItems.remove(masterKeyId);
   } else {
     mSelectedItems.add(masterKeyId);
   }
   notifyDataSetChanged();
 }
  public boolean moveCursor(int position) {
    if (position >= getItemCount() || position < -1) {
      Log.w(TAG, "Position: %d is invalid for this data set!");
      return false;
    }

    if (!mDataValid) {
      Log.d(TAG, "Attempt to move cursor over invalid data set!");
    }

    return mCursor.moveToPosition(position);
  }
  public OpenPgpSignatureResult build() {
    if (!mSignatureAvailable) {
      Log.d(Constants.TAG, "RESULT_NO_SIGNATURE");
      return OpenPgpSignatureResult.createWithNoSignature();
    }

    if (!mKnownKey) {
      Log.d(Constants.TAG, "RESULT_KEY_MISSING");
      return OpenPgpSignatureResult.createWithKeyMissing(mKeyId, mSignatureTimestamp);
    }

    if (!mValidSignature) {
      Log.d(Constants.TAG, "RESULT_INVALID_SIGNATURE");
      return OpenPgpSignatureResult.createWithInvalidSignature();
    }

    int signatureStatus;
    if (mIsKeyRevoked) {
      Log.d(Constants.TAG, "RESULT_INVALID_KEY_REVOKED");
      signatureStatus = OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED;
    } else if (mIsKeyExpired) {
      Log.d(Constants.TAG, "RESULT_INVALID_KEY_EXPIRED");
      signatureStatus = OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED;
    } else if (mInsecure) {
      Log.d(Constants.TAG, "RESULT_INVALID_INSECURE");
      signatureStatus = OpenPgpSignatureResult.RESULT_INVALID_KEY_INSECURE;
    } else if (mIsSignatureKeyCertified) {
      Log.d(Constants.TAG, "RESULT_VALID_CONFIRMED");
      signatureStatus = OpenPgpSignatureResult.RESULT_VALID_KEY_CONFIRMED;
    } else {
      Log.d(Constants.TAG, "RESULT_VALID_UNCONFIRMED");
      signatureStatus = OpenPgpSignatureResult.RESULT_VALID_KEY_UNCONFIRMED;
    }

    return OpenPgpSignatureResult.createWithValidSignature(
        signatureStatus,
        mPrimaryUserId,
        mKeyId,
        mUserIds,
        mConfirmedUserIds,
        mSenderStatusResult,
        mSignatureTimestamp);
  }
  /** Called when one specific passphrase for keyId timed out. */
  private void removeTimeoutedPassphrase(long keyId) {

    CachedPassphrase cPass = mPassphraseCache.get(keyId);
    if (cPass != null) {
      if (cPass.mPassphrase != null) {
        // clean internal char[] from memory!
        cPass.mPassphrase.removeFromMemory();
      }
      // remove passphrase object
      mPassphraseCache.remove(keyId);
    }

    Log.d(
        Constants.TAG,
        "PassphraseCacheService Timeout of keyId " + keyId + ", removed from memory!");

    updateService();
  }
  public void initValid(CanonicalizedPublicKey signingKey) {
    setSignatureAvailable(true);
    setKnownKey(true);

    CanonicalizedKeyRing signingRing = signingKey.getKeyRing();

    // from RING
    setKeyId(signingRing.getMasterKeyId());
    try {
      setPrimaryUserId(signingRing.getPrimaryUserIdWithFallback());
    } catch (PgpKeyNotFoundException e) {
      Log.d(
          Constants.TAG,
          "No primary user id in keyring with master key id " + signingRing.getMasterKeyId());
    }
    setSignatureKeyCertified(signingRing.getVerified() > 0);

    try {
      ArrayList<String> allUserIds = signingRing.getUnorderedUserIds();
      ArrayList<String> confirmedUserIds =
          mProviderHelper.getConfirmedUserIds(signingRing.getMasterKeyId());
      setUserIds(allUserIds, confirmedUserIds);

      if (mSenderAddress != null) {
        if (userIdListContainsAddress(mSenderAddress, confirmedUserIds)) {
          mSenderStatusResult = SenderStatusResult.USER_ID_CONFIRMED;
        } else if (userIdListContainsAddress(mSenderAddress, allUserIds)) {
          mSenderStatusResult = SenderStatusResult.USER_ID_UNCONFIRMED;
        } else {
          mSenderStatusResult = SenderStatusResult.USER_ID_MISSING;
        }
      } else {
        mSenderStatusResult = SenderStatusResult.UNKNOWN;
      }

    } catch (NotFoundException e) {
      throw new IllegalStateException("Key didn't exist anymore for user id query!", e);
    }

    // either master key is expired/revoked or this specific subkey is expired/revoked
    setKeyExpired(signingRing.isExpired() || signingKey.isExpired());
    setKeyRevoked(signingRing.isRevoked() || signingKey.isRevoked());
  }
  private void removeScreenLockPassphrases() {

    for (int i = 0; i < mPassphraseCache.size(); ) {
      CachedPassphrase cPass = mPassphraseCache.valueAt(i);
      if (cPass.mTimeoutMode == TimeoutMode.LOCK) {
        // remove passphrase object
        mPassphraseCache.removeAt(i);
        continue;
      }
      // only do this if we didn't remove at, which continues loop by reducing size!
      i += 1;
    }

    Log.d(
        Constants.TAG,
        "PassphraseCacheService Removing all cached-until-lock passphrases from memory!");

    updateService();
  }
  /**
   * This caches a new passphrase in memory by sending a new command to the service. An android
   * service is only run once. Thus, when the service is already started, new commands just add new
   * events to the alarm manager for new passphrases to let them timeout in the future.
   */
  public static void addCachedPassphrase(
      Context context,
      long masterKeyId,
      long subKeyId,
      Passphrase passphrase,
      String primaryUserId,
      int timeToLiveSeconds) {
    Log.d(Constants.TAG, "PassphraseCacheService.addCachedPassphrase() for " + masterKeyId);

    Intent intent = new Intent(context, PassphraseCacheService.class);
    intent.setAction(ACTION_PASSPHRASE_CACHE_ADD);

    intent.putExtra(EXTRA_TTL, timeToLiveSeconds);
    intent.putExtra(EXTRA_PASSPHRASE, passphrase);
    intent.putExtra(EXTRA_KEY_ID, masterKeyId);
    intent.putExtra(EXTRA_SUBKEY_ID, subKeyId);
    intent.putExtra(EXTRA_USER_ID, primaryUserId);

    context.startService(intent);
  }
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mAccountsLabel = (TextView) findViewById(R.id.api_accounts_label);
    mAppNameView = (TextView) findViewById(R.id.api_app_settings_app_name);
    mAppIconView = (ImageView) findViewById(R.id.api_app_settings_app_icon);
    mPackageName = (TextView) findViewById(R.id.api_app_settings_package_name);
    mPackageSignature = (TextView) findViewById(R.id.api_app_settings_package_certificate);
    mStartFab = (FloatingActionButton) findViewById(R.id.fab);

    mStartFab.setOnClickListener(
        new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            startApp();
          }
        });

    setFullScreenDialogClose(
        new View.OnClickListener() {
          @Override
          public void onClick(View v) {
            cancel();
          }
        });
    setTitle(null);

    Intent intent = getIntent();
    mAppUri = intent.getData();
    if (mAppUri == null) {
      Log.e(Constants.TAG, "Intent data missing. Should be Uri of app!");
      finish();
      return;
    } else {
      Log.d(Constants.TAG, "uri: " + mAppUri);
      loadData(savedInstanceState, mAppUri);
    }
  }
  @Override
  public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

    MatrixCursor matrix =
        new MatrixCursor(new String[] {"_id", "user_data", "grouped"}) {
          @Override
          public byte[] getBlob(int column) {
            return super.getBlob(column);
          }
        };
    data.moveToFirst();

    long lastMasterKeyId = 0;
    String lastName = "";
    ArrayList<String> uids = new ArrayList<>();

    boolean header = true;

    // Iterate over all rows
    while (!data.isAfterLast()) {
      long masterKeyId = data.getLong(INDEX_MASTER_KEY_ID);
      String userId = data.getString(INDEX_USER_ID);
      KeyRing.UserId pieces = KeyRing.splitUserId(userId);

      // Two cases:

      boolean grouped = masterKeyId == lastMasterKeyId;
      boolean subGrouped = data.isFirst() || grouped && lastName.equals(pieces.name);
      // Remember for next loop
      lastName = pieces.name;

      Log.d(Constants.TAG, Long.toString(masterKeyId, 16) + (grouped ? "grouped" : "not grouped"));

      if (!subGrouped) {
        // 1. This name should NOT be grouped with the previous, so we flush the buffer

        Parcel p = Parcel.obtain();
        p.writeStringList(uids);
        byte[] d = p.marshall();
        p.recycle();

        matrix.addRow(new Object[] {lastMasterKeyId, d, header ? 1 : 0});
        // indicate that we have a header for this masterKeyId
        header = false;

        // Now clear the buffer, and add the new user id, for the next round
        uids.clear();
      }

      // 2. This name should be grouped with the previous, just add to buffer
      uids.add(userId);
      lastMasterKeyId = masterKeyId;

      // If this one wasn't grouped, the next one's gotta be a header
      if (!grouped) {
        header = true;
      }

      // Regardless of the outcome, move to next entry
      data.moveToNext();
    }

    // If there is anything left in the buffer, flush it one last time
    if (!uids.isEmpty()) {

      Parcel p = Parcel.obtain();
      p.writeStringList(uids);
      byte[] d = p.marshall();
      p.recycle();

      matrix.addRow(new Object[] {lastMasterKeyId, d, header ? 1 : 0});
    }

    mUserIdsAdapter.swapCursor(matrix);
  }
 @Override
 public void onDestroy() {
   super.onDestroy();
   Log.d(Constants.TAG, "CryptoInputParcelCacheService, onDestroy()");
 }
 @Override
 public void onCreate() {
   super.onCreate();
   mContext = this;
   Log.d(Constants.TAG, "CryptoInputParcelCacheService, onCreate()");
 }
  /** Executed when service is started by intent */
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {

    if (intent == null || intent.getAction() == null) {
      return START_NOT_STICKY;
    }

    String action = intent.getAction();
    switch (action) {
      case ACTION_ADD:
        {
          long uuid1 = intent.getLongExtra(EXTRA_UUID1, 0);
          long uuid2 = intent.getLongExtra(EXTRA_UUID2, 0);
          UUID uuid = new UUID(uuid1, uuid2);
          CryptoInputParcel inputParcel = intent.getParcelableExtra(EXTRA_CRYPTO_INPUT_PARCEL);
          mCache.put(uuid, inputParcel);

          break;
        }
      case ACTION_GET:
        {
          long uuid1 = intent.getLongExtra(EXTRA_UUID1, 0);
          long uuid2 = intent.getLongExtra(EXTRA_UUID2, 0);
          UUID uuid = new UUID(uuid1, uuid2);
          Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);

          Message msg = Message.obtain();
          // UUID.equals isn't well documented; we use compareTo instead.
          if (NULL_UUID.compareTo(uuid) == 0) {
            msg.what = MSG_GET_NOT_FOUND;
          } else {
            CryptoInputParcel inputParcel = mCache.get(uuid);
            mCache.remove(uuid);
            msg.what = MSG_GET_OKAY;
            Bundle bundle = new Bundle();
            bundle.putParcelable(EXTRA_CRYPTO_INPUT_PARCEL, inputParcel);
            msg.setData(bundle);
          }

          try {
            messenger.send(msg);
          } catch (RemoteException e) {
            Log.e(Constants.TAG, "CryptoInputParcelCacheService: Sending message failed", e);
          }
          break;
        }
      default:
        {
          Log.e(
              Constants.TAG,
              "CryptoInputParcelCacheService: Intent or Intent Action not supported!");
          break;
        }
    }

    if (mCache.size() <= 0) {
      // stop whole service if cache is empty
      Log.d(
          Constants.TAG,
          "CryptoInputParcelCacheService: No passphrases remaining in memory, stopping service!");
      stopSelf();
    }

    return START_NOT_STICKY;
  }
  /** 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;
  }
Example #22
0
  /** Export keys */
  public void exportKeys(long[] masterKeyIds, boolean exportSecret) {
    Log.d(Constants.TAG, "exportKeys started");

    // Send all information needed to service to export key in other thread
    final Intent intent = new Intent(mActivity, KeychainIntentService.class);

    intent.setAction(KeychainIntentService.ACTION_EXPORT_KEYRING);

    // fill values for this action
    Bundle data = new Bundle();

    data.putString(KeychainIntentService.EXPORT_FILENAME, mExportFilename);
    data.putBoolean(KeychainIntentService.EXPORT_SECRET, exportSecret);

    if (masterKeyIds == null) {
      data.putBoolean(KeychainIntentService.EXPORT_ALL, true);
    } else {
      data.putLongArray(KeychainIntentService.EXPORT_KEY_RING_MASTER_KEY_ID, masterKeyIds);
    }

    intent.putExtra(KeychainIntentService.EXTRA_DATA, data);

    // Message is received after exporting is done in KeychainIntentService
    KeychainIntentServiceHandler exportHandler =
        new KeychainIntentServiceHandler(
            mActivity,
            mActivity.getString(R.string.progress_exporting),
            ProgressDialog.STYLE_HORIZONTAL,
            true,
            new DialogInterface.OnCancelListener() {
              @Override
              public void onCancel(DialogInterface dialogInterface) {
                mActivity.stopService(intent);
              }
            }) {
          public void handleMessage(Message message) {
            // handle messages by standard KeychainIntentServiceHandler first
            super.handleMessage(message);

            if (message.arg1 == KeychainIntentServiceHandler.MESSAGE_OKAY) {
              // get returned data bundle
              Bundle returnData = message.getData();

              int exported = returnData.getInt(KeychainIntentService.RESULT_EXPORT);
              String toastMessage;
              if (exported == 1) {
                toastMessage = mActivity.getString(R.string.key_exported);
              } else if (exported > 0) {
                toastMessage = mActivity.getString(R.string.keys_exported, exported);
              } else {
                toastMessage = mActivity.getString(R.string.no_keys_exported);
              }
              Toast.makeText(mActivity, toastMessage, Toast.LENGTH_SHORT).show();
            }
          }
        };

    // Create a new Messenger for the communication back
    Messenger messenger = new Messenger(exportHandler);
    intent.putExtra(KeychainIntentService.EXTRA_MESSENGER, messenger);

    // show progress dialog
    exportHandler.showProgressDialog(mActivity);

    // start service with intent
    mActivity.startService(intent);
  }
  private void loadData(Uri dataUri) {
    if (dataUri.equals(mDataUri)) {
      Log.d(Constants.TAG, "Same URI, no need to load the data again!");
      return;
    }

    mDataUri = dataUri;

    Log.i(Constants.TAG, "mDataUri: " + mDataUri.toString());

    { // label whether secret key is available, and edit button if it is
      final long masterKeyId = ProviderHelper.getMasterKeyId(getActivity(), mDataUri);
      if (ProviderHelper.hasSecretKeyByMasterKeyId(getActivity(), masterKeyId)) {
        // set this attribute. this is a LITTLE unclean, but we have the info available
        // right here, so why not.
        mSecretKey.setTextColor(getResources().getColor(R.color.emphasis));
        mSecretKey.setText(R.string.secret_key_yes);

        // certify button
        // TODO this button MIGHT be useful if the user wants to
        // certify a private key with another...
        // mActionCertify.setVisibility(View.GONE);

        // edit button
        mActionEdit.setVisibility(View.VISIBLE);
        mActionEdit.setOnClickListener(
            new View.OnClickListener() {
              public void onClick(View view) {
                Intent editIntent = new Intent(getActivity(), EditKeyActivity.class);
                editIntent.setData(
                    KeychainContract.KeyRings.buildSecretKeyRingsByMasterKeyIdUri(
                        Long.toString(masterKeyId)));
                editIntent.setAction(EditKeyActivity.ACTION_EDIT_KEY);
                startActivityForResult(editIntent, 0);
              }
            });
      } else {
        mSecretKey.setTextColor(Color.BLACK);
        mSecretKey.setText(getResources().getString(R.string.secret_key_no));

        // certify button
        mActionCertify.setVisibility(View.VISIBLE);
        // edit button
        mActionEdit.setVisibility(View.GONE);
      }

      // TODO see todo note above, doing this here for now
      mActionCertify.setOnClickListener(
          new View.OnClickListener() {
            public void onClick(View view) {
              certifyKey(
                  KeychainContract.KeyRings.buildPublicKeyRingsByMasterKeyIdUri(
                      Long.toString(masterKeyId)));
            }
          });
    }

    mActionEncrypt.setOnClickListener(
        new View.OnClickListener() {

          @Override
          public void onClick(View v) {
            encryptToContact(mDataUri);
          }
        });

    mUserIdsAdapter = new ViewKeyUserIdsAdapter(getActivity(), null, 0);
    mUserIds.setAdapter(mUserIdsAdapter);

    mKeysAdapter = new ViewKeyKeysAdapter(getActivity(), null, 0);
    mKeys.setAdapter(mKeysAdapter);

    // Prepare the loaders. Either re-connect with an existing ones,
    // or start new ones.
    getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYRING, null, this);
    getActivity().getSupportLoaderManager().initLoader(LOADER_ID_USER_IDS, null, this);
    getActivity().getSupportLoaderManager().initLoader(LOADER_ID_KEYS, null, this);
  }
  @Override
  public void onResume() {
    super.onResume();

    LinkedIdWizard wizard = (LinkedIdWizard) getActivity();
    mFingerprint = wizard.mFingerprint;
    mMasterKeyId = wizard.mMasterKeyId;

    final String oAuthCode = wizard.oAuthGetCode();
    final String oAuthState = wizard.oAuthGetState();
    if (oAuthCode == null) {
      Log.d(Constants.TAG, "no code");
      return;
    }

    final String gistText = GithubResource.generate(wizard, mFingerprint);

    Log.d(Constants.TAG, "got code: " + oAuthCode);

    new AsyncTask<Void, Void, JSONObject>() {
      @Override
      protected JSONObject doInBackground(Void... dummy) {
        try {

          long timer = System.currentTimeMillis();

          JSONObject params = new JSONObject();
          params.put("client_id", "7a011b66275f244d3f21");
          params.put("client_secret", "eaced8a6655719d8c6848396de97b3f5d7a89fec");
          params.put("code", oAuthCode);
          params.put("state", oAuthState);

          JSONObject result =
              jsonHttpRequest("https://github.com/login/oauth/access_token", params, null);

          // ux flow: this operation should take at last a second
          timer = System.currentTimeMillis() - timer;
          if (timer < 1000)
            try {
              Thread.sleep(1000 - timer);
            } catch (InterruptedException e) {
              // never mind
            }

          return result;

        } catch (IOException e) {
          Log.e(Constants.TAG, "error in request", e);
        } catch (JSONException e) {
          throw new AssertionError("json error, this is a bug!");
        }
        return null;
      }

      @Override
      protected void onPostExecute(JSONObject result) {
        super.onPostExecute(result);

        Log.d(Constants.TAG, "response: " + result);

        if (result == null || !result.has("access_token")) {
          mStatus1.setDisplayedChild(3);
          return;
        }

        mStatus1.setDisplayedChild(2);
        step2PostGist(result.optString("access_token"), gistText);
      }
    }.execute();
  }
  /**
   * Gets a cached passphrase from memory by sending an intent to the service. This method is
   * designed to wait until the service returns the passphrase.
   *
   * @return passphrase or null (if no passphrase is cached for this keyId)
   */
  public static Passphrase getCachedPassphrase(Context context, long masterKeyId, long subKeyId)
      throws KeyNotFoundException {
    Log.d(
        Constants.TAG,
        "PassphraseCacheService.getCachedPassphrase() for masterKeyId "
            + masterKeyId
            + ", subKeyId "
            + subKeyId);

    Intent intent = new Intent(context, PassphraseCacheService.class);
    intent.setAction(ACTION_PASSPHRASE_CACHE_GET);

    final Object mutex = new Object();
    final Message returnMessage = Message.obtain();

    HandlerThread handlerThread = new HandlerThread("getPassphraseThread");
    handlerThread.start();
    Handler returnHandler =
        new Handler(handlerThread.getLooper()) {
          @Override
          public void handleMessage(Message message) {
            // copy over result to handle after mutex.wait
            returnMessage.what = message.what;
            returnMessage.copyFrom(message);
            synchronized (mutex) {
              mutex.notify();
            }
            // quit handlerThread
            getLooper().quit();
          }
        };

    // Create a new Messenger for the communication back
    Messenger messenger = new Messenger(returnHandler);
    intent.putExtra(EXTRA_KEY_ID, masterKeyId);
    intent.putExtra(EXTRA_SUBKEY_ID, subKeyId);
    intent.putExtra(EXTRA_MESSENGER, messenger);
    // send intent to this service
    context.startService(intent);

    // Wait on mutex until passphrase is returned to handlerThread. Note that this local
    // variable is used in the handler closure above, so it does make sense here!
    // noinspection SynchronizationOnLocalVariableOrMethodParameter
    synchronized (mutex) {
      try {
        mutex.wait(3000);
      } catch (InterruptedException e) {
        // don't care
      }
    }

    switch (returnMessage.what) {
      case MSG_PASSPHRASE_CACHE_GET_OKAY:
        Bundle returnData = returnMessage.getData();
        returnData.setClassLoader(context.getClassLoader());
        return returnData.getParcelable(EXTRA_PASSPHRASE);
      case MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND:
        throw new KeyNotFoundException();
      default:
        Log.e(Constants.TAG, "timeout case!");
        throw new KeyNotFoundException("should not happen!");
    }
  }
  /** Internal implementation to get cached passphrase. */
  private Passphrase getCachedPassphraseImpl(long masterKeyId, long subKeyId)
      throws ProviderHelper.NotFoundException {
    // on "none" key, just do nothing
    if (masterKeyId == Constants.key.none) {
      return null;
    }

    // passphrase for symmetric encryption?
    if (masterKeyId == Constants.key.symmetric) {
      Log.d(
          Constants.TAG,
          "PassphraseCacheService.getCachedPassphraseImpl() for symmetric encryption");
      CachedPassphrase cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric);
      if (cachedPassphrase == null) {
        return null;
      }
      return cachedPassphrase.mPassphrase;
    }

    // try to get master key id which is used as an identifier for cached passphrases
    Log.d(
        Constants.TAG,
        "PassphraseCacheService.getCachedPassphraseImpl() for masterKeyId "
            + masterKeyId
            + ", subKeyId "
            + subKeyId);

    // get the type of key (from the database)
    CachedPublicKeyRing keyRing = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId);
    SecretKeyType keyType = keyRing.getSecretKeyType(subKeyId);

    switch (keyType) {
      case PASSPHRASE_EMPTY:
        return new Passphrase("");
      case UNAVAILABLE:
        throw new ProviderHelper.NotFoundException("secret key for this subkey is not available");
      case GNU_DUMMY:
        throw new ProviderHelper.NotFoundException(
            "secret key for stripped subkey is not available");
    }

    // get cached passphrase
    CachedPassphrase cachedPassphrase = mPassphraseCache.get(subKeyId);
    if (cachedPassphrase == null) {

      // If we cache strictly by subkey, exit early
      if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
        Log.d(
            Constants.TAG,
            "PassphraseCacheService: specific subkey passphrase not (yet) cached, returning null");
        // not really an error, just means the passphrase is not cached but not empty either
        return null;
      }

      if (subKeyId == masterKeyId) {
        Log.d(
            Constants.TAG,
            "PassphraseCacheService: masterkey passphrase not (yet) cached, returning null");
        // not really an error, just means the passphrase is not cached but not empty either
        return null;
      }

      cachedPassphrase = mPassphraseCache.get(masterKeyId);
      // If we cache strictly by subkey, exit early
      if (cachedPassphrase == null) {
        Log.d(
            Constants.TAG,
            "PassphraseCacheService: keyring passphrase not (yet) cached, returning null");
        // not really an error, just means the passphrase is not cached but not empty either
        return null;
      }
    }

    return cachedPassphrase.mPassphrase;
  }
  /** Executed when service is started by intent */
  @Override
  public int onStartCommand(Intent intent, int flags, int startId) {
    Log.d(Constants.TAG, "PassphraseCacheService.onStartCommand()");

    if (intent == null || intent.getAction() == null) {
      updateService();
      return START_STICKY;
    }

    String action = intent.getAction();
    switch (action) {
      case ACTION_PASSPHRASE_CACHE_ADD:
        {
          long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, -1);
          long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, -1);
          long timeoutTtl = intent.getIntExtra(EXTRA_TTL, DEFAULT_TTL);

          Passphrase passphrase = intent.getParcelableExtra(EXTRA_PASSPHRASE);
          String primaryUserID = intent.getStringExtra(EXTRA_USER_ID);

          Log.d(
              Constants.TAG,
              "PassphraseCacheService: Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with masterkeyId: "
                  + masterKeyId
                  + ", subKeyId: "
                  + subKeyId
                  + ", ttl: "
                  + timeoutTtl
                  + ", usrId: "
                  + primaryUserID);

          // if we don't cache by specific subkey id, or the requested subkey is the master key,
          // just add master key id to the cache, otherwise, add this specific subkey to the cache
          long referenceKeyId =
              Preferences.getPreferences(mContext).getPassphraseCacheSubs()
                  ? subKeyId
                  : masterKeyId;

          CachedPassphrase cachedPassphrase;
          if (timeoutTtl == 0L) {
            cachedPassphrase = CachedPassphrase.getPassphraseLock(passphrase, primaryUserID);
          } else if (timeoutTtl >= Integer.MAX_VALUE) {
            cachedPassphrase = CachedPassphrase.getPassphraseNoTimeout(passphrase, primaryUserID);
          } else {
            cachedPassphrase =
                CachedPassphrase.getPassphraseTtlTimeout(passphrase, primaryUserID, timeoutTtl);

            long triggerTime = new Date().getTime() + (timeoutTtl * 1000);
            // register new alarm with keyId for this passphrase
            AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);
            am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, referenceKeyId));
          }

          mPassphraseCache.put(referenceKeyId, cachedPassphrase);

          break;
        }
      case ACTION_PASSPHRASE_CACHE_GET:
        {
          long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, Constants.key.symmetric);
          long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, Constants.key.symmetric);
          Messenger messenger = intent.getParcelableExtra(EXTRA_MESSENGER);

          Message msg = Message.obtain();
          try {
            // If only one of these is symmetric, error out!
            if (masterKeyId == Constants.key.symmetric ^ subKeyId == Constants.key.symmetric) {
              Log.e(
                  Constants.TAG,
                  "PassphraseCacheService: Bad request, missing masterKeyId or subKeyId!");
              msg.what = MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND;
            } else {
              Passphrase passphrase = getCachedPassphraseImpl(masterKeyId, subKeyId);
              msg.what = MSG_PASSPHRASE_CACHE_GET_OKAY;
              Bundle bundle = new Bundle();
              bundle.putParcelable(EXTRA_PASSPHRASE, passphrase);
              msg.setData(bundle);
            }
          } catch (ProviderHelper.NotFoundException e) {
            Log.e(
                Constants.TAG, "PassphraseCacheService: Passphrase for unknown key was requested!");
            msg.what = MSG_PASSPHRASE_CACHE_GET_KEY_NOT_FOUND;
          }

          try {
            messenger.send(msg);
          } catch (RemoteException e) {
            Log.e(Constants.TAG, "PassphraseCacheService: Sending message failed", e);
          }
          break;
        }
      case ACTION_PASSPHRASE_CACHE_CLEAR:
        {
          AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE);

          if (intent.hasExtra(EXTRA_SUBKEY_ID) && intent.hasExtra(EXTRA_KEY_ID)) {

            long referenceKeyId;
            if (Preferences.getPreferences(mContext).getPassphraseCacheSubs()) {
              referenceKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, 0L);
            } else {
              referenceKeyId = intent.getLongExtra(EXTRA_KEY_ID, 0L);
            }
            // Stop specific ttl alarm and
            am.cancel(buildIntent(this, referenceKeyId));
            mPassphraseCache.delete(referenceKeyId);

          } else {

            // Stop all ttl alarms
            for (int i = 0; i < mPassphraseCache.size(); i++) {
              CachedPassphrase cachedPassphrase = mPassphraseCache.valueAt(i);
              if (cachedPassphrase.mTimeoutMode == TimeoutMode.TTL) {
                am.cancel(buildIntent(this, mPassphraseCache.keyAt(i)));
              }
            }
            mPassphraseCache.clear();
          }
          break;
        }
      default:
        {
          Log.e(Constants.TAG, "PassphraseCacheService: Intent or Intent Action not supported!");
          break;
        }
    }

    updateService();

    return START_STICKY;
  }