Return-Path: X-Original-To: apmail-camel-commits-archive@www.apache.org Delivered-To: apmail-camel-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 02808102AA for ; Mon, 3 Mar 2014 08:27:25 +0000 (UTC) Received: (qmail 53402 invoked by uid 500); 3 Mar 2014 08:27:24 -0000 Delivered-To: apmail-camel-commits-archive@camel.apache.org Received: (qmail 53303 invoked by uid 500); 3 Mar 2014 08:27:16 -0000 Mailing-List: contact commits-help@camel.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@camel.apache.org Delivered-To: mailing list commits@camel.apache.org Received: (qmail 53294 invoked by uid 99); 3 Mar 2014 08:27:14 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 03 Mar 2014 08:27:14 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 0DCDF93332A; Mon, 3 Mar 2014 08:27:14 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: ningjiang@apache.org To: commits@camel.apache.org Message-Id: <8066928c35d046b992196c819c15f77d@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: git commit: CAMEL-7253 Fixed the NPE of PGPDataFormat if decryptor gets body with invalid format with thanks to Franz Date: Mon, 3 Mar 2014 08:27:14 +0000 (UTC) Repository: camel Updated Branches: refs/heads/camel-2.12.x 0018b70e6 -> 88780f154 CAMEL-7253 Fixed the NPE of PGPDataFormat if decryptor gets body with invalid format with thanks to Franz Project: http://git-wip-us.apache.org/repos/asf/camel/repo Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/88780f15 Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/88780f15 Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/88780f15 Branch: refs/heads/camel-2.12.x Commit: 88780f154e22aaf4885d466113a13f91f5968454 Parents: 0018b70 Author: Willem Jiang Authored: Mon Mar 3 16:26:56 2014 +0800 Committer: Willem Jiang Committed: Mon Mar 3 16:26:56 2014 +0800 ---------------------------------------------------------------------- .../camel/converter/crypto/PGPDataFormat.java | 44 ++- .../converter/crypto/PGPDataFormatTest.java | 267 ++++++++++++++++++- 2 files changed, 293 insertions(+), 18 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/camel/blob/88780f15/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormat.java ---------------------------------------------------------------------- diff --git a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormat.java b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormat.java index d7e92b2..5074cfe 100644 --- a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormat.java +++ b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPDataFormat.java @@ -401,19 +401,32 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { } InputStream in = PGPUtil.getDecoderStream(encryptedStream); PGPObjectFactory pgpFactory = new PGPObjectFactory(in); - Object o = pgpFactory.nextObject(); + Object firstObject = pgpFactory.nextObject(); // the first object might be a PGP marker packet PGPEncryptedDataList enc; - if (o instanceof PGPEncryptedDataList) { - enc = (PGPEncryptedDataList) o; + if (firstObject instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) firstObject; } else { - enc = (PGPEncryptedDataList) pgpFactory.nextObject(); + Object secondObject = pgpFactory.nextObject(); + if (secondObject instanceof PGPEncryptedDataList) { + enc = (PGPEncryptedDataList) pgpFactory.nextObject(); + } else { + enc = null; + } + } + + if (enc == null) { + throw getFormatException(); } PGPPublicKeyEncryptedData pbe = null; PGPPrivateKey key = null; // find encrypted data for which a private key exists in the secret key ring for (int i = 0; i < enc.size() && key == null; i++) { + Object encryptedData = enc.get(i); + if (!(encryptedData instanceof PGPPublicKeyEncryptedData)) { + throw getFormatException(); + } pbe = (PGPPublicKeyEncryptedData) enc.get(i); key = PGPDataFormatUtil.findPrivateKeyWithKeyId(exchange.getContext(), findKeyFileName(exchange), findEncryptionKeyRing(exchange), pbe.getKeyID(), findKeyPassword(exchange), getPassphraseAccessor(), getProvider()); @@ -423,12 +436,16 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { } } if (key == null) { - throw new PGPException("Provided input is encrypted with unknown pair of keys."); + throw new PGPException("Message is encrypted with a key which could not be found in the Secret Key Ring."); } InputStream encData = pbe.getDataStream(new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(getProvider()).build(key)); pgpFactory = new PGPObjectFactory(encData); - PGPCompressedData comData = (PGPCompressedData) pgpFactory.nextObject(); + Object compObj = pgpFactory.nextObject(); + if (!(compObj instanceof PGPCompressedData)) { + throw getFormatException(); + } + PGPCompressedData comData = (PGPCompressedData)compObj; pgpFactory = new PGPObjectFactory(comData.getDataStream()); Object object = pgpFactory.nextObject(); @@ -440,7 +457,13 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { signature = null; } - PGPLiteralData ld = (PGPLiteralData) object; + PGPLiteralData ld; + if (object instanceof PGPLiteralData) { + ld = (PGPLiteralData) object; + } else { + throw getFormatException(); + } + InputStream litData = ld.getInputStream(); // enable streaming via OutputStreamCache @@ -485,6 +508,13 @@ public class PGPDataFormat extends ServiceSupport implements DataFormat { } } + private IllegalArgumentException getFormatException() { + return new IllegalArgumentException("The input message body has an invalid format. The PGP decryption/verification processor expects a sequence of PGP packets of the form " + + "(entries in brackets are optional and ellipses indicate repetition, comma represents sequential composition, and vertical bar separates alternatives): " + + "Public Key Encrypted Session Key ..., Symmetrically Encrypted Data | Sym. Encrypted and Integrity Protected Data, Compressed Data, (One Pass Signature ...,) " + + "Literal Data, (Signature ...,)"); + } + protected PGPSignature getSignatureWithKeyId(long keyID, PGPSignatureList sigList) { for (int i = 0; i < sigList.size(); i++) { PGPSignature signature = sigList.get(i); http://git-wip-us.apache.org/repos/asf/camel/blob/88780f15/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatTest.java ---------------------------------------------------------------------- diff --git a/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatTest.java b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatTest.java index b017a86..4231e43 100644 --- a/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatTest.java +++ b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/PGPDataFormatTest.java @@ -16,13 +16,21 @@ */ package org.apache.camel.converter.crypto; +import java.io.BufferedOutputStream; +import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; +import java.io.PrintWriter; +import java.io.UnsupportedEncodingException; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; @@ -31,14 +39,36 @@ import org.apache.camel.Message; import org.apache.camel.builder.RouteBuilder; import org.apache.camel.component.mock.MockEndpoint; import org.apache.camel.util.IOHelper; +import org.bouncycastle.bcpg.BCPGOutputStream; import org.bouncycastle.bcpg.CompressionAlgorithmTags; import org.bouncycastle.bcpg.HashAlgorithmTags; import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags; import org.bouncycastle.bcpg.sig.KeyFlags; +import org.bouncycastle.openpgp.PGPCompressedDataGenerator; +import org.bouncycastle.openpgp.PGPEncryptedDataGenerator; +import org.bouncycastle.openpgp.PGPException; +import org.bouncycastle.openpgp.PGPLiteralData; +import org.bouncycastle.openpgp.PGPLiteralDataGenerator; +import org.bouncycastle.openpgp.PGPPrivateKey; +import org.bouncycastle.openpgp.PGPPublicKey; +import org.bouncycastle.openpgp.PGPPublicKeyRing; +import org.bouncycastle.openpgp.PGPPublicKeyRingCollection; +import org.bouncycastle.openpgp.PGPSecretKey; +import org.bouncycastle.openpgp.PGPSecretKeyRing; +import org.bouncycastle.openpgp.PGPSecretKeyRingCollection; +import org.bouncycastle.openpgp.PGPSignature; +import org.bouncycastle.openpgp.PGPSignatureGenerator; +import org.bouncycastle.openpgp.PGPUtil; +import org.bouncycastle.openpgp.operator.jcajce.JcaPGPContentSignerBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePBEKeyEncryptionMethodGenerator; +import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder; +import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator; import org.junit.Test; public class PGPDataFormatTest extends AbstractPGPDataFormatTest { + private static final String PUB_KEY_RING_SUBKEYS_FILE_NAME = "org/apache/camel/component/crypto/pubringSubKeys.gpg"; private static final String SEC_KEY_RING_FILE_NAME = "org/apache/camel/component/crypto/secring.gpg"; private static final String PUB_KEY_RING_FILE_NAME = "org/apache/camel/component/crypto/pubring.gpg"; @@ -138,10 +168,7 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { template.sendBodyAndHeaders("direct:verify_exception_sig_userids", payload, headers); assertMockEndpointsSatisfied(); - //check exception text - Exception e = (Exception) exception.getExchanges().get(0).getProperty(Exchange.EXCEPTION_CAUGHT); - assertNotNull("Expected excpetion missing", e); - assertTrue(e.getMessage().contains("No public key found fitting to the signature key Id")); + checkThrownException(exception, IllegalArgumentException.class, null, "No public key found fitting to the signature key Id"); } @@ -158,10 +185,7 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { template.sendBodyAndHeaders("direct:several-signer-keys", payload, headers); assertMockEndpointsSatisfied(); - //check exception text - Exception e = (Exception) exception.getExchanges().get(0).getProperty(Exchange.EXCEPTION_CAUGHT); - assertNotNull("Expected excpetion missing", e); - assertTrue(e.getMessage().contains("No passphrase specified for signature key user ID")); + checkThrownException(exception, IllegalArgumentException.class, null, "No passphrase specified for signature key user ID"); } @@ -191,11 +215,182 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { assertEquals(1, inMess.getHeader(PGPDataFormat.NUMBER_OF_SIGNING_KEYS)); } + + @Test + public void testExceptionDecryptorIncorrectInputFormatNoPGPMessage() throws Exception { + String payload = "Not Correct Format"; + MockEndpoint mock = getMockEndpoint("mock:exception"); + mock.expectedMessageCount(1); + template.sendBody("direct:subkeyUnmarshal", payload); + assertMockEndpointsSatisfied(); + + checkThrownException(mock, IllegalArgumentException.class, null, "The input message body has an invalid format."); + } + + @Test + public void testExceptionDecryptorIncorrectInputFormatPGPSignedData() throws Exception { + + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + createSignature(bos); + MockEndpoint mock = getMockEndpoint("mock:exception"); + mock.expectedMessageCount(1); + template.sendBody("direct:subkeyUnmarshal", bos.toByteArray()); + assertMockEndpointsSatisfied(); + + checkThrownException(mock, IllegalArgumentException.class, null, "The input message body has an invalid format."); + } + + @Test + public void testExceptionDecryptorIncorrectInputNoCompression() throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + createEncryptedNonCompressedData(bos, PUB_KEY_RING_SUBKEYS_FILE_NAME); + + MockEndpoint mock = getMockEndpoint("mock:exception"); + mock.expectedMessageCount(1); + template.sendBody("direct:subkeyUnmarshal", bos.toByteArray()); + assertMockEndpointsSatisfied(); + + checkThrownException(mock, IllegalArgumentException.class, null, "The input message body has an invalid format."); + } + + @Test + public void testExceptionDecryptorNoKeyFound() throws Exception { + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + createEncryptedNonCompressedData(bos, PUB_KEY_RING_FILE_NAME); + + MockEndpoint mock = getMockEndpoint("mock:exception"); + mock.expectedMessageCount(1); + template.sendBody("direct:subkeyUnmarshal", bos.toByteArray()); + assertMockEndpointsSatisfied(); + + checkThrownException(mock, PGPException.class, null, + "Message is encrypted with a key which could not be found in the Secret Key Ring"); + } + + void createEncryptedNonCompressedData(ByteArrayOutputStream bos, String keyringPath) throws Exception, IOException, PGPException, + UnsupportedEncodingException { + PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5) + .setSecureRandom(new SecureRandom()).setProvider(getProvider())); + encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(readPublicKey(keyringPath))); + OutputStream encOut = encGen.open(bos, new byte[512]); + PGPLiteralDataGenerator litData = new PGPLiteralDataGenerator(); + OutputStream litOut = litData.open(encOut, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, new Date(), new byte[512]); + + try { + litOut.write("Test Message Without Compression".getBytes("UTF-8")); + litOut.flush(); + } finally { + IOHelper.close(litOut); + IOHelper.close(encOut, bos); + } + } + + private void createSignature(OutputStream out) throws Exception { + PGPSecretKey pgpSec = readSecretKey(); + PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder().setProvider(getProvider()).build( + "sdude".toCharArray())); + PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(pgpSec.getPublicKey().getAlgorithm(), + PGPUtil.SHA1).setProvider(getProvider())); + + sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey); + + BCPGOutputStream bOut = new BCPGOutputStream(out); + + InputStream fIn = new ByteArrayInputStream("Test Signature".getBytes("UTF-8")); + + int ch; + while ((ch = fIn.read()) >= 0) { + sGen.update((byte) ch); + } + + fIn.close(); + + sGen.generate().encode(bOut); + + } + + static PGPSecretKey readSecretKey() throws Exception { + InputStream input = new ByteArrayInputStream(getSecKeyRing()); + PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(input)); + + @SuppressWarnings("rawtypes") + Iterator keyRingIter = pgpSec.getKeyRings(); + while (keyRingIter.hasNext()) { + PGPSecretKeyRing keyRing = (PGPSecretKeyRing) keyRingIter.next(); + + @SuppressWarnings("rawtypes") + Iterator keyIter = keyRing.getSecretKeys(); + while (keyIter.hasNext()) { + PGPSecretKey key = (PGPSecretKey) keyIter.next(); + + if (key.isSigningKey()) { + return key; + } + } + } + + throw new IllegalArgumentException("Can't find signing key in key ring."); + } + + static PGPPublicKey readPublicKey(String keyringPath) throws Exception { + InputStream input = new ByteArrayInputStream(getKeyRing(keyringPath)); + PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(input)); + + @SuppressWarnings("rawtypes") + Iterator keyRingIter = pgpPub.getKeyRings(); + while (keyRingIter.hasNext()) { + PGPPublicKeyRing keyRing = (PGPPublicKeyRing) keyRingIter.next(); + + @SuppressWarnings("rawtypes") + Iterator keyIter = keyRing.getPublicKeys(); + while (keyIter.hasNext()) { + PGPPublicKey key = (PGPPublicKey) keyIter.next(); + + if (key.isEncryptionKey()) { + return key; + } + } + } + + throw new IllegalArgumentException("Can't find encryption key in key ring."); + } + + @Test + public void testExceptionDecryptorIncorrectInputFormatSymmetricEncryptedData() throws Exception { + + byte[] payload = "Not Correct Format".getBytes("UTF-8"); + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + + PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.CAST5) + .setSecureRandom(new SecureRandom()).setProvider(getProvider())); + + encGen.addMethod(new JcePBEKeyEncryptionMethodGenerator("pw".toCharArray())); + + OutputStream encOut = encGen.open(bos, new byte[1024]); + PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP); + OutputStream comOut = new BufferedOutputStream(comData.open(encOut)); + PGPLiteralDataGenerator litData = new PGPLiteralDataGenerator(); + OutputStream litOut = litData.open(comOut, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, new Date(), new byte[1024]); + litOut.write(payload); + litOut.flush(); + litOut.close(); + comOut.close(); + encOut.close(); + MockEndpoint mock = getMockEndpoint("mock:exception"); + mock.expectedMessageCount(1); + template.sendBody("direct:subkeyUnmarshal", bos.toByteArray()); + assertMockEndpointsSatisfied(); + + checkThrownException(mock, IllegalArgumentException.class, null, "The input message body has an invalid format."); + } + protected RouteBuilder[] createRouteBuilders() { return new RouteBuilder[] {new RouteBuilder() { public void configure() throws Exception { - onException(IllegalArgumentException.class).handled(true).to("mock:exception"); + onException(Exception.class).handled(true).to("mock:exception"); // START SNIPPET: pgp-format // Public Key FileName @@ -379,16 +574,29 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { }, new RouteBuilder() { public void configure() throws Exception { - // keyflag test + onException(Exception.class).handled(true).to("mock:exception"); + // keyflag test PGPDataFormat pgpKeyFlag = new PGPDataFormat(); // the following keyring contains a primary key with KeyFlag "Certify" and a subkey for signing and a subkey for encryption - pgpKeyFlag.setKeyFileName("org/apache/camel/component/crypto/pubringSubKeys.gpg"); + pgpKeyFlag.setKeyFileName(PUB_KEY_RING_SUBKEYS_FILE_NAME); pgpKeyFlag.setSignatureKeyFileName("org/apache/camel/component/crypto/secringSubKeys.gpg"); pgpKeyFlag.setSignaturePassword("Abcd1234"); pgpKeyFlag.setKeyUserid("keyflag"); pgpKeyFlag.setSignatureKeyUserid("keyflag"); from("direct:keyflag").marshal(pgpKeyFlag).to("mock:encrypted_keyflag"); + + PGPDataFormat pgpDecryptVerifySubkey = new PGPDataFormat(); + // the following keyring contains a primary key with KeyFlag "Certify" and a subkey for signing and a subkey for encryption + pgpDecryptVerifySubkey.setKeyFileName("org/apache/camel/component/crypto/secringSubKeys.gpg"); + pgpDecryptVerifySubkey.setSignatureKeyFileName(PUB_KEY_RING_SUBKEYS_FILE_NAME); + pgpDecryptVerifySubkey.setPassword("Abcd1234"); + pgpDecryptVerifySubkey.setSignatureKeyUserid("keyflag"); + + // test that the correct subkey is selected during decrypt and verify + //from("direct:subkey").marshal(pgpKeyFlag).to("mock:encrypted").unmarshal(pgpDecryptVerifySubkey).to("mock:unencrypted"); + + from("direct:subkeyUnmarshal").unmarshal(pgpDecryptVerifySubkey).to("mock:unencrypted"); } } }; } @@ -415,4 +623,41 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest { return passphraseAccessor; } + public static void checkThrownException(MockEndpoint mock, Class cl, + Class expectedCauseClass, String expectedMessagePart) throws Exception { + Exception e = (Exception) mock.getExchanges().get(0).getProperty(Exchange.EXCEPTION_CAUGHT); + assertNotNull("Expected excpetion " + cl.getName() + " missing", e); + if (e.getClass() != cl) { + String stackTrace = getStrackTrace(e); + fail("Exception " + cl.getName() + " excpected, but was " + e.getClass().getName() + ": " + stackTrace); + } + if (expectedMessagePart != null) { + if (e.getMessage() == null) { + fail("Expected excption does not contain a message. Stack trace: " + getStrackTrace(e)); + } else { + if (!e.getMessage().contains(expectedMessagePart)) { + fail("Expected excption message does not contain a expected message part " + expectedMessagePart + ". Stack trace: " + + getStrackTrace(e)); + } + } + } + if (expectedCauseClass != null) { + Throwable cause = e.getCause(); + assertNotNull("Expected cause exception" + expectedCauseClass.getName() + " missing", cause); + if (expectedCauseClass != cause.getClass()) { + fail("Cause exception " + expectedCauseClass.getName() + " expected, but was " + cause.getClass().getName() + ": " + + getStrackTrace(e)); + } + } + } + + public static String getStrackTrace(Exception e) throws UnsupportedEncodingException { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + PrintWriter w = new PrintWriter(os); + e.printStackTrace(w); + w.close(); + String stackTrace = new String(os.toByteArray(), "UTF-8"); + return stackTrace; + } + }