001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.encryption_signing;
006
007import java.util.Collections;
008import java.util.Date;
009import java.util.HashMap;
010import java.util.List;
011import java.util.Map;
012import java.util.Set;
013import javax.annotation.Nullable;
014
015import org.bouncycastle.openpgp.PGPException;
016import org.bouncycastle.openpgp.PGPPrivateKey;
017import org.bouncycastle.openpgp.PGPPublicKey;
018import org.bouncycastle.openpgp.PGPSecretKey;
019import org.bouncycastle.openpgp.PGPSecretKeyRing;
020import org.bouncycastle.openpgp.PGPSignatureGenerator;
021import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
022import org.pgpainless.PGPainless;
023import org.pgpainless.algorithm.DocumentSignatureType;
024import org.pgpainless.algorithm.HashAlgorithm;
025import org.pgpainless.algorithm.PublicKeyAlgorithm;
026import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator;
027import org.pgpainless.exception.KeyCannotSignException;
028import org.pgpainless.exception.KeyValidationError;
029import org.pgpainless.implementation.ImplementationFactory;
030import org.pgpainless.key.OpenPgpFingerprint;
031import org.pgpainless.key.SubkeyIdentifier;
032import org.pgpainless.key.info.KeyRingInfo;
033import org.pgpainless.key.protection.SecretKeyRingProtector;
034import org.pgpainless.key.protection.UnlockSecretKey;
035import org.pgpainless.policy.Policy;
036import org.pgpainless.signature.subpackets.BaseSignatureSubpackets;
037import org.pgpainless.signature.subpackets.SignatureSubpackets;
038import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper;
039
040public final class SigningOptions {
041
042    /**
043     * A method of signing.
044     */
045    public static final class SigningMethod {
046        private final PGPSignatureGenerator signatureGenerator;
047        private final boolean detached;
048        private final HashAlgorithm hashAlgorithm;
049
050        private SigningMethod(PGPSignatureGenerator signatureGenerator, boolean detached, HashAlgorithm hashAlgorithm) {
051            this.signatureGenerator = signatureGenerator;
052            this.detached = detached;
053            this.hashAlgorithm = hashAlgorithm;
054        }
055
056        /**
057         * Inline-signature method.
058         * The resulting signature will be written into the message itself, together with a one-pass-signature packet.
059         *
060         * @param signatureGenerator signature generator
061         * @return inline signing method
062         */
063        public static SigningMethod inlineSignature(PGPSignatureGenerator signatureGenerator, HashAlgorithm hashAlgorithm) {
064            return new SigningMethod(signatureGenerator, false, hashAlgorithm);
065        }
066
067        /**
068         * Detached signing method.
069         * The resulting signature will not be added to the message, and instead can be distributed separately
070         * to the signed message.
071         *
072         * @param signatureGenerator signature generator
073         * @return detached signing method
074         */
075        public static SigningMethod detachedSignature(PGPSignatureGenerator signatureGenerator, HashAlgorithm hashAlgorithm) {
076            return new SigningMethod(signatureGenerator, true, hashAlgorithm);
077        }
078
079        public boolean isDetached() {
080            return detached;
081        }
082
083        public PGPSignatureGenerator getSignatureGenerator() {
084            return signatureGenerator;
085        }
086
087        public HashAlgorithm getHashAlgorithm() {
088            return hashAlgorithm;
089        }
090    }
091
092    private final Map<SubkeyIdentifier, SigningMethod> signingMethods = new HashMap<>();
093    private HashAlgorithm hashAlgorithmOverride;
094
095    public static SigningOptions get() {
096        return new SigningOptions();
097    }
098
099    /**
100     * Add inline signatures with all secret key rings in the provided secret key ring collection.
101     *
102     * @param secrectKeyDecryptor decryptor to unlock the signing secret keys
103     * @param signingKeys collection of signing keys
104     * @param signatureType type of signature (binary, canonical text)
105     * @return this
106     * @throws KeyValidationError if something is wrong with any of the keys
107     * @throws PGPException if any of the keys cannot be unlocked or a signing method cannot be created
108     */
109    public SigningOptions addInlineSignatures(SecretKeyRingProtector secrectKeyDecryptor,
110                                              Iterable<PGPSecretKeyRing> signingKeys,
111                                              DocumentSignatureType signatureType)
112            throws KeyValidationError, PGPException {
113        for (PGPSecretKeyRing signingKey : signingKeys) {
114            addInlineSignature(secrectKeyDecryptor, signingKey, signatureType);
115        }
116        return this;
117    }
118
119    /**
120     * Add an inline-signature.
121     * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
122     * of one-pass-signature packets.
123     *
124     * @param secretKeyDecryptor decryptor to unlock the signing secret key
125     * @param secretKey signing key
126     * @param signatureType type of signature (binary, canonical text)
127     * @throws KeyValidationError if something is wrong with the key
128     * @throws PGPException if the key cannot be unlocked or the signing method cannot be created
129     * @return this
130     */
131    public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
132                                             PGPSecretKeyRing secretKey,
133                                             DocumentSignatureType signatureType)
134            throws KeyValidationError, PGPException {
135        return addInlineSignature(secretKeyDecryptor, secretKey, null, signatureType);
136    }
137
138    /**
139     * Add an inline-signature.
140     * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
141     * of one-pass-signature packets.
142     *
143     * This method uses the passed in user-id to select user-specific hash algorithms.
144     *
145     * @param secretKeyDecryptor decryptor to unlock the signing secret key
146     * @param secretKey signing key
147     * @param userId user-id of the signer
148     * @param signatureType signature type (binary, canonical text)
149     * @return this
150     * @throws KeyValidationError if the key is invalid
151     * @throws PGPException if the key cannot be unlocked or the signing method cannot be created
152     */
153    public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
154                                             PGPSecretKeyRing secretKey,
155                                             String userId,
156                                             DocumentSignatureType signatureType)
157            throws KeyValidationError, PGPException {
158        return addInlineSignature(secretKeyDecryptor, secretKey, userId, signatureType, null);
159    }
160
161    /**
162     * Add an inline-signature.
163     * Inline signatures are being embedded into the message itself and can be processed in one pass, thanks to the use
164     * of one-pass-signature packets.
165     *
166     * This method uses the passed in user-id to select user-specific hash algorithms.
167     *
168     * @param secretKeyDecryptor decryptor to unlock the signing secret key
169     * @param secretKey signing key
170     * @param userId user-id of the signer
171     * @param signatureType signature type (binary, canonical text)
172     * @param subpacketsCallback callback to modify the hashed and unhashed subpackets of the signature
173     * @return this
174     * @throws KeyValidationError if the key is invalid
175     * @throws PGPException if the key cannot be unlocked or the signing method cannot be created
176     */
177    public SigningOptions addInlineSignature(SecretKeyRingProtector secretKeyDecryptor,
178                                             PGPSecretKeyRing secretKey,
179                                             String userId,
180                                             DocumentSignatureType signatureType,
181                                             @Nullable BaseSignatureSubpackets.Callback subpacketsCallback)
182            throws KeyValidationError, PGPException {
183        KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date());
184        if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
185            throw new KeyValidationError(userId, keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId));
186        }
187
188        List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
189        if (signingPubKeys.isEmpty()) {
190            throw new KeyCannotSignException("Key " + OpenPgpFingerprint.of(secretKey) + " has no valid signing key.");
191        }
192
193        for (PGPPublicKey signingPubKey : signingPubKeys) {
194            PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
195            PGPPrivateKey signingSubkey = UnlockSecretKey.unlockSecretKey(signingSecKey, secretKeyDecryptor);
196            Set<HashAlgorithm> hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId)
197                    : keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
198            HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
199            addSigningMethod(secretKey, signingSubkey, subpacketsCallback, hashAlgorithm, signatureType, false);
200        }
201
202        return this;
203    }
204
205    /**
206     * Add detached signatures with all key rings from the provided secret key ring collection.
207     *
208     * @param secretKeyDecryptor decryptor to unlock the secret signing keys
209     * @param signingKeys collection of signing key rings
210     * @param signatureType type of the signature (binary, canonical text)
211     * @return this
212     * @throws PGPException if any of the keys cannot be validated or unlocked, or if any signing method cannot be created
213     */
214    public SigningOptions addDetachedSignatures(SecretKeyRingProtector secretKeyDecryptor,
215                                                Iterable<PGPSecretKeyRing> signingKeys,
216                                                DocumentSignatureType signatureType)
217            throws PGPException {
218        for (PGPSecretKeyRing signingKey : signingKeys) {
219            addDetachedSignature(secretKeyDecryptor, signingKey, signatureType);
220        }
221        return this;
222    }
223
224    /**
225     * Create a detached signature.
226     * Detached signatures are not being added into the PGP message itself.
227     * Instead, they can be distributed separately to the message.
228     * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
229     *
230     * @param secretKeyDecryptor decryptor to unlock the secret signing key
231     * @param secretKey signing key
232     * @param signatureType type of data that is signed (binary, canonical text)
233     * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
234     * @return this
235     */
236    public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor,
237                                               PGPSecretKeyRing secretKey,
238                                               DocumentSignatureType signatureType)
239            throws PGPException {
240        return addDetachedSignature(secretKeyDecryptor, secretKey, null, signatureType);
241    }
242
243    /**
244     * Create a detached signature.
245     * Detached signatures are not being added into the PGP message itself.
246     * Instead, they can be distributed separately to the message.
247     * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
248     *
249     * This method uses the passed in user-id to select user-specific hash algorithms.
250     *
251     * @param secretKeyDecryptor decryptor to unlock the secret signing key
252     * @param secretKey signing key
253     * @param userId user-id
254     * @param signatureType type of data that is signed (binary, canonical text)
255     * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
256     * @return this
257     */
258    public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor,
259                                               PGPSecretKeyRing secretKey,
260                                               String userId,
261                                               DocumentSignatureType signatureType)
262            throws PGPException {
263        return addDetachedSignature(secretKeyDecryptor, secretKey, userId, signatureType, null);
264    }
265
266    /**
267     * Create a detached signature.
268     * Detached signatures are not being added into the PGP message itself.
269     * Instead, they can be distributed separately to the message.
270     * Detached signatures are useful if the data that is being signed shall not be modified (e.g. when signing a file).
271     *
272     * This method uses the passed in user-id to select user-specific hash algorithms.
273     *
274     * @param secretKeyDecryptor decryptor to unlock the secret signing key
275     * @param secretKey signing key
276     * @param userId user-id
277     * @param signatureType type of data that is signed (binary, canonical text)
278     * @param subpacketCallback callback to modify hashed and unhashed subpackets of the signature
279     * @throws PGPException if the key cannot be validated or unlocked, or if no signature method can be created
280     * @return this
281     */
282    public SigningOptions addDetachedSignature(SecretKeyRingProtector secretKeyDecryptor,
283                                               PGPSecretKeyRing secretKey,
284                                               String userId,
285                                               DocumentSignatureType signatureType,
286                                               @Nullable BaseSignatureSubpackets.Callback subpacketCallback)
287            throws PGPException {
288        KeyRingInfo keyRingInfo = new KeyRingInfo(secretKey, new Date());
289        if (userId != null && !keyRingInfo.isUserIdValid(userId)) {
290            throw new KeyValidationError(userId, keyRingInfo.getLatestUserIdCertification(userId), keyRingInfo.getUserIdRevocation(userId));
291        }
292
293        List<PGPPublicKey> signingPubKeys = keyRingInfo.getSigningSubkeys();
294        if (signingPubKeys.isEmpty()) {
295            throw new KeyCannotSignException("Key has no valid signing key.");
296        }
297
298        for (PGPPublicKey signingPubKey : signingPubKeys) {
299            PGPSecretKey signingSecKey = secretKey.getSecretKey(signingPubKey.getKeyID());
300            if (signingSecKey == null) {
301                throw new PGPException("Missing secret key for signing key " + Long.toHexString(signingPubKey.getKeyID()));
302            }
303            PGPPrivateKey signingSubkey = signingSecKey.extractPrivateKey(
304                    secretKeyDecryptor.getDecryptor(signingPubKey.getKeyID()));
305            Set<HashAlgorithm> hashAlgorithms = userId != null ? keyRingInfo.getPreferredHashAlgorithms(userId)
306                    : keyRingInfo.getPreferredHashAlgorithms(signingPubKey.getKeyID());
307            HashAlgorithm hashAlgorithm = negotiateHashAlgorithm(hashAlgorithms, PGPainless.getPolicy());
308            addSigningMethod(secretKey, signingSubkey, subpacketCallback, hashAlgorithm, signatureType, true);
309        }
310
311        return this;
312    }
313
314    private void addSigningMethod(PGPSecretKeyRing secretKey,
315                                  PGPPrivateKey signingSubkey,
316                                  @Nullable BaseSignatureSubpackets.Callback subpacketCallback,
317                                  HashAlgorithm hashAlgorithm,
318                                  DocumentSignatureType signatureType,
319                                  boolean detached)
320            throws PGPException {
321        SubkeyIdentifier signingKeyIdentifier = new SubkeyIdentifier(secretKey, signingSubkey.getKeyID());
322        PGPSecretKey signingSecretKey = secretKey.getSecretKey(signingSubkey.getKeyID());
323        PublicKeyAlgorithm publicKeyAlgorithm = PublicKeyAlgorithm.fromId(signingSecretKey.getPublicKey().getAlgorithm());
324        int bitStrength = secretKey.getPublicKey().getBitStrength();
325        if (!PGPainless.getPolicy().getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) {
326            throw new IllegalArgumentException("Public key algorithm policy violation: " +
327                    publicKeyAlgorithm + " with bit strength " + bitStrength + " is not acceptable.");
328        }
329
330        PGPSignatureGenerator generator = createSignatureGenerator(signingSubkey, hashAlgorithm, signatureType);
331
332        // Subpackets
333        SignatureSubpackets hashedSubpackets = SignatureSubpackets.createHashedSubpackets(signingSecretKey.getPublicKey());
334        SignatureSubpackets unhashedSubpackets = SignatureSubpackets.createEmptySubpackets();
335        if (subpacketCallback != null) {
336            subpacketCallback.modifyHashedSubpackets(hashedSubpackets);
337            subpacketCallback.modifyUnhashedSubpackets(unhashedSubpackets);
338        }
339        generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets));
340        generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets));
341
342        SigningMethod signingMethod = detached ?
343                SigningMethod.detachedSignature(generator, hashAlgorithm) :
344                SigningMethod.inlineSignature(generator, hashAlgorithm);
345        signingMethods.put(signingKeyIdentifier, signingMethod);
346    }
347
348    /**
349     * Negotiate, which hash algorithm to use.
350     *
351     * This method gives the highest priority to the algorithm override, which can be set via {@link #overrideHashAlgorithm(HashAlgorithm)}.
352     * After that, the signing keys hash algorithm preferences are iterated to find the first acceptable algorithm.
353     * Lastly, should no acceptable algorithm be found, the {@link Policy Policies} default signature hash algorithm is
354     * used as a fallback.
355     *
356     * @param preferences preferences
357     * @param policy policy
358     * @return selected hash algorithm
359     */
360    private HashAlgorithm negotiateHashAlgorithm(Set<HashAlgorithm> preferences, Policy policy) {
361        if (hashAlgorithmOverride != null) {
362            return hashAlgorithmOverride;
363        }
364
365        return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(policy)
366                .negotiateHashAlgorithm(preferences);
367    }
368
369    private PGPSignatureGenerator createSignatureGenerator(PGPPrivateKey privateKey,
370                                                           HashAlgorithm hashAlgorithm,
371                                                           DocumentSignatureType signatureType)
372            throws PGPException {
373        int publicKeyAlgorithm = privateKey.getPublicKeyPacket().getAlgorithm();
374        PGPContentSignerBuilder signerBuilder = ImplementationFactory.getInstance()
375                .getPGPContentSignerBuilder(publicKeyAlgorithm, hashAlgorithm.getAlgorithmId());
376        PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signerBuilder);
377        signatureGenerator.init(signatureType.getSignatureType().getCode(), privateKey);
378
379        return signatureGenerator;
380    }
381
382    /**
383     * Return a map of key-ids and signing methods.
384     * For internal use.
385     *
386     * @return signing methods
387     */
388    public Map<SubkeyIdentifier, SigningMethod> getSigningMethods() {
389        return Collections.unmodifiableMap(signingMethods);
390    }
391
392    /**
393     * Override hash algorithm negotiation by dictating which hash algorithm needs to be used.
394     * If no override has been set, an accetable algorithm will be negotiated instead.
395     *
396     * Note: To override the hash algorithm for signing, call this method *before* calling
397     * {@link #addInlineSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)} or
398     * {@link #addDetachedSignature(SecretKeyRingProtector, PGPSecretKeyRing, DocumentSignatureType)}.
399     *
400     * @param hashAlgorithmOverride override hash algorithm
401     * @return this
402     */
403    public SigningOptions overrideHashAlgorithm(HashAlgorithm hashAlgorithmOverride) {
404        this.hashAlgorithmOverride = hashAlgorithmOverride;
405        return this;
406    }
407
408    /**
409     * Return the hash algorithm override (or null if no override is set).
410     *
411     * @return hash algorithm override
412     */
413    public HashAlgorithm getHashAlgorithmOverride() {
414        return hashAlgorithmOverride;
415    }
416}