check all requested keys in PassphraseDialogActivity

This commit is contained in:
Vincent Breitmoser
2017-11-23 22:19:44 +01:00
parent 66f713c847
commit 04efa9e66d
6 changed files with 139 additions and 121 deletions

View File

@@ -649,7 +649,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
passphrase = null;
} else if (secretKeyType == SecretKeyType.PASSPHRASE_EMPTY) {
passphrase = new Passphrase("");
} else if (cryptoInput.hasPassphrase()) {
} else if (cryptoInput.hasPassphraseForSubkey(subKeyId)) {
passphrase = cryptoInput.getPassphrase();
} else {
// if no passphrase was explicitly set try to get it from the cache service
@@ -712,7 +712,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
// if no passphrase is given, return here
// indicating that a passphrase is missing!
if (!cryptoInput.hasPassphrase()) {
if (!cryptoInput.hasPassphraseForSymmetric()) {
try {
passphrase = getCachedPassphrase(key.symmetric);

View File

@@ -454,7 +454,7 @@ public class PgpKeyOperation {
}
// Do we require a passphrase? If so, pass it along
if (!isDivertToCard(masterSecretKey) && !cryptoInput.hasPassphrase()) {
if (!isDivertToCard(masterSecretKey) && !cryptoInput.hasPassphraseForSubkey(masterSecretKey.getKeyID())) {
log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
masterSecretKey.getKeyID(), masterSecretKey.getKeyID(),
@@ -1277,7 +1277,7 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
if (!cryptoInput.hasPassphrase()) {
if (!cryptoInput.hasPassphraseForSubkey(nonDummy.getKeyID())) {
log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(

View File

@@ -163,7 +163,7 @@ public class OpenPgpService extends Service {
// override passphrase in input parcel if given by API call
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
inputParcel = inputParcel.withPassphrase(
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)), null);
}
// execute PGP operation!
@@ -267,7 +267,7 @@ public class OpenPgpService extends Service {
// override passphrase in input parcel if given by API call
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
inputParcel = inputParcel.withPassphrase(
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)), null);
}
// TODO this is not correct!
@@ -373,7 +373,7 @@ public class OpenPgpService extends Service {
// override passphrase in input parcel if given by API call
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
cryptoInput = cryptoInput.withPassphrase(
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)), null);
}
if (data.hasExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT)) {
OpenPgpDecryptionResult decryptionResult = data.getParcelableExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT);

View File

@@ -40,10 +40,16 @@ public abstract class CryptoInputParcel implements Parcelable {
public abstract Date getSignatureTime();
@Nullable
public abstract Passphrase getPassphrase();
@Nullable
public abstract Long getPassphraseSubkey();
public abstract boolean isCachePassphrase();
public boolean hasPassphrase() {
return getPassphrase() != null;
public boolean hasPassphraseForSubkey(long subKeyId) {
return getPassphrase() != null && (getPassphraseSubkey() == null || getPassphraseSubkey() == subKeyId);
}
public boolean hasPassphraseForSymmetric() {
return getPassphrase() != null && getPassphraseSubkey() == null;
}
// used to supply an explicit proxy to operations that require it
@@ -59,43 +65,43 @@ public abstract class CryptoInputParcel implements Parcelable {
public static CryptoInputParcel createCryptoInputParcel() {
return new AutoValue_CryptoInputParcel(null, null, true, null, Collections.<ByteBuffer,byte[]>emptyMap());
return new AutoValue_CryptoInputParcel(null, null, null, true, null, Collections.<ByteBuffer,byte[]>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.<ByteBuffer,byte[]>emptyMap());
}
public static CryptoInputParcel createCryptoInputParcel(Passphrase passphrase) {
return new AutoValue_CryptoInputParcel(null, passphrase, true, null, Collections.<ByteBuffer,byte[]>emptyMap());
return new AutoValue_CryptoInputParcel(null, passphrase, null, true, null, Collections.<ByteBuffer,byte[]>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.<ByteBuffer,byte[]>emptyMap());
}
public static CryptoInputParcel createCryptoInputParcel(ParcelableProxy parcelableProxy) {
return new AutoValue_CryptoInputParcel(null, null, true, parcelableProxy, new HashMap<ByteBuffer,byte[]>());
return new AutoValue_CryptoInputParcel(null, null, null, true, parcelableProxy, new HashMap<ByteBuffer,byte[]>());
}
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<ByteBuffer,byte[]>());
}
public static CryptoInputParcel createCryptoInputParcel(boolean cachePassphrase) {
return new AutoValue_CryptoInputParcel(null, null, cachePassphrase, null, new HashMap<ByteBuffer,byte[]>());
return new AutoValue_CryptoInputParcel(null, null, null, cachePassphrase, null, new HashMap<ByteBuffer,byte[]>());
}
// 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());
}
}

View File

@@ -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();
@@ -260,7 +261,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
break;
// special case: empty passphrase just returns the empty passphrase
case PASSPHRASE_EMPTY:
finishCaching(new Passphrase(""));
finishCaching(new Passphrase(""), subKeyId);
default:
throw new AssertionError("Unhandled SecretKeyType (should not happen)");
}
@@ -420,7 +421,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 +439,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<Void, Void, CanonicalizedSecretKey>() {
@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<Void, Void, CanonicalizedSecretKey>() {
@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 +547,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);