aboutsummaryrefslogtreecommitdiffstats
path: root/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp
diff options
context:
space:
mode:
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp')
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java20
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java41
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java78
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java3
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java59
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java91
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java114
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java162
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java (renamed from OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java)515
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java69
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java339
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java262
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java38
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java74
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java4
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java26
-rw-r--r--OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java9
17 files changed, 1313 insertions, 591 deletions
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
index 4adacaf23..770e8de91 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedKeyRing.java
@@ -19,6 +19,7 @@
package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.openpgp.PGPKeyRing;
+import org.spongycastle.openpgp.PGPPublicKey;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -26,6 +27,9 @@ import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+
/** A generic wrapped PGPKeyRing object.
*
@@ -90,6 +94,16 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
return getRing().getPublicKey().isEncryptionKey();
}
+ public Set<Long> getEncryptIds() {
+ HashSet<Long> result = new HashSet<>();
+ for(CanonicalizedPublicKey key : publicKeyIterator()) {
+ if (key.canEncrypt() && key.isValid()) {
+ result.add(key.getKeyId());
+ }
+ }
+ return result;
+ }
+
public long getEncryptId() throws PgpKeyNotFoundException {
for(CanonicalizedPublicKey key : publicKeyIterator()) {
if (key.canEncrypt() && key.isValid()) {
@@ -127,7 +141,11 @@ public abstract class CanonicalizedKeyRing extends KeyRing {
}
public CanonicalizedPublicKey getPublicKey(long id) {
- return new CanonicalizedPublicKey(this, getRing().getPublicKey(id));
+ PGPPublicKey pubKey = getRing().getPublicKey(id);
+ if (pubKey == null) {
+ return null;
+ }
+ return new CanonicalizedPublicKey(this, pubKey);
}
public byte[] getEncoded() throws IOException {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
index 8432b8f9f..be5f21f23 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedPublicKeyRing.java
@@ -18,10 +18,10 @@
package org.sufficientlysecure.keychain.pgp;
-import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPObjectFactory;
import org.spongycastle.openpgp.PGPPublicKey;
import org.spongycastle.openpgp.PGPPublicKeyRing;
+import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSecretKeyRing;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.util.IterableIterator;
@@ -62,19 +62,6 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
return mRing;
}
- /** Getter that returns the subkey that should be used for signing. */
- CanonicalizedPublicKey getEncryptionSubKey() throws PgpKeyNotFoundException {
- PGPPublicKey key = getRing().getPublicKey(getEncryptId());
- if(key != null) {
- CanonicalizedPublicKey cKey = new CanonicalizedPublicKey(this, key);
- if(!cKey.canEncrypt()) {
- throw new PgpKeyNotFoundException("key error");
- }
- return cKey;
- }
- throw new PgpKeyNotFoundException("no encryption key available");
- }
-
public IterableIterator<CanonicalizedPublicKey> publicKeyIterator() {
@SuppressWarnings("unchecked")
final Iterator<PGPPublicKey> it = getRing().getPublicKeys();
@@ -97,15 +84,25 @@ public class CanonicalizedPublicKeyRing extends CanonicalizedKeyRing {
}
/** Create a dummy secret ring from this key */
- public UncachedKeyRing createDummySecretRing () {
- PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), null);
- return new UncachedKeyRing(secRing);
- }
-
- /** Create a dummy secret ring from this key */
- public UncachedKeyRing createDivertSecretRing (byte[] cardAid) {
+ public UncachedKeyRing createDivertSecretRing (byte[] cardAid, long[] subKeyIds) {
PGPSecretKeyRing secRing = PGPSecretKeyRing.constructDummyFromPublic(getRing(), cardAid);
- return new UncachedKeyRing(secRing);
+
+ if (subKeyIds == null) {
+ return new UncachedKeyRing(secRing);
+ }
+
+ // if only specific subkeys should be promoted, construct a
+ // stripped dummy, then move divert-to-card keys over
+ PGPSecretKeyRing newRing = PGPSecretKeyRing.constructDummyFromPublic(getRing());
+ for (long subKeyId : subKeyIds) {
+ PGPSecretKey key = secRing.getSecretKey(subKeyId);
+ if (key != null) {
+ newRing = PGPSecretKeyRing.insertSecretKey(newRing, key);
+ }
+ }
+
+ return new UncachedKeyRing(newRing);
+
}
} \ No newline at end of file
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 39d0a2f1d..7394c07c3 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java
@@ -21,22 +21,18 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPPrivateKey;
-import org.spongycastle.openpgp.PGPPublicKey;
-import org.spongycastle.openpgp.PGPPublicKeyRing;
import org.spongycastle.openpgp.PGPSecretKey;
import org.spongycastle.openpgp.PGPSignature;
import org.spongycastle.openpgp.PGPSignatureGenerator;
import org.spongycastle.openpgp.PGPSignatureSubpacketGenerator;
-import org.spongycastle.openpgp.PGPSignatureSubpacketVector;
-import org.spongycastle.openpgp.PGPUserAttributeSubpacketVector;
import org.spongycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.spongycastle.openpgp.operator.PGPContentSignerBuilder;
-import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
+import org.spongycastle.openpgp.operator.jcajce.JcaPGPKeyConverter;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
-import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFactoryBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
@@ -45,10 +41,10 @@ import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
import java.nio.ByteBuffer;
-import java.util.ArrayList;
+import java.security.PrivateKey;
+import java.security.interfaces.RSAPrivateCrtKey;
import java.util.Date;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
@@ -69,9 +65,9 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
private PGPPrivateKey mPrivateKey = null;
private int mPrivateKeyState = PRIVATE_KEY_STATE_LOCKED;
- private static int PRIVATE_KEY_STATE_LOCKED = 0;
- private static int PRIVATE_KEY_STATE_UNLOCKED = 1;
- private static int PRIVATE_KEY_STATE_DIVERT_TO_CARD = 2;
+ final private static int PRIVATE_KEY_STATE_LOCKED = 0;
+ final private static int PRIVATE_KEY_STATE_UNLOCKED = 1;
+ final private static int PRIVATE_KEY_STATE_DIVERT_TO_CARD = 2;
CanonicalizedSecretKey(CanonicalizedSecretKeyRing ring, PGPSecretKey key) {
super(ring, key.getPublicKey());
@@ -123,9 +119,10 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
}
public SecretKeyType getSecretKeyType() {
- if (mSecretKey.getS2K() != null && mSecretKey.getS2K().getType() == S2K.GNU_DUMMY_S2K) {
+ S2K s2k = mSecretKey.getS2K();
+ if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K) {
// divert to card is special
- if (mSecretKey.getS2K().getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
+ if (s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
return SecretKeyType.DIVERT_TO_CARD;
}
// no matter the exact protection mode, it's some kind of dummy key
@@ -156,9 +153,10 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
*/
public boolean unlock(Passphrase passphrase) throws PgpGeneralException {
// handle keys on OpenPGP cards like they were unlocked
- if (mSecretKey.getS2K() != null
- && mSecretKey.getS2K().getType() == S2K.GNU_DUMMY_S2K
- && mSecretKey.getS2K().getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
+ S2K s2k = mSecretKey.getS2K();
+ if (s2k != null
+ && s2k.getType() == S2K.GNU_DUMMY_S2K
+ && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
mPrivateKeyState = PRIVATE_KEY_STATE_DIVERT_TO_CARD;
return true;
}
@@ -178,16 +176,6 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
return true;
}
- /**
- * Returns a list of all supported hash algorithms.
- */
- public ArrayList<Integer> getSupportedHashAlgorithms() {
- // TODO: intersection between preferred hash algos of this key and PgpConstants.PREFERRED_HASH_ALGORITHMS
- // choose best algo
-
- return PgpConstants.sPreferredHashAlgorithms;
- }
-
private PGPContentSignerBuilder getContentSignerBuilder(int hashAlgo,
Map<ByteBuffer,byte[]> signedHashes) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
@@ -206,7 +194,7 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
public PGPSignatureGenerator getCertSignatureGenerator(Map<ByteBuffer, byte[]> signedHashes) {
PGPContentSignerBuilder contentSignerBuilder = getContentSignerBuilder(
- PgpConstants.CERTIFY_HASH_ALGO, signedHashes);
+ PgpSecurityConstants.CERTIFY_HASH_ALGO, signedHashes);
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
@@ -265,20 +253,42 @@ public class CanonicalizedSecretKey extends CanonicalizedPublicKey {
}
}
- public PublicKeyDataDecryptorFactory getDecryptorFactory(CryptoInputParcel cryptoInput) {
+ public CachingDataDecryptorFactory getCachingDecryptorFactory(CryptoInputParcel cryptoInput) {
if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
throw new PrivateKeyNotUnlockedException();
}
if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
- return new NfcSyncPublicKeyDataDecryptorFactoryBuilder()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
- cryptoInput.getCryptoData()
- );
+ return new CachingDataDecryptorFactory(
+ Constants.BOUNCY_CASTLE_PROVIDER_NAME,
+ cryptoInput.getCryptoData());
} else {
- return new JcePublicKeyDataDecryptorFactoryBuilder()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey);
+ return new CachingDataDecryptorFactory(
+ new JcePublicKeyDataDecryptorFactoryBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(mPrivateKey),
+ cryptoInput.getCryptoData());
+ }
+ }
+
+ // For use only in card export; returns the secret key in Chinese Remainder Theorem format.
+ public RSAPrivateCrtKey getCrtSecretKey() throws PgpGeneralException {
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_LOCKED) {
+ throw new PgpGeneralException("Cannot get secret key attributes while key is locked.");
}
+
+ if (mPrivateKeyState == PRIVATE_KEY_STATE_DIVERT_TO_CARD) {
+ throw new PgpGeneralException("Cannot get secret key attributes of divert-to-card key.");
+ }
+
+ JcaPGPKeyConverter keyConverter = new JcaPGPKeyConverter();
+ PrivateKey retVal;
+ try {
+ retVal = keyConverter.getPrivateKey(mPrivateKey);
+ } catch (PGPException e) {
+ throw new PgpGeneralException("Error converting private key!", e);
+ }
+
+ return (RSAPrivateCrtKey)retVal;
}
public byte[] getIv() {
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java
index 825795cc6..77977b691 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/KeyRing.java
@@ -22,6 +22,7 @@ import android.text.TextUtils;
import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
+import java.io.Serializable;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -91,7 +92,7 @@ public abstract class KeyRing {
return userIdString;
}
- public static class UserId {
+ public static class UserId implements Serializable {
public final String name;
public final String email;
public final String comment;
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java
new file mode 100644
index 000000000..c4525e5cd
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpDecryptionResultBuilder.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * 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 org.openintents.openpgp.OpenPgpDecryptionResult;
+import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.util.Log;
+
+public class OpenPgpDecryptionResultBuilder {
+
+ // builder
+ private boolean mInsecure = false;
+ private boolean mEncrypted = false;
+
+ public void setInsecure(boolean insecure) {
+ this.mInsecure = insecure;
+ }
+
+ public void setEncrypted(boolean encrypted) {
+ this.mEncrypted = encrypted;
+ }
+
+ public OpenPgpDecryptionResult build() {
+ OpenPgpDecryptionResult result = new OpenPgpDecryptionResult();
+
+ if (mInsecure) {
+ Log.d(Constants.TAG, "RESULT_INSECURE");
+ result.setResult(OpenPgpDecryptionResult.RESULT_INSECURE);
+ return result;
+ }
+
+ if (mEncrypted) {
+ Log.d(Constants.TAG, "RESULT_ENCRYPTED");
+ result.setResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED);
+ } else {
+ Log.d(Constants.TAG, "RESULT_NOT_ENCRYPTED");
+ result.setResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED);
+ }
+
+ return result;
+ }
+
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
index ed4715681..9d059b58f 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/OpenPgpSignatureResultBuilder.java
@@ -30,7 +30,6 @@ import java.util.ArrayList;
*/
public class OpenPgpSignatureResultBuilder {
// OpenPgpSignatureResult
- private boolean mSignatureOnly = false;
private String mPrimaryUserId;
private ArrayList<String> mUserIds = new ArrayList<>();
private long mKeyId;
@@ -42,10 +41,7 @@ public class OpenPgpSignatureResultBuilder {
private boolean mIsSignatureKeyCertified = false;
private boolean mIsKeyRevoked = false;
private boolean mIsKeyExpired = false;
-
- public void setSignatureOnly(boolean signatureOnly) {
- this.mSignatureOnly = signatureOnly;
- }
+ private boolean mInsecure = false;
public void setPrimaryUserId(String userId) {
this.mPrimaryUserId = userId;
@@ -63,6 +59,10 @@ public class OpenPgpSignatureResultBuilder {
this.mValidSignature = validSignature;
}
+ public void setInsecure(boolean insecure) {
+ this.mInsecure = insecure;
+ }
+
public void setSignatureKeyCertified(boolean isSignatureKeyCertified) {
this.mIsSignatureKeyCertified = isSignatureKeyCertified;
}
@@ -87,6 +87,10 @@ public class OpenPgpSignatureResultBuilder {
return mValidSignature;
}
+ public boolean isInsecure() {
+ return mInsecure;
+ }
+
public void initValid(CanonicalizedPublicKeyRing signingRing,
CanonicalizedPublicKey signingKey) {
setSignatureAvailable(true);
@@ -109,47 +113,50 @@ public class OpenPgpSignatureResultBuilder {
}
public OpenPgpSignatureResult build() {
- if (mSignatureAvailable) {
- OpenPgpSignatureResult result = new OpenPgpSignatureResult();
- result.setSignatureOnly(mSignatureOnly);
-
- // valid sig!
- if (mKnownKey) {
- if (mValidSignature) {
- result.setKeyId(mKeyId);
- result.setPrimaryUserId(mPrimaryUserId);
- result.setUserIds(mUserIds);
-
- if (mIsKeyRevoked) {
- Log.d(Constants.TAG, "SIGNATURE_KEY_REVOKED");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_KEY_REVOKED);
- } else if (mIsKeyExpired) {
- Log.d(Constants.TAG, "SIGNATURE_KEY_EXPIRED");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_KEY_EXPIRED);
- } else if (mIsSignatureKeyCertified) {
- Log.d(Constants.TAG, "SIGNATURE_SUCCESS_CERTIFIED");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED);
- } else {
- Log.d(Constants.TAG, "SIGNATURE_SUCCESS_UNCERTIFIED");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED);
- }
- } else {
- Log.d(Constants.TAG, "Error! Invalid signature.");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_ERROR);
- }
- } else {
- result.setKeyId(mKeyId);
-
- Log.d(Constants.TAG, "SIGNATURE_KEY_MISSING");
- result.setStatus(OpenPgpSignatureResult.SIGNATURE_KEY_MISSING);
- }
+ OpenPgpSignatureResult result = new OpenPgpSignatureResult();
+ if (!mSignatureAvailable) {
+ Log.d(Constants.TAG, "RESULT_NO_SIGNATURE");
+ result.setResult(OpenPgpSignatureResult.RESULT_NO_SIGNATURE);
return result;
- } else {
- Log.d(Constants.TAG, "no signature found!");
+ }
+
+ if (!mKnownKey) {
+ result.setKeyId(mKeyId);
- return null;
+ Log.d(Constants.TAG, "RESULT_KEY_MISSING");
+ result.setResult(OpenPgpSignatureResult.RESULT_KEY_MISSING);
+ return result;
+ }
+
+ if (!mValidSignature) {
+ Log.d(Constants.TAG, "RESULT_INVALID_SIGNATURE");
+ result.setResult(OpenPgpSignatureResult.RESULT_INVALID_SIGNATURE);
+ return result;
}
+
+ result.setKeyId(mKeyId);
+ result.setPrimaryUserId(mPrimaryUserId);
+ result.setUserIds(mUserIds);
+
+ if (mIsKeyRevoked) {
+ Log.d(Constants.TAG, "RESULT_INVALID_KEY_REVOKED");
+ result.setResult(OpenPgpSignatureResult.RESULT_INVALID_KEY_REVOKED);
+ } else if (mIsKeyExpired) {
+ Log.d(Constants.TAG, "RESULT_INVALID_KEY_EXPIRED");
+ result.setResult(OpenPgpSignatureResult.RESULT_INVALID_KEY_EXPIRED);
+ } else if (mInsecure) {
+ Log.d(Constants.TAG, "RESULT_INVALID_INSECURE");
+ result.setResult(OpenPgpSignatureResult.RESULT_INVALID_INSECURE);
+ } else if (mIsSignatureKeyCertified) {
+ Log.d(Constants.TAG, "RESULT_VALID_CONFIRMED");
+ result.setResult(OpenPgpSignatureResult.RESULT_VALID_CONFIRMED);
+ } else {
+ Log.d(Constants.TAG, "RESULT_VALID_UNCONFIRMED");
+ result.setResult(OpenPgpSignatureResult.RESULT_VALID_UNCONFIRMED);
+ }
+
+ return result;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java
deleted file mode 100644
index f739b1e6d..000000000
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpConstants.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
- *
- * 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 org.spongycastle.bcpg.CompressionAlgorithmTags;
-import org.spongycastle.bcpg.HashAlgorithmTags;
-import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
-
-import java.util.ArrayList;
-
-public class PgpConstants {
-
- public static ArrayList<Integer> sPreferredSymmetricAlgorithms = new ArrayList<>();
- public static ArrayList<Integer> sPreferredHashAlgorithms = new ArrayList<>();
- public static ArrayList<Integer> sPreferredCompressionAlgorithms = new ArrayList<>();
-
- // TODO: use hashmaps for contains in O(1) and intersections!
-
- /*
- * Most preferred is first
- * These arrays are written as preferred algorithms into the keys on creation.
- * Other implementations may choose to honor this selection.
- *
- * These lists also define the only algorithms which are used in OpenKeychain.
- * We do not support algorithms such as MD5
- */
- static {
- sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.AES_256);
- sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.AES_192);
- sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.AES_128);
- sPreferredSymmetricAlgorithms.add(SymmetricKeyAlgorithmTags.TWOFISH);
-
- // NOTE: some implementations do not support SHA512, thus we choose SHA256 as default (Mailvelope?)
- sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA256);
- sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA512);
- sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA384);
- sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA224);
- sPreferredHashAlgorithms.add(HashAlgorithmTags.SHA1);
- sPreferredHashAlgorithms.add(HashAlgorithmTags.RIPEMD160);
-
- /*
- * Prefer ZIP
- * "ZLIB provides no benefit over ZIP and is more malleable"
- * - (OpenPGP WG mailinglist: "[openpgp] Intent to deprecate: Insecure primitives")
- * BZIP2: very slow
- */
- sPreferredCompressionAlgorithms.add(CompressionAlgorithmTags.ZIP);
- sPreferredCompressionAlgorithms.add(CompressionAlgorithmTags.ZLIB);
- sPreferredCompressionAlgorithms.add(CompressionAlgorithmTags.BZIP2);
- }
-
- public static final int CERTIFY_HASH_ALGO = HashAlgorithmTags.SHA256;
-
- /*
- * Note: s2kcount is a number between 0 and 0xff that controls the
- * number of times to iterate the password hash before use. More
- * iterations are useful against offline attacks, as it takes more
- * time to check each password. The actual number of iterations is
- * rather complex, and also depends on the hash function in use.
- * Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give
- * you more iterations. As a rough rule of thumb, when using
- * SHA256 as the hashing function, 0x10 gives you about 64
- * iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0,
- * or about 1 million iterations. The maximum you can go to is
- * 0xff, or about 2 million iterations.
- * from http://kbsriram.com/2013/01/generating-rsa-keys-with-bouncycastle.html
- *
- * Bouncy Castle default: 0x60
- * kbsriram proposes: 0xc0
- * OpenKeychain: 0x90
- */
- public static final int SECRET_KEY_ENCRYPTOR_S2K_COUNT = 0x90;
- public static final int SECRET_KEY_ENCRYPTOR_HASH_ALGO = HashAlgorithmTags.SHA256;
- public static final int SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO = SymmetricKeyAlgorithmTags.AES_256;
- public static final int SECRET_KEY_SIGNATURE_HASH_ALGO = HashAlgorithmTags.SHA256;
- // NOTE: only SHA1 is supported for key checksum calculations in OpenPGP,
- // see http://tools.ietf.org/html/rfc488 0#section-5.5.3
- public static final int SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO = HashAlgorithmTags.SHA1;
-
- public static interface OpenKeychainSymmetricKeyAlgorithmTags extends SymmetricKeyAlgorithmTags {
- public static final int USE_PREFERRED = -1;
- }
-
- public static interface OpenKeychainHashAlgorithmTags extends HashAlgorithmTags {
- public static final int USE_PREFERRED = -1;
- }
-
- public static interface OpenKeychainCompressionAlgorithmTags extends CompressionAlgorithmTags {
- public static final int USE_PREFERRED = -1;
- }
-
- public static int[] getAsArray(ArrayList<Integer> list) {
- int[] array = new int[list.size()];
- for (int i = 0; i < list.size(); i++) {
- array[i] = list.get(i);
- }
- return array;
- }
-}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
new file mode 100644
index 000000000..a6d65688c
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyInputParcel.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ * Copyright (C) 2014 Vincent Breitmoser <v.breitmoser@mugenguild.com>
+ *
+ * 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.HashSet;
+
+import android.net.Uri;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+public class PgpDecryptVerifyInputParcel implements Parcelable {
+
+ private Uri mInputUri;
+ private Uri mOutputUri;
+ private byte[] mInputBytes;
+
+ private boolean mAllowSymmetricDecryption;
+ private HashSet<Long> mAllowedKeyIds;
+ private boolean mDecryptMetadataOnly;
+ private byte[] mDetachedSignature;
+ private String mRequiredSignerFingerprint;
+ private boolean mSignedLiteralData;
+
+ public PgpDecryptVerifyInputParcel() {
+ }
+
+ public PgpDecryptVerifyInputParcel(Uri inputUri, Uri outputUri) {
+ mInputUri = inputUri;
+ mOutputUri = outputUri;
+ }
+
+ public PgpDecryptVerifyInputParcel(byte[] inputBytes) {
+ mInputBytes = inputBytes;
+ }
+
+ PgpDecryptVerifyInputParcel(Parcel source) {
+ // we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
+ mInputUri = source.readParcelable(getClass().getClassLoader());
+ mOutputUri = source.readParcelable(getClass().getClassLoader());
+ mInputBytes = source.createByteArray();
+
+ mAllowSymmetricDecryption = source.readInt() != 0;
+ mAllowedKeyIds = (HashSet<Long>) source.readSerializable();
+ mDecryptMetadataOnly = source.readInt() != 0;
+ mDetachedSignature = source.createByteArray();
+ mRequiredSignerFingerprint = source.readString();
+ mSignedLiteralData = source.readInt() != 0;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mInputUri, 0);
+ dest.writeParcelable(mOutputUri, 0);
+ dest.writeByteArray(mInputBytes);
+
+ dest.writeInt(mAllowSymmetricDecryption ? 1 : 0);
+ dest.writeSerializable(mAllowedKeyIds);
+ dest.writeInt(mDecryptMetadataOnly ? 1 : 0);
+ dest.writeByteArray(mDetachedSignature);
+ dest.writeString(mRequiredSignerFingerprint);
+ dest.writeInt(mSignedLiteralData ? 1 : 0);
+ }
+
+ byte[] getInputBytes() {
+ return mInputBytes;
+ }
+
+ Uri getInputUri() {
+ return mInputUri;
+ }
+
+ Uri getOutputUri() {
+ return mOutputUri;
+ }
+
+ boolean isAllowSymmetricDecryption() {
+ return mAllowSymmetricDecryption;
+ }
+
+ public PgpDecryptVerifyInputParcel setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
+ mAllowSymmetricDecryption = allowSymmetricDecryption;
+ return this;
+ }
+
+ HashSet<Long> getAllowedKeyIds() {
+ return mAllowedKeyIds;
+ }
+
+ public PgpDecryptVerifyInputParcel setAllowedKeyIds(HashSet<Long> allowedKeyIds) {
+ mAllowedKeyIds = allowedKeyIds;
+ return this;
+ }
+
+ boolean isDecryptMetadataOnly() {
+ return mDecryptMetadataOnly;
+ }
+
+ public PgpDecryptVerifyInputParcel setDecryptMetadataOnly(boolean decryptMetadataOnly) {
+ mDecryptMetadataOnly = decryptMetadataOnly;
+ return this;
+ }
+
+ byte[] getDetachedSignature() {
+ return mDetachedSignature;
+ }
+
+ public PgpDecryptVerifyInputParcel setDetachedSignature(byte[] detachedSignature) {
+ mDetachedSignature = detachedSignature;
+ return this;
+ }
+
+ String getRequiredSignerFingerprint() {
+ return mRequiredSignerFingerprint;
+ }
+
+ public PgpDecryptVerifyInputParcel setRequiredSignerFingerprint(String requiredSignerFingerprint) {
+ mRequiredSignerFingerprint = requiredSignerFingerprint;
+ return this;
+ }
+
+ boolean isSignedLiteralData() {
+ return mSignedLiteralData;
+ }
+
+ public PgpDecryptVerifyInputParcel setSignedLiteralData(boolean signedLiteralData) {
+ mSignedLiteralData = signedLiteralData;
+ return this;
+ }
+
+ public static final Creator<PgpDecryptVerifyInputParcel> CREATOR = new Creator<PgpDecryptVerifyInputParcel>() {
+ public PgpDecryptVerifyInputParcel createFromParcel(final Parcel source) {
+ return new PgpDecryptVerifyInputParcel(source);
+ }
+
+ public PgpDecryptVerifyInputParcel[] newArray(final int size) {
+ return new PgpDecryptVerifyInputParcel[size];
+ }
+ };
+
+}
+
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
index f6580b85a..dd30156f9 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerifyOperation.java
@@ -19,15 +19,19 @@
package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
+import android.support.annotation.NonNull;
import android.webkit.MimeTypeMap;
+import org.openintents.openpgp.OpenPgpDecryptionResult;
import org.openintents.openpgp.OpenPgpMetadata;
import org.openintents.openpgp.OpenPgpSignatureResult;
import org.spongycastle.bcpg.ArmoredInputStream;
import org.spongycastle.openpgp.PGPCompressedData;
+import org.spongycastle.openpgp.PGPDataValidationException;
import org.spongycastle.openpgp.PGPEncryptedData;
import org.spongycastle.openpgp.PGPEncryptedDataList;
import org.spongycastle.openpgp.PGPException;
+import org.spongycastle.openpgp.PGPKeyValidationException;
import org.spongycastle.openpgp.PGPLiteralData;
import org.spongycastle.openpgp.PGPOnePassSignature;
import org.spongycastle.openpgp.PGPOnePassSignatureList;
@@ -39,12 +43,13 @@ import org.spongycastle.openpgp.PGPUtil;
import org.spongycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.spongycastle.openpgp.operator.PBEDataDecryptorFactory;
import org.spongycastle.openpgp.operator.PGPDigestCalculatorProvider;
-import org.spongycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
+import org.spongycastle.openpgp.operator.jcajce.CachingDataDecryptorFactory;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPContentVerifierBuilderProvider;
import org.spongycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder;
-import org.spongycastle.openpgp.operator.jcajce.NfcSyncPublicKeyDataDecryptorFactoryBuilder;
+import org.spongycastle.util.encoders.DecoderException;
import org.sufficientlysecure.keychain.Constants;
+import org.sufficientlysecure.keychain.Constants.key;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
import org.sufficientlysecure.keychain.pgp.CanonicalizedSecretKey.SecretKeyType;
@@ -57,6 +62,7 @@ import org.sufficientlysecure.keychain.operations.results.OperationResult.Operat
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
+import org.sufficientlysecure.keychain.util.FileHelper;
import org.sufficientlysecure.keychain.util.InputData;
import org.sufficientlysecure.keychain.util.Log;
import org.sufficientlysecure.keychain.util.Passphrase;
@@ -65,154 +71,99 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
+import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.net.URLConnection;
import java.security.SignatureException;
import java.util.Date;
import java.util.Iterator;
-import java.util.Set;
-/**
- * This class uses a Builder pattern!
- */
-public class PgpDecryptVerify extends BaseOperation {
-
- private InputData mData;
- private OutputStream mOutStream;
-
- private boolean mAllowSymmetricDecryption;
- private Set<Long> mAllowedKeyIds;
- private boolean mDecryptMetadataOnly;
- private byte[] mDetachedSignature;
- private String mRequiredSignerFingerprint;
- private boolean mSignedLiteralData;
-
- protected PgpDecryptVerify(Builder builder) {
- super(builder.mContext, builder.mProviderHelper, builder.mProgressable);
-
- // private Constructor can only be called from Builder
- this.mData = builder.mData;
- this.mOutStream = builder.mOutStream;
-
- this.mAllowSymmetricDecryption = builder.mAllowSymmetricDecryption;
- this.mAllowedKeyIds = builder.mAllowedKeyIds;
- this.mDecryptMetadataOnly = builder.mDecryptMetadataOnly;
- this.mDetachedSignature = builder.mDetachedSignature;
- this.mSignedLiteralData = builder.mSignedLiteralData;
- this.mRequiredSignerFingerprint = builder.mRequiredSignerFingerprint;
- }
+public class PgpDecryptVerifyOperation extends BaseOperation<PgpDecryptVerifyInputParcel> {
- public static class Builder {
- // mandatory parameter
- private Context mContext;
- private ProviderHelper mProviderHelper;
- private InputData mData;
-
- // optional
- private OutputStream mOutStream = null;
- private Progressable mProgressable = null;
- private boolean mAllowSymmetricDecryption = true;
- private Set<Long> mAllowedKeyIds = null;
- private boolean mDecryptMetadataOnly = false;
- private byte[] mDetachedSignature = null;
- private String mRequiredSignerFingerprint = null;
- private boolean mSignedLiteralData = false;
-
- public Builder(Context context, ProviderHelper providerHelper,
- Progressable progressable,
- InputData data, OutputStream outStream) {
- mContext = context;
- mProviderHelper = providerHelper;
- mProgressable = progressable;
- mData = data;
- mOutStream = outStream;
- }
+ public PgpDecryptVerifyOperation(Context context, ProviderHelper providerHelper, Progressable progressable) {
+ super(context, providerHelper, progressable);
+ }
- /**
- * This is used when verifying signed literals to check that they are signed with
- * the required key
- */
- public Builder setRequiredSignerFingerprint(String fingerprint) {
- mRequiredSignerFingerprint = fingerprint;
- return this;
- }
+ /** Decrypts and/or verifies data based on parameters of PgpDecryptVerifyInputParcel. */
+ @NonNull
+ public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput) {
+ InputData inputData;
+ OutputStream outputStream;
- /**
- * This is to force a mode where the message is just the signature key id and
- * then a literal data packet; used in Keybase.io proofs
- */
- public Builder setSignedLiteralData(boolean signedLiteralData) {
- mSignedLiteralData = signedLiteralData;
- return this;
+ if (input.getInputBytes() != null) {
+ byte[] inputBytes = input.getInputBytes();
+ inputData = new InputData(new ByteArrayInputStream(inputBytes), inputBytes.length);
+ } else {
+ try {
+ InputStream inputStream = mContext.getContentResolver().openInputStream(input.getInputUri());
+ long inputSize = FileHelper.getFileSize(mContext, input.getInputUri(), 0);
+ inputData = new InputData(inputStream, inputSize);
+ } catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "Input URI could not be opened: " + input.getInputUri(), e);
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_DC_ERROR_INPUT, 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ }
}
- public Builder setAllowSymmetricDecryption(boolean allowSymmetricDecryption) {
- mAllowSymmetricDecryption = allowSymmetricDecryption;
- return this;
+ if (input.getOutputUri() == null) {
+ outputStream = new ByteArrayOutputStream();
+ } else {
+ try {
+ outputStream = mContext.getContentResolver().openOutputStream(input.getOutputUri());
+ } catch (FileNotFoundException e) {
+ Log.e(Constants.TAG, "Output URI could not be opened: " + input.getOutputUri(), e);
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_DC_ERROR_IO, 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ }
}
- /**
- * Allow these key ids alone for decryption.
- * This means only ciphertexts encrypted for one of these private key can be decrypted.
- */
- public Builder setAllowedKeyIds(Set<Long> allowedKeyIds) {
- mAllowedKeyIds = allowedKeyIds;
- return this;
+ DecryptVerifyResult result = executeInternal(input, cryptoInput, inputData, outputStream);
+ if (outputStream instanceof ByteArrayOutputStream) {
+ byte[] outputData = ((ByteArrayOutputStream) outputStream).toByteArray();
+ result.setOutputBytes(outputData);
}
- /**
- * If enabled, the actual decryption/verification of the content will not be executed.
- * The metadata only will be decrypted and returned.
- */
- public Builder setDecryptMetadataOnly(boolean decryptMetadataOnly) {
- mDecryptMetadataOnly = decryptMetadataOnly;
- return this;
- }
+ return result;
- /**
- * If detachedSignature != null, it will be used exclusively to verify the signature
- */
- public Builder setDetachedSignature(byte[] detachedSignature) {
- mDetachedSignature = detachedSignature;
- return this;
- }
+ }
- public PgpDecryptVerify build() {
- return new PgpDecryptVerify(this);
- }
+ @NonNull
+ public DecryptVerifyResult execute(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
+ InputData inputData, OutputStream outputStream) {
+ return executeInternal(input, cryptoInput, inputData, outputStream);
}
- /**
- * Decrypts and/or verifies data based on parameters of class
- */
- public DecryptVerifyResult execute(CryptoInputParcel cryptoInput) {
+ @NonNull
+ private DecryptVerifyResult executeInternal(PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
+ InputData inputData, OutputStream outputStream) {
try {
- if (mDetachedSignature != null) {
+ if (input.getDetachedSignature() != null) {
Log.d(Constants.TAG, "Detached signature present, verifying with this signature only");
- return verifyDetachedSignature(mData.getInputStream(), 0);
+ return verifyDetachedSignature(input, inputData, outputStream, 0);
} else {
// automatically works with PGP ascii armor and PGP binary
- InputStream in = PGPUtil.getDecoderStream(mData.getInputStream());
+ InputStream in = PGPUtil.getDecoderStream(inputData.getInputStream());
if (in instanceof ArmoredInputStream) {
ArmoredInputStream aIn = (ArmoredInputStream) in;
// it is ascii armored
Log.d(Constants.TAG, "ASCII Armor Header Line: " + aIn.getArmorHeaderLine());
- if (mSignedLiteralData) {
- return verifySignedLiteralData(aIn, 0);
+ if (input.isSignedLiteralData()) {
+ return verifySignedLiteralData(input, aIn, outputStream, 0);
} else if (aIn.isClearText()) {
// a cleartext signature, verify it with the other method
- return verifyCleartextSignature(aIn, 0);
+ return verifyCleartextSignature(aIn, outputStream, 0);
} else {
// else: ascii armored encryption! go on...
- return decryptVerify(cryptoInput, in, 0);
+ return decryptVerify(input, cryptoInput, in, outputStream, 0);
}
} else {
- return decryptVerify(cryptoInput, in, 0);
+ return decryptVerify(input, cryptoInput, in, outputStream, 0);
}
}
} catch (PGPException e) {
@@ -220,6 +171,13 @@ public class PgpDecryptVerify extends BaseOperation {
OperationLog log = new OperationLog();
log.add(LogType.MSG_DC_ERROR_PGP_EXCEPTION, 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ } catch (DecoderException | ArrayIndexOutOfBoundsException e) {
+ // these can happen if assumptions in JcaPGPObjectFactory.nextObject() aren't
+ // fulfilled, so we need to catch them here to handle this gracefully
+ Log.d(Constants.TAG, "data error", e);
+ OperationLog log = new OperationLog();
+ log.add(LogType.MSG_DC_ERROR_IO, 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
} catch (IOException e) {
Log.d(Constants.TAG, "IOException", e);
OperationLog log = new OperationLog();
@@ -228,10 +186,10 @@ public class PgpDecryptVerify extends BaseOperation {
}
}
- /**
- * Verify Keybase.io style signed literal data
- */
- private DecryptVerifyResult verifySignedLiteralData(InputStream in, int indent)
+ /**Verify signed plaintext data (PGP/INLINE). */
+ @NonNull
+ private DecryptVerifyResult verifySignedLiteralData(
+ PgpDecryptVerifyInputParcel input, InputStream in, OutputStream out, int indent)
throws IOException, PGPException {
OperationLog log = new OperationLog();
log.add(LogType.MSG_VL, indent);
@@ -282,9 +240,9 @@ public class PgpDecryptVerify extends BaseOperation {
}
String fingerprint = KeyFormattingUtils.convertFingerprintToHex(signingRing.getFingerprint());
- if (!(mRequiredSignerFingerprint.equals(fingerprint))) {
+ if (!(input.getRequiredSignerFingerprint().equals(fingerprint))) {
log.add(LogType.MSG_VL_ERROR_MISSING_KEY, indent);
- Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + mRequiredSignerFingerprint +
+ Log.d(Constants.TAG, "Fingerprint mismatch; wanted " + input.getRequiredSignerFingerprint() +
" got " + fingerprint + "!");
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
@@ -316,7 +274,7 @@ public class PgpDecryptVerify extends BaseOperation {
int length;
byte[] buffer = new byte[1 << 16];
while ((length = dataIn.read(buffer)) > 0) {
- mOutStream.write(buffer, 0, length);
+ out.write(buffer, 0, length);
signature.update(buffer, 0, length);
}
@@ -326,10 +284,6 @@ public class PgpDecryptVerify extends BaseOperation {
PGPSignatureList signatureList = (PGPSignatureList) pgpF.nextObject();
PGPSignature messageSignature = signatureList.get(signatureIndex);
- // these are not cleartext signatures!
- // TODO: what about binary signatures?
- signatureResultBuilder.setSignatureOnly(false);
-
// Verify signature and check binding signatures
boolean validSignature = signature.verify(messageSignature);
if (validSignature) {
@@ -341,8 +295,8 @@ public class PgpDecryptVerify extends BaseOperation {
OpenPgpSignatureResult signatureResult = signatureResultBuilder.build();
- if (signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_CERTIFIED
- && signatureResult.getStatus() != OpenPgpSignatureResult.SIGNATURE_SUCCESS_UNCERTIFIED) {
+ if (signatureResult.getResult() != OpenPgpSignatureResult.RESULT_VALID_CONFIRMED
+ && signatureResult.getResult() != OpenPgpSignatureResult.RESULT_VALID_UNCONFIRMED) {
log.add(LogType.MSG_VL_ERROR_INTEGRITY_CHECK, indent);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
@@ -352,19 +306,22 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_VL_OK, indent);
// Return a positive result, with metadata and verification info
- DecryptVerifyResult result =
- new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
+ DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setSignatureResult(signatureResult);
+ result.setDecryptionResult(
+ new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
return result;
}
- /**
- * Decrypt and/or verifies binary or ascii armored pgp
- */
- private DecryptVerifyResult decryptVerify(CryptoInputParcel cryptoInput,
- InputStream in, int indent) throws IOException, PGPException {
+ /** Decrypt and/or verify binary or ascii armored pgp data. */
+ @NonNull
+ private DecryptVerifyResult decryptVerify(
+ PgpDecryptVerifyInputParcel input, CryptoInputParcel cryptoInput,
+ InputStream in, OutputStream out, int indent) throws IOException, PGPException {
+ OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
+ OpenPgpDecryptionResultBuilder decryptionResultBuilder = new OpenPgpDecryptionResultBuilder();
OperationLog log = new OperationLog();
log.add(LogType.MSG_DC, indent);
@@ -384,7 +341,7 @@ public class PgpDecryptVerify extends BaseOperation {
}
if (enc == null) {
- log.add(LogType.MSG_DC_ERROR_INVALID_SIGLIST, indent);
+ log.add(LogType.MSG_DC_ERROR_INVALID_DATA, indent);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
@@ -419,6 +376,7 @@ public class PgpDecryptVerify extends BaseOperation {
}
Passphrase passphrase = null;
+ boolean skippedDisallowedKey = false;
// go through all objects and find one we can decrypt
while (it.hasNext()) {
@@ -451,29 +409,31 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
continue;
}
- // get subkey which has been used for this encryption packet
- secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
- if (secretEncryptionKey == null) {
- // should actually never happen, so no need to be more specific.
- log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
- continue;
- }
// allow only specific keys for decryption?
- if (mAllowedKeyIds != null) {
+ if (input.getAllowedKeyIds() != null) {
long masterKeyId = secretKeyRing.getMasterKeyId();
Log.d(Constants.TAG, "encData.getKeyID(): " + subKeyId);
- Log.d(Constants.TAG, "mAllowedKeyIds: " + mAllowedKeyIds);
+ Log.d(Constants.TAG, "mAllowedKeyIds: " + input.getAllowedKeyIds());
Log.d(Constants.TAG, "masterKeyId: " + masterKeyId);
- if (!mAllowedKeyIds.contains(masterKeyId)) {
+ if (!input.getAllowedKeyIds().contains(masterKeyId)) {
// this key is in our db, but NOT allowed!
// continue with the next packet in the while loop
+ skippedDisallowedKey = true;
log.add(LogType.MSG_DC_ASKIP_NOT_ALLOWED, indent + 1);
continue;
}
}
+ // get subkey which has been used for this encryption packet
+ secretEncryptionKey = secretKeyRing.getSecretKey(subKeyId);
+ if (secretEncryptionKey == null) {
+ // should actually never happen, so no need to be more specific.
+ log.add(LogType.MSG_DC_ASKIP_NO_KEY, indent + 1);
+ continue;
+ }
+
/* secret key exists in database and is allowed! */
asymmetricPacketFound = true;
@@ -499,10 +459,17 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
return new DecryptVerifyResult(log,
RequiredInputParcel.createRequiredDecryptPassphrase(
- secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()));
+ secretKeyRing.getMasterKeyId(), secretEncryptionKey.getKeyId()),
+ cryptoInput);
}
}
+ // check for insecure encryption key
+ if ( ! PgpSecurityConstants.isSecureKey(secretEncryptionKey)) {
+ log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
+ decryptionResultBuilder.setInsecure(true);
+ }
+
// break out of while, only decrypt the first packet where we have a key
break;
@@ -511,7 +478,7 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_SYM, indent);
- if (!mAllowSymmetricDecryption) {
+ if (!input.isAllowSymmetricDecryption()) {
log.add(LogType.MSG_DC_SYM_SKIP, indent + 1);
continue;
}
@@ -527,12 +494,24 @@ public class PgpDecryptVerify extends BaseOperation {
// if no passphrase is given, return here
// indicating that a passphrase is missing!
if (!cryptoInput.hasPassphrase()) {
- log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
- return new DecryptVerifyResult(log,
- RequiredInputParcel.createRequiredSymmetricPassphrase());
- }
- passphrase = cryptoInput.getPassphrase();
+ try {
+ passphrase = getCachedPassphrase(key.symmetric);
+ log.add(LogType.MSG_DC_PASS_CACHED, indent + 1);
+ } catch (PassphraseCacheInterface.NoSecretKeyException e) {
+ // nvm
+ }
+
+ if (passphrase == null) {
+ log.add(LogType.MSG_DC_PENDING_PASSPHRASE, indent + 1);
+ return new DecryptVerifyResult(log,
+ RequiredInputParcel.createRequiredSymmetricPassphrase(),
+ cryptoInput);
+ }
+
+ } else {
+ passphrase = cryptoInput.getPassphrase();
+ }
// break out of while, only decrypt the first packet
break;
@@ -568,7 +547,14 @@ public class PgpDecryptVerify extends BaseOperation {
digestCalcProvider).setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
passphrase.getCharArray());
- clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
+ try {
+ clear = encryptedDataSymmetric.getDataStream(decryptorFactory);
+ } catch (PGPDataValidationException e) {
+ log.add(LogType.MSG_DC_ERROR_SYM_PASSPHRASE, indent +1);
+ return new DecryptVerifyResult(log,
+ RequiredInputParcel.createRequiredSymmetricPassphrase(), cryptoInput);
+ }
+
encryptedData = encryptedDataSymmetric;
symmetricEncryptionAlgo = encryptedDataSymmetric.getSymmetricAlgorithm(decryptorFactory);
@@ -590,35 +576,60 @@ public class PgpDecryptVerify extends BaseOperation {
currentProgress += 2;
updateProgress(R.string.progress_preparing_streams, currentProgress, 100);
- try {
- PublicKeyDataDecryptorFactory decryptorFactory
- = secretEncryptionKey.getDecryptorFactory(cryptoInput);
- clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
+ CachingDataDecryptorFactory decryptorFactory
+ = secretEncryptionKey.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
+ if (!decryptorFactory.canDecrypt()
+ && !decryptorFactory.hasCachedSessionData(encryptedDataAsymmetric)) {
- symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
- } catch (NfcSyncPublicKeyDataDecryptorFactoryBuilder.NfcInteractionNeeded e) {
log.add(LogType.MSG_DC_PENDING_NFC, indent + 1);
return new DecryptVerifyResult(log, RequiredInputParcel.createNfcDecryptOperation(
- e.encryptedSessionKey, secretEncryptionKey.getKeyId()
- ));
+ secretEncryptionKey.getRing().getMasterKeyId(),
+ secretEncryptionKey.getKeyId(), encryptedDataAsymmetric.getSessionKey()[0]
+ ),
+ cryptoInput);
+
+ }
+
+ try {
+ clear = encryptedDataAsymmetric.getDataStream(decryptorFactory);
+ } catch (PGPKeyValidationException | ArrayIndexOutOfBoundsException e) {
+ log.add(LogType.MSG_DC_ERROR_CORRUPT_DATA, indent + 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
+
+ symmetricEncryptionAlgo = encryptedDataAsymmetric.getSymmetricAlgorithm(decryptorFactory);
+
+ cryptoInput.addCryptoData(decryptorFactory.getCachedSessionKeys());
+
encryptedData = encryptedDataAsymmetric;
} else {
- // If we didn't find any useful data, error out
+ // there wasn't even any useful data
+ if (!anyPacketFound) {
+ log.add(LogType.MSG_DC_ERROR_NO_DATA, indent + 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_NO_DATA, log);
+ }
+ // there was data but key wasn't allowed
+ if (skippedDisallowedKey) {
+ log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
+ return new DecryptVerifyResult(DecryptVerifyResult.RESULT_KEY_DISALLOWED, log);
+ }
// no packet has been found where we have the corresponding secret key in our db
- log.add(
- anyPacketFound ? LogType.MSG_DC_ERROR_NO_KEY : LogType.MSG_DC_ERROR_NO_DATA, indent + 1);
+ log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
+ decryptionResultBuilder.setEncrypted(true);
- // Warn about old encryption algorithms!
- if (!PgpConstants.sPreferredSymmetricAlgorithms.contains(symmetricEncryptionAlgo)) {
- log.add(LogType.MSG_DC_OLD_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
+ // Check for insecure encryption algorithms!
+ if (!PgpSecurityConstants.isSecureSymmetricAlgorithm(symmetricEncryptionAlgo)) {
+ log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
+ decryptionResultBuilder.setInsecure(true);
}
JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
Object dataChunk = plainFact.nextObject();
- OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
int signatureIndex = -1;
CanonicalizedPublicKeyRing signingRing = null;
CanonicalizedPublicKey signingKey = null;
@@ -682,6 +693,13 @@ public class PgpDecryptVerify extends BaseOperation {
}
}
+ // check for insecure signing key
+ // TODO: checks on signingRing ?
+ if (signingKey != null && ! PgpSecurityConstants.isSecureKey(signingKey)) {
+ log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
+ signatureResultBuilder.setInsecure(true);
+ }
+
dataChunk = plainFact.nextObject();
}
@@ -700,17 +718,12 @@ public class PgpDecryptVerify extends BaseOperation {
PGPLiteralData literalData = (PGPLiteralData) dataChunk;
- // reported size may be null if partial packets are involved (highly unlikely though)
- Long originalSize = literalData.getDataLengthIfAvailable();
-
String originalFilename = literalData.getFileName();
String mimeType = null;
if (literalData.getFormat() == PGPLiteralData.TEXT
|| literalData.getFormat() == PGPLiteralData.UTF8) {
mimeType = "text/plain";
} else {
- // TODO: better would be: https://github.com/open-keychain/open-keychain/issues/753
-
// try to guess from file ending
String extension = MimeTypeMap.getFileExtensionFromUrl(originalFilename);
if (extension != null) {
@@ -718,19 +731,10 @@ public class PgpDecryptVerify extends BaseOperation {
mimeType = mime.getMimeTypeFromExtension(extension);
}
if (mimeType == null) {
- mimeType = URLConnection.guessContentTypeFromName(originalFilename);
- }
- if (mimeType == null) {
- mimeType = "*/*";
+ mimeType = "application/octet-stream";
}
}
- metadata = new OpenPgpMetadata(
- originalFilename,
- mimeType,
- literalData.getModificationTime().getTime(),
- originalSize == null ? 0 : originalSize);
-
if (!"".equals(originalFilename)) {
log.add(LogType.MSG_DC_CLEAR_META_FILE, indent + 1, originalFilename);
}
@@ -738,20 +742,31 @@ public class PgpDecryptVerify extends BaseOperation {
mimeType);
log.add(LogType.MSG_DC_CLEAR_META_TIME, indent + 1,
new Date(literalData.getModificationTime().getTime()).toString());
- if (originalSize != null) {
- log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1,
- Long.toString(originalSize));
- } else {
- log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1);
- }
// return here if we want to decrypt the metadata only
- if (mDecryptMetadataOnly) {
+ if (input.isDecryptMetadataOnly()) {
+
+ // this operation skips the entire stream to find the data length!
+ Long originalSize = literalData.findDataLength();
+
+ if (originalSize != null) {
+ log.add(LogType.MSG_DC_CLEAR_META_SIZE, indent + 1,
+ Long.toString(originalSize));
+ } else {
+ log.add(LogType.MSG_DC_CLEAR_META_SIZE_UNKNOWN, indent + 1);
+ }
+
+ metadata = new OpenPgpMetadata(
+ originalFilename,
+ mimeType,
+ literalData.getModificationTime().getTime(),
+ originalSize == null ? 0 : originalSize);
+
log.add(LogType.MSG_DC_OK_META_ONLY, indent);
DecryptVerifyResult result =
new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setCharset(charset);
- result.setDecryptMetadata(metadata);
+ result.setDecryptionMetadata(metadata);
return result;
}
@@ -769,13 +784,13 @@ public class PgpDecryptVerify extends BaseOperation {
InputStream dataIn = literalData.getInputStream();
long alreadyWritten = 0;
- long wholeSize = mData.getSize() - mData.getStreamPosition();
+ long wholeSize = 0; // TODO inputData.getSize() - inputData.getStreamPosition();
int length;
byte[] buffer = new byte[1 << 16];
while ((length = dataIn.read(buffer)) > 0) {
- Log.d(Constants.TAG, "read bytes: " + length);
- if (mOutStream != null) {
- mOutStream.write(buffer, 0, length);
+ // Log.d(Constants.TAG, "read bytes: " + length);
+ if (out != null) {
+ out.write(buffer, 0, length);
}
// update signature buffer if signature is also present
@@ -795,6 +810,12 @@ public class PgpDecryptVerify extends BaseOperation {
// TODO: slow annealing to fake a progress?
}
+ metadata = new OpenPgpMetadata(
+ originalFilename,
+ mimeType,
+ literalData.getModificationTime().getTime(),
+ alreadyWritten);
+
if (signature != null) {
updateProgress(R.string.progress_verifying_signature, 90, 100);
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
@@ -802,11 +823,9 @@ public class PgpDecryptVerify extends BaseOperation {
PGPSignatureList signatureList = (PGPSignatureList) plainFact.nextObject();
PGPSignature messageSignature = signatureList.get(signatureIndex);
- // these are not cleartext signatures!
// TODO: what about binary signatures?
- signatureResultBuilder.setSignatureOnly(false);
- // Verify signature and check binding signatures
+ // Verify signature
boolean validSignature = signature.verify(messageSignature);
if (validSignature) {
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_OK, indent + 1);
@@ -814,10 +833,10 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
}
- // Don't allow verification of old hash algorithms!
- if (!PgpConstants.sPreferredHashAlgorithms.contains(signature.getHashAlgorithm())) {
- validSignature = false;
- log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1);
+ // check for insecure hash algorithms
+ if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
+ log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
+ signatureResultBuilder.setInsecure(true);
}
signatureResultBuilder.setValidSignature(validSignature);
@@ -844,8 +863,8 @@ public class PgpDecryptVerify extends BaseOperation {
// The MDC packet can be stripped by an attacker!
Log.d(Constants.TAG, "MDC fail");
if (!signatureResultBuilder.isValidSignature()) {
- log.add(LogType.MSG_DC_ERROR_INTEGRITY_MISSING, indent);
- return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
+ log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent);
+ decryptionResultBuilder.setInsecure(true);
}
}
@@ -854,11 +873,12 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_OK, indent);
// Return a positive result, with metadata and verification info
- DecryptVerifyResult result =
- new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
- result.setDecryptMetadata(metadata);
+ DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
+ result.setCachedCryptoInputParcel(cryptoInput);
result.setSignatureResult(signatureResultBuilder.build());
result.setCharset(charset);
+ result.setDecryptionResult(decryptionResultBuilder.build());
+ result.setDecryptionMetadata(metadata);
return result;
}
@@ -870,14 +890,13 @@ public class PgpDecryptVerify extends BaseOperation {
* The method is heavily based on
* pg/src/main/java/org/spongycastle/openpgp/examples/ClearSignedFileProcessor.java
*/
+ @NonNull
private DecryptVerifyResult verifyCleartextSignature(
- ArmoredInputStream aIn, int indent) throws IOException, PGPException {
+ ArmoredInputStream aIn, OutputStream outputStream, int indent) throws IOException, PGPException {
OperationLog log = new OperationLog();
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
- // cleartext signatures are never encrypted ;)
- signatureResultBuilder.setSignatureOnly(true);
ByteArrayOutputStream out = new ByteArrayOutputStream();
@@ -901,8 +920,9 @@ public class PgpDecryptVerify extends BaseOperation {
out.close();
byte[] clearText = out.toByteArray();
- if (mOutStream != null) {
- mOutStream.write(clearText);
+ if (outputStream != null) {
+ outputStream.write(clearText);
+ outputStream.close();
}
updateProgress(R.string.progress_processing_signature, 60, 100);
@@ -910,11 +930,11 @@ public class PgpDecryptVerify extends BaseOperation {
PGPSignatureList sigList = (PGPSignatureList) pgpFact.nextObject();
if (sigList == null) {
- log.add(LogType.MSG_DC_ERROR_INVALID_SIGLIST, 0);
+ log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
- PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder);
+ PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent);
if (signature != null) {
try {
@@ -946,10 +966,10 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
}
- // Don't allow verification of old hash algorithms!
- if (!PgpConstants.sPreferredHashAlgorithms.contains(signature.getHashAlgorithm())) {
- validSignature = false;
- log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1);
+ // check for insecure hash algorithms
+ if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
+ log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
+ signatureResultBuilder.setInsecure(true);
}
signatureResultBuilder.setValidSignature(validSignature);
@@ -964,22 +984,31 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_OK, indent);
+ OpenPgpMetadata metadata = new OpenPgpMetadata(
+ "",
+ "text/plain",
+ -1,
+ clearText.length);
+
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setSignatureResult(signatureResultBuilder.build());
+ result.setDecryptionResult(
+ new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
+ result.setDecryptionMetadata(metadata);
return result;
}
- private DecryptVerifyResult verifyDetachedSignature(InputStream in, int indent)
+ @NonNull
+ private DecryptVerifyResult verifyDetachedSignature(
+ PgpDecryptVerifyInputParcel input, InputData inputData, OutputStream out, int indent)
throws IOException, PGPException {
OperationLog log = new OperationLog();
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
- // detached signatures are never encrypted
- signatureResultBuilder.setSignatureOnly(true);
updateProgress(R.string.progress_processing_signature, 0, 100);
- InputStream detachedSigIn = new ByteArrayInputStream(mDetachedSignature);
+ InputStream detachedSigIn = new ByteArrayInputStream(input.getDetachedSignature());
detachedSigIn = PGPUtil.getDecoderStream(detachedSigIn);
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(detachedSigIn);
@@ -993,23 +1022,24 @@ public class PgpDecryptVerify extends BaseOperation {
} else if (o instanceof PGPSignatureList) {
sigList = (PGPSignatureList) o;
} else {
- log.add(LogType.MSG_DC_ERROR_INVALID_SIGLIST, 0);
+ log.add(LogType.MSG_DC_ERROR_INVALID_DATA, 0);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
- PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder);
+ PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent);
if (signature != null) {
updateProgress(R.string.progress_reading_data, 60, 100);
ProgressScaler progressScaler = new ProgressScaler(mProgressable, 60, 90, 100);
long alreadyWritten = 0;
- long wholeSize = mData.getSize() - mData.getStreamPosition();
+ long wholeSize = inputData.getSize() - inputData.getStreamPosition();
int length;
byte[] buffer = new byte[1 << 16];
+ InputStream in = inputData.getInputStream();
while ((length = in.read(buffer)) > 0) {
- if (mOutStream != null) {
- mOutStream.write(buffer, 0, length);
+ if (out != null) {
+ out.write(buffer, 0, length);
}
// update signature buffer if signature is also present
@@ -1030,9 +1060,6 @@ public class PgpDecryptVerify extends BaseOperation {
updateProgress(R.string.progress_verifying_signature, 90, 100);
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_CHECK, indent);
- // these are not cleartext signatures!
- signatureResultBuilder.setSignatureOnly(false);
-
// Verify signature and check binding signatures
boolean validSignature = signature.verify();
if (validSignature) {
@@ -1041,10 +1068,10 @@ public class PgpDecryptVerify extends BaseOperation {
log.add(LogType.MSG_DC_CLEAR_SIGNATURE_BAD, indent + 1);
}
- // Don't allow verification of old hash algorithms!
- if (!PgpConstants.sPreferredHashAlgorithms.contains(signature.getHashAlgorithm())) {
- validSignature = false;
- log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1);
+ // check for insecure hash algorithms
+ if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
+ log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
+ signatureResultBuilder.setInsecure(true);
}
signatureResultBuilder.setValidSignature(validSignature);
@@ -1056,10 +1083,15 @@ public class PgpDecryptVerify extends BaseOperation {
DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log);
result.setSignatureResult(signatureResultBuilder.build());
+ result.setDecryptionResult(
+ new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED));
return result;
}
- private PGPSignature processPGPSignatureList(PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder) throws PGPException {
+ private PGPSignature processPGPSignatureList(
+ PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder,
+ OperationLog log, int indent)
+ throws PGPException {
CanonicalizedPublicKeyRing signingRing = null;
CanonicalizedPublicKey signingKey = null;
int signatureIndex = -1;
@@ -1100,6 +1132,13 @@ public class PgpDecryptVerify extends BaseOperation {
}
}
+ // check for insecure signing key
+ // TODO: checks on signingRing ?
+ if (signingKey != null && ! PgpSecurityConstants.isSecureKey(signingKey)) {
+ log.add(LogType.MSG_DC_INSECURE_KEY, indent + 1);
+ signatureResultBuilder.setInsecure(true);
+ }
+
return signature;
}
@@ -1195,12 +1234,6 @@ public class PgpDecryptVerify extends BaseOperation {
private static byte[] getLineSeparator() {
String nl = System.getProperty("line.separator");
- byte[] nlBytes = new byte[nl.length()];
-
- for (int i = 0; i != nlBytes.length; i++) {
- nlBytes[i] = (byte) nl.charAt(i);
- }
-
- return nlBytes;
+ return nl.getBytes();
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
index d8b86a18c..e8d1d3111 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpHelper.java
@@ -21,6 +21,8 @@ package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
+import android.support.annotation.NonNull;
+import android.text.TextUtils;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
@@ -31,6 +33,7 @@ import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.security.SecureRandom;
+import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PgpHelper {
@@ -52,9 +55,6 @@ public class PgpHelper {
* <p/>
* TODO: Does this really help on flash storage?
*
- * @param context
- * @param progressable
- * @param file
* @throws IOException
*/
public static void deleteFileSecurely(Context context, Progressable progressable, File file)
@@ -78,4 +78,67 @@ public class PgpHelper {
raf.close();
file.delete();
}
+
+ /**
+ * Fixing broken PGP MESSAGE Strings coming from GMail/AOSP Mail
+ */
+ public static String fixPgpMessage(String message) {
+ // windows newline -> unix newline
+ message = message.replaceAll("\r\n", "\n");
+ // Mac OS before X newline -> unix newline
+ message = message.replaceAll("\r", "\n");
+
+ // remove whitespaces before newline
+ message = message.replaceAll(" +\n", "\n");
+ // only two consecutive newlines are allowed
+ message = message.replaceAll("\n\n+", "\n\n");
+
+ // replace non breakable spaces
+ message = message.replaceAll("\\xa0", " ");
+
+ return message;
+ }
+
+ /**
+ * Fixing broken PGP SIGNED MESSAGE Strings coming from GMail/AOSP Mail
+ */
+ public static String fixPgpCleartextSignature(CharSequence input) {
+ if (!TextUtils.isEmpty(input)) {
+ String text = input.toString();
+
+ // windows newline -> unix newline
+ text = text.replaceAll("\r\n", "\n");
+ // Mac OS before X newline -> unix newline
+ text = text.replaceAll("\r", "\n");
+
+ return text;
+ } else {
+ return null;
+ }
+ }
+
+ public static String getPgpContent(@NonNull CharSequence input) {
+ Log.dEscaped(Constants.TAG, "input: " + input);
+
+ Matcher matcher = PgpHelper.PGP_MESSAGE.matcher(input);
+ if (matcher.matches()) {
+ String text = matcher.group(1);
+ text = fixPgpMessage(text);
+
+ Log.dEscaped(Constants.TAG, "input fixed: " + text);
+ return text;
+ } else {
+ matcher = PgpHelper.PGP_CLEARTEXT_SIGNATURE.matcher(input);
+ if (matcher.matches()) {
+ String text = matcher.group(1);
+ text = fixPgpCleartextSignature(text);
+
+ Log.dEscaped(Constants.TAG, "input fixed: " + text);
+ return text;
+ } else {
+ return null;
+ }
+ }
+ }
+
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
index 89db378a9..6f156c201 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java
@@ -18,9 +18,11 @@
package org.sufficientlysecure.keychain.pgp;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
import org.spongycastle.bcpg.S2K;
import org.spongycastle.bcpg.sig.Features;
import org.spongycastle.bcpg.sig.KeyFlags;
+import org.spongycastle.bcpg.sig.RevocationReasonTags;
import org.spongycastle.jce.spec.ElGamalParameterSpec;
import org.spongycastle.openpgp.PGPException;
import org.spongycastle.openpgp.PGPKeyFlags;
@@ -45,8 +47,10 @@ import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder.NfcInteractionNeeded;
+import org.spongycastle.util.encoders.Hex;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
+import org.sufficientlysecure.keychain.operations.results.EditKeyResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
@@ -59,6 +63,7 @@ import org.sufficientlysecure.keychain.service.SaveKeyringParcel.SubkeyAdd;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel;
import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcSignOperationsBuilder;
+import org.sufficientlysecure.keychain.service.input.RequiredInputParcel.NfcKeyToCardOperationsBuilder;
import org.sufficientlysecure.keychain.ui.util.KeyFormattingUtils;
import org.sufficientlysecure.keychain.util.IterableIterator;
import org.sufficientlysecure.keychain.util.Log;
@@ -68,6 +73,7 @@ import org.sufficientlysecure.keychain.util.ProgressScaler;
import java.io.IOException;
import java.math.BigInteger;
+import java.nio.ByteBuffer;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
@@ -151,7 +157,7 @@ public class PgpKeyOperation {
}
/** Creates new secret key. */
- private PGPKeyPair createKey(SubkeyAdd add, OperationLog log, int indent) {
+ private PGPKeyPair createKey(SubkeyAdd add, Date creationTime, OperationLog log, int indent) {
try {
// Some safety checks
@@ -249,7 +255,7 @@ public class PgpKeyOperation {
}
// build new key pair
- return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), new Date());
+ return new JcaPGPKeyPair(algorithm, keyGen.generateKeyPair(), creationTime);
} catch(NoSuchProviderException | InvalidAlgorithmParameterException e) {
throw new RuntimeException(e);
@@ -295,8 +301,10 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
+ Date creationTime = new Date();
+
subProgressPush(10, 30);
- PGPKeyPair keyPair = createKey(add, log, indent);
+ PGPKeyPair keyPair = createKey(add, creationTime, log, indent);
subProgressPop();
// return null if this failed (an error will already have been logged by createKey)
@@ -308,14 +316,14 @@ public class PgpKeyOperation {
// Build key encrypter and decrypter based on passphrase
PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder()
- .build().get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
+ .build().get(PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
- PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO,
- encryptorHashCalc, PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
+ PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO,
+ encryptorHashCalc, PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build("".toCharArray());
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
- .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
+ .build().get(PgpSecurityConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
PGPSecretKey masterSecretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(),
sha1Calc, true, keyEncryptor);
@@ -323,8 +331,8 @@ public class PgpKeyOperation {
masterSecretKey.getEncoded(), new JcaKeyFingerprintCalculator());
subProgressPush(50, 100);
- CryptoInputParcel cryptoInput = new CryptoInputParcel(new Date(), new Passphrase(""));
- return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log);
+ CryptoInputParcel cryptoInput = new CryptoInputParcel(creationTime, new Passphrase(""));
+ return internal(sKR, masterSecretKey, add.mFlags, add.mExpiry, cryptoInput, saveParcel, log, indent);
} catch (PGPException e) {
log.add(LogType.MSG_CR_ERROR_INTERNAL_PGP, indent);
@@ -356,8 +364,8 @@ public class PgpKeyOperation {
*
*/
public PgpEditKeyResult modifySecretKeyRing(CanonicalizedSecretKeyRing wsKR,
- CryptoInputParcel cryptoInput,
- SaveKeyringParcel saveParcel) {
+ CryptoInputParcel cryptoInput,
+ SaveKeyringParcel saveParcel) {
OperationLog log = new OperationLog();
int indent = 0;
@@ -400,9 +408,61 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
+ // Ensure we don't have multiple keys for the same slot.
+ boolean hasSign = false;
+ boolean hasEncrypt = false;
+ boolean hasAuth = false;
+ for(SaveKeyringParcel.SubkeyChange change : saveParcel.mChangeSubKeys) {
+ if (change.mMoveKeyToCard) {
+ // If this is a keytocard operation, see if it was completed: look for a hash
+ // matching the given subkey ID in cryptoData.
+ byte[] subKeyId = new byte[8];
+ ByteBuffer buf = ByteBuffer.wrap(subKeyId);
+ buf.putLong(change.mKeyId).rewind();
+
+ byte[] serialNumber = cryptoInput.getCryptoData().get(buf);
+ if (serialNumber != null) {
+ change.mMoveKeyToCard = false;
+ change.mDummyDivert = serialNumber;
+ }
+ }
+
+ if (change.mMoveKeyToCard) {
+ // Pending keytocard operation. Need to make sure that we don't have multiple
+ // subkeys pending for the same slot.
+ CanonicalizedSecretKey wsK = wsKR.getSecretKey(change.mKeyId);
+
+ if ((wsK.canSign() || wsK.canCertify())) {
+ if (hasSign) {
+ log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ } else {
+ hasSign = true;
+ }
+ } else if ((wsK.canEncrypt())) {
+ if (hasEncrypt) {
+ log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ } else {
+ hasEncrypt = true;
+ }
+ } else if ((wsK.canAuthenticate())) {
+ if (hasAuth) {
+ log.add(LogType.MSG_MF_ERROR_DUPLICATE_KEYTOCARD_FOR_SLOT, indent + 1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ } else {
+ hasAuth = true;
+ }
+ } else {
+ log.add(LogType.MSG_MF_ERROR_INVALID_FLAGS_FOR_KEYTOCARD, indent + 1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+ }
+ }
+
if (isDummy(masterSecretKey) || saveParcel.isRestrictedOnly()) {
log.add(LogType.MSG_MF_RESTRICTED_MODE, indent);
- return internalRestricted(sKR, saveParcel, log);
+ return internalRestricted(sKR, saveParcel, log, indent + 1);
}
// Do we require a passphrase? If so, pass it along
@@ -410,7 +470,7 @@ public class PgpKeyOperation {
log.add(LogType.MSG_MF_REQUIRE_PASSPHRASE, indent);
return new PgpEditKeyResult(log, RequiredInputParcel.createRequiredSignPassphrase(
masterSecretKey.getKeyID(), masterSecretKey.getKeyID(),
- cryptoInput.getSignatureTime()));
+ cryptoInput.getSignatureTime()), cryptoInput);
}
// read masterKeyFlags, and use the same as before.
@@ -420,7 +480,7 @@ public class PgpKeyOperation {
Date expiryTime = wsKR.getPublicKey().getExpiryTime();
long masterKeyExpiry = expiryTime != null ? expiryTime.getTime() / 1000 : 0L;
- return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, cryptoInput, saveParcel, log);
+ return internal(sKR, masterSecretKey, masterKeyFlags, masterKeyExpiry, cryptoInput, saveParcel, log, indent);
}
@@ -428,13 +488,14 @@ public class PgpKeyOperation {
int masterKeyFlags, long masterKeyExpiry,
CryptoInputParcel cryptoInput,
SaveKeyringParcel saveParcel,
- OperationLog log) {
-
- int indent = 1;
+ OperationLog log,
+ int indent) {
NfcSignOperationsBuilder nfcSignOps = new NfcSignOperationsBuilder(
cryptoInput.getSignatureTime(), masterSecretKey.getKeyID(),
masterSecretKey.getKeyID());
+ NfcKeyToCardOperationsBuilder nfcKeyToCardOps = new NfcKeyToCardOperationsBuilder(
+ masterSecretKey.getKeyID());
progress(R.string.progress_modify, 0);
@@ -553,7 +614,8 @@ public class PgpKeyOperation {
PGPSignature cert = generateUserAttributeSignature(
getSignatureGenerator(masterSecretKey, cryptoInput),
cryptoInput.getSignatureTime(),
- masterPrivateKey, masterPublicKey, vector);
+ masterPrivateKey, masterPublicKey, vector,
+ masterKeyFlags, masterKeyExpiry);
modifiedPublicKey = PGPPublicKey.addCertification(modifiedPublicKey, vector, cert);
} catch (NfcInteractionNeeded e) {
nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
@@ -743,22 +805,36 @@ public class PgpKeyOperation {
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
- if (change.mDummyStrip || change.mDummyDivert != null) {
+ if (change.mDummyStrip) {
// IT'S DANGEROUS~
// no really, it is. this operation irrevocably removes the private key data from the key
- if (change.mDummyStrip) {
- sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
+ sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey());
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
+ } else if (change.mMoveKeyToCard) {
+ if (checkSmartCardCompatibility(sKey, log, indent + 1)) {
+ log.add(LogType.MSG_MF_KEYTOCARD_START, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ nfcKeyToCardOps.addSubkey(change.mKeyId);
} else {
- // the serial number must be 16 bytes in length
- if (change.mDummyDivert.length != 16) {
- log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
- indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
- return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
- }
+ // Appropriate log message already set by checkSmartCardCompatibility
+ return new PgpEditKeyResult(EditKeyResult.RESULT_ERROR, log, null);
}
+ } else if (change.mDummyDivert != null) {
+ // NOTE: Does this code get executed? Or always handled in internalRestricted?
+ if (change.mDummyDivert.length != 16) {
+ log.add(LogType.MSG_MF_ERROR_DIVERT_SERIAL,
+ indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+ log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(change.mKeyId),
+ Hex.toHexString(change.mDummyDivert, 8, 6));
+ sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
}
+
+
// This doesn't concern us any further
if (!change.mRecertify && (change.mExpiry == null && change.mFlags == null)) {
continue;
@@ -822,18 +898,35 @@ public class PgpKeyOperation {
pKey = PGPPublicKey.removeCertification(pKey, sig);
}
- PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
- cryptoInput.getPassphrase().getCharArray());
- PGPPrivateKey subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
- PGPSignature sig = generateSubkeyBindingSignature(
- getSignatureGenerator(masterSecretKey, cryptoInput),
- cryptoInput.getSignatureTime(),
- masterPublicKey, masterPrivateKey, subPrivateKey, pKey, flags, expiry);
+ PGPPrivateKey subPrivateKey;
+ if (!isDivertToCard(sKey)) {
+ PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder()
+ .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
+ cryptoInput.getPassphrase().getCharArray());
+ subPrivateKey = sKey.extractPrivateKey(keyDecryptor);
+ // super special case: subkey is allowed to sign, but isn't available
+ if (subPrivateKey == null) {
+ log.add(LogType.MSG_MF_ERROR_SUB_STRIPPED,
+ indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+ } else {
+ subPrivateKey = null;
+ }
+ try {
+ PGPSignature sig = generateSubkeyBindingSignature(
+ getSignatureGenerator(masterSecretKey, cryptoInput),
+ cryptoInput.getSignatureTime(), masterPublicKey, masterPrivateKey,
+ getSignatureGenerator(sKey, cryptoInput), subPrivateKey,
+ pKey, flags, expiry);
+
+ // generate and add new signature
+ pKey = PGPPublicKey.addCertification(pKey, sig);
+ sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
+ } catch (NfcInteractionNeeded e) {
+ nfcSignOps.addHash(e.hashToSign, e.hashAlgo);
+ }
- // generate and add new signature
- pKey = PGPPublicKey.addCertification(pKey, sig);
- sKR = PGPSecretKeyRing.insertSecretKey(sKR, PGPSecretKey.replacePublicKey(sKey, pKey));
}
subProgressPop();
@@ -884,6 +977,11 @@ public class PgpKeyOperation {
log.add(LogType.MSG_MF_SUBKEY_NEW, indent,
KeyFormattingUtils.getAlgorithmInfo(add.mAlgorithm, add.mKeySize, add.mCurve) );
+ if (isDivertToCard(masterSecretKey)) {
+ log.add(LogType.MSG_MF_ERROR_DIVERT_NEWSUB, indent +1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
if (add.mExpiry == null) {
log.add(LogType.MSG_MF_ERROR_NULL_EXPIRY, indent +1);
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
@@ -899,7 +997,7 @@ public class PgpKeyOperation {
(i-1) * (100 / saveParcel.mAddSubKeys.size()),
i * (100 / saveParcel.mAddSubKeys.size())
);
- PGPKeyPair keyPair = createKey(add, log, indent);
+ PGPKeyPair keyPair = createKey(add, cryptoInput.getSignatureTime(), log, indent);
subProgressPop();
if (keyPair == null) {
log.add(LogType.MSG_MF_ERROR_PGP, indent +1);
@@ -912,7 +1010,8 @@ public class PgpKeyOperation {
PGPSignature cert = generateSubkeyBindingSignature(
getSignatureGenerator(masterSecretKey, cryptoInput),
cryptoInput.getSignatureTime(),
- masterPublicKey, masterPrivateKey, keyPair.getPrivateKey(), pKey,
+ masterPublicKey, masterPrivateKey,
+ getSignatureGenerator(pKey, cryptoInput, false), keyPair.getPrivateKey(), pKey,
add.mFlags, add.mExpiry);
pKey = PGPPublicKey.addSubkeyBindingCertification(pKey, cert);
} catch (NfcInteractionNeeded e) {
@@ -922,15 +1021,15 @@ public class PgpKeyOperation {
PGPSecretKey sKey; {
// Build key encrypter and decrypter based on passphrase
PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder()
- .build().get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
+ .build().get(PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
PBESecretKeyEncryptor keyEncryptor = new JcePBESecretKeyEncryptorBuilder(
- PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
- PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
+ PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
+ PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(
cryptoInput.getPassphrase().getCharArray());
PGPDigestCalculator sha1Calc = new JcaPGPDigestCalculatorProviderBuilder()
- .build().get(PgpConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
+ .build().get(PgpSecurityConstants.SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO);
sKey = new PGPSecretKey(keyPair.getPrivateKey(), pKey, sha1Calc, false, keyEncryptor);
}
@@ -964,6 +1063,26 @@ public class PgpKeyOperation {
indent -= 1;
}
+ // 7. if requested, change PIN and/or Admin PIN on card
+ if (saveParcel.mCardPin != null) {
+ progress(R.string.progress_modify_pin, 90);
+ log.add(LogType.MSG_MF_PIN, indent);
+ indent += 1;
+
+ nfcKeyToCardOps.setPin(saveParcel.mCardPin);
+
+ indent -= 1;
+ }
+ if (saveParcel.mCardAdminPin != null) {
+ progress(R.string.progress_modify_admin_pin, 90);
+ log.add(LogType.MSG_MF_ADMIN_PIN, indent);
+ indent += 1;
+
+ nfcKeyToCardOps.setAdminPin(saveParcel.mCardAdminPin);
+
+ indent -= 1;
+ }
+
} catch (IOException e) {
Log.e(Constants.TAG, "encountered IOException while modifying key", e);
log.add(LogType.MSG_MF_ERROR_ENCODE, indent+1);
@@ -980,9 +1099,19 @@ public class PgpKeyOperation {
progress(R.string.progress_done, 100);
+ if (!nfcSignOps.isEmpty() && !nfcKeyToCardOps.isEmpty()) {
+ log.add(LogType.MSG_MF_ERROR_CONFLICTING_NFC_COMMANDS, indent+1);
+ return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
+ }
+
if (!nfcSignOps.isEmpty()) {
log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
- return new PgpEditKeyResult(log, nfcSignOps.build());
+ return new PgpEditKeyResult(log, nfcSignOps.build(), cryptoInput);
+ }
+
+ if (!nfcKeyToCardOps.isEmpty()) {
+ log.add(LogType.MSG_MF_REQUIRE_DIVERT, indent);
+ return new PgpEditKeyResult(log, nfcKeyToCardOps.build(), cryptoInput);
}
log.add(LogType.MSG_MF_SUCCESS, indent);
@@ -995,9 +1124,7 @@ public class PgpKeyOperation {
* otherwise.
*/
private PgpEditKeyResult internalRestricted(PGPSecretKeyRing sKR, SaveKeyringParcel saveParcel,
- OperationLog log) {
-
- int indent = 1;
+ OperationLog log, int indent) {
progress(R.string.progress_modify, 0);
@@ -1042,6 +1169,9 @@ public class PgpKeyOperation {
indent + 1, KeyFormattingUtils.convertKeyIdToHex(change.mKeyId));
return new PgpEditKeyResult(PgpEditKeyResult.RESULT_ERROR, log, null);
}
+ log.add(LogType.MSG_MF_KEYTOCARD_FINISH, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(change.mKeyId),
+ Hex.toHexString(change.mDummyDivert, 8, 6));
sKey = PGPSecretKey.constructGnuDummyKey(sKey.getPublicKey(), change.mDummyDivert);
}
sKR = PGPSecretKeyRing.insertSecretKey(sKR, sKey);
@@ -1076,7 +1206,7 @@ public class PgpKeyOperation {
// add packet with EMPTY notation data (updates old one, but will be stripped later)
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
+ PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
{ // set subpackets
@@ -1103,7 +1233,7 @@ public class PgpKeyOperation {
// add packet with "pin" notation data
PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
masterPrivateKey.getPublicKeyPacket().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
+ PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
PGPSignatureGenerator sGen = new PGPSignatureGenerator(signerBuilder);
{ // set subpackets
@@ -1150,13 +1280,13 @@ public class PgpKeyOperation {
OperationLog log, int indent) throws PGPException {
PGPDigestCalculator encryptorHashCalc = new JcaPGPDigestCalculatorProviderBuilder().build()
- .get(PgpConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
+ .get(PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_HASH_ALGO);
PBESecretKeyDecryptor keyDecryptor = new JcePBESecretKeyDecryptorBuilder().setProvider(
Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(passphrase.getCharArray());
// Build key encryptor based on new passphrase
PBESecretKeyEncryptor keyEncryptorNew = new JcePBESecretKeyEncryptorBuilder(
- PgpConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
- PgpConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
+ PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO, encryptorHashCalc,
+ PgpSecurityConstants.SECRET_KEY_ENCRYPTOR_S2K_COUNT)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME).build(newPassphrase.getCharArray());
// noinspection unchecked
@@ -1296,22 +1426,27 @@ public class PgpKeyOperation {
static PGPSignatureGenerator getSignatureGenerator(
PGPSecretKey secretKey, CryptoInputParcel cryptoInput) {
- PGPContentSignerBuilder builder;
-
S2K s2k = secretKey.getS2K();
- if (s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
- && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD) {
+ boolean isDivertToCard = s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
+ && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
+
+ return getSignatureGenerator(secretKey.getPublicKey(), cryptoInput, isDivertToCard);
+ }
+
+ static PGPSignatureGenerator getSignatureGenerator(
+ PGPPublicKey pKey, CryptoInputParcel cryptoInput, boolean divertToCard) {
+
+ PGPContentSignerBuilder builder;
+ if (divertToCard) {
// use synchronous "NFC based" SignerBuilder
builder = new NfcSyncPGPContentSignerBuilder(
- secretKey.getPublicKey().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO,
- secretKey.getKeyID(), cryptoInput.getCryptoData())
+ pKey.getAlgorithm(), PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO,
+ pKey.getKeyID(), cryptoInput.getCryptoData())
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
} else {
// content signer based on signing key algorithm and chosen hash algorithm
builder = new JcaPGPContentSignerBuilder(
- secretKey.getPublicKey().getAlgorithm(),
- PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
+ pKey.getAlgorithm(), PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
}
@@ -1319,11 +1454,9 @@ public class PgpKeyOperation {
}
- private PGPSignature generateUserIdSignature(
- PGPSignatureGenerator sGen, Date creationTime,
- PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary,
- int flags, long expiry)
- throws IOException, PGPException, SignatureException {
+ private static PGPSignatureSubpacketGenerator generateHashedSelfSigSubpackets(
+ Date creationTime, PGPPublicKey pKey, boolean primary, int flags, long expiry
+ ) {
PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
{
@@ -1339,11 +1472,11 @@ public class PgpKeyOperation {
*/
/* non-critical subpackets: */
hashedPacketsGen.setPreferredSymmetricAlgorithms(false,
- PgpConstants.getAsArray(PgpConstants.sPreferredSymmetricAlgorithms));
+ PgpSecurityConstants.PREFERRED_SYMMETRIC_ALGORITHMS);
hashedPacketsGen.setPreferredHashAlgorithms(false,
- PgpConstants.getAsArray(PgpConstants.sPreferredHashAlgorithms));
+ PgpSecurityConstants.PREFERRED_HASH_ALGORITHMS);
hashedPacketsGen.setPreferredCompressionAlgorithms(false,
- PgpConstants.getAsArray(PgpConstants.sPreferredCompressionAlgorithms));
+ PgpSecurityConstants.PREFERRED_COMPRESSION_ALGORITHMS);
hashedPacketsGen.setPrimaryUserID(false, primary);
/* critical subpackets: we consider those important for a modern pgp implementation */
@@ -1357,6 +1490,17 @@ public class PgpKeyOperation {
}
}
+ return hashedPacketsGen;
+ }
+
+ private static PGPSignature generateUserIdSignature(
+ PGPSignatureGenerator sGen, Date creationTime,
+ PGPPrivateKey masterPrivateKey, PGPPublicKey pKey, String userId, boolean primary,
+ int flags, long expiry)
+ throws IOException, PGPException, SignatureException {
+
+ PGPSignatureSubpacketGenerator hashedPacketsGen =
+ generateHashedSelfSigSubpackets(creationTime, pKey, primary, flags, expiry);
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
return sGen.generateCertification(userId, pKey);
@@ -1365,15 +1509,12 @@ public class PgpKeyOperation {
private static PGPSignature generateUserAttributeSignature(
PGPSignatureGenerator sGen, Date creationTime,
PGPPrivateKey masterPrivateKey, PGPPublicKey pKey,
- PGPUserAttributeSubpacketVector vector)
+ PGPUserAttributeSubpacketVector vector,
+ int flags, long expiry)
throws IOException, PGPException, SignatureException {
- PGPSignatureSubpacketGenerator hashedPacketsGen = new PGPSignatureSubpacketGenerator();
- {
- /* critical subpackets: we consider those important for a modern pgp implementation */
- hashedPacketsGen.setSignatureCreationTime(true, creationTime);
- }
-
+ PGPSignatureSubpacketGenerator hashedPacketsGen =
+ generateHashedSelfSigSubpackets(creationTime, pKey, false, flags, expiry);
sGen.setHashedSubpackets(hashedPacketsGen.generate());
sGen.init(PGPSignature.POSITIVE_CERTIFICATION, masterPrivateKey);
return sGen.generateCertification(vector, pKey);
@@ -1385,6 +1526,9 @@ public class PgpKeyOperation {
throws IOException, PGPException, SignatureException {
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ // we use the tag NO_REASON since gnupg does not care about the tag while verifying
+ // signatures with a revoked key, the warning is the same
+ subHashedPacketsGen.setRevocationReason(true, RevocationReasonTags.NO_REASON, "");
subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
sGen.init(PGPSignature.CERTIFICATION_REVOCATION, masterPrivateKey);
@@ -1397,6 +1541,9 @@ public class PgpKeyOperation {
throws IOException, PGPException, SignatureException {
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
+ // we use the tag NO_REASON since gnupg does not care about the tag while verifying
+ // signatures with a revoked key, the warning is the same
+ subHashedPacketsGen.setRevocationReason(true, RevocationReasonTags.NO_REASON, "");
subHashedPacketsGen.setSignatureCreationTime(true, creationTime);
sGen.setHashedSubpackets(subHashedPacketsGen.generate());
// Generate key revocation or subkey revocation, depending on master/subkey-ness
@@ -1412,7 +1559,8 @@ public class PgpKeyOperation {
static PGPSignature generateSubkeyBindingSignature(
PGPSignatureGenerator sGen, Date creationTime,
PGPPublicKey masterPublicKey, PGPPrivateKey masterPrivateKey,
- PGPPrivateKey subPrivateKey, PGPPublicKey pKey, int flags, long expiry)
+ PGPSignatureGenerator subSigGen, PGPPrivateKey subPrivateKey, PGPPublicKey pKey,
+ int flags, long expiry)
throws IOException, PGPException, SignatureException {
PGPSignatureSubpacketGenerator unhashedPacketsGen = new PGPSignatureSubpacketGenerator();
@@ -1422,10 +1570,6 @@ public class PgpKeyOperation {
// cross-certify signing keys
PGPSignatureSubpacketGenerator subHashedPacketsGen = new PGPSignatureSubpacketGenerator();
subHashedPacketsGen.setSignatureCreationTime(false, creationTime);
- PGPContentSignerBuilder signerBuilder = new JcaPGPContentSignerBuilder(
- pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO)
- .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME);
- PGPSignatureGenerator subSigGen = new PGPSignatureGenerator(signerBuilder);
subSigGen.init(PGPSignature.PRIMARYKEY_BINDING, subPrivateKey);
subSigGen.setHashedSubpackets(subHashedPacketsGen.generate());
PGPSignature certification = subSigGen.generateCertification(masterPublicKey, pKey);
@@ -1471,14 +1615,39 @@ public class PgpKeyOperation {
private static boolean isDummy(PGPSecretKey secretKey) {
S2K s2k = secretKey.getS2K();
- return s2k.getType() == S2K.GNU_DUMMY_S2K
- && s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_NO_PRIVATE_KEY;
+ return s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
+ && s2k.getProtectionMode() != S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
}
private static boolean isDivertToCard(PGPSecretKey secretKey) {
S2K s2k = secretKey.getS2K();
- return s2k.getType() == S2K.GNU_DUMMY_S2K
+ return s2k != null && s2k.getType() == S2K.GNU_DUMMY_S2K
&& s2k.getProtectionMode() == S2K.GNU_PROTECTION_MODE_DIVERT_TO_CARD;
}
+ private static boolean checkSmartCardCompatibility(PGPSecretKey key, OperationLog log, int indent) {
+ PGPPublicKey publicKey = key.getPublicKey();
+ int algorithm = publicKey.getAlgorithm();
+ if (algorithm != PublicKeyAlgorithmTags.RSA_ENCRYPT &&
+ algorithm != PublicKeyAlgorithmTags.RSA_SIGN &&
+ algorithm != PublicKeyAlgorithmTags.RSA_GENERAL) {
+ log.add(LogType.MSG_MF_ERROR_BAD_NFC_ALGO, indent + 1);
+ return false;
+ }
+
+ // Key size must be 2048
+ int keySize = publicKey.getBitStrength();
+ if (keySize != 2048) {
+ log.add(LogType.MSG_MF_ERROR_BAD_NFC_SIZE, indent + 1);
+ return false;
+ }
+
+ // Secret key parts must be available
+ if (isDivertToCard(key) || isDummy(key)) {
+ log.add(LogType.MSG_MF_ERROR_BAD_NFC_STRIPPED, indent + 1);
+ return false;
+ }
+
+ return true;
+ }
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java
new file mode 100644
index 000000000..3fa549946
--- /dev/null
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSecurityConstants.java
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2015 Dominik Schürmann <dominik@dominikschuermann.de>
+ *
+ * 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 org.spongycastle.asn1.nist.NISTNamedCurves;
+import org.spongycastle.bcpg.CompressionAlgorithmTags;
+import org.spongycastle.bcpg.HashAlgorithmTags;
+import org.spongycastle.bcpg.PublicKeyAlgorithmTags;
+import org.spongycastle.bcpg.SymmetricKeyAlgorithmTags;
+
+import java.util.HashSet;
+
+/**
+ * NIST requirements for 2011-2030 (http://www.keylength.com/en/4/):
+ * - RSA: 2048 bit
+ * - ECC: 224 bit
+ * - Symmetric: 3TDEA
+ * - Digital Signature (hash A): SHA-224 - SHA-512
+ *
+ * Extreme Decisions for Yahoo's End-to-End:
+ * https://github.com/yahoo/end-to-end/issues/31
+ * https://gist.github.com/coruus/68a8c65571e2b4225a69
+ */
+public class PgpSecurityConstants {
+
+ /**
+ * Whitelist of accepted symmetric encryption algorithms
+ * all other algorithms are rejected with OpenPgpDecryptionResult.RESULT_INSECURE
+ */
+ private static HashSet<Integer> sSymmetricAlgorithmsWhitelist = new HashSet<>();
+ static {
+ // General remarks: We try to keep the whitelist short to reduce attack surface
+ // TODO: block IDEA?: Bad key schedule (weak keys), implementation difficulties (easy to make errors)
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.IDEA);
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.TRIPLE_DES); // a MUST in RFC
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.CAST5); // default in many gpg, pgp versions, 128 bit key
+ // BLOWFISH: Twofish is the successor
+ // SAFER: not used widely
+ // DES: < 128 bit security
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_128);
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_192);
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_256);
+ sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.TWOFISH); // 128 bit
+ // CAMELLIA_128: not used widely
+ // CAMELLIA_192: not used widely
+ // CAMELLIA_256: not used widely
+ }
+
+ public static boolean isSecureSymmetricAlgorithm(int id) {
+ return sSymmetricAlgorithmsWhitelist.contains(id);
+ }
+
+ /**
+ * Whitelist of accepted hash algorithms
+ * all other algorithms are rejected with OpenPgpSignatureResult.RESULT_INSECURE
+ *
+ * coorus:
+ * Implementations SHOULD use SHA-512 for RSA or DSA signatures. They SHOULD NOT use SHA-384.
+ * ((cite to affine padding attacks; unproven status of RSA-PKCSv15))
+ *
+ * Implementations MUST NOT sign SHA-224 hashes. They SHOULD NOT accept signatures over SHA-224 hashes.
+ * ((collision resistance of 112-bits))
+ * Implementations SHOULD NOT sign SHA-256 hashes. They MUST NOT default to signing SHA-256 hashes.
+ */
+ private static HashSet<Integer> sHashAlgorithmsWhitelist = new HashSet<>();
+ static {
+ // MD5: broken
+ // SHA1: broken
+ // RIPEMD160: same security properties as SHA1
+ // DOUBLE_SHA: not used widely
+ // MD2: not used widely
+ // TIGER_192: not used widely
+ // HAVAL_5_160: not used widely
+ sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA256); // compatibility for old Mailvelope versions
+ sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA384);
+ sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA512);
+ // SHA224: Not used widely, Yahoo argues against it
+ }
+
+ public static boolean isSecureHashAlgorithm(int id) {
+ return sHashAlgorithmsWhitelist.contains(id);
+ }
+
+ /**
+ * Whitelist of accepted asymmetric algorithms in switch statement
+ * all other algorithms are rejected with OpenPgpSignatureResult.RESULT_INSECURE or
+ * OpenPgpDecryptionResult.RESULT_INSECURE
+ *
+ * coorus:
+ * Implementations MUST NOT accept, or treat any signature as valid, by an RSA key with
+ * bitlength less than 1023 bits.
+ * Implementations MUST NOT accept any RSA keys with bitlength less than 2047 bits after January 1, 2016.
+ */
+ private static HashSet<String> sCurveWhitelist = new HashSet<>();
+ static {
+ sCurveWhitelist.add(NISTNamedCurves.getOID("P-256").getId());
+ sCurveWhitelist.add(NISTNamedCurves.getOID("P-384").getId());
+ sCurveWhitelist.add(NISTNamedCurves.getOID("P-521").getId());
+ }
+
+ public static boolean isSecureKey(CanonicalizedPublicKey key) {
+ switch (key.getAlgorithm()) {
+ case PublicKeyAlgorithmTags.RSA_GENERAL: {
+ return (key.getBitStrength() >= 2048);
+ }
+ // RSA_ENCRYPT, RSA_SIGN: deprecated in RFC 4880, use RSA_GENERAL with key flags
+ case PublicKeyAlgorithmTags.ELGAMAL_ENCRYPT: {
+ return (key.getBitStrength() >= 2048);
+ }
+ case PublicKeyAlgorithmTags.DSA: {
+ return (key.getBitStrength() >= 2048);
+ }
+ case PublicKeyAlgorithmTags.ECDH:
+ case PublicKeyAlgorithmTags.ECDSA: {
+ return PgpSecurityConstants.sCurveWhitelist.contains(key.getCurveOid());
+ }
+ // ELGAMAL_GENERAL: deprecated in RFC 4880, use ELGAMAL_ENCRYPT
+ // DIFFIE_HELLMAN: unsure
+ default:
+ return false;
+ }
+ }
+
+ /**
+ * These array is written as a list of preferred encryption algorithms into keys created by us.
+ * Other implementations may choose to honor this selection.
+ * (Most preferred is first)
+ *
+ * REASON: See corresponding whitelist. AES received most cryptanalysis over the years
+ * and is still secure!
+ */
+ public static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
+ SymmetricKeyAlgorithmTags.AES_256,
+ SymmetricKeyAlgorithmTags.AES_192,
+ SymmetricKeyAlgorithmTags.AES_128,
+ };
+
+ /**
+ * These array is written as a list of preferred hash algorithms into keys created by us.
+ * Other implementations may choose to honor this selection.
+ * (Most preferred is first)
+ *
+ * REASON: See corresponding whitelist. If possible use SHA-512, this is state of the art!
+ */
+ public static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{
+ HashAlgorithmTags.SHA512,
+ };
+
+ /**
+ * These array is written as a list of preferred compression algorithms into keys created by us.
+ * Other implementations may choose to honor this selection.
+ * (Most preferred is first)
+ *
+ * REASON: See DEFAULT_COMPRESSION_ALGORITHM
+ */
+ public static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[]{
+ CompressionAlgorithmTags.ZIP,
+ };
+
+ /**
+ * Hash algorithm used to certify public keys
+ */
+ public static final int CERTIFY_HASH_ALGO = HashAlgorithmTags.SHA512;
+
+
+ /**
+ * Always use AES-256! We always ignore the preferred encryption algos of the recipient!
+ *
+ * coorus:
+ * Implementations SHOULD ignore the symmetric algorithm preferences of a recipient's public key;
+ * in particular, implementations MUST NOT choose an algorithm forbidden by this
+ * document because a recipient prefers it.
+ *
+ * NEEDCITE downgrade attacks on TLS, other protocols
+ */
+ public static final int DEFAULT_SYMMETRIC_ALGORITHM = SymmetricKeyAlgorithmTags.AES_256;
+
+ public interface OpenKeychainSymmetricKeyAlgorithmTags extends SymmetricKeyAlgorithmTags {
+ int USE_DEFAULT = -1;
+ }
+
+ /**
+ * Always use SHA-512! We always ignore the preferred hash algos of the recipient!
+ *
+ * coorus:
+ * Implementations MUST ignore the hash algorithm preferences of a recipient when signing
+ * a message to a recipient. The difficulty of forging a signature under a given key,
+ * using generic attacks on hash functions, is the difficulty of the weakest hash signed by that key.
+ *
+ * Implementations MUST default to using SHA-512 for RSA signatures,
+ *
+ * and either SHA-512 or the matched instance of SHA-2 for ECDSA signatures.
+ * TODO: Ed25519
+ * CITE: zooko's hash function table CITE: distinguishers on SHA-256
+ */
+ public static final int DEFAULT_HASH_ALGORITHM = HashAlgorithmTags.SHA512;
+
+ public interface OpenKeychainHashAlgorithmTags extends HashAlgorithmTags {
+ int USE_DEFAULT = -1;
+ }
+
+ /**
+ * Compression is disabled by default.
+ *
+ * The default compression algorithm is only used if explicitly enabled in the activity's
+ * overflow menu or via the OpenPGP API's extra OpenPgpApi.EXTRA_ENABLE_COMPRESSION
+ *
+ * REASON: Enabling compression can lead to a sidechannel. Consider a voting that is done via
+ * OpenPGP. Compression can lead to different ciphertext lengths based on the user's voting.
+ * This has happened in a voting done by Wikipedia (Google it).
+ *
+ * ZLIB: the format provides no benefits over DEFLATE, and is more malleable
+ * BZIP2: very slow
+ */
+ public static final int DEFAULT_COMPRESSION_ALGORITHM = CompressionAlgorithmTags.ZIP;
+
+ public interface OpenKeychainCompressionAlgorithmTags extends CompressionAlgorithmTags {
+ int USE_DEFAULT = -1;
+ }
+
+ /**
+ * Note: s2kcount is a number between 0 and 0xff that controls the
+ * number of times to iterate the password hash before use. More
+ * iterations are useful against offline attacks, as it takes more
+ * time to check each password. The actual number of iterations is
+ * rather complex, and also depends on the hash function in use.
+ * Refer to Section 3.7.1.3 in rfc4880.txt. Bigger numbers give
+ * you more iterations. As a rough rule of thumb, when using
+ * SHA256 as the hashing function, 0x10 gives you about 64
+ * iterations, 0x20 about 128, 0x30 about 256 and so on till 0xf0,
+ * or about 1 million iterations. The maximum you can go to is
+ * 0xff, or about 2 million iterations.
+ * from http://kbsriram.com/2013/01/generating-rsa-keys-with-bouncycastle.html
+ *
+ * Bouncy Castle default: 0x60
+ * kbsriram proposes: 0xc0
+ * Yahoo's End-to-End: 96=0x60 (65536 iterations) (https://github.com/yahoo/end-to-end/blob/master/src/javascript/crypto/e2e/openpgp/keyring.js)
+ */
+ public static final int SECRET_KEY_ENCRYPTOR_S2K_COUNT = 0x90;
+ public static final int SECRET_KEY_ENCRYPTOR_HASH_ALGO = HashAlgorithmTags.SHA512;
+ public static final int SECRET_KEY_ENCRYPTOR_SYMMETRIC_ALGO = SymmetricKeyAlgorithmTags.AES_256;
+ public static final int SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO = HashAlgorithmTags.SHA512;
+ // NOTE: only SHA1 is supported for key checksum calculations in OpenPGP,
+ // see http://tools.ietf.org/html/rfc488 0#section-5.5.3
+ public static final int SECRET_KEY_SIGNATURE_CHECKSUM_HASH_ALGO = HashAlgorithmTags.SHA1;
+
+}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
index fd3c4910c..36d1a07cb 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java
@@ -20,13 +20,8 @@ package org.sufficientlysecure.keychain.pgp;
import org.spongycastle.bcpg.CompressionAlgorithmTags;
import org.sufficientlysecure.keychain.Constants;
-import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
import org.sufficientlysecure.keychain.util.Passphrase;
-import java.nio.ByteBuffer;
-import java.util.Date;
-import java.util.Map;
-
import android.os.Parcel;
import android.os.Parcelable;
@@ -35,19 +30,20 @@ public class PgpSignEncryptInputParcel implements Parcelable {
protected String mVersionHeader = null;
protected boolean mEnableAsciiArmorOutput = false;
- protected int mCompressionId = CompressionAlgorithmTags.UNCOMPRESSED;
+ protected int mCompressionAlgorithm = CompressionAlgorithmTags.UNCOMPRESSED;
protected long[] mEncryptionMasterKeyIds = null;
protected Passphrase mSymmetricPassphrase = null;
- protected int mSymmetricEncryptionAlgorithm = PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED;
+ protected int mSymmetricEncryptionAlgorithm = PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT;
protected long mSignatureMasterKeyId = Constants.key.none;
protected Long mSignatureSubKeyId = null;
- protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED;
+ protected int mSignatureHashAlgorithm = PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT;
protected long mAdditionalEncryptId = Constants.key.none;
protected boolean mFailOnMissingEncryptionKeyIds = false;
protected String mCharset;
protected boolean mCleartextSignature;
protected boolean mDetachedSignature = false;
protected boolean mHiddenRecipients = false;
+ protected boolean mIntegrityProtected = true;
public PgpSignEncryptInputParcel() {
@@ -60,7 +56,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
// we do all of those here, so the PgpSignEncryptInput class doesn't have to be parcelable
mVersionHeader = source.readString();
mEnableAsciiArmorOutput = source.readInt() == 1;
- mCompressionId = source.readInt();
+ mCompressionAlgorithm = source.readInt();
mEncryptionMasterKeyIds = source.createLongArray();
mSymmetricPassphrase = source.readParcelable(loader);
mSymmetricEncryptionAlgorithm = source.readInt();
@@ -73,6 +69,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
mCleartextSignature = source.readInt() == 1;
mDetachedSignature = source.readInt() == 1;
mHiddenRecipients = source.readInt() == 1;
+ mIntegrityProtected = source.readInt() == 1;
}
@Override
@@ -84,7 +81,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(mVersionHeader);
dest.writeInt(mEnableAsciiArmorOutput ? 1 : 0);
- dest.writeInt(mCompressionId);
+ dest.writeInt(mCompressionAlgorithm);
dest.writeLongArray(mEncryptionMasterKeyIds);
dest.writeParcelable(mSymmetricPassphrase, 0);
dest.writeInt(mSymmetricEncryptionAlgorithm);
@@ -102,6 +99,7 @@ public class PgpSignEncryptInputParcel implements Parcelable {
dest.writeInt(mCleartextSignature ? 1 : 0);
dest.writeInt(mDetachedSignature ? 1 : 0);
dest.writeInt(mHiddenRecipients ? 1 : 0);
+ dest.writeInt(mIntegrityProtected ? 1 : 0);
}
public String getCharset() {
@@ -179,12 +177,12 @@ public class PgpSignEncryptInputParcel implements Parcelable {
return this;
}
- public int getCompressionId() {
- return mCompressionId;
+ public int getCompressionAlgorithm() {
+ return mCompressionAlgorithm;
}
- public PgpSignEncryptInputParcel setCompressionId(int compressionId) {
- mCompressionId = compressionId;
+ public PgpSignEncryptInputParcel setCompressionAlgorithm(int compressionAlgorithm) {
+ mCompressionAlgorithm = compressionAlgorithm;
return this;
}
@@ -234,6 +232,18 @@ public class PgpSignEncryptInputParcel implements Parcelable {
return this;
}
+ public boolean isIntegrityProtected() {
+ return mIntegrityProtected;
+ }
+
+ /**
+ * Only use for testing! Never disable integrity protection!
+ */
+ public PgpSignEncryptInputParcel setIntegrityProtected(boolean integrityProtected) {
+ this.mIntegrityProtected = integrityProtected;
+ return this;
+ }
+
public boolean isHiddenRecipients() {
return mHiddenRecipients;
}
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 9073e81b9..29b2ef727 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java
@@ -20,6 +20,8 @@
package org.sufficientlysecure.keychain.pgp;
import android.content.Context;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
import org.spongycastle.bcpg.ArmoredOutputStream;
import org.spongycastle.bcpg.BCPGOutputStream;
@@ -36,11 +38,11 @@ import org.spongycastle.openpgp.operator.jcajce.NfcSyncPGPContentSignerBuilder;
import org.sufficientlysecure.keychain.Constants;
import org.sufficientlysecure.keychain.R;
import org.sufficientlysecure.keychain.operations.BaseOperation;
+import org.sufficientlysecure.keychain.operations.results.OperationResult;
import org.sufficientlysecure.keychain.operations.results.OperationResult.LogType;
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
import org.sufficientlysecure.keychain.operations.results.PgpSignEncryptResult;
import org.sufficientlysecure.keychain.pgp.exception.PgpGeneralException;
-import org.sufficientlysecure.keychain.pgp.exception.PgpKeyNotFoundException;
import org.sufficientlysecure.keychain.provider.KeychainContract.KeyRings;
import org.sufficientlysecure.keychain.provider.ProviderHelper;
import org.sufficientlysecure.keychain.service.input.CryptoInputParcel;
@@ -60,9 +62,9 @@ import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.security.SignatureException;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
+import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
/**
@@ -99,6 +101,13 @@ public class PgpSignEncryptOperation extends BaseOperation {
super(context, providerHelper, progressable);
}
+ @NonNull
+ @Override
+ // TODO this is horrible, refactor ASAP!!
+ public OperationResult execute(Parcelable input, CryptoInputParcel cryptoInput) {
+ return null;
+ }
+
/**
* Signs and/or encrypts data based on parameters of class
*/
@@ -114,7 +123,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
boolean enableSignature = input.getSignatureMasterKeyId() != Constants.key.none;
boolean enableEncryption = ((input.getEncryptionMasterKeyIds() != null && input.getEncryptionMasterKeyIds().length > 0)
|| input.getSymmetricPassphrase() != null);
- boolean enableCompression = (input.getCompressionId() != CompressionAlgorithmTags.UNCOMPRESSED);
+ boolean enableCompression = (input.getCompressionAlgorithm() != CompressionAlgorithmTags.UNCOMPRESSED);
Log.d(Constants.TAG, "enableSignature:" + enableSignature
+ "\nenableEncryption:" + enableEncryption
@@ -189,7 +198,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
log.add(LogType.MSG_PSE_PENDING_PASSPHRASE, indent + 1);
return new PgpSignEncryptResult(log, RequiredInputParcel.createRequiredSignPassphrase(
signingKeyRing.getMasterKeyId(), signingKey.getKeyId(),
- cryptoInput.getSignatureTime()));
+ cryptoInput.getSignatureTime()), cryptoInput);
}
if (!signingKey.unlock(localPassphrase)) {
log.add(LogType.MSG_PSE_ERROR_BAD_PASSPHRASE, indent);
@@ -216,15 +225,10 @@ public class PgpSignEncryptOperation extends BaseOperation {
return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
}
- // Use preferred hash algo
+ // Use requested hash algo
int requestedAlgorithm = input.getSignatureHashAlgorithm();
- ArrayList<Integer> supported = signingKey.getSupportedHashAlgorithms();
- if (requestedAlgorithm == PgpConstants.OpenKeychainHashAlgorithmTags.USE_PREFERRED) {
- // get most preferred
- input.setSignatureHashAlgorithm(supported.get(0));
- } else if (!supported.contains(requestedAlgorithm)) {
- log.add(LogType.MSG_PSE_ERROR_HASH_ALGO, indent);
- return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ if (requestedAlgorithm == PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT) {
+ input.setSignatureHashAlgorithm(PgpSecurityConstants.DEFAULT_HASH_ALGORITHM);
}
}
updateProgress(R.string.progress_preparing_streams, 2, 100);
@@ -233,18 +237,15 @@ public class PgpSignEncryptOperation extends BaseOperation {
PGPEncryptedDataGenerator cPk = null;
if (enableEncryption) {
- // Use preferred encryption algo
+ // Use requested encryption algo
int algo = input.getSymmetricEncryptionAlgorithm();
- if (algo == PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_PREFERRED) {
- // get most preferred
- // TODO: get from recipients
- algo = PgpConstants.sPreferredSymmetricAlgorithms.get(0);
+ if (algo == PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT) {
+ algo = PgpSecurityConstants.DEFAULT_SYMMETRIC_ALGORITHM;
}
- // has Integrity packet enabled!
JcePGPDataEncryptorBuilder encryptorBuilder =
new JcePGPDataEncryptorBuilder(algo)
.setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME)
- .setWithIntegrityPacket(true);
+ .setWithIntegrityPacket(input.isIntegrityProtected());
cPk = new PGPEncryptedDataGenerator(encryptorBuilder);
@@ -263,15 +264,19 @@ public class PgpSignEncryptOperation extends BaseOperation {
try {
CanonicalizedPublicKeyRing keyRing = mProviderHelper.getCanonicalizedPublicKeyRing(
KeyRings.buildUnifiedKeyRingUri(id));
- CanonicalizedPublicKey key = keyRing.getEncryptionSubKey();
- cPk.addMethod(key.getPubKeyEncryptionGenerator(input.isHiddenRecipients()));
- log.add(LogType.MSG_PSE_KEY_OK, indent + 1,
- KeyFormattingUtils.convertKeyIdToHex(id));
- } catch (PgpKeyNotFoundException e) {
- log.add(LogType.MSG_PSE_KEY_WARN, indent + 1,
- KeyFormattingUtils.convertKeyIdToHex(id));
- if (input.isFailOnMissingEncryptionKeyIds()) {
- return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ Set<Long> encryptSubKeyIds = keyRing.getEncryptIds();
+ for (Long subKeyId : encryptSubKeyIds) {
+ CanonicalizedPublicKey key = keyRing.getPublicKey(subKeyId);
+ cPk.addMethod(key.getPubKeyEncryptionGenerator(input.isHiddenRecipients()));
+ log.add(LogType.MSG_PSE_KEY_OK, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(subKeyId));
+ }
+ if (encryptSubKeyIds.isEmpty()) {
+ log.add(LogType.MSG_PSE_KEY_WARN, indent + 1,
+ KeyFormattingUtils.convertKeyIdToHex(id));
+ if (input.isFailOnMissingEncryptionKeyIds()) {
+ return new PgpSignEncryptResult(PgpSignEncryptResult.RESULT_ERROR, log);
+ }
}
} catch (ProviderHelper.NotFoundException e) {
log.add(LogType.MSG_PSE_KEY_UNKNOWN, indent + 1,
@@ -327,7 +332,13 @@ public class PgpSignEncryptOperation extends BaseOperation {
if (enableCompression) {
log.add(LogType.MSG_PSE_COMPRESSING, indent);
- compressGen = new PGPCompressedDataGenerator(input.getCompressionId());
+
+ // Use preferred compression algo
+ int algo = input.getCompressionAlgorithm();
+ if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) {
+ algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM;
+ }
+ compressGen = new PGPCompressedDataGenerator(algo);
bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));
} else {
bcpgOut = new BCPGOutputStream(encryptionOut);
@@ -450,7 +461,7 @@ public class PgpSignEncryptOperation extends BaseOperation {
InputStream in = inputData.getInputStream();
if (enableCompression) {
- compressGen = new PGPCompressedDataGenerator(input.getCompressionId());
+ compressGen = new PGPCompressedDataGenerator(input.getCompressionAlgorithm());
bcpgOut = new BCPGOutputStream(compressGen.open(out));
} else {
bcpgOut = new BCPGOutputStream(out);
@@ -497,7 +508,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
// this secret key diverts to a OpenPGP card, throw exception with hash that will be signed
log.add(LogType.MSG_PSE_PENDING_NFC, indent);
return new PgpSignEncryptResult(log, RequiredInputParcel.createNfcSignOperation(
- e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()));
+ signingKey.getRing().getMasterKeyId(), signingKey.getKeyId(),
+ e.hashToSign, e.hashAlgo, cryptoInput.getSignatureTime()), cryptoInput);
}
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java
index 464de37f5..8f80a4802 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/SignEncryptParcel.java
@@ -57,6 +57,10 @@ public class SignEncryptParcel extends PgpSignEncryptInputParcel {
}
+ public boolean isIncomplete() {
+ return mInputUris.size() > mOutputUris.size();
+ }
+
public byte[] getBytes() {
return mBytes;
}
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
index 2bb4f7dc4..a7baddf8b 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedKeyRing.java
@@ -48,6 +48,7 @@ import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
@@ -78,7 +79,7 @@ import java.util.TreeSet;
*
*/
@SuppressWarnings("unchecked")
-public class UncachedKeyRing {
+public class UncachedKeyRing implements Serializable {
final PGPKeyRing mRing;
final boolean mIsSecret;
@@ -219,7 +220,7 @@ public class UncachedKeyRing {
Iterator<PGPPublicKey> it = mRing.getPublicKeys();
while (it.hasNext()) {
if (KeyFormattingUtils.convertFingerprintToHex(
- it.next().getFingerprint()).equals(expectedFingerprint)) {
+ it.next().getFingerprint()).equalsIgnoreCase(expectedFingerprint)) {
return true;
}
}
@@ -820,6 +821,15 @@ public class UncachedKeyRing {
continue;
}
+ Date keyCreationTime = key.getCreationTime(), keyCreationTimeLenient;
+ {
+ Calendar keyCreationCal = Calendar.getInstance();
+ keyCreationCal.setTime(keyCreationTime);
+ // allow for diverging clocks up to one day when checking creation time
+ keyCreationCal.add(Calendar.MINUTE, -5);
+ keyCreationTimeLenient = keyCreationCal.getTime();
+ }
+
// A subkey needs exactly one subkey binding certificate, and optionally one revocation
// certificate.
PGPPublicKey modified = key;
@@ -851,6 +861,18 @@ public class UncachedKeyRing {
continue;
}
+ if (cert.getCreationTime().before(keyCreationTime)) {
+ // Signature is earlier than key creation time
+ log.add(LogType.MSG_KC_SUB_BAD_TIME_EARLY, indent);
+ // due to an earlier accident, we generated keys which had creation timestamps
+ // a few seconds after their signature timestamp. for compatibility, we only
+ // error out with some margin of error
+ if (cert.getCreationTime().before(keyCreationTimeLenient)) {
+ badCerts += 1;
+ continue;
+ }
+ }
+
if (cert.isLocal()) {
// Creation date in the future? No way!
log.add(LogType.MSG_KC_SUB_BAD_LOCAL, indent);
diff --git a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
index 0173a1d83..013a6bf14 100644
--- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
+++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/UncachedPublicKey.java
@@ -211,12 +211,19 @@ public class UncachedPublicKey {
return getAlgorithm() == PGPPublicKey.ELGAMAL_ENCRYPT;
}
+ public boolean isRSA() {
+ return getAlgorithm() == PGPPublicKey.RSA_GENERAL
+ || getAlgorithm() == PGPPublicKey.RSA_ENCRYPT
+ || getAlgorithm() == PGPPublicKey.RSA_SIGN;
+ }
+
public boolean isDSA() {
return getAlgorithm() == PGPPublicKey.DSA;
}
public boolean isEC() {
- return getAlgorithm() == PGPPublicKey.ECDH || getAlgorithm() == PGPPublicKey.ECDSA;
+ return getAlgorithm() == PGPPublicKey.ECDH
+ || getAlgorithm() == PGPPublicKey.ECDSA;
}
public byte[] getFingerprint() {