diff options
13 files changed, 208 insertions, 154 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java index 51fa1795d..f8f0d2ac3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/Constants.java @@ -72,6 +72,7 @@ public final class Constants { public static final String DEFAULT_MESSAGE_COMPRESSION = "defaultMessageCompression"; public static final String DEFAULT_FILE_COMPRESSION = "defaultFileCompression"; public static final String PASSPHRASE_CACHE_TTL = "passphraseCacheTtl"; + public static final String PASSPHRASE_CACHE_SUBS = "passphraseCacheSubs"; public static final String LANGUAGE = "language"; public static final String KEY_SERVERS = "keyServers"; public static final String PREF_DEFAULT_VERSION = "keyServersDefaultVersion"; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PassphraseCacheInterface.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PassphraseCacheInterface.java index 433c4db00..ae1b94a34 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PassphraseCacheInterface.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PassphraseCacheInterface.java @@ -6,6 +6,8 @@ public interface PassphraseCacheInterface { } } - public String getCachedPassphrase(long masterKeyId) throws NoSecretKeyException; + public String getCachedPassphrase(long subKeyId) throws NoSecretKeyException; + + public String getCachedPassphrase(long masterKeyId, long subKeyId) throws NoSecretKeyException; } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java index 55354cdec..1b4ad1fc1 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/remote/OpenPgpService.java @@ -34,9 +34,11 @@ import org.spongycastle.util.encoders.Hex; import org.sufficientlysecure.keychain.nfc.NfcActivity; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface; +import org.sufficientlysecure.keychain.pgp.PassphraseCacheInterface.NoSecretKeyException; import org.sufficientlysecure.keychain.pgp.PgpDecryptVerify; import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing; import org.sufficientlysecure.keychain.provider.KeychainDatabase.Tables; +import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.service.results.DecryptVerifyResult; import org.sufficientlysecure.keychain.pgp.PgpHelper; import org.sufficientlysecure.keychain.pgp.PgpSignEncrypt; @@ -74,6 +76,28 @@ public class OpenPgpService extends RemoteService { static final String EMAIL_SEARCH_WHERE = Tables.KEYS + "." + KeychainContract.KeyRings.IS_REVOKED + " = 0 AND " + KeychainContract.KeyRings.IS_EXPIRED + " = 0"; + private PassphraseCacheInterface passphraseCacheInterface = new PassphraseCacheInterface() { + @Override + public String getCachedPassphrase(long subKeyId) throws NoSecretKeyException { + try { + long masterKeyId = new ProviderHelper(getContext()).getMasterKeyId(subKeyId); + return getCachedPassphrase(masterKeyId, subKeyId); + } catch (NotFoundException e) { + throw new PassphraseCacheInterface.NoSecretKeyException(); + } + } + + @Override + public String getCachedPassphrase(long masterKeyId, long subKeyId) throws NoSecretKeyException { + try { + return PassphraseCacheService.getCachedPassphrase( + getContext(), masterKeyId, subKeyId); + } catch (PassphraseCacheService.KeyNotFoundException e) { + throw new PassphraseCacheInterface.NoSecretKeyException(); + } + } + }; + /** * Search database for key ids based on emails. * @@ -232,7 +256,8 @@ public class OpenPgpService extends RemoteService { passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); } else { try { - passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), accSettings.getKeyId()); + passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), + accSettings.getKeyId(), accSettings.getKeyId()); } catch (PassphraseCacheService.KeyNotFoundException e) { // secret key that is set for this account is deleted? // show account config again! @@ -269,22 +294,11 @@ public class OpenPgpService extends RemoteService { // Find the appropriate subkey to sign with CachedPublicKeyRing signingRing = new ProviderHelper(this).getCachedPublicKeyRing(accSettings.getKeyId()); - long sigSubKeyId = signingRing.getSignId(); + final long sigSubKeyId = signingRing.getSignId(); // sign-only PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( - new ProviderHelper(getContext()), - new PassphraseCacheInterface() { - @Override - public String getCachedPassphrase(long masterKeyId) throws PassphraseCacheInterface.NoSecretKeyException { - try { - return PassphraseCacheService.getCachedPassphrase( - OpenPgpService.this, masterKeyId); - } catch (PassphraseCacheService.KeyNotFoundException e) { - throw new PassphraseCacheInterface.NoSecretKeyException(); - } - } - }, + new ProviderHelper(getContext()), passphraseCacheInterface, inputData, os ); builder.setEnableAsciiArmorOutput(asciiArmor) @@ -376,18 +390,7 @@ public class OpenPgpService extends RemoteService { InputData inputData = new InputData(is, inputLength); PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( - new ProviderHelper(getContext()), - new PassphraseCacheInterface() { - @Override - public String getCachedPassphrase(long masterKeyId) throws PassphraseCacheInterface.NoSecretKeyException { - try { - return PassphraseCacheService.getCachedPassphrase( - OpenPgpService.this, masterKeyId); - } catch (PassphraseCacheService.KeyNotFoundException e) { - throw new PassphraseCacheInterface.NoSecretKeyException(); - } - } - }, + new ProviderHelper(getContext()), passphraseCacheInterface, inputData, os ); builder.setEnableAsciiArmorOutput(asciiArmor) @@ -404,7 +407,7 @@ public class OpenPgpService extends RemoteService { passphrase = data.getStringExtra(OpenPgpApi.EXTRA_PASSPHRASE); } else { passphrase = PassphraseCacheService.getCachedPassphrase(getContext(), - accSettings.getKeyId()); + accSettings.getKeyId(), accSettings.getKeyId()); } if (passphrase == null) { // get PendingIntent for passphrase input, add it to given params and return to client @@ -496,18 +499,7 @@ public class OpenPgpService extends RemoteService { InputData inputData = new InputData(is, inputLength); PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( - new ProviderHelper(this), - new PassphraseCacheInterface() { - @Override - public String getCachedPassphrase(long masterKeyId) throws PassphraseCacheInterface.NoSecretKeyException { - try { - return PassphraseCacheService.getCachedPassphrase( - OpenPgpService.this, masterKeyId); - } catch (PassphraseCacheService.KeyNotFoundException e) { - throw new PassphraseCacheInterface.NoSecretKeyException(); - } - } - }, + new ProviderHelper(getContext()), passphraseCacheInterface, inputData, os ); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java index 94ea20328..6d23c36b9 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/KeychainIntentService.java @@ -29,6 +29,7 @@ import android.os.RemoteException; import org.sufficientlysecure.keychain.Constants; import org.sufficientlysecure.keychain.R; +import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException; import org.sufficientlysecure.keychain.util.FileHelper; import org.sufficientlysecure.keychain.util.Preferences; import org.sufficientlysecure.keychain.keyimport.HkpKeyserver; @@ -84,7 +85,7 @@ import java.util.concurrent.atomic.AtomicBoolean; * data from the activities or other apps, queues these intents, executes them, and stops itself * after doing them. */ -public class KeychainIntentService extends IntentService implements Progressable { +public class KeychainIntentService extends IntentService implements Progressable, PassphraseCacheInterface { /* extras that can be given by intent */ public static final String EXTRA_MESSENGER = "messenger"; @@ -268,19 +269,7 @@ public class KeychainIntentService extends IntentService implements Progressable /* Operation */ PgpSignEncrypt.Builder builder = new PgpSignEncrypt.Builder( - new ProviderHelper(this), - new PassphraseCacheInterface() { - @Override - public String getCachedPassphrase(long masterKeyId) throws PassphraseCacheInterface.NoSecretKeyException { - try { - return PassphraseCacheService.getCachedPassphrase( - KeychainIntentService.this, masterKeyId); - } catch (PassphraseCacheService.KeyNotFoundException e) { - throw new PassphraseCacheInterface.NoSecretKeyException(); - } - } - }, - inputData, outStream + new ProviderHelper(this), this, inputData, outStream ); builder.setProgressable(this) .setEnableAsciiArmorOutput(useAsciiArmor) @@ -348,18 +337,7 @@ public class KeychainIntentService extends IntentService implements Progressable // verifyText and decrypt returning additional resultData values for the // verification of signatures PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( - new ProviderHelper(this), - new PassphraseCacheInterface() { - @Override - public String getCachedPassphrase(long masterKeyId) throws PassphraseCacheInterface.NoSecretKeyException { - try { - return PassphraseCacheService.getCachedPassphrase( - KeychainIntentService.this, masterKeyId); - } catch (PassphraseCacheService.KeyNotFoundException e) { - throw new PassphraseCacheInterface.NoSecretKeyException(); - } - } - }, + new ProviderHelper(this), this, inputData, outStream ); builder.setProgressable(this) @@ -397,18 +375,7 @@ public class KeychainIntentService extends IntentService implements Progressable // verification of signatures PgpDecryptVerify.Builder builder = new PgpDecryptVerify.Builder( new ProviderHelper(this), - new PassphraseCacheInterface() { - @Override - public String getCachedPassphrase(long masterKeyId) throws PassphraseCacheInterface.NoSecretKeyException { - try { - return PassphraseCacheService.getCachedPassphrase( - KeychainIntentService.this, masterKeyId); - } catch (PassphraseCacheService.KeyNotFoundException e) { - throw new PassphraseCacheInterface.NoSecretKeyException(); - } - } - }, - inputData, null + this, inputData, null ); builder.setProgressable(this) .setAllowSymmetricDecryption(true) @@ -489,7 +456,7 @@ public class KeychainIntentService extends IntentService implements Progressable // cache new passphrase if (saveParcel.mNewPassphrase != null) { - PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(), + PassphraseCacheService.addCachedPassphrase(this, ring.getMasterKeyId(), ring.getMasterKeyId(), saveParcel.mNewPassphrase, ring.getPublicKey().getPrimaryUserIdWithFallback()); } @@ -715,7 +682,7 @@ public class KeychainIntentService extends IntentService implements Progressable /* Operation */ String signaturePassphrase = PassphraseCacheService.getCachedPassphrase(this, - masterKeyId); + masterKeyId, masterKeyId); if (signaturePassphrase == null) { throw new PgpGeneralException("Unable to obtain passphrase"); } @@ -899,6 +866,26 @@ public class KeychainIntentService extends IntentService implements Progressable } @Override + public String getCachedPassphrase(long subKeyId) throws NoSecretKeyException { + try { + long masterKeyId = new ProviderHelper(this).getMasterKeyId(subKeyId); + return getCachedPassphrase(masterKeyId, subKeyId); + } catch (NotFoundException e) { + throw new PassphraseCacheInterface.NoSecretKeyException(); + } + } + + @Override + public String getCachedPassphrase(long masterKeyId, long subKeyId) throws NoSecretKeyException { + try { + return PassphraseCacheService.getCachedPassphrase( + KeychainIntentService.this, masterKeyId, subKeyId); + } catch (PassphraseCacheService.KeyNotFoundException e) { + throw new PassphraseCacheInterface.NoSecretKeyException(); + } + } + + @Override public int onStartCommand(Intent intent, int flags, int startId) { if (ACTION_CANCEL.equals(intent.getAction())) { mActionCanceled.set(true); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java index 949ce0f0b..9638fb7bf 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/PassphraseCacheService.java @@ -53,14 +53,26 @@ import java.util.Date; * passphrase cache. Use the static methods addCachedPassphrase and getCachedPassphrase for * convenience. * - * Design decisions: - * - Cache passphrases based on master key ids, but try to unlock before using the subkey id - * (to be compatible with stripped keys) - * - Cache based on master key id so that there is not need to enter a passphrase twice for sign and - * decrypt (if these are two different subkeys) - * - Assume that all passphrases cached here are valid passphrases - * - Do not handle if a keyring contains subkeys with different passphrases. This is not considered - * supported and has not been seen in other OpenPGP implementations + * The passphrase cache service always works with both a master key id and a subkey id. The master + * key id is always used to retrieve relevant info from the database, while the subkey id is used + * to determine the type behavior (regular passphrase, empty passphrase, stripped key, + * divert-to-card) for the specific key requested. + * + * Caching behavior for subkeys depends on the cacheSubs preference: + * + * - If cacheSubs is NOT set, passphrases will be cached and retrieved by master key id. The + * checks for special subkeys will still be done, but otherwise it is assumed that all subkeys + * from the same master key will use the same passphrase. This can lead to bad passphrase + * errors if two subkeys are encrypted differently. This is the default behavior. + * + * - If cacheSubs IS set, passphrases will be cached per subkey id. This means that if a keyring + * has two subkeys for different purposes, passphrases will be cached independently and the + * user will be asked for a passphrase once per subkey even if it is the same one. This mode + * of operation is more precise, since we can assume that all passphrases returned from cache + * will be correct without fail. Since keyrings with differently encrypted subkeys are a very + * rare occurrence, and caching by keyring is what the user expects in the vast majority of + * cases, this is not the default behavior. + * */ public class PassphraseCacheService extends Service { @@ -76,6 +88,7 @@ public class PassphraseCacheService extends Service { public static final String EXTRA_TTL = "ttl"; public static final String EXTRA_KEY_ID = "key_id"; + public static final String EXTRA_SUBKEY_ID = "subkey_id"; public static final String EXTRA_PASSPHRASE = "passphrase"; public static final String EXTRA_MESSENGER = "messenger"; public static final String EXTRA_USER_ID = "user_id"; @@ -107,21 +120,19 @@ public class PassphraseCacheService extends Service { * 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. - * - * @param context - * @param keyId - * @param passphrase */ - public static void addCachedPassphrase(Context context, long keyId, String passphrase, + public static void addCachedPassphrase(Context context, long masterKeyId, long subKeyId, + String passphrase, String primaryUserId) { - Log.d(Constants.TAG, "PassphraseCacheService.cacheNewPassphrase() for " + keyId); + Log.d(Constants.TAG, "PassphraseCacheService.cacheNewPassphrase() for " + masterKeyId); Intent intent = new Intent(context, PassphraseCacheService.class); intent.setAction(ACTION_PASSPHRASE_CACHE_ADD); intent.putExtra(EXTRA_TTL, Preferences.getPreferences(context).getPassphraseCacheTtl()); intent.putExtra(EXTRA_PASSPHRASE, passphrase); - intent.putExtra(EXTRA_KEY_ID, keyId); + intent.putExtra(EXTRA_KEY_ID, masterKeyId); + intent.putExtra(EXTRA_SUBKEY_ID, subKeyId); intent.putExtra(EXTRA_USER_ID, primaryUserId); context.startService(intent); @@ -130,13 +141,11 @@ public class PassphraseCacheService extends Service { /** * 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. - * - * @param context - * @param keyId + * @return passphrase or null (if no passphrase is cached for this keyId) */ - public static String getCachedPassphrase(Context context, long keyId) throws KeyNotFoundException { - Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphrase() get masterKeyId for " + keyId); + public static String getCachedPassphrase(Context context, long masterKeyId, long subKeyId) throws KeyNotFoundException { + Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphrase() get masterKeyId for " + masterKeyId); Intent intent = new Intent(context, PassphraseCacheService.class); intent.setAction(ACTION_PASSPHRASE_CACHE_GET); @@ -162,16 +171,20 @@ public class PassphraseCacheService extends Service { // Create a new Messenger for the communication back Messenger messenger = new Messenger(returnHandler); - intent.putExtra(EXTRA_KEY_ID, keyId); + 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 + // 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 } } @@ -187,34 +200,31 @@ public class PassphraseCacheService extends Service { /** * Internal implementation to get cached passphrase. - * - * @param subKeyId - * @return */ - private String getCachedPassphraseImpl(long subKeyId) throws ProviderHelper.NotFoundException { + private String getCachedPassphraseImpl(long masterKeyId, long subKeyId) throws ProviderHelper.NotFoundException { // passphrase for symmetric encryption? - if (subKeyId == Constants.key.symmetric) { + if (masterKeyId == Constants.key.symmetric) { Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for symmetric encryption"); String cachedPassphrase = mPassphraseCache.get(Constants.key.symmetric).getPassphrase(); if (cachedPassphrase == null) { return null; } - addCachedPassphrase(this, Constants.key.symmetric, cachedPassphrase, getString(R.string.passp_cache_notif_pwd)); + addCachedPassphrase(this, Constants.key.symmetric, Constants.key.symmetric, + cachedPassphrase, getString(R.string.passp_cache_notif_pwd)); return cachedPassphrase; } // on "none" key, just do nothing - if(subKeyId == Constants.key.none) { + if(masterKeyId == Constants.key.none) { return null; } // try to get master key id which is used as an identifier for cached passphrases - Log.d(Constants.TAG, "PassphraseCacheService.getCachedPassphraseImpl() for masterKeyId " + subKeyId); - // find a master key id for our key - long masterKeyId = new ProviderHelper(this).getMasterKeyId(subKeyId); - CachedPublicKeyRing keyRing = new ProviderHelper(this).getCachedPublicKeyRing(masterKeyId); + 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) { @@ -237,14 +247,33 @@ public class PassphraseCacheService extends Service { // get cached passphrase CachedPassphrase cachedPassphrase = mPassphraseCache.get(subKeyId); if (cachedPassphrase == null) { - Log.d(Constants.TAG, "PassphraseCacheService: Passphrase not (yet) cached, returning null"); - // not really an error, just means the passphrase is not cached but not empty either - return 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; + } + } // set it again to reset the cache life cycle Log.d(Constants.TAG, "PassphraseCacheService: Cache passphrase again when getting it!"); - addCachedPassphrase(this, subKeyId, cachedPassphrase.getPassphrase(), cachedPassphrase.getPrimaryUserID()); + addCachedPassphrase(this, masterKeyId, subKeyId, cachedPassphrase.getPassphrase(), cachedPassphrase.getPrimaryUserID()); return cachedPassphrase.getPassphrase(); } @@ -277,10 +306,6 @@ public class PassphraseCacheService extends Service { /** * Build pending intent that is executed by alarm manager to time out a specific passphrase - * - * @param context - * @param keyId - * @return */ private static PendingIntent buildIntent(Context context, long keyId) { Intent intent = new Intent(BROADCAST_ACTION_PASSPHRASE_CACHE_SERVICE); @@ -302,38 +327,57 @@ public class PassphraseCacheService extends Service { if (intent != null && intent.getAction() != null) { if (ACTION_PASSPHRASE_CACHE_ADD.equals(intent.getAction())) { long ttl = intent.getLongExtra(EXTRA_TTL, DEFAULT_TTL); - long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1); + long masterKeyId = intent.getLongExtra(EXTRA_KEY_ID, -1); + long subKeyId = intent.getLongExtra(EXTRA_SUBKEY_ID, -1); String passphrase = intent.getStringExtra(EXTRA_PASSPHRASE); String primaryUserID = intent.getStringExtra(EXTRA_USER_ID); Log.d(Constants.TAG, - "PassphraseCacheService: Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with keyId: " - + keyId + ", ttl: " + ttl + ", usrId: " + primaryUserID + "PassphraseCacheService: Received ACTION_PASSPHRASE_CACHE_ADD intent in onStartCommand() with masterkeyId: " + + masterKeyId + ", subKeyId: " + subKeyId + ", ttl: " + ttl + ", usrId: " + primaryUserID ); - // add keyId, passphrase and primary user id to memory - mPassphraseCache.put(keyId, new CachedPassphrase(passphrase, primaryUserID)); - - if (ttl > 0) { - // register new alarm with keyId for this passphrase - long triggerTime = new Date().getTime() + (ttl * 1000); - AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); - am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, keyId)); + // 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 + if (subKeyId == masterKeyId || !Preferences.getPreferences(mContext).getPassphraseCacheSubs()) { + mPassphraseCache.put(masterKeyId, new CachedPassphrase(passphrase, primaryUserID)); + if (ttl > 0) { + // register new alarm with keyId for this passphrase + long triggerTime = new Date().getTime() + (ttl * 1000); + AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); + am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, masterKeyId)); + } + } else { + // otherwise, add this specific subkey to the cache + mPassphraseCache.put(subKeyId, new CachedPassphrase(passphrase, primaryUserID)); + if (ttl > 0) { + // register new alarm with keyId for this passphrase + long triggerTime = new Date().getTime() + (ttl * 1000); + AlarmManager am = (AlarmManager) this.getSystemService(Context.ALARM_SERVICE); + am.set(AlarmManager.RTC_WAKEUP, triggerTime, buildIntent(this, subKeyId)); + } } updateService(); } else if (ACTION_PASSPHRASE_CACHE_GET.equals(intent.getAction())) { - long keyId = intent.getLongExtra(EXTRA_KEY_ID, -1); + 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 { - String passphrase = getCachedPassphraseImpl(keyId); - msg.what = MSG_PASSPHRASE_CACHE_GET_OKAY; - Bundle bundle = new Bundle(); - bundle.putString(EXTRA_PASSPHRASE, passphrase); - msg.setData(bundle); + // 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 { + String passphrase = getCachedPassphraseImpl(masterKeyId, subKeyId); + msg.what = MSG_PASSPHRASE_CACHE_GET_OKAY; + Bundle bundle = new Bundle(); + bundle.putString(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; @@ -365,9 +409,6 @@ public class PassphraseCacheService extends Service { /** * Called when one specific passphrase for keyId timed out - * - * @param context - * @param keyId */ private void timeout(Context context, long keyId) { // remove passphrase corresponding to keyId from memory diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java index 70276bba3..438b56eb0 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/CertifyKeyFragment.java @@ -271,7 +271,7 @@ public class CertifyKeyFragment extends LoaderFragment // get the user's passphrase for this key (if required) String passphrase; try { - passphrase = PassphraseCacheService.getCachedPassphrase(mActivity, mMasterKeyId); + passphrase = PassphraseCacheService.getCachedPassphrase(mActivity, mMasterKeyId, mMasterKeyId); } catch (PassphraseCacheService.KeyNotFoundException e) { Log.e(Constants.TAG, "Key not found!", e); mActivity.finish(); diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java index e7d5d3869..a7db73a0a 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/EditKeyFragment.java @@ -236,7 +236,7 @@ public class EditKeyFragment extends LoaderFragment implements try { mCurrentPassphrase = PassphraseCacheService.getCachedPassphrase(getActivity(), - mSaveKeyringParcel.mMasterKeyId); + mSaveKeyringParcel.mMasterKeyId, mSaveKeyringParcel.mMasterKeyId); } catch (PassphraseCacheService.KeyNotFoundException e) { finishWithError(LogType.MSG_EK_ERROR_NOT_FOUND); return; diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java index ddd5fb5f0..decd1757f 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -64,7 +64,6 @@ public class PassphraseDialogActivity extends FragmentActivity { // special extra for OpenPgpService public static final String EXTRA_DATA = "data"; - @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -251,8 +250,9 @@ public class PassphraseDialogActivity extends FragmentActivity { // Early breakout if we are dealing with a symmetric key if (mSecretRing == null) { - PassphraseCacheService.addCachedPassphrase(getActivity(), Constants.key.symmetric, - passphrase, getString(R.string.passp_cache_notif_pwd)); + PassphraseCacheService.addCachedPassphrase(getActivity(), + Constants.key.symmetric, Constants.key.symmetric, passphrase, + getString(R.string.passp_cache_notif_pwd)); finishCaching(passphrase); return; @@ -309,8 +309,9 @@ public class PassphraseDialogActivity extends FragmentActivity { Log.d(Constants.TAG, "Everything okay! Caching entered passphrase"); try { - PassphraseCacheService.addCachedPassphrase(getActivity(), mSubKeyId, - passphrase, mSecretRing.getPrimaryUserIdWithFallback()); + PassphraseCacheService.addCachedPassphrase(getActivity(), + mSecretRing.getMasterKeyId(), mSubKeyId, passphrase, + mSecretRing.getPrimaryUserIdWithFallback()); } catch (PgpGeneralException e) { Log.e(Constants.TAG, "adding of a passphrase failed", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java index fb419dd60..3ffc182fb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PreferencesActivity.java @@ -82,7 +82,7 @@ public class PreferencesActivity extends PreferenceActivity { } else if (action != null && action.equals(ACTION_PREFS_ADV)) { addPreferencesFromResource(R.xml.adv_preferences); - initializePassPassphraceCacheTtl( + initializePassphraseCacheTtl( (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL)); initializeEncryptionAlgorithm( @@ -228,7 +228,10 @@ public class PreferencesActivity extends PreferenceActivity { // Load the preferences from an XML resource addPreferencesFromResource(R.xml.adv_preferences); - initializePassPassphraceCacheTtl( + initializePassphraseCacheSubs( + (CheckBoxPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_SUBS)); + + initializePassphraseCacheTtl( (IntegerListPreference) findPreference(Constants.Pref.PASSPHRASE_CACHE_TTL)); initializeEncryptionAlgorithm( @@ -281,7 +284,18 @@ public class PreferencesActivity extends PreferenceActivity { || super.isValidFragment(fragmentName); } - private static void initializePassPassphraceCacheTtl(final IntegerListPreference mPassphraseCacheTtl) { + private static void initializePassphraseCacheSubs(final CheckBoxPreference mPassphraseCacheSubs) { + mPassphraseCacheSubs.setChecked(sPreferences.getPassphraseCacheSubs()); + mPassphraseCacheSubs.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() { + public boolean onPreferenceChange(Preference preference, Object newValue) { + mPassphraseCacheSubs.setChecked((Boolean) newValue); + sPreferences.setPassphraseCacheSubs((Boolean) newValue); + return false; + } + }); + } + + private static void initializePassphraseCacheTtl(final IntegerListPreference mPassphraseCacheTtl) { mPassphraseCacheTtl.setValue("" + sPreferences.getPassphraseCacheTtl()); mPassphraseCacheTtl.setSummary(mPassphraseCacheTtl.getEntry()); mPassphraseCacheTtl diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java index 6739e8374..a89d4be1c 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/dialog/PassphraseDialogFragment.java @@ -242,7 +242,7 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor // Early breakout if we are dealing with a symmetric key if (mSecretRing == null) { PassphraseCacheService.addCachedPassphrase(getActivity(), Constants.key.symmetric, - passphrase, getString(R.string.passp_cache_notif_pwd)); + Constants.key.symmetric, passphrase, getString(R.string.passp_cache_notif_pwd)); // also return passphrase back to activity Bundle data = new Bundle(); data.putString(MESSAGE_DATA_PASSPHRASE, passphrase); @@ -301,8 +301,9 @@ public class PassphraseDialogFragment extends DialogFragment implements OnEditor Log.d(Constants.TAG, "Everything okay! Caching entered passphrase"); try { - PassphraseCacheService.addCachedPassphrase(getActivity(), mSubKeyId, - passphrase, mSecretRing.getPrimaryUserIdWithFallback()); + PassphraseCacheService.addCachedPassphrase(getActivity(), + mSecretRing.getMasterKeyId(), mSubKeyId, passphrase, + mSecretRing.getPrimaryUserIdWithFallback()); } catch (PgpGeneralException e) { Log.e(Constants.TAG, "adding of a passphrase failed", e); } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java index 35570ef6e..d4cd9b385 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Preferences.java @@ -93,6 +93,16 @@ public class Preferences { editor.commit(); } + public boolean getPassphraseCacheSubs() { + return mSharedPreferences.getBoolean(Pref.PASSPHRASE_CACHE_SUBS, false); + } + + public void setPassphraseCacheSubs(boolean value) { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putBoolean(Pref.PASSPHRASE_CACHE_SUBS, value); + editor.commit(); + } + public int getDefaultEncryptionAlgorithm() { return mSharedPreferences.getInt(Constants.Pref.DEFAULT_ENCRYPTION_ALGORITHM, PGPEncryptedData.AES_256); diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index ea55a8932..01b461316 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -111,6 +111,7 @@ <string name="label_hash_algorithm">"Hash algorithm"</string> <string name="label_symmetric">"Encrypt with passphrase"</string> <string name="label_passphrase_cache_ttl">"Passphrase cache"</string> + <string name="label_passphrase_cache_subs">"Cache passphrases by subkey"</string> <string name="label_message_compression">"Message compression"</string> <string name="label_file_compression">"File compression"</string> <string name="label_keyservers">"Keyservers"</string> diff --git a/OpenKeychain/src/main/res/xml/adv_preferences.xml b/OpenKeychain/src/main/res/xml/adv_preferences.xml index 250f21e2a..92e0f2ffc 100644 --- a/OpenKeychain/src/main/res/xml/adv_preferences.xml +++ b/OpenKeychain/src/main/res/xml/adv_preferences.xml @@ -6,6 +6,10 @@ android:key="passphraseCacheTtl" android:persistent="false" android:title="@string/label_passphrase_cache_ttl" /> + <CheckBoxPreference + android:key="passphraseCacheSubs" + android:persistent="false" + android:title="@string/label_passphrase_cache_subs" /> <org.sufficientlysecure.keychain.ui.widget.IntegerListPreference android:key="defaultEncryptionAlgorithm" android:persistent="false" |