mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-30 08:12:06 +01:00
Introduce HashAlgorithmNegotiator
This commit is contained in:
parent
3f31b076dd
commit
a8998f27ad
4 changed files with 67 additions and 41 deletions
|
@ -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();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
|
@ -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,
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in a new issue