Merge pull request #2226 from open-keychain/multi-passphrase
Handle decryption with multiple candidate keys
This commit is contained in:
@@ -78,6 +78,7 @@ import org.sufficientlysecure.keychain.provider.KeyWritableRepository;
|
|||||||
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
||||||
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
|
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.ui.util.KeyFormattingUtils;
|
||||||
import org.sufficientlysecure.keychain.util.CharsetVerifier;
|
import org.sufficientlysecure.keychain.util.CharsetVerifier;
|
||||||
import org.sufficientlysecure.keychain.util.FileHelper;
|
import org.sufficientlysecure.keychain.util.FileHelper;
|
||||||
@@ -581,6 +582,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
|
|
||||||
Iterator<?> it = enc.getEncryptedDataObjects();
|
Iterator<?> it = enc.getEncryptedDataObjects();
|
||||||
|
|
||||||
|
RequireAnyDecryptPassphraseBuilder requirePassphraseBuilder = new RequireAnyDecryptPassphraseBuilder();
|
||||||
|
|
||||||
// go through all objects and find one we can decrypt
|
// go through all objects and find one we can decrypt
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
Object obj = it.next();
|
Object obj = it.next();
|
||||||
@@ -644,7 +647,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
passphrase = null;
|
passphrase = null;
|
||||||
} else if (secretKeyType == SecretKeyType.PASSPHRASE_EMPTY) {
|
} else if (secretKeyType == SecretKeyType.PASSPHRASE_EMPTY) {
|
||||||
passphrase = new Passphrase("");
|
passphrase = new Passphrase("");
|
||||||
} else if (cryptoInput.hasPassphrase()) {
|
} else if (cryptoInput.hasPassphraseForSubkey(subKeyId)) {
|
||||||
passphrase = cryptoInput.getPassphrase();
|
passphrase = cryptoInput.getPassphrase();
|
||||||
} else {
|
} else {
|
||||||
// if no passphrase was explicitly set try to get it from the cache service
|
// if no passphrase was explicitly set try to get it from the cache service
|
||||||
@@ -660,9 +663,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
// if passphrase was not cached, return here indicating that a passphrase is missing!
|
||||||
if (passphrase == null) {
|
if (passphrase == null) {
|
||||||
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
|
||||||
return result.with(new DecryptVerifyResult(log,
|
requirePassphraseBuilder.add(masterKeyId, subKeyId);
|
||||||
RequiredInputParcel.createRequiredDecryptPassphrase(masterKeyId, subKeyId),
|
continue;
|
||||||
cryptoInput));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -708,7 +710,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
|
|
||||||
// if no passphrase is given, return here
|
// if no passphrase is given, return here
|
||||||
// indicating that a passphrase is missing!
|
// indicating that a passphrase is missing!
|
||||||
if (!cryptoInput.hasPassphrase()) {
|
if (!cryptoInput.hasPassphraseForSymmetric()) {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
passphrase = getCachedPassphrase(key.symmetric);
|
passphrase = getCachedPassphrase(key.symmetric);
|
||||||
@@ -736,6 +738,10 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!asymmetricPacketFound && !requirePassphraseBuilder.isEmpty()) {
|
||||||
|
return result.with(new DecryptVerifyResult(log, requirePassphraseBuilder.build(), cryptoInput));
|
||||||
|
}
|
||||||
|
|
||||||
// More data, just acknowledge and ignore.
|
// More data, just acknowledge and ignore.
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
Object obj = it.next();
|
Object obj = it.next();
|
||||||
|
|||||||
@@ -453,7 +453,7 @@ public class PgpKeyOperation {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Do we require a passphrase? If so, pass it along
|
// 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);
|
log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
|
||||||
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||||
masterSecretKey.getKeyID(), masterSecretKey.getKeyID(),
|
masterSecretKey.getKeyID(), masterSecretKey.getKeyID(),
|
||||||
@@ -1276,7 +1276,7 @@ public class PgpKeyOperation {
|
|||||||
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cryptoInput.hasPassphrase()) {
|
if (!cryptoInput.hasPassphraseForSubkey(nonDummy.getKeyID())) {
|
||||||
log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
|
log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
|
||||||
|
|
||||||
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
|
||||||
|
|||||||
@@ -162,7 +162,7 @@ public class OpenPgpService extends Service {
|
|||||||
// override passphrase in input parcel if given by API call
|
// override passphrase in input parcel if given by API call
|
||||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||||
inputParcel = inputParcel.withPassphrase(
|
inputParcel = inputParcel.withPassphrase(
|
||||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
|
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute PGP operation!
|
// execute PGP operation!
|
||||||
@@ -266,7 +266,7 @@ public class OpenPgpService extends Service {
|
|||||||
// override passphrase in input parcel if given by API call
|
// override passphrase in input parcel if given by API call
|
||||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||||
inputParcel = inputParcel.withPassphrase(
|
inputParcel = inputParcel.withPassphrase(
|
||||||
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)));
|
new Passphrase(data.getCharArrayExtra(OpenPgpApi.EXTRA_PASSPHRASE)), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO this is not correct!
|
// TODO this is not correct!
|
||||||
@@ -372,7 +372,7 @@ public class OpenPgpService extends Service {
|
|||||||
// override passphrase in input parcel if given by API call
|
// override passphrase in input parcel if given by API call
|
||||||
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
if (data.hasExtra(OpenPgpApi.EXTRA_PASSPHRASE)) {
|
||||||
cryptoInput = cryptoInput.withPassphrase(
|
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)) {
|
if (data.hasExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT)) {
|
||||||
OpenPgpDecryptionResult decryptionResult = data.getParcelableExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT);
|
OpenPgpDecryptionResult decryptionResult = data.getParcelableExtra(OpenPgpApi.EXTRA_DECRYPTION_RESULT);
|
||||||
|
|||||||
@@ -40,10 +40,16 @@ public abstract class CryptoInputParcel implements Parcelable {
|
|||||||
public abstract Date getSignatureTime();
|
public abstract Date getSignatureTime();
|
||||||
@Nullable
|
@Nullable
|
||||||
public abstract Passphrase getPassphrase();
|
public abstract Passphrase getPassphrase();
|
||||||
|
@Nullable
|
||||||
|
public abstract Long getPassphraseSubkey();
|
||||||
public abstract boolean isCachePassphrase();
|
public abstract boolean isCachePassphrase();
|
||||||
|
|
||||||
public boolean hasPassphrase() {
|
public boolean hasPassphraseForSubkey(long subKeyId) {
|
||||||
return getPassphrase() != null;
|
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
|
// 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() {
|
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) {
|
public static CryptoInputParcel createCryptoInputParcel(Date signatureTime, Passphrase passphrase) {
|
||||||
if (signatureTime == null) {
|
if (signatureTime == null) {
|
||||||
signatureTime = new Date();
|
signatureTime = new Date();
|
||||||
}
|
}
|
||||||
return new AutoValue_CryptoInputParcel(signatureTime, passphrase, true, null,
|
return new AutoValue_CryptoInputParcel(signatureTime, passphrase, null, true, null,
|
||||||
Collections.<ByteBuffer,byte[]>emptyMap());
|
Collections.<ByteBuffer,byte[]>emptyMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CryptoInputParcel createCryptoInputParcel(Passphrase passphrase) {
|
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) {
|
public static CryptoInputParcel createCryptoInputParcel(Date signatureTime) {
|
||||||
if (signatureTime == null) {
|
if (signatureTime == null) {
|
||||||
signatureTime = new Date();
|
signatureTime = new Date();
|
||||||
}
|
}
|
||||||
return new AutoValue_CryptoInputParcel(signatureTime, null, true, null,
|
return new AutoValue_CryptoInputParcel(signatureTime, null, null, true, null,
|
||||||
Collections.<ByteBuffer,byte[]>emptyMap());
|
Collections.<ByteBuffer,byte[]>emptyMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CryptoInputParcel createCryptoInputParcel(ParcelableProxy parcelableProxy) {
|
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) {
|
public static CryptoInputParcel createCryptoInputParcel(Date signatureTime, boolean cachePassphrase) {
|
||||||
if (signatureTime == null) {
|
if (signatureTime == null) {
|
||||||
signatureTime = new Date();
|
signatureTime = new Date();
|
||||||
}
|
}
|
||||||
return new AutoValue_CryptoInputParcel(signatureTime, null, cachePassphrase, null,
|
return new AutoValue_CryptoInputParcel(signatureTime, null, null, cachePassphrase, null,
|
||||||
new HashMap<ByteBuffer,byte[]>());
|
new HashMap<ByteBuffer,byte[]>());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static CryptoInputParcel createCryptoInputParcel(boolean cachePassphrase) {
|
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!
|
// TODO get rid of this!
|
||||||
@@ -105,8 +111,8 @@ public abstract class CryptoInputParcel implements Parcelable {
|
|||||||
newCryptoData.put(ByteBuffer.wrap(hash), signedHash);
|
newCryptoData.put(ByteBuffer.wrap(hash), signedHash);
|
||||||
newCryptoData = Collections.unmodifiableMap(newCryptoData);
|
newCryptoData = Collections.unmodifiableMap(newCryptoData);
|
||||||
|
|
||||||
return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), isCachePassphrase(),
|
return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), getPassphraseSubkey(),
|
||||||
getParcelableProxy(), newCryptoData);
|
isCachePassphrase(), getParcelableProxy(), newCryptoData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
@@ -115,32 +121,32 @@ public abstract class CryptoInputParcel implements Parcelable {
|
|||||||
newCryptoData.putAll(cachedSessionKeys);
|
newCryptoData.putAll(cachedSessionKeys);
|
||||||
newCryptoData = Collections.unmodifiableMap(newCryptoData);
|
newCryptoData = Collections.unmodifiableMap(newCryptoData);
|
||||||
|
|
||||||
return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), isCachePassphrase(),
|
return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), getPassphraseSubkey(),
|
||||||
getParcelableProxy(), newCryptoData);
|
isCachePassphrase(), getParcelableProxy(), newCryptoData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
public CryptoInputParcel withPassphrase(Passphrase passphrase) {
|
public CryptoInputParcel withPassphrase(Passphrase passphrase, Long subKeyId) {
|
||||||
return new AutoValue_CryptoInputParcel(getSignatureTime(), passphrase, isCachePassphrase(),
|
return new AutoValue_CryptoInputParcel(getSignatureTime(), passphrase, subKeyId, isCachePassphrase(),
|
||||||
getParcelableProxy(), getCryptoData());
|
getParcelableProxy(), getCryptoData());
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
public CryptoInputParcel withNoCachePassphrase() {
|
public CryptoInputParcel withNoCachePassphrase() {
|
||||||
return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), false, getParcelableProxy(),
|
return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), getPassphraseSubkey(),
|
||||||
getCryptoData());
|
false, getParcelableProxy(), getCryptoData());
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
public CryptoInputParcel withSignatureTime(Date signatureTime) {
|
public CryptoInputParcel withSignatureTime(Date signatureTime) {
|
||||||
return new AutoValue_CryptoInputParcel(signatureTime, getPassphrase(), isCachePassphrase(),
|
return new AutoValue_CryptoInputParcel(signatureTime, getPassphrase(), getPassphraseSubkey(),
|
||||||
getParcelableProxy(), getCryptoData());
|
isCachePassphrase(), getParcelableProxy(), getCryptoData());
|
||||||
}
|
}
|
||||||
|
|
||||||
@CheckResult
|
@CheckResult
|
||||||
public CryptoInputParcel withParcelableProxy(ParcelableProxy parcelableProxy) {
|
public CryptoInputParcel withParcelableProxy(ParcelableProxy parcelableProxy) {
|
||||||
return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), isCachePassphrase(),
|
return new AutoValue_CryptoInputParcel(getSignatureTime(), getPassphrase(), getPassphraseSubkey(),
|
||||||
parcelableProxy, getCryptoData());
|
isCachePassphrase(), parcelableProxy, getCryptoData());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,19 +46,25 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
public final byte[][] mInputData;
|
public final byte[][] mInputData;
|
||||||
public final int[] mSignAlgos;
|
public final int[] mSignAlgos;
|
||||||
|
|
||||||
private Long mMasterKeyId;
|
private long[] mMasterKeyIds;
|
||||||
private Long mSubKeyId;
|
private long[] mSubKeyIds;
|
||||||
|
|
||||||
public boolean mSkipCaching = false;
|
public boolean mSkipCaching = false;
|
||||||
|
|
||||||
private RequiredInputParcel(RequiredInputType type, byte[][] inputData,
|
private RequiredInputParcel(RequiredInputType type, byte[][] inputData,
|
||||||
int[] signAlgos, Date signatureTime, Long masterKeyId, Long subKeyId) {
|
int[] signAlgos, Date signatureTime, long[] masterKeyIds, long[] subKeyIds) {
|
||||||
mType = type;
|
mType = type;
|
||||||
mInputData = inputData;
|
mInputData = inputData;
|
||||||
mSignAlgos = signAlgos;
|
mSignAlgos = signAlgos;
|
||||||
mSignatureTime = signatureTime;
|
mSignatureTime = signatureTime;
|
||||||
mMasterKeyId = masterKeyId;
|
mMasterKeyIds = masterKeyIds;
|
||||||
mSubKeyId = subKeyId;
|
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) {
|
public RequiredInputParcel(Parcel source) {
|
||||||
@@ -87,18 +93,26 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mSignatureTime = source.readInt() != 0 ? new Date(source.readLong()) : null;
|
mSignatureTime = source.readInt() != 0 ? new Date(source.readLong()) : null;
|
||||||
mMasterKeyId = source.readInt() != 0 ? source.readLong() : null;
|
mMasterKeyIds = source.readInt() != 0 ? source.createLongArray() : null;
|
||||||
mSubKeyId = source.readInt() != 0 ? source.readLong() : null;
|
mSubKeyIds = source.readInt() != 0 ? source.createLongArray() : null;
|
||||||
mSkipCaching = source.readInt() != 0;
|
mSkipCaching = source.readInt() != 0;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getMasterKeyId() {
|
public Long getMasterKeyId() {
|
||||||
return mMasterKeyId;
|
return mMasterKeyIds == null ? null : mMasterKeyIds[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getSubKeyId() {
|
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() {
|
public static RequiredInputParcel createRetryUploadOperation() {
|
||||||
@@ -134,7 +148,7 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
|
|
||||||
public static RequiredInputParcel createSecurityTokenReset() {
|
public static RequiredInputParcel createSecurityTokenReset() {
|
||||||
return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_RESET_CARD,
|
return new RequiredInputParcel(RequiredInputType.SECURITY_TOKEN_RESET_CARD,
|
||||||
null, null, null, null, null);
|
null, null, null, (long[]) null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RequiredInputParcel createRequiredAuthenticationPassphrase(
|
public static RequiredInputParcel createRequiredAuthenticationPassphrase(
|
||||||
@@ -157,18 +171,18 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
|
|
||||||
public static RequiredInputParcel createRequiredSymmetricPassphrase() {
|
public static RequiredInputParcel createRequiredSymmetricPassphrase() {
|
||||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE_SYMMETRIC,
|
return new RequiredInputParcel(RequiredInputType.PASSPHRASE_SYMMETRIC,
|
||||||
null, null, null, null, null);
|
null, null, null, (long[]) null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RequiredInputParcel createRequiredBackupCode() {
|
public static RequiredInputParcel createRequiredBackupCode() {
|
||||||
return new RequiredInputParcel(RequiredInputType.BACKUP_CODE,
|
return new RequiredInputParcel(RequiredInputType.BACKUP_CODE,
|
||||||
null, null, null, null, null);
|
null, null, null, (long[]) null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RequiredInputParcel createRequiredPassphrase(
|
public static RequiredInputParcel createRequiredPassphrase(
|
||||||
RequiredInputParcel req) {
|
RequiredInputParcel req) {
|
||||||
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
|
return new RequiredInputParcel(RequiredInputType.PASSPHRASE,
|
||||||
null, null, req.mSignatureTime, req.mMasterKeyId, req.mSubKeyId);
|
null, null, req.mSignatureTime, req.mMasterKeyIds, req.mSubKeyIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -197,15 +211,15 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
} else {
|
} else {
|
||||||
dest.writeInt(0);
|
dest.writeInt(0);
|
||||||
}
|
}
|
||||||
if (mMasterKeyId != null) {
|
if (mMasterKeyIds != null) {
|
||||||
dest.writeInt(1);
|
dest.writeInt(1);
|
||||||
dest.writeLong(mMasterKeyId);
|
dest.writeLongArray(mMasterKeyIds);
|
||||||
} else {
|
} else {
|
||||||
dest.writeInt(0);
|
dest.writeInt(0);
|
||||||
}
|
}
|
||||||
if (mSubKeyId != null) {
|
if (mSubKeyIds != null) {
|
||||||
dest.writeInt(1);
|
dest.writeInt(1);
|
||||||
dest.writeLong(mSubKeyId);
|
dest.writeLongArray(mSubKeyIds);
|
||||||
} else {
|
} else {
|
||||||
dest.writeInt(0);
|
dest.writeInt(0);
|
||||||
}
|
}
|
||||||
@@ -319,7 +333,7 @@ public class RequiredInputParcel implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void addAll(RequiredInputParcel input) {
|
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!");
|
throw new AssertionError("Master keys must match, this is a programming error!");
|
||||||
}
|
}
|
||||||
if (input.mType != RequiredInputType.SECURITY_TOKEN_MOVE_KEY_TO_CARD) {
|
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<Long> masterKeyIds = new ArrayList<>();
|
||||||
|
private final ArrayList<Long> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import android.content.DialogInterface;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.SystemClock;
|
||||||
import android.support.annotation.NonNull;
|
import android.support.annotation.NonNull;
|
||||||
import android.support.v4.app.DialogFragment;
|
import android.support.v4.app.DialogFragment;
|
||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
@@ -117,7 +118,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
if (pubRing.getSecretKeyType(requiredInput.getSubKeyId()) == SecretKeyType.PASSPHRASE_EMPTY) {
|
if (pubRing.getSecretKeyType(requiredInput.getSubKeyId()) == SecretKeyType.PASSPHRASE_EMPTY) {
|
||||||
// also return passphrase back to activity
|
// also return passphrase back to activity
|
||||||
Intent returnIntent = new Intent();
|
Intent returnIntent = new Intent();
|
||||||
cryptoInputParcel = cryptoInputParcel.withPassphrase(new Passphrase(""));
|
cryptoInputParcel = cryptoInputParcel.withPassphrase(new Passphrase(""), requiredInput.getSubKeyId());
|
||||||
returnIntent.putExtra(RESULT_CRYPTO_INPUT, cryptoInputParcel);
|
returnIntent.putExtra(RESULT_CRYPTO_INPUT, cryptoInputParcel);
|
||||||
setResult(RESULT_OK, returnIntent);
|
setResult(RESULT_OK, returnIntent);
|
||||||
finish();
|
finish();
|
||||||
@@ -231,40 +232,45 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
hint = getString(R.string.label_passphrase);
|
hint = getString(R.string.label_passphrase);
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
long subKeyId = mRequiredInput.getSubKeyId();
|
long[] subKeyIds = mRequiredInput.getSubKeyIds();
|
||||||
|
if (subKeyIds.length > 1) {
|
||||||
KeyRepository helper =
|
message = getString(R.string.passphrase_for_any);
|
||||||
KeyRepository.create(getContext());
|
hint = getString(R.string.label_passphrase);
|
||||||
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 {
|
} else {
|
||||||
userId = getString(R.string.user_id_no_name);
|
long subKeyId = subKeyIds[0];
|
||||||
}
|
|
||||||
|
|
||||||
keyType = cachedPublicKeyRing.getSecretKeyType(subKeyId);
|
KeyRepository helper =
|
||||||
switch (keyType) {
|
KeyRepository.create(getContext());
|
||||||
case PASSPHRASE:
|
CachedPublicKeyRing cachedPublicKeyRing = helper.getCachedPublicKeyRing(
|
||||||
message = getString(R.string.passphrase_for, userId);
|
KeychainContract.KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId));
|
||||||
hint = getString(R.string.label_passphrase);
|
// yes the inner try/catch block is necessary, otherwise the final variable
|
||||||
break;
|
// above can't be statically verified to have been set in all cases because
|
||||||
case DIVERT_TO_CARD:
|
// the catch clause doesn't return.
|
||||||
message = getString(R.string.security_token_pin_for, userId);
|
String mainUserId = cachedPublicKeyRing.getPrimaryUserIdWithFallback();
|
||||||
hint = getString(R.string.label_pin);
|
OpenPgpUtils.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId);
|
||||||
break;
|
if (mainUserIdSplit.name != null) {
|
||||||
// special case: empty passphrase just returns the empty passphrase
|
userId = mainUserIdSplit.name;
|
||||||
case PASSPHRASE_EMPTY:
|
} else {
|
||||||
finishCaching(new Passphrase(""));
|
userId = getString(R.string.user_id_no_name);
|
||||||
default:
|
}
|
||||||
throw new AssertionError("Unhandled SecretKeyType (should not happen)");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
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) {
|
} catch (PgpKeyNotFoundException | KeyRepository.NotFoundException e) {
|
||||||
alert.setTitle(R.string.title_key_not_found);
|
alert.setTitle(R.string.title_key_not_found);
|
||||||
alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId()));
|
alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId()));
|
||||||
@@ -420,7 +426,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
|
backupCodeInput.deleteCharAt(backupCodeInput.length() - 1);
|
||||||
|
|
||||||
Passphrase passphrase = new Passphrase(backupCodeInput.toString());
|
Passphrase passphrase = new Passphrase(backupCodeInput.toString());
|
||||||
finishCaching(passphrase);
|
finishCaching(passphrase, null);
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -438,96 +444,107 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
getString(R.string.passp_cache_notif_pwd), timeToLiveSeconds);
|
getString(R.string.passp_cache_notif_pwd), timeToLiveSeconds);
|
||||||
}
|
}
|
||||||
|
|
||||||
finishCaching(passphrase);
|
finishCaching(passphrase, null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mLayout.setDisplayedChild(1);
|
checkPassphraseAndFinishCaching(positive, passphrase, timeToLiveSeconds);
|
||||||
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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
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.
|
// any indication this isn't needed anymore, don't do it.
|
||||||
if (mIsCancelled || getActivity() == null) {
|
if (mIsCancelled || getActivity() == null) {
|
||||||
return;
|
return;
|
||||||
@@ -535,7 +552,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
|
|||||||
|
|
||||||
CryptoInputParcel inputParcel = getArguments().getParcelable(EXTRA_CRYPTO_INPUT);
|
CryptoInputParcel inputParcel = getArguments().getParcelable(EXTRA_CRYPTO_INPUT);
|
||||||
// noinspection ConstantConditions, we handle the non-null case in PassphraseDialogActivity.onCreate()
|
// noinspection ConstantConditions, we handle the non-null case in PassphraseDialogActivity.onCreate()
|
||||||
inputParcel = inputParcel.withPassphrase(passphrase);
|
inputParcel = inputParcel.withPassphrase(passphrase, subKeyId);
|
||||||
|
|
||||||
((PassphraseDialogActivity) getActivity()).handleResult(inputParcel);
|
((PassphraseDialogActivity) getActivity()).handleResult(inputParcel);
|
||||||
|
|
||||||
|
|||||||
@@ -328,6 +328,7 @@
|
|||||||
<string name="passphrase_for_symmetric_encryption">"Enter password"</string>
|
<string name="passphrase_for_symmetric_encryption">"Enter password"</string>
|
||||||
<string name="passphrase_for_backup">"Enter backup code"</string>
|
<string name="passphrase_for_backup">"Enter backup code"</string>
|
||||||
<string name="passphrase_for">"Enter password for '%s'"</string>
|
<string name="passphrase_for">"Enter password for '%s'"</string>
|
||||||
|
<string name="passphrase_for_any">"Enter password"</string>
|
||||||
<string name="passphrase_keyboard_hint_alpha">"Switch to alphabetic keyboard"</string>
|
<string name="passphrase_keyboard_hint_alpha">"Switch to alphabetic keyboard"</string>
|
||||||
<string name="passphrase_keyboard_hint_numeric">"Switch to numeric keyboard"</string>
|
<string name="passphrase_keyboard_hint_numeric">"Switch to numeric keyboard"</string>
|
||||||
<string name="pin_for">"Enter PIN for '%s'"</string>
|
<string name="pin_for">"Enter PIN for '%s'"</string>
|
||||||
|
|||||||
@@ -175,7 +175,7 @@ public class AuthenticationOperationTest {
|
|||||||
.createAuthenticationParcel(authData.build(), challenge);
|
.createAuthenticationParcel(authData.build(), challenge);
|
||||||
|
|
||||||
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
||||||
inputParcel = inputParcel.withPassphrase(mKeyPhrase);
|
inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId);
|
||||||
|
|
||||||
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
||||||
|
|
||||||
@@ -221,7 +221,7 @@ public class AuthenticationOperationTest {
|
|||||||
.createAuthenticationParcel(authData.build(), challenge);
|
.createAuthenticationParcel(authData.build(), challenge);
|
||||||
|
|
||||||
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
||||||
inputParcel = inputParcel.withPassphrase(mKeyPhrase);
|
inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId);
|
||||||
|
|
||||||
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
||||||
|
|
||||||
@@ -267,7 +267,7 @@ public class AuthenticationOperationTest {
|
|||||||
.createAuthenticationParcel(authData.build(), challenge);
|
.createAuthenticationParcel(authData.build(), challenge);
|
||||||
|
|
||||||
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
||||||
inputParcel = inputParcel.withPassphrase(mKeyPhrase);
|
inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId);
|
||||||
|
|
||||||
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
||||||
|
|
||||||
@@ -315,7 +315,7 @@ public class AuthenticationOperationTest {
|
|||||||
.createAuthenticationParcel(authData.build(), challenge);
|
.createAuthenticationParcel(authData.build(), challenge);
|
||||||
|
|
||||||
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
||||||
inputParcel = inputParcel.withPassphrase(mKeyPhrase);
|
inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId);
|
||||||
|
|
||||||
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
||||||
|
|
||||||
@@ -364,7 +364,7 @@ public class AuthenticationOperationTest {
|
|||||||
.createAuthenticationParcel(authData.build(), challenge);
|
.createAuthenticationParcel(authData.build(), challenge);
|
||||||
|
|
||||||
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
||||||
inputParcel = inputParcel.withPassphrase(mKeyPhrase);
|
inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId);
|
||||||
|
|
||||||
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
||||||
|
|
||||||
@@ -387,7 +387,7 @@ public class AuthenticationOperationTest {
|
|||||||
.createAuthenticationParcel(authData.build(), challenge);
|
.createAuthenticationParcel(authData.build(), challenge);
|
||||||
|
|
||||||
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
CryptoInputParcel inputParcel = CryptoInputParcel.createCryptoInputParcel();
|
||||||
inputParcel = inputParcel.withPassphrase(mKeyPhrase);
|
inputParcel = inputParcel.withPassphrase(mKeyPhrase, authSubKeyId);
|
||||||
|
|
||||||
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
AuthenticationResult result = op.execute(authData.build(), inputParcel, authenticationParcel);
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRingData;
|
|||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel;
|
||||||
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyChange;
|
||||||
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
|
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.service.input.RequiredInputParcel.RequiredInputType;
|
||||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
import org.sufficientlysecure.keychain.support.KeyringTestingHelper;
|
||||||
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
import org.sufficientlysecure.keychain.support.KeyringTestingHelper.RawPacket;
|
||||||
@@ -1064,6 +1065,25 @@ public class PgpEncryptDecryptTest {
|
|||||||
Assert.assertEquals(1024, encryptionKeySecurityProblem.bitStrength);
|
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(
|
private PgpDecryptVerifyOperation operationWithFakePassphraseCache(
|
||||||
final Passphrase passphrase, final Long checkMasterKeyId, final Long checkSubKeyId) {
|
final Passphrase passphrase, final Long checkMasterKeyId, final Long checkSubKeyId) {
|
||||||
|
|
||||||
|
|||||||
BIN
OpenKeychain/src/test/resources/test-ciphertexts/two_keys.asc
Normal file
BIN
OpenKeychain/src/test/resources/test-ciphertexts/two_keys.asc
Normal file
Binary file not shown.
Reference in New Issue
Block a user