001/*
002 * Copyright 2018 Paul Schaub.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.pgpainless.key.generation;
017
018
019import javax.annotation.Nonnull;
020import java.nio.charset.Charset;
021import java.security.InvalidAlgorithmParameterException;
022import java.security.KeyPair;
023import java.security.KeyPairGenerator;
024import java.security.NoSuchAlgorithmException;
025import java.security.NoSuchProviderException;
026import java.util.ArrayList;
027import java.util.Date;
028import java.util.List;
029
030import org.bouncycastle.bcpg.sig.KeyFlags;
031import org.bouncycastle.jce.provider.BouncyCastleProvider;
032import org.bouncycastle.openpgp.PGPEncryptedData;
033import org.bouncycastle.openpgp.PGPException;
034import org.bouncycastle.openpgp.PGPKeyPair;
035import org.bouncycastle.openpgp.PGPKeyRingGenerator;
036import org.bouncycastle.openpgp.PGPPublicKeyRing;
037import org.bouncycastle.openpgp.PGPSecretKeyRing;
038import org.bouncycastle.openpgp.PGPSignature;
039import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
040import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
041import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
042import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
043import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
044import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
045import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
046import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
047import org.pgpainless.algorithm.HashAlgorithm;
048import org.pgpainless.algorithm.KeyFlag;
049import org.pgpainless.key.collection.PGPKeyRing;
050import org.pgpainless.key.generation.type.ECDH;
051import org.pgpainless.key.generation.type.ECDSA;
052import org.pgpainless.key.generation.type.KeyType;
053import org.pgpainless.key.generation.type.RSA_GENERAL;
054import org.pgpainless.key.generation.type.curve.EllipticCurve;
055import org.pgpainless.key.generation.type.length.RsaLength;
056import org.pgpainless.util.KeyRingSubKeyFix;
057import org.pgpainless.util.Passphrase;
058
059public class KeyRingBuilder implements KeyRingBuilderInterface {
060
061    private final Charset UTF8 = Charset.forName("UTF-8");
062
063    private List<KeySpec> keySpecs = new ArrayList<>();
064    private String userId;
065    private Passphrase passphrase;
066
067    /**
068     * Creates a simple RSA KeyPair of length {@code length} with user-id {@code userId}.
069     * The KeyPair consists of a single RSA master key which is used for signing, encryption and certification.
070     *
071     * @param userId user id.
072     * @param length length in bits.
073     * @return {@link PGPSecretKeyRing} containing the KeyPair.
074     * @throws PGPException
075     * @throws NoSuchAlgorithmException
076     * @throws NoSuchProviderException
077     * @throws InvalidAlgorithmParameterException
078     */
079    public PGPKeyRing simpleRsaKeyRing(@Nonnull String userId, @Nonnull RsaLength length)
080            throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
081        return withMasterKey(
082                        KeySpec.getBuilder(RSA_GENERAL.withLength(length))
083                                .withDefaultKeyFlags()
084                                .withDefaultAlgorithms())
085                .withPrimaryUserId(userId)
086                .withoutPassphrase()
087                .build();
088    }
089
090    /**
091     * Creates a key ring consisting of an ECDSA master key and an ECDH sub-key.
092     * The ECDSA master key is used for signing messages and certifying the sub key.
093     * The ECDH sub-key is used for encryption of messages.
094     *
095     * @param userId user-id
096     * @return {@link PGPSecretKeyRing} containing the key pairs.
097     * @throws PGPException
098     * @throws NoSuchAlgorithmException
099     * @throws NoSuchProviderException
100     * @throws InvalidAlgorithmParameterException
101     */
102    public PGPKeyRing simpleEcKeyRing(@Nonnull String userId)
103            throws PGPException, NoSuchAlgorithmException, NoSuchProviderException, InvalidAlgorithmParameterException {
104        return withSubKey(
105                        KeySpec.getBuilder(ECDH.fromCurve(EllipticCurve._P256))
106                                .withKeyFlags(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)
107                                .withDefaultAlgorithms())
108                .withMasterKey(
109                        KeySpec.getBuilder(ECDSA.fromCurve(EllipticCurve._P256))
110                                .withKeyFlags(KeyFlag.AUTHENTICATION, KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)
111                                .withDefaultAlgorithms())
112                .withPrimaryUserId(userId)
113                .withoutPassphrase()
114                .build();
115    }
116
117    @Override
118    public KeyRingBuilderInterface withSubKey(@Nonnull KeySpec type) {
119        KeyRingBuilder.this.keySpecs.add(type);
120        return this;
121    }
122
123    @Override
124    public WithPrimaryUserId withMasterKey(@Nonnull KeySpec spec) {
125        if ((spec.getSubpackets().getKeyFlags() & KeyFlags.CERTIFY_OTHER) == 0) {
126            throw new IllegalArgumentException("Certification Key MUST have KeyFlag CERTIFY_OTHER");
127        }
128        KeyRingBuilder.this.keySpecs.add(0, spec);
129        return new WithPrimaryUserIdImpl();
130    }
131
132    class WithPrimaryUserIdImpl implements WithPrimaryUserId {
133
134        @Override
135        public WithPassphrase withPrimaryUserId(@Nonnull String userId) {
136            KeyRingBuilder.this.userId = userId;
137            return new WithPassphraseImpl();
138        }
139
140        @Override
141        public WithPassphrase withPrimaryUserId(@Nonnull byte[] userId) {
142            return withPrimaryUserId(new String(userId, UTF8));
143        }
144    }
145
146    class WithPassphraseImpl implements WithPassphrase {
147
148        @Override
149        public Build withPassphrase(@Nonnull Passphrase passphrase) {
150            KeyRingBuilder.this.passphrase = passphrase;
151            return new BuildImpl();
152        }
153
154        @Override
155        public Build withoutPassphrase() {
156            KeyRingBuilder.this.passphrase = null;
157            return new BuildImpl();
158        }
159
160        class BuildImpl implements Build {
161
162            @Override
163            public PGPKeyRing build() throws NoSuchAlgorithmException, PGPException, NoSuchProviderException,
164                    InvalidAlgorithmParameterException {
165
166                // Hash Calculator
167                PGPDigestCalculator calculator = new JcaPGPDigestCalculatorProviderBuilder()
168                        .setProvider(BouncyCastleProvider.PROVIDER_NAME)
169                        .build()
170                        .get(HashAlgorithm.SHA1.getAlgorithmId());
171
172                // Encryptor for encrypting secret keys
173                PBESecretKeyEncryptor encryptor = passphrase == null ?
174                        null : // unencrypted key pair, otherwise AES-256 encrypted
175                        new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, calculator)
176                                .setProvider(BouncyCastleProvider.PROVIDER_NAME)
177                                .build(passphrase != null ? passphrase.getChars() : null);
178
179                if (passphrase != null) {
180                    passphrase.clear();
181                }
182
183                // First key is the Master Key
184                KeySpec certKeySpec = keySpecs.get(0);
185                // Remove master key, so that we later only add sub keys.
186                keySpecs.remove(0);
187
188                // Generate Master Key
189                PGPKeyPair certKey = generateKeyPair(certKeySpec);
190
191                // Signer for creating self-signature
192                PGPContentSignerBuilder signer = new JcaPGPContentSignerBuilder(
193                        certKey.getPublicKey().getAlgorithm(), HashAlgorithm.SHA512.getAlgorithmId())
194                        .setProvider(BouncyCastleProvider.PROVIDER_NAME);
195
196                PGPSignatureSubpacketVector hashedSubPackets = certKeySpec.getSubpackets();
197
198                // Generator which the user can get the key pair from
199                PGPKeyRingGenerator ringGenerator = new PGPKeyRingGenerator(
200                        PGPSignature.POSITIVE_CERTIFICATION, certKey,
201                        userId, calculator,
202                        hashedSubPackets, null, signer, encryptor);
203
204                for (KeySpec subKeySpec : keySpecs) {
205                    PGPKeyPair subKey = generateKeyPair(subKeySpec);
206                    if (subKeySpec.isInheritedSubPackets()) {
207                        ringGenerator.addSubKey(subKey);
208                    } else {
209                        ringGenerator.addSubKey(subKey, subKeySpec.getSubpackets(), null);
210                    }
211                }
212
213                PGPPublicKeyRing publicKeys = ringGenerator.generatePublicKeyRing();
214                PGPSecretKeyRing secretKeys = ringGenerator.generateSecretKeyRing();
215
216                // TODO: Remove once BC 1.61 is released
217                secretKeys = KeyRingSubKeyFix.repairSubkeyPackets(secretKeys, null, null);
218
219                return new PGPKeyRing(publicKeys, secretKeys);
220            }
221
222            private PGPKeyPair generateKeyPair(KeySpec spec)
223                    throws NoSuchProviderException, NoSuchAlgorithmException, PGPException,
224                    InvalidAlgorithmParameterException {
225                KeyType type = spec.getKeyType();
226                KeyPairGenerator certKeyGenerator = KeyPairGenerator.getInstance(
227                        type.getName(), BouncyCastleProvider.PROVIDER_NAME);
228                certKeyGenerator.initialize(type.getAlgorithmSpec());
229
230                // Create raw Key Pair
231                KeyPair keyPair = certKeyGenerator.generateKeyPair();
232
233                // Form PGP key pair
234                PGPKeyPair pgpKeyPair = new JcaPGPKeyPair(type.getAlgorithm().getAlgorithmId(),
235                        keyPair, new Date());
236
237                return pgpKeyPair;
238            }
239        }
240    }
241}