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}