Improve comments and reasons in PgpConstants, simple checks for insecure asymmetric keys

This commit is contained in:
Dominik Schürmann
2015-08-02 23:34:00 +02:00
parent 7c40d89eea
commit 3d8eda6e3e
24 changed files with 471 additions and 277 deletions

View File

@@ -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;
@@ -195,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();

View File

@@ -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;
}
}

View File

@@ -1,174 +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.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
*
* Many decisions are based on https://gist.github.com/coruus/68a8c65571e2b4225a69
*/
public class PgpConstants {
// public interface MIN_REQUIREMENT {
// int MIN_BITS;
// int BINDING_SIGNATURE_HASH_ALGO; // for User IDs, subkeys,...
// int SYMMETRIC_ALGO;
// }
// https://tools.ietf.org/html/rfc6637#section-13
/*
PgpDecryptVerify: Secure Algorithms Whitelist
all other algorithms will be rejected with OpenPgpDecryptionResult.RESULT_INSECURE
No broken ciphers or ciphers with key length smaller than 128 bit are allowed!
*/
public static HashSet<Integer> sSymmetricAlgorithmsWhitelist = new HashSet<>();
static {
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_256);
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_192);
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.AES_128);
sSymmetricAlgorithmsWhitelist.add(SymmetricKeyAlgorithmTags.TWOFISH); // 128 bit
}
// all other algorithms will be rejected with OpenPgpSignatureResult.RESULT_INVALID_INSECURE
public static HashSet<Integer> sHashAlgorithmsWhitelist = new HashSet<>();
static {
sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA512);
sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA384);
/*
TODO: SHA256 and SHA224 are still allowed even though
coruus advises against it, to enable better backward compatibility
coruus:
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.
*/
sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA256);
sHashAlgorithmsWhitelist.add(HashAlgorithmTags.SHA224);
}
/*
* Most preferred is first
* These arrays are written as preferred algorithms into the keys on creation.
* Other implementations may choose to honor this selection.
*/
public static final int[] PREFERRED_SYMMETRIC_ALGORITHMS = new int[]{
SymmetricKeyAlgorithmTags.AES_256,
SymmetricKeyAlgorithmTags.AES_192,
SymmetricKeyAlgorithmTags.AES_128,
};
/*
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))
*/
public static final int[] PREFERRED_HASH_ALGORITHMS = new int[]{
HashAlgorithmTags.SHA512,
};
/*
* Prefer ZIP
* "ZLIB provides no benefit over ZIP and is more malleable"
* - (OpenPGP WG mailinglist: "[openpgp] Intent to deprecate: Insecure primitives")
* BZIP2: very slow
*/
public static final int[] PREFERRED_COMPRESSION_ALGORITHMS = new int[]{
CompressionAlgorithmTags.ZIP,
};
public static final int CERTIFY_HASH_ALGO = HashAlgorithmTags.SHA512;
/*
Always use AES-256! 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! 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;
}
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
* OpenKeychain: 0x90
*/
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;
}

View File

@@ -321,7 +321,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
InputStream in, OutputStream out, int indent) throws IOException, PGPException {
OpenPgpSignatureResultBuilder signatureResultBuilder = new OpenPgpSignatureResultBuilder();
OpenPgpDecryptionResult decryptionResult = new OpenPgpDecryptionResult();
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,12 +620,12 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
log.add(LogType.MSG_DC_ERROR_NO_KEY, indent + 1);
return new DecryptVerifyResult(DecryptVerifyResult.RESULT_ERROR, log);
}
decryptionResult.setResult(OpenPgpDecryptionResult.RESULT_ENCRYPTED);
decryptionResultBuilder.setEncrypted(true);
// Check for insecure encryption algorithms!
if (!PgpConstants.sSymmetricAlgorithmsWhitelist.contains(symmetricEncryptionAlgo)) {
log.add(LogType.MSG_DC_OLD_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
decryptionResult.setResult(OpenPgpDecryptionResult.RESULT_INSECURE);
if (!PgpSecurityConstants.isSecureSymmetricAlgorithm(symmetricEncryptionAlgo)) {
log.add(LogType.MSG_DC_INSECURE_SYMMETRIC_ENCRYPTION_ALGO, indent + 1);
decryptionResultBuilder.setInsecure(true);
}
JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
@@ -687,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();
}
@@ -821,8 +834,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
// check for insecure hash algorithms
if (!PgpConstants.sHashAlgorithmsWhitelist.contains(signature.getHashAlgorithm())) {
log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1);
if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
signatureResultBuilder.setInsecure(true);
}
@@ -850,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);
decryptionResult.setResult(OpenPgpDecryptionResult.RESULT_INSECURE);
log.add(LogType.MSG_DC_INSECURE_MDC_MISSING, indent);
decryptionResultBuilder.setInsecure(true);
}
}
@@ -864,7 +877,7 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
result.setCachedCryptoInputParcel(cryptoInput);
result.setSignatureResult(signatureResultBuilder.build());
result.setCharset(charset);
result.setDecryptionResult(decryptionResult);
result.setDecryptionResult(decryptionResultBuilder.build());
result.setDecryptionMetadata(metadata);
return result;
@@ -921,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 {
@@ -954,8 +967,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
// check for insecure hash algorithms
if (!PgpConstants.sHashAlgorithmsWhitelist.contains(signature.getHashAlgorithm())) {
log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1);
if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
signatureResultBuilder.setInsecure(true);
}
@@ -1013,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);
@@ -1056,8 +1069,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
// check for insecure hash algorithms
if (!PgpConstants.sHashAlgorithmsWhitelist.contains(signature.getHashAlgorithm())) {
log.add(LogType.MSG_DC_ERROR_UNSUPPORTED_HASH_ALGO, indent + 1);
if (!PgpSecurityConstants.isSecureHashAlgorithm(signature.getHashAlgorithm())) {
log.add(LogType.MSG_DC_INSECURE_HASH_ALGO, indent + 1);
signatureResultBuilder.setInsecure(true);
}
@@ -1076,7 +1089,8 @@ public class PgpDecryptVerify extends BaseOperation<PgpDecryptVerifyInputParcel>
}
private PGPSignature processPGPSignatureList(
PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder)
PGPSignatureList sigList, OpenPgpSignatureResultBuilder signatureResultBuilder,
OperationLog log, int indent)
throws PGPException {
CanonicalizedPublicKeyRing signingRing = null;
CanonicalizedPublicKey signingKey = null;
@@ -1118,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;
}

View File

@@ -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_BINDING_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_BINDING_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_BINDING_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_BINDING_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.PREFERRED_SYMMETRIC_ALGORITHMS);
PgpSecurityConstants.PREFERRED_SYMMETRIC_ALGORITHMS);
hashedPacketsGen.setPreferredHashAlgorithms(false,
PgpConstants.PREFERRED_HASH_ALGORITHMS);
PgpSecurityConstants.PREFERRED_HASH_ALGORITHMS);
hashedPacketsGen.setPreferredCompressionAlgorithms(false,
PgpConstants.PREFERRED_COMPRESSION_ALGORITHMS);
PgpSecurityConstants.PREFERRED_COMPRESSION_ALGORITHMS);
hashedPacketsGen.setPrimaryUserID(false, primary);
/* critical subpackets: we consider those important for a modern pgp implementation */

View File

@@ -0,0 +1,282 @@
/*
* 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 {
/*
* TODO:
* 1. Check binding signatures for requirements on import! throw out binding signatures with insecure
* signatures (bit length, hash algo)
*
* - put checks for curve OIDs and algorithm tags into import instead of PgpDecryptVerify?
* - check signingRing in PgpDecryptVerify?
* - ECC checks https://tools.ietf.org/html/rfc6637#section-13
* - check encryption algo used for encrypting secret keys?
* - check S2K security?
* - check for min rsa/dsa/elgamal/ecc requirements in key creation backend
*/
/**
* 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
*
* REASON:
* Don't allow ELGAMAL_GENERAL (20), reason in RFC
*
* 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:
case PublicKeyAlgorithmTags.RSA_ENCRYPT:
case PublicKeyAlgorithmTags.RSA_SIGN: {
return (key.getBitStrength() >= 2048);
}
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: Must not be used, 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;
}

View File

@@ -33,10 +33,10 @@ public class PgpSignEncryptInputParcel implements Parcelable {
protected int mCompressionAlgorithm = CompressionAlgorithmTags.UNCOMPRESSED;
protected long[] mEncryptionMasterKeyIds = null;
protected Passphrase mSymmetricPassphrase = null;
protected int mSymmetricEncryptionAlgorithm = PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT;
protected int mSymmetricEncryptionAlgorithm = PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT;
protected long mSignatureMasterKeyId = Constants.key.none;
protected Long mSignatureSubKeyId = null;
protected int mSignatureHashAlgorithm = PgpConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT;
protected int mSignatureHashAlgorithm = PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT;
protected long mAdditionalEncryptId = Constants.key.none;
protected boolean mFailOnMissingEncryptionKeyIds = false;
protected String mCharset;

View File

@@ -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;
@@ -228,8 +227,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
// Use requested hash algo
int requestedAlgorithm = input.getSignatureHashAlgorithm();
if (requestedAlgorithm == PgpConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT) {
input.setSignatureHashAlgorithm(PgpConstants.DEFAULT_HASH_ALGORITHM);
if (requestedAlgorithm == PgpSecurityConstants.OpenKeychainHashAlgorithmTags.USE_DEFAULT) {
input.setSignatureHashAlgorithm(PgpSecurityConstants.DEFAULT_HASH_ALGORITHM);
}
}
updateProgress(R.string.progress_preparing_streams, 2, 100);
@@ -240,8 +239,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
// Use requested encryption algo
int algo = input.getSymmetricEncryptionAlgorithm();
if (algo == PgpConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT) {
algo = PgpConstants.DEFAULT_SYMMETRIC_ALGORITHM;
if (algo == PgpSecurityConstants.OpenKeychainSymmetricKeyAlgorithmTags.USE_DEFAULT) {
algo = PgpSecurityConstants.DEFAULT_SYMMETRIC_ALGORITHM;
}
// has Integrity packet enabled!
JcePGPDataEncryptorBuilder encryptorBuilder =
@@ -337,8 +336,8 @@ public class PgpSignEncryptOperation extends BaseOperation {
// Use preferred compression algo
int algo = input.getCompressionAlgorithm();
if (algo == PgpConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) {
algo = PgpConstants.DEFAULT_COMPRESSION_ALGORITHM;
if (algo == PgpSecurityConstants.OpenKeychainCompressionAlgorithmTags.USE_DEFAULT) {
algo = PgpSecurityConstants.DEFAULT_COMPRESSION_ALGORITHM;
}
compressGen = new PGPCompressedDataGenerator(algo);
bcpgOut = new BCPGOutputStream(compressGen.open(encryptionOut));

View File

@@ -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() {