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

Re-encrypting whole keyring successful

This commit is contained in:
Paul Schaub 2020-10-25 20:43:09 +01:00
parent 623c4c930d
commit 99af9e0171
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
4 changed files with 109 additions and 19 deletions

View file

@ -16,8 +16,10 @@
package org.pgpainless.key.modification; package org.pgpainless.key.modification;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import javax.annotation.Nullable; import javax.annotation.Nullable;
@ -29,6 +31,7 @@ import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignature;
import org.bouncycastle.openpgp.PGPSignatureGenerator; import org.bouncycastle.openpgp.PGPSignatureGenerator;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
import org.bouncycastle.openpgp.operator.PGPDigestCalculator; import org.bouncycastle.openpgp.operator.PGPDigestCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder; import org.bouncycastle.openpgp.operator.bc.BcPGPContentSignerBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
@ -37,7 +40,11 @@ import org.pgpainless.algorithm.SignatureType;
import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.OpenPgpV4Fingerprint;
import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.generation.KeySpec;
import org.pgpainless.key.protection.KeyRingProtectionSettings; import org.pgpainless.key.protection.KeyRingProtectionSettings;
import org.pgpainless.key.protection.PassphraseMapKeyRingProtector;
import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector;
import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.SecretKeyRingProtector;
import org.pgpainless.key.protection.UnprotectedKeysProtector;
import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider;
import org.pgpainless.key.util.OpenPgpKeyAttributeUtil; import org.pgpainless.key.util.OpenPgpKeyAttributeUtil;
import org.pgpainless.util.Passphrase; import org.pgpainless.util.Passphrase;
@ -100,12 +107,14 @@ public class KeyRingEditor implements KeyRingEditorInterface {
return preferredHashAlgorithms.get(0); return preferredHashAlgorithms.get(0);
} }
// TODO: Move to utility class
private PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector protector) throws PGPException { private PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector protector) throws PGPException {
PBESecretKeyDecryptor secretKeyDecryptor = protector.getDecryptor(secretKey.getKeyID()); PBESecretKeyDecryptor secretKeyDecryptor = protector.getDecryptor(secretKey.getKeyID());
PGPPrivateKey privateKey = secretKey.extractPrivateKey(secretKeyDecryptor); PGPPrivateKey privateKey = secretKey.extractPrivateKey(secretKeyDecryptor);
return privateKey; return privateKey;
} }
// TODO: Move to utility class?
private String sanitizeUserId(String userId) { private String sanitizeUserId(String userId) {
userId = userId.trim(); userId = userId.trim();
// TODO: Further research how to sanitize user IDs. // TODO: Further research how to sanitize user IDs.
@ -146,14 +155,22 @@ public class KeyRingEditor implements KeyRingEditorInterface {
@Override @Override
public WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase(@Nullable Passphrase oldPassphrase, public WithKeyRingEncryptionSettings changePassphraseFromOldPassphrase(@Nullable Passphrase oldPassphrase,
@Nonnull KeyRingProtectionSettings oldProtectionSettings) { @Nonnull KeyRingProtectionSettings oldProtectionSettings) {
return new WithKeyRingEncryptionSettingsImpl(); SecretKeyRingProtector protector = new PasswordBasedSecretKeyRingProtector(
oldProtectionSettings,
new SolitaryPassphraseProvider(oldPassphrase));
return new WithKeyRingEncryptionSettingsImpl(null, protector);
} }
@Override @Override
public WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase(@Nonnull Long keyId, public WithKeyRingEncryptionSettings changeSubKeyPassphraseFromOldPassphrase(@Nonnull Long keyId,
@Nullable Passphrase oldPassphrase, @Nullable Passphrase oldPassphrase,
@Nonnull KeyRingProtectionSettings oldProtectionSettings) { @Nonnull KeyRingProtectionSettings oldProtectionSettings) {
return new WithKeyRingEncryptionSettingsImpl(); Map<Long, Passphrase> passphraseMap = Collections.singletonMap(keyId, oldPassphrase);
SecretKeyRingProtector protector = new PassphraseMapKeyRingProtector(
passphraseMap, oldProtectionSettings, null);
return new WithKeyRingEncryptionSettingsImpl(keyId, protector);
} }
@Override @Override
@ -161,7 +178,16 @@ public class KeyRingEditor implements KeyRingEditorInterface {
return secretKeyRing; return secretKeyRing;
} }
private class WithKeyRingEncryptionSettingsImpl implements WithKeyRingEncryptionSettings { private final class WithKeyRingEncryptionSettingsImpl implements WithKeyRingEncryptionSettings {
private final Long keyId;
// Protector to unlock the key with the old passphrase
private final SecretKeyRingProtector oldProtector;
private WithKeyRingEncryptionSettingsImpl(Long keyId, SecretKeyRingProtector oldProtector) {
this.keyId = keyId;
this.oldProtector = oldProtector;
}
@Override @Override
public WithPassphrase withSecureDefaultSettings() { public WithPassphrase withSecureDefaultSettings() {
@ -170,20 +196,85 @@ public class KeyRingEditor implements KeyRingEditorInterface {
@Override @Override
public WithPassphrase withCustomSettings(KeyRingProtectionSettings settings) { public WithPassphrase withCustomSettings(KeyRingProtectionSettings settings) {
return new WithPassphraseImpl(); return new WithPassphraseImpl(keyId, oldProtector, settings);
} }
} }
private class WithPassphraseImpl implements WithPassphrase { private final class WithPassphraseImpl implements WithPassphrase {
private final SecretKeyRingProtector oldProtector;
private final KeyRingProtectionSettings newProtectionSettings;
private final Long keyId;
private WithPassphraseImpl(Long keyId, SecretKeyRingProtector oldProtector, KeyRingProtectionSettings newProtectionSettings) {
this.keyId = keyId;
this.oldProtector = oldProtector;
this.newProtectionSettings = newProtectionSettings;
}
@Override @Override
public KeyRingEditorInterface toNewPassphrase(Passphrase passphrase) { public KeyRingEditorInterface toNewPassphrase(Passphrase passphrase) throws PGPException {
SecretKeyRingProtector newProtector = new PasswordBasedSecretKeyRingProtector(
newProtectionSettings, new SolitaryPassphraseProvider(passphrase));
PGPSecretKeyRing secretKeys = changePassphrase(keyId, KeyRingEditor.this.secretKeyRing, oldProtector, newProtector);
KeyRingEditor.this.secretKeyRing = secretKeys;
return KeyRingEditor.this; return KeyRingEditor.this;
} }
@Override @Override
public KeyRingEditorInterface noPassphrase() { public KeyRingEditorInterface noPassphrase() throws PGPException {
SecretKeyRingProtector newProtector = new UnprotectedKeysProtector();
PGPSecretKeyRing secretKeys = changePassphrase(keyId, KeyRingEditor.this.secretKeyRing, oldProtector, newProtector);
KeyRingEditor.this.secretKeyRing = secretKeys;
return KeyRingEditor.this; 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<PGPSecretKey> newlyEncryptedSecretKeys = new ArrayList<>();
Iterator<PGPSecretKey> 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<PGPSecretKey> secretKeyList = new ArrayList<>();
Iterator<PGPSecretKey> 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);
}
}
// TODO: Move to utility class
private PGPSecretKey lockPrivateKey(PGPPrivateKey privateKey, PGPPublicKey publicKey, SecretKeyRingProtector protector) throws PGPException {
PGPDigestCalculator checksumCalculator = new BcPGPDigestCalculatorProvider()
// TODO: Again, SHA1?
.get(HashAlgorithm.SHA1.getAlgorithmId());
PBESecretKeyEncryptor encryptor = protector.getEncryptor(publicKey.getKeyID());
PGPSecretKey secretKey = new PGPSecretKey(privateKey, publicKey, checksumCalculator, publicKey.isMasterKey(), encryptor);
return secretKey;
}
} }
} }

View file

@ -153,14 +153,14 @@ public interface KeyRingEditorInterface {
* @param passphrase passphrase * @param passphrase passphrase
* @return editor builder * @return editor builder
*/ */
KeyRingEditorInterface toNewPassphrase(Passphrase passphrase); KeyRingEditorInterface toNewPassphrase(Passphrase passphrase) throws PGPException;
/** /**
* Leave the key unprotected. * Leave the key unprotected.
* *
* @return editor builder * @return editor builder
*/ */
KeyRingEditorInterface noPassphrase(); KeyRingEditorInterface noPassphrase() throws PGPException;
} }
/** /**

View file

@ -18,7 +18,6 @@ package org.pgpainless.key.modification;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException; import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException; import java.security.NoSuchAlgorithmException;
import java.util.Iterator; import java.util.Iterator;
@ -35,7 +34,7 @@ import org.pgpainless.util.Passphrase;
public class AddUserIdTest { public class AddUserIdTest {
@Test @Test
public void addUserIdToExistingKeyRing() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException { public void addUserIdToExistingKeyRing() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException {
PGPKeyRing keyRing = PGPainless.generateKeyRing().simpleEcKeyRing("alice@wonderland.lit", "rabb1th0le"); PGPKeyRing keyRing = PGPainless.generateKeyRing().simpleEcKeyRing("alice@wonderland.lit", "rabb1th0le");
PGPSecretKeyRing secretKeys = keyRing.getSecretKeys(); PGPSecretKeyRing secretKeys = keyRing.getSecretKeys();

View file

@ -36,7 +36,7 @@ import org.pgpainless.util.Passphrase;
public class ChangeSecretKeyRingPassphraseTest { public class ChangeSecretKeyRingPassphraseTest {
@Test @Test
public void changePassphraseTest() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException { public void changePassphraseOfWholeKeyRingTest() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException {
PGPKeyRing keyRing = PGPainless.generateKeyRing().simpleEcKeyRing("password@encryp.ted", "weakPassphrase"); PGPKeyRing keyRing = PGPainless.generateKeyRing().simpleEcKeyRing("password@encryp.ted", "weakPassphrase");
PGPSecretKeyRing secretKeys = PGPainless.modifyKeyRing(keyRing.getSecretKeys()) PGPSecretKeyRing secretKeys = PGPainless.modifyKeyRing(keyRing.getSecretKeys())
.changePassphraseFromOldPassphrase(Passphrase.fromPassword("weakPassphrase")) .changePassphraseFromOldPassphrase(Passphrase.fromPassword("weakPassphrase"))
@ -62,13 +62,13 @@ public class ChangeSecretKeyRingPassphraseTest {
private void signDummyMessageWithKeysAndPassphrase(PGPKeyRing keyRing, Passphrase passphrase) throws IOException, PGPException { private void signDummyMessageWithKeysAndPassphrase(PGPKeyRing keyRing, Passphrase passphrase) throws IOException, PGPException {
String dummyMessage = "dummy"; String dummyMessage = "dummy";
ByteArrayOutputStream dummy = new ByteArrayOutputStream(); ByteArrayOutputStream dummy = new ByteArrayOutputStream();
EncryptionStream stream = PGPainless.createEncryptor().onOutputStream(dummy) EncryptionStream stream = PGPainless.createEncryptor().onOutputStream(dummy)
.doNotEncrypt() .doNotEncrypt()
.signWith(PasswordBasedSecretKeyRingProtector.forKey(keyRing.getSecretKeys(), passphrase), keyRing.getSecretKeys()) .signWith(PasswordBasedSecretKeyRingProtector.forKey(keyRing.getSecretKeys(), passphrase), keyRing.getSecretKeys())
.noArmor(); .noArmor();
Streams.pipeAll(new ByteArrayInputStream(dummyMessage.getBytes()), stream); Streams.pipeAll(new ByteArrayInputStream(dummyMessage.getBytes()), stream);
stream.close(); stream.close();
} }
} }