diff --git a/pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.java b/pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.java new file mode 100644 index 00000000..63d14a31 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/algorithm/OpenPgpPacket.java @@ -0,0 +1,71 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm; + +import org.bouncycastle.bcpg.PacketTags; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.NoSuchElementException; + +public enum OpenPgpPacket { + PKESK(PacketTags.PUBLIC_KEY_ENC_SESSION), + SIG(PacketTags.SIGNATURE), + SKESK(PacketTags.SYMMETRIC_KEY_ENC_SESSION), + OPS(PacketTags.ONE_PASS_SIGNATURE), + SK(PacketTags.SECRET_KEY), + PK(PacketTags.PUBLIC_KEY), + SSK(PacketTags.SECRET_SUBKEY), + COMP(PacketTags.COMPRESSED_DATA), + SED(PacketTags.SYMMETRIC_KEY_ENC), + MARKER(PacketTags.MARKER), + LIT(PacketTags.LITERAL_DATA), + TRUST(PacketTags.TRUST), + UID(PacketTags.USER_ID), + PSK(PacketTags.PUBLIC_SUBKEY), + UATTR(PacketTags.USER_ATTRIBUTE), + SEIPD(PacketTags.SYM_ENC_INTEGRITY_PRO), + MOD(PacketTags.MOD_DETECTION_CODE), + + EXP_1(PacketTags.EXPERIMENTAL_1), + EXP_2(PacketTags.EXPERIMENTAL_2), + EXP_3(PacketTags.EXPERIMENTAL_3), + EXP_4(PacketTags.EXPERIMENTAL_4), + ; + + static final Map MAP = new HashMap<>(); + + static { + for (OpenPgpPacket p : OpenPgpPacket.values()) { + MAP.put(p.getTag(), p); + } + } + + final int tag; + + @Nullable + public static OpenPgpPacket fromTag(int tag) { + return MAP.get(tag); + } + + @Nonnull + public static OpenPgpPacket requireFromTag(int tag) { + OpenPgpPacket p = fromTag(tag); + if (p == null) { + throw new NoSuchElementException("No OpenPGP packet known for tag " + tag); + } + return p; + } + + OpenPgpPacket(int tag) { + this.tag = tag; + } + + int getTag() { + return tag; + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/PGPDecryptionStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/PGPDecryptionStream.java new file mode 100644 index 00000000..c3053555 --- /dev/null +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/PGPDecryptionStream.java @@ -0,0 +1,238 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.decryption_verification; + +import org.bouncycastle.bcpg.BCPGInputStream; +import org.bouncycastle.bcpg.BCPGOutputStream; +import org.bouncycastle.bcpg.ModDetectionCodePacket; +import org.bouncycastle.bcpg.OnePassSignaturePacket; +import org.bouncycastle.bcpg.Packet; +import org.bouncycastle.bcpg.PacketTags; +import org.bouncycastle.bcpg.PublicKeyEncSessionPacket; +import org.bouncycastle.bcpg.SignaturePacket; +import org.bouncycastle.bcpg.SymmetricEncDataPacket; +import org.bouncycastle.bcpg.SymmetricEncIntegrityPacket; +import org.bouncycastle.bcpg.SymmetricKeyEncSessionPacket; +import org.bouncycastle.bcpg.TrustPacket; +import org.bouncycastle.bcpg.UserAttributePacket; +import org.bouncycastle.bcpg.UserIDPacket; +import org.bouncycastle.openpgp.PGPCompressedData; +import org.bouncycastle.openpgp.PGPEncryptedDataList; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPOnePassSignatureList; +import org.bouncycastle.openpgp.PGPSignatureList; +import org.pgpainless.algorithm.OpenPgpPacket; +import org.pgpainless.exception.MalformedOpenPgpMessageException; +import org.pgpainless.implementation.ImplementationFactory; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.NoSuchElementException; +import java.util.Stack; + +public class PGPDecryptionStream extends InputStream { + + PushdownAutomaton automaton = new PushdownAutomaton(); + // nested streams, outermost at the bottom of the stack + Stack packetLayers = new Stack<>(); + + public PGPDecryptionStream(InputStream inputStream) throws IOException, PGPException { + try { + packetLayers.push(Layer.initial(inputStream)); + walkLayer(); + } catch (MalformedOpenPgpMessageException e) { + throw e.toRuntimeException(); + } + } + + private void walkLayer() throws PGPException, IOException { + if (packetLayers.isEmpty()) { + return; + } + + Layer layer = packetLayers.peek(); + BCPGInputStream inputStream = (BCPGInputStream) layer.inputStream; + + loop: while (true) { + if (inputStream.nextPacketTag() == -1) { + popLayer(); + break loop; + } + OpenPgpPacket tag = nextTagOrThrow(inputStream); + switch (tag) { + + case PKESK: + PublicKeyEncSessionPacket pkeskPacket = (PublicKeyEncSessionPacket) inputStream.readPacket(); + PGPEncryptedDataList encList = null; + break; + case SIG: + automaton.next(PushdownAutomaton.InputAlphabet.Signatures); + + ByteArrayOutputStream buf = new ByteArrayOutputStream(); + BCPGOutputStream bcpgOut = new BCPGOutputStream(buf); + while (inputStream.nextPacketTag() == PacketTags.SIGNATURE || inputStream.nextPacketTag() == PacketTags.MARKER) { + Packet packet = inputStream.readPacket(); + if (packet instanceof SignaturePacket) { + SignaturePacket sig = (SignaturePacket) packet; + sig.encode(bcpgOut); + } + } + PGPSignatureList signatures = (PGPSignatureList) ImplementationFactory.getInstance() + .getPGPObjectFactory(buf.toByteArray()).nextObject(); + break; + case SKESK: + SymmetricKeyEncSessionPacket skeskPacket = (SymmetricKeyEncSessionPacket) inputStream.readPacket(); + + break; + case OPS: + automaton.next(PushdownAutomaton.InputAlphabet.OnePassSignatures); + buf = new ByteArrayOutputStream(); + bcpgOut = new BCPGOutputStream(buf); + while (inputStream.nextPacketTag() == PacketTags.ONE_PASS_SIGNATURE || inputStream.nextPacketTag() == PacketTags.MARKER) { + Packet packet = inputStream.readPacket(); + if (packet instanceof OnePassSignaturePacket) { + OnePassSignaturePacket sig = (OnePassSignaturePacket) packet; + sig.encode(bcpgOut); + } + } + PGPOnePassSignatureList onePassSignatures = (PGPOnePassSignatureList) ImplementationFactory.getInstance() + .getPGPObjectFactory(buf.toByteArray()).nextObject(); + break; + case SK: + break; + case PK: + break; + case SSK: + break; + case COMP: + automaton.next(PushdownAutomaton.InputAlphabet.CompressedData); + PGPCompressedData compressedData = new PGPCompressedData(inputStream); + inputStream = new BCPGInputStream(compressedData.getDataStream()); + packetLayers.push(Layer.CompressedData(inputStream)); + break; + case SED: + automaton.next(PushdownAutomaton.InputAlphabet.EncryptedData); + SymmetricEncDataPacket symmetricEncDataPacket = (SymmetricEncDataPacket) inputStream.readPacket(); + break; + case MARKER: + inputStream.readPacket(); // discard + break; + case LIT: + automaton.next(PushdownAutomaton.InputAlphabet.LiteralData); + PGPLiteralData literalData = new PGPLiteralData(inputStream); + packetLayers.push(Layer.LiteralMessage(literalData.getDataStream())); + break loop; + case TRUST: + TrustPacket trustPacket = (TrustPacket) inputStream.readPacket(); + break; + case UID: + UserIDPacket userIDPacket = (UserIDPacket) inputStream.readPacket(); + break; + case PSK: + break; + case UATTR: + UserAttributePacket userAttributePacket = (UserAttributePacket) inputStream.readPacket(); + break; + case SEIPD: + automaton.next(PushdownAutomaton.InputAlphabet.EncryptedData); + SymmetricEncIntegrityPacket symmetricEncIntegrityPacket = (SymmetricEncIntegrityPacket) inputStream.readPacket(); + break; + case MOD: + ModDetectionCodePacket modDetectionCodePacket = (ModDetectionCodePacket) inputStream.readPacket(); + break; + case EXP_1: + break; + case EXP_2: + break; + case EXP_3: + break; + case EXP_4: + break; + } + } + } + + private OpenPgpPacket nextTagOrThrow(BCPGInputStream inputStream) + throws IOException, InvalidOpenPgpPacketException { + try { + return OpenPgpPacket.requireFromTag(inputStream.nextPacketTag()); + } catch (NoSuchElementException e) { + throw new InvalidOpenPgpPacketException(e.getMessage()); + } + } + + private void popLayer() throws MalformedOpenPgpMessageException { + if (packetLayers.pop().isNested) + automaton.next(PushdownAutomaton.InputAlphabet.EndOfSequence); + } + + @Override + public int read() throws IOException { + if (packetLayers.isEmpty()) { + try { + automaton.assertValid(); + } catch (MalformedOpenPgpMessageException e) { + throw e.toRuntimeException(); + } + return -1; + } + + int r = -1; + try { + r = packetLayers.peek().inputStream.read(); + } catch (IOException e) { + } + if (r == -1) { + try { + popLayer(); + walkLayer(); + } catch (MalformedOpenPgpMessageException e) { + throw e.toRuntimeException(); + } + catch (PGPException e) { + throw new RuntimeException(e); + } + return read(); + } + return r; + } + + public static class InvalidOpenPgpPacketException extends PGPException { + + public InvalidOpenPgpPacketException(String message) { + super(message); + } + } + + private static class Layer { + InputStream inputStream; + boolean isNested; + + private Layer(InputStream inputStream, boolean isNested) { + this.inputStream = inputStream; + this.isNested = isNested; + } + + static Layer initial(InputStream inputStream) { + BCPGInputStream bcpgIn; + if (inputStream instanceof BCPGInputStream) { + bcpgIn = (BCPGInputStream) inputStream; + } else { + bcpgIn = new BCPGInputStream(inputStream); + } + return new Layer(bcpgIn, true); + } + + static Layer LiteralMessage(InputStream inputStream) { + return new Layer(inputStream, false); + } + + static Layer CompressedData(InputStream inputStream) { + return new Layer(inputStream, true); + } + } +} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/PushdownAutomaton.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/PushdownAutomaton.java index 6930de4b..86075280 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/PushdownAutomaton.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/PushdownAutomaton.java @@ -342,6 +342,12 @@ public class PushdownAutomaton { return getState() == State.Valid && stack.isEmpty(); } + public void assertValid() throws MalformedOpenPgpMessageException { + if (!isValid()) { + throw new MalformedOpenPgpMessageException("Pushdown Automaton is not in an acceptable state: " + toString()); + } + } + /** * Pop an item from the stack. * diff --git a/pgpainless-core/src/main/java/org/pgpainless/exception/MalformedOpenPgpMessageException.java b/pgpainless-core/src/main/java/org/pgpainless/exception/MalformedOpenPgpMessageException.java index 07fed365..9ce2284d 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/exception/MalformedOpenPgpMessageException.java +++ b/pgpainless-core/src/main/java/org/pgpainless/exception/MalformedOpenPgpMessageException.java @@ -28,4 +28,15 @@ public class MalformedOpenPgpMessageException extends PGPException { PushdownAutomaton.StackAlphabet stackItem) { this("Invalid input: There is no legal transition from state '" + state + "' for input '" + input + "' when '" + stackItem + "' is on top of the stack."); } + + public RTE toRuntimeException() { + return new RTE(this); + } + + public static class RTE extends RuntimeException { + + public RTE(MalformedOpenPgpMessageException e) { + super(e); + } + } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/algorithm/OpenPgpPacketTest.java b/pgpainless-core/src/test/java/org/pgpainless/algorithm/OpenPgpPacketTest.java new file mode 100644 index 00000000..88146605 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/algorithm/OpenPgpPacketTest.java @@ -0,0 +1,38 @@ +// SPDX-FileCopyrightText: 2022 Paul Schaub +// +// SPDX-License-Identifier: Apache-2.0 + +package org.pgpainless.algorithm; + +import org.bouncycastle.bcpg.PacketTags; +import org.junit.jupiter.api.Test; + +import java.util.NoSuchElementException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class OpenPgpPacketTest { + + @Test + public void testFromInvalidTag() { + int tag = PacketTags.RESERVED; + assertNull(OpenPgpPacket.fromTag(tag)); + assertThrows(NoSuchElementException.class, + () -> OpenPgpPacket.requireFromTag(tag)); + } + + @Test + public void testFromExistingTags() { + for (OpenPgpPacket p : OpenPgpPacket.values()) { + assertEquals(p, OpenPgpPacket.fromTag(p.getTag())); + assertEquals(p, OpenPgpPacket.requireFromTag(p.getTag())); + } + } + + @Test + public void testPKESKTagMatches() { + assertEquals(PacketTags.PUBLIC_KEY_ENC_SESSION, OpenPgpPacket.PKESK.getTag()); + } +} diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PGPDecryptionStreamTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PGPDecryptionStreamTest.java new file mode 100644 index 00000000..bb2742b5 --- /dev/null +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PGPDecryptionStreamTest.java @@ -0,0 +1,290 @@ +package org.pgpainless.decryption_verification; + +import org.bouncycastle.bcpg.ArmoredInputStream; +import org.bouncycastle.bcpg.ArmoredOutputStream; +import org.bouncycastle.bcpg.CompressionAlgorithmTags; +import org.bouncycastle.openpgp.PGPCompressedDataGenerator; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPLiteralDataGenerator; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.util.io.Streams; +import org.junit.jupiter.api.Test; +import org.pgpainless.PGPainless; +import org.pgpainless.encryption_signing.EncryptionResult; +import org.pgpainless.encryption_signing.EncryptionStream; +import org.pgpainless.encryption_signing.ProducerOptions; +import org.pgpainless.encryption_signing.SigningOptions; +import org.pgpainless.exception.MalformedOpenPgpMessageException; +import org.pgpainless.key.protection.SecretKeyRingProtector; +import org.pgpainless.util.ArmoredInputStreamFactory; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.NoSuchAlgorithmException; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class PGPDecryptionStreamTest { + + private static final String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Version: PGPainless\n" + + "Comment: DA05 848F 37D4 68E6 F982 C889 7A70 1FC6 904D 3F4C\n" + + "Comment: Alice \n" + + "\n" + + "lFgEYxzSCBYJKwYBBAHaRw8BAQdAeJU8m4GOJb1eQgv/ryilFHRfNLTYFMNqL6zj\n" + + "r0vF7dsAAP42rAtngpJ6dZxoZlJX0Je65zk1VMPeTrXaWfPS2HSKBRGptBxBbGlj\n" + + "ZSA8YWxpY2VAcGdwYWlubGVzcy5vcmc+iI8EExYKAEEFAmMc0ggJEHpwH8aQTT9M\n" + + "FiEE2gWEjzfUaOb5gsiJenAfxpBNP0wCngECmwEFFgIDAQAECwkIBwUVCgkICwKZ\n" + + "AQAApZEBALUXHtvswPZG28YO+16Men6/fpk+scvqpNMnD4ty3IkAAPwK6TuXjNnZ\n" + + "0XuWdnilvLMV23Ai1d5g6em+lwLK5M2SApxdBGMc0ggSCisGAQQBl1UBBQEBB0D8\n" + + "mNUVX8y2MXFaSeFYqOTPFnGT7dgNVdn6yc0UtkkHOgMBCAcAAP9y9OtP4SX9voPb\n" + + "ID2u9PkJKgo4hTB8NK5LouGppdRtEBGriHUEGBYKAB0FAmMc0ggCngECmwwFFgID\n" + + "AQAECwkIBwUVCgkICwAKCRB6cB/GkE0/TAywAQDpZRJS/joFH4+xcwheqWfI7ay/\n" + + "WfojUoGQMYGnUjsgYwEAkceRUsgkqI0SVgYvuglfaQpZ9k2ns1mZGVLkXvu/yQyc\n" + + "WARjHNIIFgkrBgEEAdpHDwEBB0BGN9BybSOrj8B6gim1SjbB/IiqAshlzMDunVkQ\n" + + "X23npQABAJqvjOOY7qhBuTusC5/Q5+25iLrhMn4TI+LXlJHMVNOaE0OI1QQYFgoA\n" + + "fQUCYxzSCAKeAQKbAgUWAgMBAAQLCQgHBRUKCQgLXyAEGRYKAAYFAmMc0ggACgkQ\n" + + "KALh4BJQXl6yTQD/dh0N5228Uwtu7XHy6dmpMRX62cac5tXQ9WaDzpy8STgBAMdn\n" + + "Mq948UOYEhdk/ZY2/hwux/4t+FHvqrXW8ziBe4cLAAoJEHpwH8aQTT9M1hQA/3Ms\n" + + "P3kzoed3VsWu1ZMr7dKEngbc6SoJ2XPayzN0QYJaAQCIY5NcT9mZF97HWV3Vgeum\n" + + "00sWMHXfkW3+nl5OpUZaDA==\n" + + "=THgv\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + + private static final String PLAINTEXT = "Hello, World!\n"; + + private static final String LIT = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "yxRiAAAAAABIZWxsbywgV29ybGQhCg==\n" + + "=WGju\n" + + "-----END PGP MESSAGE-----"; + + private static final String LIT_LIT = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: BCPG v1.71\n" + + "\n" + + "yxRiAAAAAABIZWxsbywgV29ybGQhCssUYgAAAAAASGVsbG8sIFdvcmxkIQo=\n" + + "=A91Q\n" + + "-----END PGP MESSAGE-----"; + + private static final String COMP_LIT = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: BCPG v1.71\n" + + "\n" + + "owE7LZLEAAIeqTk5+ToK4flFOSmKXAA=\n" + + "=ZYDg\n" + + "-----END PGP MESSAGE-----"; + + private static final String COMP = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: BCPG v1.71\n" + + "\n" + + "owEDAA==\n" + + "=MDzg\n" + + "-----END PGP MESSAGE-----"; + + private static final String COMP_COMP_LIT = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: BCPG v1.71\n" + + "\n" + + "owEBRwC4/6MDQlpoOTFBWSZTWVuW2KAAAAr3hGAQBABgBABAAIAWBJAAAAggADFM\n" + + "ABNBqBo00N6puqWR+TqInoXQ58XckU4UJBbltigA\n" + + "=K9Zl\n" + + "-----END PGP MESSAGE-----"; + + private static final String SIG_LIT = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: BCPG v1.71\n" + + "\n" + + "iHUEABYKACcFAmMc1i0JECgC4eASUF5eFiEEjN3RiJxCf/TyYOQjKALh4BJQXl4A\n" + + "AHkrAP98uPpqrgIix7epgL7MM1cjXXGSxqbDfXHwgptk1YGQlgD/fw89VGcXwFaI\n" + + "2k7kpXQYy/1BqnovM/jZ3X3mXhhTaAOjATstksQAAh6pOTn5Ogrh+UU5KYpcAA==\n" + + "=WKPn\n" + + "-----END PGP MESSAGE-----"; + + @Test + public void genLIT() throws IOException { + ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out); + PGPLiteralDataGenerator litGen = new PGPLiteralDataGenerator(); + OutputStream litOut = litGen.open(armorOut, PGPLiteralDataGenerator.BINARY, "", PGPLiteralData.NOW, new byte[1 << 9]); + litOut.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8)); + litOut.close(); + armorOut.close(); + } + + @Test + public void processLIT() throws IOException, PGPException { + ByteArrayInputStream bytesIn = new ByteArrayInputStream(LIT.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn); + PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decIn, out); + assertEquals(PLAINTEXT, out.toString()); + armorIn.close(); + } + + @Test + public void getLIT_LIT() throws IOException { + ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out); + PGPLiteralDataGenerator litGen = new PGPLiteralDataGenerator(); + OutputStream litOut = litGen.open(armorOut, PGPLiteralDataGenerator.BINARY, "", PGPLiteralData.NOW, new byte[1 << 9]); + litOut.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8)); + litOut.close(); + + litOut = litGen.open(armorOut, PGPLiteralDataGenerator.BINARY, "", PGPLiteralData.NOW, new byte[1 << 9]); + litOut.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8)); + litOut.close(); + + armorOut.close(); + } + + @Test + public void processLIT_LIT() throws IOException, PGPException { + ByteArrayInputStream bytesIn = new ByteArrayInputStream(LIT_LIT.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn); + PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + assertThrows(MalformedOpenPgpMessageException.RTE.class, () -> Streams.pipeAll(decIn, out)); + } + + @Test + public void genCOMP_LIT() throws IOException { + ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out); + PGPCompressedDataGenerator compGen = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP); + OutputStream compOut = compGen.open(armorOut); + PGPLiteralDataGenerator litGen = new PGPLiteralDataGenerator(); + OutputStream litOut = litGen.open(compOut, PGPLiteralDataGenerator.BINARY, "", PGPLiteralData.NOW, new byte[1 << 9]); + litOut.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8)); + litOut.close(); + compOut.close(); + armorOut.close(); + } + + @Test + public void processCOMP_LIT() throws IOException, PGPException { + ByteArrayInputStream bytesIn = new ByteArrayInputStream(COMP_LIT.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn); + PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decIn, out); + decIn.close(); + armorIn.close(); + + assertEquals(PLAINTEXT, out.toString()); + } + + @Test + public void genCOMP() throws IOException { + ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out); + PGPCompressedDataGenerator compGen = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP); + OutputStream compOut = compGen.open(armorOut); + compOut.close(); + armorOut.close(); + } + + @Test + public void processCOMP() throws IOException { + ByteArrayInputStream bytesIn = new ByteArrayInputStream(COMP.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn); + assertThrows(MalformedOpenPgpMessageException.RTE.class, () -> { + PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn); + Streams.drain(decIn); + }); + } + + @Test + public void genCOMP_COMP_LIT() throws IOException { + ArmoredOutputStream armorOut = new ArmoredOutputStream(System.out); + + PGPCompressedDataGenerator compGen1 = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP); + OutputStream compOut1 = compGen1.open(armorOut); + + PGPCompressedDataGenerator compGen2 = new PGPCompressedDataGenerator(CompressionAlgorithmTags.BZIP2); + OutputStream compOut2 = compGen2.open(compOut1); + + PGPLiteralDataGenerator litGen = new PGPLiteralDataGenerator(); + OutputStream litOut = litGen.open(compOut2, PGPLiteralDataGenerator.BINARY, "", PGPLiteralDataGenerator.NOW, new byte[1 << 9]); + + litOut.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8)); + litOut.close(); + compOut2.close(); + compOut1.close(); + armorOut.close(); + } + + @Test + public void processCOMP_COMP_LIT() throws PGPException, IOException { + ByteArrayInputStream bytesIn = new ByteArrayInputStream(COMP_COMP_LIT.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn); + PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decIn, out); + decIn.close(); + + assertEquals(PLAINTEXT, out.toString()); + } + + @Test + public void genKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { + System.out.println(PGPainless.asciiArmor( + PGPainless.generateKeyRing().modernKeyRing("Alice ") + )); + } + + @Test + public void genSIG_LIT() throws PGPException, IOException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); + ByteArrayOutputStream msgOut = new ByteArrayOutputStream(); + EncryptionStream signer = PGPainless.encryptAndOrSign() + .onOutputStream(msgOut) + .withOptions( + ProducerOptions.sign( + SigningOptions.get() + .addDetachedSignature(SecretKeyRingProtector.unprotectedKeys(), secretKeys) + ).setAsciiArmor(false) + ); + + Streams.pipeAll(new ByteArrayInputStream(PLAINTEXT.getBytes(StandardCharsets.UTF_8)), signer); + signer.close(); + EncryptionResult result = signer.getResult(); + PGPSignature detachedSignature = result.getDetachedSignatures().get(result.getDetachedSignatures().keySet().iterator().next()).iterator().next(); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + ArmoredOutputStream armorOut = new ArmoredOutputStream(out); + armorOut.flush(); + detachedSignature.encode(armorOut); + armorOut.write(msgOut.toByteArray()); + armorOut.close(); + + String armored = out.toString(); + System.out.println(armored + .replace("-----BEGIN PGP SIGNATURE-----\n", "-----BEGIN PGP MESSAGE-----\n") + .replace("-----END PGP SIGNATURE-----", "-----END PGP MESSAGE-----")); + } + + @Test + public void processSIG_LIT() throws IOException, PGPException { + ByteArrayInputStream bytesIn = new ByteArrayInputStream(SIG_LIT.getBytes(StandardCharsets.UTF_8)); + ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn); + PGPDecryptionStream decIn = new PGPDecryptionStream(armorIn); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + Streams.pipeAll(decIn, out); + decIn.close(); + + System.out.println(out); + } +}