From 3b49840c9c2db6fbca7e873cc90c7e91ea492eb6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 24 Nov 2021 18:32:50 +0100 Subject: [PATCH 1/6] Reuse GNUObjectIdentifiers.Ed25519 --- .../src/main/java/org/pgpainless/key/info/KeyInfo.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java index e34de8c9..1cc4b6c8 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/info/KeyInfo.java @@ -5,6 +5,7 @@ package org.pgpainless.key.info; import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.asn1.gnu.GNUObjectIdentifiers; import org.bouncycastle.bcpg.ECDHPublicBCPGKey; import org.bouncycastle.bcpg.ECDSAPublicBCPGKey; import org.bouncycastle.bcpg.ECPublicBCPGKey; @@ -89,7 +90,7 @@ public class KeyInfo { ASN1ObjectIdentifier identifier = key.getCurveOID(); // Workaround for ECUtil not recognizing ed25519 - if (identifier.getId().equals("1.3.6.1.4.1.11591.15.1")) { + if (identifier.equals(GNUObjectIdentifiers.Ed25519)) { return EdDSACurve._Ed25519.getName(); } From 5364e21b5e2fc96c8a2708e88ba29e2e63dc43a8 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Wed, 24 Nov 2021 18:46:29 +0100 Subject: [PATCH 2/6] WiP implementation of public key parameter validation --- .../exception/KeyIntegrityException.java | 12 ++ .../key/protection/UnlockSecretKey.java | 15 +- .../PublicKeyParameterValidationUtil.java | 172 ++++++++++++++++++ .../GenerateEllipticCurveKeyTest.java | 3 + 4 files changed, 199 insertions(+), 3 deletions(-) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/exception/KeyIntegrityException.java create mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/exception/KeyIntegrityException.java b/pgpainless-core/src/main/java/org/pgpainless/exception/KeyIntegrityException.java new file mode 100644 index 00000000..65ed3dea --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/exception/KeyIntegrityException.java @@ -0,0 +1,12 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.exception; + +public class KeyIntegrityException extends AssertionError { + + public KeyIntegrityException() { + super("Key Integrity Exception"); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java index c68e4914..ce7bb5b6 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java @@ -9,8 +9,10 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.pgpainless.exception.KeyIntegrityException; import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.key.info.KeyInfo; +import org.pgpainless.key.util.PublicKeyParameterValidationUtil; import org.pgpainless.util.Passphrase; public final class UnlockSecretKey { @@ -20,13 +22,20 @@ public final class UnlockSecretKey { } public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector protector) - throws WrongPassphraseException { + throws WrongPassphraseException, KeyIntegrityException { try { PBESecretKeyDecryptor decryptor = null; if (KeyInfo.isEncrypted(secretKey)) { decryptor = protector.getDecryptor(secretKey.getKeyID()); } - return secretKey.extractPrivateKey(decryptor); + PGPPrivateKey privateKey = secretKey.extractPrivateKey(decryptor); + + if (secretKey.getPublicKey() != null) { + PublicKeyParameterValidationUtil.verifyPublicKeyParameterIntegrity(privateKey, secretKey.getPublicKey()); + } + return privateKey; + } catch (KeyIntegrityException e) { + throw e; } catch (PGPException e) { throw new WrongPassphraseException(secretKey.getKeyID(), e); } @@ -40,7 +49,7 @@ public final class UnlockSecretKey { } } - public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, Passphrase passphrase) throws WrongPassphraseException { + public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, Passphrase passphrase) throws WrongPassphraseException, KeyIntegrityException { return unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith(passphrase, secretKey)); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java new file mode 100644 index 00000000..e2cf058f --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java @@ -0,0 +1,172 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.util; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.math.BigInteger; +import java.security.SecureRandom; + +import org.bouncycastle.bcpg.BCPGKey; +import org.bouncycastle.bcpg.DSAPublicBCPGKey; +import org.bouncycastle.bcpg.DSASecretBCPGKey; +import org.bouncycastle.bcpg.EdDSAPublicBCPGKey; +import org.bouncycastle.bcpg.EdSecretBCPGKey; +import org.bouncycastle.bcpg.RSAPublicBCPGKey; +import org.bouncycastle.bcpg.RSASecretBCPGKey; +import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; +import org.bouncycastle.openpgp.PGPEncryptedDataList; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; +import org.bouncycastle.util.Arrays; +import org.bouncycastle.util.io.Streams; +import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.algorithm.PublicKeyAlgorithm; +import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; +import org.pgpainless.exception.KeyIntegrityException; +import org.pgpainless.implementation.ImplementationFactory; + +public class PublicKeyParameterValidationUtil { + + public static void verifyPublicKeyParameterIntegrity(PGPPrivateKey privateKey, PGPPublicKey publicKey) + throws KeyIntegrityException, PGPException { + PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.fromId(publicKey.getAlgorithm()); + boolean valid = true; + // Additional to the algorithm-specific tests further below, we also perform + // generic functionality tests with the key, such as whether it is able to decrypt encrypted data + // or verify signatures. + // These tests should be more or less constant time. + if (publicKeyAlgorithm.isSigningCapable()) { + valid = verifyCanSign(privateKey, publicKey) && valid; + } + if (publicKeyAlgorithm.isEncryptionCapable()) { + valid = verifyCanDecrypt(privateKey, publicKey) && valid; + } + + // Algorithm specific validations + BCPGKey key = privateKey.getPrivateKeyDataPacket(); + if (key instanceof RSASecretBCPGKey) { + valid = verifyRSAKeyIntegrity( + (RSASecretBCPGKey) key, + (RSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey()) + && valid; + } else if (key instanceof EdSecretBCPGKey) { + valid = verifyEdDsaKeyIntegrity( + (EdSecretBCPGKey) key, + (EdDSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey()) + && valid; + } else if (key instanceof DSASecretBCPGKey) { + valid = verifyDsaKeyIntegrity( + (DSASecretBCPGKey) key, + (DSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey()) + && valid; + } + + // TODO: ElGamal + + if (!valid) { + throw new KeyIntegrityException(); + } + } + + private static boolean verifyCanSign(PGPPrivateKey privateKey, PGPPublicKey publicKey) throws PGPException { + SecureRandom random = new SecureRandom(); + PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.fromId(publicKey.getAlgorithm()); + PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( + ImplementationFactory.getInstance().getPGPContentSignerBuilder(publicKeyAlgorithm, HashAlgorithm.SHA256) + ); + + signatureGenerator.init(SignatureType.TIMESTAMP.getCode(), privateKey); + + byte[] data = new byte[512]; + random.nextBytes(data); + + signatureGenerator.update(data); + PGPSignature sig = signatureGenerator.generate(); + + sig.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), publicKey); + sig.update(data); + return sig.verify(); + } + + private static boolean verifyCanDecrypt(PGPPrivateKey privateKey, PGPPublicKey publicKey) { + SecureRandom random = new SecureRandom(); + PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator( + ImplementationFactory.getInstance().getPGPDataEncryptorBuilder(SymmetricKeyAlgorithm.AES_256) + ); + encryptedDataGenerator.addMethod( + ImplementationFactory.getInstance().getPublicKeyKeyEncryptionMethodGenerator(publicKey)); + + byte[] data = new byte[1024]; + random.nextBytes(data); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try { + OutputStream outputStream = encryptedDataGenerator.open(out, new byte[1024]); + outputStream.write(data); + encryptedDataGenerator.close(); + PGPEncryptedDataList encryptedDataList = new PGPEncryptedDataList(out.toByteArray()); + PublicKeyDataDecryptorFactory decryptorFactory = + ImplementationFactory.getInstance().getPublicKeyDataDecryptorFactory(privateKey); + PGPPublicKeyEncryptedData encryptedData = + (PGPPublicKeyEncryptedData) encryptedDataList.getEncryptedDataObjects().next(); + InputStream decrypted = encryptedData.getDataStream(decryptorFactory); + out = new ByteArrayOutputStream(); + Streams.pipeAll(decrypted, out); + decrypted.close(); + } catch (IOException | PGPException e) { + return false; + } + + return Arrays.constantTimeAreEqual(data, out.toByteArray()); + } + + private static boolean verifyEdDsaKeyIntegrity(EdSecretBCPGKey privateKey, EdDSAPublicBCPGKey publicKey) + throws KeyIntegrityException { + // TODO: Implement + return true; + } + + private static boolean verifyDsaKeyIntegrity(DSASecretBCPGKey privateKey, DSAPublicBCPGKey publicKey) + throws KeyIntegrityException { + // Not sure what value to put here in order to have a "robust" primality check + // I went with 40, since that's what SO recommends: + // https://stackoverflow.com/a/6330138 + final int certainty = 40; + BigInteger pG = publicKey.getG(); + BigInteger pP = publicKey.getP(); + BigInteger pQ = publicKey.getQ(); + BigInteger pY = publicKey.getY(); + BigInteger sX = privateKey.getX(); + + boolean pPrime = pP.isProbablePrime(certainty); + boolean qPrime = pQ.isProbablePrime(certainty); + // q > 160 bits + boolean qLarge = pQ.getLowestSetBit() > 160; + // q divides p - 1 + boolean qDividesPminus1 = pP.subtract(BigInteger.ONE).mod(pQ).equals(BigInteger.ZERO); + // 1 < g < p + boolean gInBounds = BigInteger.ONE.max(pG).equals(pG) && pG.max(pP).equals(pP); + // g^q = 1 mod p + boolean gPowXModPEquals1 = pG.modPow(pQ, pP).equals(BigInteger.ONE); + // y = g^x mod p + boolean yEqualsGPowXModP = pY.equals(pG.modPow(sX, pP)); + + return pPrime && qPrime && qLarge && qDividesPminus1 && gInBounds && gPowXModPEquals1 && yEqualsGPowXModP; + } + + private static boolean verifyRSAKeyIntegrity(RSASecretBCPGKey secretKey, RSAPublicBCPGKey publicKey) + throws KeyIntegrityException { + // Verify that the public keys N is equal to private keys p*q + return publicKey.getModulus().equals(secretKey.getPrimeP().multiply(secretKey.getPrimeQ())); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateEllipticCurveKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateEllipticCurveKeyTest.java index f1988d8f..eef7812b 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateEllipticCurveKeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/generation/GenerateEllipticCurveKeyTest.java @@ -21,6 +21,8 @@ import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.generation.type.KeyType; import org.pgpainless.key.generation.type.eddsa.EdDSACurve; import org.pgpainless.key.generation.type.xdh.XDHSpec; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.key.util.UserId; public class GenerateEllipticCurveKeyTest { @@ -38,5 +40,6 @@ public class GenerateEllipticCurveKeyTest { .build(); assertEquals(PublicKeyAlgorithm.EDDSA.getAlgorithmId(), keyRing.getPublicKey().getAlgorithm()); + UnlockSecretKey.unlockSecretKey(keyRing.getSecretKey(), SecretKeyRingProtector.unprotectedKeys()); } } From aef9ebfd7b0a252a29ec3a1a5ee5e70d5db44fa6 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Sun, 28 Nov 2021 14:45:53 +0100 Subject: [PATCH 3/6] Incorporate feedback --- .../PublicKeyParameterValidationUtil.java | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java index e2cf058f..592ef945 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java @@ -42,16 +42,6 @@ public class PublicKeyParameterValidationUtil { throws KeyIntegrityException, PGPException { PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.fromId(publicKey.getAlgorithm()); boolean valid = true; - // Additional to the algorithm-specific tests further below, we also perform - // generic functionality tests with the key, such as whether it is able to decrypt encrypted data - // or verify signatures. - // These tests should be more or less constant time. - if (publicKeyAlgorithm.isSigningCapable()) { - valid = verifyCanSign(privateKey, publicKey) && valid; - } - if (publicKeyAlgorithm.isEncryptionCapable()) { - valid = verifyCanDecrypt(privateKey, publicKey) && valid; - } // Algorithm specific validations BCPGKey key = privateKey.getPrivateKeyDataPacket(); @@ -77,6 +67,21 @@ public class PublicKeyParameterValidationUtil { if (!valid) { throw new KeyIntegrityException(); } + + // Additional to the algorithm-specific tests further above, we also perform + // generic functionality tests with the key, such as whether it is able to decrypt encrypted data + // or verify signatures. + // These tests should be more or less constant time. + if (publicKeyAlgorithm.isSigningCapable()) { + valid = verifyCanSign(privateKey, publicKey); + } + if (publicKeyAlgorithm.isEncryptionCapable()) { + valid = verifyCanDecrypt(privateKey, publicKey) && valid; + } + + if (!valid) { + throw new KeyIntegrityException(); + } } private static boolean verifyCanSign(PGPPrivateKey privateKey, PGPPublicKey publicKey) throws PGPException { @@ -149,19 +154,46 @@ public class PublicKeyParameterValidationUtil { BigInteger sX = privateKey.getX(); boolean pPrime = pP.isProbablePrime(certainty); + if (!pPrime) { + return false; + } + boolean qPrime = pQ.isProbablePrime(certainty); + if (!qPrime) { + return false; + } + // q > 160 bits boolean qLarge = pQ.getLowestSetBit() > 160; + if (!qLarge) { + return false; + } + // q divides p - 1 boolean qDividesPminus1 = pP.subtract(BigInteger.ONE).mod(pQ).equals(BigInteger.ZERO); + if (!qDividesPminus1) { + return false; + } + // 1 < g < p boolean gInBounds = BigInteger.ONE.max(pG).equals(pG) && pG.max(pP).equals(pP); + if (!gInBounds) { + return false; + } + // g^q = 1 mod p boolean gPowXModPEquals1 = pG.modPow(pQ, pP).equals(BigInteger.ONE); + if (!gPowXModPEquals1) { + return false; + } + // y = g^x mod p boolean yEqualsGPowXModP = pY.equals(pG.modPow(sX, pP)); + if (!yEqualsGPowXModP) { + return false; + } - return pPrime && qPrime && qLarge && qDividesPminus1 && gInBounds && gPowXModPEquals1 && yEqualsGPowXModP; + return true; } private static boolean verifyRSAKeyIntegrity(RSASecretBCPGKey secretKey, RSAPublicBCPGKey publicKey) From d54a40196b94b3523222057baaf5f1d1c2ed66cb Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 6 Dec 2021 15:01:37 +0100 Subject: [PATCH 4/6] Fix NPE when attempting to decrypt GNU_DUMMY_S2K keys --- .../key/protection/UnlockSecretKey.java | 47 +++++++++++-------- .../builder/AbstractSignatureBuilder.java | 10 ++-- .../CertificationSignatureBuilder.java | 7 ++- .../builder/DirectKeySignatureBuilder.java | 5 +- .../PrimaryKeyBindingSignatureBuilder.java | 3 +- .../builder/RevocationSignatureBuilder.java | 3 +- .../builder/SelfSignatureBuilder.java | 7 ++- .../SubkeyBindingSignatureBuilder.java | 3 +- .../builder/UniversalSignatureBuilder.java | 5 +- .../pgpainless/example/UnlockSecretKeys.java | 3 +- 10 files changed, 45 insertions(+), 48 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java index ce7bb5b6..5557fe0b 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java @@ -22,34 +22,41 @@ public final class UnlockSecretKey { } public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector protector) - throws WrongPassphraseException, KeyIntegrityException { - try { - PBESecretKeyDecryptor decryptor = null; - if (KeyInfo.isEncrypted(secretKey)) { - decryptor = protector.getDecryptor(secretKey.getKeyID()); - } - PGPPrivateKey privateKey = secretKey.extractPrivateKey(decryptor); + throws PGPException, KeyIntegrityException { - if (secretKey.getPublicKey() != null) { - PublicKeyParameterValidationUtil.verifyPublicKeyParameterIntegrity(privateKey, secretKey.getPublicKey()); - } - return privateKey; - } catch (KeyIntegrityException e) { - throw e; + PBESecretKeyDecryptor decryptor = null; + if (KeyInfo.isEncrypted(secretKey)) { + decryptor = protector.getDecryptor(secretKey.getKeyID()); + } + PGPPrivateKey privateKey = unlockSecretKey(secretKey, decryptor); + return privateKey; + } + + public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, PBESecretKeyDecryptor decryptor) throws PGPException { + PGPPrivateKey privateKey; + try { + privateKey = secretKey.extractPrivateKey(decryptor); } catch (PGPException e) { throw new WrongPassphraseException(secretKey.getKeyID(), e); } - } - public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, PBESecretKeyDecryptor decryptor) throws WrongPassphraseException { - try { - return secretKey.extractPrivateKey(decryptor); - } catch (PGPException e) { - throw new WrongPassphraseException(secretKey.getKeyID(), e); + if (privateKey == null) { + int s2kType = secretKey.getS2K().getType(); + if (s2kType >= 100 && s2kType <= 110) { + throw new PGPException("Cannot decrypt secret key" + Long.toHexString(secretKey.getKeyID()) + ": " + + "Unsupported private S2K usage type " + s2kType); + } + + throw new PGPException("Cannot decrypt secret key."); } + + if (secretKey.getPublicKey() != null) { + PublicKeyParameterValidationUtil.verifyPublicKeyParameterIntegrity(privateKey, secretKey.getPublicKey()); + } + return privateKey; } - public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, Passphrase passphrase) throws WrongPassphraseException, KeyIntegrityException { + public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, Passphrase passphrase) throws PGPException, KeyIntegrityException { return unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith(passphrase, secretKey)); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/AbstractSignatureBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/AbstractSignatureBuilder.java index 7b92ab02..9eaa70b4 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/AbstractSignatureBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/AbstractSignatureBuilder.java @@ -5,6 +5,7 @@ package org.pgpainless.signature.builder; import java.util.Set; +import javax.annotation.Nonnull; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPPrivateKey; @@ -16,7 +17,6 @@ import org.pgpainless.PGPainless; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.SignatureType; import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; -import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.UnlockSecretKey; @@ -24,8 +24,6 @@ import org.pgpainless.key.util.OpenPgpKeyAttributeUtil; import org.pgpainless.signature.subpackets.SignatureSubpackets; import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; -import javax.annotation.Nonnull; - public abstract class AbstractSignatureBuilder> { protected final PGPPrivateKey privateSigningKey; protected final PGPPublicKey publicSigningKey; @@ -42,7 +40,7 @@ public abstract class AbstractSignatureBuilder { public CertificationSignatureBuilder(PGPSecretKey certificationKey, SecretKeyRingProtector protector) - throws WrongPassphraseException { + throws PGPException { this(SignatureType.GENERIC_CERTIFICATION, certificationKey, protector); } public CertificationSignatureBuilder(SignatureType signatureType, PGPSecretKey signingKey, SecretKeyRingProtector protector) - throws WrongPassphraseException { + throws PGPException { super(signatureType, signingKey, protector); } @@ -33,7 +32,7 @@ public class CertificationSignatureBuilder extends AbstractSignatureBuilder { - public DirectKeySignatureBuilder(PGPSecretKey certificationKey, SecretKeyRingProtector protector, PGPSignature archetypeSignature) throws WrongPassphraseException { + public DirectKeySignatureBuilder(PGPSecretKey certificationKey, SecretKeyRingProtector protector, PGPSignature archetypeSignature) throws PGPException { super(certificationKey, protector, archetypeSignature); } - public DirectKeySignatureBuilder(SignatureType signatureType, PGPSecretKey signingKey, SecretKeyRingProtector protector) throws WrongPassphraseException { + public DirectKeySignatureBuilder(SignatureType signatureType, PGPSecretKey signingKey, SecretKeyRingProtector protector) throws PGPException { super(signatureType, signingKey, protector); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/PrimaryKeyBindingSignatureBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/PrimaryKeyBindingSignatureBuilder.java index 0f88dec4..93339f86 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/PrimaryKeyBindingSignatureBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/PrimaryKeyBindingSignatureBuilder.java @@ -11,14 +11,13 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSignature; import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; public class PrimaryKeyBindingSignatureBuilder extends AbstractSignatureBuilder { public PrimaryKeyBindingSignatureBuilder(PGPSecretKey subkey, SecretKeyRingProtector subkeyProtector) - throws WrongPassphraseException { + throws PGPException { super(SignatureType.PRIMARYKEY_BINDING, subkey, subkeyProtector); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/RevocationSignatureBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/RevocationSignatureBuilder.java index 3449a5dd..ebae151d 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/RevocationSignatureBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/RevocationSignatureBuilder.java @@ -12,13 +12,12 @@ import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.signature.subpackets.RevocationSignatureSubpackets; public class RevocationSignatureBuilder extends AbstractSignatureBuilder { - public RevocationSignatureBuilder(SignatureType signatureType, PGPSecretKey signingKey, SecretKeyRingProtector protector) throws WrongPassphraseException { + public RevocationSignatureBuilder(SignatureType signatureType, PGPSecretKey signingKey, SecretKeyRingProtector protector) throws PGPException { super(signatureType, signingKey, protector); getHashedSubpackets().setRevocable(true, false); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/SelfSignatureBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/SelfSignatureBuilder.java index a7ffd488..e6bf94c3 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/SelfSignatureBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/SelfSignatureBuilder.java @@ -12,18 +12,17 @@ import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPUserAttributeSubpacketVector; import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.signature.subpackets.SelfSignatureSubpackets; public class SelfSignatureBuilder extends AbstractSignatureBuilder { - public SelfSignatureBuilder(PGPSecretKey signingKey, SecretKeyRingProtector protector) throws WrongPassphraseException { + public SelfSignatureBuilder(PGPSecretKey signingKey, SecretKeyRingProtector protector) throws PGPException { this(SignatureType.GENERIC_CERTIFICATION, signingKey, protector); } public SelfSignatureBuilder(SignatureType signatureType, PGPSecretKey signingKey, SecretKeyRingProtector protector) - throws WrongPassphraseException { + throws PGPException { super(signatureType, signingKey, protector); } @@ -31,7 +30,7 @@ public class SelfSignatureBuilder extends AbstractSignatureBuilder { public SubkeyBindingSignatureBuilder(PGPSecretKey signingKey, SecretKeyRingProtector protector) - throws WrongPassphraseException { + throws PGPException { super(SignatureType.SUBKEY_BINDING, signingKey, protector); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/UniversalSignatureBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/UniversalSignatureBuilder.java index 3b63a032..b3ed5bf0 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/builder/UniversalSignatureBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/builder/UniversalSignatureBuilder.java @@ -11,7 +11,6 @@ import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.pgpainless.algorithm.SignatureType; -import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.signature.subpackets.BaseSignatureSubpackets; import org.pgpainless.signature.subpackets.SignatureSubpackets; @@ -22,12 +21,12 @@ import org.pgpainless.signature.subpackets.SignatureSubpackets; public class UniversalSignatureBuilder extends AbstractSignatureBuilder { public UniversalSignatureBuilder(SignatureType signatureType, PGPSecretKey signingKey, SecretKeyRingProtector protector) - throws WrongPassphraseException { + throws PGPException { super(signatureType, signingKey, protector); } public UniversalSignatureBuilder(PGPSecretKey certificationKey, SecretKeyRingProtector protector, PGPSignature archetypeSignature) - throws WrongPassphraseException { + throws PGPException { super(certificationKey, protector, archetypeSignature); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/example/UnlockSecretKeys.java b/pgpainless-core/src/test/java/org/pgpainless/example/UnlockSecretKeys.java index a7b056b7..f4716211 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/example/UnlockSecretKeys.java +++ b/pgpainless-core/src/test/java/org/pgpainless/example/UnlockSecretKeys.java @@ -11,7 +11,6 @@ import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.junit.jupiter.api.Test; import org.pgpainless.PGPainless; -import org.pgpainless.exception.WrongPassphraseException; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.TestKeys; import org.pgpainless.key.protection.CachingSecretKeyRingProtector; @@ -120,7 +119,7 @@ public class UnlockSecretKeys { } private void assertProtectorUnlocksAllSecretKeys(PGPSecretKeyRing secretKey, SecretKeyRingProtector protector) - throws WrongPassphraseException { + throws PGPException { for (PGPSecretKey key : secretKey) { UnlockSecretKey.unlockSecretKey(key, protector); } From a34cd779201050ea8ed592aeedd2a64ff8e94f60 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 6 Dec 2021 15:02:31 +0100 Subject: [PATCH 5/6] Add test keys --- .../ModifiedPublicKeysInvestigation.java | 157 ++++++++++++++++++ 1 file changed, 157 insertions(+) create mode 100644 pgpainless-core/src/test/java/investigations/ModifiedPublicKeysInvestigation.java diff --git a/pgpainless-core/src/test/java/investigations/ModifiedPublicKeysInvestigation.java b/pgpainless-core/src/test/java/investigations/ModifiedPublicKeysInvestigation.java new file mode 100644 index 00000000..f418f577 --- /dev/null +++ b/pgpainless-core/src/test/java/investigations/ModifiedPublicKeysInvestigation.java @@ -0,0 +1,157 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package investigations; + +import java.io.IOException; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.exception.KeyIntegrityException; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.key.protection.UnlockSecretKey; +import org.pgpainless.util.Passphrase; + +public class ModifiedPublicKeysInvestigation { + + private static final String DSA = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: OpenPGP.js VERSION\n" + + "Comment: https://openpgpjs.org\n" + + "\n" + + "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\n" + + "=p9kX\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + private static final String ELGAMAL = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: OpenPGP.js VERSION\n" + + "Comment: https://openpgpjs.org\n" + + "\n" + + "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\n" + + "TsMBqN9H5d+2XQ==\n" + + "=lI+G\n" + + "-----END PGP PRIVATE KEY BLOCK-----\n"; + + @Test + public void investigate() throws IOException { + SecretKeyRingProtector protector = SecretKeyRingProtector.unlockAnyKeyWith(Passphrase.fromPassword("12345678")); + + PGPSecretKeyRing dsa = PGPainless.readKeyRing().secretKeyRing(DSA); + PGPSecretKeyRing elgamal = PGPainless.readKeyRing().secretKeyRing(ELGAMAL); + + // CHECKSTYLE:OFF + for (PGPSecretKey secretKey : dsa) { + try { + UnlockSecretKey.unlockSecretKey(secretKey, protector); + System.out.println("No KeyIntegrityException for dsa key " + Long.toHexString(secretKey.getKeyID())); + } catch (KeyIntegrityException e) { + System.out.println("KeyIntegrityException for dsa key " + Long.toHexString(secretKey.getKeyID())); + } catch (PGPException e) { + System.out.println("Cannot unlock dsa key: " + e.getMessage()); + } + } + + for (PGPSecretKey secretKey : elgamal) { + try { + UnlockSecretKey.unlockSecretKey(secretKey, protector); + System.out.println("No KeyIntegrityException for elgamal key " + Long.toHexString(secretKey.getKeyID())); + } catch (KeyIntegrityException e) { + System.out.println("KeyIntegrityException for elgamal key " + Long.toHexString(secretKey.getKeyID())); + }catch (PGPException e) { + System.out.println("Cannot unlock elgamal key: " + e.getMessage()); + } + } + // CHECKSTYLE:ON + } +} From af1d4f3e5b320d3ee3c810add8b87372bc097eee Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 6 Dec 2021 16:43:27 +0100 Subject: [PATCH 6/6] Add ElGamal validation ported from openpgpjs --- .../PublicKeyParameterValidationUtil.java | 101 +++++++++++++++--- 1 file changed, 89 insertions(+), 12 deletions(-) diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java index 592ef945..5ee8135b 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/PublicKeyParameterValidationUtil.java @@ -16,6 +16,8 @@ import org.bouncycastle.bcpg.DSAPublicBCPGKey; import org.bouncycastle.bcpg.DSASecretBCPGKey; import org.bouncycastle.bcpg.EdDSAPublicBCPGKey; import org.bouncycastle.bcpg.EdSecretBCPGKey; +import org.bouncycastle.bcpg.ElGamalPublicBCPGKey; +import org.bouncycastle.bcpg.ElGamalSecretBCPGKey; import org.bouncycastle.bcpg.RSAPublicBCPGKey; import org.bouncycastle.bcpg.RSASecretBCPGKey; import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; @@ -39,7 +41,7 @@ import org.pgpainless.implementation.ImplementationFactory; public class PublicKeyParameterValidationUtil { public static void verifyPublicKeyParameterIntegrity(PGPPrivateKey privateKey, PGPPublicKey publicKey) - throws KeyIntegrityException, PGPException { + throws KeyIntegrityException { PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.fromId(publicKey.getAlgorithm()); boolean valid = true; @@ -60,10 +62,13 @@ public class PublicKeyParameterValidationUtil { (DSASecretBCPGKey) key, (DSAPublicBCPGKey) publicKey.getPublicKeyPacket().getKey()) && valid; + } else if (key instanceof ElGamalSecretBCPGKey) { + valid = verifyElGamalKeyIntegrity( + (ElGamalSecretBCPGKey) key, + (ElGamalPublicBCPGKey) publicKey.getPublicKeyPacket().getKey()) + && valid; } - // TODO: ElGamal - if (!valid) { throw new KeyIntegrityException(); } @@ -84,26 +89,43 @@ public class PublicKeyParameterValidationUtil { } } - private static boolean verifyCanSign(PGPPrivateKey privateKey, PGPPublicKey publicKey) throws PGPException { + /** + * Verify that the public key can be used to successfully verify a signature made by the private key. + * @param privateKey private key + * @param publicKey public key + * @return false if signature verification fails + */ + private static boolean verifyCanSign(PGPPrivateKey privateKey, PGPPublicKey publicKey) { SecureRandom random = new SecureRandom(); PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.fromId(publicKey.getAlgorithm()); PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator( ImplementationFactory.getInstance().getPGPContentSignerBuilder(publicKeyAlgorithm, HashAlgorithm.SHA256) ); - signatureGenerator.init(SignatureType.TIMESTAMP.getCode(), privateKey); + try { + signatureGenerator.init(SignatureType.TIMESTAMP.getCode(), privateKey); - byte[] data = new byte[512]; - random.nextBytes(data); + byte[] data = new byte[512]; + random.nextBytes(data); - signatureGenerator.update(data); - PGPSignature sig = signatureGenerator.generate(); + signatureGenerator.update(data); + PGPSignature sig = signatureGenerator.generate(); - sig.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), publicKey); - sig.update(data); - return sig.verify(); + sig.init(ImplementationFactory.getInstance().getPGPContentVerifierBuilderProvider(), publicKey); + sig.update(data); + return sig.verify(); + } catch (PGPException e) { + return false; + } } + /** + * Verify that the public key can be used to encrypt a message which can successfully be + * decrypted using the private key. + * @param privateKey private key + * @param publicKey public key + * @return false if decryption of a message encrypted with the public key fails + */ private static boolean verifyCanDecrypt(PGPPrivateKey privateKey, PGPPublicKey publicKey) { SecureRandom random = new SecureRandom(); PGPEncryptedDataGenerator encryptedDataGenerator = new PGPEncryptedDataGenerator( @@ -201,4 +223,59 @@ public class PublicKeyParameterValidationUtil { // Verify that the public keys N is equal to private keys p*q return publicKey.getModulus().equals(secretKey.getPrimeP().multiply(secretKey.getPrimeQ())); } + + /** + * Validate ElGamal public key parameters. + * + * Original implementation by the openpgpjs authors: + * https://github.com/openpgpjs/openpgpjs/blob/main/src/crypto/public_key/elgamal.js#L76-L143 + * @param secretKey secret key + * @param publicKey public key + * @return true if supposedly valid, false if invalid + */ + private static boolean verifyElGamalKeyIntegrity(ElGamalSecretBCPGKey secretKey, ElGamalPublicBCPGKey publicKey) { + BigInteger p = publicKey.getP(); + BigInteger g = publicKey.getG(); + BigInteger y = publicKey.getY(); + BigInteger one = BigInteger.ONE; + + // 1 < g < p + if (g.min(one).equals(g) || g.max(p).equals(g)) { + return false; + } + + // p-1 is large + if (p.bitLength() < 1023) { + return false; + } + + // g^(p-1) mod p = 1 + if (!g.modPow(p.subtract(one), p).equals(one)) { + return false; + } + + // check g^i mod p != 1 for i < threshold + BigInteger res = g; + BigInteger i = BigInteger.valueOf(1); + BigInteger threshold = BigInteger.valueOf(2).shiftLeft(17); + while (i.compareTo(threshold) < 0) { + res = res.multiply(g).mod(p); + if (res.equals(one)) { + return false; + } + i = i.add(one); + } + + // blinded exponentiation to check y = g^(r*(p-1)+x) mod p + SecureRandom random = new SecureRandom(); + BigInteger x = secretKey.getX(); + BigInteger r = new BigInteger(p.bitLength(), random); + BigInteger rqx = p.subtract(one).multiply(r).add(x); + if (!y.equals(g.modPow(rqx, p))) { + return false; + } + + return true; + } + }