diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java index 3cd78acdd..c642c2197 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java @@ -78,6 +78,7 @@ import org.sufficientlysecure.keychain.provider.KeyWritableRepository; import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequireAnyDecryptPassphraseBuilder; import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils; import org.sufficientlysecure.keychain.util.CharsetVerifier; import org.sufficientlysecure.keychain.util.FileHelper; @@ -581,6 +582,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation it = enc.getEncryptedDataObjects(); + RequireAnyDecryptPassphraseBuilder requirePassphraseBuilder = new RequireAnyDecryptPassphraseBuilder(); + // go through all objects and find one we can decrypt while (it.hasNext()) { Object obj = it.next(); @@ -644,7 +647,7 @@ public class PgpDecryptVerifyOperation extends BaseOperationemptyMap()); + return new AutoValue_CryptoInputParcel(null, null, null, true, null, Collections.emptyMap()); } public static CryptoInputParcel createCryptoInputParcel(Date signatureTime, Passphrase passphrase) { if (signatureTime == null) { signatureTime = new Date(); } - return new AutoValue_CryptoInputParcel(signatureTime, passphrase, true, null, + return new AutoValue_CryptoInputParcel(signatureTime, passphrase, null, true, null, Collections.emptyMap()); } public static CryptoInputParcel createCryptoInputParcel(Passphrase passphrase) { - return new AutoValue_CryptoInputParcel(null, passphrase, true, null, Collections.emptyMap()); + return new AutoValue_CryptoInputParcel(null, passphrase, null, true, null, Collections.emptyMap()); } public static CryptoInputParcel createCryptoInputParcel(Date signatureTime) { if (signatureTime == null) { signatureTime = new Date(); } - return new AutoValue_CryptoInputParcel(signatureTime, null, true, null, + return new AutoValue_CryptoInputParcel(signatureTime, null, null, true, null, Collections.emptyMap()); } public static CryptoInputParcel createCryptoInputParcel(ParcelableProxy parcelableProxy) { - return new AutoValue_CryptoInputParcel(null, null, true, parcelableProxy, new HashMap()); + return new AutoValue_CryptoInputParcel(null, null, null, true, parcelableProxy, new HashMap()); } public static CryptoInputParcel createCryptoInputParcel(Date signatureTime, boolean cachePassphrase) { if (signatureTime == null) { signatureTime = new Date(); } - return new AutoValue_CryptoInputParcel(signatureTime, null, cachePassphrase, null, + return new AutoValue_CryptoInputParcel(signatureTime, null, null, cachePassphrase, null, new HashMap()); } public static CryptoInputParcel createCryptoInputParcel(boolean cachePassphrase) { - return new AutoValue_CryptoInputParcel(null, null, cachePassphrase, null, new HashMap()); + return new AutoValue_CryptoInputParcel(null, null, null, cachePassphrase, null, new HashMap()); } // TODO get rid of this! @@ -105,8 +111,8 @@ public abstract class CryptoInputParcel implements Parcelable { newCryptoData.put(ByteBuffer.wrap(hash), signedHash); newCryptoData = Collections.unmodifiableMap(newCryptoData); - return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), isCachePassphrase(), - getParcelableProxy(), newCryptoData); + return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), getPassphraseSubkey(), + isCachePassphrase(), getParcelableProxy(), newCryptoData); } @CheckResult @@ -115,32 +121,32 @@ public abstract class CryptoInputParcel implements Parcelable { newCryptoData.putAll(cachedSessionKeys); newCryptoData = Collections.unmodifiableMap(newCryptoData); - return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), isCachePassphrase(), - getParcelableProxy(), newCryptoData); + return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), getPassphraseSubkey(), + isCachePassphrase(), getParcelableProxy(), newCryptoData); } @CheckResult - public CryptoInputParcel withPassphrase(Passphrase passphrase) { - return new AutoValue_CryptoInputParcel(getSignatureTime(), passphrase, isCachePassphrase(), + public CryptoInputParcel withPassphrase(Passphrase passphrase, Long subKeyId) { + return new AutoValue_CryptoInputParcel(getSignatureTime(), passphrase, subKeyId, isCachePassphrase(), getParcelableProxy(), getCryptoData()); } @CheckResult public CryptoInputParcel withNoCachePassphrase() { - return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), false, getParcelableProxy(), - getCryptoData()); + return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), getPassphraseSubkey(), + false, getParcelableProxy(), getCryptoData()); } @CheckResult public CryptoInputParcel withSignatureTime(Date signatureTime) { - return new AutoValue_CryptoInputParcel(signatureTime, getPassphrase(), isCachePassphrase(), - getParcelableProxy(), getCryptoData()); + return new AutoValue_CryptoInputParcel(signatureTime, getPassphrase(), getPassphraseSubkey(), + isCachePassphrase(), getParcelableProxy(), getCryptoData()); } @CheckResult public CryptoInputParcel withParcelableProxy(ParcelableProxy parcelableProxy) { - return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), isCachePassphrase(), - parcelableProxy, getCryptoData()); + return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), getPassphraseSubkey(), + isCachePassphrase(), parcelableProxy, getCryptoData()); } } diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java index 15cd45e6d..db29136fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/service/input/RequiredInputParcel.java @@ -46,19 +46,25 @@ public class RequiredInputParcel implements Parcelable { public final byte[][] mInputData; public final int[] mSignAlgos; - private Long mMasterKeyId; - private Long mSubKeyId; + private long[] mMasterKeyIds; + private long[] mSubKeyIds; public boolean mSkipCaching = false; private RequiredInputParcel(RequiredInputType type, byte[][] inputData, - int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) { + int[] signAlgos, Date signatureTime, long[] masterKeyIds, long[] subKeyIds) { mType = type; mInputData = inputData; mSignAlgos = signAlgos; mSignatureTime = signatureTime; - mMasterKeyId = masterKeyId; - mSubKeyId = subKeyId; + mMasterKeyIds = masterKeyIds; + mSubKeyIds = subKeyIds; + } + + private RequiredInputParcel(RequiredInputType type, byte[][] inputData, + int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) { + this(type, inputData, signAlgos, signatureTime, masterKeyId != null ? new long[] { masterKeyId } : null, + subKeyId != null ? new long[] { subKeyId } : null); } public RequiredInputParcel(Parcel source) { @@ -87,18 +93,26 @@ public class RequiredInputParcel implements Parcelable { } mSignatureTime = source.readInt() != 0 ? new Date(source.readLong()) : null; - mMasterKeyId = source.readInt() != 0 ? source.readLong() : null; - mSubKeyId = source.readInt() != 0 ? source.readLong() : null; + mMasterKeyIds = source.readInt() != 0 ? source.createLongArray() : null; + mSubKeyIds = source.readInt() != 0 ? source.createLongArray() : null; mSkipCaching = source.readInt() != 0; } public Long getMasterKeyId() { - return mMasterKeyId; + return mMasterKeyIds == null ? null : mMasterKeyIds[0]; } public Long getSubKeyId() { - return mSubKeyId; + return mSubKeyIds == null ? null : mSubKeyIds[0]; + } + + public long[] getMasterKeyIds() { + return mMasterKeyIds; + } + + public long[] getSubKeyIds() { + return mSubKeyIds; } public static RequiredInputParcel createRetryUploadOperation() { @@ -134,7 +148,7 @@ public class RequiredInputParcel implements Parcelable { public static RequiredInputParcel createSecurityTokenReset() { return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_RESET_CARD, - null, null, null, null, null); + null, null, null, (long[]) null, null); } public static RequiredInputParcel createRequiredAuthenticationPassphrase( @@ -157,18 +171,18 @@ public class RequiredInputParcel implements Parcelable { public static RequiredInputParcel createRequiredSymmetricPassphrase() { return new RequiredInputParcel(RequiredInputType.PASSPHRASE_SYMMETRIC, - null, null, null, null, null); + null, null, null, (long[]) null, null); } public static RequiredInputParcel createRequiredBackupCode() { return new RequiredInputParcel(RequiredInputType.BACKUP_CODE, - null, null, null, null, null); + null, null, null, (long[]) null, null); } public static RequiredInputParcel createRequiredPassphrase( RequiredInputParcel req) { return new RequiredInputParcel(RequiredInputType.PASSPHRASE, - null, null, req.mSignatureTime, req.mMasterKeyId, req.mSubKeyId); + null, null, req.mSignatureTime, req.mMasterKeyIds, req.mSubKeyIds); } @Override @@ -197,15 +211,15 @@ public class RequiredInputParcel implements Parcelable { } else { dest.writeInt(0); } - if (mMasterKeyId != null) { + if (mMasterKeyIds != null) { dest.writeInt(1); - dest.writeLong(mMasterKeyId); + dest.writeLongArray(mMasterKeyIds); } else { dest.writeInt(0); } - if (mSubKeyId != null) { + if (mSubKeyIds != null) { dest.writeInt(1); - dest.writeLong(mSubKeyId); + dest.writeLongArray(mSubKeyIds); } else { dest.writeInt(0); } @@ -319,7 +333,7 @@ public class RequiredInputParcel implements Parcelable { } public void addAll(RequiredInputParcel input) { - if (!mMasterKeyId.equals(input.mMasterKeyId)) { + if (!mMasterKeyId.equals(input.mMasterKeyIds)) { throw new AssertionError("Master keys must match, this is a programming error!"); } if (input.mType != RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD) { @@ -335,4 +349,30 @@ public class RequiredInputParcel implements Parcelable { } + public static class RequireAnyDecryptPassphraseBuilder { + private final ArrayList masterKeyIds = new ArrayList<>(); + private final ArrayList subKeyIds = new ArrayList<>(); + + public RequiredInputParcel build() { + int numIds = masterKeyIds.size(); + long[] masterKeyIdsArr = new long[numIds]; + long[] subKeyIdsArr = new long[numIds]; + for (int i = 0; i < numIds; i++) { + masterKeyIdsArr[i] = masterKeyIds.get(i); + subKeyIdsArr[i] = subKeyIds.get(i); + } + + return new RequiredInputParcel(RequiredInputType.PASSPHRASE, + null, null, null, masterKeyIdsArr, subKeyIdsArr); + } + + public void add(long masterKeyId, long subKeyId) { + masterKeyIds.add(masterKeyId); + subKeyIds.add(subKeyId); + } + + public boolean isEmpty() { + return masterKeyIds.isEmpty(); + } + } } 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 0c11366f7..f7bb40781 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java @@ -25,6 +25,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; +import android.os.SystemClock; import android.support.annotation.NonNull; import android.support.v4.app.DialogFragment; import android.support.v4.app.FragmentActivity; @@ -117,7 +118,7 @@ public class PassphraseDialogActivity extends FragmentActivity { if (pubRing.getSecretKeyType(requiredInput.getSubKeyId()) == SecretKeyType.PASSPHRASE_EMPTY) { // also return passphrase back to activity Intent returnIntent = new Intent(); - cryptoInputParcel = cryptoInputParcel.withPassphrase(new Passphrase("")); + cryptoInputParcel = cryptoInputParcel.withPassphrase(new Passphrase(""), requiredInput.getSubKeyId()); returnIntent.putExtra(RESULT_CRYPTO_INPUT, cryptoInputParcel); setResult(RESULT_OK, returnIntent); finish(); @@ -231,40 +232,45 @@ public class PassphraseDialogActivity extends FragmentActivity { hint = getString(R.string.label_passphrase); } else { try { - long subKeyId = mRequiredInput.getSubKeyId(); - - KeyRepository helper = - KeyRepository.create(getContext()); - CachedPublicKeyRing cachedPublicKeyRing = helper.getCachedPublicKeyRing( - 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. - String mainUserId = cachedPublicKeyRing.getPrimaryUserIdWithFallback(); - OpenPgpUtils.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId); - if (mainUserIdSplit.name != null) { - userId = mainUserIdSplit.name; + long[] subKeyIds = mRequiredInput.getSubKeyIds(); + if (subKeyIds.length > 1) { + message = getString(R.string.passphrase_for_any); + hint = getString(R.string.label_passphrase); } else { - userId = getString(R.string.user_id_no_name); - } + long subKeyId = subKeyIds[0]; - keyType = cachedPublicKeyRing.getSecretKeyType(subKeyId); - switch (keyType) { - case PASSPHRASE: - message = getString(R.string.passphrase_for, userId); - hint = getString(R.string.label_passphrase); - break; - case DIVERT_TO_CARD: - message = getString(R.string.security_token_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)"); - } + KeyRepository helper = + KeyRepository.create(getContext()); + CachedPublicKeyRing cachedPublicKeyRing = helper.getCachedPublicKeyRing( + 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. + String mainUserId = cachedPublicKeyRing.getPrimaryUserIdWithFallback(); + OpenPgpUtils.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId); + if (mainUserIdSplit.name != null) { + userId = mainUserIdSplit.name; + } else { + userId = getString(R.string.user_id_no_name); + } + keyType = cachedPublicKeyRing.getSecretKeyType(subKeyId); + switch (keyType) { + case PASSPHRASE: + message = getString(R.string.passphrase_for, userId); + hint = getString(R.string.label_passphrase); + break; + case DIVERT_TO_CARD: + message = getString(R.string.security_token_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(""), subKeyId); + default: + throw new AssertionError("Unhandled SecretKeyType (should not happen)"); + } + } } catch (PgpKeyNotFoundException | KeyRepository.NotFoundException e) { alert.setTitle(R.string.title_key_not_found); alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId())); @@ -420,7 +426,7 @@ public class PassphraseDialogActivity extends FragmentActivity { backupCodeInput.deleteCharAt(backupCodeInput.length() - 1); Passphrase passphrase = new Passphrase(backupCodeInput.toString()); - finishCaching(passphrase); + finishCaching(passphrase, null); return; } @@ -438,96 +444,107 @@ public class PassphraseDialogActivity extends FragmentActivity { getString(R.string.passp_cache_notif_pwd), timeToLiveSeconds); } - finishCaching(passphrase); + finishCaching(passphrase, null); return; } - mLayout.setDisplayedChild(1); - positive.setEnabled(false); - - new AsyncTask() { - - @Override - protected CanonicalizedSecretKey doInBackground(Void... params) { - try { - long timeBeforeOperation = System.currentTimeMillis(); - - Long subKeyId = mRequiredInput.getSubKeyId(); - CanonicalizedSecretKeyRing secretKeyRing = - KeyRepository.create(getContext()).getCanonicalizedSecretKeyRing( - KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); - CanonicalizedSecretKey secretKeyToUnlock = - secretKeyRing.getSecretKey(subKeyId); - - // this is the operation may take a very long time (100ms to several seconds!) - boolean unlockSucceeded = secretKeyToUnlock.unlock(passphrase); - - // if it didn't take that long, give the user time to appreciate the progress bar - long operationTime = System.currentTimeMillis() - timeBeforeOperation; - if (operationTime < 100) { - try { - Thread.sleep(100 - operationTime); - } catch (InterruptedException e) { - // ignore - } - } - - return unlockSucceeded ? secretKeyToUnlock : null; - } catch (NotFoundException | PgpGeneralException e) { - Toast.makeText(getActivity(), R.string.error_could_not_extract_private_key, - Toast.LENGTH_SHORT).show(); - - getActivity().setResult(RESULT_CANCELED); - dismiss(); - getActivity().finish(); - return null; - } - } - - /** Handle a good or bad passphrase. This happens in the UI thread! */ - @Override - protected void onPostExecute(CanonicalizedSecretKey result) { - super.onPostExecute(result); - - // if we were cancelled in the meantime, the result isn't relevant anymore - if (mIsCancelled) { - return; - } - - // if the passphrase was wrong, reset and re-enable the dialogue - if (result == null) { - mPassphraseEditText.setText(""); - mPassphraseEditText.setError(getString(R.string.wrong_passphrase)); - mLayout.setDisplayedChild(0); - positive.setEnabled(true); - return; - } - - // cache the new passphrase as specified in CryptoInputParcel - Log.d(Constants.TAG, "Everything okay!"); - - if (mRequiredInput.mSkipCaching) { - Log.d(Constants.TAG, "Not caching entered passphrase!"); - } else { - Log.d(Constants.TAG, "Caching entered passphrase"); - - try { - PassphraseCacheService.addCachedPassphrase(getActivity(), - mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId(), passphrase, - result.getRing().getPrimaryUserIdWithFallback(), timeToLiveSeconds); - } catch (PgpKeyNotFoundException e) { - Log.e(Constants.TAG, "adding of a passphrase failed", e); - } - } - - finishCaching(passphrase); - } - }.execute(); + checkPassphraseAndFinishCaching(positive, passphrase, timeToLiveSeconds); } + }); } - private void finishCaching(Passphrase passphrase) { + private void checkPassphraseAndFinishCaching(final Button positive, final Passphrase passphrase, + final int timeToLiveSeconds) { + mLayout.setDisplayedChild(1); + positive.setEnabled(false); + + new AsyncTask() { + + @Override + protected CanonicalizedSecretKey doInBackground(Void... params) { + try { + long timeBeforeOperation = SystemClock.elapsedRealtime(); + + CanonicalizedSecretKey canonicalizedSecretKey = null; + for (long subKeyId : mRequiredInput.getSubKeyIds()) { + CanonicalizedSecretKeyRing secretKeyRing = + KeyRepository.create(getContext()).getCanonicalizedSecretKeyRing( + KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)); + CanonicalizedSecretKey secretKeyToUnlock = + secretKeyRing.getSecretKey(subKeyId); + + // this is the operation may take a very long time (100ms to several seconds!) + boolean unlockSucceeded = secretKeyToUnlock.unlock(passphrase); + if (unlockSucceeded) { + canonicalizedSecretKey = secretKeyToUnlock; + } + } + + // if it didn't take that long, give the user time to appreciate the progress bar + long operationTime = SystemClock.elapsedRealtime() - timeBeforeOperation; + if (operationTime < 100) { + try { + Thread.sleep(100 - operationTime); + } catch (InterruptedException e) { + // ignore + } + } + + return canonicalizedSecretKey; + } catch (NotFoundException | PgpGeneralException e) { + Toast.makeText(getActivity(), R.string.error_could_not_extract_private_key, + Toast.LENGTH_SHORT).show(); + + getActivity().setResult(RESULT_CANCELED); + dismiss(); + getActivity().finish(); + return null; + } + } + + /** Handle a good or bad passphrase. This happens in the UI thread! */ + @Override + protected void onPostExecute(CanonicalizedSecretKey unlockedKey) { + super.onPostExecute(unlockedKey); + + // if we were cancelled in the meantime, the result isn't relevant anymore + if (mIsCancelled) { + return; + } + + // if the passphrase was wrong, reset and re-enable the dialogue + if (unlockedKey == null) { + mPassphraseEditText.setText(""); + mPassphraseEditText.setError(getString(R.string.wrong_passphrase)); + mLayout.setDisplayedChild(0); + positive.setEnabled(true); + return; + } + + // cache the new passphrase as specified in CryptoInputParcel + Log.d(Constants.TAG, "Everything okay!"); + + if (mRequiredInput.mSkipCaching) { + Log.d(Constants.TAG, "Not caching entered passphrase!"); + } else { + Log.d(Constants.TAG, "Caching entered passphrase"); + + try { + PassphraseCacheService.addCachedPassphrase(getActivity(), + unlockedKey.getRing().getMasterKeyId(), unlockedKey.getKeyId(), passphrase, + unlockedKey.getRing().getPrimaryUserIdWithFallback(), timeToLiveSeconds); + } catch (PgpKeyNotFoundException e) { + Log.e(Constants.TAG, "adding of a passphrase failed", e); + } + } + + finishCaching(passphrase, unlockedKey.getKeyId()); + } + }.execute(); + } + + private void finishCaching(Passphrase passphrase, Long subKeyId) { // any indication this isn't needed anymore, don't do it. if (mIsCancelled || getActivity() == null) { return; @@ -535,7 +552,7 @@ public class PassphraseDialogActivity extends FragmentActivity { CryptoInputParcel inputParcel = getArguments().getParcelable(EXTRA_CRYPTO_INPUT); // noinspection ConstantConditions, we handle the non-null case in PassphraseDialogActivity.onCreate() - inputParcel = inputParcel.withPassphrase(passphrase); + inputParcel = inputParcel.withPassphrase(passphrase, subKeyId); ((PassphraseDialogActivity) getActivity()).handleResult(inputParcel); diff --git a/OpenKeychain/src/main/res/values/strings.xml b/OpenKeychain/src/main/res/values/strings.xml index c50a93087..f39ba7d98 100644 --- a/OpenKeychain/src/main/res/values/strings.xml +++ b/OpenKeychain/src/main/res/values/strings.xml @@ -328,6 +328,7 @@ "Enter password" "Enter backup code" "Enter password for '%s'" + "Enter password" "Switch to alphabetic keyboard" "Switch to numeric keyboard" "Enter PIN for '%s'" diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java index 898e2a91d..8f3726895 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/AuthenticationOperationTest.java @@ -175,7 +175,7 @@ public class AuthenticationOperationTest { .createAuthenticationParcel(authData.build(), challenge); CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(); - inputParcel = inputParcel.withPassphrase(mKeyPhrase); + inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId); AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel); @@ -221,7 +221,7 @@ public class AuthenticationOperationTest { .createAuthenticationParcel(authData.build(), challenge); CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(); - inputParcel = inputParcel.withPassphrase(mKeyPhrase); + inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId); AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel); @@ -267,7 +267,7 @@ public class AuthenticationOperationTest { .createAuthenticationParcel(authData.build(), challenge); CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(); - inputParcel = inputParcel.withPassphrase(mKeyPhrase); + inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId); AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel); @@ -315,7 +315,7 @@ public class AuthenticationOperationTest { .createAuthenticationParcel(authData.build(), challenge); CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(); - inputParcel = inputParcel.withPassphrase(mKeyPhrase); + inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId); AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel); @@ -364,7 +364,7 @@ public class AuthenticationOperationTest { .createAuthenticationParcel(authData.build(), challenge); CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(); - inputParcel = inputParcel.withPassphrase(mKeyPhrase); + inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId); AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel); @@ -387,7 +387,7 @@ public class AuthenticationOperationTest { .createAuthenticationParcel(authData.build(), challenge); CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel(); - inputParcel = inputParcel.withPassphrase(mKeyPhrase); + inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId); AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel); diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java index 2fa7e3895..943446728 100644 --- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java +++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpEncryptDecryptTest.java @@ -58,6 +58,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData; import org.sufficientlysecure.keychain.service.SaveKeyringParcel; import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange; import org.sufficientlysecure.keychain.service.input.CryptoInputParcel; +import org.sufficientlysecure.keychain.service.input.RequiredInputParcel; import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.RequiredInputType; import org.sufficientlysecure.keychain.support.KeyringTestingHelper; import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket; @@ -1064,6 +1065,25 @@ public class PgpEncryptDecryptTest { Assert.assertEquals(1024, encryptionKeySecurityProblem.bitStrength); } + @Test + public void testDecryptForTwoKeys() throws Exception { + InputStream in = getResourceAsStream("/test-ciphertexts/two_keys.asc"); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + InputData data = new InputData(in, in.available()); + + PgpDecryptVerifyOperation op = operationWithFakePassphraseCache(null, null, null); + PgpDecryptVerifyInputParcel input = PgpDecryptVerifyInputParcel.builder().build(); + DecryptVerifyResult result = op.execute(input, CryptoInputParcel.createCryptoInputParcel(), data, out); + + RequiredInputParcel requiredInputParcel = result.getRequiredInputParcel(); + Assert.assertNotNull(requiredInputParcel); + Assert.assertEquals(3, requiredInputParcel.getMasterKeyIds().length); + Assert.assertEquals(mStaticRing1.getMasterKeyId(), requiredInputParcel.getMasterKeyIds()[0]); + Assert.assertEquals(mStaticRing1.getMasterKeyId(), requiredInputParcel.getMasterKeyIds()[1]); + Assert.assertEquals(mStaticRing2.getMasterKeyId(), requiredInputParcel.getMasterKeyIds()[2]); + } + private PgpDecryptVerifyOperation operationWithFakePassphraseCache( final Passphrase passphrase, final Long checkMasterKeyId, final Long checkSubKeyId) { diff --git a/OpenKeychain/src/test/resources/test-ciphertexts/two_keys.asc b/OpenKeychain/src/test/resources/test-ciphertexts/two_keys.asc new file mode 100644 index 000000000..9c7b345cb Binary files /dev/null and b/OpenKeychain/src/test/resources/test-ciphertexts/two_keys.asc differ