mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-12-26 21:07:58 +01:00
Improve CachingSecretKeyRingProtector
This commit is contained in:
parent
95121e2a55
commit
9358e58fb3
6 changed files with 177 additions and 76 deletions
|
@ -55,7 +55,7 @@ import org.pgpainless.key.generation.KeyRingBuilder;
|
|||
import org.pgpainless.key.generation.KeySpec;
|
||||
import org.pgpainless.key.info.KeyRingInfo;
|
||||
import org.pgpainless.key.protection.KeyRingProtectionSettings;
|
||||
import org.pgpainless.key.protection.PassphraseMapKeyRingProtector;
|
||||
import org.pgpainless.key.protection.CachingSecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.PasswordBasedSecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||
|
@ -590,7 +590,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
|||
@Nullable Passphrase oldPassphrase,
|
||||
@Nonnull KeyRingProtectionSettings oldProtectionSettings) {
|
||||
Map<Long, Passphrase> passphraseMap = Collections.singletonMap(keyId, oldPassphrase);
|
||||
SecretKeyRingProtector protector = new PassphraseMapKeyRingProtector(
|
||||
SecretKeyRingProtector protector = new CachingSecretKeyRingProtector(
|
||||
passphraseMap, oldProtectionSettings, null);
|
||||
|
||||
return new WithKeyRingEncryptionSettingsImpl(keyId, protector);
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
/*
|
||||
* Copyright 2018 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 java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.PGPKeyRing;
|
||||
import org.bouncycastle.openpgp.PGPPublicKey;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link SecretKeyRingProtector} which holds a map of key ids and their passwords.
|
||||
* In case the needed passphrase is not contained in the map, the {@code missingPassphraseCallback} will be consulted,
|
||||
* and the passphrase is added to the map.
|
||||
*/
|
||||
public class CachingSecretKeyRingProtector implements SecretKeyRingProtector, SecretKeyPassphraseProvider {
|
||||
|
||||
private final Map<Long, Passphrase> cache = new HashMap<>();
|
||||
private final SecretKeyRingProtector protector;
|
||||
private final SecretKeyPassphraseProvider provider;
|
||||
|
||||
public CachingSecretKeyRingProtector(@Nonnull Map<Long, Passphrase> passphrases,
|
||||
@Nonnull KeyRingProtectionSettings protectionSettings,
|
||||
@Nullable SecretKeyPassphraseProvider missingPassphraseCallback) {
|
||||
this.cache.putAll(passphrases);
|
||||
this.protector = new PasswordBasedSecretKeyRingProtector(protectionSettings, this);
|
||||
this.provider = missingPassphraseCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a passphrase to the cache.
|
||||
*
|
||||
* @param keyId id of the key
|
||||
* @param passphrase passphrase
|
||||
*/
|
||||
public void addPassphrase(@Nonnull Long keyId, @Nullable Passphrase passphrase) {
|
||||
this.cache.put(keyId, passphrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember the given passphrase for all keys in the given key ring.
|
||||
*
|
||||
* @param keyRing key ring
|
||||
* @param passphrase passphrase
|
||||
*/
|
||||
public void addPassphrase(@Nonnull PGPKeyRing keyRing, @Nullable Passphrase passphrase) {
|
||||
Iterator<PGPPublicKey> keys = keyRing.getPublicKeys();
|
||||
while (keys.hasNext()) {
|
||||
PGPPublicKey publicKey = keys.next();
|
||||
addPassphrase(publicKey, passphrase);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remember the given passphrase for the given (sub-)key.
|
||||
*
|
||||
* @param key key
|
||||
* @param passphrase passphrase
|
||||
*/
|
||||
public void addPassphrase(@Nonnull PGPPublicKey key, @Nullable Passphrase passphrase) {
|
||||
addPassphrase(key.getKeyID(), passphrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a passphrase from the cache.
|
||||
* The passphrase will be cleared and then removed.
|
||||
*
|
||||
* @param keyId id of the key
|
||||
*/
|
||||
public void forgetPassphrase(@Nonnull Long keyId) {
|
||||
Passphrase passphrase = cache.get(keyId);
|
||||
passphrase.clear();
|
||||
cache.remove(keyId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget the passphrase to all keys in the provided key ring.
|
||||
*
|
||||
* @param keyRing key ring
|
||||
*/
|
||||
public void forgetPassphrase(@Nonnull PGPKeyRing keyRing) {
|
||||
Iterator<PGPPublicKey> keys = keyRing.getPublicKeys();
|
||||
while (keys.hasNext()) {
|
||||
PGPPublicKey publicKey = keys.next();
|
||||
forgetPassphrase(publicKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget the passphrase of the given public key.
|
||||
*
|
||||
* @param key key
|
||||
*/
|
||||
public void forgetPassphrase(@Nonnull PGPPublicKey key) {
|
||||
forgetPassphrase(key.getKeyID());
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Passphrase getPassphraseFor(Long keyId) {
|
||||
Passphrase passphrase = cache.get(keyId);
|
||||
if (passphrase == null || !passphrase.isValid()) {
|
||||
if (provider == null) {
|
||||
return null;
|
||||
}
|
||||
passphrase = provider.getPassphraseFor(keyId);
|
||||
if (passphrase != null) {
|
||||
cache.put(keyId, passphrase);
|
||||
}
|
||||
}
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PBESecretKeyDecryptor getDecryptor(@Nonnull Long keyId) throws PGPException {
|
||||
return protector.getDecryptor(keyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PBESecretKeyEncryptor getEncryptor(@Nonnull Long keyId) throws PGPException {
|
||||
return protector.getEncryptor(keyId);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* Copyright 2018 Paul Schaub.
|
||||
* 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.
|
||||
|
@ -15,83 +15,21 @@
|
|||
*/
|
||||
package org.pgpainless.key.protection;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nonnull;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import org.bouncycastle.openpgp.PGPException;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||
import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
|
||||
/**
|
||||
* Implementation of the {@link SecretKeyRingProtector} which holds a map of key ids and their passwords.
|
||||
* In case the needed passphrase is not contained in the map, the {@code missingPassphraseCallback} will be consulted,
|
||||
* and the passphrase is added to the map.
|
||||
* Stub class for API backwards compatibility.
|
||||
* @deprecated use {@link CachingSecretKeyRingProtector} instead.
|
||||
*/
|
||||
public class PassphraseMapKeyRingProtector implements SecretKeyRingProtector, SecretKeyPassphraseProvider {
|
||||
@Deprecated
|
||||
public class PassphraseMapKeyRingProtector extends CachingSecretKeyRingProtector {
|
||||
|
||||
private final Map<Long, Passphrase> cache = new HashMap<>();
|
||||
private final SecretKeyRingProtector protector;
|
||||
private final SecretKeyPassphraseProvider provider;
|
||||
|
||||
public PassphraseMapKeyRingProtector(@Nonnull Map<Long, Passphrase> passphrases,
|
||||
@Nonnull KeyRingProtectionSettings protectionSettings,
|
||||
@Nullable SecretKeyPassphraseProvider missingPassphraseCallback) {
|
||||
this.cache.putAll(passphrases);
|
||||
this.protector = new PasswordBasedSecretKeyRingProtector(protectionSettings, this);
|
||||
this.provider = missingPassphraseCallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a passphrase to the cache.
|
||||
*
|
||||
* @param keyId id of the key
|
||||
* @param passphrase passphrase
|
||||
*/
|
||||
public void addPassphrase(@Nonnull Long keyId, @Nullable Passphrase passphrase) {
|
||||
this.cache.put(keyId, passphrase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a passphrase from the cache.
|
||||
* The passphrase will be cleared and then removed.
|
||||
*
|
||||
* @param keyId id of the key
|
||||
*/
|
||||
public void forgetPassphrase(@Nonnull Long keyId) {
|
||||
Passphrase passphrase = cache.get(keyId);
|
||||
passphrase.clear();
|
||||
cache.remove(keyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Passphrase getPassphraseFor(Long keyId) {
|
||||
Passphrase passphrase = cache.get(keyId);
|
||||
if (passphrase == null || !passphrase.isValid()) {
|
||||
if (provider == null) {
|
||||
return null;
|
||||
}
|
||||
passphrase = provider.getPassphraseFor(keyId);
|
||||
if (passphrase != null) {
|
||||
cache.put(keyId, passphrase);
|
||||
}
|
||||
}
|
||||
return passphrase;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PBESecretKeyDecryptor getDecryptor(@Nonnull Long keyId) throws PGPException {
|
||||
return protector.getDecryptor(keyId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public PBESecretKeyEncryptor getEncryptor(@Nonnull Long keyId) throws PGPException {
|
||||
return protector.getEncryptor(keyId);
|
||||
public PassphraseMapKeyRingProtector(@Nonnull Map<Long, Passphrase> passphrases, @Nonnull KeyRingProtectionSettings protectionSettings, @Nullable SecretKeyPassphraseProvider missingPassphraseCallback) {
|
||||
super(passphrases, protectionSettings, missingPassphraseCallback);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
*/
|
||||
package org.pgpainless.key.protection;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import javax.annotation.Nullable;
|
||||
|
@ -24,6 +25,7 @@ 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.util.Passphrase;
|
||||
|
||||
/**
|
||||
|
@ -50,6 +52,21 @@ public interface SecretKeyRingProtector {
|
|||
*/
|
||||
@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.
|
||||
*
|
||||
* @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 subkeys in the provided key ring.
|
||||
*
|
||||
|
@ -92,6 +109,6 @@ public interface SecretKeyRingProtector {
|
|||
* @return protector
|
||||
*/
|
||||
static SecretKeyRingProtector fromPassphraseMap(Map<Long, Passphrase> passphraseMap) {
|
||||
return new PassphraseMapKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null);
|
||||
return new CachingSecretKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -98,7 +98,7 @@ public class SecretKeyRingProtectorTest {
|
|||
public void testFromPassphraseMap() {
|
||||
Map<Long, Passphrase> passphraseMap = new ConcurrentHashMap<>();
|
||||
passphraseMap.put(1L, Passphrase.emptyPassphrase());
|
||||
PassphraseMapKeyRingProtector protector = (PassphraseMapKeyRingProtector) SecretKeyRingProtector.fromPassphraseMap(passphraseMap);
|
||||
CachingSecretKeyRingProtector protector = (CachingSecretKeyRingProtector) SecretKeyRingProtector.fromPassphraseMap(passphraseMap);
|
||||
|
||||
assertNotNull(protector.getPassphraseFor(1L));
|
||||
assertNull(protector.getPassphraseFor(5L));
|
||||
|
@ -114,7 +114,7 @@ public class SecretKeyRingProtectorTest {
|
|||
public void testMissingPassphraseCallback() {
|
||||
Map<Long, Passphrase> passphraseMap = new ConcurrentHashMap<>();
|
||||
passphraseMap.put(1L, Passphrase.emptyPassphrase());
|
||||
PassphraseMapKeyRingProtector protector = new PassphraseMapKeyRingProtector(passphraseMap,
|
||||
CachingSecretKeyRingProtector protector = new CachingSecretKeyRingProtector(passphraseMap,
|
||||
KeyRingProtectionSettings.secureDefaultSettings(), new SecretKeyPassphraseProvider() {
|
||||
@Nullable
|
||||
@Override
|
||||
|
|
|
@ -39,7 +39,7 @@ import org.pgpainless.encryption_signing.EncryptionBuilderInterface;
|
|||
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||
import org.pgpainless.key.protection.KeyRingProtectionSettings;
|
||||
import org.pgpainless.key.protection.PassphraseMapKeyRingProtector;
|
||||
import org.pgpainless.key.protection.CachingSecretKeyRingProtector;
|
||||
import org.pgpainless.util.Passphrase;
|
||||
import picocli.CommandLine;
|
||||
|
||||
|
@ -148,7 +148,7 @@ public class Encrypt implements Runnable {
|
|||
.usingSecureAlgorithms();
|
||||
EncryptionBuilderInterface.Armor builder_armor;
|
||||
if (signWith.length != 0) {
|
||||
EncryptionBuilderInterface.DocumentType documentType = builder.signWith(new PassphraseMapKeyRingProtector(passphraseMap,
|
||||
EncryptionBuilderInterface.DocumentType documentType = builder.signWith(new CachingSecretKeyRingProtector(passphraseMap,
|
||||
KeyRingProtectionSettings.secureDefaultSettings(), null), secretKeys);
|
||||
if (type == Type.text || type == Type.mime) {
|
||||
builder_armor = documentType.signCanonicalText();
|
||||
|
|
Loading…
Reference in a new issue