mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-11-26 22:32:07 +01:00
Get rid of redundant SecretKeyRingProtector implementations.
This commit is contained in:
parent
8313895f26
commit
95121e2a55
7 changed files with 34 additions and 156 deletions
|
@ -1,67 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 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.Map;
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import org.bouncycastle.openpgp.PGPException;
|
|
||||||
import org.bouncycastle.openpgp.PGPSecretKey;
|
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
|
||||||
import org.pgpainless.implementation.ImplementationFactory;
|
|
||||||
import org.pgpainless.util.Passphrase;
|
|
||||||
|
|
||||||
public class CallbackBasedKeyringProtector implements SecretKeyRingProtector2 {
|
|
||||||
|
|
||||||
private final Map<Long, Passphrase> passphraseCache = new ConcurrentHashMap<>();
|
|
||||||
private final Callback callback;
|
|
||||||
|
|
||||||
public CallbackBasedKeyringProtector(Callback callback) {
|
|
||||||
if (callback == null) {
|
|
||||||
throw new NullPointerException("Callback MUST NOT be null.");
|
|
||||||
}
|
|
||||||
this.callback = callback;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PBESecretKeyDecryptor getDecryptor(PGPSecretKey key) throws PGPException {
|
|
||||||
Passphrase passphrase = lookupPassphraseInCache(key);
|
|
||||||
if (passphrase == null) {
|
|
||||||
passphrase = callback.getPassphraseFor(key);
|
|
||||||
passphraseCache.put(key.getKeyID(), passphrase);
|
|
||||||
}
|
|
||||||
return ImplementationFactory.getInstance().getPBESecretKeyDecryptor(passphrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PBESecretKeyEncryptor getEncryptor(PGPSecretKey key) throws PGPException {
|
|
||||||
Passphrase passphrase = lookupPassphraseInCache(key);
|
|
||||||
if (passphrase == null) {
|
|
||||||
passphrase = callback.getPassphraseFor(key);
|
|
||||||
passphraseCache.put(key.getKeyID(), passphrase);
|
|
||||||
}
|
|
||||||
return ImplementationFactory.getInstance().getPBESecretKeyEncryptor(key, passphrase);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Passphrase lookupPassphraseInCache(PGPSecretKey key) {
|
|
||||||
return passphraseCache.get(key.getKeyID());
|
|
||||||
}
|
|
||||||
|
|
||||||
public interface Callback {
|
|
||||||
Passphrase getPassphraseFor(PGPSecretKey secretKey);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -50,6 +50,13 @@ public interface SecretKeyRingProtector {
|
||||||
*/
|
*/
|
||||||
@Nullable PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException;
|
@Nullable PBESecretKeyEncryptor getEncryptor(Long keyId) throws PGPException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the provided passphrase to lock/unlock all subkeys in the provided key ring.
|
||||||
|
*
|
||||||
|
* @param passphrase passphrase
|
||||||
|
* @param keys key ring
|
||||||
|
* @return protector
|
||||||
|
*/
|
||||||
static SecretKeyRingProtector unlockAllKeysWith(Passphrase passphrase, PGPSecretKeyRing keys) {
|
static SecretKeyRingProtector unlockAllKeysWith(Passphrase passphrase, PGPSecretKeyRing keys) {
|
||||||
Map<Long, Passphrase> map = new ConcurrentHashMap<>();
|
Map<Long, Passphrase> map = new ConcurrentHashMap<>();
|
||||||
for (PGPSecretKey secretKey : keys) {
|
for (PGPSecretKey secretKey : keys) {
|
||||||
|
@ -58,14 +65,32 @@ public interface SecretKeyRingProtector {
|
||||||
return fromPassphraseMap(map);
|
return fromPassphraseMap(map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use the provided passphrase to lock/unlock only the provided (sub-)key.
|
||||||
|
*
|
||||||
|
* @param passphrase passphrase
|
||||||
|
* @param key key to lock/unlock
|
||||||
|
* @return protector
|
||||||
|
*/
|
||||||
static SecretKeyRingProtector unlockSingleKeyWith(Passphrase passphrase, PGPSecretKey key) {
|
static SecretKeyRingProtector unlockSingleKeyWith(Passphrase passphrase, PGPSecretKey key) {
|
||||||
return PasswordBasedSecretKeyRingProtector.forKey(key, passphrase);
|
return PasswordBasedSecretKeyRingProtector.forKey(key, passphrase);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Protector for unprotected keys.
|
||||||
|
*
|
||||||
|
* @return protector
|
||||||
|
*/
|
||||||
static SecretKeyRingProtector unprotectedKeys() {
|
static SecretKeyRingProtector unprotectedKeys() {
|
||||||
return new UnprotectedKeysProtector();
|
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(Map<Long, Passphrase> passphraseMap) {
|
static SecretKeyRingProtector fromPassphraseMap(Map<Long, Passphrase> passphraseMap) {
|
||||||
return new PassphraseMapKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null);
|
return new PassphraseMapKeyRingProtector(passphraseMap, KeyRingProtectionSettings.secureDefaultSettings(), null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 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.PGPSecretKey;
|
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
|
||||||
|
|
||||||
public interface SecretKeyRingProtector2 {
|
|
||||||
|
|
||||||
PBESecretKeyDecryptor getDecryptor(PGPSecretKey key) throws PGPException;
|
|
||||||
|
|
||||||
PBESecretKeyEncryptor getEncryptor(PGPSecretKey key) throws PGPException;
|
|
||||||
}
|
|
|
@ -1,34 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2020 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.PGPSecretKey;
|
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
|
|
||||||
import org.bouncycastle.openpgp.operator.PBESecretKeyEncryptor;
|
|
||||||
|
|
||||||
public interface SecretKeyRingProtectorAdapter extends SecretKeyRingProtector, SecretKeyRingProtector2 {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default PBESecretKeyDecryptor getDecryptor(PGPSecretKey key) throws PGPException {
|
|
||||||
return getDecryptor(key.getKeyID());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
default PBESecretKeyEncryptor getEncryptor(PGPSecretKey key) throws PGPException {
|
|
||||||
return getEncryptor(key.getKeyID());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -34,15 +34,6 @@ public class UnlockSecretKey {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, SecretKeyRingProtector2 protector) throws WrongPassphraseException {
|
|
||||||
try {
|
|
||||||
PBESecretKeyDecryptor decryptor = protector.getDecryptor(secretKey);
|
|
||||||
return secretKey.extractPrivateKey(decryptor);
|
|
||||||
} catch (PGPException e) {
|
|
||||||
throw new WrongPassphraseException(secretKey.getKeyID(), e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, PBESecretKeyDecryptor decryptor) throws WrongPassphraseException {
|
public static PGPPrivateKey unlockSecretKey(PGPSecretKey secretKey, PBESecretKeyDecryptor decryptor) throws WrongPassphraseException {
|
||||||
try {
|
try {
|
||||||
return secretKey.extractPrivateKey(decryptor);
|
return secretKey.extractPrivateKey(decryptor);
|
||||||
|
|
|
@ -25,6 +25,15 @@ import org.pgpainless.util.Passphrase;
|
||||||
*/
|
*/
|
||||||
public interface SecretKeyPassphraseProvider {
|
public interface SecretKeyPassphraseProvider {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return a passphrase for the given secret key.
|
||||||
|
* If no record is found, return null.
|
||||||
|
* Note: In case of an unprotected secret key, this method must may not return null, but a {@link Passphrase} with
|
||||||
|
* a content of null.
|
||||||
|
*
|
||||||
|
* @param secretKey secret key
|
||||||
|
* @return passphrase or null, if no passphrase record is found.
|
||||||
|
*/
|
||||||
@Nullable default Passphrase getPassphraseFor(PGPSecretKey secretKey) {
|
@Nullable default Passphrase getPassphraseFor(PGPSecretKey secretKey) {
|
||||||
return getPassphraseFor(secretKey.getKeyID());
|
return getPassphraseFor(secretKey.getKeyID());
|
||||||
}
|
}
|
||||||
|
|
|
@ -126,22 +126,4 @@ public class SecretKeyRingProtectorTest {
|
||||||
assertEquals(Passphrase.emptyPassphrase(), protector.getPassphraseFor(1L));
|
assertEquals(Passphrase.emptyPassphrase(), protector.getPassphraseFor(1L));
|
||||||
assertEquals(Passphrase.fromPassword("missingP455w0rd"), protector.getPassphraseFor(3L));
|
assertEquals(Passphrase.fromPassword("missingP455w0rd"), protector.getPassphraseFor(3L));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
|
||||||
@MethodSource("org.pgpainless.util.TestUtil#provideImplementationFactories")
|
|
||||||
public void testCallbackBasedKeyRingProtector(ImplementationFactory implementationFactory) throws IOException, PGPException {
|
|
||||||
ImplementationFactory.setFactoryImplementation(implementationFactory);
|
|
||||||
SecretKeyRingProtector2 protector = new CallbackBasedKeyringProtector(new CallbackBasedKeyringProtector.Callback() {
|
|
||||||
@Override
|
|
||||||
public Passphrase getPassphraseFor(PGPSecretKey secretKey) {
|
|
||||||
return TestKeys.CRYPTIE_PASSPHRASE;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
PGPSecretKeyRing secretKeys = TestKeys.getEmilSecretKeyRing();
|
|
||||||
for (PGPSecretKey secretKey : secretKeys) {
|
|
||||||
UnlockSecretKey.unlockSecretKey(secretKey, protector);
|
|
||||||
assertNotNull(protector.getEncryptor(secretKey));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue