You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ni...@apache.org on 2014/03/03 02:50:50 UTC

git commit: CAMEL-7253 Fixed the NPE of PGPDataFormat if decryptor gets body with invalid format with thanks to Franz

Repository: camel
Updated Branches:
  refs/heads/master cc54cb49d -> 9ea05aae6


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/9ea05aae
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/9ea05aae
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/9ea05aae

Branch: refs/heads/master
Commit: 9ea05aae64256ad652235865ad6c0f31a00e682a
Parents: cc54cb4
Author: Willem Jiang <wi...@gmail.com>
Authored: Mon Mar 3 09:50:35 2014 +0800
Committer: Willem Jiang <wi...@gmail.com>
Committed: Mon Mar 3 09:50:35 2014 +0800

----------------------------------------------------------------------
 .../crypto/PGPKeyAccessDataFormat.java          |  47 +++-
 .../converter/crypto/PGPDataFormatTest.java     | 279 +++++++++++++++++--
 2 files changed, 299 insertions(+), 27 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/9ea05aae/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPKeyAccessDataFormat.java
----------------------------------------------------------------------
diff --git a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPKeyAccessDataFormat.java b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPKeyAccessDataFormat.java
index a0762c8..92663c4 100644
--- a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPKeyAccessDataFormat.java
+++ b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/PGPKeyAccessDataFormat.java
@@ -309,20 +309,33 @@ public class PGPKeyAccessDataFormat 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)secondObject;
+            } 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++) {
-            pbe = (PGPPublicKeyEncryptedData) enc.get(i);
+            Object encryptedData = enc.get(i);
+            if (!(encryptedData instanceof PGPPublicKeyEncryptedData)) {
+                throw getFormatException();
+            }
+            pbe = (PGPPublicKeyEncryptedData) encryptedData;
             key = secretKeyAccessor.getPrivateKey(exchange, pbe.getKeyID());
             if (key != null) {
                 // take the first key
@@ -330,12 +343,16 @@ public class PGPKeyAccessDataFormat 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();
 
@@ -347,7 +364,12 @@ public class PGPKeyAccessDataFormat 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
@@ -392,6 +414,13 @@ public class PGPKeyAccessDataFormat 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);
@@ -419,7 +448,7 @@ public class PGPKeyAccessDataFormat extends ServiceSupport implements DataFormat
         if (signatureList.isEmpty()) {
             return null;
         } else {
-            throw new IllegalArgumentException("No public key found fitting to the signature key Id; cannot verify the signature");
+            throw new IllegalArgumentException("No public key found fitting to the signature key Id; cannot verify the signature.");
         }
 
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/9ea05aae/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 f99746b..5237710 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";
 
@@ -143,10 +173,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");
 
     }
 
@@ -163,11 +190,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");
     }
 
     /**
@@ -195,7 +218,7 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest {
         assertEquals(1, inMess.getHeader(PGPDataFormat.NUMBER_OF_ENCRYPTION_KEYS));
         assertEquals(1, inMess.getHeader(PGPDataFormat.NUMBER_OF_SIGNING_KEYS));
     }
-    
+
     /**
      * You get three keys with the UserId "keyflag", a primary key and its two
      * sub-keys. The sub-key with KeyFlag {@link KeyFlags#SIGN_DATA} should be
@@ -204,10 +227,11 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest {
      * {@link KeyFlags#ENCRYPT_STORAGE} should be used for decryption.
      * <p>
      * Tests also the decryption and verifying part with the subkeys.
+     * 
      * @throws Exception
      */
     @Test
-    public void testDecryptVerifyWithSubkey() throws Exception {       
+    public void testDecryptVerifyWithSubkey() throws Exception {
         // do not use doRoundTripEncryptionTests("direct:subkey"); because otherwise you get an error in the dynamic test
         String payload = "Test Message";
         MockEndpoint mockSubkey = getMockEndpoint("mock:unencrypted");
@@ -216,11 +240,190 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest {
         assertMockEndpointsSatisfied();
     }
 
+    @Test
+    public void testEmptyBody() throws Exception {
+        String payload = "";
+        MockEndpoint mockSubkey = getMockEndpoint("mock:unencrypted");
+        mockSubkey.expectedBodiesReceived(payload);
+        template.sendBody("direct:subkey", payload);
+        assertMockEndpointsSatisfied();
+    }
+
+    @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
@@ -404,27 +607,30 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest {
 
         }, new RouteBuilder() {
             public void configure() throws Exception {
+
+                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("org/apache/camel/component/crypto/pubringSubKeys.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:subkey").marshal(pgpKeyFlag).to("mock:encrypted").unmarshal(pgpDecryptVerifySubkey).to("mock:unencrypted");
+
+                from("direct:subkeyUnmarshal").unmarshal(pgpDecryptVerifySubkey).to("mock:unencrypted");
             }
         }, new RouteBuilder() {
             public void configure() throws Exception {
@@ -473,4 +679,41 @@ public class PGPDataFormatTest extends AbstractPGPDataFormatTest {
         return passphraseAccessor;
     }
 
+    public static void checkThrownException(MockEndpoint mock, Class<? extends Exception> cl,
+            Class<? extends Exception> 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;
+    }
+
 }