diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java index 1c2c233b..64f9f2da 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java @@ -60,6 +60,7 @@ import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.signature.DetachedSignature; import org.pgpainless.signature.OnePassSignature; import org.pgpainless.util.IntegrityProtectedInputStream; @@ -209,7 +210,7 @@ public final class DecryptionStreamFactory { if (!encryptedData.isIntegrityProtected()) { throw new MessageNotIntegrityProtectedException(); } - + // Data is passphrase encrypted if (encryptedData instanceof PGPPBEEncryptedData) { PGPPBEEncryptedData pbeEncryptedData = (PGPPBEEncryptedData) encryptedData; if (decryptionPassphrase != null) { @@ -228,8 +229,9 @@ public final class DecryptionStreamFactory { LOGGER.log(LEVEL, "Probable passphrase mismatch, skip PBE encrypted data block", e); } } - - } else if (encryptedData instanceof PGPPublicKeyEncryptedData) { + } + // data is public key encrypted + else if (encryptedData instanceof PGPPublicKeyEncryptedData) { PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedData; long keyId = publicKeyEncryptedData.getKeyID(); if (decryptionKeys != null) { @@ -242,7 +244,7 @@ public final class DecryptionStreamFactory { LOGGER.log(LEVEL, "Found respective secret key " + Long.toHexString(keyId)); // Watch out! This assignment is possibly done multiple times. encryptedSessionKey = publicKeyEncryptedData; - decryptionKey = secretKey.extractPrivateKey(decryptionKeyDecryptor.getDecryptor(keyId)); + decryptionKey = UnlockSecretKey.unlockSecretKey(secretKey, decryptionKeyDecryptor); resultBuilder.setDecryptionFingerprint(new OpenPgpV4Fingerprint(secretKey)); } } else { diff --git a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java index 67ee58ec..c0684438 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java +++ b/pgpainless-core/src/main/java/org/pgpainless/encryption_signing/EncryptionBuilder.java @@ -46,6 +46,7 @@ import org.pgpainless.key.KeyRingValidator; import org.pgpainless.key.OpenPgpV4Fingerprint; import org.pgpainless.key.SubkeyIdentifier; import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.util.Passphrase; import org.pgpainless.util.Tuple; import org.pgpainless.util.selection.key.PublicKeySelectionStrategy; @@ -345,7 +346,7 @@ public class EncryptionBuilder implements EncryptionBuilderInterface { PGPSecretKeyRing secretKeyRing = signingKeys.get(signingKey); PGPSecretKey secretKey = secretKeyRing.getSecretKey(signingKey.getSubkeyFingerprint().getKeyId()); PBESecretKeyDecryptor decryptor = signingKeysDecryptor.getDecryptor(secretKey.getKeyID()); - PGPPrivateKey privateKey = secretKey.extractPrivateKey(decryptor); + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, decryptor); privateKeys.put(signingKey, new Tuple<>(secretKeyRing, privateKey)); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/exception/WrongPassphraseException.java b/pgpainless-core/src/main/java/org/pgpainless/exception/WrongPassphraseException.java new file mode 100644 index 00000000..f5207c06 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/exception/WrongPassphraseException.java @@ -0,0 +1,29 @@ +/* + * Copyright 2021 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.exception; + +import org.bouncycastle.openpgp.PGPException; + +public class WrongPassphraseException extends PGPException { + + public WrongPassphraseException(long keyId, PGPException cause) { + this("Wrong passphrase provided for key " + Long.toHexString(keyId), cause); + } + + public WrongPassphraseException(String message, PGPException cause) { + super(message, cause); + } +} 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 ac0d8574..135103e7 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 @@ -57,6 +57,7 @@ import org.pgpainless.key.generation.type.KeyType; import org.pgpainless.key.generation.type.eddsa.EdDSACurve; import org.pgpainless.key.generation.type.rsa.RsaLength; import org.pgpainless.key.generation.type.xdh.XDHCurve; +import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.key.util.UserId; import org.pgpainless.provider.ProviderFactory; import org.pgpainless.util.Passphrase; @@ -376,7 +377,7 @@ public class KeyRingBuilder implements KeyRingBuilderInterface { // Attempt to add additional user-ids to the primary public key PGPPublicKey primaryPubKey = secretKeys.next().getPublicKey(); - PGPPrivateKey privateKey = secretKeyRing.getSecretKey().extractPrivateKey(secretKeyDecryptor); + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKeyRing.getSecretKey(), secretKeyDecryptor); for (String additionalUserId : additionalUserIds) { signatureGenerator.init(SignatureType.POSITIVE_CERTIFICATION.getCode(), privateKey); PGPSignature additionalUserIdSignature = diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java index 28ad1d9f..20078df6 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/modification/secretkeyring/SecretKeyRingEditor.java @@ -15,7 +15,6 @@ */ package org.pgpainless.key.modification.secretkeyring; -import static org.pgpainless.key.util.KeyRingUtils.unlockSecretKey; import static org.pgpainless.util.CollectionUtils.iteratorToList; import java.security.InvalidAlgorithmParameterException; @@ -59,6 +58,7 @@ 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.UnlockSecretKey; import org.pgpainless.key.protection.UnprotectedKeysProtector; import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider; import org.pgpainless.key.util.KeyRingUtils; @@ -102,7 +102,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { if (secretKey.getKeyID() == keyId) { found = true; PGPPublicKey publicKey = secretKey.getPublicKey(); - PGPPrivateKey privateKey = unlockSecretKey(secretKey, secretKeyRingProtector); + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, secretKeyRingProtector); publicKey = addUserIdToPubKey(userId, privateKey, publicKey); secretKey = PGPSecretKey.replacePublicKey(secretKey, publicKey); } @@ -222,7 +222,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { HashAlgorithm.SHA256.getAlgorithmId() // TODO: Why SHA256? ); - PGPPrivateKey privateSubKey = unlockSecretKey(secretSubKey, subKeyProtector); + PGPPrivateKey privateSubKey = UnlockSecretKey.unlockSecretKey(secretSubKey, subKeyProtector); PGPKeyPair subKeyPair = new PGPKeyPair(secretSubKey.getPublicKey(), privateSubKey); PGPKeyRingGenerator keyRingGenerator = new PGPKeyRingGenerator( @@ -362,7 +362,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { RevocationAttributes revocationAttributes) throws PGPException { PGPPublicKey publicKey = KeyRingUtils.requirePublicKeyFrom(secretKeyRing, subKeyId); PGPSecretKey primaryKey = secretKeyRing.getSecretKey(); - PGPPrivateKey privateKey = unlockSecretKey(primaryKey, protector); + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(primaryKey, protector); PGPSignatureSubpacketGenerator subpacketGenerator = new PGPSignatureSubpacketGenerator(); subpacketGenerator.setSignatureCreationTime(false, new Date()); @@ -445,7 +445,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { throw new IllegalArgumentException("Expiration date cannot be before creation date."); } - PGPPrivateKey privateKey = KeyRingUtils.unlockSecretKey(primaryKey, secretKeyRingProtector); + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(primaryKey, secretKeyRingProtector); PGPPublicKey subjectPubKey = subjectKey.getPublicKey(); PGPSignature oldSignature = getPreviousSignature(primaryKey, subjectPubKey); @@ -562,7 +562,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { PGPSignatureSubpacketVector subPackets = subpacketGenerator.generate(); signatureGenerator.setHashedSubpackets(subPackets); - PGPPrivateKey privateKey = primaryKey.extractPrivateKey(protector.getDecryptor(primaryKey.getKeyID())); + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(primaryKey, protector); PGPSignature revocation; if (revokeeSubKey.isMasterKey()) { @@ -675,7 +675,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { Iterator secretKeyIterator = secretKeys.getSecretKeys(); while (secretKeyIterator.hasNext()) { PGPSecretKey secretKey = secretKeyIterator.next(); - PGPPrivateKey privateKey = unlockSecretKey(secretKey, oldProtector); + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, oldProtector); secretKey = lockPrivateKey(privateKey, secretKey.getPublicKey(), newProtector); newlyEncryptedSecretKeys.add(secretKey); } @@ -689,7 +689,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface { if (secretKey.getPublicKey().getKeyID() == keyId) { // Re-encrypt only the selected subkey - PGPPrivateKey privateKey = unlockSecretKey(secretKey, oldProtector); + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, oldProtector); secretKey = lockPrivateKey(privateKey, secretKey.getPublicKey(), newProtector); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java new file mode 100644 index 00000000..94f18a86 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java @@ -0,0 +1,57 @@ +/* + * Copyright 2021 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.protection; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.pgpainless.exception.WrongPassphraseException; +import org.pgpainless.util.Passphrase; + +public class UnlockSecretKey { + + public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector protector) + throws WrongPassphraseException { + try { + PBESecretKeyDecryptor decryptor = protector.getDecryptor(secretKey.getKeyID()); + return secretKey.extractPrivateKey(decryptor); + } catch (PGPException e) { + throw new WrongPassphraseException(secretKey.getKeyID(), e); + } + } + + public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector2 protector) throws WrongPassphraseException { + try { + PBESecretKeyDecryptor decryptor = protector.getDecryptor(secretKey); + return secretKey.extractPrivateKey(decryptor); + } catch (PGPException e) { + throw new WrongPassphraseException(secretKey.getKeyID(), e); + } + } + + public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, PBESecretKeyDecryptor decryptor) throws WrongPassphraseException { + try { + return secretKey.extractPrivateKey(decryptor); + } catch (PGPException e) { + throw new WrongPassphraseException(secretKey.getKeyID(), e); + } + } + + public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, Passphrase passphrase) throws WrongPassphraseException { + return unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith(passphrase, secretKey)); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java index 88830bc7..630f4611 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/util/KeyRingUtils.java @@ -27,8 +27,8 @@ import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; -import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.key.protection.UnlockSecretKey; public class KeyRingUtils { @@ -136,8 +136,6 @@ public class KeyRingUtils { * @throws PGPException if something goes wrong (eg. wrong passphrase) */ public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector protector) throws PGPException { - PBESecretKeyDecryptor secretKeyDecryptor = protector.getDecryptor(secretKey.getKeyID()); - PGPPrivateKey privateKey = secretKey.extractPrivateKey(secretKeyDecryptor); - return privateKey; + return UnlockSecretKey.unlockSecretKey(secretKey, protector); } } 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 index c198bc44..814f0d65 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddSubKeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/modification/AddSubKeyTest.java @@ -39,6 +39,8 @@ import org.pgpainless.key.generation.KeySpec; import org.pgpainless.key.generation.type.ecc.EllipticCurve; import org.pgpainless.key.generation.type.ecc.ecdsa.ECDSA; import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.util.Passphrase; public class AddSubKeyTest { @@ -73,9 +75,8 @@ public class AddSubKeyTest { long subKeyId = keyIdsAfter.get(0); PGPSecretKey subKey = secretKeys.getSecretKey(subKeyId); - PGPPrivateKey privateKey = subKey.extractPrivateKey( - PasswordBasedSecretKeyRingProtector - .forKey(subKey, Passphrase.fromPassword("subKeyPassphrase")) - .getDecryptor(subKeyId)); + SecretKeyRingProtector protector = SecretKeyRingProtector.unlockAllKeysWith( + Passphrase.fromPassword("subKeyPassphrase"), secretKeys); + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(subKey, protector); } } 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 12159f3f..d5845e37 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 @@ -42,6 +42,7 @@ import org.pgpainless.encryption_signing.EncryptionStream; import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.protection.KeyRingProtectionSettings; import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector; +import org.pgpainless.key.protection.UnlockSecretKey; import org.pgpainless.util.Passphrase; public class ChangeSecretKeyRingPassphraseTest { @@ -184,7 +185,7 @@ public class ChangeSecretKeyRingPassphraseTest { PBESecretKeyDecryptor decryptor = passphrase.isEmpty() ? null : new BcPBESecretKeyDecryptorBuilder(digestCalculatorProvider) .build(passphrase.getChars()); - secretKey.extractPrivateKey(decryptor); + UnlockSecretKey.unlockSecretKey(secretKey, decryptor); } private void signDummyMessageWithKeysAndPassphrase(PGPSecretKeyRing keyRing, Passphrase passphrase) throws IOException, PGPException { diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java index 11349324..ef1a74c1 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/SecretKeyRingProtectorTest.java @@ -140,7 +140,7 @@ public class SecretKeyRingProtectorTest { PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing(); for (PGPSecretKey secretKey : secretKeys) { - secretKey.extractPrivateKey(protector.getDecryptor(secretKey)); + UnlockSecretKey.unlockSecretKey(secretKey, protector); assertNotNull(protector.getEncryptor(secretKey)); } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/UnlockSecretKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/UnlockSecretKeyTest.java new file mode 100644 index 00000000..ba770113 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/UnlockSecretKeyTest.java @@ -0,0 +1,58 @@ +/* + * Copyright 2021 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.protection; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; + +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.exception.WrongPassphraseException; +import org.pgpainless.util.Passphrase; + +public class UnlockSecretKeyTest { + + @Test + public void testUnlockSecretKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException { + PGPSecretKeyRing secretKeyRing = PGPainless.generateKeyRing() + .simpleEcKeyRing("alice@wonderland.lit", "heureka!"); + PGPSecretKey secretKey = secretKeyRing.getSecretKey(); + + SecretKeyRingProtector correctPassphrase = SecretKeyRingProtector.unlockAllKeysWith(Passphrase.fromPassword("heureka!"), secretKeyRing); + SecretKeyRingProtector incorrectPassphrase = SecretKeyRingProtector.unlockAllKeysWith(Passphrase.fromPassword("bazinga!"), secretKeyRing); + SecretKeyRingProtector emptyPassphrase = SecretKeyRingProtector.unlockAllKeysWith(Passphrase.emptyPassphrase(), secretKeyRing); + Passphrase cleared = Passphrase.fromPassword("cleared"); + cleared.clear(); + SecretKeyRingProtector invalidPassphrase = SecretKeyRingProtector.unlockAllKeysWith(cleared, secretKeyRing); + // Correct passphrase works + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(secretKey, correctPassphrase); + assertNotNull(privateKey); + + assertThrows(WrongPassphraseException.class, () -> + UnlockSecretKey.unlockSecretKey(secretKey, incorrectPassphrase)); + assertThrows(WrongPassphraseException.class, () -> + UnlockSecretKey.unlockSecretKey(secretKey, emptyPassphrase)); + assertThrows(WrongPassphraseException.class, () -> + UnlockSecretKey.unlockSecretKey(secretKey, invalidPassphrase)); + } +}