From 20634f88c36eed83529bb7e54d6a96c1424dabca Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Mon, 10 Oct 2022 18:01:30 +0200 Subject: [PATCH] Change HardwareSecurity DecryptionCallback to emit key-id --- .../ConsumerOptions.java | 15 +++----- .../CustomPublicKeyDataDecryptorFactory.java | 13 +++++++ .../DecryptionStreamFactory.java | 34 +++++++++---------- .../HardwareSecurity.java | 17 +++++++--- ...stomPublicKeyDataDecryptorFactoryTest.java | 8 ++--- 5 files changed, 51 insertions(+), 36 deletions(-) create mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java index 8f0576ee..dba9fca7 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/ConsumerOptions.java @@ -49,7 +49,7 @@ public class ConsumerOptions { // Session key for decryption without passphrase/key private SessionKey sessionKey = null; - private Map, PublicKeyDataDecryptorFactory> customPublicKeyDataDecryptorFactories = new HashMap<>(); + private Map customPublicKeyDataDecryptorFactories = new HashMap<>(); private final Map decryptionKeys = new HashMap<>(); private final Set decryptionPassphrases = new HashSet<>(); @@ -245,20 +245,15 @@ public class ConsumerOptions { * hardware-backed secret keys. * (See e.g. {@link org.pgpainless.decryption_verification.HardwareSecurity.HardwareDataDecryptorFactory}). * - * The set of key-ids determines, whether the decryptor factory shall be consulted to decrypt a given session key. - * See for example {@link HardwareSecurity#getIdsOfHardwareBackedKeys(PGPSecretKeyRing)}. - * - * @param keyIds set of key-ids for which the factory shall be consulted - * @param decryptorFactory decryptor factory + * @param factory decryptor factory * @return options */ - public ConsumerOptions addCustomDecryptorFactory( - @Nonnull Set keyIds, @Nonnull PublicKeyDataDecryptorFactory decryptorFactory) { - this.customPublicKeyDataDecryptorFactories.put(keyIds, decryptorFactory); + public ConsumerOptions addCustomDecryptorFactory(@Nonnull CustomPublicKeyDataDecryptorFactory factory) { + this.customPublicKeyDataDecryptorFactories.put(factory.getKeyId(), factory); return this; } - Map, PublicKeyDataDecryptorFactory> getCustomDecryptorFactories() { + Map getCustomDecryptorFactories() { return new HashMap<>(customPublicKeyDataDecryptorFactories); } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.java new file mode 100644 index 00000000..43e8e723 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactory.java @@ -0,0 +1,13 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification; + +import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; + +public interface CustomPublicKeyDataDecryptorFactory extends PublicKeyDataDecryptorFactory { + + long getKeyId(); + +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java index ba837940..23e3e47d 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/DecryptionStreamFactory.java @@ -409,30 +409,28 @@ public final class DecryptionStreamFactory { } // Try custom PublicKeyDataDecryptorFactories (e.g. hardware-backed). - Map, PublicKeyDataDecryptorFactory> customFactories = options.getCustomDecryptorFactories(); + Map customFactories = options.getCustomDecryptorFactories(); for (PGPPublicKeyEncryptedData publicKeyEncryptedData : publicKeyProtected) { Long keyId = publicKeyEncryptedData.getKeyID(); - for (Set keyIds : customFactories.keySet()) { - if (!keyIds.contains(keyId)) { - continue; - } + if (!customFactories.containsKey(keyId)) { + continue; + } - PublicKeyDataDecryptorFactory decryptorFactory = customFactories.get(keyIds); - try { - InputStream decryptedDataStream = publicKeyEncryptedData.getDataStream(decryptorFactory); - PGPSessionKey pgpSessionKey = publicKeyEncryptedData.getSessionKey(decryptorFactory); - SessionKey sessionKey = new SessionKey(pgpSessionKey); - resultBuilder.setSessionKey(sessionKey); + PublicKeyDataDecryptorFactory decryptorFactory = customFactories.get(keyId); + try { + InputStream decryptedDataStream = publicKeyEncryptedData.getDataStream(decryptorFactory); + PGPSessionKey pgpSessionKey = publicKeyEncryptedData.getSessionKey(decryptorFactory); + SessionKey sessionKey = new SessionKey(pgpSessionKey); + resultBuilder.setSessionKey(sessionKey); - throwIfAlgorithmIsRejected(sessionKey.getAlgorithm()); + throwIfAlgorithmIsRejected(sessionKey.getAlgorithm()); - integrityProtectedEncryptedInputStream = - new IntegrityProtectedInputStream(decryptedDataStream, publicKeyEncryptedData, options); + integrityProtectedEncryptedInputStream = + new IntegrityProtectedInputStream(decryptedDataStream, publicKeyEncryptedData, options); - return integrityProtectedEncryptedInputStream; - } catch (PGPException e) { - LOGGER.debug("Decryption with custom PublicKeyDataDecryptorFactory failed", e); - } + return integrityProtectedEncryptedInputStream; + } catch (PGPException e) { + LOGGER.debug("Decryption with custom PublicKeyDataDecryptorFactory failed", e); } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/HardwareSecurity.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/HardwareSecurity.java index cc8cf598..d5bf60db 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/HardwareSecurity.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/HardwareSecurity.java @@ -28,13 +28,14 @@ public class HardwareSecurity { * * If decryption fails for some reason, a subclass of the {@link HardwareSecurityException} is thrown. * + * @param keyId id of the key * @param keyAlgorithm algorithm * @param sessionKeyData encrypted session key * * @return decrypted session key * @throws HardwareSecurityException exception */ - byte[] decryptSessionKey(int keyAlgorithm, byte[] sessionKeyData) + byte[] decryptSessionKey(long keyId, int keyAlgorithm, byte[] sessionKeyData) throws HardwareSecurityException; } @@ -67,20 +68,22 @@ public class HardwareSecurity { * to a {@link DecryptionCallback}. * Users can provide such a callback to delegate decryption of messages to hardware security SDKs. */ - public static class HardwareDataDecryptorFactory implements PublicKeyDataDecryptorFactory { + public static class HardwareDataDecryptorFactory implements CustomPublicKeyDataDecryptorFactory { private final DecryptionCallback callback; // luckily we can instantiate the BcPublicKeyDataDecryptorFactory with null as argument. private final PublicKeyDataDecryptorFactory factory = new BcPublicKeyDataDecryptorFactory(null); + private long keyId; /** * Create a new {@link HardwareDataDecryptorFactory}. * * @param callback decryption callback */ - public HardwareDataDecryptorFactory(DecryptionCallback callback) { + public HardwareDataDecryptorFactory(long keyId, DecryptionCallback callback) { this.callback = callback; + this.keyId = keyId; } @Override @@ -88,7 +91,7 @@ public class HardwareSecurity { throws PGPException { try { // delegate decryption to the callback - return callback.decryptSessionKey(keyAlgorithm, secKeyData[0]); + return callback.decryptSessionKey(keyId, keyAlgorithm, secKeyData[0]); } catch (HardwareSecurityException e) { throw new PGPException("Hardware-backed decryption failed.", e); } @@ -99,10 +102,16 @@ public class HardwareSecurity { throws PGPException { return factory.createDataDecryptor(withIntegrityPacket, encAlgorithm, key); } + + @Override + public long getKeyId() { + return keyId; + } } public static class HardwareSecurityException extends Exception { } + } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactoryTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactoryTest.java index f507e02e..4ea894e5 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactoryTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/CustomPublicKeyDataDecryptorFactoryTest.java @@ -29,7 +29,6 @@ import java.io.IOException; import java.nio.charset.StandardCharsets; import java.security.InvalidAlgorithmParameterException; import java.security.NoSuchAlgorithmException; -import java.util.Collections; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -55,7 +54,7 @@ public class CustomPublicKeyDataDecryptorFactoryTest { HardwareSecurity.DecryptionCallback hardwareDecryptionCallback = new HardwareSecurity.DecryptionCallback() { @Override - public byte[] decryptSessionKey(int keyAlgorithm, byte[] sessionKeyData) + public byte[] decryptSessionKey(long keyId, int keyAlgorithm, byte[] sessionKeyData) throws HardwareSecurity.HardwareSecurityException { // Emulate hardware decryption. try { @@ -74,8 +73,9 @@ public class CustomPublicKeyDataDecryptorFactoryTest { .onInputStream(new ByteArrayInputStream(ciphertextOut.toByteArray())) .withOptions(ConsumerOptions.get() .addCustomDecryptorFactory( - Collections.singleton(encryptionKey.getKeyID()), - new HardwareSecurity.HardwareDataDecryptorFactory(hardwareDecryptionCallback))); + new HardwareSecurity.HardwareDataDecryptorFactory( + encryptionKey.getKeyID(), + hardwareDecryptionCallback))); ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream(); Streams.pipeAll(decryptionStream, decryptedOut);