diff options
Diffstat (limited to 'OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp')
10 files changed, 498 insertions, 259 deletions
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 31a3925da..7394c07c3 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/CanonicalizedSecretKey.java @@ -43,7 +43,6 @@ import org.sufficientlysecure.keychain.util.Passphrase; import java.nio.ByteBuffer; import java.security.PrivateKey; import java.security.interfaces.RSAPrivateCrtKey; -import java.util.ArrayList; import java.util.Date; import java.util.HashMap; import java.util.Map; @@ -177,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) { @@ -205,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(); 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/PgpDecryptVerify.java b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java index e264b4678..1e51403fc 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpDecryptVerify.java @@ -22,6 +22,7 @@ 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; @@ -283,10 +284,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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) { @@ -298,8 +295,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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); } @@ -309,9 +306,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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; } @@ -322,6 +320,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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); @@ -464,6 +464,12 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> } } + // 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; @@ -614,15 +620,16 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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; @@ -686,6 +693,13 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> } } + // 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(); } @@ -752,7 +766,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); result.setCharset(charset); - result.setDecryptMetadata(metadata); + result.setDecryptionMetadata(metadata); return result; } @@ -809,11 +823,9 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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); @@ -821,10 +833,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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); @@ -851,8 +863,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> // 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); } } @@ -861,12 +873,12 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> log.add(LogType.MSG_DC_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.setCachedCryptoInputParcel(cryptoInput); - result.setDecryptMetadata(metadata); result.setSignatureResult(signatureResultBuilder.build()); result.setCharset(charset); + result.setDecryptionResult(decryptionResultBuilder.build()); + result.setDecryptionMetadata(metadata); return result; } @@ -885,8 +897,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> OperationLog log = new OperationLog(); OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder(); - // cleartext signatures are never encrypted ;) - signatureResultBuilder.setSignatureOnly(true); ByteArrayOutputStream out = new ByteArrayOutputStream(); @@ -924,7 +934,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log); } - PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder); + PGPSignature signature = processPGPSignatureList(sigList, signatureResultBuilder, log, indent); if (signature != null) { try { @@ -956,10 +966,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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); @@ -981,8 +991,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> clearText.length); DecryptVerifyResult result = new DecryptVerifyResult(DecryptVerifyResult.RESULT_OK, log); - result.setDecryptMetadata(metadata); result.setSignatureResult(signatureResultBuilder.build()); + result.setDecryptionResult( + new OpenPgpDecryptionResult(OpenPgpDecryptionResult.RESULT_NOT_ENCRYPTED)); + result.setDecryptionMetadata(metadata); return result; } @@ -994,8 +1006,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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(input.getDetachedSignature()); @@ -1016,7 +1026,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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); @@ -1050,9 +1060,6 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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) { @@ -1061,10 +1068,10 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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); @@ -1076,11 +1083,14 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> 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) + PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder, + OperationLog log, int indent) throws PGPException { CanonicalizedPublicKeyRing signingRing = null; CanonicalizedPublicKey signingKey = null; @@ -1122,6 +1132,13 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel> } } + // 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; } 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 c82cbce8f..6f156c201 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpKeyOperation.java @@ -316,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); @@ -1021,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); } @@ -1206,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 @@ -1233,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 @@ -1280,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 @@ -1440,13 +1440,13 @@ public class PgpKeyOperation { if (divertToCard) { // use synchronous "NFC based" SignerBuilder builder = new NfcSyncPGPContentSignerBuilder( - pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO, + 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( - pKey.getAlgorithm(), PgpConstants.SECRET_KEY_SIGNATURE_HASH_ALGO) + pKey.getAlgorithm(), PgpSecurityConstants.SECRET_KEY_BINDING_SIGNATURE_HASH_ALGO) .setProvider(Constants.BOUNCY_CASTLE_PROVIDER_NAME); } @@ -1472,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 */ 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..94fb2d877 --- /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 (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 = 96; + 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 fa6268758..36d1a07cb 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptInputParcel.java @@ -30,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() { @@ -55,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(); @@ -68,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 @@ -79,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); @@ -97,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() { @@ -174,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; } @@ -229,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 8fb41a909..29b2ef727 100644 --- a/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java +++ b/OpenKeychain/src/main/java/org/sufficientlysecure/keychain/pgp/PgpSignEncryptOperation.java @@ -62,7 +62,6 @@ 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; @@ -124,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 @@ -226,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); @@ -243,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); @@ -341,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); @@ -464,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); 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 9276cba10..26f046372 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() { |