mirror of
https://github.com/pgpainless/pgpainless.git
synced 2025-01-09 11:48:00 +01:00
Take hash algorithm usage date into account when checking algorithm acceptance
This commit is contained in:
parent
4764202ac9
commit
6c983d66e0
3 changed files with 175 additions and 12 deletions
|
@ -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,
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue