From 47b1ccc07128bd24ed5200cb5ce35aeb0d2dca27 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 30 Oct 2020 13:30:21 +0100 Subject: [PATCH] More rigurous testing of key re-encryption --- .../ChangeSecretKeyRingPassphraseTest.java | 142 +++++++++++++++++- 1 file changed, 138 insertions(+), 4 deletions(-) diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangeSecretKeyRingPassphraseTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangeSecretKeyRingPassphraseTest.java index 25fac950..8c7c9e44 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangeSecretKeyRingPassphraseTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/ChangeSecretKeyRingPassphraseTest.java @@ -15,6 +15,7 @@ */ package org.pgpainless.key.modification; +import static junit.framework.TestCase.assertEquals; import static org.junit.Assert.fail; import java.io.ByteArrayInputStream; @@ -22,44 +23,177 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; +import java.util.Iterator; import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; import org.bouncycastle.util.io.Streams; import org.junit.Test; import org.pgpainless.PGPainless; +import org.pgpainless.algorithm.SymmetricKeyAlgorithm; import org.pgpainless.encryption_signing.EncryptionStream; import org.pgpainless.key.collection.PGPKeyRing; +import org.pgpainless.key.protection.KeyRingProtectionSettings; import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; import org.pgpainless.util.Passphrase; public class ChangeSecretKeyRingPassphraseTest { + private final PGPKeyRing keyRing = PGPainless.generateKeyRing().simpleEcKeyRing("password@encryp.ted", "weakPassphrase"); + + public ChangeSecretKeyRingPassphraseTest() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException { + } + @Test public void changePassphraseOfWholeKeyRingTest() throws InvalidAlgorithmParameterException, NoSuchAlgorithmException, PGPException, IOException { - PGPKeyRing keyRing = PGPainless.generateKeyRing().simpleEcKeyRing("password@encryp.ted", "weakPassphrase"); + PGPSecretKeyRing secretKeys = PGPainless.modifyKeyRing(keyRing.getSecretKeys()) .changePassphraseFromOldPassphrase(Passphrase.fromPassword("weakPassphrase")) .withSecureDefaultSettings() .toNewPassphrase(Passphrase.fromPassword("1337p455phr453")) .done(); - keyRing = new PGPKeyRing(secretKeys); + PGPKeyRing changedPassphraseKeyRing = new PGPKeyRing(secretKeys); + + assertEquals(KeyRingProtectionSettings.secureDefaultSettings().getEncryptionAlgorithm().getAlgorithmId(), + changedPassphraseKeyRing.getSecretKeys().getSecretKey().getKeyEncryptionAlgorithm()); try { - signDummyMessageWithKeysAndPassphrase(keyRing, Passphrase.fromPassword("weakPassphrase")); + signDummyMessageWithKeysAndPassphrase(changedPassphraseKeyRing, Passphrase.emptyPassphrase()); + fail("Unlocking secret key ring with empty passphrase MUST fail."); + } catch (PGPException e) { + // yay + } + + try { + signDummyMessageWithKeysAndPassphrase(changedPassphraseKeyRing, Passphrase.fromPassword("weakPassphrase")); fail("Unlocking secret key ring with old passphrase MUST fail."); } catch (PGPException e) { // yay } try { - signDummyMessageWithKeysAndPassphrase(keyRing, Passphrase.fromPassword("1337p455phr453")); + signDummyMessageWithKeysAndPassphrase(changedPassphraseKeyRing, Passphrase.fromPassword("1337p455phr453")); } catch (PGPException e) { fail("Unlocking the secret key ring with the new passphrase MUST succeed."); } } + @Test + public void changePassphraseOfWholeKeyRingToEmptyPassphrase() throws PGPException, IOException { + PGPSecretKeyRing secretKeys = PGPainless.modifyKeyRing(keyRing.getSecretKeys()) + .changePassphraseFromOldPassphrase(Passphrase.fromPassword("weakPassphrase")) + .withSecureDefaultSettings() + .toNoPassphrase() + .done(); + + PGPKeyRing changedPassphraseKeyRing = new PGPKeyRing(secretKeys); + + assertEquals(SymmetricKeyAlgorithm.NULL.getAlgorithmId(), + changedPassphraseKeyRing.getSecretKeys().getSecretKey().getKeyEncryptionAlgorithm()); + + signDummyMessageWithKeysAndPassphrase(changedPassphraseKeyRing, Passphrase.emptyPassphrase()); + } + + @Test + public void changePassphraseOfSingleSubkeyToNewPassphrase() throws PGPException { + + Iterator keys = keyRing.getSecretKeys().getSecretKeys(); + PGPSecretKey primaryKey = keys.next(); + PGPSecretKey subKey = keys.next(); + + extractPrivateKey(primaryKey, Passphrase.fromPassword("weakPassphrase")); + extractPrivateKey(subKey, Passphrase.fromPassword("weakPassphrase")); + + PGPSecretKeyRing secretKeys = PGPainless.modifyKeyRing(keyRing.getSecretKeys()) + .changeSubKeyPassphraseFromOldPassphrase(subKey.getPublicKey().getKeyID(), + Passphrase.fromPassword("weakPassphrase")) + .withSecureDefaultSettings() + .toNewPassphrase(Passphrase.fromPassword("subKeyPassphrase")) + .done(); + + keys = secretKeys.getSecretKeys(); + primaryKey = keys.next(); + subKey = keys.next(); + + extractPrivateKey(primaryKey, Passphrase.fromPassword("weakPassphrase")); + extractPrivateKey(subKey, Passphrase.fromPassword("subKeyPassphrase")); + + try { + extractPrivateKey(primaryKey, Passphrase.fromPassword("subKeyPassphrase")); + fail("Unlocking the primary key with the subkey passphrase must fail."); + } catch (PGPException e) { + // yay + } + + try { + extractPrivateKey(subKey, Passphrase.fromPassword("weakPassphrase")); + fail("Unlocking the subkey with the primary key passphrase must fail."); + } catch (PGPException e) { + // yay + } + } + + @Test + public void changePassphraseOfSingleSubkeyToEmptyPassphrase() throws PGPException { + Iterator keys = keyRing.getSecretKeys().getSecretKeys(); + PGPSecretKey primaryKey = keys.next(); + PGPSecretKey subKey = keys.next(); + + PGPSecretKeyRing secretKeys = PGPainless.modifyKeyRing(keyRing.getSecretKeys()) + .changeSubKeyPassphraseFromOldPassphrase(primaryKey.getKeyID(), Passphrase.fromPassword("weakPassphrase")) + .withSecureDefaultSettings() + .toNoPassphrase() + .done(); + + keys = secretKeys.getSecretKeys(); + primaryKey = keys.next(); + subKey = keys.next(); + + extractPrivateKey(primaryKey, Passphrase.emptyPassphrase()); + extractPrivateKey(subKey, Passphrase.fromPassword("weakPassphrase")); + + try { + extractPrivateKey(primaryKey, Passphrase.fromPassword("weakPassphrase")); + fail("Unlocking the unprotected primary key with the old passphrase must fail."); + } catch (PGPException e) { + // yay + } + + try { + extractPrivateKey(subKey, Passphrase.emptyPassphrase()); + fail("Unlocking the still protected subkey with an empty passphrase must fail."); + } catch (PGPException e) { + // yay + } + + } + + /** + * This method throws an PGPException if the provided passphrase cannot unlock the secret key. + * + * @param secretKey secret key + * @param passphrase passphrase + * @throws PGPException if passphrase is wrong + */ + private void extractPrivateKey(PGPSecretKey secretKey, Passphrase passphrase) throws PGPException { + PGPDigestCalculatorProvider digestCalculatorProvider = new BcPGPDigestCalculatorProvider(); + if (passphrase.isEmpty() && secretKey.getKeyEncryptionAlgorithm() != SymmetricKeyAlgorithm.NULL.getAlgorithmId()) { + throw new PGPException("Cannot unlock encrypted private key with empty passphrase."); + } else if (!passphrase.isEmpty() && secretKey.getKeyEncryptionAlgorithm() == SymmetricKeyAlgorithm.NULL.getAlgorithmId()) { + throw new PGPException("Cannot unlock unprotected private key with non-empty passphrase."); + } + PBESecretKeyDecryptor decryptor = passphrase.isEmpty() ? null : new BcPBESecretKeyDecryptorBuilder(digestCalculatorProvider) + .build(passphrase.getChars()); + + secretKey.extractPrivateKey(decryptor); + } + private void signDummyMessageWithKeysAndPassphrase(PGPKeyRing keyRing, Passphrase passphrase) throws IOException, PGPException { String dummyMessage = "dummy"; ByteArrayOutputStream dummy = new ByteArrayOutputStream();