001// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
002//
003// SPDX-License-Identifier: Apache-2.0
004
005package org.pgpainless.key.generation;
006
007
008import java.io.IOException;
009import java.nio.charset.Charset;
010import java.security.InvalidAlgorithmParameterException;
011import java.security.KeyPair;
012import java.security.KeyPairGenerator;
013import java.security.NoSuchAlgorithmException;
014import java.util.ArrayList;
015import java.util.Date;
016import java.util.Iterator;
017import java.util.LinkedHashMap;
018import java.util.List;
019import java.util.Map;
020import javax.annotation.Nonnull;
021import javax.annotation.Nullable;
022
023import org.bouncycastle.bcpg.sig.KeyFlags;
024import org.bouncycastle.openpgp.PGPException;
025import org.bouncycastle.openpgp.PGPKeyPair;
026import org.bouncycastle.openpgp.PGPKeyRingGenerator;
027import org.bouncycastle.openpgp.PGPPrivateKey;
028import org.bouncycastle.openpgp.PGPPublicKey;
029import org.bouncycastle.openpgp.PGPSecretKey;
030import org.bouncycastle.openpgp.PGPSecretKeyRing;
031import org.bouncycastle.openpgp.PGPSignature;
032import org.bouncycastle.openpgp.PGPSignatureGenerator;
033import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
034import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
035import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
036import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
037import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
038import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
039import org.pgpainless.PGPainless;
040import org.pgpainless.algorithm.HashAlgorithm;
041import org.pgpainless.algorithm.KeyFlag;
042import org.pgpainless.algorithm.PublicKeyAlgorithm;
043import org.pgpainless.algorithm.SignatureType;
044import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
045import org.pgpainless.implementation.ImplementationFactory;
046import org.pgpainless.key.generation.type.KeyType;
047import org.pgpainless.key.protection.UnlockSecretKey;
048import org.pgpainless.policy.Policy;
049import org.pgpainless.provider.ProviderFactory;
050import org.pgpainless.signature.subpackets.SelfSignatureSubpackets;
051import org.pgpainless.signature.subpackets.SignatureSubpackets;
052import org.pgpainless.signature.subpackets.SignatureSubpacketsHelper;
053import org.pgpainless.util.Passphrase;
054
055public class KeyRingBuilder implements KeyRingBuilderInterface<KeyRingBuilder> {
056
057    @SuppressWarnings("CharsetObjectCanBeUsed")
058    private final Charset UTF8 = Charset.forName("UTF-8");
059
060    private KeySpec primaryKeySpec;
061    private final List<KeySpec> subkeySpecs = new ArrayList<>();
062    private final Map<String, SelfSignatureSubpackets.Callback> userIds = new LinkedHashMap<>();
063    private Passphrase passphrase = Passphrase.emptyPassphrase();
064    private Date expirationDate = null;
065
066    @Override
067    public KeyRingBuilder setPrimaryKey(@Nonnull KeySpec keySpec) {
068        verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy());
069        verifyMasterKeyCanCertify(keySpec);
070        this.primaryKeySpec = keySpec;
071        return this;
072    }
073
074    @Override
075    public KeyRingBuilder addSubkey(@Nonnull KeySpec keySpec) {
076        verifyKeySpecCompliesToPolicy(keySpec, PGPainless.getPolicy());
077        this.subkeySpecs.add(keySpec);
078        return this;
079    }
080
081    @Override
082    public KeyRingBuilder addUserId(@Nonnull String userId) {
083        this.userIds.put(userId.trim(), null);
084        return this;
085    }
086
087    public KeyRingBuilder addUserId(
088            @Nonnull String userId,
089            @Nullable SelfSignatureSubpackets.Callback subpacketsCallback) {
090        this.userIds.put(userId.trim(), subpacketsCallback);
091        return this;
092    }
093
094    @Override
095    public KeyRingBuilder addUserId(@Nonnull byte[] userId) {
096        return addUserId(new String(userId, UTF8));
097    }
098
099    @Override
100    public KeyRingBuilder setExpirationDate(@Nonnull Date expirationDate) {
101        Date now = new Date();
102        if (now.after(expirationDate)) {
103            throw new IllegalArgumentException("Expiration date must be in the future.");
104        }
105        this.expirationDate = expirationDate;
106        return this;
107    }
108
109    @Override
110    public KeyRingBuilder setPassphrase(@Nonnull Passphrase passphrase) {
111        this.passphrase = passphrase;
112        return this;
113    }
114
115    private void verifyKeySpecCompliesToPolicy(KeySpec keySpec, Policy policy) {
116        PublicKeyAlgorithm publicKeyAlgorithm = keySpec.getKeyType().getAlgorithm();
117        int bitStrength = keySpec.getKeyType().getBitStrength();
118
119        if (!policy.getPublicKeyAlgorithmPolicy().isAcceptable(publicKeyAlgorithm, bitStrength)) {
120            throw new IllegalArgumentException("Public key algorithm policy violation: " +
121                    publicKeyAlgorithm + " with bit strength " + bitStrength + " is not acceptable.");
122        }
123    }
124
125    private void verifyMasterKeyCanCertify(KeySpec spec) {
126        if (!hasCertifyOthersFlag(spec)) {
127            throw new IllegalArgumentException("Certification Key MUST have KeyFlag CERTIFY_OTHER");
128        }
129        if (!keyIsCertificationCapable(spec)) {
130            throw new IllegalArgumentException("Key algorithm " + spec.getKeyType().getName() + " is not capable of creating certifications.");
131        }
132    }
133
134    private boolean hasCertifyOthersFlag(KeySpec keySpec) {
135        KeyFlags keyFlags = keySpec.getSubpacketGenerator().getKeyFlagsSubpacket();
136        return keyFlags != null && KeyFlag.hasKeyFlag(keyFlags.getFlags(), KeyFlag.CERTIFY_OTHER);
137    }
138
139    private boolean keyIsCertificationCapable(KeySpec keySpec) {
140        return keySpec.getKeyType().canCertify();
141    }
142
143    @Override
144    public PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException,
145            InvalidAlgorithmParameterException {
146        if (userIds.isEmpty()) {
147            throw new IllegalStateException("At least one user-id is required.");
148        }
149        PGPDigestCalculator keyFingerprintCalculator = ImplementationFactory.getInstance().getV4FingerprintCalculator();
150        PBESecretKeyEncryptor secretKeyEncryptor = buildSecretKeyEncryptor(keyFingerprintCalculator);
151        PBESecretKeyDecryptor secretKeyDecryptor = buildSecretKeyDecryptor();
152
153        passphrase.clear();
154
155        // Generate Primary Key
156        PGPKeyPair certKey = generateKeyPair(primaryKeySpec);
157        PGPContentSignerBuilder signer = buildContentSigner(certKey);
158        PGPSignatureGenerator signatureGenerator = new PGPSignatureGenerator(signer);
159
160        // Prepare primary user-id sig
161        SignatureSubpackets hashedSubPacketGenerator = primaryKeySpec.getSubpacketGenerator();
162        hashedSubPacketGenerator.setIssuerFingerprintAndKeyId(certKey.getPublicKey());
163        hashedSubPacketGenerator.setPrimaryUserId();
164        if (expirationDate != null) {
165            hashedSubPacketGenerator.setKeyExpirationTime(certKey.getPublicKey(), expirationDate);
166        }
167        PGPSignatureSubpacketGenerator generator = new PGPSignatureSubpacketGenerator();
168        SignatureSubpacketsHelper.applyTo(hashedSubPacketGenerator, generator);
169        PGPSignatureSubpacketVector hashedSubPackets = generator.generate();
170
171        PGPKeyRingGenerator ringGenerator = buildRingGenerator(
172                certKey, signer, keyFingerprintCalculator, hashedSubPackets, secretKeyEncryptor);
173        addSubKeys(certKey, ringGenerator);
174
175        // Generate secret key ring with only primary user id
176        PGPSecretKeyRing secretKeyRing = ringGenerator.generateSecretKeyRing();
177
178        Iterator<PGPSecretKey> secretKeys = secretKeyRing.getSecretKeys();
179
180        // Attempt to add additional user-ids to the primary public key
181        PGPPublicKey primaryPubKey = secretKeys.next().getPublicKey();
182        PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKeyRing.getSecretKey(), secretKeyDecryptor);
183        Iterator<Map.Entry<String, SelfSignatureSubpackets.Callback>> userIdIterator =
184                this.userIds.entrySet().iterator();
185        userIdIterator.next(); // Skip primary user id
186        while (userIdIterator.hasNext()) {
187            Map.Entry<String, SelfSignatureSubpackets.Callback> additionalUserId = userIdIterator.next();
188            String userIdString = additionalUserId.getKey();
189            SelfSignatureSubpackets.Callback callback = additionalUserId.getValue();
190            SelfSignatureSubpackets subpackets = null;
191            if (callback == null) {
192                subpackets = hashedSubPacketGenerator;
193                subpackets.setPrimaryUserId(null);
194                // additional user-ids are not primary
195            } else {
196                subpackets = SignatureSubpackets.createHashedSubpackets(primaryPubKey);
197                callback.modifyHashedSubpackets(subpackets);
198            }
199            signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.getCode(), privateKey);
200            signatureGenerator.setHashedSubpackets(
201                    SignatureSubpacketsHelper.toVector((SignatureSubpackets) subpackets));
202            PGPSignature additionalUserIdSignature =
203                    signatureGenerator.generateCertification(userIdString, primaryPubKey);
204            primaryPubKey = PGPPublicKey.addCertification(primaryPubKey,
205                    userIdString, additionalUserIdSignature);
206        }
207
208        // "reassemble" secret key ring with modified primary key
209        PGPSecretKey primarySecKey = new PGPSecretKey(
210                privateKey, primaryPubKey, keyFingerprintCalculator, true, secretKeyEncryptor);
211        List<PGPSecretKey> secretKeyList = new ArrayList<>();
212        secretKeyList.add(primarySecKey);
213        while (secretKeys.hasNext()) {
214            secretKeyList.add(secretKeys.next());
215        }
216        secretKeyRing = new PGPSecretKeyRing(secretKeyList);
217        return secretKeyRing;
218    }
219
220    private PGPKeyRingGenerator buildRingGenerator(PGPKeyPair certKey,
221                                                   PGPContentSignerBuilder signer,
222                                                   PGPDigestCalculator keyFingerprintCalculator,
223                                                   PGPSignatureSubpacketVector hashedSubPackets,
224                                                   PBESecretKeyEncryptor secretKeyEncryptor)
225            throws PGPException {
226        String primaryUserId = userIds.entrySet().iterator().next().getKey();
227        return new PGPKeyRingGenerator(
228                SignatureType.POSITIVE_CERTIFICATION.getCode(), certKey,
229                primaryUserId, keyFingerprintCalculator,
230                hashedSubPackets, null, signer, secretKeyEncryptor);
231    }
232
233    private void addSubKeys(PGPKeyPair primaryKey, PGPKeyRingGenerator ringGenerator)
234            throws NoSuchAlgorithmException, PGPException, InvalidAlgorithmParameterException {
235        for (KeySpec subKeySpec : subkeySpecs) {
236            PGPKeyPair subKey = generateKeyPair(subKeySpec);
237            if (subKeySpec.isInheritedSubPackets()) {
238                ringGenerator.addSubKey(subKey);
239            } else {
240                PGPSignatureSubpacketVector hashedSubpackets = subKeySpec.getSubpackets();
241                try {
242                    hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary(
243                            primaryKey, subKey, hashedSubpackets);
244                } catch (IOException e) {
245                    throw new PGPException("Exception while adding primary key binding signature to signing subkey", e);
246                }
247                ringGenerator.addSubKey(subKey, hashedSubpackets, null);
248            }
249        }
250    }
251
252    private PGPSignatureSubpacketVector addPrimaryKeyBindingSignatureIfNecessary(
253            PGPKeyPair primaryKey, PGPKeyPair subKey, PGPSignatureSubpacketVector hashedSubpackets)
254            throws PGPException, IOException {
255        int keyFlagMask = hashedSubpackets.getKeyFlags();
256        if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) &&
257                !KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
258            return hashedSubpackets;
259        }
260
261        PGPSignatureGenerator bindingSignatureGenerator = new PGPSignatureGenerator(buildContentSigner(subKey));
262        bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.getCode(), subKey.getPrivateKey());
263        PGPSignature primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.getPublicKey(), subKey.getPublicKey());
264        PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator(hashedSubpackets);
265        subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig);
266        return subpacketGenerator.generate();
267    }
268
269    private PGPContentSignerBuilder buildContentSigner(PGPKeyPair certKey) {
270        HashAlgorithm hashAlgorithm = PGPainless.getPolicy()
271                .getSignatureHashAlgorithmPolicy().defaultHashAlgorithm();
272        return ImplementationFactory.getInstance().getPGPContentSignerBuilder(
273                certKey.getPublicKey().getAlgorithm(),
274                hashAlgorithm.getAlgorithmId());
275    }
276
277    private PBESecretKeyEncryptor buildSecretKeyEncryptor(PGPDigestCalculator keyFingerprintCalculator) {
278        SymmetricKeyAlgorithm keyEncryptionAlgorithm = PGPainless.getPolicy()
279                .getSymmetricKeyEncryptionAlgorithmPolicy()
280                .getDefaultSymmetricKeyAlgorithm();
281        if (!passphrase.isValid()) {
282            throw new IllegalStateException("Passphrase was cleared.");
283        }
284        return passphrase.isEmpty() ? null : // unencrypted key pair, otherwise AES-256 encrypted
285                ImplementationFactory.getInstance().getPBESecretKeyEncryptor(
286                        keyEncryptionAlgorithm, keyFingerprintCalculator, passphrase);
287    }
288
289    private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException {
290        if (!passphrase.isValid()) {
291            throw new IllegalStateException("Passphrase was cleared.");
292        }
293        return passphrase.isEmpty() ? null :
294                ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase);
295    }
296
297    public static PGPKeyPair generateKeyPair(KeySpec spec)
298            throws NoSuchAlgorithmException, PGPException,
299            InvalidAlgorithmParameterException {
300        KeyType type = spec.getKeyType();
301        KeyPairGenerator certKeyGenerator = KeyPairGenerator.getInstance(type.getName(),
302                ProviderFactory.getProvider());
303        certKeyGenerator.initialize(type.getAlgorithmSpec());
304
305        // Create raw Key Pair
306        KeyPair keyPair = certKeyGenerator.generateKeyPair();
307
308        // Form PGP key pair
309        PGPKeyPair pgpKeyPair = ImplementationFactory.getInstance()
310                .getPGPKeyPair(type.getAlgorithm(), keyPair, new Date());
311        return pgpKeyPair;
312    }
313}