From ef33d9d584c2bdb0c9a394e425f86e4277fb4154 Mon Sep 17 00:00:00 2001 From: Paul Schaub Date: Fri, 16 Sep 2022 00:51:49 +0200 Subject: [PATCH] Implement experimental signature verification (correctness only) --- .../MessageDecryptionStream.java | 278 ------------- .../OpenPgpMessageInputStream.java | 167 +++++++- .../automaton/NestingPDA.java | 339 ---------------- .../MalformedOpenPgpMessageException.java | 15 - .../OpenPgpMessageInputStreamTest.java | 368 +++++++++++++++++- .../PGPDecryptionStreamTest.java | 361 ----------------- .../automaton/NestingPDATest.java | 205 ---------- 7 files changed, 497 insertions(+), 1236 deletions(-) delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageDecryptionStream.java delete mode 100644 pgpainless-core/src/main/java/org/pgpainless/decryption_verification/automaton/NestingPDA.java delete mode 100644 pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PGPDecryptionStreamTest.java delete mode 100644 pgpainless-core/src/test/java/org/pgpainless/decryption_verification/automaton/NestingPDATest.java diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageDecryptionStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageDecryptionStream.java deleted file mode 100644 index 335e9d57..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/MessageDecryptionStream.java +++ /dev/null @@ -1,278 +0,0 @@ -// 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.SymmetricKeyEncSessionPacket; -import org.bouncycastle.openpgp.PGPCompressedData; -import org.bouncycastle.openpgp.PGPEncryptedData; -import org.bouncycastle.openpgp.PGPEncryptedDataList; -import org.bouncycastle.openpgp.PGPException; -import org.bouncycastle.openpgp.PGPLiteralData; -import org.bouncycastle.openpgp.PGPOnePassSignatureList; -import org.bouncycastle.openpgp.PGPPBEEncryptedData; -import org.bouncycastle.openpgp.PGPSignatureList; -import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; -import org.pgpainless.algorithm.OpenPgpPacket; -import org.pgpainless.decryption_verification.automaton.InputAlphabet; -import org.pgpainless.decryption_verification.automaton.NestingPDA; -import org.pgpainless.exception.MalformedOpenPgpMessageException; -import org.pgpainless.exception.MessageNotIntegrityProtectedException; -import org.pgpainless.exception.MissingDecryptionMethodException; -import org.pgpainless.implementation.ImplementationFactory; -import org.pgpainless.util.Passphrase; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.SequenceInputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.NoSuchElementException; -import java.util.Stack; - -public class MessageDecryptionStream extends InputStream { - - private final ConsumerOptions options; - - NestingPDA automaton = new NestingPDA(); - // nested streams, outermost at the bottom of the stack - Stack packetLayers = new Stack<>(); - List pkeskList = new ArrayList<>(); - List skeskList = new ArrayList<>(); - - public MessageDecryptionStream(InputStream inputStream, ConsumerOptions options) - throws IOException, PGPException { - this.options = options; - packetLayers.push(Layer.initial(inputStream)); - walkLayer(); - } - - private void walkLayer() throws PGPException, IOException { - if (packetLayers.isEmpty()) { - return; - } - - // We are currently in the deepest layer - 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 LIT: - automaton.next(InputAlphabet.LiteralData); - PGPLiteralData literalData = new PGPLiteralData(inputStream); - packetLayers.push(Layer.literalMessage(literalData.getDataStream())); - break loop; - - case COMP: - automaton.next(InputAlphabet.CompressedData); - PGPCompressedData compressedData = new PGPCompressedData(inputStream); - inputStream = new BCPGInputStream(compressedData.getDataStream()); - packetLayers.push(Layer.compressedData(inputStream)); - break; - - case OPS: - automaton.next(InputAlphabet.OnePassSignatures); - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - BCPGOutputStream 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 SIG: - automaton.next(InputAlphabet.Signatures); - - buf = new ByteArrayOutputStream(); - 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 PKESK: - PublicKeyEncSessionPacket pkeskPacket = (PublicKeyEncSessionPacket) inputStream.readPacket(); - pkeskList.add(pkeskPacket); - break; - - case SKESK: - SymmetricKeyEncSessionPacket skeskPacket = (SymmetricKeyEncSessionPacket) inputStream.readPacket(); - skeskList.add(skeskPacket); - break; - - case SED: - if (!options.isIgnoreMDCErrors()) { - throw new MessageNotIntegrityProtectedException(); - } - // No break; we continue below! - case SEIPD: - automaton.next(InputAlphabet.EncryptedData); - PGPEncryptedDataList encryptedDataList = assembleEncryptedDataList(inputStream); - - for (PGPEncryptedData encData : encryptedDataList) { - if (encData instanceof PGPPBEEncryptedData) { - PGPPBEEncryptedData skenc = (PGPPBEEncryptedData) encData; - for (Passphrase passphrase : options.getDecryptionPassphrases()) { - PBEDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance() - .getPBEDataDecryptorFactory(passphrase); - InputStream decryptedIn = skenc.getDataStream(decryptorFactory); - packetLayers.push(Layer.encryptedData(new BCPGInputStream(decryptedIn))); - walkLayer(); - break loop; - } - } - } - throw new MissingDecryptionMethodException("Cannot decrypt message."); - - case MARKER: - inputStream.readPacket(); // discard - break; - - case SK: - case PK: - case SSK: - case PSK: - case TRUST: - case UID: - case UATTR: - throw new MalformedOpenPgpMessageException("OpenPGP packet " + tag + " MUST NOT be part of OpenPGP messages."); - case MOD: - ModDetectionCodePacket modDetectionCodePacket = (ModDetectionCodePacket) inputStream.readPacket(); - break; - case EXP_1: - case EXP_2: - case EXP_3: - case EXP_4: - throw new MalformedOpenPgpMessageException("Experimental packet " + tag + " found inside the message."); - } - } - } - - private PGPEncryptedDataList assembleEncryptedDataList(BCPGInputStream inputStream) - throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - BCPGOutputStream bcpgOut = new BCPGOutputStream(buf); - - for (SymmetricKeyEncSessionPacket skesk : skeskList) { - bcpgOut.write(skesk.getEncoded()); - } - skeskList.clear(); - for (PublicKeyEncSessionPacket pkesk : pkeskList) { - bcpgOut.write(pkesk.getEncoded()); - } - pkeskList.clear(); - - SequenceInputStream sqin = new SequenceInputStream( - new ByteArrayInputStream(buf.toByteArray()), inputStream); - - PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) ImplementationFactory.getInstance() - .getPGPObjectFactory(sqin).nextObject(); - return encryptedDataList; - } - - 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(InputAlphabet.EndOfSequence); - } - - @Override - public int read() throws IOException { - if (packetLayers.isEmpty()) { - automaton.assertValid(); - return -1; - } - - int r = -1; - try { - r = packetLayers.peek().inputStream.read(); - } catch (IOException e) { - } - if (r == -1) { - popLayer(); - try { - walkLayer(); - } 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); - } - - static Layer encryptedData(InputStream inputStream) { - return new Layer(inputStream, true); - } - } -} diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java index 4c6a85c8..8dff7189 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStream.java @@ -18,11 +18,13 @@ import org.bouncycastle.openpgp.PGPPBEEncryptedData; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData; +import org.bouncycastle.openpgp.PGPPublicKeyRing; import org.bouncycastle.openpgp.PGPSecretKey; import org.bouncycastle.openpgp.PGPSecretKeyRing; import org.bouncycastle.openpgp.PGPSignature; import org.bouncycastle.openpgp.PGPSignatureList; import org.bouncycastle.openpgp.operator.PBEDataDecryptorFactory; +import org.bouncycastle.openpgp.operator.PGPContentVerifierBuilderProvider; import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory; import org.bouncycastle.openpgp.operator.SessionKeyDataDecryptorFactory; import org.pgpainless.PGPainless; @@ -38,6 +40,7 @@ import org.pgpainless.implementation.ImplementationFactory; import org.pgpainless.key.info.KeyRingInfo; import org.pgpainless.key.protection.SecretKeyRingProtector; import org.pgpainless.key.protection.UnlockSecretKey; +import org.pgpainless.signature.SignatureUtils; import org.pgpainless.util.Passphrase; import org.pgpainless.util.Tuple; @@ -57,7 +60,7 @@ public class OpenPgpMessageInputStream extends InputStream { private boolean closed = false; - private Signatures signatures = new Signatures(); + private Signatures signatures; public OpenPgpMessageInputStream(InputStream inputStream, ConsumerOptions options) throws IOException, PGPException { @@ -69,6 +72,7 @@ public class OpenPgpMessageInputStream extends InputStream { } this.options = options; + this.signatures = new Signatures(options); this.signatures.addDetachedSignatures(options.getDetachedSignatures()); consumePackets(); @@ -88,8 +92,9 @@ public class OpenPgpMessageInputStream extends InputStream { throws IOException, PGPException { System.out.println("Walk " + automaton); int tag; - loop: while ((tag = bcpgIn.nextPacketTag()) != -1) { + loop: while ((tag = getTag()) != -1) { OpenPgpPacket nextPacket = OpenPgpPacket.requireFromTag(tag); + System.out.println(nextPacket); switch (nextPacket) { // Literal Data - the literal data content is the new input stream @@ -114,9 +119,10 @@ public class OpenPgpMessageInputStream extends InputStream { // Signatures - either prepended to the message, or corresponding to the One Pass Signatures case SIG: + boolean isCorrespondingToOPS = automaton.peekStack() == StackAlphabet.ops; automaton.next(InputAlphabet.Signatures); PGPSignatureList signatureList = readSignatures(); - if (automaton.peekStack() == StackAlphabet.ops) { + if (isCorrespondingToOPS) { signatures.addOnePassCorrespondingSignatures(signatureList); } else { signatures.addPrependedSignatures(signatureList); @@ -246,6 +252,19 @@ public class OpenPgpMessageInputStream extends InputStream { } } + private int getTag() throws IOException { + try { + return bcpgIn.nextPacketTag(); + } catch (IOException e) { + if ("Stream closed".equals(e.getMessage())) { + // ZipInflater Streams sometimes close under our feet -.- + // Therefore we catch resulting IOEs and return -1 instead. + return -1; + } + throw e; + } + } + private List> findPotentialDecryptionKeys(PGPPublicKeyEncryptedData pkesk) { int algorithm = pkesk.getAlgorithm(); List> decryptionKeyCandidates = new ArrayList<>(); @@ -276,8 +295,8 @@ public class OpenPgpMessageInputStream extends InputStream { private PGPOnePassSignatureList readOnePassSignatures() throws IOException { ByteArrayOutputStream buf = new ByteArrayOutputStream(); BCPGOutputStream bcpgOut = new BCPGOutputStream(buf); - int tag = bcpgIn.nextPacketTag(); - while (tag == PacketTags.ONE_PASS_SIGNATURE || tag == PacketTags.MARKER) { + int tag; + while ((tag = getTag()) == PacketTags.ONE_PASS_SIGNATURE || tag == PacketTags.MARKER) { Packet packet = bcpgIn.readPacket(); if (tag == PacketTags.ONE_PASS_SIGNATURE) { OnePassSignaturePacket sigPacket = (OnePassSignaturePacket) packet; @@ -294,13 +313,13 @@ public class OpenPgpMessageInputStream extends InputStream { private PGPSignatureList readSignatures() throws IOException { ByteArrayOutputStream buf = new ByteArrayOutputStream(); BCPGOutputStream bcpgOut = new BCPGOutputStream(buf); - int tag = bcpgIn.nextPacketTag(); + int tag = getTag(); while (tag == PacketTags.SIGNATURE || tag == PacketTags.MARKER) { Packet packet = bcpgIn.readPacket(); if (tag == PacketTags.SIGNATURE) { SignaturePacket sigPacket = (SignaturePacket) packet; sigPacket.encode(bcpgOut); - tag = bcpgIn.nextPacketTag(); + tag = getTag(); } } bcpgOut.close(); @@ -328,6 +347,16 @@ public class OpenPgpMessageInputStream extends InputStream { byte b = (byte) r; signatures.update(b); } else { + in.close(); + in = null; + + try { + consumePackets(); + } catch (PGPException e) { + throw new RuntimeException(e); + } + signatures.finish(); + /* if (in instanceof OpenPgpMessageInputStream) { in.close(); in = null; @@ -335,10 +364,12 @@ public class OpenPgpMessageInputStream extends InputStream { try { System.out.println("Read consume"); consumePackets(); + signatures.finish(); } catch (PGPException e) { throw new RuntimeException(e); } } + */ } return r; } @@ -354,6 +385,16 @@ public class OpenPgpMessageInputStream extends InputStream { int r = in.read(b, off, len); if (r == -1) { + in.close(); + in = null; + + try { + consumePackets(); + } catch (PGPException e) { + throw new RuntimeException(e); + } + signatures.finish(); + /* if (in instanceof OpenPgpMessageInputStream) { in.close(); in = null; @@ -364,6 +405,7 @@ public class OpenPgpMessageInputStream extends InputStream { throw new RuntimeException(e); } } + */ } return r; } @@ -380,6 +422,12 @@ public class OpenPgpMessageInputStream extends InputStream { in = null; } + try { + consumePackets(); + } catch (PGPException e) { + throw new RuntimeException(e); + } + automaton.next(InputAlphabet.EndOfSequence); automaton.assertValid(); closed = true; @@ -418,50 +466,133 @@ public class OpenPgpMessageInputStream extends InputStream { } private static class Signatures { + final ConsumerOptions options; List detachedSignatures = new ArrayList<>(); List prependedSignatures = new ArrayList<>(); List onePassSignatures = new ArrayList<>(); List correspondingSignatures = new ArrayList<>(); + + private Signatures(ConsumerOptions options) { + this.options = options; + } + void addDetachedSignatures(Collection signatures) { + for (PGPSignature signature : signatures) { + long keyId = SignatureUtils.determineIssuerKeyId(signature); + PGPPublicKeyRing certificate = findCertificate(keyId); + initialize(signature, certificate, keyId); + } this.detachedSignatures.addAll(signatures); } void addPrependedSignatures(PGPSignatureList signatures) { + System.out.println("Adding " + signatures.size() + " prepended Signatures"); for (PGPSignature signature : signatures) { + long keyId = SignatureUtils.determineIssuerKeyId(signature); + PGPPublicKeyRing certificate = findCertificate(keyId); + initialize(signature, certificate, keyId); this.prependedSignatures.add(signature); } } void addOnePassSignatures(PGPOnePassSignatureList signatures) { + System.out.println("Adding " + signatures.size() + " OPSs"); for (PGPOnePassSignature ops : signatures) { + PGPPublicKeyRing certificate = findCertificate(ops.getKeyID()); + initialize(ops, certificate); this.onePassSignatures.add(ops); } } void addOnePassCorrespondingSignatures(PGPSignatureList signatures) { + System.out.println("Adding " + signatures.size() + " Corresponding Signatures"); for (PGPSignature signature : signatures) { correspondingSignatures.add(signature); } } + private void initialize(PGPSignature signature, PGPPublicKeyRing certificate, long keyId) { + if (certificate == null) { + // SHIT + return; + } + PGPContentVerifierBuilderProvider verifierProvider = ImplementationFactory.getInstance() + .getPGPContentVerifierBuilderProvider(); + try { + signature.init(verifierProvider, certificate.getPublicKey(keyId)); + } catch (PGPException e) { + throw new RuntimeException(e); + } + } + + private void initialize(PGPOnePassSignature ops, PGPPublicKeyRing certificate) { + if (certificate == null) { + return; + } + PGPContentVerifierBuilderProvider verifierProvider = ImplementationFactory.getInstance() + .getPGPContentVerifierBuilderProvider(); + try { + ops.init(verifierProvider, certificate.getPublicKey(ops.getKeyID())); + } catch (PGPException e) { + throw new RuntimeException(e); + } + } + + private PGPPublicKeyRing findCertificate(long keyId) { + for (PGPPublicKeyRing cert : options.getCertificates()) { + PGPPublicKey verificationKey = cert.getPublicKey(keyId); + if (verificationKey != null) { + return cert; + } + } + return null; // TODO: Missing cert for sig + } + public void update(byte b) { - /** - for (PGPSignature prepended : prependedSignatures) { - prepended.update(b); - } - for (PGPOnePassSignature ops : onePassSignatures) { - ops.update(b); - } - for (PGPSignature detached : detachedSignatures) { - detached.update(b); - } - */ + for (PGPSignature prepended : prependedSignatures) { + prepended.update(b); + } + for (PGPOnePassSignature ops : onePassSignatures) { + ops.update(b); + } + for (PGPSignature detached : detachedSignatures) { + detached.update(b); + } } public void finish() { for (PGPSignature detached : detachedSignatures) { + boolean verified = false; + try { + verified = detached.verify(); + } catch (PGPException e) { + System.out.println(e.getMessage()); + } + System.out.println("Detached Signature by " + Long.toHexString(detached.getKeyID()) + " is " + (verified ? "verified" : "unverified")); + } + for (PGPSignature prepended : prependedSignatures) { + boolean verified = false; + try { + verified = prepended.verify(); + } catch (PGPException e) { + System.out.println(e.getMessage()); + } + System.out.println("Prepended Signature by " + Long.toHexString(prepended.getKeyID()) + " is " + (verified ? "verified" : "unverified")); + } + + + for (int i = 0; i < onePassSignatures.size(); i++) { + PGPOnePassSignature ops = onePassSignatures.get(i); + PGPSignature signature = correspondingSignatures.get(correspondingSignatures.size() - i - 1); + boolean verified = false; + try { + verified = ops.verify(signature); + } catch (PGPException e) { + System.out.println(e.getMessage()); + } + System.out.println("One-Pass-Signature by " + Long.toHexString(ops.getKeyID()) + " is " + (verified ? "verified" : "unverified")); } } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/automaton/NestingPDA.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/automaton/NestingPDA.java deleted file mode 100644 index cf5ee674..00000000 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/automaton/NestingPDA.java +++ /dev/null @@ -1,339 +0,0 @@ -package org.pgpainless.decryption_verification.automaton; - -import org.pgpainless.exception.MalformedOpenPgpMessageException; - -import java.util.Stack; - -import static org.pgpainless.decryption_verification.automaton.StackAlphabet.msg; -import static org.pgpainless.decryption_verification.automaton.StackAlphabet.ops; -import static org.pgpainless.decryption_verification.automaton.StackAlphabet.terminus; - -/** - * Pushdown Automaton to verify the correct syntax of OpenPGP messages during decryption. - *

- * OpenPGP messages MUST follow certain rules in order to be well-formed. - * Section §11.3. of RFC4880 specifies a formal grammar for OpenPGP messages. - *

- * This grammar was transformed into a pushdown automaton, which is implemented below. - * The automaton only ends up in a valid state ({@link #isValid()} iff the OpenPGP message conformed to the - * grammar. - *

- * There are some specialties with this implementation though: - * Bouncy Castle combines ESKs and Encrypted Data Packets into a single object, so we do not have to - * handle those manually. - *

- * Bouncy Castle further combines OnePassSignatures and Signatures into lists, so instead of pushing multiple - * 'o's onto the stack repeatedly, a sequence of OnePassSignatures causes a single 'o' to be pushed to the stack. - * The same is true for Signatures. - *

- * Therefore, a message is valid, even if the number of OnePassSignatures and Signatures does not match. - * If a message contains at least one OnePassSignature, it is sufficient if there is at least one Signature to - * not cause a {@link MalformedOpenPgpMessageException}. - * - * @see RFC4880 §11.3. OpenPGP Messages - */ -public class NestingPDA { - - /** - * Set of states of the automaton. - * Each state defines its valid transitions in their {@link State#transition(InputAlphabet, NestingPDA)} - * method. - */ - public enum State { - - OpenPgpMessage { - @Override - State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException { - StackAlphabet stackItem = automaton.popStack(); - if (stackItem != msg) { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - switch (input) { - - case LiteralData: - return LiteralMessage; - - case Signatures: - automaton.pushStack(msg); - return OpenPgpMessage; - - case OnePassSignatures: - automaton.pushStack(ops); - automaton.pushStack(msg); - return OpenPgpMessage; - - case CompressedData: - return CompressedMessage; - - case EncryptedData: - return EncryptedMessage; - - case EndOfSequence: - default: - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - } - }, - - LiteralMessage { - @Override - State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException { - StackAlphabet stackItem = automaton.popStack(); - switch (input) { - - case Signatures: - if (stackItem == ops) { - return CorrespondingSignature; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case EndOfSequence: - if (stackItem == terminus && automaton.stack.isEmpty()) { - return Valid; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case LiteralData: - case OnePassSignatures: - case CompressedData: - case EncryptedData: - default: - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - } - }, - - CompressedMessage { - @Override - State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException { - StackAlphabet stackItem = automaton.popStack(); - switch (input) { - case Signatures: - if (stackItem == ops) { - return CorrespondingSignature; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case EndOfSequence: - if (stackItem == terminus && automaton.stack.isEmpty()) { - return Valid; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case LiteralData: - case OnePassSignatures: - case CompressedData: - case EncryptedData: - default: - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - } - }, - - EncryptedMessage { - @Override - State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException { - StackAlphabet stackItem = automaton.popStack(); - switch (input) { - case Signatures: - if (stackItem == ops) { - return CorrespondingSignature; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case EndOfSequence: - if (stackItem == terminus && automaton.stack.isEmpty()) { - return Valid; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - - case LiteralData: - case OnePassSignatures: - case CompressedData: - case EncryptedData: - default: - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - } - }, - - CorrespondingSignature { - @Override - State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException { - StackAlphabet stackItem = automaton.popStack(); - if (stackItem == terminus && input == InputAlphabet.EndOfSequence && automaton.stack.isEmpty()) { - return Valid; - } else { - throw new MalformedOpenPgpMessageException(this, input, stackItem); - } - } - }, - - Valid { - @Override - State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException { - throw new MalformedOpenPgpMessageException(this, input, null); - } - }, - ; - - /** - * Pop the automatons stack and transition to another state. - * If no valid transition from the current state is available given the popped stack item and input symbol, - * a {@link MalformedOpenPgpMessageException} is thrown. - * Otherwise, the stack is manipulated according to the valid transition and the new state is returned. - * - * @param input input symbol - * @param automaton automaton - * @return new state of the automaton - * @throws MalformedOpenPgpMessageException in case of an illegal input symbol - */ - abstract State transition(InputAlphabet input, NestingPDA automaton) throws MalformedOpenPgpMessageException; - } - - private final Stack stack = new Stack<>(); - private State state; - // Some OpenPGP packets have nested contents (e.g. compressed / encrypted data). - NestingPDA nestedSequence = null; - - public NestingPDA() { - state = State.OpenPgpMessage; - stack.push(terminus); - stack.push(msg); - } - - /** - * Process the next input packet. - * - * @param input input - * @throws MalformedOpenPgpMessageException in case the input packet is illegal here - */ - public void next(InputAlphabet input) throws MalformedOpenPgpMessageException { - _next(input); - } - - /** - * Process the next input packet. - * This method returns true, iff the given input triggered a successful closing of this PDAs nested PDA. - *

- * This is for example the case, if the current packet is a Compressed Data packet which contains a - * valid nested OpenPGP message and the last input was {@link InputAlphabet#EndOfSequence} indicating the - * end of the Compressed Data packet. - *

- * If the input triggered this PDAs nested PDA to close its nested PDA, this method returns false - * in order to prevent this PDA from closing its nested PDA prematurely. - * - * @param input input - * @return true if this just closed its nested sequence, false otherwise - * @throws MalformedOpenPgpMessageException if the input is illegal - */ - private boolean _next(InputAlphabet input) throws MalformedOpenPgpMessageException { - if (nestedSequence != null) { - boolean sequenceInNestedSequenceWasClosed = nestedSequence._next(input); - if (sequenceInNestedSequenceWasClosed) return false; // No need to close out nested sequence too. - } else { - // make a state transition in this automaton - state = state.transition(input, this); - - // If the processed packet contains nested sequence, open nested automaton for it - if (input == InputAlphabet.CompressedData || input == InputAlphabet.EncryptedData) { - nestedSequence = new NestingPDA(); - } - } - - if (input != InputAlphabet.EndOfSequence) { - return false; - } - - // Close nested sequence if needed - boolean nestedIsInnerMost = nestedSequence != null && nestedSequence.isInnerMost(); - if (nestedIsInnerMost) { - if (nestedSequence.isValid()) { - // Close nested sequence - nestedSequence = null; - return true; - } else { - throw new MalformedOpenPgpMessageException("Climbing up nested message validation failed." + - " Automaton for current nesting level is not in valid state: " + nestedSequence.getState() + " " + nestedSequence.stack.peek() + " (Input was " + input + ")"); - } - } - return false; - } - - /** - * Return the current state of the PDA. - * - * @return state - */ - private State getState() { - return state; - } - - /** - * Return true, if the PDA is in a valid state (the OpenPGP message is valid). - * - * @return true if valid, false otherwise - */ - public boolean isValid() { - 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. - * - * @return stack item - */ - private StackAlphabet popStack() { - return stack.pop(); - } - - /** - * Push an item onto the stack. - * - * @param item item - */ - private void pushStack(StackAlphabet item) { - stack.push(item); - } - - /** - * Return true, if this packet sequence has no nested sequence. - * A nested sequence is for example the content of a Compressed Data packet. - * - * @return true if PDA is innermost, false if it has a nested sequence - */ - private boolean isInnerMost() { - return nestedSequence == null; - } - - @Override - public String toString() { - StringBuilder out = new StringBuilder("State: ").append(state) - .append(", Stack (asc.): ").append(stack) - .append('\n'); - if (nestedSequence != null) { - // recursively call toString() on nested PDAs and indent their representation - String nestedToString = nestedSequence.toString(); - String[] lines = nestedToString.split("\n"); - for (int i = 0; i < lines.length; i++) { - String nestedLine = lines[i]; - out.append(i == 0 ? "⤷ " : " ") // indent nested PDA - .append(nestedLine) - .append('\n'); - } - } - return out.toString(); - } -} 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 21b6e807..0069209c 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/exception/MalformedOpenPgpMessageException.java +++ b/pgpainless-core/src/main/java/org/pgpainless/exception/MalformedOpenPgpMessageException.java @@ -5,7 +5,6 @@ package org.pgpainless.exception; import org.pgpainless.decryption_verification.automaton.InputAlphabet; -import org.pgpainless.decryption_verification.automaton.NestingPDA; import org.pgpainless.decryption_verification.automaton.PDA; import org.pgpainless.decryption_verification.automaton.StackAlphabet; @@ -21,21 +20,7 @@ public class MalformedOpenPgpMessageException extends RuntimeException { super(message); } - public MalformedOpenPgpMessageException(String message, MalformedOpenPgpMessageException cause) { - super(message, cause); - } - - public MalformedOpenPgpMessageException(NestingPDA.State state, - InputAlphabet input, - 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 MalformedOpenPgpMessageException(PDA.State state, InputAlphabet input, 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 MalformedOpenPgpMessageException(String message, PDA automaton) { - super(message + automaton.toString()); - } } diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStreamTest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStreamTest.java index 5b42101c..af1fac44 100644 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStreamTest.java +++ b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/OpenPgpMessageInputStreamTest.java @@ -1,87 +1,402 @@ 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.PGPPublicKeyRing; 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.algorithm.CompressionAlgorithm; +import org.pgpainless.encryption_signing.EncryptionOptions; +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 org.pgpainless.util.Passphrase; 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; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.COMP; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.COMP_COMP_LIT; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.COMP_LIT; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.KEY; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.LIT; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.LIT_LIT; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.PASSPHRASE; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.PENC_COMP_LIT; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.PLAINTEXT; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.SENC_LIT; -import static org.pgpainless.decryption_verification.PGPDecryptionStreamTest.SIG_LIT; public class OpenPgpMessageInputStreamTest { + + public 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-----"; + + public static final String PLAINTEXT = "Hello, World!\n"; + public static final String PASSPHRASE = "sw0rdf1sh"; + + public static final String LIT = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "yxRiAAAAAABIZWxsbywgV29ybGQhCg==\n" + + "=WGju\n" + + "-----END PGP MESSAGE-----"; + + public static final String LIT_LIT = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: BCPG v1.71\n" + + "\n" + + "yxRiAAAAAABIZWxsbywgV29ybGQhCssUYgAAAAAASGVsbG8sIFdvcmxkIQo=\n" + + "=A91Q\n" + + "-----END PGP MESSAGE-----"; + + public static final String COMP_LIT = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: BCPG v1.71\n" + + "\n" + + "owE7LZLEAAIeqTk5+ToK4flFOSmKXAA=\n" + + "=ZYDg\n" + + "-----END PGP MESSAGE-----"; + + public static final String COMP = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: BCPG v1.71\n" + + "\n" + + "owEDAA==\n" + + "=MDzg\n" + + "-----END PGP MESSAGE-----"; + + public 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-----"; + + public 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-----"; + + public static final String SENC_LIT = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "jA0ECQMCuZ0qHNXWnGhg0j8Bdm1cxV65sYb7jDgb4rRMtdNpQ1dC4UpSYuk9YWS2\n" + + "DpNEijbX8b/P1UOK2kJczNDADMRegZuLEI+dNsBnJjk=\n" + + "=i4Y0\n" + + "-----END PGP MESSAGE-----"; + + public static final String PENC_COMP_LIT = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "hF4Dyqa/GWUy6WsSAQdAQ62BwmUt8Iby0+jvrLhMgST79KR/as+dyl0nf1uki2sw\n" + + "Thg1Ojtf0hOyJgcpQ4nP2Q0wYFR0F1sCydaIlTGreYZHlGtybP7/Ml6KNZILTRWP\n" + + "0kYBkGBgK7oQWRIVyoF2POvEP6EX1X8nvQk7O3NysVdRVbnia7gE3AzRYuha4kxs\n" + + "pI6xJkntLMS3K6him1Y9FHINIASFSB+C\n" + + "=5p00\n" + + "-----END PGP MESSAGE-----"; + + public static final String OPS_LIT_SIG = "" + + "-----BEGIN PGP MESSAGE-----\n" + + "Version: PGPainless\n" + + "\n" + + "kA0DAAoWKALh4BJQXl4ByxRiAAAAAABIZWxsbywgV29ybGQhCoh1BAAWCgAnBQJj\n" + + "I3fSCRAoAuHgElBeXhYhBIzd0YicQn/08mDkIygC4eASUF5eAADLOgEA766VyMMv\n" + + "sxfQwQHly3T6ySHSNhYEpoyvdxVqhjBBR+EA/3i6C8lKFPPTh/PvTGbVFOl+eUSV\n" + + "I0w3c+BRY/pO0m4H\n" + + "=tkTV\n" + + "-----END PGP MESSAGE-----"; + + public static void main(String[] args) throws Exception { + // genLIT(); + // genLIT_LIT(); + // genCOMP_LIT(); + // genCOMP(); + // genCOMP_COMP_LIT(); + // genKey(); + // genSIG_LIT(); + // genSENC_LIT(); + // genPENC_COMP_LIT(); + genOPS_LIT_SIG(); + } + + public static 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(); + } + + public static void genLIT_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(); + } + + public static 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(); + } + + public static 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(); + } + + public static 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(); + } + + public static void genKey() throws PGPException, InvalidAlgorithmParameterException, NoSuchAlgorithmException, IOException { + System.out.println(PGPainless.asciiArmor( + PGPainless.generateKeyRing().modernKeyRing("Alice ") + )); + } + + public static 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-----")); + } + + public static void genSENC_LIT() throws PGPException, IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + EncryptionStream enc = PGPainless.encryptAndOrSign() + .onOutputStream(out) + .withOptions(ProducerOptions.encrypt(EncryptionOptions.get() + .addPassphrase(Passphrase.fromPassword(PASSPHRASE))) + .overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)); + enc.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8)); + enc.close(); + + System.out.println(out); + } + + public static void genPENC_COMP_LIT() throws IOException, PGPException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); + PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKeys); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + EncryptionStream enc = PGPainless.encryptAndOrSign() + .onOutputStream(out) + .withOptions(ProducerOptions.encrypt(EncryptionOptions.get() + .addRecipient(cert)) + .overrideCompressionAlgorithm(CompressionAlgorithm.ZLIB)); + + Streams.pipeAll(new ByteArrayInputStream(PLAINTEXT.getBytes(StandardCharsets.UTF_8)), enc); + enc.close(); + + System.out.println(out); + } + + public static void genOPS_LIT_SIG() throws PGPException, IOException { + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + EncryptionStream enc = PGPainless.encryptAndOrSign() + .onOutputStream(out) + .withOptions(ProducerOptions.sign(SigningOptions.get() + .addSignature(SecretKeyRingProtector.unprotectedKeys(), secretKeys)) + .overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)); + Streams.pipeAll(new ByteArrayInputStream(PLAINTEXT.getBytes(StandardCharsets.UTF_8)), enc); + enc.close(); + + System.out.println(out); + } + @Test public void testProcessLIT() throws IOException, PGPException { - String plain = process(LIT, ConsumerOptions.get()); + String plain = processReadBuffered(LIT, ConsumerOptions.get()); + assertEquals(PLAINTEXT, plain); + + plain = processReadSequential(LIT, ConsumerOptions.get()); assertEquals(PLAINTEXT, plain); } @Test public void testProcessLIT_LIT_fails() { assertThrows(MalformedOpenPgpMessageException.class, - () -> process(LIT_LIT, ConsumerOptions.get())); + () -> processReadBuffered(LIT_LIT, ConsumerOptions.get())); + + assertThrows(MalformedOpenPgpMessageException.class, + () -> processReadSequential(LIT_LIT, ConsumerOptions.get())); } @Test public void testProcessCOMP_LIT() throws PGPException, IOException { - String plain = process(COMP_LIT, ConsumerOptions.get()); + String plain = processReadBuffered(COMP_LIT, ConsumerOptions.get()); + assertEquals(PLAINTEXT, plain); + + plain = processReadSequential(COMP_LIT, ConsumerOptions.get()); assertEquals(PLAINTEXT, plain); } @Test public void testProcessCOMP_fails() { assertThrows(MalformedOpenPgpMessageException.class, - () -> process(COMP, ConsumerOptions.get())); + () -> processReadBuffered(COMP, ConsumerOptions.get())); + + assertThrows(MalformedOpenPgpMessageException.class, + () -> processReadSequential(COMP, ConsumerOptions.get())); } @Test public void testProcessCOMP_COMP_LIT() throws PGPException, IOException { - String plain = process(COMP_COMP_LIT, ConsumerOptions.get()); + String plain = processReadBuffered(COMP_COMP_LIT, ConsumerOptions.get()); + assertEquals(PLAINTEXT, plain); + + plain = processReadSequential(COMP_COMP_LIT, ConsumerOptions.get()); assertEquals(PLAINTEXT, plain); } @Test public void testProcessSIG_LIT() throws PGPException, IOException { - String plain = process(SIG_LIT, ConsumerOptions.get()); + PGPPublicKeyRing cert = PGPainless.extractCertificate( + PGPainless.readKeyRing().secretKeyRing(KEY)); + + String plain = processReadBuffered(SIG_LIT, ConsumerOptions.get() + .addVerificationCert(cert)); + assertEquals(PLAINTEXT, plain); + + plain = processReadSequential(SIG_LIT, ConsumerOptions.get() + .addVerificationCert(cert)); assertEquals(PLAINTEXT, plain); } @Test public void testProcessSENC_LIT() throws PGPException, IOException { - String plain = process(SENC_LIT, ConsumerOptions.get().addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE))); + String plain = processReadBuffered(SENC_LIT, ConsumerOptions.get().addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE))); + assertEquals(PLAINTEXT, plain); + + plain = processReadSequential(SENC_LIT, ConsumerOptions.get().addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE))); assertEquals(PLAINTEXT, plain); } @Test public void testProcessPENC_COMP_LIT() throws IOException, PGPException { PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); - String plain = process(PENC_COMP_LIT, ConsumerOptions.get() + String plain = processReadBuffered(PENC_COMP_LIT, ConsumerOptions.get() + .addDecryptionKey(secretKeys)); + assertEquals(PLAINTEXT, plain); + + plain = processReadSequential(PENC_COMP_LIT, ConsumerOptions.get() .addDecryptionKey(secretKeys)); assertEquals(PLAINTEXT, plain); } - private String process(String armoredMessage, ConsumerOptions options) throws PGPException, IOException { + @Test + public void testProcessOPS_LIT_SIG() throws IOException, PGPException { + PGPPublicKeyRing cert = PGPainless.extractCertificate(PGPainless.readKeyRing().secretKeyRing(KEY)); + String plain = processReadBuffered(OPS_LIT_SIG, ConsumerOptions.get() + .addVerificationCert(cert)); + assertEquals(PLAINTEXT, plain); + + plain = processReadSequential(OPS_LIT_SIG, ConsumerOptions.get() + .addVerificationCert(cert)); + assertEquals(PLAINTEXT, plain); + } + + private String processReadBuffered(String armoredMessage, ConsumerOptions options) throws PGPException, IOException { OpenPgpMessageInputStream in = get(armoredMessage, options); ByteArrayOutputStream out = new ByteArrayOutputStream(); Streams.pipeAll(in, out); @@ -89,6 +404,19 @@ public class OpenPgpMessageInputStreamTest { return out.toString(); } + private String processReadSequential(String armoredMessage, ConsumerOptions options) throws PGPException, IOException { + OpenPgpMessageInputStream in = get(armoredMessage, options); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + + int r; + while ((r = in.read()) != -1) { + out.write(r); + } + + in.close(); + return out.toString(); + } + private OpenPgpMessageInputStream get(String armored, ConsumerOptions options) throws IOException, PGPException { ByteArrayInputStream bytesIn = new ByteArrayInputStream(armored.getBytes(StandardCharsets.UTF_8)); ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn); 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 deleted file mode 100644 index 8da44ec8..00000000 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/PGPDecryptionStreamTest.java +++ /dev/null @@ -1,361 +0,0 @@ -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.PGPPublicKeyRing; -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.algorithm.CompressionAlgorithm; -import org.pgpainless.encryption_signing.EncryptionOptions; -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 org.pgpainless.util.Passphrase; - -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 { - - public 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-----"; - - public static final String PLAINTEXT = "Hello, World!\n"; - public static final String PASSPHRASE = "sw0rdf1sh"; - - public static final String LIT = "" + - "-----BEGIN PGP MESSAGE-----\n" + - "Version: PGPainless\n" + - "\n" + - "yxRiAAAAAABIZWxsbywgV29ybGQhCg==\n" + - "=WGju\n" + - "-----END PGP MESSAGE-----"; - - public static final String LIT_LIT = "" + - "-----BEGIN PGP MESSAGE-----\n" + - "Version: BCPG v1.71\n" + - "\n" + - "yxRiAAAAAABIZWxsbywgV29ybGQhCssUYgAAAAAASGVsbG8sIFdvcmxkIQo=\n" + - "=A91Q\n" + - "-----END PGP MESSAGE-----"; - - public static final String COMP_LIT = "" + - "-----BEGIN PGP MESSAGE-----\n" + - "Version: BCPG v1.71\n" + - "\n" + - "owE7LZLEAAIeqTk5+ToK4flFOSmKXAA=\n" + - "=ZYDg\n" + - "-----END PGP MESSAGE-----"; - - public static final String COMP = "" + - "-----BEGIN PGP MESSAGE-----\n" + - "Version: BCPG v1.71\n" + - "\n" + - "owEDAA==\n" + - "=MDzg\n" + - "-----END PGP MESSAGE-----"; - - public 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-----"; - - public 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-----"; - - public static final String SENC_LIT = "" + - "-----BEGIN PGP MESSAGE-----\n" + - "Version: PGPainless\n" + - "\n" + - "jA0ECQMCuZ0qHNXWnGhg0j8Bdm1cxV65sYb7jDgb4rRMtdNpQ1dC4UpSYuk9YWS2\n" + - "DpNEijbX8b/P1UOK2kJczNDADMRegZuLEI+dNsBnJjk=\n" + - "=i4Y0\n" + - "-----END PGP MESSAGE-----"; - - public static final String PENC_COMP_LIT = "" + - "-----BEGIN PGP MESSAGE-----\n" + - "Version: PGPainless\n" + - "\n" + - "hF4Dyqa/GWUy6WsSAQdAQ62BwmUt8Iby0+jvrLhMgST79KR/as+dyl0nf1uki2sw\n" + - "Thg1Ojtf0hOyJgcpQ4nP2Q0wYFR0F1sCydaIlTGreYZHlGtybP7/Ml6KNZILTRWP\n" + - "0kYBkGBgK7oQWRIVyoF2POvEP6EX1X8nvQk7O3NysVdRVbnia7gE3AzRYuha4kxs\n" + - "pI6xJkntLMS3K6him1Y9FHINIASFSB+C\n" + - "=5p00\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); - MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get()); - - 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); - MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get()); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - assertThrows(MalformedOpenPgpMessageException.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); - MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get()); - - 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.class, () -> { - MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get()); - 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); - MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get()); - - 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); - MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get()); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Streams.pipeAll(decIn, out); - decIn.close(); - - System.out.println(out); - } - - @Test - public void genSENC_LIT() throws PGPException, IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - EncryptionStream enc = PGPainless.encryptAndOrSign() - .onOutputStream(out) - .withOptions(ProducerOptions.encrypt(EncryptionOptions.get() - .addPassphrase(Passphrase.fromPassword(PASSPHRASE))) - .overrideCompressionAlgorithm(CompressionAlgorithm.UNCOMPRESSED)); - enc.write(PLAINTEXT.getBytes(StandardCharsets.UTF_8)); - enc.close(); - - System.out.println(out); - } - - @Test - public void processSENC_LIT() throws IOException, PGPException { - ByteArrayInputStream bytesIn = new ByteArrayInputStream(SENC_LIT.getBytes(StandardCharsets.UTF_8)); - ArmoredInputStream armorIn = ArmoredInputStreamFactory.get(bytesIn); - MessageDecryptionStream decIn = new MessageDecryptionStream(armorIn, ConsumerOptions.get() - .addDecryptionPassphrase(Passphrase.fromPassword(PASSPHRASE))); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - Streams.pipeAll(decIn, out); - decIn.close(); - - System.out.println(out); - } - - @Test - public void genPENC_COMP_LIT() throws IOException, PGPException { - PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); - PGPPublicKeyRing cert = PGPainless.extractCertificate(secretKeys); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); - EncryptionStream enc = PGPainless.encryptAndOrSign() - .onOutputStream(out) - .withOptions(ProducerOptions.encrypt(EncryptionOptions.get() - .addRecipient(cert)) - .overrideCompressionAlgorithm(CompressionAlgorithm.ZLIB)); - - Streams.pipeAll(new ByteArrayInputStream(PLAINTEXT.getBytes(StandardCharsets.UTF_8)), enc); - enc.close(); - - System.out.println(out); - } -} diff --git a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/automaton/NestingPDATest.java b/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/automaton/NestingPDATest.java deleted file mode 100644 index 8c1c4921..00000000 --- a/pgpainless-core/src/test/java/org/pgpainless/decryption_verification/automaton/NestingPDATest.java +++ /dev/null @@ -1,205 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Paul Schaub -// -// SPDX-License-Identifier: Apache-2.0 - -package org.pgpainless.decryption_verification.automaton; - -import org.junit.jupiter.api.Test; -import org.pgpainless.exception.MalformedOpenPgpMessageException; - -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class NestingPDATest { - - /** - * MSG is valid. - * - * @throws MalformedOpenPgpMessageException fail - */ - @Test - public void testSimpleLiteralMessageIsValid() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.LiteralData); - automaton.next(InputAlphabet.EndOfSequence); - - assertTrue(automaton.isValid()); - } - - /** - * OPS MSG SIG is valid. - * - * @throws MalformedOpenPgpMessageException fail - */ - @Test - public void testSimpleOpsSignedMesssageIsValid() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.OnePassSignatures); - automaton.next(InputAlphabet.LiteralData); - automaton.next(InputAlphabet.Signatures); - automaton.next(InputAlphabet.EndOfSequence); - - assertTrue(automaton.isValid()); - } - - /** - * SIG MSG is valid. - * - * @throws MalformedOpenPgpMessageException fail - */ - @Test - public void testSimplePrependSignedMessageIsValid() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.Signatures); - automaton.next(InputAlphabet.LiteralData); - automaton.next(InputAlphabet.EndOfSequence); - - assertTrue(automaton.isValid()); - } - - /** - * OPS COMP(MSG) SIG is valid. - * - * @throws MalformedOpenPgpMessageException fail - */ - @Test - public void testOPSSignedCompressedMessageIsValid() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.OnePassSignatures); - automaton.next(InputAlphabet.CompressedData); - automaton.next(InputAlphabet.LiteralData); - automaton.next(InputAlphabet.EndOfSequence); - automaton.next(InputAlphabet.Signatures); - automaton.next(InputAlphabet.EndOfSequence); - - assertTrue(automaton.isValid()); - } - - /** - * OPS ENC(COMP(COMP(MSG))) SIG is valid. - * - * @throws MalformedOpenPgpMessageException fail - */ - @Test - public void testOpsSignedEncryptedCompressedCompressedMessageIsValid() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.OnePassSignatures); - automaton.next(InputAlphabet.EncryptedData); - automaton.next(InputAlphabet.CompressedData); - automaton.next(InputAlphabet.CompressedData); - - automaton.next(InputAlphabet.LiteralData); - - automaton.next(InputAlphabet.EndOfSequence); - automaton.next(InputAlphabet.EndOfSequence); - automaton.next(InputAlphabet.EndOfSequence); - automaton.next(InputAlphabet.Signatures); - automaton.next(InputAlphabet.EndOfSequence); - - assertTrue(automaton.isValid()); - } - - /** - * MSG SIG is invalid. - * - * @throws MalformedOpenPgpMessageException fail - */ - @Test - public void testLiteralPlusSigsFails() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.LiteralData); - assertThrows(MalformedOpenPgpMessageException.class, - () -> automaton.next(InputAlphabet.Signatures)); - } - - /** - * MSG MSG is invalid. - * - * @throws MalformedOpenPgpMessageException fail - */ - @Test - public void testTwoLiteralDataPacketsFails() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.LiteralData); - assertThrows(MalformedOpenPgpMessageException.class, - () -> automaton.next(InputAlphabet.LiteralData)); - } - - /** - * OPS COMP(MSG MSG) SIG is invalid (two literal packets are illegal). - * - * @throws MalformedOpenPgpMessageException fail - */ - @Test - public void testOPSSignedMessageWithTwoLiteralDataPacketsFails() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.OnePassSignatures); - automaton.next(InputAlphabet.CompressedData); - automaton.next(InputAlphabet.LiteralData); - assertThrows(MalformedOpenPgpMessageException.class, - () -> automaton.next(InputAlphabet.LiteralData)); - } - - /** - * OPS COMP(MSG) MSG SIG is invalid. - * - * @throws MalformedOpenPgpMessageException fail - */ - @Test - public void testOPSSignedMessageWithTwoLiteralDataPacketsFails2() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.OnePassSignatures); - automaton.next(InputAlphabet.CompressedData); - automaton.next(InputAlphabet.LiteralData); - automaton.next(InputAlphabet.EndOfSequence); - assertThrows(MalformedOpenPgpMessageException.class, - () -> automaton.next(InputAlphabet.LiteralData)); - } - - /** - * OPS COMP(MSG SIG) is invalid (MSG SIG does not form valid nested message). - * - * @throws MalformedOpenPgpMessageException fail - */ - @Test - public void testCorrespondingSignaturesOfOpsSignedMessageAreLayerFurtherDownFails() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.OnePassSignatures); - automaton.next(InputAlphabet.CompressedData); - automaton.next(InputAlphabet.LiteralData); - assertThrows(MalformedOpenPgpMessageException.class, - () -> automaton.next(InputAlphabet.Signatures)); - } - - /** - * Empty COMP is invalid. - */ - @Test - public void testEmptyCompressedDataIsInvalid() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.CompressedData); - assertThrows(MalformedOpenPgpMessageException.class, - () -> automaton.next(InputAlphabet.EndOfSequence)); - } - - @Test - public void testOPSSignedEncryptedCompressedOPSSignedMessageIsValid() throws MalformedOpenPgpMessageException { - NestingPDA automaton = new NestingPDA(); - automaton.next(InputAlphabet.OnePassSignatures); - - automaton.next(InputAlphabet.EncryptedData); - automaton.next(InputAlphabet.OnePassSignatures); - - automaton.next(InputAlphabet.CompressedData); - automaton.next(InputAlphabet.LiteralData); - automaton.next(InputAlphabet.EndOfSequence); - - automaton.next(InputAlphabet.Signatures); - automaton.next(InputAlphabet.EndOfSequence); - - automaton.next(InputAlphabet.Signatures); - automaton.next(InputAlphabet.EndOfSequence); - - assertTrue(automaton.isValid()); - } -}