Take hash algorithm usage date into account when checking algorithm acceptance

This commit is contained in:
Paul Schaub 2022-04-22 22:45:39 +02:00
parent 4764202ac9
commit 6c983d66e0
Signed by: vanitasvitae
GPG Key ID: 62BEE9264BF17311
3 changed files with 175 additions and 12 deletions

View File

@ -6,7 +6,9 @@ package org.pgpainless.policy;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@ -18,6 +20,7 @@ import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.util.DateUtil;
import org.pgpainless.util.NotationRegistry;
/**
@ -301,11 +304,39 @@ public final class Policy {
public static final class HashAlgorithmPolicy {
private final HashAlgorithm defaultHashAlgorithm;
private final List<HashAlgorithm> acceptableHashAlgorithms;
private final Map<HashAlgorithm, Date> acceptableHashAlgorithmsAndTerminationDates;
public HashAlgorithmPolicy(HashAlgorithm defaultHashAlgorithm, List<HashAlgorithm> acceptableHashAlgorithms) {
/**
* Create a {@link HashAlgorithmPolicy} which accepts all {@link HashAlgorithm HashAlgorithms} from the
* given map, if the queried usage date is BEFORE the respective termination date.
* A termination date value of <pre>null</pre> means no termination, resulting in the algorithm being
* acceptable, regardless of usage date.
*
* @param defaultHashAlgorithm default hash algorithm
* @param algorithmTerminationDates map of acceptable algorithms and their termination dates
*/
public HashAlgorithmPolicy(@Nonnull HashAlgorithm defaultHashAlgorithm, @Nonnull Map<HashAlgorithm, Date> algorithmTerminationDates) {
this.defaultHashAlgorithm = defaultHashAlgorithm;
this.acceptableHashAlgorithms = Collections.unmodifiableList(acceptableHashAlgorithms);
this.acceptableHashAlgorithmsAndTerminationDates = algorithmTerminationDates;
}
/**
* Create a {@link HashAlgorithmPolicy} which accepts all {@link HashAlgorithm HashAlgorithms} listed in
* the given list, regardless of usage date.
*
* @param defaultHashAlgorithm default hash algorithm (e.g. used as fallback if negotiation fails)
* @param acceptableHashAlgorithms list of acceptable hash algorithms
*/
public HashAlgorithmPolicy(@Nonnull HashAlgorithm defaultHashAlgorithm, @Nonnull List<HashAlgorithm> acceptableHashAlgorithms) {
this(defaultHashAlgorithm, Collections.unmodifiableMap(listToMap(acceptableHashAlgorithms)));
}
private static Map<HashAlgorithm, Date> listToMap(@Nonnull List<HashAlgorithm> algorithms) {
Map<HashAlgorithm, Date> algorithmsAndTerminationDates = new HashMap<>();
for (HashAlgorithm algorithm : algorithms) {
algorithmsAndTerminationDates.put(algorithm, null);
}
return algorithmsAndTerminationDates;
}
/**
@ -319,17 +350,17 @@ public final class Policy {
}
/**
* Return true if the given hash algorithm is acceptable by this policy.
* Return true if the given hash algorithm is currently acceptable by this policy.
*
* @param hashAlgorithm hash algorithm
* @return true if the hash algorithm is acceptable, false otherwise
*/
public boolean isAcceptable(HashAlgorithm hashAlgorithm) {
return acceptableHashAlgorithms.contains(hashAlgorithm);
public boolean isAcceptable(@Nonnull HashAlgorithm hashAlgorithm) {
return isAcceptable(hashAlgorithm, new Date());
}
/**
* Return true if the given hash algorithm is acceptable by this policy.
* Return true if the given hash algorithm is currently acceptable by this policy.
*
* @param algorithmId hash algorithm
* @return true if the hash algorithm is acceptable, false otherwise
@ -344,6 +375,39 @@ public final class Policy {
}
}
/**
* Return true, if the given algorithm is acceptable for the given usage date.
*
* @param hashAlgorithm algorithm
* @param usageDate usage date (e.g. signature creation time)
*
* @return acceptance
*/
public boolean isAcceptable(@Nonnull HashAlgorithm hashAlgorithm, @Nonnull Date usageDate) {
if (!acceptableHashAlgorithmsAndTerminationDates.containsKey(hashAlgorithm)) {
return false;
}
// Check termination date
Date terminationDate = acceptableHashAlgorithmsAndTerminationDates.get(hashAlgorithm);
if (terminationDate == null) {
return true;
}
// Reject if usage date is past termination date
return terminationDate.after(usageDate);
}
public boolean isAcceptable(int algorithmId, @Nonnull Date usageDate) {
try {
HashAlgorithm algorithm = HashAlgorithm.requireFromId(algorithmId);
return isAcceptable(algorithm, usageDate);
} catch (NoSuchElementException e) {
// Unknown algorithm is not acceptable
return false;
}
}
/**
* The default signature hash algorithm policy of PGPainless.
* Note that this policy is only used for non-revocation signatures.
@ -352,6 +416,44 @@ public final class Policy {
* @return default signature hash algorithm policy
*/
public static HashAlgorithmPolicy defaultSignatureAlgorithmPolicy() {
return smartSignatureHashAlgorithmPolicy();
}
/**
* {@link HashAlgorithmPolicy} which takes the date of the algorithm usage into consideration.
* If the policy has a termination date for a given algorithm, and the usage date is after that termination
* date, the algorithm is rejected.
*
* This policy is inspired by Sequoia-PGP's collision resistant algorithm policy.
*
* @see <a href="https://gitlab.com/sequoia-pgp/sequoia/-/blob/main/openpgp/src/policy.rs#L604">
* Sequoia-PGP's Collision Resistant Algorithm Policy</a>
*
* @return smart signature algorithm policy
*/
public static HashAlgorithmPolicy smartSignatureHashAlgorithmPolicy() {
Map<HashAlgorithm, Date> algorithmDateMap = new HashMap<>();
algorithmDateMap.put(HashAlgorithm.MD5, DateUtil.parseUTCDate("1997-02-01 00:00:00 UTC"));
algorithmDateMap.put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC"));
algorithmDateMap.put(HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC"));
algorithmDateMap.put(HashAlgorithm.SHA224, null);
algorithmDateMap.put(HashAlgorithm.SHA256, null);
algorithmDateMap.put(HashAlgorithm.SHA384, null);
algorithmDateMap.put(HashAlgorithm.SHA512, null);
return new HashAlgorithmPolicy(HashAlgorithm.SHA512, algorithmDateMap);
}
/**
* {@link HashAlgorithmPolicy} which only accepts signatures made using algorithms which are acceptable
* according to 2022 standards.
*
* Particularly this policy only accepts algorithms from the SHA2 family.
*
* @return static signature algorithm policy
*/
public static HashAlgorithmPolicy static2022SignatureAlgorithmPolicy() {
return new HashAlgorithmPolicy(HashAlgorithm.SHA512, Arrays.asList(
HashAlgorithm.SHA224,
HashAlgorithm.SHA256,
@ -366,6 +468,41 @@ public final class Policy {
* @return default revocation signature hash algorithm policy
*/
public static HashAlgorithmPolicy defaultRevocationSignatureHashAlgorithmPolicy() {
return smartRevocationSignatureHashAlgorithmPolicy();
}
/**
* Revocation Signature {@link HashAlgorithmPolicy} which takes the date of the algorithm usage
* into consideration.
* If the policy has a termination date for a given algorithm, and the usage date is after that termination
* date, the algorithm is rejected.
*
* This policy is inspired by Sequoia-PGP's collision resistant algorithm policy.
*
* @see <a href="https://gitlab.com/sequoia-pgp/sequoia/-/blob/main/openpgp/src/policy.rs#L604">
* Sequoia-PGP's Collision Resistant Algorithm Policy</a>
*
* @return smart signature revocation algorithm policy
*/
public static HashAlgorithmPolicy smartRevocationSignatureHashAlgorithmPolicy() {
Map<HashAlgorithm, Date> algorithmDateMap = new HashMap<>();
algorithmDateMap.put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC"));
algorithmDateMap.put(HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC"));
algorithmDateMap.put(HashAlgorithm.SHA224, null);
algorithmDateMap.put(HashAlgorithm.SHA256, null);
algorithmDateMap.put(HashAlgorithm.SHA384, null);
algorithmDateMap.put(HashAlgorithm.SHA512, null);
return new HashAlgorithmPolicy(HashAlgorithm.SHA512, algorithmDateMap);
}
/**
* Hash algorithm policy for revocation signatures, which accepts SHA1 & SHA2 algorithms, as well as RIPEMD160.
*
* @return static revocation signature hash algorithm policy
*/
public static HashAlgorithmPolicy static2022RevocationSignatureHashAlgorithmPolicy() {
return new HashAlgorithmPolicy(HashAlgorithm.SHA512, Arrays.asList(
HashAlgorithm.RIPEMD160,
HashAlgorithm.SHA1,

View File

@ -195,8 +195,9 @@ public abstract class SignatureValidator {
try {
HashAlgorithm hashAlgorithm = HashAlgorithm.requireFromId(signature.getHashAlgorithm());
Policy.HashAlgorithmPolicy hashAlgorithmPolicy = getHashAlgorithmPolicyForSignature(signature, policy);
if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm())) {
throw new SignatureValidationException("Signature uses unacceptable hash algorithm " + hashAlgorithm);
if (!hashAlgorithmPolicy.isAcceptable(signature.getHashAlgorithm(), signature.getCreationTime())) {
throw new SignatureValidationException("Signature uses unacceptable hash algorithm " + hashAlgorithm +
" (Signature creation time: " + DateUtil.formatUTCDate(signature.getCreationTime()) + ")");
}
} catch (NoSuchElementException e) {
throw new SignatureValidationException("Signature uses unknown hash algorithm " + signature.getHashAlgorithm());

View File

@ -9,6 +9,9 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@ -16,6 +19,7 @@ import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.util.DateUtil;
public class PolicyTest {
@ -33,11 +37,23 @@ public class PolicyTest {
policy.setSymmetricKeyDecryptionAlgorithmPolicy(new Policy.SymmetricKeyAlgorithmPolicy(SymmetricKeyAlgorithm.AES_256,
Arrays.asList(SymmetricKeyAlgorithm.AES_256, SymmetricKeyAlgorithm.AES_192, SymmetricKeyAlgorithm.AES_128, SymmetricKeyAlgorithm.BLOWFISH)));
policy.setSignatureHashAlgorithmPolicy(new Policy.HashAlgorithmPolicy(HashAlgorithm.SHA512,
Arrays.asList(HashAlgorithm.SHA512, HashAlgorithm.SHA384, HashAlgorithm.SHA256)));
Map<HashAlgorithm, Date> sigHashAlgoMap = new HashMap<>();
sigHashAlgoMap.put(HashAlgorithm.SHA512, null);
sigHashAlgoMap.put(HashAlgorithm.SHA384, null);
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));
Map<HashAlgorithm, Date> revHashAlgoMap = new HashMap<>();
revHashAlgoMap.put(HashAlgorithm.SHA512, null);
revHashAlgoMap.put(HashAlgorithm.SHA384, null);
revHashAlgoMap.put(HashAlgorithm.SHA256, null);
revHashAlgoMap.put(HashAlgorithm.SHA224, null);
revHashAlgoMap.put(HashAlgorithm.SHA1, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC"));
revHashAlgoMap.put(HashAlgorithm.RIPEMD160, DateUtil.parseUTCDate("2013-02-01 00:00:00 UTC"));
policy.setRevocationSignatureHashAlgorithmPolicy(new Policy.HashAlgorithmPolicy(HashAlgorithm.SHA512,
Arrays.asList(HashAlgorithm.SHA512, HashAlgorithm.SHA384, HashAlgorithm.SHA256, HashAlgorithm.SHA224, HashAlgorithm.SHA1)));
revHashAlgoMap));
policy.setPublicKeyAlgorithmPolicy(Policy.PublicKeyAlgorithmPolicy.defaultPublicKeyAlgorithmPolicy());
}
@ -92,12 +108,17 @@ public class PolicyTest {
public void testAcceptableSignatureHashAlgorithm() {
assertTrue(policy.getSignatureHashAlgorithmPolicy().isAcceptable(HashAlgorithm.SHA512));
assertTrue(policy.getSignatureHashAlgorithmPolicy().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")));
}
@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")));
}
@Test
@ -109,12 +130,16 @@ public class PolicyTest {
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")));
}
@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")));
}
@Test