1
0
Fork 0
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:
Paul Schaub 2021-05-14 18:37:47 +02:00
parent 8313895f26
commit 95121e2a55
Signed by: vanitasvitae
GPG key ID: 62BEE9264BF17311
7 changed files with 34 additions and 156 deletions

View file

@ -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);
}
}

View file

@ -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);
} }

View file

@ -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;
}

View file

@ -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());
}
}

View file

@ -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);

View file

@ -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());
} }

View file

@ -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));
}
}
} }