diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.java new file mode 100644 index 00000000..f76a2c26 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/negotiation/HashAlgorithmNegotiator.java @@ -0,0 +1,37 @@ +// SPDX-FileCopyrightText: 2021 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm.negotiation; + +import java.util.Set; + +import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.policy.Policy; + +public interface HashAlgorithmNegotiator { + + HashAlgorithm negotiateHashAlgorithm(Set orderedHashAlgorithmPreferencesSet); + + static HashAlgorithmNegotiator negotiateSignatureHashAlgorithm(Policy policy) { + return negotiateByPolicy(policy.getSignatureHashAlgorithmPolicy()); + } + + static HashAlgorithmNegotiator negotiateRevocationSignatureAlgorithm(Policy policy) { + return negotiateByPolicy(policy.getRevocationSignatureHashAlgorithmPolicy()); + } + + static HashAlgorithmNegotiator negotiateByPolicy(Policy.HashAlgorithmPolicy hashAlgorithmPolicy) { + return new HashAlgorithmNegotiator() { + @Override + public HashAlgorithm negotiateHashAlgorithm(Set orderedPreferencesSet) { + for (HashAlgorithm preference : orderedPreferencesSet) { + if (hashAlgorithmPolicy.isAcceptable(preference)) { + return preference; + } + } + return hashAlgorithmPolicy.defaultHashAlgorithm(); + } + }; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java index eb50db5d..651e96ea 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java @@ -23,6 +23,7 @@ import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.DocumentSignatureType; import org.pgpainless.algorithm.HashAlgorithm; +import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; import org.pgpainless.exception.KeyCannotSignException; import org.pgpainless.exception.KeyValidationError; import org.pgpainless.implementation.ImplementationFactory; @@ -270,7 +271,7 @@ public final class SigningOptions { /** * Negotiate, which hash algorithm to use. * - * This method gives highest priority to the algorithm override, which can be set via {@link #overrideHashAlgorithm(HashAlgorithm)}. + * This method gives the highest priority to the algorithm override, which can be set via {@link #overrideHashAlgorithm(HashAlgorithm)}. * After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm. * Lastly, should no acceptable algorithm be found, the {@link Policy Policies} default signature hash algorithm is * used as a fallback. @@ -284,18 +285,8 @@ public final class SigningOptions { return hashAlgorithmOverride; } - HashAlgorithm algorithm = policy.getSignatureHashAlgorithmPolicy().defaultHashAlgorithm(); - if (preferences.isEmpty()) { - return algorithm; - } - - for (HashAlgorithm pref : preferences) { - if (policy.getSignatureHashAlgorithmPolicy().isAcceptable(pref)) { - return pref; - } - } - - return algorithm; + return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(policy) + .negotiateHashAlgorithm(preferences); } private PGPSignatureGenerator createSignatureGenerator(PGPPrivateKey privateKey, diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/OpenPgpKeyAttributeUtil.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/OpenPgpKeyAttributeUtil.java index 673b86ec..a775bc08 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/OpenPgpKeyAttributeUtil.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/OpenPgpKeyAttributeUtil.java @@ -8,7 +8,9 @@ import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.Iterator; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPSignature; @@ -23,7 +25,6 @@ public final class OpenPgpKeyAttributeUtil { public static List getPreferredHashAlgorithms(PGPPublicKey publicKey) { List hashAlgorithms = new ArrayList<>(); - // TODO: I'd assume that we have to use publicKey.getKeySignatures() here, but that is empty... Iterator keySignatures = publicKey.getSignatures(); while (keySignatures.hasNext()) { PGPSignature signature = (PGPSignature) keySignatures.next(); @@ -44,8 +45,6 @@ public final class OpenPgpKeyAttributeUtil { hashAlgorithms.add(HashAlgorithm.fromId(h)); } // Exit the loop after the first key signature with hash algorithms. - // TODO: Find out, if it is possible that there are multiple key signatures which specify preferred - // algorithms and how to deal with that. break; } } @@ -87,4 +86,21 @@ public final class OpenPgpKeyAttributeUtil { } return Collections.singletonList(hashAlgorithm); } + + /** + * Try to extract hash algorithm preferences from self signatures. + * If no self-signature containing hash algorithm preferences is found, + * try to derive a hash algorithm preference by inspecting the hash algorithm used by existing + * self-signatures. + * + * @param publicKey key + * @return hash algorithm preferences (might be empty!) + */ + public static Set getOrGuessPreferredHashAlgorithms(PGPPublicKey publicKey) { + List preferredHashAlgorithms = OpenPgpKeyAttributeUtil.getPreferredHashAlgorithms(publicKey); + if (preferredHashAlgorithms.isEmpty()) { + preferredHashAlgorithms = OpenPgpKeyAttributeUtil.guessPreferredHashAlgorithms(publicKey); + } + return new LinkedHashSet<>(preferredHashAlgorithms); + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java index ff05ca9f..0a6003f2 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java +++ b/pgpainless-core/src/main/java/org/pgpainless/signature/SignatureUtils.java @@ -10,7 +10,9 @@ import java.io.InputStream; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.Date; +import java.util.LinkedHashSet; import java.util.List; +import java.util.Set; import org.bouncycastle.bcpg.sig.IssuerKeyID; import org.bouncycastle.bcpg.sig.KeyExpirationTime; @@ -30,6 +32,7 @@ import org.bouncycastle.util.encoders.Hex; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.util.OpenPgpKeyAttributeUtil; @@ -78,39 +81,18 @@ public final class SignatureUtils { * If no preferences can be derived, the key will fall back to the default hash algorithm as set in * the {@link org.pgpainless.policy.Policy}. * - * TODO: Move negotiation to negotiator class - * * @param publicKey public key * @return content signer builder */ private static PGPContentSignerBuilder getPgpContentSignerBuilderForKey(PGPPublicKey publicKey) { - List preferredHashAlgorithms = OpenPgpKeyAttributeUtil.getPreferredHashAlgorithms(publicKey); - if (preferredHashAlgorithms.isEmpty()) { - preferredHashAlgorithms = OpenPgpKeyAttributeUtil.guessPreferredHashAlgorithms(publicKey); - } - HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(preferredHashAlgorithms); + Set hashAlgorithmSet = OpenPgpKeyAttributeUtil.getOrGuessPreferredHashAlgorithms(publicKey); + + HashAlgorithm hashAlgorithm = HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(PGPainless.getPolicy()) + .negotiateHashAlgorithm(hashAlgorithmSet); return ImplementationFactory.getInstance().getPGPContentSignerBuilder(publicKey.getAlgorithm(), hashAlgorithm.getAlgorithmId()); } - /** - * Negotiate an acceptable hash algorithm from the provided list of options. - * Acceptance of hash algorithms can be changed by setting a custom {@link Policy}. - * - * @param preferredHashAlgorithms list of preferred hash algorithms of a key - * @return first acceptable algorithm, or policies default hash algorithm - */ - private static HashAlgorithm negotiateHashAlgorithm(List preferredHashAlgorithms) { - Policy policy = PGPainless.getPolicy(); - for (HashAlgorithm option : preferredHashAlgorithms) { - if (policy.getSignatureHashAlgorithmPolicy().isAcceptable(option)) { - return option; - } - } - - return PGPainless.getPolicy().getSignatureHashAlgorithmPolicy().defaultHashAlgorithm(); - } - /** * Extract and return the key expiration date value from the given signature. * If the signature does not carry a {@link KeyExpirationTime} subpacket, return null.