diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java index 3e7ec4d3..d73c15f8 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/generation/KeyRingBuilder.java @@ -357,23 +357,23 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { .build() .get(HashAlgorithm.SHA1.getAlgorithmId()); } - - private PGPKeyPair generateKeyPair(KeySpec spec) - throws NoSuchAlgorithmException, PGPException, - InvalidAlgorithmParameterException { - KeyType type = spec.getKeyType(); - KeyPairGenerator certKeyGenerator = KeyPairGenerator.getInstance(type.getName(), ProviderFactory.getProvider()); - certKeyGenerator.initialize(type.getAlgorithmSpec()); - - // Create raw Key Pair - KeyPair keyPair = certKeyGenerator.generateKeyPair(); - - // Form PGP key pair - PGPKeyPair pgpKeyPair = new JcaPGPKeyPair(type.getAlgorithm().getAlgorithmId(), - keyPair, new Date()); - - return pgpKeyPair; - } } } + + public static PGPKeyPair generateKeyPair(KeySpec spec) + throws NoSuchAlgorithmException, PGPException, + InvalidAlgorithmParameterException { + KeyType type = spec.getKeyType(); + KeyPairGenerator certKeyGenerator = KeyPairGenerator.getInstance(type.getName(), ProviderFactory.getProvider()); + certKeyGenerator.initialize(type.getAlgorithmSpec()); + + // Create raw Key Pair + KeyPair keyPair = certKeyGenerator.generateKeyPair(); + + // Form PGP key pair + PGPKeyPair pgpKeyPair = new JcaPGPKeyPair(type.getAlgorithm().getAlgorithmId(), + keyPair, new Date()); + + return pgpKeyPair; + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditor.java index 0756fe52..32a21650 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditor.java @@ -15,6 +15,8 @@ */ package org.pgpainless.key.modification; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Collections; import java.util.Iterator; @@ -25,6 +27,8 @@ import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPKeyPair; +import org.bouncycastle.openpgp.PGPKeyRingGenerator; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; @@ -34,12 +38,16 @@ import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.PGPDigestCalculator; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.pgpainless.algorithm.HashAlgorithm; import org.pgpainless.algorithm.SignatureType; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.key.OpenPgpV4Fingerprint; +import org.pgpainless.key.generation.KeyRingBuilder; import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.protection.KeyRingProtectionSettings; import org.pgpainless.key.protection.PassphraseMapKeyRingProtector; @@ -186,17 +194,71 @@ public class KeyRingEditor implements KeyRingEditorInterface { } @Override - public KeyRingEditorInterface addSubKey(KeySpec keySpec, SecretKeyRingProtector protector) { - throw new NotYetImplementedException(); + public KeyRingEditorInterface addSubKey(@Nonnull KeySpec keySpec, + @Nonnull Passphrase subKeyPassphrase, + SecretKeyRingProtector secretKeyRingProtector) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + + PGPSecretKey secretSubKey = generateSubKey(keySpec, subKeyPassphrase); + SecretKeyRingProtector subKeyProtector = PasswordBasedSecretKeyRingProtector + .forKey(secretSubKey, subKeyPassphrase); + + return addSubKey(secretSubKey, subKeyProtector, secretKeyRingProtector); } @Override - public KeyRingEditorInterface deleteSubKey(OpenPgpV4Fingerprint fingerprint, SecretKeyRingProtector protector) { + public KeyRingEditorInterface addSubKey(PGPSecretKey secretSubKey, + SecretKeyRingProtector subKeyProtector, + SecretKeyRingProtector keyRingProtector) + throws PGPException { + + PGPPublicKey primaryKey = secretKeyRing.getSecretKey().getPublicKey(); + + PBESecretKeyDecryptor ringDecryptor = keyRingProtector.getDecryptor(primaryKey.getKeyID()); + PBESecretKeyEncryptor subKeyEncryptor = subKeyProtector.getEncryptor(secretSubKey.getKeyID()); + + PGPDigestCalculator digestCalculator = new BcPGPDigestCalculatorProvider() + .get(defaultDigestHashAlgorithm.getAlgorithmId()); + PGPContentSignerBuilder contentSignerBuilder = new BcPGPContentSignerBuilder( + primaryKey.getAlgorithm(), HashAlgorithm.SHA256.getAlgorithmId()); + + PGPPrivateKey privateSubKey = unlockSecretKey(secretSubKey, subKeyProtector); + PGPKeyPair subKeyPair = new PGPKeyPair(secretSubKey.getPublicKey(), privateSubKey); + + PGPKeyRingGenerator keyRingGenerator = new PGPKeyRingGenerator( + secretKeyRing, ringDecryptor, digestCalculator, contentSignerBuilder, subKeyEncryptor); + + keyRingGenerator.addSubKey(subKeyPair); + secretKeyRing = keyRingGenerator.generateSecretKeyRing(); + + return this; + } + + private PGPSecretKey generateSubKey(@Nonnull KeySpec keySpec, + @Nonnull Passphrase subKeyPassphrase) + throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPDigestCalculator checksumCalculator = new BcPGPDigestCalculatorProvider() + .get(defaultDigestHashAlgorithm.getAlgorithmId()); + + PBESecretKeyEncryptor subKeyEncryptor = subKeyPassphrase.isEmpty() ? null : + new BcPBESecretKeyEncryptorBuilder(SymmetricKeyAlgorithm.AES_256.getAlgorithmId()) + .build(subKeyPassphrase.getChars()); + + PGPKeyPair keyPair = KeyRingBuilder.generateKeyPair(keySpec); + PGPSecretKey secretKey = new PGPSecretKey(keyPair.getPrivateKey(), keyPair.getPublicKey(), + checksumCalculator, false, subKeyEncryptor); + return secretKey; + } + + @Override + public KeyRingEditorInterface deleteSubKey(OpenPgpV4Fingerprint fingerprint, + SecretKeyRingProtector protector) { return deleteSubKey(fingerprint.getKeyId(), protector); } @Override - public KeyRingEditorInterface deleteSubKey(long subKeyId, SecretKeyRingProtector protector) { + public KeyRingEditorInterface deleteSubKey(long subKeyId, + SecretKeyRingProtector protector) { if (secretKeyRing.getSecretKey().getKeyID() == subKeyId) { throw new IllegalArgumentException("You cannot delete the primary key of this key ring."); } @@ -309,48 +371,48 @@ public class KeyRingEditor implements KeyRingEditorInterface { return KeyRingEditor.this; } + } - private PGPSecretKeyRing changePassphrase(Long keyId, - PGPSecretKeyRing secretKeys, - SecretKeyRingProtector oldProtector, - SecretKeyRingProtector newProtector) throws PGPException { - if (keyId == null) { - // change passphrase of whole key ring - List newlyEncryptedSecretKeys = new ArrayList<>(); - Iterator secretKeyIterator = secretKeys.getSecretKeys(); - while (secretKeyIterator.hasNext()) { - PGPSecretKey secretKey = secretKeyIterator.next(); + private PGPSecretKeyRing changePassphrase(Long keyId, + PGPSecretKeyRing secretKeys, + SecretKeyRingProtector oldProtector, + SecretKeyRingProtector newProtector) throws PGPException { + if (keyId == null) { + // change passphrase of whole key ring + List newlyEncryptedSecretKeys = new ArrayList<>(); + Iterator secretKeyIterator = secretKeys.getSecretKeys(); + while (secretKeyIterator.hasNext()) { + PGPSecretKey secretKey = secretKeyIterator.next(); + PGPPrivateKey privateKey = unlockSecretKey(secretKey, oldProtector); + secretKey = lockPrivateKey(privateKey, secretKey.getPublicKey(), newProtector); + newlyEncryptedSecretKeys.add(secretKey); + } + return new PGPSecretKeyRing(newlyEncryptedSecretKeys); + } else { + // change passphrase of selected subkey only + List secretKeyList = new ArrayList<>(); + Iterator secretKeyIterator = secretKeys.getSecretKeys(); + while (secretKeyIterator.hasNext()) { + PGPSecretKey secretKey = secretKeyIterator.next(); + + if (secretKey.getPublicKey().getKeyID() == keyId) { + // Re-encrypt only the selected subkey PGPPrivateKey privateKey = unlockSecretKey(secretKey, oldProtector); secretKey = lockPrivateKey(privateKey, secretKey.getPublicKey(), newProtector); - newlyEncryptedSecretKeys.add(secretKey); } - return new PGPSecretKeyRing(newlyEncryptedSecretKeys); - } else { - // change passphrase of selected subkey only - List secretKeyList = new ArrayList<>(); - Iterator secretKeyIterator = secretKeys.getSecretKeys(); - while (secretKeyIterator.hasNext()) { - PGPSecretKey secretKey = secretKeyIterator.next(); - if (secretKey.getPublicKey().getKeyID() == keyId) { - // Re-encrypt only the selected subkey - PGPPrivateKey privateKey = unlockSecretKey(secretKey, oldProtector); - secretKey = lockPrivateKey(privateKey, secretKey.getPublicKey(), newProtector); - } - - secretKeyList.add(secretKey); - } - return new PGPSecretKeyRing(secretKeyList); + secretKeyList.add(secretKey); } - } - - // TODO: Move to utility class - private PGPSecretKey lockPrivateKey(PGPPrivateKey privateKey, PGPPublicKey publicKey, SecretKeyRingProtector protector) throws PGPException { - PGPDigestCalculator checksumCalculator = new BcPGPDigestCalculatorProvider() - .get(defaultDigestHashAlgorithm.getAlgorithmId()); - PBESecretKeyEncryptor encryptor = protector.getEncryptor(publicKey.getKeyID()); - PGPSecretKey secretKey = new PGPSecretKey(privateKey, publicKey, checksumCalculator, publicKey.isMasterKey(), encryptor); - return secretKey; + return new PGPSecretKeyRing(secretKeyList); } } + + // TODO: Move to utility class + private PGPSecretKey lockPrivateKey(PGPPrivateKey privateKey, PGPPublicKey publicKey, SecretKeyRingProtector protector) throws PGPException { + PGPDigestCalculator checksumCalculator = new BcPGPDigestCalculatorProvider() + .get(defaultDigestHashAlgorithm.getAlgorithmId()); + PBESecretKeyEncryptor encryptor = protector.getEncryptor(publicKey.getKeyID()); + PGPSecretKey secretKey = new PGPSecretKey(privateKey, publicKey, checksumCalculator, publicKey.isMasterKey(), encryptor); + return secretKey; + } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java index 717476dd..3a9403b3 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/KeyRingEditorInterface.java @@ -15,10 +15,13 @@ */ package org.pgpainless.key.modification; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.generation.KeySpec; @@ -63,7 +66,13 @@ public interface KeyRingEditorInterface { * @param keySpec key specification * @return the builder */ - KeyRingEditorInterface addSubKey(KeySpec keySpec, SecretKeyRingProtector secretKeyRingProtector); + KeyRingEditorInterface addSubKey(@Nonnull KeySpec keySpec, + @Nonnull Passphrase subKeyPassphrase, + SecretKeyRingProtector secretKeyRingProtector) + throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException; + + KeyRingEditorInterface addSubKey(PGPSecretKey subKey, SecretKeyRingProtector subKeyProtector, SecretKeyRingProtector keyRingProtector) + throws PGPException; /** * Delete a subkey from the key ring. diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java index cb8bfd91..7328b77b 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java @@ -20,6 +20,7 @@ import javax.annotation.Nullable; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPKeyRing; +import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; @@ -68,6 +69,21 @@ public class PasswordBasedSecretKeyRingProtector implements SecretKeyRingProtect return new PasswordBasedSecretKeyRingProtector(protectionSettings, passphraseProvider); } + public static PasswordBasedSecretKeyRingProtector forKey(PGPSecretKey key, Passphrase passphrase) { + KeyRingProtectionSettings protectionSettings = KeyRingProtectionSettings.secureDefaultSettings(); + SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() { + @Override + @Nullable + public Passphrase getPassphraseFor(Long keyId) { + if (key.getKeyID() == keyId) { + return passphrase; + } + return null; + } + }; + return new PasswordBasedSecretKeyRingProtector(protectionSettings, passphraseProvider); + } + @Override @Nullable public PBESecretKeyDecryptor getDecryptor(Long keyId) { diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddSubKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddSubKeyTest.java new file mode 100644 index 00000000..997a7b12 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddSubKeyTest.java @@ -0,0 +1,77 @@ +/* + * Copyright 2020 Paul Schaub. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.pgpainless.key.modification; + +import static org.junit.Assert.assertNotEquals; + +import java.io.IOException; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.KeyFlag; +import org.pgpainless.key.TestKeys; +import org.pgpainless.key.generation.KeySpec; +import org.pgpainless.key.generation.type.ECDSA; +import org.pgpainless.key.generation.type.curve.EllipticCurve; +import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; +import org.pgpainless.util.Passphrase; + +public class AddSubKeyTest { + + @Test + public void testAddSubKey() throws IOException, PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPSecretKeyRing secretKeys = TestKeys.getCryptieSecretKeyRing(); + + List keyIdsBefore = new ArrayList<>(); + for (Iterator it = secretKeys.getPublicKeys(); it.hasNext(); ) { + keyIdsBefore.add(it.next().getKeyID()); + } + + secretKeys = PGPainless.modifyKeyRing(secretKeys) + .addSubKey( + KeySpec.getBuilder(ECDSA.fromCurve(EllipticCurve._P256)) + .withKeyFlags(KeyFlag.SIGN_DATA) + .withDefaultAlgorithms(), + Passphrase.fromPassword("subKeyPassphrase"), + PasswordBasedSecretKeyRingProtector.forKey(secretKeys, Passphrase.fromPassword("password123"))) + .done(); + + List keyIdsAfter = new ArrayList<>(); + for (Iterator it = secretKeys.getPublicKeys(); it.hasNext(); ) { + keyIdsAfter.add(it.next().getKeyID()); + } + assertNotEquals(keyIdsAfter, keyIdsBefore); + + keyIdsAfter.removeAll(keyIdsBefore); + long subKeyId = keyIdsAfter.get(0); + + PGPSecretKey subKey = secretKeys.getSecretKey(subKeyId); + PGPPrivateKey privateKey = subKey.extractPrivateKey( + PasswordBasedSecretKeyRingProtector + .forKey(subKey, Passphrase.fromPassword("subKeyPassphrase")) + .getDecryptor(subKeyId)); + } +}