mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-26 14:22:05 +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.generation.KeySpec;
|
||||||
import org.pgpainless.key.info.KeyRingInfo;
|
import org.pgpainless.key.info.KeyRingInfo;
|
||||||
import org.pgpainless.key.protection.KeyRingProtectionSettings;
|
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.PasswordBasedSecretKeyRingProtector;
|
||||||
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
||||||
import org.pgpainless.key.protection.UnlockSecretKey;
|
import org.pgpainless.key.protection.UnlockSecretKey;
|
||||||
|
@ -590,7 +590,7 @@ public class SecretKeyRingEditor implements SecretKeyRingEditorInterface {
|
||||||
@Nullable Passphrase oldPassphrase,
|
@Nullable Passphrase oldPassphrase,
|
||||||
@Nonnull KeyRingProtectionSettings oldProtectionSettings) {
|
@Nonnull KeyRingProtectionSettings oldProtectionSettings) {
|
||||||
Map<Long, Passphrase> passphraseMap = Collections.singletonMap(keyId, oldPassphrase);
|
Map<Long, Passphrase> passphraseMap = Collections.singletonMap(keyId, oldPassphrase);
|
||||||
SecretKeyRingProtector protector = new PassphraseMapKeyRingProtector(
|
SecretKeyRingProtector protector = new CachingSecretKeyRingProtector(
|
||||||
passphraseMap, oldProtectionSettings, null);
|
passphraseMap, oldProtectionSettings, null);
|
||||||
|
|
||||||
return new WithKeyRingEncryptionSettingsImpl(keyId, protector);
|
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");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -15,83 +15,21 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.key.protection;
|
package org.pgpainless.key.protection;
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.Nullable;
|
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.key.protection.passphrase_provider.SecretKeyPassphraseProvider;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Implementation of the {@link SecretKeyRingProtector} which holds a map of key ids and their passwords.
|
* Stub class for API backwards compatibility.
|
||||||
* In case the needed passphrase is not contained in the map, the {@code missingPassphraseCallback} will be consulted,
|
* @deprecated use {@link CachingSecretKeyRingProtector} instead.
|
||||||
* and the passphrase is added to the map.
|
|
||||||
*/
|
*/
|
||||||
public class PassphraseMapKeyRingProtector implements SecretKeyRingProtector, SecretKeyPassphraseProvider {
|
@Deprecated
|
||||||
|
public class PassphraseMapKeyRingProtector extends CachingSecretKeyRingProtector {
|
||||||
|
|
||||||
private final Map<Long, Passphrase> cache = new HashMap<>();
|
public PassphraseMapKeyRingProtector(@Nonnull Map<Long, Passphrase> passphrases, @Nonnull KeyRingProtectionSettings protectionSettings, @Nullable SecretKeyPassphraseProvider missingPassphraseCallback) {
|
||||||
private final SecretKeyRingProtector protector;
|
super(passphrases, protectionSettings, missingPassphraseCallback);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
*/
|
*/
|
||||||
package org.pgpainless.key.protection;
|
package org.pgpainless.key.protection;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
@ -24,6 +25,7 @@ import org.bouncycastle.openpgp.PGPSecretKey;
|
||||||
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
||||||
|
import org.pgpainless.key.protection.passphrase_provider.SecretKeyPassphraseProvider;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -50,6 +52,21 @@ public interface SecretKeyRingProtector {
|
||||||
*/
|
*/
|
||||||
@Nullable PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException;
|
@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.
|
* Use the provided passphrase to lock/unlock all subkeys in the provided key ring.
|
||||||
*
|
*
|
||||||
|
@ -92,6 +109,6 @@ public interface SecretKeyRingProtector {
|
||||||
* @return protector
|
* @return protector
|
||||||
*/
|
*/
|
||||||
static SecretKeyRingProtector fromPassphraseMap(Map<Long, Passphrase> passphraseMap) {
|
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() {
|
public void testFromPassphraseMap() {
|
||||||
Map<Long, Passphrase> passphraseMap = new ConcurrentHashMap<>();
|
Map<Long, Passphrase> passphraseMap = new ConcurrentHashMap<>();
|
||||||
passphraseMap.put(1L, Passphrase.emptyPassphrase());
|
passphraseMap.put(1L, Passphrase.emptyPassphrase());
|
||||||
PassphraseMapKeyRingProtector protector = (PassphraseMapKeyRingProtector) SecretKeyRingProtector.fromPassphraseMap(passphraseMap);
|
CachingSecretKeyRingProtector protector = (CachingSecretKeyRingProtector) SecretKeyRingProtector.fromPassphraseMap(passphraseMap);
|
||||||
|
|
||||||
assertNotNull(protector.getPassphraseFor(1L));
|
assertNotNull(protector.getPassphraseFor(1L));
|
||||||
assertNull(protector.getPassphraseFor(5L));
|
assertNull(protector.getPassphraseFor(5L));
|
||||||
|
@ -114,7 +114,7 @@ public class SecretKeyRingProtectorTest {
|
||||||
public void testMissingPassphraseCallback() {
|
public void testMissingPassphraseCallback() {
|
||||||
Map<Long, Passphrase> passphraseMap = new ConcurrentHashMap<>();
|
Map<Long, Passphrase> passphraseMap = new ConcurrentHashMap<>();
|
||||||
passphraseMap.put(1L, Passphrase.emptyPassphrase());
|
passphraseMap.put(1L, Passphrase.emptyPassphrase());
|
||||||
PassphraseMapKeyRingProtector protector = new PassphraseMapKeyRingProtector(passphraseMap,
|
CachingSecretKeyRingProtector protector = new CachingSecretKeyRingProtector(passphraseMap,
|
||||||
KeyRingProtectionSettings.secureDefaultSettings(), new SecretKeyPassphraseProvider() {
|
KeyRingProtectionSettings.secureDefaultSettings(), new SecretKeyPassphraseProvider() {
|
||||||
@Nullable
|
@Nullable
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -39,7 +39,7 @@ import org.pgpainless.encryption_signing.EncryptionBuilderInterface;
|
||||||
import org.pgpainless.encryption_signing.EncryptionStream;
|
import org.pgpainless.encryption_signing.EncryptionStream;
|
||||||
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
import org.pgpainless.key.OpenPgpV4Fingerprint;
|
||||||
import org.pgpainless.key.protection.KeyRingProtectionSettings;
|
import org.pgpainless.key.protection.KeyRingProtectionSettings;
|
||||||
import org.pgpainless.key.protection.PassphraseMapKeyRingProtector;
|
import org.pgpainless.key.protection.CachingSecretKeyRingProtector;
|
||||||
import org.pgpainless.util.Passphrase;
|
import org.pgpainless.util.Passphrase;
|
||||||
import picocli.CommandLine;
|
import picocli.CommandLine;
|
||||||
|
|
||||||
|
@ -148,7 +148,7 @@ public class Encrypt implements Runnable {
|
||||||
.usingSecureAlgorithms();
|
.usingSecureAlgorithms();
|
||||||
EncryptionBuilderInterface.Armor builder_armor;
|
EncryptionBuilderInterface.Armor builder_armor;
|
||||||
if (signWith.length != 0) {
|
if (signWith.length != 0) {
|
||||||
EncryptionBuilderInterface.DocumentType documentType = builder.signWith(new PassphraseMapKeyRingProtector(passphraseMap,
|
EncryptionBuilderInterface.DocumentType documentType = builder.signWith(new CachingSecretKeyRingProtector(passphraseMap,
|
||||||
KeyRingProtectionSettings.secureDefaultSettings(), null), secretKeys);
|
KeyRingProtectionSettings.secureDefaultSettings(), null), secretKeys);
|
||||||
if (type == Type.text || type == Type.mime) {
|
if (type == Type.text || type == Type.mime) {
|
||||||
builder_armor = documentType.signCanonicalText();
|
builder_armor = documentType.signCanonicalText();
|
||||||
|
|
Loading…
Reference in a new issue