diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp')
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() { |