1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2024-11-26 06:12:06 +01:00

Wip: Allow for additional user-ids to be added

This commit is contained in:
Paul Schaub 2020-10-16 12:46:58 +02:00
parent 6a4fa47c12
commit 8c30db9bf1
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
3 changed files with 118 additions and 15 deletions

View file

@ -23,6 +23,7 @@ import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.Iterator;
import java.util.List; import java.util.List;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
@ -30,16 +31,21 @@ import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPKeyPair; import org.bouncycastle.openpgp.PGPKeyPair;
import org.bouncycastle.openpgp.PGPKeyRingGenerator; import org.bouncycastle.openpgp.PGPKeyRingGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.PGPSignatureSubpacketVector; import org.bouncycastle.openpgp.PGPSignatureSubpacketVector;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator; import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPDigestCalculatorProviderBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair; import org.bouncycastle.openpgp.operator.jcajce.JcaPGPKeyPair;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder; import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyEncryptorBuilder;
import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.HashAlgorithm;
import org.pgpainless.algorithm.KeyFlag; import org.pgpainless.algorithm.KeyFlag;
@ -59,6 +65,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
private List<KeySpec> keySpecs = new ArrayList<>(); private List<KeySpec> keySpecs = new ArrayList<>();
private String userId; private String userId;
private List<String> additionalUserIds = new ArrayList<>();
private Passphrase passphrase; private Passphrase passphrase;
/** /**
@ -95,7 +102,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
*/ */
public PGPKeyRing simpleRsaKeyRing(@Nonnull String userId, @Nonnull RsaLength length, String password) public PGPKeyRing simpleRsaKeyRing(@Nonnull String userId, @Nonnull RsaLength length, String password)
throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
WithPassphrase builder = this WithAdditionalUserIdOrPassphrase builder = this
.withMasterKey( .withMasterKey(
KeySpec.getBuilder(RSA_GENERAL.withLength(length)) KeySpec.getBuilder(RSA_GENERAL.withLength(length))
.withDefaultKeyFlags() .withDefaultKeyFlags()
@ -143,7 +150,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
*/ */
public PGPKeyRing simpleEcKeyRing(@Nonnull String userId, String password) public PGPKeyRing simpleEcKeyRing(@Nonnull String userId, String password)
throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException { throws PGPException, NoSuchAlgorithmException, InvalidAlgorithmParameterException {
WithPassphrase builder = this WithAdditionalUserIdOrPassphrase builder = this
.withSubKey( .withSubKey(
KeySpec.getBuilder(ECDH.fromCurve(EllipticCurve._P256)) KeySpec.getBuilder(ECDH.fromCurve(EllipticCurve._P256))
.withKeyFlags(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS) .withKeyFlags(KeyFlag.ENCRYPT_STORAGE, KeyFlag.ENCRYPT_COMMS)
@ -189,18 +196,29 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
class WithPrimaryUserIdImpl implements WithPrimaryUserId { class WithPrimaryUserIdImpl implements WithPrimaryUserId {
@Override @Override
public WithPassphrase withPrimaryUserId(@Nonnull String userId) { public WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull String userId) {
KeyRingBuilder.this.userId = userId; KeyRingBuilder.this.userId = userId;
return new WithPassphraseImpl(); return new WithAdditionalUserIdOrPassphraseImpl();
} }
@Override @Override
public WithPassphrase withPrimaryUserId(@Nonnull byte[] userId) { public WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull byte[] userId) {
return withPrimaryUserId(new String(userId, UTF8)); return withPrimaryUserId(new String(userId, UTF8));
} }
} }
class WithPassphraseImpl implements WithPassphrase { class WithAdditionalUserIdOrPassphraseImpl implements WithAdditionalUserIdOrPassphrase {
@Override
public WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull String userId) {
KeyRingBuilder.this.additionalUserIds.add(userId);
return this;
}
@Override
public WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull byte[] userId) {
return withAdditionalUserId(new String(userId, UTF8));
}
@Override @Override
public Build withPassphrase(@Nonnull Passphrase passphrase) { public Build withPassphrase(@Nonnull Passphrase passphrase) {
@ -216,6 +234,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
class BuildImpl implements Build { class BuildImpl implements Build {
private PGPSignatureGenerator signatureGenerator;
private PGPDigestCalculator digestCalculator; private PGPDigestCalculator digestCalculator;
private PBESecretKeyEncryptor secretKeyEncryptor; private PBESecretKeyEncryptor secretKeyEncryptor;
@ -224,6 +243,11 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
InvalidAlgorithmParameterException { InvalidAlgorithmParameterException {
digestCalculator = buildDigestCalculator(); digestCalculator = buildDigestCalculator();
secretKeyEncryptor = buildSecretKeyEncryptor(); secretKeyEncryptor = buildSecretKeyEncryptor();
PBESecretKeyDecryptor secretKeyDecryptor = buildSecretKeyDecryptor();
if (passphrase != null) {
passphrase.clear();
}
// First key is the Master Key // First key is the Master Key
KeySpec certKeySpec = keySpecs.remove(0); KeySpec certKeySpec = keySpecs.remove(0);
@ -231,6 +255,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
// Generate Master Key // Generate Master Key
PGPKeyPair certKey = generateKeyPair(certKeySpec); PGPKeyPair certKey = generateKeyPair(certKeySpec);
PGPContentSignerBuilder signer = buildContentSigner(certKey); PGPContentSignerBuilder signer = buildContentSigner(certKey);
signatureGenerator = new PGPSignatureGenerator(signer);
PGPSignatureSubpacketVector hashedSubPackets = certKeySpec.getSubpackets(); PGPSignatureSubpacketVector hashedSubPackets = certKeySpec.getSubpackets();
// Generator which the user can get the key pair from // Generator which the user can get the key pair from
@ -238,10 +263,41 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
addSubKeys(ringGenerator); addSubKeys(ringGenerator);
PGPPublicKeyRing publicKeys = ringGenerator.generatePublicKeyRing(); // Generate secret key ring with only primary user id
PGPSecretKeyRing secretKeys = ringGenerator.generateSecretKeyRing(); PGPSecretKeyRing secretKeyRing = ringGenerator.generateSecretKeyRing();
return new PGPKeyRing(publicKeys, secretKeys); Iterator<PGPSecretKey> secretKeys = secretKeyRing.getSecretKeys();
// Attempt to add additional user-ids to the primary public key
PGPPublicKey primaryPubKey = secretKeys.next().getPublicKey();
for (String additionalUserId : additionalUserIds) {
// This fails :(
PGPSignature additionalUserIdSignature =
signatureGenerator.generateCertification(additionalUserId, primaryPubKey);
primaryPubKey = PGPPublicKey.addCertification(primaryPubKey,
additionalUserId, additionalUserIdSignature);
}
// "reassemble" secret key ring with modified primary key
PGPSecretKey primarySecKey = new PGPSecretKey(
secretKeyRing.getSecretKey().extractPrivateKey(secretKeyDecryptor),
primaryPubKey, digestCalculator, true, secretKeyEncryptor);
List<PGPSecretKey> secretKeyList = new ArrayList<>();
secretKeyList.add(primarySecKey);
while (secretKeys.hasNext()) {
secretKeyList.add(secretKeys.next());
}
secretKeyRing = new PGPSecretKeyRing(secretKeyList);
// extract public key ring from secret keys
List<PGPPublicKey> publicKeyList = new ArrayList<>();
Iterator<PGPPublicKey> publicKeys = secretKeyRing.getPublicKeys();
while (publicKeys.hasNext()) {
publicKeyList.add(publicKeys.next());
}
PGPPublicKeyRing publicKeyRing = new PGPPublicKeyRing(publicKeyList);
return new PGPKeyRing(publicKeyRing, secretKeyRing);
} }
private PGPKeyRingGenerator buildRingGenerator(PGPKeyPair certKey, private PGPKeyRingGenerator buildRingGenerator(PGPKeyPair certKey,
@ -278,12 +334,17 @@ public class KeyRingBuilder implements KeyRingBuilderInterface {
new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, digestCalculator) new JcePBESecretKeyEncryptorBuilder(PGPEncryptedData.AES_256, digestCalculator)
.setProvider(ProviderFactory.getProvider()) .setProvider(ProviderFactory.getProvider())
.build(passphrase.getChars()); .build(passphrase.getChars());
if (passphrase != null) {
passphrase.clear();
}
return encryptor; return encryptor;
} }
private PBESecretKeyDecryptor buildSecretKeyDecryptor() throws PGPException {
PBESecretKeyDecryptor decryptor = passphrase == null ?
null :
new JcePBESecretKeyDecryptorBuilder()
.build(passphrase.getChars());
return decryptor;
}
private PGPDigestCalculator buildDigestCalculator() throws PGPException { private PGPDigestCalculator buildDigestCalculator() throws PGPException {
return new JcaPGPDigestCalculatorProviderBuilder() return new JcaPGPDigestCalculatorProviderBuilder()
.setProvider(ProviderFactory.getProvider()) .setProvider(ProviderFactory.getProvider())

View file

@ -31,13 +31,17 @@ public interface KeyRingBuilderInterface {
interface WithPrimaryUserId { interface WithPrimaryUserId {
WithPassphrase withPrimaryUserId(@Nonnull String userId); WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull String userId);
WithPassphrase withPrimaryUserId(@Nonnull byte[] userId); WithAdditionalUserIdOrPassphrase withPrimaryUserId(@Nonnull byte[] userId);
} }
interface WithPassphrase { interface WithAdditionalUserIdOrPassphrase {
WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull String userId);
WithAdditionalUserIdOrPassphrase withAdditionalUserId(@Nonnull byte[] userId);
Build withPassphrase(@Nonnull Passphrase passphrase); Build withPassphrase(@Nonnull Passphrase passphrase);

View file

@ -0,0 +1,38 @@
package org.pgpainless.key.generation;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.openpgp.PGPException;
import org.junit.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.key.collection.PGPKeyRing;
import org.pgpainless.key.generation.type.KeyType;
import org.pgpainless.key.generation.type.RSA_SIGN;
import org.pgpainless.key.generation.type.length.RsaLength;
public class GenerateKeyWithAdditionalUserIdTest {
@Test
public void test() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
PGPKeyRing keyRing = PGPainless.generateKeyRing()
.withMasterKey(KeySpec.getBuilder(RSA_SIGN.withLength(RsaLength._3072))
.withDefaultKeyFlags()
.withDefaultAlgorithms())
.withPrimaryUserId("primary@user.id")
.withAdditionalUserId("additional@user.id")
.withoutPassphrase()
.build();
ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
ArmoredOutputStream armor = new ArmoredOutputStream(byteOut);
keyRing.getSecretKeys().encode(armor);
armor.close();
System.out.println(byteOut.toString("UTF-8"));
}
}