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 deleted file mode 100644 index 34d4bbcf..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/SigningOptions.java +++ /dev/null @@ -1,634 +0,0 @@ -// SPDX-FileCopyrightText: 2021 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.encryption_signing; - -import java.util.Collections; -import java.util.Date; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import javax.annotation.Nonnull; -import javax.annotation.Nullable; - -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPPrivateKey; -import org.bouncycastle.openpgp.PGPPublicKey; -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.algorithm.PublicKeyAlgorithm; -import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator; -import org.pgpainless.exception.KeyException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.key.OpenPgpFingerprint; -import org.pgpainless.key.SubkeyIdentifier; -import org.pgpainless.key.info.KeyRingInfo; -import org.pgpainless.key.protection.SecretKeyRingProtector; -import org.pgpainless.key.protection.UnlockSecretKey; -import org.pgpainless.policy.Policy; -import org.pgpainless.signature.subpackets.BaseSignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpackets; -import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper; - -public final class SigningOptions { - - /** - * A method of signing. - */ - public static final class SigningMethod { - private final PGPSignatureGenerator signatureGenerator; - private final boolean detached; - private final HashAlgorithm hashAlgorithm; - - private SigningMethod(@Nonnull PGPSignatureGenerator signatureGenerator, - boolean detached, - @Nonnull HashAlgorithm hashAlgorithm) { - this.signatureGenerator = signatureGenerator; - this.detached = detached; - this.hashAlgorithm = hashAlgorithm; - } - - /** - * Inline-signature method. - * The resulting signature will be written into the message itself, together with a one-pass-signature packet. - * - * @param signatureGenerator signature generator - * @param hashAlgorithm hash algorithm used to generate the signature - * @return inline signing method - */ - public static SigningMethod inlineSignature(@Nonnull PGPSignatureGenerator signatureGenerator, - @Nonnull HashAlgorithm hashAlgorithm) { - return new SigningMethod(signatureGenerator, false, hashAlgorithm); - } - - /** - * 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 - * @param hashAlgorithm hash algorithm used to generate the signature - * @return detached signing method - */ - public static SigningMethod detachedSignature(@Nonnull PGPSignatureGenerator signatureGenerator, - @Nonnull HashAlgorithm hashAlgorithm) { - return new SigningMethod(signatureGenerator, true, hashAlgorithm); - } - - public boolean isDetached() { - return detached; - } - - public PGPSignatureGenerator getSignatureGenerator() { - return signatureGenerator; - } - - public HashAlgorithm getHashAlgorithm() { - return hashAlgorithm; - } - } - - private final Map signingMethods = new HashMap<>(); - private HashAlgorithm hashAlgorithmOverride; - private Date evaluationDate = new Date(); - - @Nonnull - public static SigningOptions get() { - return new SigningOptions(); - } - - /** - * Override the evaluation date for signing keys with the given date. - * - * @param evaluationDate new evaluation date - * @return this - */ - public SigningOptions setEvaluationDate(@Nonnull Date evaluationDate) { - this.evaluationDate = evaluationDate; - return this; - } - - /** - * Sign the message using an inline signature made by the provided signing key. - * - * @param signingKeyProtector protector to unlock the signing key - * @param signingKey key ring containing the signing key - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be unlocked or a signing method cannot be created - */ - @Nonnull - public SigningOptions addSignature(@Nonnull SecretKeyRingProtector signingKeyProtector, - @Nonnull PGPSecretKeyRing signingKey) - throws PGPException { - return addInlineSignature(signingKeyProtector, signingKey, DocumentSignatureType.BINARY_DOCUMENT); - } - - /** - * Add inline signatures with all secret key rings in the provided secret key ring collection. - * - * @param secrectKeyDecryptor decryptor to unlock the signing secret keys - * @param signingKeys collection of signing keys - * @param signatureType type of signature (binary, canonical text) - * @return this - * - * @throws KeyException if something is wrong with any of the keys - * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created - */ - @Nonnull - public SigningOptions addInlineSignatures(@Nonnull SecretKeyRingProtector secrectKeyDecryptor, - @Nonnull Iterable signingKeys, - @Nonnull DocumentSignatureType signatureType) - throws KeyException, PGPException { - for (PGPSecretKeyRing signingKey : signingKeys) { - addInlineSignature(secrectKeyDecryptor, signingKey, signatureType); - } - return this; - } - - /** - * 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) - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be unlocked or the signing method cannot be created - */ - @Nonnull - public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nonnull DocumentSignatureType signatureType) - throws KeyException, PGPException { - return addInlineSignature(secretKeyDecryptor, secretKey, null, 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 KeyException if something is wrong with the key - * @throws PGPException if the key cannot be unlocked or the signing method cannot be created - */ - @Nonnull - public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nullable CharSequence userId, - @Nonnull DocumentSignatureType signatureType) - throws KeyException, PGPException { - return addInlineSignature(secretKeyDecryptor, secretKey, userId, signatureType, null); - } - - /** - * 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) - * @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature - * @return this - * - * @throws KeyException if the key is invalid - * @throws PGPException if the key cannot be unlocked or the signing method cannot be created - */ - @Nonnull - public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nullable CharSequence userId, - @Nonnull DocumentSignatureType signatureType, - @Nullable BaseSignatureSubpackets.Callback subpacketsCallback) - throws KeyException, PGPException { - KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate); - if (userId != null && !keyRingInfo.isUserIdValid(userId)) { - throw new KeyException.UnboundUserIdException( - OpenPgpFingerprint.of(secretKey), - userId.toString(), - keyRingInfo.getLatestUserIdCertification(userId), - keyRingInfo.getUserIdRevocation(userId) - ); - } - - List signingPubKeys = keyRingInfo.getSigningSubkeys(); - if (signingPubKeys.isEmpty()) { - throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey)); - } - - for (PGPPublicKey signingPubKey : signingPubKeys) { - PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID()); - if (signingSecKey == null) { - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID()); - } - PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor); - Set hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId) - : keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID()); - HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy()); - addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false); - } - - return this; - } - - /** - * Create a binary inline signature using the signing key with the given keyId. - * - * @param secretKeyDecryptor decryptor to unlock the secret key - * @param secretKey secret key ring - * @param keyId keyId of the signing (sub-)key - * @return builder - * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. - * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys - * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key - */ - @Nonnull - public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - long keyId) throws PGPException { - return addInlineSignature(secretKeyDecryptor, secretKey, keyId, DocumentSignatureType.BINARY_DOCUMENT, null); - } - - - /** - * Create an inline signature using the signing key with the given keyId. - * - * @param secretKeyDecryptor decryptor to unlock the secret key - * @param secretKey secret key ring - * @param keyId keyId of the signing (sub-)key - * @param signatureType signature type - * @param subpacketsCallback callback to modify the signatures subpackets - * @return builder - * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. - * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys - * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key - */ - @Nonnull - public SigningOptions addInlineSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - long keyId, - @Nonnull DocumentSignatureType signatureType, - @Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws PGPException { - KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate); - - List signingPubKeys = keyRingInfo.getSigningSubkeys(); - if (signingPubKeys.isEmpty()) { - throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey)); - } - - for (PGPPublicKey signingPubKey : signingPubKeys) { - if (signingPubKey.getKeyID() == keyId) { - - PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID()); - if (signingSecKey == null) { - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID()); - } - PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor); - Set hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID()); - HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy()); - addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false); - return this; - } - } - - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), keyId); - } - - /** - * Add detached signatures with all key rings from the provided secret key ring collection. - * - * @param secretKeyDecryptor decryptor to unlock the secret signing keys - * @param signingKeys collection of signing key rings - * @param signatureType type of the signature (binary, canonical text) - * @return this - * - * @throws KeyException if something is wrong with any of the keys - * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created - */ - @Nonnull - public SigningOptions addDetachedSignatures(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull Iterable signingKeys, - @Nonnull DocumentSignatureType signatureType) - throws PGPException { - for (PGPSecretKeyRing signingKey : signingKeys) { - addDetachedSignature(secretKeyDecryptor, signingKey, signatureType); - } - return this; - } - - /** - * Create a detached signature. - * The signature will be of type {@link DocumentSignatureType#BINARY_DOCUMENT}. - * - * @param secretKeyDecryptor decryptor to unlock the secret signing key - * @param signingKey signing key - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing signingKey) - throws PGPException { - return addDetachedSignature(secretKeyDecryptor, signingKey, DocumentSignatureType.BINARY_DOCUMENT); - } - - /** - * 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 (e.g. 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) - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nonnull DocumentSignatureType signatureType) - throws PGPException { - return addDetachedSignature(secretKeyDecryptor, secretKey, null, 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 (e.g. 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) - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nullable CharSequence userId, - @Nonnull DocumentSignatureType signatureType) - throws PGPException { - return addDetachedSignature(secretKeyDecryptor, secretKey, userId, signatureType, null); - } - - /** - * 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 (e.g. 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) - * @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature - * @return this - * - * @throws KeyException if something is wrong with the key - * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - @Nullable CharSequence userId, - @Nonnull DocumentSignatureType signatureType, - @Nullable BaseSignatureSubpackets.Callback subpacketCallback) - throws PGPException { - KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate); - if (userId != null && !keyRingInfo.isUserIdValid(userId)) { - throw new KeyException.UnboundUserIdException( - OpenPgpFingerprint.of(secretKey), - userId.toString(), - keyRingInfo.getLatestUserIdCertification(userId), - keyRingInfo.getUserIdRevocation(userId) - ); - } - - List signingPubKeys = keyRingInfo.getSigningSubkeys(); - if (signingPubKeys.isEmpty()) { - throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey)); - } - - for (PGPPublicKey signingPubKey : signingPubKeys) { - PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID()); - if (signingSecKey == null) { - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID()); - } - PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor); - Set hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId) - : keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID()); - HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy()); - addSigningMethod(secretKey, signingSubkey, subpacketCallback, hashAlgorithm, signatureType, true); - } - - return this; - } - - /** - * Create a detached binary signature using the signing key with the given keyId. - * - * @param secretKeyDecryptor decryptor to unlock the secret key - * @param secretKey secret key ring - * @param keyId keyId of the signing (sub-)key - * @return builder - * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. - * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys - * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - long keyId) throws PGPException { - return addDetachedSignature(secretKeyDecryptor, secretKey, keyId, DocumentSignatureType.BINARY_DOCUMENT, null); - } - - /** - * Create a detached signature using the signing key with the given keyId. - * - * @param secretKeyDecryptor decryptor to unlock the secret key - * @param secretKey secret key ring - * @param keyId keyId of the signing (sub-)key - * @param signatureType signature type - * @param subpacketsCallback callback to modify the signatures subpackets - * @return builder - * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. - * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys - * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key - */ - @Nonnull - public SigningOptions addDetachedSignature(@Nonnull SecretKeyRingProtector secretKeyDecryptor, - @Nonnull PGPSecretKeyRing secretKey, - long keyId, - @Nonnull DocumentSignatureType signatureType, - @Nullable BaseSignatureSubpackets.Callback subpacketsCallback) throws PGPException { - KeyRingInfo keyRingInfo = PGPainless.inspectKeyRing(secretKey, evaluationDate); - - List signingPubKeys = keyRingInfo.getSigningSubkeys(); - if (signingPubKeys.isEmpty()) { - throw new KeyException.UnacceptableSigningKeyException(OpenPgpFingerprint.of(secretKey)); - } - - for (PGPPublicKey signingPubKey : signingPubKeys) { - if (signingPubKey.getKeyID() == keyId) { - - PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID()); - if (signingSecKey == null) { - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), signingPubKey.getKeyID()); - } - PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor); - Set hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID()); - HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy()); - addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, true); - return this; - } - } - - throw new KeyException.MissingSecretKeyException(OpenPgpFingerprint.of(secretKey), keyId); - } - - private void addSigningMethod(@Nonnull PGPSecretKeyRing secretKey, - @Nonnull PGPPrivateKey signingSubkey, - @Nullable BaseSignatureSubpackets.Callback subpacketCallback, - @Nonnull HashAlgorithm hashAlgorithm, - @Nonnull DocumentSignatureType signatureType, - boolean detached) - throws PGPException { - SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(secretKey, signingSubkey.getKeyID()); - PGPSecretKey signingSecretKey = secretKey.getSecretKey(signingSubkey.getKeyID()); - PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.requireFromId(signingSecretKey.getPublicKey().getAlgorithm()); - int bitStrength = signingSecretKey.getPublicKey().getBitStrength(); - if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) { - throw new KeyException.UnacceptableSigningKeyException( - new KeyException.PublicKeyAlgorithmPolicyException( - OpenPgpFingerprint.of(secretKey), signingSecretKey.getKeyID(), publicKeyAlgorithm, bitStrength)); - } - - PGPSignatureGenerator generator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType); - - // Subpackets - SignatureSubpackets hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.getPublicKey()); - SignatureSubpackets unhashedSubpackets = SignatureSubpackets.createEmptySubpackets(); - if (subpacketCallback != null) { - subpacketCallback.modifyHashedSubpackets(hashedSubpackets); - subpacketCallback.modifyUnhashedSubpackets(unhashedSubpackets); - } - generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets)); - generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets)); - - SigningMethod signingMethod = detached ? - SigningMethod.detachedSignature(generator, hashAlgorithm) : - SigningMethod.inlineSignature(generator, hashAlgorithm); - signingMethods.put(signingKeyIdentifier, signingMethod); - } - - /** - * Negotiate, which hash algorithm to use. - *

- * 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. - * - * @param preferences preferences - * @param policy policy - * @return selected hash algorithm - */ - @Nonnull - private HashAlgorithm negotiateHashAlgorithm(@Nonnull Set preferences, - @Nonnull Policy policy) { - if (hashAlgorithmOverride != null) { - return hashAlgorithmOverride; - } - - return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(policy) - .negotiateHashAlgorithm(preferences); - } - - @Nonnull - private PGPSignatureGenerator createSignatureGenerator(@Nonnull PGPPrivateKey privateKey, - @Nonnull HashAlgorithm hashAlgorithm, - @Nonnull DocumentSignatureType signatureType) - throws PGPException { - int publicKeyAlgorithm = privateKey.getPublicKeyPacket().getAlgorithm(); - PGPContentSignerBuilder signerBuilder = ImplementationFactory.getInstance() - .getPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm.getAlgorithmId()); - PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder); - signatureGenerator.init(signatureType.getSignatureType().getCode(), privateKey); - - return signatureGenerator; - } - - /** - * Return a map of key-ids and signing methods. - * For internal use. - * - * @return signing methods - */ - @Nonnull - Map 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 - */ - @Nonnull - public SigningOptions overrideHashAlgorithm(@Nonnull HashAlgorithm hashAlgorithmOverride) { - this.hashAlgorithmOverride = hashAlgorithmOverride; - return this; - } - - /** - * Return the hash algorithm override (or null if no override is set). - * - * @return hash algorithm override - */ - @Nullable - public HashAlgorithm getHashAlgorithmOverride() { - return hashAlgorithmOverride; - } -} diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt new file mode 100644 index 00000000..b9208ed5 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/encryption_signing/SigningOptions.kt @@ -0,0 +1,451 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.encryption_signing + +import org.bouncycastle.openpgp.* +import org.pgpainless.PGPainless.Companion.getPolicy +import org.pgpainless.PGPainless.Companion.inspectKeyRing +import org.pgpainless.algorithm.DocumentSignatureType +import org.pgpainless.algorithm.HashAlgorithm +import org.pgpainless.algorithm.PublicKeyAlgorithm.Companion.requireFromId +import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator.Companion.negotiateSignatureHashAlgorithm +import org.pgpainless.exception.KeyException +import org.pgpainless.exception.KeyException.* +import org.pgpainless.implementation.ImplementationFactory +import org.pgpainless.key.OpenPgpFingerprint.Companion.of +import org.pgpainless.key.SubkeyIdentifier +import org.pgpainless.key.protection.SecretKeyRingProtector +import org.pgpainless.key.protection.UnlockSecretKey.Companion.unlockSecretKey +import org.pgpainless.policy.Policy +import org.pgpainless.signature.subpackets.BaseSignatureSubpackets.Callback +import org.pgpainless.signature.subpackets.SignatureSubpackets +import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper +import java.util.* + +class SigningOptions { + + val signingMethods: Map = mutableMapOf() + private var _hashAlgorithmOverride: HashAlgorithm? = null + private var _evaluationDate: Date = Date() + + val hashAlgorithmOverride: HashAlgorithm? + get() = _hashAlgorithmOverride + + /** + * Override hash algorithm negotiation by dictating which hash algorithm needs to be used. + * If no override has been set, an acceptable algorithm will be negotiated instead. + * Note: To override the hash algorithm for signing, call this method *before* calling + * [addInlineSignature] or [addDetachedSignature]. + * + * @param hashAlgorithmOverride override hash algorithm + * @return this + */ + fun overrideHashAlgorithm(hashAlgorithmOverride: HashAlgorithm) = apply { + _hashAlgorithmOverride = hashAlgorithmOverride + } + + val evaluationDate: Date + get() = _evaluationDate + + /** + * Override the evaluation date for signing keys with the given date. + * + * @param evaluationDate new evaluation date + * @return this + */ + fun setEvaluationDate(evaluationDate: Date) = apply { + _evaluationDate = evaluationDate + } + + /** + * Sign the message using an inline signature made by the provided signing key. + * + * @param signingKeyProtector protector to unlock the signing key + * @param signingKey key ring containing the signing key + * @return this + * + * @throws KeyException if something is wrong with the key + * @throws PGPException if the key cannot be unlocked or a signing method cannot be created + */ + @Throws(KeyException::class, PGPException::class) + fun addSignature(signingKeyProtector: SecretKeyRingProtector, signingKey: PGPSecretKeyRing) = apply { + addInlineSignature(signingKeyProtector, signingKey, null, DocumentSignatureType.BINARY_DOCUMENT) + } + + /** + * Add inline signatures with all secret key rings in the provided secret key ring collection. + * + * @param signingKeyProtector decryptor to unlock the signing secret keys + * @param signingKeys collection of signing keys + * @param signatureType type of signature (binary, canonical text) + * @return this + * + * @throws KeyException if something is wrong with any of the keys + * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created + */ + @Throws(KeyException::class, PGPException::class) + fun addInlineSignatures(signingKeyProtector: SecretKeyRingProtector, + signingKeys: Iterable, + signatureType: DocumentSignatureType) = apply { + signingKeys.forEach { + addInlineSignature(signingKeyProtector, it, null, 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 signingKeyProtector decryptor to unlock the signing secret key + * @param signingKey signing key + * @param signatureType type of signature (binary, canonical text) + * @return this + * + * @throws KeyException if something is wrong with the key + * @throws PGPException if the key cannot be unlocked or the signing method cannot be created + */ + @Throws(KeyException::class, PGPException::class) + fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + signatureType: DocumentSignatureType) = apply { + addInlineSignature(signingKeyProtector, signingKey, null, 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 signingKeyProtector decryptor to unlock the signing secret key + * @param signingKey signing key + * @param userId user-id of the signer + * @param signatureType signature type (binary, canonical text) + * @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature + * @return this + * + * @throws KeyException if the key is invalid + * @throws PGPException if the key cannot be unlocked or the signing method cannot be created + */ + @Throws(KeyException::class, PGPException::class) + @JvmOverloads + fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + userId: CharSequence? = null, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null) = apply { + val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) + if (userId != null && !keyRingInfo.isUserIdValid(userId)) { + throw UnboundUserIdException( + of(signingKey), + userId.toString(), + keyRingInfo.getLatestUserIdCertification(userId), + keyRingInfo.getUserIdRevocation(userId) + ) + } + + val signingPubKeys = keyRingInfo.signingSubkeys + if (signingPubKeys.isEmpty()) { + throw UnacceptableSigningKeyException(of(signingKey)) + } + + for (signingPubKey in signingPubKeys) { + val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) + ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) + val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector) + val hashAlgorithms = + if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) + else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) + } + } + + /** + * Create an inline signature using the signing key with the given keyId. + * + * @param signingKeyProtector decryptor to unlock the secret key + * @param signingKey secret key ring + * @param keyId keyId of the signing (sub-)key + * @param signatureType signature type + * @param subpacketsCallback callback to modify the signatures subpackets + * @return builder + * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. + * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys + * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key + */ + @Throws(KeyException::class, PGPException::class) + @JvmOverloads + fun addInlineSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + keyId: Long, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null) = apply { + val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) + val signingPubKeys = keyRingInfo.signingSubkeys + if (signingPubKeys.isEmpty()) { + throw UnacceptableSigningKeyException(of(signingKey)) + } + + for (signingPubKey in signingPubKeys) { + if (signingPubKey.keyID != keyId) { + continue + } + + val signingSecKey = signingKey.getSecretKey(signingPubKey.keyID) + ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) + val signingSubkey = unlockSecretKey(signingSecKey, signingKeyProtector) + val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, false, subpacketsCallback) + return this + } + throw MissingSecretKeyException(of(signingKey), keyId) + } + + /** + * Add detached signatures with all key rings from the provided secret key ring collection. + * + * @param signingKeyProtector decryptor to unlock the secret signing keys + * @param signingKeys collection of signing key rings + * @param signatureType type of the signature (binary, canonical text) + * @return this + * + * @throws KeyException if something is wrong with any of the keys + * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created + */ + @Throws(KeyException::class, PGPException::class) + fun addDetachedSignatures(signingKeyProtector: SecretKeyRingProtector, + signingKeys: Iterable, + signatureType: DocumentSignatureType) = apply { + signingKeys.forEach { + addDetachedSignature(signingKeyProtector, it, null, 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 (e.g. when signing a file). + * + * @param signingKeyProtector decryptor to unlock the secret signing key + * @param signingKey signing key + * @param signatureType type of data that is signed (binary, canonical text) + * @return this + * + * @throws KeyException if something is wrong with the key + * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created + */ + @Throws(KeyException::class, PGPException::class) + fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + signatureType: DocumentSignatureType) = apply { + addDetachedSignature(signingKeyProtector, signingKey, null, 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 (e.g. when signing a file). + *

+ * This method uses the passed in user-id to select user-specific hash algorithms. + * + * @param signingKeyProtector decryptor to unlock the secret signing key + * @param signingKey signing key + * @param userId user-id + * @param signatureType type of data that is signed (binary, canonical text) + * @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature + * @return this + * + * @throws KeyException if something is wrong with the key + * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created + */ + @JvmOverloads + @Throws(KeyException::class, PGPException::class) + fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + userId: String? = null, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketCallback: Callback? = null) = apply { + val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) + if (userId != null && !keyRingInfo.isUserIdValid(userId)) { + throw UnboundUserIdException( + of(signingKey), + userId.toString(), + keyRingInfo.getLatestUserIdCertification(userId), + keyRingInfo.getUserIdRevocation(userId) + ) + } + + val signingPubKeys = keyRingInfo.signingSubkeys + if (signingPubKeys.isEmpty()) { + throw UnacceptableSigningKeyException(of(signingKey)) + } + + for (signingPubKey in signingPubKeys) { + val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) + ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) + val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector) + val hashAlgorithms = + if (userId != null) keyRingInfo.getPreferredHashAlgorithms(userId) + else keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketCallback) + } + } + + /** + * Create a detached signature using the signing key with the given keyId. + * + * @param signingKeyProtector decryptor to unlock the secret key + * @param signingKey secret key ring + * @param keyId keyId of the signing (sub-)key + * @param signatureType signature type + * @param subpacketsCallback callback to modify the signatures subpackets + * @return builder + * @throws PGPException if the secret key cannot be unlocked or if no signing method can be created. + * @throws KeyException.UnacceptableSigningKeyException if the key ring does not carry any signing-capable subkeys + * @throws KeyException.MissingSecretKeyException if the key ring does not contain the identified secret key + */ + @Throws(KeyException::class, PGPException::class) + @JvmOverloads + fun addDetachedSignature(signingKeyProtector: SecretKeyRingProtector, + signingKey: PGPSecretKeyRing, + keyId: Long, + signatureType: DocumentSignatureType = DocumentSignatureType.BINARY_DOCUMENT, + subpacketsCallback: Callback? = null) = apply { + val keyRingInfo = inspectKeyRing(signingKey, evaluationDate) + + val signingPubKeys = keyRingInfo.signingSubkeys + if (signingPubKeys.isEmpty()) { + throw UnacceptableSigningKeyException(of(signingKey)) + } + + for (signingPubKey in signingPubKeys) { + if (signingPubKey.keyID == keyId) { + val signingSecKey: PGPSecretKey = signingKey.getSecretKey(signingPubKey.keyID) + ?: throw MissingSecretKeyException(of(signingKey), signingPubKey.keyID) + val signingSubkey: PGPPrivateKey = unlockSecretKey(signingSecKey, signingKeyProtector) + val hashAlgorithms = keyRingInfo.getPreferredHashAlgorithms(signingPubKey.keyID) + val hashAlgorithm: HashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, getPolicy()) + addSigningMethod(signingKey, signingSubkey, hashAlgorithm, signatureType, true, subpacketsCallback) + return this + } + } + + throw MissingSecretKeyException(of(signingKey), keyId) + } + + private fun addSigningMethod(signingKey: PGPSecretKeyRing, + signingSubkey: PGPPrivateKey, + hashAlgorithm: HashAlgorithm, + signatureType: DocumentSignatureType, + detached: Boolean, + subpacketCallback: Callback? = null) { + val signingKeyIdentifier = SubkeyIdentifier(signingKey, signingSubkey.keyID) + val signingSecretKey: PGPSecretKey = signingKey.getSecretKey(signingSubkey.keyID) + val publicKeyAlgorithm = requireFromId(signingSecretKey.publicKey.algorithm) + val bitStrength = signingSecretKey.publicKey.bitStrength + if (!getPolicy().publicKeyAlgorithmPolicy.isAcceptable(publicKeyAlgorithm, bitStrength)) { + throw UnacceptableSigningKeyException( + PublicKeyAlgorithmPolicyException( + of(signingKey), signingSecretKey.keyID, publicKeyAlgorithm, bitStrength)) + } + + val generator: PGPSignatureGenerator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType) + + // Subpackets + val hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.publicKey) + val unhashedSubpackets = SignatureSubpackets.createEmptySubpackets() + if (subpacketCallback != null) { + subpacketCallback.modifyHashedSubpackets(hashedSubpackets) + subpacketCallback.modifyUnhashedSubpackets(unhashedSubpackets) + } + generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets)) + generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets)) + + val signingMethod = + if (detached) SigningMethod.detachedSignature(generator, hashAlgorithm) + else SigningMethod.inlineSignature(generator, hashAlgorithm) + (signingMethods as MutableMap)[signingKeyIdentifier] = signingMethod + } + + /** + * Negotiate, which hash algorithm to use. + * + * + * This method gives the highest priority to the algorithm override, which can be set via [.overrideHashAlgorithm]. + * After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm. + * Lastly, should no acceptable algorithm be found, the [Policies][Policy] default signature hash algorithm is + * used as a fallback. + * + * @param preferences preferences + * @param policy policy + * @return selected hash algorithm + */ + private fun negotiateHashAlgorithm(preferences: Set, + policy: Policy): HashAlgorithm { + return _hashAlgorithmOverride ?: negotiateSignatureHashAlgorithm(policy).negotiateHashAlgorithm(preferences) + } + + @Throws(PGPException::class) + private fun createSignatureGenerator(privateKey: PGPPrivateKey, + hashAlgorithm: HashAlgorithm, + signatureType: DocumentSignatureType): PGPSignatureGenerator { + return ImplementationFactory.getInstance() + .getPGPContentSignerBuilder(privateKey.publicKeyPacket.algorithm, hashAlgorithm.algorithmId) + .let { csb -> + PGPSignatureGenerator(csb).also { it.init(signatureType.signatureType.code, privateKey) } + } + } + + + companion object { + @JvmStatic + fun get() = SigningOptions() + } + + /** + * A method of signing. + */ + class SigningMethod private constructor( + val signatureGenerator: PGPSignatureGenerator, + val isDetached: Boolean, + val hashAlgorithm: HashAlgorithm + ) { + companion object { + + /** + * Inline-signature method. + * The resulting signature will be written into the message itself, together with a one-pass-signature packet. + * + * @param signatureGenerator signature generator + * @param hashAlgorithm hash algorithm used to generate the signature + * @return inline signing method + */ + @JvmStatic + fun inlineSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) = + SigningMethod(signatureGenerator, false, hashAlgorithm) + + /** + * 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 + * @param hashAlgorithm hash algorithm used to generate the signature + * @return detached signing method + */ + @JvmStatic + fun detachedSignature(signatureGenerator: PGPSignatureGenerator, hashAlgorithm: HashAlgorithm) = + SigningMethod(signatureGenerator, true, hashAlgorithm) + } + } +} \ No newline at end of file