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 a1364997..2d031edc 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 @@ -4,12 +4,17 @@ package org.pgpainless.decryption_verification; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Stack; + import org.bouncycastle.bcpg.BCPGInputStream; -import org.bouncycastle.bcpg.BCPGOutputStream; -import org.bouncycastle.bcpg.OnePassSignaturePacket; -import org.bouncycastle.bcpg.Packet; -import org.bouncycastle.bcpg.PacketTags; -import org.bouncycastle.bcpg.SignaturePacket; import org.bouncycastle.openpgp.PGPCompressedData; import org.bouncycastle.openpgp.PGPEncryptedData; import org.bouncycastle.openpgp.PGPEncryptedDataList; @@ -17,7 +22,6 @@ import org.bouncycastle.openpgp.PGPException; import org.bouncycastle.openpgp.PGPLiteralData; import org.bouncycastle.openpgp.PGPObjectFactory; import org.bouncycastle.openpgp.PGPOnePassSignature; -import org.bouncycastle.openpgp.PGPOnePassSignatureList; import org.bouncycastle.openpgp.PGPPBEEncryptedData; import org.bouncycastle.openpgp.PGPPrivateKey; import org.bouncycastle.openpgp.PGPPublicKey; @@ -26,11 +30,12 @@ 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.PGPUtil; 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.bouncycastle.util.encoders.Hex; import org.pgpainless.PGPainless; import org.pgpainless.algorithm.CompressionAlgorithm; import org.pgpainless.algorithm.EncryptionPurpose; @@ -54,15 +59,6 @@ import org.pgpainless.util.Tuple; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Stack; - public class OpenPgpMessageInputStream extends InputStream { private static final Logger LOGGER = LoggerFactory.getLogger(OpenPgpMessageInputStream.class); @@ -100,13 +96,7 @@ public class OpenPgpMessageInputStream extends InputStream { this.signatures.addDetachedSignatures(options.getDetachedSignatures()); } - // TODO: Use BCPGInputStream.wrap(inputStream); - BCPGInputStream bcpg = null; - if (inputStream instanceof BCPGInputStream) { - bcpg = (BCPGInputStream) inputStream; - } else { - bcpg = new BCPGInputStream(inputStream); - } + BCPGInputStream bcpg = BCPGInputStream.wrap(inputStream); this.packetInputStream = new TeeBCPGInputStream(bcpg, signatures); // *omnomnom* @@ -133,13 +123,20 @@ public class OpenPgpMessageInputStream extends InputStream { throws IOException, PGPException { int tag; loop: while ((tag = nextTag()) != -1) { - OpenPgpPacket nextPacket = OpenPgpPacket.requireFromTag(tag); + OpenPgpPacket nextPacket; + try { + nextPacket = OpenPgpPacket.requireFromTag(tag); + } catch (NoSuchElementException e) { + log("Invalid tag: " + tag); + throw e; + } + log(nextPacket.toString()); + signatures.nextPacket(nextPacket); switch (nextPacket) { // Literal Data - the literal data content is the new input stream case LIT: automaton.next(InputAlphabet.LiteralData); - signatures.commitNested(); processLiteralData(); break loop; @@ -153,12 +150,8 @@ public class OpenPgpMessageInputStream extends InputStream { // One Pass Signature case OPS: automaton.next(InputAlphabet.OnePassSignatures); - // signatures.addOnePassSignature(readOnePassSignature()); - PGPOnePassSignatureList onePassSignatureList = readOnePassSignatures(); - for (PGPOnePassSignature ops : onePassSignatureList) { - signatures.addOnePassSignature(ops); - } - // signatures.addOnePassSignatures(readOnePassSignatures()); + PGPOnePassSignature onePassSignature = readOnePassSignature(); + signatures.addOnePassSignature(onePassSignature); break; // Signature - either prepended to the message, or corresponding to a One Pass Signature @@ -166,13 +159,8 @@ public class OpenPgpMessageInputStream extends InputStream { boolean isSigForOPS = automaton.peekStack() == StackAlphabet.ops; automaton.next(InputAlphabet.Signatures); - // PGPSignature signature = readSignature(); - // processSignature(signature, isSigForOPS); - - PGPSignatureList signatureList = readSignatures(); - for (PGPSignature signature : signatureList) { - processSignature(signature, isSigForOPS); - } + PGPSignature signature = readSignature(); + processSignature(signature, isSigForOPS); break; @@ -220,6 +208,7 @@ public class OpenPgpMessageInputStream extends InputStream { private void processSignature(PGPSignature signature, boolean isSigForOPS) { if (isSigForOPS) { + signatures.popNested(); signatures.addCorrespondingOnePassSignature(signature); } else { signatures.addPrependedSignature(signature); @@ -240,6 +229,41 @@ public class OpenPgpMessageInputStream extends InputStream { nestedInputStream = literalData.getDataStream(); } + private void debugEncryptedData() throws PGPException, IOException { + PGPEncryptedDataList encDataList = new PGPEncryptedDataList(packetInputStream); + + // TODO: Replace with !encDataList.isIntegrityProtected() + if (!encDataList.get(0).isIntegrityProtected()) { + throw new MessageNotIntegrityProtectedException(); + } + + SortedESKs esks = new SortedESKs(encDataList); + for (PGPPublicKeyEncryptedData pkesk : esks.pkesks) { + long keyId = pkesk.getKeyID(); + PGPSecretKeyRing decryptionKeys = getDecryptionKey(keyId); + if (decryptionKeys == null) { + continue; + } + SecretKeyRingProtector protector = options.getSecretKeyProtector(decryptionKeys); + PGPSecretKey decryptionKey = decryptionKeys.getSecretKey(keyId); + PGPPrivateKey privateKey = UnlockSecretKey.unlockSecretKey(decryptionKey, protector); + + PublicKeyDataDecryptorFactory decryptorFactory = ImplementationFactory.getInstance() + .getPublicKeyDataDecryptorFactory(privateKey); + try { + InputStream decrypted = pkesk.getDataStream(decryptorFactory); + InputStream decoder = PGPUtil.getDecoderStream(decrypted); + PGPObjectFactory objectFactory = ImplementationFactory.getInstance() + .getPGPObjectFactory(decoder); + objectFactory.nextObject(); + objectFactory.nextObject(); + objectFactory.nextObject(); + } catch (PGPException e) { + // hm :/ + } + } + } + private boolean processEncryptedData() throws IOException, PGPException { PGPEncryptedDataList encDataList = new PGPEncryptedDataList(packetInputStream); @@ -309,7 +333,8 @@ public class OpenPgpMessageInputStream extends InputStream { InputStream decrypted = pkesk.getDataStream(decryptorFactory); MessageMetadata.EncryptedData encryptedData = new MessageMetadata.EncryptedData( SymmetricKeyAlgorithm.requireFromId(pkesk.getSymmetricAlgorithm(decryptorFactory))); - nestedInputStream = new OpenPgpMessageInputStream(decrypted, options, encryptedData); + + nestedInputStream = new OpenPgpMessageInputStream(PGPUtil.getDecoderStream(decrypted), options, encryptedData); return true; } catch (PGPException e) { // hm :/ @@ -382,52 +407,12 @@ public class OpenPgpMessageInputStream extends InputStream { private PGPOnePassSignature readOnePassSignature() throws PGPException, IOException { - //return new PGPOnePassSignature(packetInputStream); - return null; + return new PGPOnePassSignature(packetInputStream); } private PGPSignature readSignature() throws PGPException, IOException { - //return new PGPSignature(packetInputStream); - return null; - } - - private PGPOnePassSignatureList readOnePassSignatures() throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - BCPGOutputStream bcpgOut = new BCPGOutputStream(buf); - int tag; - while ((tag = nextTag()) == PacketTags.ONE_PASS_SIGNATURE || tag == PacketTags.MARKER) { - Packet packet = packetInputStream.readPacket(); - if (tag == PacketTags.ONE_PASS_SIGNATURE) { - OnePassSignaturePacket sigPacket = (OnePassSignaturePacket) packet; - byte[] bytes = sigPacket.getEncoded(); - bcpgOut.write(bytes); - } - } - bcpgOut.close(); - - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(buf.toByteArray()); - PGPOnePassSignatureList signatureList = (PGPOnePassSignatureList) objectFactory.nextObject(); - return signatureList; - } - - private PGPSignatureList readSignatures() throws IOException { - ByteArrayOutputStream buf = new ByteArrayOutputStream(); - BCPGOutputStream bcpgOut = new BCPGOutputStream(buf); - int tag = nextTag(); - while (tag == PacketTags.SIGNATURE || tag == PacketTags.MARKER) { - Packet packet = packetInputStream.readPacket(); - if (tag == PacketTags.SIGNATURE) { - SignaturePacket sigPacket = (SignaturePacket) packet; - sigPacket.encode(bcpgOut); - tag = nextTag(); - } - } - bcpgOut.close(); - - PGPObjectFactory objectFactory = ImplementationFactory.getInstance().getPGPObjectFactory(buf.toByteArray()); - PGPSignatureList signatureList = (PGPSignatureList) objectFactory.nextObject(); - return signatureList; + return new PGPSignature(packetInputStream); } @Override @@ -446,12 +431,11 @@ public class OpenPgpMessageInputStream extends InputStream { boolean eos = r == -1; if (!eos) { byte b = (byte) r; - signatures.update(b); + signatures.updateLiteral(b); } else { nestedInputStream.close(); collectMetadata(); nestedInputStream = null; - signatures.popNested(); try { consumePackets(); @@ -473,11 +457,13 @@ public class OpenPgpMessageInputStream extends InputStream { } int r = nestedInputStream.read(b, off, len); - if (r == -1) { + if (r != -1) { + signatures.updateLiteral(b, off, r); + } + else { nestedInputStream.close(); collectMetadata(); nestedInputStream = null; - signatures.popNested(); try { consumePackets(); @@ -569,14 +555,11 @@ public class OpenPgpMessageInputStream extends InputStream { final ConsumerOptions options; final List detachedSignatures; final List prependedSignatures; - final List onePassSignatures; - final Stack> opsUpdateStack; + final List onePassSignatures; + final Stack> opsUpdateStack; + List literalOPS = new ArrayList<>(); final List correspondingSignatures; - ByteArrayOutputStream out = new ByteArrayOutputStream(); - - List opsCurrentNesting = new ArrayList<>(); - private Signatures(ConsumerOptions options) { this.options = options; this.detachedSignatures = new ArrayList<>(); @@ -608,57 +591,42 @@ public class OpenPgpMessageInputStream extends InputStream { void addOnePassSignature(PGPOnePassSignature signature) { PGPPublicKeyRing certificate = findCertificate(signature.getKeyID()); - initialize(signature, certificate); - onePassSignatures.add(signature); + OPS ops = new OPS(signature); + ops.init(certificate); + onePassSignatures.add(ops); - opsCurrentNesting.add(signature); - if (isContaining(signature)) { + literalOPS.add(ops); + if (signature.isContaining()) { commitNested(); } } - boolean isContaining(PGPOnePassSignature ops) { - try { - byte[] bytes = ops.getEncoded(); - return bytes[bytes.length - 1] == 1; - } catch (IOException e) { - return false; - } - } - void addCorrespondingOnePassSignature(PGPSignature signature) { - for (PGPOnePassSignature onePassSignature : onePassSignatures) { - if (onePassSignature.getKeyID() != signature.getKeyID()) { + for (int i = onePassSignatures.size() - 1; i >= 0; i--) { + OPS onePassSignature = onePassSignatures.get(i); + if (onePassSignature.signature.getKeyID() != signature.getKeyID()) { + continue; + } + if (onePassSignature.finished) { continue; } - boolean verified = false; - try { - verified = onePassSignature.verify(signature); - } catch (PGPException e) { - log("Cannot verify OPS signature.", e); - } - log("One-Pass-Signature by " + Long.toHexString(onePassSignature.getKeyID()) + " is " + (verified ? "verified" : "unverified")); - try { - log(ArmorUtils.toAsciiArmoredString(out.toByteArray())); - } catch (IOException e) { - throw new RuntimeException(e); - } + boolean verified = onePassSignature.verify(signature); + log("One-Pass-Signature by " + Long.toHexString(onePassSignature.signature.getKeyID()) + " is " + (verified ? "verified" : "unverified")); + System.out.println(onePassSignature); + break; } } void commitNested() { - if (opsCurrentNesting.isEmpty()) { - return; - } - - log("Committing " + opsCurrentNesting.size() + " OPS sigs for updating"); - opsUpdateStack.push(opsCurrentNesting); - opsCurrentNesting = new ArrayList<>(); + opsUpdateStack.push(literalOPS); + literalOPS = new ArrayList<>(); } void popNested() { - log("Popping nested"); + if (opsUpdateStack.isEmpty()) { + return; + } opsUpdateStack.pop(); } @@ -676,7 +644,7 @@ public class OpenPgpMessageInputStream extends InputStream { } } - private void initialize(PGPOnePassSignature ops, PGPPublicKeyRing certificate) { + private static void initialize(PGPOnePassSignature ops, PGPPublicKeyRing certificate) { if (certificate == null) { return; } @@ -699,20 +667,9 @@ public class OpenPgpMessageInputStream extends InputStream { return null; // TODO: Missing cert for sig } - public void update(byte b) { - if (!opsUpdateStack.isEmpty()) { - log("Update"); - out.write(b); - } - - for (PGPSignature prepended : prependedSignatures) { - prepended.update(b); - } - - for (List opss : opsUpdateStack) { - for (PGPOnePassSignature ops : opss) { - ops.update(b); - } + public void updateLiteral(byte b) { + for (OPS ops : literalOPS) { + ops.update(b); } for (PGPSignature detached : detachedSignatures) { @@ -720,6 +677,33 @@ public class OpenPgpMessageInputStream extends InputStream { } } + public void updateLiteral(byte[] b, int off, int len) { + for (OPS ops : literalOPS) { + ops.update(b, off, len); + } + + for (PGPSignature detached : detachedSignatures) { + detached.update(b, off, len); + } + } + + public void updatePacket(byte b) { + for (List nestedOPSs : opsUpdateStack) { + for (OPS ops : nestedOPSs) { + ops.update(b); + } + } + } + + public void updatePacket(byte[] buf, int off, int len) { + for (int i = opsUpdateStack.size() - 1; i >= 0; i--) { + List nestedOPSs = opsUpdateStack.get(i); + for (OPS ops : nestedOPSs) { + ops.update(buf, off, len); + } + } + } + public void finish() { for (PGPSignature detached : detachedSignatures) { boolean verified = false; @@ -743,8 +727,97 @@ public class OpenPgpMessageInputStream extends InputStream { } @Override - public void write(int b) throws IOException { - update((byte) b); + public void write(int b) { + updatePacket((byte) b); + } + + @Override + public void write(byte[] b, int off, int len) { + updatePacket(b, off, len); + } + + public void nextPacket(OpenPgpPacket nextPacket) { + if (nextPacket == OpenPgpPacket.LIT) { + if (literalOPS.isEmpty() && !opsUpdateStack.isEmpty()) { + literalOPS = opsUpdateStack.pop(); + } + } + } + + static class OPS { + ByteArrayOutputStream bytes = new ByteArrayOutputStream(); + PGPOnePassSignature signature; + boolean finished; + boolean valid; + + public OPS(PGPOnePassSignature signature) { + this.signature = signature; + } + + public void init(PGPPublicKeyRing certificate) { + initialize(signature, certificate); + } + + public boolean verify(PGPSignature signature) { + if (this.signature.getKeyID() != signature.getKeyID()) { + // nope + return false; + } + finished = true; + try { + valid = this.signature.verify(signature); + } catch (PGPException e) { + log("Cannot verify OPS " + signature.getKeyID()); + } + return valid; + } + + public void update(byte b) { + if (finished) { + log("Updating finished sig!"); + return; + } + signature.update(b); + bytes.write(b); + } + + public void update(byte[] bytes, int off, int len) { + if (finished) { + log("Updating finished sig!"); + return; + } + signature.update(bytes, off, len); + this.bytes.write(bytes, off, len); + } + + @Override + public String toString() { + String OPS = "c40d03000a01fbfcc82a015e733001"; + String LIT_H = "cb28620000000000"; + String LIT = "656e637279707420e28898207369676e20e28898207369676e20e28898207369676e"; + String SIG1 = "c2c10400010a006f058262c806350910fbfcc82a015e7330471400000000001e002073616c74406e6f746174696f6e732e736571756f69612d7067702e6f7267b0409ed8ea96dac66447bdff5b7b60c9f80a0ab91d257029153dc3b6d8c27b98162104d1a66e1a23b182c9980f788cfbfcc82a015e7330000029640c00846b5096d92474fd446cc7edaf9f14572cab93a80e12384c1e829f95debc6e8373c2ce5402be53dc1a18cf92a0ed909e0fb38855713ef8ffb13502ffac7c830fa254cc1aa6c666a97b0cc3bc176538f6913d3b8e8981a65cc42df10e0f39e4d0a06dfe961437b59a71892f4fca1116aed15123ea0d86a7b2ce47dd9d3ef22d920631bc011e82babe03ad5d72b3ba7f95bf646f20ccf6f7a4d95de37397c76c7d53741458e51ab6074007f61181c7b88b7c98f5b7510c8dfa3be01f4841501679478b15c5249d928e2a10d15ec63efa1500b994d5bfb32ffb174a976116930eb97a111e6dfd4c5e43e04a5d76ba74806a62fda63a8c3f53f6eebaf852892340e81dd08bbf348454a2cf525aeb512cf33aeeee78465ee4c442e41cc45ac4e3bb0c3333677aa60332ee7f464d9020f8554b82d619872477cca18d8431888f4ae8abe5894e9720f759c410cd7991db12703dc147040dd0d3758223e0b75de6ceae49c1a0c2c45efedeb7114ae785cc886afdc45c82172e4476e1ab5b86dc4314dd76"; + String SIG2 = "c2c10400010a006f058262c806350910fbfcc82a015e7330471400000000001e002073616c74406e6f746174696f6e732e736571756f69612d7067702e6f7267a4d9c117dc7ba3a7e9270856f128d2ab271743eac3cb5750b22a89bd5fd60753162104d1a66e1a23b182c9980f788cfbfcc82a015e73300000b8400bff796c20fa8b25ff7a42686338e06417a2966e85a0fc2723c928bef6cd19d34cf5e7d55ada33080613012dadb79e0278e59d9e7ed7d2d6102912a5f768c2e75b60099225c3d8bfe0c123240188b80dbee89b9b3bd5b13ccc662abc37e2129b6968adac9aba43aa778c0fe4fe337591ee87a96a29a013debc83555293c877144fc676aa1b03782c501949521a320adf6ad96c4f2e036b52a18369c637fdc49033696a84d03a69580b953187fce5aca6fb26fc8815da9f3b513bfe8e304f33ecb4b521aeb7d09c4a284ea66123bd0d6a358b2526d762ca110e1f7f20b3038d774b64d5cfd34e2213765828359d7afc5bf24d5270e99d80c3c1568fa01624b6ea1e9ce4e6890ce9bacf6611a45d41e2671f68f5b096446bf08d27ce75608425b2e3ab92146229ad1fcd8224aca5b5f73960506e7df07bfbf3664348e8ecbfb2eb467b9cfe412cb377a6ee2eb5fd11be9cf9208fe9a74c296f52cfa02a1eb0519ad9a8349bf6ccd6495feb7e391451bf96e08a0798883dee5974e47cbf3b51f111b6d3"; + String out = signature.getKeyID() + " last=" + signature.isContaining() + "\n"; + + String hex = Hex.toHexString(bytes.toByteArray()); + while (hex.contains(OPS)) { + hex = hex.replace(OPS, "[OPS]"); + } + while (hex.contains(LIT_H)) { + hex = hex.replace(LIT_H, "[LIT]"); + } + while (hex.contains(LIT)) { + hex = hex.replace(LIT, ""); + } + while (hex.contains(SIG1)) { + hex = hex.replace(SIG1, "[SIG1]"); + } + while (hex.contains(SIG2)) { + hex = hex.replace(SIG2, "[SIG2]"); + } + + return out + hex; + } } } diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java index ab24f22e..cce21051 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/TeeBCPGInputStream.java @@ -1,6 +1,7 @@ package org.pgpainless.decryption_verification; import org.bouncycastle.bcpg.BCPGInputStream; +import org.pgpainless.util.ArmorUtils; import java.io.IOException; import java.io.InputStream; diff --git a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/automaton/PDA.java b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/automaton/PDA.java index dda2adce..1d4b1189 100644 --- a/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/automaton/PDA.java +++ b/pgpainless-core/src/main/java/org/pgpainless/decryption_verification/automaton/PDA.java @@ -149,11 +149,19 @@ public class PDA { @Override State transition(InputAlphabet input, PDA 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); + if (input == InputAlphabet.EndOfSequence) { + if (stackItem == terminus && automaton.stack.isEmpty()) { + return Valid; + } else { + // premature end of stream + throw new MalformedOpenPgpMessageException(this, input, stackItem); + } + } else if (input == InputAlphabet.Signatures) { + if (stackItem == ops) { + return CorrespondingSignature; + } } + throw new MalformedOpenPgpMessageException(this, input, stackItem); } }, 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 e52a6016..f780b2eb 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 @@ -433,6 +433,151 @@ public class OpenPgpMessageInputStreamTest { assertNull(metadata.getCompressionAlgorithm()); } + @ParameterizedTest(name = "Process PENC(OPS OPS OPS LIT SIG SIG SIG) using {0}") + @MethodSource("provideMessageProcessors") + public void testProcessOPS_OPS_OPS_LIT_SIG_SIG_SIG(Processor processor) throws IOException, PGPException { + String KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" + + "Comment: Bob's OpenPGP Transferable Secret Key\n" + + "\n" + + "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\n" + + "=miES\n" + + "-----END PGP PRIVATE KEY BLOCK-----"; + String MSG = "-----BEGIN PGP MESSAGE-----\n" + + "\n" + + "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\n" + + "=JP9T\n" + + "-----END PGP MESSAGE-----"; + PGPSecretKeyRing secretKeys = PGPainless.readKeyRing().secretKeyRing(KEY); + PGPPublicKeyRing certificate = PGPainless.extractCertificate(secretKeys); + + Tuple result = processor.process(MSG, ConsumerOptions.get() + .addVerificationCert(certificate) + .addDecryptionKey(secretKeys)); + String plain = result.getA(); + assertEquals("encrypt ∘ sign ∘ sign ∘ sign", plain); + MessageMetadata metadata = result.getB(); + assertEquals(SymmetricKeyAlgorithm.AES_256, metadata.getEncryptionAlgorithm()); + assertNull(metadata.getCompressionAlgorithm()); + } + private static Tuple processReadBuffered(String armoredMessage, ConsumerOptions options) throws PGPException, IOException { OpenPgpMessageInputStream in = get(armoredMessage, options); diff --git a/version.gradle b/version.gradle index dc50f086..7d25632e 100644 --- a/version.gradle +++ b/version.gradle @@ -4,7 +4,7 @@ allprojects { ext { - shortVersion = '1.3.14' + shortVersion = '1.4.0' isSnapshot = true pgpainlessMinAndroidSdk = 10 javaSourceCompatibility = 1.8