001// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.signature.builder;
006
007import java.util.Set;
008import javax.annotation.Nonnull;
009
010import org.bouncycastle.openpgp.PGPException;
011import org.bouncycastle.openpgp.PGPPrivateKey;
012import org.bouncycastle.openpgp.PGPPublicKey;
013import org.bouncycastle.openpgp.PGPSecretKey;
014import org.bouncycastle.openpgp.PGPSignature;
015import org.bouncycastle.openpgp.PGPSignatureGenerator;
016import org.pgpainless.PGPainless;
017import org.pgpainless.algorithm.HashAlgorithm;
018import org.pgpainless.algorithm.SignatureType;
019import org.pgpainless.algorithm.negotiation.HashAlgorithmNegotiator;
020import org.pgpainless.implementation.ImplementationFactory;
021import org.pgpainless.key.protection.SecretKeyRingProtector;
022import org.pgpainless.key.protection.UnlockSecretKey;
023import org.pgpainless.key.util.OpenPgpKeyAttributeUtil;
024import org.pgpainless.signature.subpackets.SignatureSubpackets;
025import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper;
026
027public abstract class AbstractSignatureBuilder<B extends AbstractSignatureBuilder<B>> {
028    protected final PGPPrivateKey privateSigningKey;
029    protected final PGPPublicKey publicSigningKey;
030
031    protected HashAlgorithm hashAlgorithm;
032    protected SignatureType signatureType;
033
034    protected SignatureSubpackets unhashedSubpackets;
035    protected SignatureSubpackets hashedSubpackets;
036
037    protected AbstractSignatureBuilder(SignatureType signatureType,
038                                       PGPSecretKey signingKey,
039                                       SecretKeyRingProtector protector,
040                                       HashAlgorithm hashAlgorithm,
041                                       SignatureSubpackets hashedSubpackets,
042                                       SignatureSubpackets unhashedSubpackets)
043            throws PGPException {
044        if (!isValidSignatureType(signatureType)) {
045            throw new IllegalArgumentException("Invalid signature type.");
046        }
047        this.signatureType = signatureType;
048        this.privateSigningKey = UnlockSecretKey.unlockSecretKey(signingKey, protector);
049        this.publicSigningKey = signingKey.getPublicKey();
050        this.hashAlgorithm = hashAlgorithm;
051        this.hashedSubpackets = hashedSubpackets;
052        this.unhashedSubpackets = unhashedSubpackets;
053    }
054
055    public AbstractSignatureBuilder(SignatureType signatureType, PGPSecretKey signingKey, SecretKeyRingProtector protector)
056            throws PGPException {
057        this(
058                signatureType,
059                signingKey,
060                protector,
061                negotiateHashAlgorithm(signingKey.getPublicKey()),
062                SignatureSubpackets.createHashedSubpackets(signingKey.getPublicKey()),
063                SignatureSubpackets.createEmptySubpackets()
064        );
065    }
066
067    public AbstractSignatureBuilder(PGPSecretKey certificationKey, SecretKeyRingProtector protector, PGPSignature archetypeSignature)
068            throws PGPException {
069        this(
070                SignatureType.valueOf(archetypeSignature.getSignatureType()),
071                certificationKey,
072                protector,
073                negotiateHashAlgorithm(certificationKey.getPublicKey()),
074                SignatureSubpackets.refreshHashedSubpackets(certificationKey.getPublicKey(), archetypeSignature),
075                SignatureSubpackets.refreshUnhashedSubpackets(archetypeSignature)
076        );
077    }
078
079    /**
080     * Negotiate a {@link HashAlgorithm} to be used when creating the signature.
081     *
082     * @param publicKey signing public key
083     * @return hash algorithm
084     */
085    protected static HashAlgorithm negotiateHashAlgorithm(PGPPublicKey publicKey) {
086        Set<HashAlgorithm> hashAlgorithmPreferences = OpenPgpKeyAttributeUtil.getOrGuessPreferredHashAlgorithms(publicKey);
087        return HashAlgorithmNegotiator.negotiateSignatureHashAlgorithm(PGPainless.getPolicy())
088                .negotiateHashAlgorithm(hashAlgorithmPreferences);
089    }
090
091    public B overrideHashAlgorithm(@Nonnull HashAlgorithm hashAlgorithm) {
092        this.hashAlgorithm = hashAlgorithm;
093        return (B) this;
094    }
095
096    /**
097     * Set the builders {@link SignatureType}.
098     * Note that only those types who are valid for the concrete subclass of this {@link AbstractSignatureBuilder}
099     * are allowed. Invalid choices result in an {@link IllegalArgumentException} to be thrown.
100     *
101     * @param type signature type
102     * @return builder
103     */
104    public B setSignatureType(SignatureType type) {
105        if (!isValidSignatureType(type)) {
106            throw new IllegalArgumentException("Invalid signature type: " + type);
107        }
108        this.signatureType = type;
109        return (B) this;
110    }
111
112    /**
113     * Build an instance of {@link PGPSignatureGenerator} initialized with the signing key
114     * and with hashed and unhashed subpackets.
115     *
116     * @return pgp signature generator
117     */
118    protected PGPSignatureGenerator buildAndInitSignatureGenerator() throws PGPException {
119        PGPSignatureGenerator generator = new PGPSignatureGenerator(
120                ImplementationFactory.getInstance().getPGPContentSignerBuilder(
121                        publicSigningKey.getAlgorithm(), hashAlgorithm.getAlgorithmId()
122                )
123        );
124        generator.setUnhashedSubpackets(SignatureSubpacketsHelper.toVector(unhashedSubpackets));
125        generator.setHashedSubpackets(SignatureSubpacketsHelper.toVector(hashedSubpackets));
126        generator.init(signatureType.getCode(), privateSigningKey);
127        return generator;
128    }
129
130    /**
131     * Return true if the given {@link SignatureType} is a valid choice for the concrete implementation
132     * of {@link AbstractSignatureBuilder}.
133     *
134     * @param type type
135     * @return return true if valid, false otherwise
136     */
137    protected abstract boolean isValidSignatureType(SignatureType type);
138}