Merge pull request #2226 from open-keychain/multi-passphrase
Handle decryption with multiple candidate keys
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user