mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-22 19:08:00 +01:00
Kotlin conversion: SecretKeyRingProtector and subclasses
This commit is contained in:
parent
c40e2ba6c2
commit
5fce443ad9
16 changed files with 198 additions and 215 deletions
|
@ -44,13 +44,13 @@ public class BaseSecretKeyRingProtector implements SecretKeyRingProtector {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphraseFor(Long keyId) {
|
public boolean hasPassphraseFor(long keyId) {
|
||||||
return passphraseProvider.hasPassphrase(keyId);
|
return passphraseProvider.hasPassphrase(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException {
|
public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException {
|
||||||
Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId);
|
Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId);
|
||||||
return passphrase == null || passphrase.isEmpty() ? null :
|
return passphrase == null || passphrase.isEmpty() ? null :
|
||||||
ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase);
|
ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase);
|
||||||
|
@ -58,7 +58,7 @@ public class BaseSecretKeyRingProtector implements SecretKeyRingProtector {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException {
|
public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException {
|
||||||
Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId);
|
Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId);
|
||||||
return passphrase == null || passphrase.isEmpty() ? null :
|
return passphrase == null || passphrase.isEmpty() ? null :
|
||||||
ImplementationFactory.getInstance().getPBESecretKeyEncryptor(
|
ImplementationFactory.getInstance().getPBESecretKeyEncryptor(
|
||||||
|
|
|
@ -60,12 +60,12 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
|
||||||
* containing a key with the same key-id but different passphrases.
|
* 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
|
* 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 keyId id of the key
|
||||||
* @param passphrase passphrase
|
* @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)) {
|
if (this.cache.containsKey(keyId)) {
|
||||||
throw new IllegalArgumentException("The cache already holds a passphrase for ID " + Long.toHexString(keyId) + ".\n" +
|
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.");
|
"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 keyId keyId
|
||||||
* @param passphrase passphrase
|
* @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);
|
this.cache.put(keyId, passphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,7 +152,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
|
||||||
*
|
*
|
||||||
* @param keyId id of the key
|
* @param keyId id of the key
|
||||||
*/
|
*/
|
||||||
public void forgetPassphrase(@Nonnull Long keyId) {
|
public void forgetPassphrase(long keyId) {
|
||||||
Passphrase passphrase = cache.remove(keyId);
|
Passphrase passphrase = cache.remove(keyId);
|
||||||
if (passphrase != null) {
|
if (passphrase != null) {
|
||||||
passphrase.clear();
|
passphrase.clear();
|
||||||
|
@ -183,7 +183,7 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(long keyId) {
|
||||||
Passphrase passphrase = cache.get(keyId);
|
Passphrase passphrase = cache.get(keyId);
|
||||||
if (passphrase == null || !passphrase.isValid()) {
|
if (passphrase == null || !passphrase.isValid()) {
|
||||||
if (provider == null) {
|
if (provider == null) {
|
||||||
|
@ -198,25 +198,25 @@ public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, Se
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
Passphrase passphrase = cache.get(keyId);
|
Passphrase passphrase = cache.get(keyId);
|
||||||
return passphrase != null && passphrase.isValid();
|
return passphrase != null && passphrase.isValid();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphraseFor(Long keyId) {
|
public boolean hasPassphraseFor(long keyId) {
|
||||||
return hasPassphrase(keyId);
|
return hasPassphrase(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public PBESecretKeyDecryptor getDecryptor(@Nonnull Long keyId) throws PGPException {
|
public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException {
|
||||||
return protector.getDecryptor(keyId);
|
return protector.getDecryptor(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public PBESecretKeyEncryptor getEncryptor(@Nonnull Long keyId) throws PGPException {
|
public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException {
|
||||||
return protector.getEncryptor(keyId);
|
return protector.getEncryptor(keyId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,12 +40,12 @@ public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtec
|
||||||
SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() {
|
SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() {
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(long keyId) {
|
||||||
return hasPassphrase(keyId) ? passphrase : null;
|
return hasPassphrase(keyId) ? passphrase : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return keyRing.getPublicKey(keyId) != null;
|
return keyRing.getPublicKey(keyId) != null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -60,7 +60,7 @@ public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtec
|
||||||
SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() {
|
SecretKeyPassphraseProvider passphraseProvider = new SecretKeyPassphraseProvider() {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(long keyId) {
|
||||||
if (keyId == singleKeyId) {
|
if (keyId == singleKeyId) {
|
||||||
return passphrase;
|
return passphrase;
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,7 @@ public class PasswordBasedSecretKeyRingProtector extends BaseSecretKeyRingProtec
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return keyId == singleKeyId;
|
return keyId == singleKeyId;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,168 +0,0 @@
|
||||||
// SPDX-FileCopyrightText: 2018 Paul Schaub <vanitasvitae@fsfe.org>
|
|
||||||
//
|
|
||||||
// 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<Long, Passphrase> 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<Long, Passphrase> passphraseMap) {
|
|
||||||
return new CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -61,6 +61,7 @@ public final class UnlockSecretKey {
|
||||||
|
|
||||||
public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, Passphrase passphrase)
|
public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, Passphrase passphrase)
|
||||||
throws PGPException, KeyIntegrityException {
|
throws PGPException, KeyIntegrityException {
|
||||||
return unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith(passphrase, secretKey));
|
return unlockSecretKey(secretKey, SecretKeyRingProtector.unlockSingleKeyWith(
|
||||||
|
passphrase == null ? Passphrase.emptyPassphrase() : passphrase, secretKey));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,19 +15,19 @@ import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||||
public class UnprotectedKeysProtector implements SecretKeyRingProtector {
|
public class UnprotectedKeysProtector implements SecretKeyRingProtector {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphraseFor(Long keyId) {
|
public boolean hasPassphraseFor(long keyId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public PBESecretKeyDecryptor getDecryptor(Long keyId) {
|
public PBESecretKeyDecryptor getDecryptor(long keyId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public PBESecretKeyEncryptor getEncryptor(Long keyId) {
|
public PBESecretKeyEncryptor getEncryptor(long keyId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,12 +31,12 @@ public class MapBasedPassphraseProvider implements SecretKeyPassphraseProvider {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(long keyId) {
|
||||||
return map.get(keyId);
|
return map.get(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return map.containsKey(keyId);
|
return map.containsKey(keyId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,7 +34,7 @@ public interface SecretKeyPassphraseProvider {
|
||||||
* @param keyId if of the secret key
|
* @param keyId if of the secret key
|
||||||
* @return passphrase or null, if no passphrase record has been found.
|
* @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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,13 +21,13 @@ public class SolitaryPassphraseProvider implements SecretKeyPassphraseProvider {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(long keyId) {
|
||||||
// always return the same passphrase.
|
// always return the same passphrase.
|
||||||
return passphrase;
|
return passphrase;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,150 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Paul Schaub <vanitasvitae@fsfe.org>
|
||||||
|
//
|
||||||
|
// 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<Long, Passphrase>): SecretKeyRingProtector =
|
||||||
|
CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null)
|
||||||
|
}
|
||||||
|
}
|
|
@ -63,13 +63,13 @@ public class MissingPassphraseForDecryptionTest {
|
||||||
// interactive callback
|
// interactive callback
|
||||||
SecretKeyPassphraseProvider callback = new SecretKeyPassphraseProvider() {
|
SecretKeyPassphraseProvider callback = new SecretKeyPassphraseProvider() {
|
||||||
@Override
|
@Override
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(long keyId) {
|
||||||
// is called in interactive mode
|
// is called in interactive mode
|
||||||
return Passphrase.fromPassword(passphrase);
|
return Passphrase.fromPassword(passphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -95,13 +95,13 @@ public class MissingPassphraseForDecryptionTest {
|
||||||
|
|
||||||
SecretKeyPassphraseProvider callback = new SecretKeyPassphraseProvider() {
|
SecretKeyPassphraseProvider callback = new SecretKeyPassphraseProvider() {
|
||||||
@Override
|
@Override
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(long keyId) {
|
||||||
fail("MUST NOT get called in non-interactive mode.");
|
fail("MUST NOT get called in non-interactive mode.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -120,13 +120,13 @@ public class PostponeDecryptionUsingKeyWithMissingPassphraseTest {
|
||||||
public void missingPassphraseFirst() throws PGPException, IOException {
|
public void missingPassphraseFirst() throws PGPException, IOException {
|
||||||
SecretKeyRingProtector protector1 = new CachingSecretKeyRingProtector(new SecretKeyPassphraseProvider() {
|
SecretKeyRingProtector protector1 = new CachingSecretKeyRingProtector(new SecretKeyPassphraseProvider() {
|
||||||
@Override
|
@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.");
|
fail("Although the first PKESK is for k1, we should have skipped it and tried k2 first, which has passphrase available.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -150,13 +150,13 @@ public class PostponeDecryptionUsingKeyWithMissingPassphraseTest {
|
||||||
SecretKeyRingProtector protector1 = SecretKeyRingProtector.unlockEachKeyWith(p1, k1);
|
SecretKeyRingProtector protector1 = SecretKeyRingProtector.unlockEachKeyWith(p1, k1);
|
||||||
SecretKeyRingProtector protector2 = new CachingSecretKeyRingProtector(new SecretKeyPassphraseProvider() {
|
SecretKeyRingProtector protector2 = new CachingSecretKeyRingProtector(new SecretKeyPassphraseProvider() {
|
||||||
@Override
|
@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.");
|
fail("This callback should not get called, since the first PKESK is for k1, which has a passphrase available.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -178,13 +178,13 @@ public class PostponeDecryptionUsingKeyWithMissingPassphraseTest {
|
||||||
public void messagePassphraseFirst() throws PGPException, IOException {
|
public void messagePassphraseFirst() throws PGPException, IOException {
|
||||||
SecretKeyPassphraseProvider provider = new SecretKeyPassphraseProvider() {
|
SecretKeyPassphraseProvider provider = new SecretKeyPassphraseProvider() {
|
||||||
@Override
|
@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.");
|
fail("Since we provide a decryption passphrase, we should not try to decrypt any key.");
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -32,13 +32,13 @@ public class CachingSecretKeyRingProtectorTest {
|
||||||
// Dummy passphrase callback that returns the doubled key-id as passphrase
|
// Dummy passphrase callback that returns the doubled key-id as passphrase
|
||||||
private final SecretKeyPassphraseProvider dummyCallback = new SecretKeyPassphraseProvider() {
|
private final SecretKeyPassphraseProvider dummyCallback = new SecretKeyPassphraseProvider() {
|
||||||
@Override
|
@Override
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(long keyId) {
|
||||||
long doubled = keyId * 2;
|
long doubled = keyId * 2;
|
||||||
return Passphrase.fromPassword(Long.toString(doubled));
|
return Passphrase.fromPassword(Long.toString(doubled));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -31,8 +31,8 @@ public class PassphraseProtectedKeyTest {
|
||||||
new SecretKeyPassphraseProvider() {
|
new SecretKeyPassphraseProvider() {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(long keyId) {
|
||||||
if (keyId.equals(TestKeys.CRYPTIE_KEY_ID)) {
|
if (keyId == TestKeys.CRYPTIE_KEY_ID) {
|
||||||
return new Passphrase(TestKeys.CRYPTIE_PASSWORD.toCharArray());
|
return new Passphrase(TestKeys.CRYPTIE_PASSWORD.toCharArray());
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
@ -40,8 +40,8 @@ public class PassphraseProtectedKeyTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return keyId.equals(TestKeys.CRYPTIE_KEY_ID);
|
return keyId == TestKeys.CRYPTIE_KEY_ID;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -108,12 +108,12 @@ public class SecretKeyRingProtectorTest {
|
||||||
CachingSecretKeyRingProtector protector = new CachingSecretKeyRingProtector(passphraseMap,
|
CachingSecretKeyRingProtector protector = new CachingSecretKeyRingProtector(passphraseMap,
|
||||||
KeyRingProtectionSettings.secureDefaultSettings(), new SecretKeyPassphraseProvider() {
|
KeyRingProtectionSettings.secureDefaultSettings(), new SecretKeyPassphraseProvider() {
|
||||||
@Override
|
@Override
|
||||||
public Passphrase getPassphraseFor(Long keyId) {
|
public Passphrase getPassphraseFor(long keyId) {
|
||||||
return Passphrase.fromPassword("missingP455w0rd");
|
return Passphrase.fromPassword("missingP455w0rd");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphrase(Long keyId) {
|
public boolean hasPassphrase(long keyId) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -88,19 +88,19 @@ public class MatchMakingSecretKeyRingProtector implements SecretKeyRingProtector
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean hasPassphraseFor(Long keyId) {
|
public boolean hasPassphraseFor(long keyId) {
|
||||||
return protector.hasPassphrase(keyId);
|
return protector.hasPassphrase(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public PBESecretKeyDecryptor getDecryptor(Long keyId) throws PGPException {
|
public PBESecretKeyDecryptor getDecryptor(long keyId) throws PGPException {
|
||||||
return protector.getDecryptor(keyId);
|
return protector.getDecryptor(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException {
|
public PBESecretKeyEncryptor getEncryptor(long keyId) throws PGPException {
|
||||||
return protector.getEncryptor(keyId);
|
return protector.getEncryptor(keyId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue