aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain
diff options
context:
space:
mode:
authorVincent <valodim@mugenguild.com>2016-02-10 18:39:55 +0100
committerVincent <valodim@mugenguild.com>2016-02-10 18:39:55 +0100
commit2eac4ebb950c9f36fbb8852b4266529065ff6731 (patch)
treeda193d233e2f753e2c31d9c446f4c2cb5ad23248 /OpenKeychain
parenta94713103b6c72136fbf4886bf561616b2a01e4e (diff)
parentda6dfb57a0aa816919cd23752e1707ba6a2e8cae (diff)
downloadopen-keychain-2eac4ebb950c9f36fbb8852b4266529065ff6731.tar.gz
open-keychain-2eac4ebb950c9f36fbb8852b4266529065ff6731.tar.bz2
open-keychain-2eac4ebb950c9f36fbb8852b4266529065ff6731.zip
Merge pull request #1708 from open-keychain/performance
performance improvements
Diffstat (limited to 'OpenKeychain')
-rw-r--r--OpenKeychain/src/main/java/org/bouncycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java (renamed from OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java)0
-rw-r--r--OpenKeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java (renamed from OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java)0
-rw-r--r--OpenKeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java (renamed from OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java)0
-rw-r--r--OpenKeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java116
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java28
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java52
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java14
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java121
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java141
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java19
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java2
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java8
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java80
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java61
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java6
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java2
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java58
-rw-r--r--OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java8
18 files changed, 535 insertions, 181 deletions
diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java b/OpenKeychain/src/main/java/org/bouncycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java
index 72d6036ab..72d6036ab 100644
--- a/OpenKeychain/src/main/java/org/spongycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java
+++ b/OpenKeychain/src/main/java/org/bouncycastle/openpgp/jcajce/JcaSkipMarkerPGPObjectFactory.java
diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java b/OpenKeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java
index 703af94f4..703af94f4 100644
--- a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java
+++ b/OpenKeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/CachingDataDecryptorFactory.java
diff --git a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java b/OpenKeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java
index 584d86891..584d86891 100644
--- a/OpenKeychain/src/main/java/org/spongycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java
+++ b/OpenKeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/NfcSyncPGPContentSignerBuilder.java
diff --git a/OpenKeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java b/OpenKeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java
new file mode 100644
index 000000000..6f1b6f5ef
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/bouncycastle/openpgp/operator/jcajce/SessionKeySecretKeyDecryptorBuilder.java
@@ -0,0 +1,116 @@
+/**
+ * Copyright (c) 2016 Vincent Breitmoser
+ *
+ * Licensed under the Bouncy Castle License (MIT license). See LICENSE file for details.
+ */
+
+package org.bouncycastle.openpgp.operator.jcajce;
+
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.Provider;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.spec.IvParameterSpec;
+import org.bouncycastle.bcpg.S2K;
+import org.bouncycastle.jcajce.util.DefaultJcaJceHelper;
+import org.bouncycastle.jcajce.util.NamedJcaJceHelper;
+import org.bouncycastle.jcajce.util.ProviderJcaJceHelper;
+import org.bouncycastle.openpgp.PGPException;
+import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
+import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider;
+
+
+/** This is a builder for a special PBESecretKeyDecryptor which is parametrized by a
+ * fixed session key, which is used in place of the one obtained from a passphrase.
+ */
+public class SessionKeySecretKeyDecryptorBuilder
+{
+ private OperatorHelper helper = new OperatorHelper(new DefaultJcaJceHelper());
+ private PGPDigestCalculatorProvider calculatorProvider;
+
+ private JcaPGPDigestCalculatorProviderBuilder calculatorProviderBuilder;
+
+ public SessionKeySecretKeyDecryptorBuilder()
+ {
+ this.calculatorProviderBuilder = new JcaPGPDigestCalculatorProviderBuilder();
+ }
+
+ public SessionKeySecretKeyDecryptorBuilder(PGPDigestCalculatorProvider calculatorProvider)
+ {
+ this.calculatorProvider = calculatorProvider;
+ }
+
+ public SessionKeySecretKeyDecryptorBuilder setProvider(Provider provider)
+ {
+ this.helper = new OperatorHelper(new ProviderJcaJceHelper(provider));
+
+ if (calculatorProviderBuilder != null)
+ {
+ calculatorProviderBuilder.setProvider(provider);
+ }
+
+ return this;
+ }
+
+ public SessionKeySecretKeyDecryptorBuilder setProvider(String providerName)
+ {
+ this.helper = new OperatorHelper(new NamedJcaJceHelper(providerName));
+
+ if (calculatorProviderBuilder != null)
+ {
+ calculatorProviderBuilder.setProvider(providerName);
+ }
+
+ return this;
+ }
+
+ public PBESecretKeyDecryptor build(final byte[] sessionKey)
+ throws PGPException
+ {
+ if (calculatorProvider == null)
+ {
+ calculatorProvider = calculatorProviderBuilder.build();
+ }
+
+ return new PBESecretKeyDecryptor(null, calculatorProvider)
+ {
+ @Override
+ public byte[] makeKeyFromPassPhrase(int keyAlgorithm, S2K s2k) throws PGPException {
+ return sessionKey;
+ }
+
+ public byte[] recoverKeyData(int encAlgorithm, byte[] key, byte[] iv, byte[] keyData, int keyOff, int keyLen)
+ throws PGPException
+ {
+ try
+ {
+ Cipher c = helper.createCipher(PGPUtil.getSymmetricCipherName(encAlgorithm) + "/CFB/NoPadding");
+
+ c.init(Cipher.DECRYPT_MODE, PGPUtil.makeSymmetricKey(encAlgorithm, key), new IvParameterSpec(iv));
+
+ return c.doFinal(keyData, keyOff, keyLen);
+ }
+ catch (IllegalBlockSizeException e)
+ {
+ throw new PGPException("illegal block size: " + e.getMessage(), e);
+ }
+ catch (BadPaddingException e)
+ {
+ throw new PGPException("bad padding: " + e.getMessage(), e);
+ }
+ catch (InvalidAlgorithmParameterException e)
+ {
+ throw new PGPException("invalid parameter: " + e.getMessage(), e);
+ }
+ catch (InvalidKeyException e)
+ {
+ throw new PGPException("invalid key: " + e.getMessage(), e);
+ }
+ }
+ };
+ }
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
index 4ad75fde1..79b42ecc4 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/operations/CertifyOperation.java
@@ -38,6 +38,7 @@ import org.sufficientlysecure.keychain.pgp.PgpCertifyOperation.PgpCertifyResult;
import org.sufficientlysecure.keychain.pgp.Progressable;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
import org.sufficientlysecure.keychain.service.CertifyActionsParcel;
@@ -75,24 +76,22 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
// Retrieve and unlock secret key
CanonicalizedSecretKey certificationKey;
+ long masterKeyId = parcel.mMasterKeyId;
try {
log.add(LogType.MSG_CRT_MASTER_FETCH, 1);
- CanonicalizedSecretKeyRing secretKeyRing =
- mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId);
- log.add(LogType.MSG_CRT_UNLOCK, 1);
- certificationKey = secretKeyRing.getSecretKey();
+ CachedPublicKeyRing cachedPublicKeyRing = mProviderHelper.getCachedPublicKeyRing(masterKeyId);
Passphrase passphrase;
- switch (certificationKey.getSecretKeyType()) {
+ switch (cachedPublicKeyRing.getSecretKeyType(masterKeyId)) {
case PIN:
case PATTERN:
case PASSPHRASE:
passphrase = cryptoInput.getPassphrase();
if (passphrase == null) {
try {
- passphrase = getCachedPassphrase(certificationKey.getKeyId(), certificationKey.getKeyId());
+ passphrase = getCachedPassphrase(masterKeyId, masterKeyId);
} catch (PassphraseCacheInterface.NoSecretKeyException ignored) {
// treat as a cache miss for error handling purposes
}
@@ -100,10 +99,7 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
if (passphrase == null) {
return new CertifyResult(log,
- RequiredInputParcel.createRequiredSignPassphrase(
- certificationKey.getKeyId(),
- certificationKey.getKeyId(),
- null),
+ RequiredInputParcel.createRequiredSignPassphrase(masterKeyId, masterKeyId, null),
cryptoInput
);
}
@@ -123,7 +119,14 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
}
- if (!certificationKey.unlock(passphrase)) {
+ // Get actual secret key
+ CanonicalizedSecretKeyRing secretKeyRing =
+ mProviderHelper.getCanonicalizedSecretKeyRing(parcel.mMasterKeyId);
+ certificationKey = secretKeyRing.getSecretKey();
+
+ log.add(LogType.MSG_CRT_UNLOCK, 1);
+ boolean unlockSuccessful = certificationKey.unlock(passphrase);
+ if (!unlockSuccessful) {
log.add(LogType.MSG_CRT_ERROR_UNLOCK, 2);
return new CertifyResult(CertifyResult.RESULT_ERROR, log);
}
@@ -142,8 +145,7 @@ public class CertifyOperation extends BaseOperation<CertifyActionsParcel> {
int certifyOk = 0, certifyError = 0, uploadOk = 0, uploadError = 0;
NfcSignOperationsBuilder allRequiredInput = new NfcSignOperationsBuilder(
- cryptoInput.getSignatureTime(), certificationKey.getKeyId(),
- certificationKey.getKeyId());
+ cryptoInput.getSignatureTime(), masterKeyId, masterKeyId);
// Work through all requested certifications
for (CertifyAction action : parcel.mCertifyActions) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
index ee05453e0..a02ff6685 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
@@ -18,7 +18,16 @@
package org.sufficientlysecure.keychain.pgp;
+
+import java.nio.ByteBuffer;
+import java.security.PrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+
import org.bouncycastle.bcpg.S2K;
+import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPSecretKey;
@@ -33,20 +42,15 @@ import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.bouncycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
+import org.bouncycastle.openpgp.operator.jcajce.SessionKeySecretKeyDecryptorBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
-import java.nio.ByteBuffer;
-import java.security.PrivateKey;
-import java.security.interfaces.RSAPrivateCrtKey;
-import java.util.Date;
-import java.util.HashMap;
-import java.util.Map;
-
/**
* Wrapper for a PGPSecretKey.
@@ -118,7 +122,14 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
}
- public SecretKeyType getSecretKeyType() {
+ /** This method returns the SecretKeyType for this secret key, testing for an empty
+ * passphrase in the process.
+ *
+ * This method can potentially take a LONG time (i.e. seconds), so it should only
+ * ever be called by {@link ProviderHelper} for the purpose of caching its output
+ * in the database.
+ */
+ public SecretKeyType getSecretKeyTypeSuperExpensive() {
S2K s2k = mSecretKey.getS2K();
if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K) {
// divert to card is special
@@ -145,13 +156,12 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
// Otherwise, it's just a regular ol' passphrase
return SecretKeyType.PASSPHRASE;
}
-
}
/**
* Returns true on right passphrase
*/
- public boolean unlock(Passphrase passphrase) throws PgpGeneralException {
+ public boolean unlock(final Passphrase passphrase) throws PgpGeneralException {
// handle keys on OpenPGP cards like they were unlocked
S2K s2k = mSecretKey.getS2K();
if (s2k != null
@@ -163,8 +173,26 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
// try to extract keys using the passphrase
try {
- PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
- Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
+
+ int keyEncryptionAlgorithm = mSecretKey.getKeyEncryptionAlgorithm();
+ if (keyEncryptionAlgorithm == SymmetricKeyAlgorithmTags.NULL) {
+ mPrivateKey = mSecretKey.extractPrivateKey(null);
+ mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED;
+ return true;
+ }
+
+ byte[] sessionKey;
+ sessionKey = passphrase.getCachedSessionKeyForParameters(keyEncryptionAlgorithm, s2k);
+ if (sessionKey == null) {
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
+ // this operation is EXPENSIVE, so we cache its result in the passed Passphrase object!
+ sessionKey = keyDecryptor.makeKeyFromPassPhrase(keyEncryptionAlgorithm, s2k);
+ passphrase.addCachedSessionKeyForParameters(keyEncryptionAlgorithm, s2k, sessionKey);
+ }
+
+ PBESecretKeyDecryptor keyDecryptor = new SessionKeySecretKeyDecryptorBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(sessionKey);
mPrivateKey = mSecretKey.extractPrivateKey(keyDecryptor);
mPrivateKeyState = PRIVATE_KEY_STATE_UNLOCKED;
} catch (PGPException e) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java
index 63ffc3156..6ed225a83 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKeyRing.java
@@ -70,20 +70,6 @@ public class CanonicalizedSecretKeyRing extends CanonicalizedKeyRing {
return new CanonicalizedSecretKey(this, mRing.getSecretKey(id));
}
- /** Returns the key id which should be used for signing.
- *
- * This method returns keys which are actually available (ie. secret available, and not stripped,
- * revoked, or expired), hence only works on keyrings where a secret key is available!
- */
- public long getSecretSignId() throws PgpGeneralException {
- for(CanonicalizedSecretKey key : secretKeyIterator()) {
- if (key.canSign() && key.isValid() && key.getSecretKeyType().isUsable()) {
- return key.getKeyId();
- }
- }
- throw new PgpGeneralException("no valid signing key available");
- }
-
public IterableIterator<CanonicalizedSecretKey> secretKeyIterator() {
final Iterator<PGPSecretKey> it = mRing.getSecretKeys();
return new IterableIterator<>(new Iterator<CanonicalizedSecretKey>() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java
new file mode 100644
index 000000000..99b9cdab2
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/ComparableS2K.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 Vincent Breitmoser <look@my.amazin.horse>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+package org.sufficientlysecure.keychain.pgp;
+
+
+import java.util.Arrays;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import org.bouncycastle.bcpg.S2K;
+
+
+/** This is an immutable and parcelable class which stores the full s2k parametrization
+ * of an encrypted secret key, i.e. all fields of the {@link S2K} class (type, hash algo,
+ * iteration count, iv) plus the encryptionAlgorithm. This class is intended to be used
+ * as key in a HashMap for session key caching purposes, and overrides the
+ * {@link #hashCode} and {@link #equals} methods in a suitable way.
+ *
+ * Note that although it is a rather unlikely scenario that secret keys of the same key
+ * are encrypted with different ciphers, the encryption algorithm still determines the
+ * length of the specific session key and thus needs to be considered for purposes of
+ * session key caching.
+ *
+ * @see org.bouncycastle.bcpg.S2K
+ */
+public class ComparableS2K implements Parcelable {
+
+ private final int encryptionAlgorithm;
+ private final int s2kType;
+ private final int s2kHashAlgo;
+ private final long s2kItCount;
+ private final byte[] s2kIV;
+
+ Integer cachedHashCode;
+
+ public ComparableS2K(int encryptionAlgorithm, S2K s2k) {
+ this.encryptionAlgorithm = encryptionAlgorithm;
+ this.s2kType = s2k.getType();
+ this.s2kHashAlgo = s2k.getHashAlgorithm();
+ this.s2kItCount = s2k.getIterationCount();
+ this.s2kIV = s2k.getIV();
+ }
+
+ protected ComparableS2K(Parcel in) {
+ encryptionAlgorithm = in.readInt();
+ s2kType = in.readInt();
+ s2kHashAlgo = in.readInt();
+ s2kItCount = in.readLong();
+ s2kIV = in.createByteArray();
+ }
+
+ @Override
+ public int hashCode() {
+ if (cachedHashCode == null) {
+ cachedHashCode = encryptionAlgorithm;
+ cachedHashCode = 31 * cachedHashCode + s2kType;
+ cachedHashCode = 31 * cachedHashCode + s2kHashAlgo;
+ cachedHashCode = 31 * cachedHashCode + (int) (s2kItCount ^ (s2kItCount >>> 32));
+ cachedHashCode = 31 * cachedHashCode + Arrays.hashCode(s2kIV);
+ }
+
+ return cachedHashCode;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ boolean isComparableS2K = o instanceof ComparableS2K;
+ if (!isComparableS2K) {
+ return false;
+ }
+ ComparableS2K other = (ComparableS2K) o;
+ return encryptionAlgorithm == other.encryptionAlgorithm
+ && s2kType == other.s2kType
+ && s2kHashAlgo == other.s2kHashAlgo
+ && s2kItCount == other.s2kItCount
+ && Arrays.equals(s2kIV, other.s2kIV);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeInt(encryptionAlgorithm);
+ dest.writeInt(s2kType);
+ dest.writeInt(s2kHashAlgo);
+ dest.writeLong(s2kItCount);
+ dest.writeByteArray(s2kIV);
+ }
+
+ public static final Creator<ComparableS2K> CREATOR = new Creator<ComparableS2K>() {
+ @Override
+ public ComparableS2K createFromParcel(Parcel in) {
+ return new ComparableS2K(in);
+ }
+
+ @Override
+ public ComparableS2K[] newArray(int size) {
+ return new ComparableS2K[size];
+ }
+ };
+
+}
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 1ebc75b7d..b6f102593 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
@@ -1,6 +1,7 @@
/*
* Copyright (C) 2012-2014 Dominik Schürmann <dominik@dominikschuermann.de>
* Copyright (C) 2010-2014 Thialfihar <thi@thialfihar.org>
+ * Copyright (C) 2015-2016 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -64,6 +65,8 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.LogTyp
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -539,7 +542,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
PGPPublicKeyEncryptedData encryptedDataAsymmetric = null;
PGPPBEEncryptedData encryptedDataSymmetric = null;
- CanonicalizedSecretKey secretEncryptionKey = null;
+ CanonicalizedSecretKey decryptionKey = null;
Passphrase passphrase = null;
@@ -560,83 +563,87 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
log.add(LogType.MSG_DC_ASYM, indent,
KeyFormattingUtils.convertKeyIdToHex(subKeyId));
- CanonicalizedSecretKeyRing secretKeyRing;
+ CachedPublicKeyRing cachedPublicKeyRing;
try {
// get actual keyring object based on master key id
- secretKeyRing = mProviderHelper.getCanonicalizedSecretKeyRing(
+ cachedPublicKeyRing = mProviderHelper.getCachedPublicKeyRing(
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(subKeyId)
);
- } catch (ProviderHelper.NotFoundException e) {
- // continue with the next packet in the while loop
- log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
- continue;
- }
+ long masterKeyId = cachedPublicKeyRing.getMasterKeyId();
+
+ // allow only specific keys for decryption?
+ if (input.getAllowedKeyIds() != null) {
+ Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
+ Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
+ Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
+
+ if (!input.getAllowedKeyIds().contains(masterKeyId)) {
+ // this key is in our db, but NOT allowed!
+ // continue with the next packet in the while loop
+ result.skippedDisallowedKey = true;
+ log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1);
+ continue;
+ }
+ }
- // allow only specific keys for decryption?
- if (input.getAllowedKeyIds() != null) {
- long masterKeyId = secretKeyRing.getMasterKeyId();
- Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
- Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
- Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
-
- if (!input.getAllowedKeyIds().contains(masterKeyId)) {
- // this key is in our db, but NOT allowed!
- // continue with the next packet in the while loop
- result.skippedDisallowedKey = true;
- log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1);
+ SecretKeyType secretKeyType = cachedPublicKeyRing.getSecretKeyType(subKeyId);
+ if (!secretKeyType.isUsable()) {
+ decryptionKey = null;
+ log.add(LogType.MSG_DC_ASKIP_UNAVAILABLE, indent + 1);
continue;
}
- }
-
- // get subkey which has been used for this encryption packet
- secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
- if (!secretEncryptionKey.canEncrypt()) {
- secretEncryptionKey = null;
- log.add(LogType.MSG_DC_ASKIP_BAD_FLAGS, indent + 1);
- continue;
- }
-
- if (!secretEncryptionKey.getSecretKeyType().isUsable()) {
- secretEncryptionKey = null;
- log.add(LogType.MSG_DC_ASKIP_UNAVAILABLE, indent + 1);
- continue;
- }
+ // get actual subkey which has been used for this encryption packet
+ CanonicalizedSecretKeyRing canonicalizedSecretKeyRing = mProviderHelper
+ .getCanonicalizedSecretKeyRing(masterKeyId);
+ CanonicalizedSecretKey candidateDecryptionKey = canonicalizedSecretKeyRing.getSecretKey(subKeyId);
- /* secret key exists in database and is allowed! */
- asymmetricPacketFound = true;
+ if (!candidateDecryptionKey.canEncrypt()) {
+ log.add(LogType.MSG_DC_ASKIP_BAD_FLAGS, indent + 1);
+ continue;
+ }
- encryptedDataAsymmetric = encData;
+ if (secretKeyType == SecretKeyType.DIVERT_TO_CARD) {
+ passphrase = null;
+ } else if (secretKeyType == SecretKeyType.PASSPHRASE_EMPTY) {
+ passphrase = new Passphrase("");
+ } else if (cryptoInput.hasPassphrase()) {
+ passphrase = cryptoInput.getPassphrase();
+ } else {
+ // if no passphrase was explicitly set try to get it from the cache service
+ try {
+ // returns "" if key has no passphrase
+ passphrase = getCachedPassphrase(subKeyId);
+ log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
+ } catch (PassphraseCacheInterface.NoSecretKeyException e) {
+ log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
+ return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
+ }
- if (secretEncryptionKey.getSecretKeyType() == SecretKeyType.DIVERT_TO_CARD) {
- passphrase = null;
- } else if (cryptoInput.hasPassphrase()) {
- passphrase = cryptoInput.getPassphrase();
- } else {
- // if no passphrase was explicitly set try to get it from the cache service
- try {
- // returns "" if key has no passphrase
- passphrase = getCachedPassphrase(subKeyId);
- log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
- } catch (PassphraseCacheInterface.NoSecretKeyException e) {
- log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
- return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
+ // if passphrase was not cached, return here indicating that a passphrase is missing!
+ if (passphrase == null) {
+ log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
+ return result.with(new DecryptVerifyResult(log,
+ RequiredInputParcel.createRequiredDecryptPassphrase(masterKeyId, subKeyId),
+ cryptoInput));
+ }
}
- // if passphrase was not cached, return here indicating that a passphrase is missing!
- if (passphrase == null) {
- log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
- return result.with(new DecryptVerifyResult(log,
- RequiredInputParcel.createRequiredDecryptPassphrase(
- secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()),
- cryptoInput));
+ // check for insecure encryption key
+ if ( ! PgpSecurityConstants.isSecureKey(candidateDecryptionKey)) {
+ log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
+ result.insecureEncryptionKey = true;
}
- }
- // check for insecure encryption key
- if ( ! PgpSecurityConstants.isSecureKey(secretEncryptionKey)) {
- log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
- result.insecureEncryptionKey = true;
+ // we're good, write down the data for later
+ asymmetricPacketFound = true;
+ encryptedDataAsymmetric = encData;
+ decryptionKey = candidateDecryptionKey;
+
+ } catch (PgpKeyNotFoundException | ProviderHelper.NotFoundException e) {
+ // continue with the next packet in the while loop
+ log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
+ continue;
}
// break out of while, only decrypt the first packet where we have a key
@@ -735,7 +742,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
try {
log.add(LogType.MSG_DC_UNLOCKING, indent + 1);
- if (!secretEncryptionKey.unlock(passphrase)) {
+ if (!decryptionKey.unlock(passphrase)) {
log.add(LogType.MSG_DC_ERROR_BAD_PASSPHRASE, indent + 1);
return result.with(new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log));
}
@@ -748,7 +755,7 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
CachingDataDecryptorFactory decryptorFactory
- = secretEncryptionKey.getCachingDecryptorFactory(cryptoInput);
+ = decryptionKey.getCachingDecryptorFactory(cryptoInput);
// special case: if the decryptor does not have a session key cached for this encrypted
// data, and can't actually decrypt on its own, return a pending intent
@@ -757,8 +764,8 @@ public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInp
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
return result.with(new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
- secretEncryptionKey.getRing().getMasterKeyId(),
- secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
+ decryptionKey.getRing().getMasterKeyId(),
+ decryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
), cryptoInput));
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
index 4c3471b21..f1d4d1272 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
@@ -166,12 +166,13 @@ public class PgpSignEncryptOperation extends BaseOperation {
updateProgress(R.string.progress_extracting_signature_key, 0, 100);
try {
- // fetch the indicated master key id (the one whose name we sign in)
- CanonicalizedSecretKeyRing signingKeyRing =
- mProviderHelper.getCanonicalizedSecretKeyRing(input.getSignatureMasterKeyId());
-
- // fetch the specific subkey to sign with, or just use the master key if none specified
- signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());
+ long signingMasterKeyId = input.getSignatureMasterKeyId();
+ long signingSubKeyId = input.getSignatureSubKeyId();
+ {
+ CanonicalizedSecretKeyRing signingKeyRing =
+ mProviderHelper.getCanonicalizedSecretKeyRing(signingMasterKeyId);
+ signingKey = signingKeyRing.getSecretKey(input.getSignatureSubKeyId());
+ }
// Make sure we are allowed to sign here!
if (!signingKey.canSign()) {
@@ -179,7 +180,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
}
- switch (signingKey.getSecretKeyType()) {
+ switch (mProviderHelper.getCachedPublicKeyRing(signingMasterKeyId).getSecretKeyType(signingSubKeyId)) {
case DIVERT_TO_CARD:
case PASSPHRASE_EMPTY: {
if (!signingKey.unlock(new Passphrase())) {
@@ -196,14 +197,14 @@ public class PgpSignEncryptOperation extends BaseOperation {
Passphrase localPassphrase = cryptoInput.getPassphrase();
if (localPassphrase == null) {
try {
- localPassphrase = getCachedPassphrase(signingKeyRing.getMasterKeyId(), signingKey.getKeyId());
+ localPassphrase = getCachedPassphrase(signingMasterKeyId, signingKey.getKeyId());
} catch (PassphraseCacheInterface.NoSecretKeyException ignored) {
}
}
if (localPassphrase == null) {
log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase(
- signingKeyRing.getMasterKeyId(), signingKey.getKeyId(),
+ signingMasterKeyId, signingKey.getKeyId(),
cryptoInput.getSignatureTime()), cryptoInput);
}
if (!signingKey.unlock(localPassphrase)) {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java
index 7c9ef719e..604a5a027 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/CachedPublicKeyRing.java
@@ -74,7 +74,7 @@ public class CachedPublicKeyRing extends KeyRing {
public long extractOrGetMasterKeyId() throws PgpKeyNotFoundException {
// try extracting from the uri first
String firstSegment = mUri.getPathSegments().get(1);
- if (!firstSegment.equals("find")) try {
+ if (!"find".equals(firstSegment)) try {
return Long.parseLong(firstSegment);
} catch (NumberFormatException e) {
// didn't work? oh well.
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
index 1cd073717..83e2baf9a 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/provider/ProviderHelper.java
@@ -34,6 +34,7 @@ import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.results.ImportKeyResult;
import org.sufficientlysecure.keychain.pgp.WrappedUserAttribute;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.UserPackets;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.ParcelableFileCache.IteratorWithSize;
@@ -254,8 +255,9 @@ public class ProviderHelper {
KeyRings.MASTER_KEY_ID, FIELD_TYPE_INTEGER);
}
- public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) {
- return new CachedPublicKeyRing(this, queryUri);
+ public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException {
+ long masterKeyId = new CachedPublicKeyRing(this, queryUri).extractOrGetMasterKeyId();
+ return getCachedPublicKeyRing(masterKeyId);
}
public CachedPublicKeyRing getCachedPublicKeyRing(long id) {
@@ -828,7 +830,7 @@ public class ProviderHelper {
mIndent += 1;
for (CanonicalizedSecretKey sub : keyRing.secretKeyIterator()) {
long id = sub.getKeyId();
- SecretKeyType mode = sub.getSecretKeyType();
+ SecretKeyType mode = sub.getSecretKeyTypeSuperExpensive();
values.put(Keys.HAS_SECRET, mode.getNum());
int upd = mContentResolver.update(uri, values, Keys.KEY_ID + " = ?",
new String[]{Long.toString(id)});
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 a973a67e0..88616117f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/ui/PassphraseDialogActivity.java
@@ -54,6 +54,7 @@ import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.KeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import org.sufficientlysecure.keychain.provider.CachedPublicKeyRing;
import org.sufficientlysecure.keychain.provider.KeychainContract;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.provider.ProviderHelper.NotFoundException;
@@ -112,12 +113,10 @@ public class PassphraseDialogActivity extends FragmentActivity {
// handle empty passphrases by directly returning an empty crypto input parcel
try {
- CanonicalizedSecretKeyRing pubRing =
- new ProviderHelper(this).getCanonicalizedSecretKeyRing(
- requiredInput.getMasterKeyId());
+ CachedPublicKeyRing pubRing =
+ new ProviderHelper(this).getCachedPublicKeyRing(requiredInput.getMasterKeyId());
// use empty passphrase for empty passphrase
- if (pubRing.getSecretKey(requiredInput.getSubKeyId()).getSecretKeyType() ==
- SecretKeyType.PASSPHRASE_EMPTY) {
+ if (pubRing.getSecretKeyType(requiredInput.getSubKeyId()) == SecretKeyType.PASSPHRASE_EMPTY) {
// also return passphrase back to activity
Intent returnIntent = new Intent();
cryptoInputParcel.mPassphrase = new Passphrase("");
@@ -161,7 +160,6 @@ public class PassphraseDialogActivity extends FragmentActivity {
private TextView mPassphraseText;
private EditText[] mBackupCodeEditText;
- private CanonicalizedSecretKeyRing mSecretRing = null;
private boolean mIsCancelled = false;
private RequiredInputParcel mRequiredInput;
@@ -233,24 +231,20 @@ public class PassphraseDialogActivity extends FragmentActivity {
long subKeyId = mRequiredInput.getSubKeyId();
ProviderHelper helper = new ProviderHelper(activity);
- mSecretRing = helper.getCanonicalizedSecretKeyRing(
+ 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.
- try {
- String mainUserId = mSecretRing.getPrimaryUserIdWithFallback();
- KeyRing.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId);
- if (mainUserIdSplit.name != null) {
- userId = mainUserIdSplit.name;
- } else {
- userId = getString(R.string.user_id_no_name);
- }
- } catch (PgpKeyNotFoundException e) {
- userId = null;
+ String mainUserId = cachedPublicKeyRing.getPrimaryUserIdWithFallback();
+ KeyRing.UserId mainUserIdSplit = KeyRing.splitUserId(mainUserId);
+ if (mainUserIdSplit.name != null) {
+ userId = mainUserIdSplit.name;
+ } else {
+ userId = getString(R.string.user_id_no_name);
}
- keyType = mSecretRing.getSecretKey(subKeyId).getSecretKeyType();
+ keyType = cachedPublicKeyRing.getSecretKeyType(subKeyId);
switch (keyType) {
case PASSPHRASE:
message = getString(R.string.passphrase_for, userId);
@@ -271,7 +265,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
throw new AssertionError("Unhandled SecretKeyType (should not happen)");
}
- } catch (ProviderHelper.NotFoundException e) {
+ } catch (PgpKeyNotFoundException | ProviderHelper.NotFoundException e) {
alert.setTitle(R.string.title_key_not_found);
alert.setMessage(getString(R.string.key_not_found, mRequiredInput.getSubKeyId()));
alert.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@@ -389,7 +383,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
final int timeToLiveSeconds = mTimeToLiveSpinner.getSelectedTimeToLive();
// Early breakout if we are dealing with a symmetric key
- if (mSecretRing == null) {
+ if (mRequiredInput.mType == RequiredInputType.PASSPHRASE_SYMMETRIC) {
if (!mRequiredInput.mSkipCaching) {
PassphraseCacheService.addCachedPassphrase(getActivity(),
Constants.key.symmetric, Constants.key.symmetric, passphrase,
@@ -403,32 +397,48 @@ public class PassphraseDialogActivity extends FragmentActivity {
mLayout.setDisplayedChild(1);
positive.setEnabled(false);
- new AsyncTask<Void, Void, Boolean>() {
+ new AsyncTask<Void, Void, CanonicalizedSecretKey>() {
+
@Override
- protected Boolean doInBackground(Void... params) {
+ protected CanonicalizedSecretKey doInBackground(Void... params) {
try {
- // wait some 100ms here, give the user time to appreciate the progress bar
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- // never mind
+ long timeBeforeOperation = System.currentTimeMillis();
+
+ Long subKeyId = mRequiredInput.getSubKeyId();
+ CanonicalizedSecretKeyRing secretKeyRing =
+ new ProviderHelper(getActivity()).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
+ }
}
- // make sure this unlocks
- return mSecretRing.getSecretKey(mRequiredInput.getSubKeyId()).unlock(passphrase);
- } catch (PgpGeneralException e) {
+
+ 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 false;
+ return null;
}
}
/** Handle a good or bad passphrase. This happens in the UI thread! */
@Override
- protected void onPostExecute(Boolean result) {
+ protected void onPostExecute(CanonicalizedSecretKey result) {
super.onPostExecute(result);
// if we were cancelled in the meantime, the result isn't relevant anymore
@@ -437,7 +447,7 @@ public class PassphraseDialogActivity extends FragmentActivity {
}
// if the passphrase was wrong, reset and re-enable the dialogue
- if (!result) {
+ if (result == null) {
mPassphraseEditText.setText("");
mPassphraseEditText.setError(getString(R.string.wrong_passphrase));
mLayout.setDisplayedChild(0);
@@ -455,8 +465,8 @@ public class PassphraseDialogActivity extends FragmentActivity {
try {
PassphraseCacheService.addCachedPassphrase(getActivity(),
- mSecretRing.getMasterKeyId(), mRequiredInput.getSubKeyId(), passphrase,
- mSecretRing.getPrimaryUserIdWithFallback(), timeToLiveSeconds);
+ mRequiredInput.getMasterKeyId(), mRequiredInput.getSubKeyId(), passphrase,
+ result.getRing().getPrimaryUserIdWithFallback(), timeToLiveSeconds);
} catch (PgpKeyNotFoundException e) {
Log.e(Constants.TAG, "adding of a passphrase failed", e);
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java
index fe42c7a2c..b49b30d1d 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/util/Passphrase.java
@@ -1,5 +1,6 @@
/*
* Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2016 Vincent Breitmoser <look@my.amazin.horse>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -22,13 +23,23 @@ import android.os.Parcelable;
import android.text.Editable;
import android.widget.EditText;
+import org.bouncycastle.bcpg.S2K;
import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.pgp.ComparableS2K;
import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map.Entry;
+
/**
- * Passwords should not be stored as Strings in memory.
- * This class wraps a char[] that can be erased after it is no longer used.
+ * This class wraps a char[] array that is overwritten before the object is freed, to avoid
+ * keeping passphrases in memory as much as possible.
+ *
+ * In addition to the raw passphrases, this class can cache the session key output of an applied
+ * S2K algorithm for a given set of S2K parameters. Since S2K operations are very expensive, this
+ * mechanism should be used to cache session keys whenever possible.
+ *
* See also:
* <p/>
* http://docs.oracle.com/javase/6/docs/technotes/guides/security/crypto/CryptoSpec.html#PBEEx
@@ -38,6 +49,7 @@ import java.util.Arrays;
*/
public class Passphrase implements Parcelable {
private char[] mPassphrase;
+ private HashMap<ComparableS2K, byte[]> mCachedSessionKeys;
/**
* According to http://stackoverflow.com/a/15844273 EditText is not using String internally
@@ -87,8 +99,24 @@ public class Passphrase implements Parcelable {
return mPassphrase.length;
}
- public char charAt(int index) {
- return mPassphrase[index];
+ /** @return A cached session key, or null if none exists for the given parameters. */
+ public byte[] getCachedSessionKeyForParameters(int keyEncryptionAlgorithm, S2K s2k) {
+ if (mCachedSessionKeys == null) {
+ return null;
+ }
+ return mCachedSessionKeys.get(new ComparableS2K(keyEncryptionAlgorithm, s2k));
+ }
+
+ /** Adds a session key for a set of s2k parameters to this Passphrase object's
+ * cache. The caller should make sure that the supplied session key is the result
+ * of an S2K operation applied to exactly the passphrase stored by this object
+ * with the given parameters.
+ */
+ public void addCachedSessionKeyForParameters(int keyEncryptionAlgorithm, S2K s2k, byte[] sessionKey) {
+ if (mCachedSessionKeys == null) {
+ mCachedSessionKeys = new HashMap<>();
+ }
+ mCachedSessionKeys.put(new ComparableS2K(keyEncryptionAlgorithm, s2k), sessionKey);
}
/**
@@ -98,6 +126,12 @@ public class Passphrase implements Parcelable {
if (mPassphrase != null) {
Arrays.fill(mPassphrase, ' ');
}
+ if (mCachedSessionKeys == null) {
+ return;
+ }
+ for (byte[] cachedSessionKey : mCachedSessionKeys.values()) {
+ Arrays.fill(cachedSessionKey, (byte) 0);
+ }
}
@Override
@@ -144,10 +178,29 @@ public class Passphrase implements Parcelable {
private Passphrase(Parcel source) {
mPassphrase = source.createCharArray();
+ int size = source.readInt();
+ if (size == 0) {
+ return;
+ }
+ mCachedSessionKeys = new HashMap<>(size);
+ for (int i = 0; i < size; i++) {
+ ComparableS2K cachedS2K = source.readParcelable(getClass().getClassLoader());
+ byte[] cachedSessionKey = source.createByteArray();
+ mCachedSessionKeys.put(cachedS2K, cachedSessionKey);
+ }
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeCharArray(mPassphrase);
+ if (mCachedSessionKeys == null || mCachedSessionKeys.isEmpty()) {
+ dest.writeInt(0);
+ return;
+ }
+ dest.writeInt(mCachedSessionKeys.size());
+ for (Entry<ComparableS2K,byte[]> entry : mCachedSessionKeys.entrySet()) {
+ dest.writeParcelable(entry.getKey(), 0);
+ dest.writeByteArray(entry.getValue());
+ }
}
public static final Creator<Passphrase> CREATOR = new Creator<Passphrase>() {
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java
index 33857a96e..442e252af 100644
--- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/operations/PromoteKeyOperationTest.java
@@ -145,7 +145,7 @@ public class PromoteKeyOperationTest {
for (CanonicalizedSecretKey key : ring.secretKeyIterator()) {
Assert.assertEquals("all subkeys must be divert-to-card",
- SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
+ SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive());
Assert.assertArrayEquals("all subkeys must have correct iv",
aid, key.getIv());
}
@@ -176,12 +176,12 @@ public class PromoteKeyOperationTest {
for (CanonicalizedSecretKey key : ring.secretKeyIterator()) {
if (key.getKeyId() == keyId) {
Assert.assertEquals("subkey must be divert-to-card",
- SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
+ SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive());
Assert.assertArrayEquals("subkey must have correct iv",
aid, key.getIv());
} else {
Assert.assertEquals("some subkeys must be gnu dummy",
- SecretKeyType.GNU_DUMMY, key.getSecretKeyType());
+ SecretKeyType.GNU_DUMMY, key.getSecretKeyTypeSuperExpensive());
}
}
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java
index 213a79b11..19fb0345b 100644
--- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperationTest.java
@@ -1254,7 +1254,7 @@ public class PgpKeyOperationTest {
modified.getEncoded(), false, 0);
Assert.assertEquals("secret key type should be 'pin' after this",
SecretKeyType.PIN,
- secretRing.getSecretKey().getSecretKeyType());
+ secretRing.getSecretKey().getSecretKeyTypeSuperExpensive());
// need to sleep for a sec, so the timestamp changes for notation data
Thread.sleep(1000);
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java
index 503ff9a7c..3393f61b3 100644
--- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/InteropTest.java
@@ -30,6 +30,7 @@ import org.openintents.openpgp.OpenPgpSignatureResult;
import org.robolectric.RobolectricGradleTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
+import org.robolectric.shadows.ShadowLog;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.sufficientlysecure.keychain.WorkaroundBuildConfig;
import org.sufficientlysecure.keychain.operations.results.DecryptVerifyResult;
@@ -37,10 +38,12 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.pgp.CanonicalizedKeyRing;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKey;
import org.sufficientlysecure.keychain.pgp.CanonicalizedPublicKeyRing;
+import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyInputParcel;
import org.sufficientlysecure.keychain.pgp.PgpDecryptVerifyOperation;
import org.sufficientlysecure.keychain.pgp.UncachedKeyRing;
+import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
@@ -64,6 +67,7 @@ public class InteropTest {
@BeforeClass
public static void setUpOnce() throws Exception {
Security.insertProviderAt(new BouncyCastleProvider(), 1);
+ ShadowLog.stream = System.out;
}
@Test
@@ -243,21 +247,45 @@ public class InteropTest {
KeyRings.buildUnifiedKeyRingsFindBySubkeyUri(verify.getMasterKeyId()) : null;
ProviderHelper helper = new ProviderHelper(RuntimeEnvironment.application) {
- @Override
- public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri q)
- throws NotFoundException {
- Assert.assertEquals(msg + ": query should be for verification key",
- q, verifyUri);
- return verify;
- }
- @Override
- public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri q)
- throws NotFoundException {
- Assert.assertEquals(msg + ": query should be for the decryption key",
- q, decryptUri);
- return decrypt;
- }
- };
+
+ @Override
+ public CachedPublicKeyRing getCachedPublicKeyRing(Uri queryUri) throws PgpKeyNotFoundException {
+ Assert.assertEquals(msg + ": query should be for the decryption key", queryUri, decryptUri);
+ return new CachedPublicKeyRing(this, queryUri) {
+ @Override
+ public long getMasterKeyId() throws PgpKeyNotFoundException {
+ return decrypt.getMasterKeyId();
+ }
+
+ @Override
+ public SecretKeyType getSecretKeyType(long keyId) throws NotFoundException {
+ return decrypt.getSecretKey(keyId).getSecretKeyTypeSuperExpensive();
+ }
+ };
+ }
+
+ @Override
+ public CanonicalizedPublicKeyRing getCanonicalizedPublicKeyRing(Uri q)
+ throws NotFoundException {
+ Assert.assertEquals(msg + ": query should be for verification key", q, verifyUri);
+ return verify;
+ }
+
+ @Override
+ public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(Uri q)
+ throws NotFoundException {
+ Assert.assertEquals(msg + ": query should be for the decryption key", q, decryptUri);
+ return decrypt;
+ }
+
+ @Override
+ public CanonicalizedSecretKeyRing getCanonicalizedSecretKeyRing(long masterKeyId)
+ throws NotFoundException {
+ Assert.assertEquals(msg + ": query should be for the decryption key",
+ masterKeyId, decrypt.getMasterKeyId());
+ return decrypt;
+ }
+ };
return new PgpDecryptVerifyOperation(RuntimeEnvironment.application, helper, null) {
@Override
diff --git a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java
index 0bbbbc58f..8190fa03e 100644
--- a/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java
+++ b/OpenKeychain/src/test/java/org/sufficientlysecure/keychain/provider/ProviderHelperSaveTest.java
@@ -153,7 +153,7 @@ public class ProviderHelperSaveTest {
Assert.assertEquals("first subkey should be of type sign+certify",
KeyFlags.CERTIFY_OTHER | KeyFlags.SIGN_DATA, (int) key.getKeyUsage());
Assert.assertEquals("first subkey should be divert-to-card",
- SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
+ SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive());
Assert.assertTrue("canCertify() should be true", key.canCertify());
Assert.assertTrue("canSign() should be true", key.canSign());
@@ -168,7 +168,7 @@ public class ProviderHelperSaveTest {
Assert.assertEquals("second subkey should be of type authenticate",
KeyFlags.AUTHENTICATION, (int) key.getKeyUsage());
Assert.assertEquals("second subkey should be divert-to-card",
- SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
+ SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive());
Assert.assertTrue("canAuthenticate() should be true", key.canAuthenticate());
// cached
@@ -182,7 +182,7 @@ public class ProviderHelperSaveTest {
Assert.assertEquals("first subkey should be of type encrypt (both types)",
KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE, (int) key.getKeyUsage());
Assert.assertEquals("third subkey should be divert-to-card",
- SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyType());
+ SecretKeyType.DIVERT_TO_CARD, key.getSecretKeyTypeSuperExpensive());
Assert.assertTrue("canEncrypt() should be true", key.canEncrypt());
// cached
@@ -237,7 +237,7 @@ public class ProviderHelperSaveTest {
Assert.assertTrue("master key should have sign flag", ring.getPublicKey().canSign());
Assert.assertTrue("master key should have encrypt flag", ring.getPublicKey().canEncrypt());
- signId = ring.getSecretSignId();
+ signId = mProviderHelper.getCachedPublicKeyRing(masterKeyId).getSecretSignId();
Assert.assertNotEquals("encrypt id should not be 0", 0, signId);
Assert.assertNotEquals("encrypt key should be different from master key", masterKeyId, signId);
}