From de9a16125218d8404aaa8e44563026b7ad407499 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 4 Jan 2024 18:20:09 +0100 Subject: [PATCH] Accept certification signatures using SHA-1 before 2023-02-01 This commit introduces a dedicated SignatureHashAlgorithmPolicy for certification signatures. The default configuration will accept SHA-1 on sigs created before 2023-02-01. --- .../negotiation/HashAlgorithmNegotiator.kt | 2 +- .../key/generation/KeyRingBuilder.kt | 3 +- .../kotlin/org/pgpainless/policy/Policy.kt | 32 +++++++++++++++++-- .../signature/consumer/SignatureValidator.kt | 12 +++++-- .../encryption_signing/SigningTest.java | 4 +-- .../org/pgpainless/example/ManagePolicy.java | 11 ++++--- .../pgpainless/policy/PolicySetterTest.java | 10 ++++-- .../org/pgpainless/policy/PolicyTest.java | 32 +++++++++---------- 8 files changed, 74 insertions(+), 32 deletions(-) diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt index 31d6b118..b9474247 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.kt @@ -34,7 +34,7 @@ interface HashAlgorithmNegotiator { */ @JvmStatic fun negotiateSignatureHashAlgorithm(policy: Policy): HashAlgorithmNegotiator { - return negotiateByPolicy(policy.signatureHashAlgorithmPolicy) + return negotiateByPolicy(policy.dataSignatureHashAlgorithmPolicy) } /** diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt index 67ad9669..2eeab371 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/generation/KeyRingBuilder.kt @@ -216,7 +216,8 @@ class KeyRingBuilder : KeyRingBuilderInterface { } private fun buildContentSigner(certKey: PGPKeyPair): PGPContentSignerBuilder { - val hashAlgorithm = PGPainless.getPolicy().signatureHashAlgorithmPolicy.defaultHashAlgorithm + val hashAlgorithm = + PGPainless.getPolicy().certificationSignatureHashAlgorithmPolicy.defaultHashAlgorithm return ImplementationFactory.getInstance() .getPGPContentSignerBuilder(certKey.publicKey.algorithm, hashAlgorithm.algorithmId) } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt index 22f42cd9..ba022838 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/policy/Policy.kt @@ -10,8 +10,9 @@ import org.pgpainless.util.DateUtil import org.pgpainless.util.NotationRegistry class Policy( - var signatureHashAlgorithmPolicy: HashAlgorithmPolicy, + var certificationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, var revocationSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, + var dataSignatureHashAlgorithmPolicy: HashAlgorithmPolicy, var symmetricKeyEncryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, var symmetricKeyDecryptionAlgorithmPolicy: SymmetricKeyAlgorithmPolicy, var compressionAlgorithmPolicy: CompressionAlgorithmPolicy, @@ -21,8 +22,9 @@ class Policy( constructor() : this( - HashAlgorithmPolicy.smartSignatureHashAlgorithmPolicy(), - HashAlgorithmPolicy.smartSignatureHashAlgorithmPolicy(), + HashAlgorithmPolicy.smartCertificationSignatureHashAlgorithmPolicy(), + HashAlgorithmPolicy.smartCertificationSignatureHashAlgorithmPolicy(), + HashAlgorithmPolicy.smartDataSignatureHashAlgorithmPolicy(), SymmetricKeyAlgorithmPolicy.symmetricKeyEncryptionPolicy2022(), SymmetricKeyAlgorithmPolicy.symmetricKeyDecryptionPolicy2022(), CompressionAlgorithmPolicy.anyCompressionAlgorithmPolicy(), @@ -89,6 +91,30 @@ class Policy( fun defaultHashAlgorithm() = defaultHashAlgorithm companion object { + // https://sequoia-pgp.org/blog/2023/02/01/202302-happy-sha1-day/ + // signature data which is not attacker-controlled is acceptable before 2023-02-01 + @JvmStatic + fun smartCertificationSignatureHashAlgorithmPolicy() = + HashAlgorithmPolicy( + HashAlgorithm.SHA512, + buildMap { + put(HashAlgorithm.SHA3_512, null) + put(HashAlgorithm.SHA3_512, null) + put(HashAlgorithm.SHA3_256, null) + put(HashAlgorithm.SHA512, null) + put(HashAlgorithm.SHA384, null) + put(HashAlgorithm.SHA256, null) + put(HashAlgorithm.SHA224, null) + put( + HashAlgorithm.RIPEMD160, + DateUtil.parseUTCDate("2023-02-01 00:00:00 UTC")) + put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2023-02-01 00:00:00 UTC")) + put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC")) + }) + + @JvmStatic + fun smartDataSignatureHashAlgorithmPolicy() = smartSignatureHashAlgorithmPolicy() + @JvmStatic fun smartSignatureHashAlgorithmPolicy() = HashAlgorithmPolicy( diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidator.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidator.kt index c7cbd6fd..0aa8b3b4 100644 --- a/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidator.kt +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/signature/consumer/SignatureValidator.kt @@ -235,12 +235,18 @@ abstract class SignatureValidator { signature: PGPSignature, policy: Policy ): Policy.HashAlgorithmPolicy { - val type = SignatureType.requireFromCode(signature.signatureType) - return when (type) { + return when (SignatureType.requireFromCode(signature.signatureType)) { SignatureType.CERTIFICATION_REVOCATION, SignatureType.KEY_REVOCATION, SignatureType.SUBKEY_REVOCATION -> policy.revocationSignatureHashAlgorithmPolicy - else -> policy.signatureHashAlgorithmPolicy + SignatureType.GENERIC_CERTIFICATION, + SignatureType.NO_CERTIFICATION, + SignatureType.CASUAL_CERTIFICATION, + SignatureType.POSITIVE_CERTIFICATION, + SignatureType.DIRECT_KEY, + SignatureType.SUBKEY_BINDING, + SignatureType.PRIMARYKEY_BINDING -> policy.certificationSignatureHashAlgorithmPolicy + else -> policy.dataSignatureHashAlgorithmPolicy } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java index bfd330aa..1dd974f1 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/encryption_signing/SigningTest.java @@ -207,7 +207,7 @@ public class SigningTest { SubkeyIdentifier signingKey = sigs.keySet().iterator().next(); PGPSignature signature = sigs.get(signingKey).iterator().next(); - assertEquals(PGPainless.getPolicy().getSignatureHashAlgorithmPolicy().defaultHashAlgorithm().getAlgorithmId(), + assertEquals(PGPainless.getPolicy().getDataSignatureHashAlgorithmPolicy().defaultHashAlgorithm().getAlgorithmId(), signature.getHashAlgorithm()); } @@ -237,7 +237,7 @@ public class SigningTest { SubkeyIdentifier signingKey = sigs.keySet().iterator().next(); PGPSignature signature = sigs.get(signingKey).iterator().next(); - assertEquals(PGPainless.getPolicy().getSignatureHashAlgorithmPolicy().defaultHashAlgorithm().getAlgorithmId(), + assertEquals(PGPainless.getPolicy().getDataSignatureHashAlgorithmPolicy().defaultHashAlgorithm().getAlgorithmId(), signature.getHashAlgorithm()); } diff --git a/pgpainless-core/src/test/java/org/pgpainless/example/ManagePolicy.java b/pgpainless-core/src/test/java/org/pgpainless/example/ManagePolicy.java index 858c99a9..3b29e35d 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/example/ManagePolicy.java +++ b/pgpainless-core/src/test/java/org/pgpainless/example/ManagePolicy.java @@ -43,7 +43,10 @@ public class ManagePolicy { @AfterEach public void resetPolicy() { // Policy for hash algorithms in non-revocation signatures - PGPainless.getPolicy().setSignatureHashAlgorithmPolicy( + PGPainless.getPolicy().setCertificationSignatureHashAlgorithmPolicy( + Policy.HashAlgorithmPolicy.static2022SignatureHashAlgorithmPolicy()); + // Policy for hash algorithms in data signatures + PGPainless.getPolicy().setDataSignatureHashAlgorithmPolicy( Policy.HashAlgorithmPolicy.static2022SignatureHashAlgorithmPolicy()); // Policy for hash algorithms in revocation signatures PGPainless.getPolicy().setRevocationSignatureHashAlgorithmPolicy( @@ -83,7 +86,7 @@ public class ManagePolicy { // Get PGPainless' policy singleton Policy policy = PGPainless.getPolicy(); - Policy.HashAlgorithmPolicy sigHashAlgoPolicy = policy.getSignatureHashAlgorithmPolicy(); + Policy.HashAlgorithmPolicy sigHashAlgoPolicy = policy.getDataSignatureHashAlgorithmPolicy(); assertTrue(sigHashAlgoPolicy.isAcceptable(HashAlgorithm.SHA512)); // Per default, non-revocation signatures using SHA-1 are rejected assertFalse(sigHashAlgoPolicy.isAcceptable(HashAlgorithm.SHA1)); @@ -95,9 +98,9 @@ public class ManagePolicy { // List of acceptable hash algorithms Arrays.asList(HashAlgorithm.SHA512, HashAlgorithm.SHA384, HashAlgorithm.SHA256, HashAlgorithm.SHA224, HashAlgorithm.SHA1)); // Set the hash algo policy as policy for non-revocation signatures - policy.setSignatureHashAlgorithmPolicy(customPolicy); + policy.setDataSignatureHashAlgorithmPolicy(customPolicy); - sigHashAlgoPolicy = policy.getSignatureHashAlgorithmPolicy(); + sigHashAlgoPolicy = policy.getDataSignatureHashAlgorithmPolicy(); assertTrue(sigHashAlgoPolicy.isAcceptable(HashAlgorithm.SHA512)); // SHA-1 is now acceptable as well assertTrue(sigHashAlgoPolicy.isAcceptable(HashAlgorithm.SHA1)); diff --git a/pgpainless-core/src/test/java/org/pgpainless/policy/PolicySetterTest.java b/pgpainless-core/src/test/java/org/pgpainless/policy/PolicySetterTest.java index 31092c28..6e90847d 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/policy/PolicySetterTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/policy/PolicySetterTest.java @@ -16,9 +16,15 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm; public class PolicySetterTest { @Test - public void testSetSignatureHashAlgorithmPolicy_NullFails() { + public void testSetCertificationSignatureHashAlgorithmPolicy_NullFails() { Policy policy = Policy.getInstance(); - assertThrows(NullPointerException.class, () -> policy.setSignatureHashAlgorithmPolicy(null)); + assertThrows(NullPointerException.class, () -> policy.setCertificationSignatureHashAlgorithmPolicy(null)); + } + + @Test + public void testSetDataSignatureHashAlgorithmPolicy_NullFails() { + Policy policy = Policy.getInstance(); + assertThrows(NullPointerException.class, () -> policy.setDataSignatureHashAlgorithmPolicy(null)); } @Test diff --git a/pgpainless-core/src/test/java/org/pgpainless/policy/PolicyTest.java b/pgpainless-core/src/test/java/org/pgpainless/policy/PolicyTest.java index aa7078e4..9ff4df85 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/policy/PolicyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/policy/PolicyTest.java @@ -44,7 +44,7 @@ public class PolicyTest { sigHashAlgoMap.put(HashAlgorithm.SHA256, null); sigHashAlgoMap.put(HashAlgorithm.SHA224, null); sigHashAlgoMap.put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC")); - policy.setSignatureHashAlgorithmPolicy(new Policy.HashAlgorithmPolicy(HashAlgorithm.SHA512, sigHashAlgoMap)); + policy.setCertificationSignatureHashAlgorithmPolicy(new Policy.HashAlgorithmPolicy(HashAlgorithm.SHA512, sigHashAlgoMap)); Map revHashAlgoMap = new HashMap<>(); revHashAlgoMap.put(HashAlgorithm.SHA512, null); @@ -107,40 +107,40 @@ public class PolicyTest { @Test public void testAcceptableSignatureHashAlgorithm() { - assertTrue(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA512)); - assertTrue(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA512.getAlgorithmId())); + assertTrue(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA512)); + assertTrue(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA512.getAlgorithmId())); // Usage date before termination date -> acceptable - assertTrue(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2000-01-01 00:00:00 UTC"))); - assertTrue(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1.getAlgorithmId(), DateUtil.parseUTCDate("2000-01-01 00:00:00 UTC"))); + assertTrue(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2000-01-01 00:00:00 UTC"))); + assertTrue(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1.getAlgorithmId(), DateUtil.parseUTCDate("2000-01-01 00:00:00 UTC"))); } @Test public void testUnacceptableSignatureHashAlgorithm() { - assertFalse(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1)); - assertFalse(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1.getAlgorithmId())); - assertFalse(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2020-01-01 00:00:00 UTC"))); - assertFalse(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1.getAlgorithmId(), DateUtil.parseUTCDate("2020-01-01 00:00:00 UTC"))); + assertFalse(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1)); + assertFalse(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1.getAlgorithmId())); + assertFalse(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2020-01-01 00:00:00 UTC"))); + assertFalse(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1.getAlgorithmId(), DateUtil.parseUTCDate("2020-01-01 00:00:00 UTC"))); } @Test public void testDefaultSignatureHashAlgorithm() { - assertEquals(HashAlgorithm.SHA512, policy.getSignatureHashAlgorithmPolicy().defaultHashAlgorithm()); + assertEquals(HashAlgorithm.SHA512, policy.getCertificationSignatureHashAlgorithmPolicy().defaultHashAlgorithm()); } @Test public void testAcceptableRevocationSignatureHashAlgorithm() { assertTrue(policy.getRevocationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA384)); assertTrue(policy.getRevocationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA384.getAlgorithmId())); - assertTrue(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2000-01-01 00:00:00 UTC"))); - assertTrue(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1.getAlgorithmId(), DateUtil.parseUTCDate("2000-01-01 00:00:00 UTC"))); + assertTrue(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2000-01-01 00:00:00 UTC"))); + assertTrue(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1.getAlgorithmId(), DateUtil.parseUTCDate("2000-01-01 00:00:00 UTC"))); } @Test public void testUnacceptableRevocationSignatureHashAlgorithm() { assertFalse(policy.getRevocationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.RIPEMD160)); assertFalse(policy.getRevocationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.RIPEMD160.getAlgorithmId())); - assertFalse(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2020-01-01 00:00:00 UTC"))); - assertFalse(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1.getAlgorithmId(), DateUtil.parseUTCDate("2020-01-01 00:00:00 UTC"))); + assertFalse(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2020-01-01 00:00:00 UTC"))); + assertFalse(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA1.getAlgorithmId(), DateUtil.parseUTCDate("2020-01-01 00:00:00 UTC"))); } @Test @@ -181,8 +181,8 @@ public class PolicyTest { @Test public void testUnknownSignatureHashAlgorithmIsNotAcceptable() { - assertFalse(policy.getSignatureHashAlgorithmPolicy().isAcceptable(-1)); - assertFalse(policy.getSignatureHashAlgorithmPolicy().isAcceptable(-1, new Date())); + assertFalse(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(-1)); + assertFalse(policy.getCertificationSignatureHashAlgorithmPolicy().isAcceptable(-1, new Date())); } @Test