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}