1
0
Fork 0
mirror of https://github.com/pgpainless/pgpainless.git synced 2025-09-10 18:59:39 +02:00

Implement exploratory support for custom decryption factories

This may enable decryption of messages with hardware-backed keys
This commit is contained in:
Paul Schaub 2022-09-23 16:17:42 +02:00
parent d39d062a0d
commit 529c64cf43
5 changed files with 221 additions and 47 deletions

View file

@ -0,0 +1,86 @@
// SPDX-FileCopyrightText: 2022 Paul Schaub <vanitasvitae@fsfe.org>
//
// SPDX-License-Identifier: Apache-2.0
package org.pgpainless.decryption_verification;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRing;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.algorithm.EncryptionPurpose;
import org.pgpainless.encryption_signing.EncryptionOptions;
import org.pgpainless.encryption_signing.EncryptionStream;
import org.pgpainless.encryption_signing.ProducerOptions;
import org.pgpainless.key.info.KeyRingInfo;
import org.pgpainless.key.protection.UnlockSecretKey;
import org.pgpainless.util.Passphrase;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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;
public class CustomPublicKeyDataDecryptorFactoryTest {
@Test
public void testDecryptionWithEmulatedHardwareDecryptionCallback()
throws PGPException, IOException, InvalidAlgorithmParameterException, NoSuchAlgorithmException {
PGPSecretKeyRing secretKey = PGPainless.generateKeyRing().modernKeyRing("Alice");
PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKey);
KeyRingInfo info = PGPainless.inspectKeyRing(secretKey);
PGPPublicKey encryptionKey = info.getEncryptionSubkeys(EncryptionPurpose.ANY).get(0);
// Encrypt a test message
String plaintext = "Hello, World!\n";
ByteArrayOutputStream ciphertextOut = new ByteArrayOutputStream();
EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
.onOutputStream(ciphertextOut)
.withOptions(ProducerOptions.encrypt(EncryptionOptions.get()
.addRecipient(cert)));
encryptionStream.write(plaintext.getBytes(StandardCharsets.UTF_8));
encryptionStream.close();
HardwareSecurity.DecryptionCallback hardwareDecryptionCallback = new HardwareSecurity.DecryptionCallback() {
@Override
public byte[] decryptSessionKey(int keyAlgorithm, byte[] sessionKeyData)
throws HardwareSecurity.HardwareSecurityException {
// Emulate hardware decryption.
try {
PGPSecretKey decryptionKey = secretKey.getSecretKey(encryptionKey.getKeyID());
PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(decryptionKey, Passphrase.emptyPassphrase());
PublicKeyDataDecryptorFactory internal = new BcPublicKeyDataDecryptorFactory(privateKey);
return internal.recoverSessionData(keyAlgorithm, new byte[][] {sessionKeyData});
} catch (PGPException e) {
throw new HardwareSecurity.HardwareSecurityException();
}
}
};
// Decrypt
DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
.onInputStream(new ByteArrayInputStream(ciphertextOut.toByteArray()))
.withOptions(ConsumerOptions.get()
.addCustomDecryptorFactory(
Collections.singleton(encryptionKey.getKeyID()),
new HardwareSecurity.HardwareDataDecryptorFactory(hardwareDecryptionCallback)));
ByteArrayOutputStream decryptedOut = new ByteArrayOutputStream();
Streams.pipeAll(decryptionStream, decryptedOut);
decryptionStream.close();
assertEquals(plaintext, decryptedOut.toString());
}
}

View file

@ -1,39 +0,0 @@
package org.pgpainless.decryption_verification;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.junit.jupiter.api.Test;
import org.pgpainless.PGPainless;
import org.pgpainless.util.SessionKey;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class HardwareSecurityCallbackTest {
@Test
public void test() throws PGPException, IOException {
PGPainless.decryptAndOrVerify()
.onInputStream(new ByteArrayInputStream(new byte[0]))
.withOptions(ConsumerOptions.get()
.setHardwareDecryptionCallback(new HardwareSecurity.DecryptionCallback() {
@Override
public SessionKey decryptSessionKey(PGPPublicKeyEncryptedData pkesk) throws HardwareSecurity.HardwareSecurityException {
/*
pkesk.getSessionKey(new PublicKeyDataDecryptorFactory() {
@Override
public byte[] recoverSessionData(int keyAlgorithm, byte[][] secKeyData) throws PGPException {
return new byte[0];
}
@Override
public PGPDataDecryptor createDataDecryptor(boolean withIntegrityPacket, int encAlgorithm, byte[] key) throws PGPException {
return null;
}
});
*/
return null;
}
}));
}
}