diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java index 2e91d37b..b4059dd5 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PassphraseMapKeyRingProtector.java @@ -21,51 +21,68 @@ import java.util.Map; import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; -import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; -import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder; -import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; 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 PassphraseMapKeyRingProtector implements SecretKeyRingProtector { +public class PassphraseMapKeyRingProtector implements SecretKeyRingProtector, SecretKeyPassphraseProvider { - private static final PGPDigestCalculatorProvider calculatorProvider = new BcPGPDigestCalculatorProvider(); + private final Map cache = new HashMap<>(); + private final SecretKeyRingProtector protector; + private final SecretKeyPassphraseProvider provider; - private final Map passphrases = new HashMap<>(); - private final KeyRingProtectionSettings protectionSettings; - - public PassphraseMapKeyRingProtector(Map passphrases, KeyRingProtectionSettings protectionSettings) { - this.passphrases.putAll(passphrases); - this.protectionSettings = protectionSettings; + public PassphraseMapKeyRingProtector(Map passphrases, + KeyRingProtectionSettings protectionSettings, + 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(Long keyId, Passphrase passphrase) { - this.passphrases.put(keyId, 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(Long keyId) { - Passphrase passphrase = passphrases.get(keyId); + Passphrase passphrase = cache.get(keyId); passphrase.clear(); - passphrases.remove(keyId); + cache.remove(keyId); + } + + @Override + public Passphrase getPassphraseFor(Long keyId) { + Passphrase passphrase = cache.get(keyId); + if (passphrase == null || !passphrase.isValid()) { + passphrase = provider.getPassphraseFor(keyId); + if (passphrase != null) { + cache.put(keyId, passphrase); + } + } + return passphrase; } @Override public PBESecretKeyDecryptor getDecryptor(Long keyId) { - Passphrase passphrase = passphrases.get(keyId); - return new BcPBESecretKeyDecryptorBuilder(calculatorProvider) - .build(passphrase != null ? passphrase.getChars() : null); + return protector.getDecryptor(keyId); } @Override public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException { - Passphrase passphrase = passphrases.get(keyId); - return new BcPBESecretKeyEncryptorBuilder( - protectionSettings.getEncryptionAlgorithm().getAlgorithmId(), - calculatorProvider.get(protectionSettings.getHashAlgorithm().getAlgorithmId()), - protectionSettings.getS2kCount()) - .build(passphrase != null ? passphrase.getChars() : null); + 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 new file mode 100644 index 00000000..f28c6e16 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/PasswordBasedSecretKeyRingProtector.java @@ -0,0 +1,67 @@ +/* + * 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 org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor; +import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor; +import org.bouncycastle.openpgp.operator.PGPDigestCalculatorProvider; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyEncryptorBuilder; +import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider; +import org.pgpainless.util.Passphrase; + +/** + * Provides {@link PBESecretKeyDecryptor} and {@link PBESecretKeyEncryptor} objects while getting the passphrases + * from a {@link SecretKeyPassphraseProvider} and using settings from an {@link KeyRingProtectionSettings}. + */ +public class PasswordBasedSecretKeyRingProtector implements SecretKeyRingProtector { + + private static final PGPDigestCalculatorProvider calculatorProvider = new BcPGPDigestCalculatorProvider(); + + protected final KeyRingProtectionSettings protectionSettings; + protected final SecretKeyPassphraseProvider passphraseProvider; + + /** + * Constructor. + * Passphrases for keys are sourced from the {@code passphraseProvider} and decryptors/encryptors are constructed + * following the settings given in {@code settings}. + * + * @param settings S2K settings etc. + * @param passphraseProvider provider which provides passphrases. + */ + public PasswordBasedSecretKeyRingProtector(KeyRingProtectionSettings settings, SecretKeyPassphraseProvider passphraseProvider) { + this.protectionSettings = settings; + this.passphraseProvider = passphraseProvider; + } + + @Override + public PBESecretKeyDecryptor getDecryptor(Long keyId) { + Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); + return new BcPBESecretKeyDecryptorBuilder(calculatorProvider) + .build(passphrase != null ? passphrase.getChars() : null); + } + + @Override + public PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException { + Passphrase passphrase = passphraseProvider.getPassphraseFor(keyId); + return new BcPBESecretKeyEncryptorBuilder( + protectionSettings.getEncryptionAlgorithm().getAlgorithmId(), + calculatorProvider.get(protectionSettings.getHashAlgorithm().getAlgorithmId()), + protectionSettings.getS2kCount()) + .build(passphrase != null ? passphrase.getChars() : null); + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyPassphraseProvider.java b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyPassphraseProvider.java new file mode 100644 index 00000000..1577a325 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/key/protection/SecretKeyPassphraseProvider.java @@ -0,0 +1,32 @@ +/* + * 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 org.pgpainless.util.Passphrase; + +/** + * Interface to allow the user to provide a passphrase for an encrypted OpenPGP secret key. + */ +public interface SecretKeyPassphraseProvider { + + /** + * Return a passphrase for the given key. + * + * @param keyId id of the key + * @return passphrase + */ + Passphrase getPassphraseFor(Long keyId); +}