1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-26 22:32:07 +01:00

Introduce HashAlgorithmNegotiator

This commit is contained in:
Paul Schaub 2021-10-20 21:26:47 +02:00
parent 3f31b076dd
commit a8998f27ad
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
4 changed files with 67 additions and 41 deletions

View file

@ -0,0 +1,37 @@
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
//
// 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<HashAlgorithm> 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<HashAlgorithm> orderedPreferencesSet) {
for (HashAlgorithm preference : orderedPreferencesSet) {
if (hashAlgorithmPolicy.isAcceptable(preference)) {
return preference;
}
}
return hashAlgorithmPolicy.defaultHashAlgorithm();
}
};
}
}

View file

@ -23,6 +23,7 @@ import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.DocumentSignatureType; import org.pgpainless.algorithm.DocumentSignatureType;
import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator;
import org.pgpainless.exception.KeyCannotSignException; import org.pgpainless.exception.KeyCannotSignException;
import org.pgpainless.exception.KeyValidationError; import org.pgpainless.exception.KeyValidationError;
import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.implementation.ImplementationFactory;
@ -270,7 +271,7 @@ public final class SigningOptions {
/** /**
* Negotiate, which hash algorithm to use. * 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. * 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 * Lastly, should no acceptable algorithm be found, the {@link Policy Policies} default signature hash algorithm is
* used as a fallback. * used as a fallback.
@ -284,18 +285,8 @@ public final class SigningOptions {
return hashAlgorithmOverride; return hashAlgorithmOverride;
} }
HashAlgorithm algorithm = policy.getSignatureHashAlgorithmPolicy().defaultHashAlgorithm(); return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(policy)
if (preferences.isEmpty()) { .negotiateHashAlgorithm(preferences);
return algorithm;
}
for (HashAlgorithm pref : preferences) {
if (policy.getSignatureHashAlgorithmPolicy().isAcceptable(pref)) {
return pref;
}
}
return algorithm;
} }
private PGPSignatureGenerator createSignatureGenerator(PGPPrivateKey privateKey, private PGPSignatureGenerator createSignatureGenerator(PGPPrivateKey privateKey,

View file

@ -8,7 +8,9 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignature;
@ -23,7 +25,6 @@ public final class OpenPgpKeyAttributeUtil {
public static List<HashAlgorithm> getPreferredHashAlgorithms(PGPPublicKey publicKey) { public static List<HashAlgorithm> getPreferredHashAlgorithms(PGPPublicKey publicKey) {
List<HashAlgorithm> hashAlgorithms = new ArrayList<>(); List<HashAlgorithm> hashAlgorithms = new ArrayList<>();
// TODO: I'd assume that we have to use publicKey.getKeySignatures() here, but that is empty...
Iterator<?> keySignatures = publicKey.getSignatures(); Iterator<?> keySignatures = publicKey.getSignatures();
while (keySignatures.hasNext()) { while (keySignatures.hasNext()) {
PGPSignature signature = (PGPSignature) keySignatures.next(); PGPSignature signature = (PGPSignature) keySignatures.next();
@ -44,8 +45,6 @@ public final class OpenPgpKeyAttributeUtil {
hashAlgorithms.add(HashAlgorithm.fromId(h)); hashAlgorithms.add(HashAlgorithm.fromId(h));
} }
// Exit the loop after the first key signature with hash algorithms. // 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; break;
} }
} }
@ -87,4 +86,21 @@ public final class OpenPgpKeyAttributeUtil {
} }
return Collections.singletonList(hashAlgorithm); 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<HashAlgorithm> getOrGuessPreferredHashAlgorithms(PGPPublicKey publicKey) {
List<HashAlgorithm> preferredHashAlgorithms = OpenPgpKeyAttributeUtil.getPreferredHashAlgorithms(publicKey);
if (preferredHashAlgorithms.isEmpty()) {
preferredHashAlgorithms = OpenPgpKeyAttributeUtil.guessPreferredHashAlgorithms(publicKey);
}
return new LinkedHashSet<>(preferredHashAlgorithms);
}
} }

View file

@ -10,7 +10,9 @@ import java.io.InputStream;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set;
import org.bouncycastle.bcpg.sig.IssuerKeyID; import org.bouncycastle.bcpg.sig.IssuerKeyID;
import org.bouncycastle.bcpg.sig.KeyExpirationTime; import org.bouncycastle.bcpg.sig.KeyExpirationTime;
@ -30,6 +32,7 @@ import org.bouncycastle.util.encoders.Hex;
import org.pgpainless.PGPainless; import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.SignatureType; import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator;
import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.implementation.ImplementationFactory;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.util.OpenPgpKeyAttributeUtil; 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 * 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}. * the {@link org.pgpainless.policy.Policy}.
* *
* TODO: Move negotiation to negotiator class
*
* @param publicKey public key * @param publicKey public key
* @return content signer builder * @return content signer builder
*/ */
private static PGPContentSignerBuilder getPgpContentSignerBuilderForKey(PGPPublicKey publicKey) { private static PGPContentSignerBuilder getPgpContentSignerBuilderForKey(PGPPublicKey publicKey) {
List<HashAlgorithm> preferredHashAlgorithms = OpenPgpKeyAttributeUtil.getPreferredHashAlgorithms(publicKey); Set<HashAlgorithm> hashAlgorithmSet = OpenPgpKeyAttributeUtil.getOrGuessPreferredHashAlgorithms(publicKey);
if (preferredHashAlgorithms.isEmpty()) {
preferredHashAlgorithms = OpenPgpKeyAttributeUtil.guessPreferredHashAlgorithms(publicKey); HashAlgorithm hashAlgorithm = HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(PGPainless.getPolicy())
} .negotiateHashAlgorithm(hashAlgorithmSet);
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(preferredHashAlgorithms);
return ImplementationFactory.getInstance().getPGPContentSignerBuilder(publicKey.getAlgorithm(), hashAlgorithm.getAlgorithmId()); 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<HashAlgorithm> 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. * Extract and return the key expiration date value from the given signature.
* If the signature does not carry a {@link KeyExpirationTime} subpacket, return null. * If the signature does not carry a {@link KeyExpirationTime} subpacket, return null.