From 5fce443ad9c73efbdcbe8ea5b8530624da0f4f1d Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Thu, 31 Aug 2023 17:15:36 +0200 Subject: [PATCH] Kotlin conversion: SecretKeyRingProtector and subclasses --- .../BaseSecretKeyRingProtector.java | 6 +- .../CachingSecretKeyRingProtector.java | 18 +- .../PasswordBasedSecretKeyRingProtector.java | 8 +- .../protection/SecretKeyRingProtector.java | 168 ------------------ .../key/protection/UnlockSecretKey.java | 3 +- .../protection/UnprotectedKeysProtector.java | 6 +- .../MapBasedPassphraseProvider.java | 4 +- .../SecretKeyPassphraseProvider.java | 4 +- .../SolitaryPassphraseProvider.java | 4 +- .../key/protection/SecretKeyRingProtector.kt | 150 ++++++++++++++++ .../MissingPassphraseForDecryptionTest.java | 8 +- ...tionUsingKeyWithMissingPassphraseTest.java | 12 +- .../CachingSecretKeyRingProtectorTest.java | 4 +- .../PassphraseProtectedKeyTest.java | 8 +- .../SecretKeyRingProtectorTest.java | 4 +- .../MatchMakingSecretKeyRingProtector.java | 6 +- 16 files changed, 198 insertions(+), 215 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java create mode 100644 pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java index 1a31d0e8..fead3938 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/BaseSecretKeyRingProtector.java @@ -44,13 +44,13 @@ public class BaseSecretKeyRingProtector implements SecretKeyRingProtector { } @Override - public boolean hasPassphraseFor(Long keyId) { + public boolean hasPassphraseFor(long keyId) { return passphraseProvider.hasPassphrase(keyId); } @Override @Nullable - public PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException { + public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException { Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); return passphrase == null || passphrase.isEmpty() ? null : ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase); @@ -58,7 +58,7 @@ public class BaseSecretKeyRingProtector implements SecretKeyRingProtector { @Override @Nullable - public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException { + public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException { Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); return passphrase == null || passphrase.isEmpty() ? null : ImplementationFactory.getInstance().getPBESecretKeyEncryptor( diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java index 8cdb3efb..0b4fe084 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/CachingSecretKeyRingProtector.java @@ -60,12 +60,12 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se * containing a key with the same key-id but different passphrases. * * If you can ensure that there will be no key-id clash, and you want to replace the passphrase, you can use - * {@link #replacePassphrase(Long, Passphrase)} to replace the passphrase. + * {@link #replacePassphrase(long, Passphrase)} to replace the passphrase. * * @param keyId id of the key * @param passphrase passphrase */ - public void addPassphrase(@Nonnull Long keyId, @Nonnull Passphrase passphrase) { + public void addPassphrase(long keyId, @Nonnull Passphrase passphrase) { if (this.cache.containsKey(keyId)) { throw new IllegalArgumentException("The cache already holds a passphrase for ID " + Long.toHexString(keyId) + ".\n" + "If you want to replace the passphrase, use replacePassphrase(Long, Passphrase) instead."); @@ -79,7 +79,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se * @param keyId keyId * @param passphrase passphrase */ - public void replacePassphrase(@Nonnull Long keyId, @Nonnull Passphrase passphrase) { + public void replacePassphrase(long keyId, @Nonnull Passphrase passphrase) { this.cache.put(keyId, passphrase); } @@ -152,7 +152,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se * * @param keyId id of the key */ - public void forgetPassphrase(@Nonnull Long keyId) { + public void forgetPassphrase(long keyId) { Passphrase passphrase = cache.remove(keyId); if (passphrase != null) { passphrase.clear(); @@ -183,7 +183,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se @Override @Nullable - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { Passphrase passphrase = cache.get(keyId); if (passphrase == null || !passphrase.isValid()) { if (provider == null) { @@ -198,25 +198,25 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { Passphrase passphrase = cache.get(keyId); return passphrase != null && passphrase.isValid(); } @Override - public boolean hasPassphraseFor(Long keyId) { + public boolean hasPassphraseFor(long keyId) { return hasPassphrase(keyId); } @Override @Nullable - public PBESecretKeyDecryptor getDecryptor(@Nonnull Long keyId) throws PGPException { + public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException { return protector.getDecryptor(keyId); } @Override @Nullable - public PBESecretKeyEncryptor getEncryptor(@Nonnull Long keyId) throws PGPException { + public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException { return protector.getEncryptor(keyId); } } 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 c5745068..0e387085 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 @@ -40,12 +40,12 @@ public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtec SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() { @Override @Nullable - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { return hasPassphrase(keyId) ? passphrase : null; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return keyRing.getPublicKey(keyId) != null; } }; @@ -60,7 +60,7 @@ public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtec SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() { @Nullable @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { if (keyId == singleKeyId) { return passphrase; } @@ -68,7 +68,7 @@ public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtec } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return keyId == singleKeyId; } }; diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java deleted file mode 100644 index d7ed5c85..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyRingProtector.java +++ /dev/null @@ -1,168 +0,0 @@ -// SPDX-FileCopyrightText: 2018 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.key.protection; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -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.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; -import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider; -import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider; -import org.pgpainless.util.Passphrase; - -/** - * Task of the {@link SecretKeyRingProtector} is to map encryptor/decryptor objects to key-ids. - * {@link PBESecretKeyEncryptor PBESecretKeyEncryptors}/{@link PBESecretKeyDecryptor PBESecretKeyDecryptors} are used - * to encrypt/decrypt secret keys using a passphrase. - * - * While it is easy to create an implementation of this interface that fits your needs, there are a bunch of - * implementations ready for use. - */ -public interface SecretKeyRingProtector { - - /** - * Returns true, if the protector has a passphrase for the key with the given key-id. - * - * @param keyId key id - * @return true if it has a passphrase, false otherwise - */ - boolean hasPassphraseFor(Long keyId); - - /** - * Return a decryptor for the key of id {@code keyId}. - * This method returns null if the key is unprotected. - * - * @param keyId id of the key - * @return decryptor for the key - * - * @throws PGPException if the decryptor cannot be created for some reason - */ - @Nullable PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException; - - /** - * Return an encryptor for the key of id {@code keyId}. - * This method returns null if the key is unprotected. - * - * @param keyId id of the key - * @return encryptor for the key - * - * @throws PGPException if the encryptor cannot be created for some reason - */ - @Nullable PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException; - - /** - * Return a protector for secret keys. - * The protector maintains an in-memory cache of passphrases and can be extended with new passphrases - * at runtime. - * - * See {@link CachingSecretKeyRingProtector} for how to memorize/forget additional passphrases during runtime. - * - * @param missingPassphraseCallback callback that is used to provide missing passphrases. - * @return caching secret key protector - */ - static CachingSecretKeyRingProtector defaultSecretKeyRingProtector(SecretKeyPassphraseProvider missingPassphraseCallback) { - return new CachingSecretKeyRingProtector( - new HashMap<>(), - KeyRingProtectionSettings.secureDefaultSettings(), - missingPassphraseCallback); - } - - /** - * Use the provided passphrase to lock/unlock all keys in the provided key ring. - * - * This protector will use the provided passphrase to lock/unlock all subkeys present in the provided keys object. - * For other keys that are not present in the ring, it will return null. - * - * @param passphrase passphrase - * @param keys key ring - * @return protector - * @deprecated use {@link #unlockEachKeyWith(Passphrase, PGPSecretKeyRing)} instead. - * - * TODO: Remove in 1.2.X - */ - @Deprecated - static SecretKeyRingProtector unlockAllKeysWith(@Nonnull Passphrase passphrase, @Nonnull PGPSecretKeyRing keys) { - return unlockEachKeyWith(passphrase, keys); - } - - /** - * Use the provided passphrase to lock/unlock all keys in the provided key ring. - * - * This protector will use the provided passphrase to lock/unlock all subkeys present in the provided keys object. - * For other keys that are not present in the ring, it will return null. - * - * @param passphrase passphrase - * @param keys key ring - * @return protector - */ - static SecretKeyRingProtector unlockEachKeyWith(@Nonnull Passphrase passphrase, @Nonnull PGPSecretKeyRing keys) { - Map map = new ConcurrentHashMap<>(); - for (PGPSecretKey secretKey : keys) { - map.put(secretKey.getKeyID(), passphrase); - } - return fromPassphraseMap(map); - } - - /** - * Use the provided passphrase to unlock any key. - * - * @param passphrase passphrase - * @return protector - */ - static SecretKeyRingProtector unlockAnyKeyWith(@Nonnull Passphrase passphrase) { - return new BaseSecretKeyRingProtector(new SolitaryPassphraseProvider(passphrase)); - } - - /** - * Use the provided passphrase to lock/unlock only the provided (sub-)key. - * This protector will only return a non-null encryptor/decryptor based on the provided passphrase if - * {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} is getting called with the key-id of the provided key. - * - * Otherwise, this protector will always return null. - * - * @param passphrase passphrase - * @param key key to lock/unlock - * @return protector - */ - static SecretKeyRingProtector unlockSingleKeyWith(@Nonnull Passphrase passphrase, @Nonnull PGPSecretKey key) { - return PasswordBasedSecretKeyRingProtector.forKey(key, passphrase); - } - - static SecretKeyRingProtector unlockSingleKeyWith(@Nonnull Passphrase passphrase, long keyId) { - return PasswordBasedSecretKeyRingProtector.forKeyId(keyId, passphrase); - } - - /** - * Protector for unprotected keys. - * This protector returns null for all {@link #getEncryptor(Long)}/{@link #getDecryptor(Long)} calls, - * no matter what the key-id is. - * - * As a consequence, this protector can only "unlock" keys which are not protected using a passphrase, and it will - * leave keys unprotected, should it be used to "protect" a key - * (e.g. in {@link org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor#changePassphraseFromOldPassphrase(Passphrase)}). - * - * @return protector - */ - static SecretKeyRingProtector unprotectedKeys() { - return new UnprotectedKeysProtector(); - } - - /** - * Use the provided map of key-ids and passphrases to unlock keys. - * - * @param passphraseMap map of key ids and their respective passphrases - * @return protector - */ - static SecretKeyRingProtector fromPassphraseMap(@Nonnull Map passphraseMap) { - return new CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null); - } -} 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 index 78c849ab..154a1aa1 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnlockSecretKey.java @@ -61,6 +61,7 @@ public final class UnlockSecretKey { public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, Passphrase passphrase) throws PGPException, KeyIntegrityException { - return unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith(passphrase, secretKey)); + return unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith( + passphrase == null ? Passphrase.emptyPassphrase() : passphrase, secretKey)); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java index 26b5c596..5d3f3361 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/UnprotectedKeysProtector.java @@ -15,19 +15,19 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; public class UnprotectedKeysProtector implements SecretKeyRingProtector { @Override - public boolean hasPassphraseFor(Long keyId) { + public boolean hasPassphraseFor(long keyId) { return true; } @Override @Nullable - public PBESecretKeyDecryptor getDecryptor(Long keyId) { + public PBESecretKeyDecryptor getDecryptor(long keyId) { return null; } @Override @Nullable - public PBESecretKeyEncryptor getEncryptor(Long keyId) { + public PBESecretKeyEncryptor getEncryptor(long keyId) { return null; } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java index 859758b1..3f0ee2f9 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/MapBasedPassphraseProvider.java @@ -31,12 +31,12 @@ public class MapBasedPassphraseProvider implements SecretKeyPassphraseProvider { @Nullable @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { return map.get(keyId); } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return map.containsKey(keyId); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java index 59cb39ce..91c2bc95 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SecretKeyPassphraseProvider.java @@ -34,7 +34,7 @@ public interface SecretKeyPassphraseProvider { * @param keyId if of the secret key * @return passphrase or null, if no passphrase record has been found. */ - @Nullable Passphrase getPassphraseFor(Long keyId); + @Nullable Passphrase getPassphraseFor(long keyId); - boolean hasPassphrase(Long keyId); + boolean hasPassphrase(long keyId); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java index b439ef56..9400229b 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/passphrase_provider/SolitaryPassphraseProvider.java @@ -21,13 +21,13 @@ public class SolitaryPassphraseProvider implements SecretKeyPassphraseProvider { @Nullable @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { // always return the same passphrase. return passphrase; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return true; } } diff --git a/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt new file mode 100644 index 00000000..8bdac8d6 --- /dev/null +++ b/pgpainless-core/src/main/kotlin/org/pgpainless/key/protection/SecretKeyRingProtector.kt @@ -0,0 +1,150 @@ +// SPDX-FileCopyrightText: 2023 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.key.protection + +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.PBESecretKeyEncryptor +import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider +import org.pgpainless.key.protection.passphrase_provider.SolitaryPassphraseProvider +import org.pgpainless.util.Passphrase +import kotlin.jvm.Throws + +/** + * Task of the [SecretKeyRingProtector] is to map encryptor/decryptor objects to key-ids. + * [PBESecretKeyEncryptor]/[PBESecretKeyDecryptor] are used to encrypt/decrypt secret keys using a passphrase. + * + * While it is easy to create an implementation of this interface that fits your needs, there are a bunch of + * implementations ready for use. + */ +interface SecretKeyRingProtector { + + /** + * Returns true, if the protector has a passphrase for the key with the given key-id. + * + * @param keyId key id + * @return true if it has a passphrase, false otherwise + */ + fun hasPassphraseFor(keyId: Long): Boolean + + /** + * Return a decryptor for the key of id `keyId`. + * This method returns null if the key is unprotected. + * + * @param keyId id of the key + * @return decryptor for the key + */ + @Throws(PGPException::class) + fun getDecryptor(keyId: Long): PBESecretKeyDecryptor? + + /** + * Return an encryptor for the key of id `keyId`. + * This method returns null if the key is unprotected. + * + * @param keyId id of the key + * @return encryptor for the key + */ + @Throws(PGPException::class) + fun getEncryptor(keyId: Long): PBESecretKeyEncryptor? + + companion object { + + /** + * Return a protector for secret keys. + * The protector maintains an in-memory cache of passphrases and can be extended with new passphrases + * at runtime. + * + * See [CachingSecretKeyRingProtector] for how to memorize/forget additional passphrases during runtime. + * + * @param missingPassphraseCallback callback that is used to provide missing passphrases. + * @return caching secret key protector + */ + @JvmStatic + fun defaultSecretKeyRingProtector( + missingPassphraseCallback: SecretKeyPassphraseProvider? + ): CachingSecretKeyRingProtector = CachingSecretKeyRingProtector( + mapOf(), KeyRingProtectionSettings.secureDefaultSettings(), missingPassphraseCallback) + + /** + * Use the provided passphrase to lock/unlock all keys in the provided key ring. + * + * This protector will use the provided passphrase to lock/unlock all subkeys present in the provided keys object. + * For other keys that are not present in the ring, it will return null. + * + * @param passphrase passphrase + * @param keys key ring + * @return protector + */ + @JvmStatic + fun unlockEachKeyWith(passphrase: Passphrase, keys: PGPSecretKeyRing): SecretKeyRingProtector = + fromPassphraseMap(keys.map { it.keyID }.associateWith { passphrase }) + + /** + * Use the provided passphrase to unlock any key. + * + * @param passphrase passphrase + * @return protector + */ + @JvmStatic + fun unlockAnyKeyWith(passphrase: Passphrase): SecretKeyRingProtector = + BaseSecretKeyRingProtector(SolitaryPassphraseProvider(passphrase)) + + /** + * Use the provided passphrase to lock/unlock only the provided (sub-)key. + * This protector will only return a non-null encryptor/decryptor based on the provided passphrase if + * [getEncryptor]/[getDecryptor] is getting called with the key-id of the provided key. + * + * Otherwise, this protector will always return null. + * + * @param passphrase passphrase + * @param key key to lock/unlock + * @return protector + */ + @JvmStatic + fun unlockSingleKeyWith(passphrase: Passphrase, key: PGPSecretKey): SecretKeyRingProtector = + PasswordBasedSecretKeyRingProtector.forKey(key, passphrase) + + /** + * Use the provided passphrase to lock/unlock only the provided (sub-)key. + * This protector will only return a non-null encryptor/decryptor based on the provided passphrase if + * [getEncryptor]/[getDecryptor] is getting called with the key-id of the provided key. + * + * Otherwise, this protector will always return null. + * + * @param passphrase passphrase + * @param keyId id of the key to lock/unlock + * @return protector + */ + @JvmStatic + fun unlockSingleKeyWith(passphrase: Passphrase, keyId: Long): SecretKeyRingProtector = + PasswordBasedSecretKeyRingProtector.forKeyId(keyId, passphrase) + + /** + * Protector for unprotected keys. + * This protector returns null for all [getEncryptor]/[getDecryptor] calls, + * no matter what the key-id is. + * + * As a consequence, this protector can only "unlock" keys which are not protected using a passphrase, and it will + * leave keys unprotected, should it be used to "protect" a key + * (e.g. in [org.pgpainless.key.modification.secretkeyring.SecretKeyRingEditor.changePassphraseFromOldPassphrase]). + * + * @return protector + */ + @JvmStatic + fun unprotectedKeys() = UnprotectedKeysProtector() + + /** + * Use the provided map of key-ids and passphrases to unlock keys. + * + * @param passphraseMap map of key ids and their respective passphrases + * @return protector + */ + @JvmStatic + fun fromPassphraseMap(passphraseMap: Map): SecretKeyRingProtector = + CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null) + } +} \ No newline at end of file diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java index 42562713..1cf55a4d 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/MissingPassphraseForDecryptionTest.java @@ -63,13 +63,13 @@ public class MissingPassphraseForDecryptionTest { // interactive callback SecretKeyPassphraseProvider callback = new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { // is called in interactive mode return Passphrase.fromPassword(passphrase); } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return true; } }; @@ -95,13 +95,13 @@ public class MissingPassphraseForDecryptionTest { SecretKeyPassphraseProvider callback = new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { fail("MUST NOT get called in non-interactive mode."); return null; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return true; } }; diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PostponeDecryptionUsingKeyWithMissingPassphraseTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PostponeDecryptionUsingKeyWithMissingPassphraseTest.java index c86a823c..914f477e 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PostponeDecryptionUsingKeyWithMissingPassphraseTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PostponeDecryptionUsingKeyWithMissingPassphraseTest.java @@ -120,13 +120,13 @@ public class PostponeDecryptionUsingKeyWithMissingPassphraseTest { public void missingPassphraseFirst() throws PGPException, IOException { SecretKeyRingProtector protector1 = new CachingSecretKeyRingProtector(new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { fail("Although the first PKESK is for k1, we should have skipped it and tried k2 first, which has passphrase available."); return null; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return false; } }); @@ -150,13 +150,13 @@ public class PostponeDecryptionUsingKeyWithMissingPassphraseTest { SecretKeyRingProtector protector1 = SecretKeyRingProtector.unlockEachKeyWith(p1, k1); SecretKeyRingProtector protector2 = new CachingSecretKeyRingProtector(new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { fail("This callback should not get called, since the first PKESK is for k1, which has a passphrase available."); return null; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return false; } }); @@ -178,13 +178,13 @@ public class PostponeDecryptionUsingKeyWithMissingPassphraseTest { public void messagePassphraseFirst() throws PGPException, IOException { SecretKeyPassphraseProvider provider = new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { fail("Since we provide a decryption passphrase, we should not try to decrypt any key."); return null; } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return false; } }; diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java index 3f7a9e6e..9869f7b5 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/CachingSecretKeyRingProtectorTest.java @@ -32,13 +32,13 @@ public class CachingSecretKeyRingProtectorTest { // Dummy passphrase callback that returns the doubled key-id as passphrase private final SecretKeyPassphraseProvider dummyCallback = new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { long doubled = keyId * 2; return Passphrase.fromPassword(Long.toString(doubled)); } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return true; } }; diff --git a/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java b/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java index 45aaf20f..b5703eef 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/key/protection/PassphraseProtectedKeyTest.java @@ -31,8 +31,8 @@ public class PassphraseProtectedKeyTest { new SecretKeyPassphraseProvider() { @Nullable @Override - public Passphrase getPassphraseFor(Long keyId) { - if (keyId.equals(TestKeys.CRYPTIE_KEY_ID)) { + public Passphrase getPassphraseFor(long keyId) { + if (keyId == TestKeys.CRYPTIE_KEY_ID) { return new Passphrase(TestKeys.CRYPTIE_PASSWORD.toCharArray()); } else { return null; @@ -40,8 +40,8 @@ public class PassphraseProtectedKeyTest { } @Override - public boolean hasPassphrase(Long keyId) { - return keyId.equals(TestKeys.CRYPTIE_KEY_ID); + public boolean hasPassphrase(long keyId) { + return keyId == TestKeys.CRYPTIE_KEY_ID; } }); 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 a5030f74..b6781355 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 @@ -108,12 +108,12 @@ public class SecretKeyRingProtectorTest { CachingSecretKeyRingProtector protector = new CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), new SecretKeyPassphraseProvider() { @Override - public Passphrase getPassphraseFor(Long keyId) { + public Passphrase getPassphraseFor(long keyId) { return Passphrase.fromPassword("missingP455w0rd"); } @Override - public boolean hasPassphrase(Long keyId) { + public boolean hasPassphrase(long keyId) { return true; } }); diff --git a/pgpainless-sop/src/main/java/org/pgpainless/sop/MatchMakingSecretKeyRingProtector.java b/pgpainless-sop/src/main/java/org/pgpainless/sop/MatchMakingSecretKeyRingProtector.java index 5badf1f1..67386bde 100644 --- a/pgpainless-sop/src/main/java/org/pgpainless/sop/MatchMakingSecretKeyRingProtector.java +++ b/pgpainless-sop/src/main/java/org/pgpainless/sop/MatchMakingSecretKeyRingProtector.java @@ -88,19 +88,19 @@ public class MatchMakingSecretKeyRingProtector implements SecretKeyRingProtector } @Override - public boolean hasPassphraseFor(Long keyId) { + public boolean hasPassphraseFor(long keyId) { return protector.hasPassphrase(keyId); } @Nullable @Override - public PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException { + public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException { return protector.getDecryptor(keyId); } @Nullable @Override - public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException { + public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException { return protector.getEncryptor(keyId); }