1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-12-17 09:41:08 +01:00

Merge branch 'key_generator_rework'

This commit is contained in:
Paul Schaub 2021-09-20 14:50:02 +02:00
commit 1aa6541766
28 changed files with 489 additions and 779 deletions

View file

@ -15,7 +15,6 @@
*/
package org.pgpainless.algorithm;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
@ -45,9 +44,9 @@ public class AlgorithmSuite {
CompressionAlgorithm.UNCOMPRESSED)
);
private Set<SymmetricKeyAlgorithm> symmetricKeyAlgorithms;
private Set<HashAlgorithm> hashAlgorithms;
private Set<CompressionAlgorithm> compressionAlgorithms;
private final Set<SymmetricKeyAlgorithm> symmetricKeyAlgorithms;
private final Set<HashAlgorithm> hashAlgorithms;
private final Set<CompressionAlgorithm> compressionAlgorithms;
public AlgorithmSuite(List<SymmetricKeyAlgorithm> symmetricKeyAlgorithms,
List<HashAlgorithm> hashAlgorithms,
@ -57,57 +56,18 @@ public class AlgorithmSuite {
this.compressionAlgorithms = Collections.unmodifiableSet(new LinkedHashSet<>(compressionAlgorithms));
}
public void setSymmetricKeyAlgorithms(List<SymmetricKeyAlgorithm> symmetricKeyAlgorithms) {
this.symmetricKeyAlgorithms = Collections.unmodifiableSet(new LinkedHashSet<>(symmetricKeyAlgorithms));
}
public Set<SymmetricKeyAlgorithm> getSymmetricKeyAlgorithms() {
return new LinkedHashSet<>(symmetricKeyAlgorithms);
}
public int[] getSymmetricKeyAlgorithmIds() {
int[] array = new int[symmetricKeyAlgorithms.size()];
List<SymmetricKeyAlgorithm> list = new ArrayList<>(getSymmetricKeyAlgorithms());
for (int i = 0; i < array.length; i++) {
array[i] = list.get(i).getAlgorithmId();
}
return array;
}
public void setHashAlgorithms(List<HashAlgorithm> hashAlgorithms) {
this.hashAlgorithms = Collections.unmodifiableSet(new LinkedHashSet<>(hashAlgorithms));
}
public Set<HashAlgorithm> getHashAlgorithms() {
return new LinkedHashSet<>(hashAlgorithms);
}
public int[] getHashAlgorithmIds() {
int[] array = new int[hashAlgorithms.size()];
List<HashAlgorithm> list = new ArrayList<>(getHashAlgorithms());
for (int i = 0; i < array.length; i++) {
array[i] = list.get(i).getAlgorithmId();
}
return array;
}
public void setCompressionAlgorithms(List<CompressionAlgorithm> compressionAlgorithms) {
this.compressionAlgorithms = Collections.unmodifiableSet(new LinkedHashSet<>(compressionAlgorithms));
}
public Set<CompressionAlgorithm> getCompressionAlgorithms() {
return new LinkedHashSet<>(compressionAlgorithms);
}
public int[] getCompressionAlgorithmIds() {
int[] array = new int[compressionAlgorithms.size()];
List<CompressionAlgorithm> list = new ArrayList<>(getCompressionAlgorithms());
for (int i = 0; i < array.length; i++) {
array[i] = list.get(i).getAlgorithmId();
}
return array;
}
public static AlgorithmSuite getDefaultAlgorithmSuite() {
return defaultAlgorithmSuite;
}

View file

@ -466,7 +466,7 @@ public final class DecryptionStreamFactory {
}
private void throwIfAlgorithmIsRejected(SymmetricKeyAlgorithm algorithm) throws UnacceptableAlgorithmException {
if (!PGPainless.getPolicy().getSymmetricKeyDecryptionAlgoritmPolicy().isAcceptable(algorithm)) {
if (!PGPainless.getPolicy().getSymmetricKeyDecryptionAlgorithmPolicy().isAcceptable(algorithm)) {
throw new UnacceptableAlgorithmException("Data is "
+ (algorithm == SymmetricKeyAlgorithm.NULL ? "unencrypted" : "encrypted with symmetric algorithm " + algorithm) + " which is not acceptable as per PGPainless' policy.\n" +
"To mark this algorithm as acceptable, use PGPainless.getPolicy().setSymmetricKeyDecryptionAlgorithmPolicy().");

View file

@ -61,14 +61,18 @@ import org.pgpainless.provider.ProviderFactory;
import org.pgpainless.util.Passphrase;
import org.pgpainless.signature.subpackets.SignatureSubpacketGeneratorUtil;
public class KeyRingBuilder implements KeyRingBuilderInterface {
public class KeyRingBuilder implements KeyRingBuilderInterface<KeyRingBuilder> {
private final Charset UTF8 = Charset.forName("UTF-8");
private final List<KeySpec> keySpecs = new ArrayList<>();
private String userId;
private final Set<String> additionalUserIds = new LinkedHashSet<>();
private Passphrase passphrase;
private PGPSignatureGenerator signatureGenerator;
private PGPDigestCalculator digestCalculator;
private PBESecretKeyEncryptor secretKeyEncryptor;
private KeySpec primaryKeySpec;
private final List<KeySpec> subkeySpecs = new ArrayList<>();
private final Set<String> userIds = new LinkedHashSet<>();
private Passphrase passphrase = null;
private Date expirationDate = null;
/**
@ -126,18 +130,14 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
*/
public PGPSecretKeyRing simpleRsaKeyRing(@Nonnull String userId, @Nonnull RsaLength length, String password)
throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
WithAdditionalUserIdOrPassphrase builder = this
.withPrimaryKey(
KeySpec.getBuilder(KeyType.RSA(length))
.withKeyFlags(KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS)
.withDefaultAlgorithms())
.withPrimaryUserId(userId);
KeyRingBuilder builder = new KeyRingBuilder()
.setPrimaryKey(KeySpec.getBuilder(KeyType.RSA(length), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA, KeyFlag.ENCRYPT_COMMS))
.addUserId(userId);
if (password == null) {
return builder.withoutPassphrase().build();
} else {
return builder.withPassphrase(new Passphrase(password.toCharArray())).build();
if (!isNullOrEmpty(password)) {
builder.setPassphrase(Passphrase.fromPassword(password));
}
return builder.build();
}
/**
@ -195,22 +195,15 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
*/
public PGPSecretKeyRing simpleEcKeyRing(@Nonnull String userId, String password)
throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
WithAdditionalUserIdOrPassphrase builder = this
.withSubKey(
KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519))
.withKeyFlags(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)
.withDefaultAlgorithms())
.withPrimaryKey(
KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
.withKeyFlags(KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA)
.withDefaultAlgorithms())
.withPrimaryUserId(userId);
KeyRingBuilder builder = new KeyRingBuilder()
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER, KeyFlag.SIGN_DATA))
.addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS))
.addUserId(userId);
if (password == null) {
return builder.withoutPassphrase().build();
} else {
return builder.withPassphrase(new Passphrase(password.toCharArray())).build();
if (!isNullOrEmpty(password)) {
builder.setPassphrase(Passphrase.fromPassword(password));
}
return builder.build();
}
/**
@ -223,40 +216,59 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
*/
public PGPSecretKeyRing modernKeyRing(String userId, String password)
throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
WithAdditionalUserIdOrPassphrase builder = this
.withSubKey(
KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519))
.withKeyFlags(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)
.withDefaultAlgorithms())
.withSubKey(
KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
.withKeyFlags(KeyFlag.SIGN_DATA)
.withDefaultAlgorithms())
.withPrimaryKey(
KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519))
.withKeyFlags(KeyFlag.CERTIFY_OTHER)
.withDefaultAlgorithms())
.withPrimaryUserId(userId);
if (password == null) {
return builder.withoutPassphrase().build();
} else {
return builder.withPassphrase(new Passphrase(password.toCharArray())).build();
KeyRingBuilder builder = new KeyRingBuilder()
.setPrimaryKey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.CERTIFY_OTHER))
.addSubkey(KeySpec.getBuilder(KeyType.XDH(XDHSpec._X25519), KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS))
.addSubkey(KeySpec.getBuilder(KeyType.EDDSA(EdDSACurve._Ed25519), KeyFlag.SIGN_DATA))
.addUserId(userId);
if (!isNullOrEmpty(password)) {
builder.setPassphrase(Passphrase.fromPassword(password));
}
return builder.build();
}
@Override
public KeyRingBuilderInterface withSubKey(@Nonnull KeySpec type) {
KeyRingBuilder.this.keySpecs.add(type);
public KeyRingBuilder setPrimaryKey(@Nonnull KeySpec keySpec) {
verifyMasterKeyCanCertify(keySpec);
this.primaryKeySpec = keySpec;
return this;
}
@Override
public WithPrimaryUserId withPrimaryKey(@Nonnull KeySpec spec) {
verifyMasterKeyCanCertify(spec);
public KeyRingBuilder addSubkey(@Nonnull KeySpec keySpec) {
this.subkeySpecs.add(keySpec);
return this;
}
KeyRingBuilder.this.keySpecs.add(0, spec);
return new WithPrimaryUserIdImpl();
@Override
public KeyRingBuilder addUserId(@Nonnull String userId) {
this.userIds.add(userId.trim());
return this;
}
@Override
public KeyRingBuilder addUserId(@Nonnull byte[] userId) {
return addUserId(new String(userId, UTF8));
}
@Override
public KeyRingBuilder setExpirationDate(@Nonnull Date expirationDate) {
Date now = new Date();
if (now.after(expirationDate)) {
throw new IllegalArgumentException("Expiration date must be in the future.");
}
this.expirationDate = expirationDate;
return this;
}
@Override
public KeyRingBuilder setPassphrase(@Nonnull Passphrase passphrase) {
this.passphrase = passphrase;
return this;
}
private static boolean isNullOrEmpty(String password) {
return password == null || password.trim().isEmpty();
}
private void verifyMasterKeyCanCertify(KeySpec spec) {
@ -276,196 +288,139 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
return keySpec.getKeyType().canCertify();
}
class WithPrimaryUserIdImpl implements WithPrimaryUserId {
@Override
public PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException,
InvalidAlgorithmParameterException {
if (userIds.isEmpty()) {
throw new IllegalStateException("At least one user-id is required.");
}
digestCalculator = buildDigestCalculator();
secretKeyEncryptor = buildSecretKeyEncryptor();
PBESecretKeyDecryptor secretKeyDecryptor = buildSecretKeyDecryptor();
@Override
public WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull String userId) {
KeyRingBuilder.this.userId = userId.trim();
return new WithAdditionalUserIdOrPassphraseImpl();
if (passphrase != null) {
passphrase.clear();
}
@Override
public WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull byte[] userId) {
return withPrimaryUserId(new String(userId, UTF8));
// Generate Primary Key
PGPKeyPair certKey = generateKeyPair(primaryKeySpec);
PGPContentSignerBuilder signer = buildContentSigner(certKey);
signatureGenerator = new PGPSignatureGenerator(signer);
PGPSignatureSubpacketGenerator hashedSubPacketGenerator = primaryKeySpec.getSubpacketGenerator();
hashedSubPacketGenerator.setPrimaryUserID(false, true);
if (expirationDate != null) {
SignatureSubpacketGeneratorUtil.setExpirationDateInSubpacketGenerator(
expirationDate, new Date(), hashedSubPacketGenerator);
}
PGPSignatureSubpacketVector hashedSubPackets = hashedSubPacketGenerator.generate();
// Generator which the user can get the key pair from
PGPKeyRingGenerator ringGenerator = buildRingGenerator(certKey, signer, hashedSubPackets);
addSubKeys(certKey, ringGenerator);
// Generate secret key ring with only primary user id
PGPSecretKeyRing secretKeyRing = ringGenerator.generateSecretKeyRing();
Iterator<PGPSecretKey> secretKeys = secretKeyRing.getSecretKeys();
// Attempt to add additional user-ids to the primary public key
PGPPublicKey primaryPubKey = secretKeys.next().getPublicKey();
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKeyRing.getSecretKey(), secretKeyDecryptor);
Iterator<String> additionalUserIds = userIds.iterator();
additionalUserIds.next(); // Skip primary user id
while (additionalUserIds.hasNext()) {
String additionalUserId = additionalUserIds.next();
signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.getCode(), privateKey);
PGPSignature additionalUserIdSignature =
signatureGenerator.generateCertification(additionalUserId, primaryPubKey);
primaryPubKey = PGPPublicKey.addCertification(primaryPubKey,
additionalUserId, additionalUserIdSignature);
}
// "reassemble" secret key ring with modified primary key
PGPSecretKey primarySecKey = new PGPSecretKey(
privateKey,
primaryPubKey, digestCalculator, true, secretKeyEncryptor);
List<PGPSecretKey> secretKeyList = new ArrayList<>();
secretKeyList.add(primarySecKey);
while (secretKeys.hasNext()) {
secretKeyList.add(secretKeys.next());
}
secretKeyRing = new PGPSecretKeyRing(secretKeyList);
return secretKeyRing;
}
private PGPKeyRingGenerator buildRingGenerator(PGPKeyPair certKey,
PGPContentSignerBuilder signer,
PGPSignatureSubpacketVector hashedSubPackets)
throws PGPException {
String primaryUserId = userIds.iterator().next();
return new PGPKeyRingGenerator(
SignatureType.POSITIVE_CERTIFICATION.getCode(), certKey,
primaryUserId, digestCalculator,
hashedSubPackets, null, signer, secretKeyEncryptor);
}
private void addSubKeys(PGPKeyPair primaryKey, PGPKeyRingGenerator ringGenerator)
throws NoSuchAlgorithmException, PGPException, InvalidAlgorithmParameterException {
for (KeySpec subKeySpec : subkeySpecs) {
PGPKeyPair subKey = generateKeyPair(subKeySpec);
if (subKeySpec.isInheritedSubPackets()) {
ringGenerator.addSubKey(subKey);
} else {
PGPSignatureSubpacketVector hashedSubpackets = subKeySpec.getSubpackets();
try {
hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary(primaryKey, subKey, hashedSubpackets);
} catch (IOException e) {
throw new PGPException("Exception while adding primary key binding signature to signing subkey", e);
}
ringGenerator.addSubKey(subKey, hashedSubpackets, null);
}
}
}
class WithAdditionalUserIdOrPassphraseImpl implements WithAdditionalUserIdOrPassphrase {
@Override
public WithAdditionalUserIdOrPassphrase setExpirationDate(@Nonnull Date expirationDate) {
Date now = new Date();
if (now.after(expirationDate)) {
throw new IllegalArgumentException("Expiration date must be in the future.");
}
KeyRingBuilder.this.expirationDate = expirationDate;
return this;
private PGPSignatureSubpacketVector addPrimaryKeyBindingSignatureIfNecessary(PGPKeyPair primaryKey, PGPKeyPair subKey, PGPSignatureSubpacketVector hashedSubpackets) throws PGPException, IOException {
int keyFlagMask = hashedSubpackets.getKeyFlags();
if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) && !KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
return hashedSubpackets;
}
@Override
public WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull String userId) {
String trimmed = userId.trim();
if (KeyRingBuilder.this.userId.equals(trimmed)) {
throw new IllegalArgumentException("Additional user-id MUST NOT be equal to primary user-id.");
}
KeyRingBuilder.this.additionalUserIds.add(trimmed);
return this;
}
PGPSignatureGenerator bindingSignatureGenerator = new PGPSignatureGenerator(buildContentSigner(subKey));
bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.getCode(), subKey.getPrivateKey());
PGPSignature primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.getPublicKey(), subKey.getPublicKey());
PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator(hashedSubpackets);
subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig);
return subpacketGenerator.generate();
}
@Override
public WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull byte[] userId) {
return withAdditionalUserId(new String(userId, UTF8));
}
private PGPContentSignerBuilder buildContentSigner(PGPKeyPair certKey) {
HashAlgorithm hashAlgorithm = PGPainless.getPolicy().getSignatureHashAlgorithmPolicy().defaultHashAlgorithm();
return ImplementationFactory.getInstance().getPGPContentSignerBuilder(
certKey.getPublicKey().getAlgorithm(),
hashAlgorithm.getAlgorithmId());
}
@Override
public Build withPassphrase(@Nonnull Passphrase passphrase) {
KeyRingBuilder.this.passphrase = passphrase;
return new BuildImpl();
}
private PBESecretKeyEncryptor buildSecretKeyEncryptor() {
SymmetricKeyAlgorithm keyEncryptionAlgorithm = PGPainless.getPolicy().getSymmetricKeyEncryptionAlgorithmPolicy()
.getDefaultSymmetricKeyAlgorithm();
PBESecretKeyEncryptor encryptor = passphrase == null || passphrase.isEmpty() ?
null : // unencrypted key pair, otherwise AES-256 encrypted
ImplementationFactory.getInstance().getPBESecretKeyEncryptor(
keyEncryptionAlgorithm, digestCalculator, passphrase);
return encryptor;
}
@Override
public Build withoutPassphrase() {
KeyRingBuilder.this.passphrase = null;
return new BuildImpl();
}
private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException {
PBESecretKeyDecryptor decryptor = passphrase == null || passphrase.isEmpty() ?
null :
ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase);
return decryptor;
}
class BuildImpl implements Build {
private PGPSignatureGenerator signatureGenerator;
private PGPDigestCalculator digestCalculator;
private PBESecretKeyEncryptor secretKeyEncryptor;
@Override
public PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException,
InvalidAlgorithmParameterException {
digestCalculator = buildDigestCalculator();
secretKeyEncryptor = buildSecretKeyEncryptor();
PBESecretKeyDecryptor secretKeyDecryptor = buildSecretKeyDecryptor();
if (passphrase != null) {
passphrase.clear();
}
// First key is the Master Key
KeySpec certKeySpec = keySpecs.remove(0);
// Generate Master Key
PGPKeyPair certKey = generateKeyPair(certKeySpec);
PGPContentSignerBuilder signer = buildContentSigner(certKey);
signatureGenerator = new PGPSignatureGenerator(signer);
PGPSignatureSubpacketGenerator hashedSubPacketGenerator = certKeySpec.getSubpacketGenerator();
hashedSubPacketGenerator.setPrimaryUserID(false, true);
if (expirationDate != null) {
SignatureSubpacketGeneratorUtil.setExpirationDateInSubpacketGenerator(
expirationDate, new Date(), hashedSubPacketGenerator);
}
PGPSignatureSubpacketVector hashedSubPackets = hashedSubPacketGenerator.generate();
// Generator which the user can get the key pair from
PGPKeyRingGenerator ringGenerator = buildRingGenerator(certKey, signer, hashedSubPackets);
addSubKeys(certKey, ringGenerator);
// Generate secret key ring with only primary user id
PGPSecretKeyRing secretKeyRing = ringGenerator.generateSecretKeyRing();
Iterator<PGPSecretKey> secretKeys = secretKeyRing.getSecretKeys();
// Attempt to add additional user-ids to the primary public key
PGPPublicKey primaryPubKey = secretKeys.next().getPublicKey();
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKeyRing.getSecretKey(), secretKeyDecryptor);
for (String additionalUserId : additionalUserIds) {
signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.getCode(), privateKey);
PGPSignature additionalUserIdSignature =
signatureGenerator.generateCertification(additionalUserId, primaryPubKey);
primaryPubKey = PGPPublicKey.addCertification(primaryPubKey,
additionalUserId, additionalUserIdSignature);
}
// "reassemble" secret key ring with modified primary key
PGPSecretKey primarySecKey = new PGPSecretKey(
privateKey,
primaryPubKey, digestCalculator, true, secretKeyEncryptor);
List<PGPSecretKey> secretKeyList = new ArrayList<>();
secretKeyList.add(primarySecKey);
while (secretKeys.hasNext()) {
secretKeyList.add(secretKeys.next());
}
secretKeyRing = new PGPSecretKeyRing(secretKeyList);
return secretKeyRing;
}
private PGPKeyRingGenerator buildRingGenerator(PGPKeyPair certKey,
PGPContentSignerBuilder signer,
PGPSignatureSubpacketVector hashedSubPackets)
throws PGPException {
return new PGPKeyRingGenerator(
SignatureType.POSITIVE_CERTIFICATION.getCode(), certKey,
userId, digestCalculator,
hashedSubPackets, null, signer, secretKeyEncryptor);
}
private void addSubKeys(PGPKeyPair primaryKey, PGPKeyRingGenerator ringGenerator)
throws NoSuchAlgorithmException, PGPException, InvalidAlgorithmParameterException {
for (KeySpec subKeySpec : keySpecs) {
PGPKeyPair subKey = generateKeyPair(subKeySpec);
if (subKeySpec.isInheritedSubPackets()) {
ringGenerator.addSubKey(subKey);
} else {
PGPSignatureSubpacketVector hashedSubpackets = subKeySpec.getSubpackets();
try {
hashedSubpackets = addPrimaryKeyBindingSignatureIfNecessary(primaryKey, subKey, hashedSubpackets);
} catch (IOException e) {
throw new PGPException("Exception while adding primary key binding signature to signing subkey", e);
}
ringGenerator.addSubKey(subKey, hashedSubpackets, null);
}
}
}
private PGPSignatureSubpacketVector addPrimaryKeyBindingSignatureIfNecessary(PGPKeyPair primaryKey, PGPKeyPair subKey, PGPSignatureSubpacketVector hashedSubpackets) throws PGPException, IOException {
int keyFlagMask = hashedSubpackets.getKeyFlags();
if (!KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.SIGN_DATA) && !KeyFlag.hasKeyFlag(keyFlagMask, KeyFlag.CERTIFY_OTHER)) {
return hashedSubpackets;
}
PGPSignatureGenerator bindingSignatureGenerator = new PGPSignatureGenerator(buildContentSigner(subKey));
bindingSignatureGenerator.init(SignatureType.PRIMARYKEY_BINDING.getCode(), subKey.getPrivateKey());
PGPSignature primaryKeyBindingSig = bindingSignatureGenerator.generateCertification(primaryKey.getPublicKey(), subKey.getPublicKey());
PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator(hashedSubpackets);
subpacketGenerator.addEmbeddedSignature(false, primaryKeyBindingSig);
return subpacketGenerator.generate();
}
private PGPContentSignerBuilder buildContentSigner(PGPKeyPair certKey) {
HashAlgorithm hashAlgorithm = PGPainless.getPolicy().getSignatureHashAlgorithmPolicy().defaultHashAlgorithm();
return ImplementationFactory.getInstance().getPGPContentSignerBuilder(
certKey.getPublicKey().getAlgorithm(),
hashAlgorithm.getAlgorithmId());
}
private PBESecretKeyEncryptor buildSecretKeyEncryptor() {
SymmetricKeyAlgorithm keyEncryptionAlgorithm = PGPainless.getPolicy().getSymmetricKeyEncryptionAlgorithmPolicy()
.getDefaultSymmetricKeyAlgorithm();
PBESecretKeyEncryptor encryptor = passphrase == null || passphrase.isEmpty() ?
null : // unencrypted key pair, otherwise AES-256 encrypted
ImplementationFactory.getInstance().getPBESecretKeyEncryptor(
keyEncryptionAlgorithm, digestCalculator, passphrase);
return encryptor;
}
private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException {
PBESecretKeyDecryptor decryptor = passphrase == null || passphrase.isEmpty() ?
null :
ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase);
return decryptor;
}
private PGPDigestCalculator buildDigestCalculator() throws PGPException {
return ImplementationFactory.getInstance().getPGPDigestCalculator(HashAlgorithm.SHA1);
}
}
private PGPDigestCalculator buildDigestCalculator() throws PGPException {
return ImplementationFactory.getInstance().getPGPDigestCalculator(HashAlgorithm.SHA1);
}
public static PGPKeyPair generateKeyPair(KeySpec spec)

View file

@ -25,50 +25,32 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.pgpainless.key.util.UserId;
import org.pgpainless.util.Passphrase;
public interface KeyRingBuilderInterface {
public interface KeyRingBuilderInterface<B extends KeyRingBuilderInterface<B>> {
KeyRingBuilderInterface withSubKey(@Nonnull KeySpec keySpec);
WithPrimaryUserId withPrimaryKey(@Nonnull KeySpec keySpec);
interface WithPrimaryUserId {
default WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull UserId userId) {
return withPrimaryUserId(userId.toString());
}
WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull String userId);
WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull byte[] userId);
B setPrimaryKey(@Nonnull KeySpec keySpec);
default B setPrimaryKey(@Nonnull KeySpecBuilder builder) {
return setPrimaryKey(builder.build());
}
interface WithAdditionalUserIdOrPassphrase {
B addSubkey(@Nonnull KeySpec keySpec);
default WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull UserId userId) {
return withAdditionalUserId(userId.toString());
}
/**
* Set an expiration date for the key.
*
* @param expirationDate date on which the key will expire.
* @return builder
*/
WithAdditionalUserIdOrPassphrase setExpirationDate(@Nonnull Date expirationDate);
WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull String userId);
WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull byte[] userId);
Build withPassphrase(@Nonnull Passphrase passphrase);
Build withoutPassphrase();
default B addSubkey(@Nonnull KeySpecBuilder builder) {
return addSubkey(builder.build());
}
interface Build {
default B addUserId(UserId userId) {
return addUserId(userId.toString());
}
PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException,
B addUserId(@Nonnull String userId);
B addUserId(@Nonnull byte[] userId);
B setExpirationDate(@Nonnull Date expirationDate);
B setPassphrase(@Nonnull Passphrase passphrase);
PGPSecretKeyRing build() throws NoSuchAlgorithmException, PGPException,
InvalidAlgorithmParameterException;
}
}

View file

@ -20,6 +20,7 @@ import javax.annotation.Nullable;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.key.generation.type.KeyType;
public class KeySpec {
@ -54,7 +55,7 @@ public class KeySpec {
return inheritedSubPackets;
}
public static KeySpecBuilder getBuilder(KeyType type) {
return new KeySpecBuilder(type);
public static KeySpecBuilder getBuilder(KeyType type, KeyFlag flag, KeyFlag... flags) {
return new KeySpecBuilder(type, flag, flags);
}
}

View file

@ -15,10 +15,14 @@
*/
package org.pgpainless.key.generation;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.Set;
import javax.annotation.Nonnull;
import org.bouncycastle.bcpg.sig.Features;
import org.bouncycastle.openpgp.PGPSignatureSubpacketGenerator;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.AlgorithmSuite;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.Feature;
@ -26,24 +30,89 @@ import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.util.CollectionUtils;
public class KeySpecBuilder implements KeySpecBuilderInterface {
private final KeyType type;
private final KeyFlag[] keyFlags;
private final PGPSignatureSubpacketGenerator hashedSubPackets = new PGPSignatureSubpacketGenerator();
private final AlgorithmSuite algorithmSuite = PGPainless.getPolicy().getKeyGenerationAlgorithmSuite();
private Set<CompressionAlgorithm> preferredCompressionAlgorithms = algorithmSuite.getCompressionAlgorithms();
private Set<HashAlgorithm> preferredHashAlgorithms = algorithmSuite.getHashAlgorithms();
private Set<SymmetricKeyAlgorithm> preferredSymmetricAlgorithms = algorithmSuite.getSymmetricKeyAlgorithms();
KeySpecBuilder(@Nonnull KeyType type) {
KeySpecBuilder(@Nonnull KeyType type, KeyFlag flag, KeyFlag... flags) {
if (flag == null) {
throw new IllegalArgumentException("Key MUST carry at least one key flag");
}
if (flags == null) {
throw new IllegalArgumentException("List of additional flags MUST NOT be null.");
}
flags = CollectionUtils.concat(flag, flags);
assureKeyCanCarryFlags(type, flags);
this.type = type;
this.keyFlags = flags;
}
@Override
public WithDetailedConfiguration withKeyFlags(@Nonnull KeyFlag... flags) {
assureKeyCanCarryFlags(flags);
this.hashedSubPackets.setKeyFlags(false, KeyFlag.toBitmask(flags));
return new WithDetailedConfigurationImpl();
public KeySpecBuilder overridePreferredCompressionAlgorithms(@Nonnull CompressionAlgorithm... compressionAlgorithms) {
this.preferredCompressionAlgorithms = new LinkedHashSet<>(Arrays.asList(compressionAlgorithms));
return this;
}
private void assureKeyCanCarryFlags(KeyFlag... flags) {
@Override
public KeySpecBuilder overridePreferredHashAlgorithms(@Nonnull HashAlgorithm... preferredHashAlgorithms) {
this.preferredHashAlgorithms = new LinkedHashSet<>(Arrays.asList(preferredHashAlgorithms));
return this;
}
@Override
public KeySpecBuilder overridePreferredSymmetricKeyAlgorithms(@Nonnull SymmetricKeyAlgorithm... preferredSymmetricKeyAlgorithms) {
this.preferredSymmetricAlgorithms = new LinkedHashSet<>(Arrays.asList(preferredSymmetricKeyAlgorithms));
return this;
}
@Override
public KeySpec build() {
this.hashedSubPackets.setKeyFlags(false, KeyFlag.toBitmask(keyFlags));
this.hashedSubPackets.setPreferredCompressionAlgorithms(false, getPreferredCompressionAlgorithmIDs());
this.hashedSubPackets.setPreferredHashAlgorithms(false, getPreferredHashAlgorithmIDs());
this.hashedSubPackets.setPreferredSymmetricAlgorithms(false, getPreferredSymmetricKeyAlgorithmIDs());
this.hashedSubPackets.setFeature(false, Feature.MODIFICATION_DETECTION.getFeatureId());
return new KeySpec(type, hashedSubPackets, false);
}
private int[] getPreferredCompressionAlgorithmIDs() {
int[] ids = new int[preferredCompressionAlgorithms.size()];
Iterator<CompressionAlgorithm> iterator = preferredCompressionAlgorithms.iterator();
for (int i = 0; i < ids.length; i++) {
ids[i] = iterator.next().getAlgorithmId();
}
return ids;
}
private int[] getPreferredHashAlgorithmIDs() {
int[] ids = new int[preferredHashAlgorithms.size()];
Iterator<HashAlgorithm> iterator = preferredHashAlgorithms.iterator();
for (int i = 0; i < ids.length; i++) {
ids[i] = iterator.next().getAlgorithmId();
}
return ids;
}
private int[] getPreferredSymmetricKeyAlgorithmIDs() {
int[] ids = new int[preferredSymmetricAlgorithms.size()];
Iterator<SymmetricKeyAlgorithm> iterator = preferredSymmetricAlgorithms.iterator();
for (int i = 0; i < ids.length; i++) {
ids[i] = iterator.next().getAlgorithmId();
}
return ids;
}
private static void assureKeyCanCarryFlags(KeyType type, KeyFlag... flags) {
final int mask = KeyFlag.toBitmask(flags);
if (!type.canCertify() && KeyFlag.hasKeyFlag(mask, KeyFlag.CERTIFY_OTHER)) {
@ -66,120 +135,4 @@ public class KeySpecBuilder implements KeySpecBuilderInterface {
throw new IllegalArgumentException("KeyType " + type.getName() + " cannot carry key flag AUTHENTIACTION.");
}
}
@Override
public KeySpec withInheritedSubPackets() {
return new KeySpec(type, null, true);
}
class WithDetailedConfigurationImpl implements WithDetailedConfiguration {
@Deprecated
@Override
public WithPreferredSymmetricAlgorithms withDetailedConfiguration() {
return new WithPreferredSymmetricAlgorithmsImpl();
}
@Override
public KeySpec withDefaultAlgorithms() {
AlgorithmSuite defaultSuite = AlgorithmSuite.getDefaultAlgorithmSuite();
hashedSubPackets.setPreferredCompressionAlgorithms(false, defaultSuite.getCompressionAlgorithmIds());
hashedSubPackets.setPreferredSymmetricAlgorithms(false, defaultSuite.getSymmetricKeyAlgorithmIds());
hashedSubPackets.setPreferredHashAlgorithms(false, defaultSuite.getHashAlgorithmIds());
hashedSubPackets.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);
return new KeySpec(
KeySpecBuilder.this.type,
KeySpecBuilder.this.hashedSubPackets,
false);
}
}
class WithPreferredSymmetricAlgorithmsImpl implements WithPreferredSymmetricAlgorithms {
@Override
public WithPreferredHashAlgorithms withPreferredSymmetricAlgorithms(@Nonnull SymmetricKeyAlgorithm... algorithms) {
int[] ids = new int[algorithms.length];
for (int i = 0; i < ids.length; i++) {
ids[i] = algorithms[i].getAlgorithmId();
}
KeySpecBuilder.this.hashedSubPackets.setPreferredSymmetricAlgorithms(false, ids);
return new WithPreferredHashAlgorithmsImpl();
}
@Override
public WithPreferredHashAlgorithms withDefaultSymmetricAlgorithms() {
KeySpecBuilder.this.hashedSubPackets.setPreferredSymmetricAlgorithms(false,
AlgorithmSuite.getDefaultAlgorithmSuite().getSymmetricKeyAlgorithmIds());
return new WithPreferredHashAlgorithmsImpl();
}
@Override
public WithFeatures withDefaultAlgorithms() {
hashedSubPackets.setPreferredSymmetricAlgorithms(false,
AlgorithmSuite.getDefaultAlgorithmSuite().getSymmetricKeyAlgorithmIds());
hashedSubPackets.setPreferredCompressionAlgorithms(false,
AlgorithmSuite.getDefaultAlgorithmSuite().getCompressionAlgorithmIds());
hashedSubPackets.setPreferredHashAlgorithms(false,
AlgorithmSuite.getDefaultAlgorithmSuite().getHashAlgorithmIds());
return new WithFeaturesImpl();
}
}
class WithPreferredHashAlgorithmsImpl implements WithPreferredHashAlgorithms {
@Override
public WithPreferredCompressionAlgorithms withPreferredHashAlgorithms(@Nonnull HashAlgorithm... algorithms) {
int[] ids = new int[algorithms.length];
for (int i = 0; i < ids.length; i++) {
ids[i] = algorithms[i].getAlgorithmId();
}
KeySpecBuilder.this.hashedSubPackets.setPreferredHashAlgorithms(false, ids);
return new WithPreferredCompressionAlgorithmsImpl();
}
@Override
public WithPreferredCompressionAlgorithms withDefaultHashAlgorithms() {
KeySpecBuilder.this.hashedSubPackets.setPreferredHashAlgorithms(false,
AlgorithmSuite.getDefaultAlgorithmSuite().getHashAlgorithmIds());
return new WithPreferredCompressionAlgorithmsImpl();
}
}
class WithPreferredCompressionAlgorithmsImpl implements WithPreferredCompressionAlgorithms {
@Override
public WithFeatures withPreferredCompressionAlgorithms(@Nonnull CompressionAlgorithm... algorithms) {
int[] ids = new int[algorithms.length];
for (int i = 0; i < ids.length; i++) {
ids[i] = algorithms[i].getAlgorithmId();
}
KeySpecBuilder.this.hashedSubPackets.setPreferredCompressionAlgorithms(false, ids);
return new WithFeaturesImpl();
}
@Override
public WithFeatures withDefaultCompressionAlgorithms() {
KeySpecBuilder.this.hashedSubPackets.setPreferredCompressionAlgorithms(false,
AlgorithmSuite.getDefaultAlgorithmSuite().getCompressionAlgorithmIds());
return new WithFeaturesImpl();
}
}
class WithFeaturesImpl implements WithFeatures {
@Override
public WithFeatures withFeature(@Nonnull Feature feature) {
KeySpecBuilder.this.hashedSubPackets.setFeature(false, feature.getFeatureId());
return this;
}
@Override
public KeySpec done() {
return new KeySpec(
KeySpecBuilder.this.type,
hashedSubPackets,
false);
}
}
}

View file

@ -18,55 +18,16 @@ package org.pgpainless.key.generation;
import javax.annotation.Nonnull;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.Feature;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.KeyFlag;
import org.pgpainless.algorithm.SymmetricKeyAlgorithm;
public interface KeySpecBuilderInterface {
WithDetailedConfiguration withKeyFlags(@Nonnull KeyFlag... flags);
KeySpecBuilder overridePreferredCompressionAlgorithms(@Nonnull CompressionAlgorithm... compressionAlgorithms);
KeySpec withInheritedSubPackets();
KeySpecBuilder overridePreferredHashAlgorithms(@Nonnull HashAlgorithm... preferredHashAlgorithms);
interface WithDetailedConfiguration {
WithPreferredSymmetricAlgorithms withDetailedConfiguration();
KeySpec withDefaultAlgorithms();
}
interface WithPreferredSymmetricAlgorithms {
WithPreferredHashAlgorithms withPreferredSymmetricAlgorithms(@Nonnull SymmetricKeyAlgorithm... algorithms);
WithPreferredHashAlgorithms withDefaultSymmetricAlgorithms();
WithFeatures withDefaultAlgorithms();
}
interface WithPreferredHashAlgorithms {
WithPreferredCompressionAlgorithms withPreferredHashAlgorithms(@Nonnull HashAlgorithm... algorithms);
WithPreferredCompressionAlgorithms withDefaultHashAlgorithms();
}
interface WithPreferredCompressionAlgorithms {
WithFeatures withPreferredCompressionAlgorithms(@Nonnull CompressionAlgorithm... algorithms);
WithFeatures withDefaultCompressionAlgorithms();
}
interface WithFeatures {
WithFeatures withFeature(@Nonnull Feature feature);
KeySpec done();
}
KeySpecBuilder overridePreferredSymmetricKeyAlgorithms(@Nonnull SymmetricKeyAlgorithm... preferredSymmetricKeyAlgorithms);
KeySpec build();
}

View file

@ -21,6 +21,9 @@ import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull;
import org.pgpainless.algorithm.AlgorithmSuite;
import org.pgpainless.algorithm.CompressionAlgorithm;
import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.PublicKeyAlgorithm;
@ -48,6 +51,8 @@ public final class Policy {
PublicKeyAlgorithmPolicy.defaultPublicKeyAlgorithmPolicy();
private final NotationRegistry notationRegistry = new NotationRegistry();
private AlgorithmSuite keyGenerationAlgorithmSuite = AlgorithmSuite.getDefaultAlgorithmSuite();
Policy() {
}
@ -122,7 +127,7 @@ public final class Policy {
*
* @return symmetric algorithm policy for decryption
*/
public SymmetricKeyAlgorithmPolicy getSymmetricKeyDecryptionAlgoritmPolicy() {
public SymmetricKeyAlgorithmPolicy getSymmetricKeyDecryptionAlgorithmPolicy() {
return symmetricKeyDecryptionAlgorithmPolicy;
}
@ -459,4 +464,21 @@ public final class Policy {
public NotationRegistry getNotationRegistry() {
return notationRegistry;
}
/**
* Return the current {@link AlgorithmSuite} which defines preferred algorithms used during key generation.
* @return current algorithm suite
*/
public @Nonnull AlgorithmSuite getKeyGenerationAlgorithmSuite() {
return keyGenerationAlgorithmSuite;
}
/**
* Set a custom {@link AlgorithmSuite} which defines preferred algorithms used during key generation.
*
* @param algorithmSuite custom algorithm suite
*/
public void setKeyGenerationAlgorithmSuite(@Nonnull AlgorithmSuite algorithmSuite) {
this.keyGenerationAlgorithmSuite = algorithmSuite;
}
}

View file

@ -15,6 +15,7 @@
*/
package org.pgpainless.util;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
@ -33,4 +34,11 @@ public final class CollectionUtils {
}
return items;
}
public static <T> T[] concat(T t, T[] ts) {
T[] concat = (T[]) Array.newInstance(t.getClass(), ts.length + 1);
concat[0] = t;
System.arraycopy(ts, 0, concat, 1, ts.length);
return concat;
}
}