mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-23 04:42:06 +01:00
Add SymmetricKeyAlgorithmNegotiator
This commit is contained in:
parent
821a49576f
commit
412b0aa119
10 changed files with 426 additions and 194 deletions
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.algorithm.negotiation;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.policy.Policy;
|
||||
|
||||
/**
|
||||
* Interface for symmetric key algorithm negotiation.
|
||||
*/
|
||||
public interface SymmetricKeyAlgorithmNegotiator {
|
||||
|
||||
/**
|
||||
* Negotiate a symmetric encryption algorithm.
|
||||
*
|
||||
* @param policy algorithm policy
|
||||
* @param override algorithm override (if not null, return this)
|
||||
* @param keyPreferences list of preferences per key
|
||||
* @return negotiated algorithm
|
||||
*/
|
||||
SymmetricKeyAlgorithm negotiate(Policy.SymmetricKeyAlgorithmPolicy policy, SymmetricKeyAlgorithm override, List<Set<SymmetricKeyAlgorithm>> keyPreferences);
|
||||
|
||||
static SymmetricKeyAlgorithmNegotiator byPopularity() {
|
||||
return new SymmetricKeyAlgorithmNegotiator() {
|
||||
@Override
|
||||
public SymmetricKeyAlgorithm negotiate(Policy.SymmetricKeyAlgorithmPolicy policy, SymmetricKeyAlgorithm override, List<Set<SymmetricKeyAlgorithm>> preferences) {
|
||||
if (override == SymmetricKeyAlgorithm.NULL) {
|
||||
throw new IllegalArgumentException("Algorithm override cannot be NULL (plaintext).");
|
||||
}
|
||||
|
||||
if (override != null) {
|
||||
return override;
|
||||
}
|
||||
|
||||
Map<SymmetricKeyAlgorithm, Integer> supportWeight = new LinkedHashMap<>();
|
||||
|
||||
for (Set<SymmetricKeyAlgorithm> keyPreferences : preferences) {
|
||||
for (SymmetricKeyAlgorithm preferred : keyPreferences) {
|
||||
if (supportWeight.containsKey(preferred)) {
|
||||
supportWeight.put(preferred, supportWeight.get(preferred) + 1);
|
||||
} else {
|
||||
supportWeight.put(preferred, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
List<SymmetricKeyAlgorithm> scoreboard = new ArrayList<>(supportWeight.keySet());
|
||||
// Sort scoreboard by descending popularity
|
||||
Collections.sort(scoreboard, new Comparator<SymmetricKeyAlgorithm>() {
|
||||
@Override
|
||||
public int compare(SymmetricKeyAlgorithm t0, SymmetricKeyAlgorithm t1) {
|
||||
return -supportWeight.get(t0).compareTo(supportWeight.get(t1));
|
||||
}
|
||||
});
|
||||
|
||||
for (SymmetricKeyAlgorithm mostWanted : scoreboard) {
|
||||
if (policy.isAcceptable(mostWanted)) {
|
||||
return mostWanted;
|
||||
}
|
||||
}
|
||||
|
||||
return policy.getDefaultSymmetricKeyAlgorithm();
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
/**
|
||||
* Classes related to algorithm negotiation.
|
||||
*/
|
||||
package org.pgpainless.algorithm.negotiation;
|
|
@ -18,11 +18,8 @@ package org.pgpainless.encryption_signing;
|
|||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
|
@ -33,14 +30,12 @@ import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
|
|||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.algorithm.negotiation.SymmetricKeyAlgorithmNegotiator;
|
||||
import org.pgpainless.decryption_verification.OpenPgpMetadata;
|
||||
import org.pgpainless.exception.KeyValidationException;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyView;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.policy.Policy;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
public class EncryptionBuilder implements EncryptionBuilderInterface {
|
||||
|
@ -262,69 +257,22 @@ public class EncryptionBuilder implements EncryptionBuilderInterface {
|
|||
|
||||
/**
|
||||
* Negotiate the {@link SymmetricKeyAlgorithm} used for message encryption.
|
||||
* If the user chose to set an override ({@link EncryptionOptions#overrideEncryptionAlgorithm(SymmetricKeyAlgorithm)}, use that.
|
||||
* Otherwise find an algorithm which is acceptable for all recipients.
|
||||
* If no consensus can be reached, use {@link Policy.SymmetricKeyAlgorithmPolicy#getDefaultSymmetricKeyAlgorithm()}.
|
||||
*
|
||||
* @param encryptionOptions encryption options
|
||||
* @return negotiated symmetric key algorithm
|
||||
*/
|
||||
public static SymmetricKeyAlgorithm negotiateSymmetricEncryptionAlgorithm(EncryptionOptions encryptionOptions) {
|
||||
SymmetricKeyAlgorithm encryptionAlgorithmOverride = encryptionOptions.getEncryptionAlgorithmOverride();
|
||||
if (encryptionAlgorithmOverride != null) {
|
||||
return encryptionAlgorithmOverride;
|
||||
}
|
||||
|
||||
Map<SymmetricKeyAlgorithm, Integer> supportWeight = new LinkedHashMap<>();
|
||||
|
||||
List<Set<SymmetricKeyAlgorithm>> preferences = new ArrayList<>();
|
||||
for (SubkeyIdentifier key : encryptionOptions.getKeyViews().keySet()) {
|
||||
KeyView keyView = encryptionOptions.getKeyViews().get(key);
|
||||
for (SymmetricKeyAlgorithm preferred : keyView.getPreferredSymmetricKeyAlgorithms()) {
|
||||
if (supportWeight.containsKey(preferred)) {
|
||||
supportWeight.put(preferred, supportWeight.get(preferred) + 1);
|
||||
} else {
|
||||
supportWeight.put(preferred, 1);
|
||||
}
|
||||
}
|
||||
preferences.add(encryptionOptions.getKeyViews().get(key).getPreferredSymmetricKeyAlgorithms());
|
||||
}
|
||||
|
||||
List<SymmetricKeyAlgorithm> scoreboard = new ArrayList<>(supportWeight.keySet());
|
||||
// Sort scoreboard by descending popularity
|
||||
Collections.sort(scoreboard, new Comparator<SymmetricKeyAlgorithm>() {
|
||||
@Override
|
||||
public int compare(SymmetricKeyAlgorithm t0, SymmetricKeyAlgorithm t1) {
|
||||
return -supportWeight.get(t0).compareTo(supportWeight.get(t1));
|
||||
}
|
||||
});
|
||||
|
||||
for (SymmetricKeyAlgorithm mostWanted : scoreboard) {
|
||||
if (PGPainless.getPolicy().getSymmetricKeyEncryptionAlgorithmPolicy().isAcceptable(mostWanted)) {
|
||||
return mostWanted;
|
||||
}
|
||||
}
|
||||
|
||||
return PGPainless.getPolicy().getSymmetricKeyEncryptionAlgorithmPolicy().getDefaultSymmetricKeyAlgorithm();
|
||||
}
|
||||
|
||||
/**
|
||||
* Negotiate the {@link HashAlgorithm} used for signatures.
|
||||
*
|
||||
* If we encrypt and sign, we look at the recipients keys to determine which algorithm to use.
|
||||
* If we only sign, we look at the singing keys preferences instead.
|
||||
*
|
||||
* @param encryptionOptions encryption options (recipients keys)
|
||||
* @param signingOptions signing options (signing keys)
|
||||
* @return negotiated hash algorithm
|
||||
*/
|
||||
public static HashAlgorithm negotiateSignatureHashAlgorithm(EncryptionOptions encryptionOptions, SigningOptions signingOptions) {
|
||||
HashAlgorithm hashAlgorithmOverride = signingOptions.getHashAlgorithmOverride();
|
||||
if (hashAlgorithmOverride != null) {
|
||||
return hashAlgorithmOverride;
|
||||
}
|
||||
|
||||
// TODO: Negotiation
|
||||
|
||||
return PGPainless.getPolicy().getSignatureHashAlgorithmPolicy().defaultHashAlgorithm();
|
||||
return SymmetricKeyAlgorithmNegotiator
|
||||
.byPopularity()
|
||||
.negotiate(
|
||||
PGPainless.getPolicy().getSymmetricKeyEncryptionAlgorithmPolicy(),
|
||||
encryptionOptions.getEncryptionAlgorithmOverride(),
|
||||
preferences);
|
||||
}
|
||||
|
||||
public static CompressionAlgorithm negotiateCompressionAlgorithm(ProducerOptions producerOptions) {
|
||||
|
|
|
@ -32,7 +32,7 @@ import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
|||
import org.pgpainless.implementation.ImplementationFactory;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.info.KeyView;
|
||||
import org.pgpainless.key.info.KeyAccessor;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
/**
|
||||
|
@ -67,7 +67,7 @@ public class EncryptionOptions {
|
|||
private final Set<PGPKeyEncryptionMethodGenerator> encryptionMethods = new LinkedHashSet<>();
|
||||
private final Set<SubkeyIdentifier> encryptionKeys = new LinkedHashSet<>();
|
||||
private final Map<SubkeyIdentifier, KeyRingInfo> keyRingInfo = new HashMap<>();
|
||||
private final Map<SubkeyIdentifier, KeyView> keyViews = new HashMap<>();
|
||||
private final Map<SubkeyIdentifier, KeyAccessor> keyViews = new HashMap<>();
|
||||
private final EncryptionKeySelector encryptionKeySelector = encryptToFirstSubkey();
|
||||
|
||||
private SymmetricKeyAlgorithm encryptionAlgorithmOverride = null;
|
||||
|
@ -135,7 +135,7 @@ public class EncryptionOptions {
|
|||
for (PGPPublicKey encryptionSubkey : encryptionSubkeys) {
|
||||
SubkeyIdentifier keyId = new SubkeyIdentifier(key, encryptionSubkey.getKeyID());
|
||||
keyRingInfo.put(keyId, info);
|
||||
keyViews.put(keyId, new KeyView.ViaUserId(info, keyId, userId));
|
||||
keyViews.put(keyId, new KeyAccessor.ViaUserId(info, keyId, userId));
|
||||
addRecipientKey(key, encryptionSubkey);
|
||||
}
|
||||
|
||||
|
@ -169,7 +169,7 @@ public class EncryptionOptions {
|
|||
for (PGPPublicKey encryptionSubkey : encryptionSubkeys) {
|
||||
SubkeyIdentifier keyId = new SubkeyIdentifier(key, encryptionSubkey.getKeyID());
|
||||
keyRingInfo.put(keyId, info);
|
||||
keyViews.put(keyId, new KeyView.ViaKeyId(info, keyId));
|
||||
keyViews.put(keyId, new KeyAccessor.ViaKeyId(info, keyId));
|
||||
addRecipientKey(key, encryptionSubkey);
|
||||
}
|
||||
|
||||
|
@ -224,7 +224,7 @@ public class EncryptionOptions {
|
|||
return new HashSet<>(encryptionKeys);
|
||||
}
|
||||
|
||||
public Map<SubkeyIdentifier, KeyView> getKeyViews() {
|
||||
public Map<SubkeyIdentifier, KeyAccessor> getKeyViews() {
|
||||
return new HashMap<>(keyViews);
|
||||
}
|
||||
|
||||
|
|
|
@ -140,7 +140,7 @@ public final class EncryptionStream extends OutputStream {
|
|||
}
|
||||
|
||||
private void prepareCompression() throws IOException {
|
||||
CompressionAlgorithm compressionAlgorithm = options.getCompressionAlgorithmOverride();
|
||||
CompressionAlgorithm compressionAlgorithm = EncryptionBuilder.negotiateCompressionAlgorithm(options);
|
||||
resultBuilder.setCompressionAlgorithm(compressionAlgorithm);
|
||||
compressedDataGenerator = new PGPCompressedDataGenerator(
|
||||
compressionAlgorithm.getAlgorithmId());
|
||||
|
|
|
@ -20,6 +20,7 @@ import java.util.Date;
|
|||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPPrivateKey;
|
||||
|
@ -28,6 +29,7 @@ import org.bouncycastle.openpgp.PGPSecretKey;
|
|||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPSignatureGenerator;
|
||||
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
|
||||
import org.pgpainless.PGPainless;
|
||||
import org.pgpainless.algorithm.DocumentSignatureType;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.exception.KeyValidationException;
|
||||
|
@ -35,9 +37,13 @@ import org.pgpainless.implementation.ImplementationFactory;
|
|||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.policy.Policy;
|
||||
|
||||
public final class SigningOptions {
|
||||
|
||||
/**
|
||||
* A method of signing.
|
||||
*/
|
||||
public static final class SigningMethod {
|
||||
private final PGPSignatureGenerator signatureGenerator;
|
||||
private final boolean detached;
|
||||
|
@ -47,10 +53,25 @@ public final class SigningOptions {
|
|||
this.detached = detached;
|
||||
}
|
||||
|
||||
/**
|
||||
* Inline-signature method.
|
||||
* The resulting signature will be written into the message itself, together with a one-pass-signature packet.
|
||||
*
|
||||
* @param signatureGenerator signature generator
|
||||
* @return inline signing method
|
||||
*/
|
||||
public static SigningMethod inlineSignature(PGPSignatureGenerator signatureGenerator) {
|
||||
return new SigningMethod(signatureGenerator, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Detached signing method.
|
||||
* The resulting signature will not be added to the message, and instead can be distributed separately
|
||||
* to the signed message.
|
||||
*
|
||||
* @param signatureGenerator signature generator
|
||||
* @return detached signing method
|
||||
*/
|
||||
public static SigningMethod detachedSignature(PGPSignatureGenerator signatureGenerator) {
|
||||
return new SigningMethod(signatureGenerator, true);
|
||||
}
|
||||
|
@ -64,20 +85,47 @@ public final class SigningOptions {
|
|||
}
|
||||
}
|
||||
|
||||
private Map<SubkeyIdentifier, SigningMethod> signingMethods = new HashMap<>();
|
||||
private final Map<SubkeyIdentifier, SigningMethod> signingMethods = new HashMap<>();
|
||||
private HashAlgorithm hashAlgorithmOverride;
|
||||
|
||||
public void addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
|
||||
PGPSecretKeyRing secretKey,
|
||||
DocumentSignatureType signatureType)
|
||||
/**
|
||||
* Add an inline-signature.
|
||||
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
||||
* of one-pass-signature packets.
|
||||
*
|
||||
* @param secretKeyDecryptor decryptor to unlock the signing secret key
|
||||
* @param secretKey signing key
|
||||
* @param signatureType type of signature (binary, canonical text)
|
||||
* @throws KeyValidationException if something is wrong with the key
|
||||
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
||||
* @return this
|
||||
*/
|
||||
public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
|
||||
PGPSecretKeyRing secretKey,
|
||||
DocumentSignatureType signatureType)
|
||||
throws KeyValidationException, PGPException {
|
||||
addInlineSignature(secretKeyDecryptor, secretKey, null, signatureType);
|
||||
return addInlineSignature(secretKeyDecryptor, secretKey, null, signatureType);
|
||||
}
|
||||
|
||||
public void addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
|
||||
PGPSecretKeyRing secretKey,
|
||||
String userId,
|
||||
DocumentSignatureType signatureType)
|
||||
/**
|
||||
* Add an inline-signature.
|
||||
* Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
|
||||
* of one-pass-signature packets.
|
||||
*
|
||||
* This method uses the passed in user-id to select user-specific hash algorithms.
|
||||
*
|
||||
* @param secretKeyDecryptor decryptor to unlock the signing secret key
|
||||
* @param secretKey signing key
|
||||
* @param userId user-id of the signer
|
||||
* @param signatureType signature type (binary, canonical text)
|
||||
* @return this
|
||||
* @throws KeyValidationException if the key is invalid
|
||||
* @throws PGPException if the key cannot be unlocked or the signing method cannot be created
|
||||
*/
|
||||
public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
|
||||
PGPSecretKeyRing secretKey,
|
||||
String userId,
|
||||
DocumentSignatureType signatureType)
|
||||
throws KeyValidationException, PGPException {
|
||||
KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date());
|
||||
if (userId != null) {
|
||||
|
@ -94,22 +142,52 @@ public final class SigningOptions {
|
|||
for (PGPPublicKey signingPubKey : signingPubKeys) {
|
||||
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
|
||||
PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey(secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID()));
|
||||
List<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
|
||||
addSigningMethod(secretKey, signingSubkey, hashAlgorithms.get(0), signatureType, false);
|
||||
Set<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
|
||||
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
|
||||
addSigningMethod(secretKey, signingSubkey, hashAlgorithm, signatureType, false);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public void addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor,
|
||||
PGPSecretKeyRing secretKey,
|
||||
DocumentSignatureType signatureType)
|
||||
/**
|
||||
* Create a detached signature.
|
||||
* Detached signatures are not being added into the PGP message itself.
|
||||
* Instead they can be distributed separately to the message.
|
||||
* Detached signatures are useful if the data that is being signed shall not be modified (eg. when signing a file).
|
||||
*
|
||||
* @param secretKeyDecryptor decryptor to unlock the secret signing key
|
||||
* @param secretKey signing key
|
||||
* @param signatureType type of data that is signed (binary, canonical text)
|
||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
||||
* @return this
|
||||
*/
|
||||
public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor,
|
||||
PGPSecretKeyRing secretKey,
|
||||
DocumentSignatureType signatureType)
|
||||
throws PGPException {
|
||||
addDetachedSignature(secretKeyDecryptor, secretKey, null, signatureType);
|
||||
return addDetachedSignature(secretKeyDecryptor, secretKey, null, signatureType);
|
||||
}
|
||||
|
||||
public void addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor,
|
||||
PGPSecretKeyRing secretKey,
|
||||
String userId,
|
||||
DocumentSignatureType signatureType)
|
||||
/**
|
||||
* Create a detached signature.
|
||||
* Detached signatures are not being added into the PGP message itself.
|
||||
* Instead they can be distributed separately to the message.
|
||||
* Detached signatures are useful if the data that is being signed shall not be modified (eg. when signing a file).
|
||||
*
|
||||
* This method uses the passed in user-id to select user-specific hash algorithms.
|
||||
*
|
||||
* @param secretKeyDecryptor decryptor to unlock the secret signing key
|
||||
* @param secretKey signing key
|
||||
* @param userId user-id
|
||||
* @param signatureType type of data that is signed (binary, canonical text)
|
||||
* @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
|
||||
* @return this
|
||||
*/
|
||||
public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor,
|
||||
PGPSecretKeyRing secretKey,
|
||||
String userId,
|
||||
DocumentSignatureType signatureType)
|
||||
throws PGPException {
|
||||
KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date());
|
||||
if (userId != null) {
|
||||
|
@ -126,9 +204,12 @@ public final class SigningOptions {
|
|||
for (PGPPublicKey signingPubKey : signingPubKeys) {
|
||||
PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
|
||||
PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey(secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID()));
|
||||
List<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
|
||||
addSigningMethod(secretKey, signingSubkey, hashAlgorithms.get(0), signatureType, true);
|
||||
Set<HashAlgorithm> hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(userId, signingPubKey.getKeyID());
|
||||
HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
|
||||
addSigningMethod(secretKey, signingSubkey, hashAlgorithm, signatureType, true);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private void addSigningMethod(PGPSecretKeyRing secretKey,
|
||||
|
@ -143,6 +224,37 @@ public final class SigningOptions {
|
|||
signingMethods.put(signingKeyIdentifier, signingMethod);
|
||||
}
|
||||
|
||||
/**
|
||||
* Negotiate, which hash algorithm to use.
|
||||
*
|
||||
* This method gives 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.
|
||||
*
|
||||
* @param preferences preferences
|
||||
* @param policy policy
|
||||
* @return selected hash algorithm
|
||||
*/
|
||||
private HashAlgorithm negotiateHashAlgorithm(Set<HashAlgorithm> preferences, Policy policy) {
|
||||
if (hashAlgorithmOverride != null) {
|
||||
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;
|
||||
}
|
||||
|
||||
private PGPSignatureGenerator createSignatureGenerator(PGPPrivateKey privateKey,
|
||||
HashAlgorithm hashAlgorithm,
|
||||
DocumentSignatureType signatureType)
|
||||
|
@ -156,15 +268,37 @@ public final class SigningOptions {
|
|||
return signatureGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a map of key-ids and signing methods.
|
||||
* For internal use.
|
||||
*
|
||||
* @return signing methods
|
||||
*/
|
||||
public Map<SubkeyIdentifier, SigningMethod> getSigningMethods() {
|
||||
return Collections.unmodifiableMap(signingMethods);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override hash algorithm negotiation by dictating which hash algorithm needs to be used.
|
||||
* If no override has been set, an accetable algorithm will be negotiated instead.
|
||||
*
|
||||
* Note: To override the hash algorithm for signing, call this method *before* calling
|
||||
* {@link #addInlineSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)} or
|
||||
* {@link #addDetachedSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)}.
|
||||
*
|
||||
* @param hashAlgorithmOverride override hash algorithm
|
||||
* @return this
|
||||
*/
|
||||
public SigningOptions overrideHashAlgorithm(HashAlgorithm hashAlgorithmOverride) {
|
||||
this.hashAlgorithmOverride = hashAlgorithmOverride;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the hash algorithm override (or null if no override is set).
|
||||
*
|
||||
* @return hash algorithm override
|
||||
*/
|
||||
public HashAlgorithm getHashAlgorithmOverride() {
|
||||
return hashAlgorithmOverride;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,136 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.key.info;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||
|
||||
public abstract class KeyAccessor {
|
||||
|
||||
protected final KeyRingInfo info;
|
||||
protected final SubkeyIdentifier key;
|
||||
|
||||
public KeyAccessor(KeyRingInfo info, SubkeyIdentifier key) {
|
||||
this.info = info;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
/**
|
||||
* Depending on the way we address the key (key-id or user-id), return the respective {@link PGPSignature}
|
||||
* which contains the algorithm preferences we are going to use.
|
||||
*
|
||||
* If we address a key via its user-id, we want to rely on the algorithm preferences in the user-id certification,
|
||||
* while we would instead rely on those in the direct-key signature if we'd address the key by key-id.
|
||||
*
|
||||
* @return signature
|
||||
*/
|
||||
public abstract @Nonnull PGPSignature getSignatureWithPreferences();
|
||||
|
||||
/**
|
||||
* Return preferred symmetric key encryption algorithms.
|
||||
*
|
||||
* @return preferred symmetric algorithms
|
||||
*/
|
||||
public Set<SymmetricKeyAlgorithm> getPreferredSymmetricKeyAlgorithms() {
|
||||
return SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(getSignatureWithPreferences());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return preferred hash algorithms.
|
||||
*
|
||||
* @return preferred hash algorithms
|
||||
*/
|
||||
public Set<HashAlgorithm> getPreferredHashAlgorithms() {
|
||||
return SignatureSubpacketsUtil.parsePreferredHashAlgorithms(getSignatureWithPreferences());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return preferred compression algorithms.
|
||||
*
|
||||
* @return preferred compression algorithms
|
||||
*/
|
||||
public Set<CompressionAlgorithm> getPreferredCompressionAlgorithms() {
|
||||
return SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(getSignatureWithPreferences());
|
||||
}
|
||||
|
||||
/**
|
||||
* Address the key via a user-id (eg "Alice <alice@wonderland.lit>).
|
||||
* In this case we are sourcing preferred algorithms from the user-id certification first.
|
||||
*/
|
||||
public static class ViaUserId extends KeyAccessor {
|
||||
|
||||
private final String userId;
|
||||
|
||||
/**
|
||||
* Access a key via user-id.
|
||||
*
|
||||
* @param info info about a key at a given date
|
||||
* @param key id of the subkey
|
||||
* @param userId user-id
|
||||
*/
|
||||
public ViaUserId(KeyRingInfo info, SubkeyIdentifier key, String userId) {
|
||||
super(info, key);
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull PGPSignature getSignatureWithPreferences() {
|
||||
PGPSignature signature = info.getLatestUserIdCertification(userId);
|
||||
if (signature != null) {
|
||||
return signature;
|
||||
}
|
||||
throw new IllegalStateException("No valid user-id certification signature found for '" + userId + "'.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Address the key via key-id.
|
||||
* In this case we are sourcing preferred algorithms from the keys direct-key signature first.
|
||||
*/
|
||||
public static class ViaKeyId extends KeyAccessor {
|
||||
|
||||
/**
|
||||
* Address the key via key-id.
|
||||
* @param info info about the key at a given date
|
||||
* @param key key-id
|
||||
*/
|
||||
public ViaKeyId(KeyRingInfo info, SubkeyIdentifier key) {
|
||||
super(info, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @Nonnull PGPSignature getSignatureWithPreferences() {
|
||||
PGPSignature signature = info.getLatestDirectKeySelfSignature();
|
||||
if (signature != null) {
|
||||
return signature;
|
||||
}
|
||||
|
||||
signature = info.getLatestUserIdCertification(info.getPrimaryUserId());
|
||||
if (signature == null) {
|
||||
throw new IllegalStateException("No valid signature found.");
|
||||
}
|
||||
return signature;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -43,6 +43,7 @@ import org.pgpainless.algorithm.PublicKeyAlgorithm;
|
|||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.exception.KeyValidationException;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.policy.Policy;
|
||||
import org.pgpainless.signature.SignaturePicker;
|
||||
import org.pgpainless.signature.SignatureUtils;
|
||||
|
@ -633,18 +634,10 @@ public class KeyRingInfo {
|
|||
return signingKeys;
|
||||
}
|
||||
|
||||
public List<HashAlgorithm> getPreferredHashAlgorithms(String userId, long keyID) {
|
||||
PGPSignature signature = getLatestUserIdCertification(userId == null ? getPrimaryUserId() : userId);
|
||||
if (signature == null) {
|
||||
signature = getLatestDirectKeySelfSignature();
|
||||
}
|
||||
if (signature == null) {
|
||||
signature = getCurrentSubkeyBindingSignature(keyID);
|
||||
}
|
||||
if (signature == null) {
|
||||
throw new IllegalStateException("No valid signature.");
|
||||
}
|
||||
return SignatureSubpacketsUtil.parsePreferredHashAlgorithms(signature);
|
||||
public Set<HashAlgorithm> getPreferredHashAlgorithms(String userId, long keyID) {
|
||||
KeyAccessor keyAccessor = userId == null ? new KeyAccessor.ViaKeyId(this, new SubkeyIdentifier(keys, keyID))
|
||||
: new KeyAccessor.ViaUserId(this, new SubkeyIdentifier(keys, keyID), userId);
|
||||
return keyAccessor.getPreferredHashAlgorithms();
|
||||
}
|
||||
|
||||
public static class Signatures {
|
||||
|
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
* Copyright 2021 Paul Schaub.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.pgpainless.key.info;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPSignature;
|
||||
import org.pgpainless.algorithm.CompressionAlgorithm;
|
||||
import org.pgpainless.algorithm.HashAlgorithm;
|
||||
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
|
||||
import org.pgpainless.key.SubkeyIdentifier;
|
||||
import org.pgpainless.signature.subpackets.SignatureSubpacketsUtil;
|
||||
|
||||
public abstract class KeyView {
|
||||
|
||||
protected final KeyRingInfo info;
|
||||
protected final SubkeyIdentifier key;
|
||||
|
||||
public KeyView(KeyRingInfo info, SubkeyIdentifier key) {
|
||||
this.info = info;
|
||||
this.key = key;
|
||||
}
|
||||
|
||||
public abstract PGPSignature getSignatureWithPreferences();
|
||||
|
||||
public List<SymmetricKeyAlgorithm> getPreferredSymmetricKeyAlgorithms() {
|
||||
List<SymmetricKeyAlgorithm> algos = SignatureSubpacketsUtil.parsePreferredSymmetricKeyAlgorithms(getSignatureWithPreferences());
|
||||
return new ArrayList<>(new LinkedHashSet<>(algos)); // remove duplicates
|
||||
}
|
||||
|
||||
public List<HashAlgorithm> getPreferredHashAlgorithms() {
|
||||
List<HashAlgorithm> algos = SignatureSubpacketsUtil.parsePreferredHashAlgorithms(getSignatureWithPreferences());
|
||||
return new ArrayList<>(new LinkedHashSet<>(algos)); // remove duplicates
|
||||
}
|
||||
|
||||
public List<CompressionAlgorithm> getPreferredCompressionAlgorithms() {
|
||||
List<CompressionAlgorithm> algos = SignatureSubpacketsUtil.parsePreferredCompressionAlgorithms(getSignatureWithPreferences());
|
||||
return new ArrayList<>(new LinkedHashSet<>(algos)); // remove duplicates
|
||||
}
|
||||
|
||||
public static class ViaUserId extends KeyView {
|
||||
|
||||
private final String userId;
|
||||
|
||||
public ViaUserId(KeyRingInfo info, SubkeyIdentifier key, String userId) {
|
||||
super(info, key);
|
||||
this.userId = userId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPSignature getSignatureWithPreferences() {
|
||||
return info.getLatestUserIdCertification(userId);
|
||||
}
|
||||
}
|
||||
|
||||
public static class ViaKeyId extends KeyView {
|
||||
|
||||
public ViaKeyId(KeyRingInfo info, SubkeyIdentifier key) {
|
||||
super(info, key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public PGPSignature getSignatureWithPreferences() {
|
||||
PGPSignature signature = info.getLatestDirectKeySelfSignature();
|
||||
if (signature != null) {
|
||||
return signature;
|
||||
}
|
||||
|
||||
return info.getLatestUserIdCertification(info.getPrimaryUserId());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -18,7 +18,9 @@ package org.pgpainless.signature.subpackets;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
@ -206,8 +208,8 @@ public class SignatureSubpacketsUtil {
|
|||
return hashed(signature, SignatureSubpacket.preferredSymmetricAlgorithms);
|
||||
}
|
||||
|
||||
public static List<SymmetricKeyAlgorithm> parsePreferredSymmetricKeyAlgorithms(PGPSignature signature) {
|
||||
List<SymmetricKeyAlgorithm> algorithms = new ArrayList<>();
|
||||
public static Set<SymmetricKeyAlgorithm> parsePreferredSymmetricKeyAlgorithms(PGPSignature signature) {
|
||||
Set<SymmetricKeyAlgorithm> algorithms = new LinkedHashSet<>();
|
||||
PreferredAlgorithms preferences = getPreferredSymmetricAlgorithms(signature);
|
||||
if (preferences != null) {
|
||||
for (int code : preferences.getPreferences()) {
|
||||
|
@ -227,8 +229,8 @@ public class SignatureSubpacketsUtil {
|
|||
return hashed(signature, SignatureSubpacket.preferredHashAlgorithms);
|
||||
}
|
||||
|
||||
public static List<HashAlgorithm> parsePreferredHashAlgorithms(PGPSignature signature) {
|
||||
List<HashAlgorithm> algorithms = new ArrayList<>();
|
||||
public static Set<HashAlgorithm> parsePreferredHashAlgorithms(PGPSignature signature) {
|
||||
Set<HashAlgorithm> algorithms = new LinkedHashSet<>();
|
||||
PreferredAlgorithms preferences = getPreferredHashAlgorithms(signature);
|
||||
if (preferences != null) {
|
||||
for (int code : preferences.getPreferences()) {
|
||||
|
@ -248,8 +250,8 @@ public class SignatureSubpacketsUtil {
|
|||
return hashed(signature, SignatureSubpacket.preferredCompressionAlgorithms);
|
||||
}
|
||||
|
||||
public static List<CompressionAlgorithm> parsePreferredCompressionAlgorithms(PGPSignature signature) {
|
||||
List<CompressionAlgorithm> algorithms = new ArrayList<>();
|
||||
public static Set<CompressionAlgorithm> parsePreferredCompressionAlgorithms(PGPSignature signature) {
|
||||
Set<CompressionAlgorithm> algorithms = new LinkedHashSet<>();
|
||||
PreferredAlgorithms preferences = getPreferredCompressionAlgorithms(signature);
|
||||
if (preferences != null) {
|
||||
for (int code : preferences.getPreferences()) {
|
||||
|
|
Loading…
Reference in a new issue