Merge pull request #2226 from open-keychain/multi-passphrase

Handle decryption with multiple candidate keys
This commit is contained in:
Vincent Breitmoser
2018-01-12 15:10:25 +01:00
committed by GitHub
10 changed files with 262 additions and 172 deletions

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();
@@ -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<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 +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);