You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by ac...@apache.org on 2018/08/24 06:47:37 UTC

[camel] branch master updated: CAMEL-12605 Added encrypt/decrypt logic for enveloped entities

This is an automated email from the ASF dual-hosted git repository.

acosentino pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/master by this push:
     new 15e5072  CAMEL-12605 Added encrypt/decrypt logic for enveloped entities
15e5072 is described below

commit 15e507209bef47df6ccaf13e14427827952ff66b
Author: William Collins <pu...@gmail.com>
AuthorDate: Thu Aug 23 11:43:24 2018 -0400

    CAMEL-12605 Added encrypt/decrypt logic for enveloped entities
---
 .../camel/component/as2/api/AS2ClientManager.java  |  16 +++
 .../component/as2/api/AS2SignedDataGenerator.java  |   1 +
 ...s7Mime.java => ApplicationPkcs7MimeEntity.java} |  39 ++++++--
 .../component/as2/api/entity/EntityParser.java     | 107 +++++++++++++++++++++
 .../camel/component/as2/api/util/AS2Utils.java     |   6 +-
 5 files changed, 160 insertions(+), 9 deletions(-)

diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientManager.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientManager.java
index e081289..4e22e7b 100644
--- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientManager.java
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientManager.java
@@ -275,4 +275,20 @@ public class AS2ClientManager {
 
     }
 
+    public AS2SignedDataGenerator createEncryptingGenerator(HttpCoreContext httpContext) throws HttpException {
+
+        Certificate[] certificateChain = httpContext.getAttribute(SIGNING_CERTIFICATE_CHAIN, Certificate[].class);
+        if (certificateChain == null) {
+            throw new HttpException("Signing certificate chain missing");
+        }
+
+        PrivateKey privateKey = httpContext.getAttribute(SIGNING_PRIVATE_KEY, PrivateKey.class);
+        if (privateKey == null) {
+            throw new HttpException("Signing private key missing");
+        }
+
+        return SigningUtils.createSigningGenerator(certificateChain, privateKey);
+
+    }
+
 }
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2SignedDataGenerator.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2SignedDataGenerator.java
index f52429a..1ab44c0 100644
--- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2SignedDataGenerator.java
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2SignedDataGenerator.java
@@ -108,6 +108,7 @@ public class AS2SignedDataGenerator extends CMSSignedDataGenerator {
     /**
      * Creates a <code>multipart/signed</code> content type containing the algorithms used by this generator.
      *
+     * @param boundary - boundary to use to demarcate content.
      * @return A <code>multipart/signed</code> content type
      */
     public ContentType createMultipartSignedContentType(String boundary) {
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationPkcs7Mime.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationPkcs7MimeEntity.java
similarity index 72%
rename from components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationPkcs7Mime.java
rename to components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationPkcs7MimeEntity.java
index 88d70b6..b61b440 100644
--- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationPkcs7Mime.java
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/ApplicationPkcs7MimeEntity.java
@@ -16,38 +16,50 @@
  */
 package org.apache.camel.component.as2.api.entity;
 
+import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.io.OutputStream;
+import java.security.PrivateKey;
+import java.util.Collection;
+import java.util.Iterator;
 
 import org.apache.camel.component.as2.api.AS2Charset;
 import org.apache.camel.component.as2.api.AS2Header;
 import org.apache.camel.component.as2.api.CanonicalOutputStream;
+import org.apache.camel.component.as2.api.io.AS2SessionInputBuffer;
+import org.apache.camel.component.as2.api.util.EntityUtils;
 import org.apache.http.Header;
 import org.apache.http.HeaderIterator;
 import org.apache.http.HttpException;
 import org.apache.http.entity.ContentType;
+import org.apache.http.impl.io.HttpTransportMetricsImpl;
 import org.apache.http.message.BasicNameValuePair;
 import org.apache.http.util.Args;
 import org.bouncycastle.cms.CMSEnvelopedData;
 import org.bouncycastle.cms.CMSEnvelopedDataGenerator;
 import org.bouncycastle.cms.CMSProcessableByteArray;
 import org.bouncycastle.cms.CMSTypedData;
+import org.bouncycastle.cms.Recipient;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
 import org.bouncycastle.operator.OutputEncryptor;
 
-public class ApplicationPkcs7Mime extends MimeEntity {
+public class ApplicationPkcs7MimeEntity extends MimeEntity {
     
     private static final String CONTENT_DISPOSITION = "attachment; filename=\"smime.p7m\"";
     
     private byte[] encryptedData;
     
-    public ApplicationPkcs7Mime(MimeEntity entity2Encrypt,
+    public ApplicationPkcs7MimeEntity(MimeEntity entity2Encrypt,
                                 CMSEnvelopedDataGenerator dataGenerator,
                                 OutputEncryptor encryptor,
                                 String encryptedContentTransferEncoding,
                                 boolean isMainBody)
             throws HttpException {
-        setContentType(ContentType.create("application/pkcs7-mime", new BasicNameValuePair("smime-type", "enveloped-datat"),
+        setContentType(ContentType.create("application/pkcs7-mime", new BasicNameValuePair("smime-type", "enveloped-data"),
                 new BasicNameValuePair("name", "smime.p7m")));
         setContentTransferEncoding(encryptedContentTransferEncoding);
         addHeader(AS2Header.CONTENT_DISPOSITION, CONTENT_DISPOSITION);
@@ -59,7 +71,7 @@ public class ApplicationPkcs7Mime extends MimeEntity {
         }
     }
     
-    public ApplicationPkcs7Mime(byte[] encryptedData, String encryptedContentTransferEncoding, boolean isMainBody) {
+    public ApplicationPkcs7MimeEntity(byte[] encryptedData, String encryptedContentTransferEncoding, boolean isMainBody) {
         this.encryptedData = Args.notNull(encryptedData, "encryptedData");
         
         setContentType(ContentType.create("application/pkcs7-mime", new BasicNameValuePair("smime-type", "enveloped-datat"),
@@ -88,15 +100,30 @@ public class ApplicationPkcs7Mime extends MimeEntity {
             }
         }
         
+        // Write out signed data.
+        String transferEncoding = getContentTransferEncoding() == null ? null : getContentTransferEncoding().getValue();
+        try (OutputStream transferEncodedStream = EntityUtils.encode(ncos, transferEncoding)) {
+
+            transferEncodedStream.write(encryptedData);
+        } catch (Exception e) {
+            throw new IOException("Failed to write to output stream", e);
+        }
+    }
+    
+    public MimeEntity getEncryptedEntity(PrivateKey privateKey) {
+        
+        return EntityParser.parseEnvelopedEntity(encryptedData, privateKey);
+        
+        
     }
     
-    private byte[] createEncryptedData(MimeEntity entity2Encrypt, CMSEnvelopedDataGenerator dataGenerator, OutputEncryptor encryptor) throws Exception {
+    private byte[] createEncryptedData(MimeEntity entity2Encrypt, CMSEnvelopedDataGenerator envelopedDataGenerator, OutputEncryptor encryptor) throws Exception {
         try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
             entity2Encrypt.writeTo(bos);
             bos.flush();
 
             CMSTypedData contentData = new CMSProcessableByteArray(bos.toByteArray());
-            CMSEnvelopedData  envelopedData = dataGenerator.generate(contentData, encryptor);
+            CMSEnvelopedData  envelopedData = envelopedDataGenerator.generate(contentData, encryptor);
             return envelopedData.getEncoded();
         } catch (Exception e) {
             throw new Exception("", e);
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java
index 4e86d14..b2c3f46 100644
--- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/EntityParser.java
@@ -16,10 +16,15 @@
  */
 package org.apache.camel.component.as2.api.entity;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.io.InputStream;
 import java.nio.charset.Charset;
 import java.nio.charset.CharsetDecoder;
+import java.security.PrivateKey;
 import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
 import java.util.List;
 
 import org.apache.camel.component.as2.api.AS2Charset;
@@ -44,6 +49,11 @@ import org.apache.http.message.LineParser;
 import org.apache.http.message.ParserCursor;
 import org.apache.http.util.Args;
 import org.apache.http.util.CharArrayBuffer;
+import org.bouncycastle.cms.CMSEnvelopedData;
+import org.bouncycastle.cms.Recipient;
+import org.bouncycastle.cms.RecipientInformation;
+import org.bouncycastle.cms.RecipientInformationStore;
+import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -164,7 +174,71 @@ public final class EntityParser {
         }
 
     }
+    
+    public static MimeEntity parseEnvelopedEntity(byte[] envelopedContent, PrivateKey privateKey) {
+        
+        try {
+            byte[] decryptedContent = decryptData(envelopedContent, privateKey);
+            
+            InputStream is = new ByteArrayInputStream(decryptedContent);
+            AS2SessionInputBuffer inbuffer = new AS2SessionInputBuffer(new HttpTransportMetricsImpl(), DEFAULT_BUFFER_SIZE);
+            inbuffer.bind(is);
+
+            // Read Text Report Body Part Headers
+            Header[] headers = AbstractMessageParser.parseHeaders(inbuffer, -1, -1, BasicLineParser.INSTANCE,
+                    new ArrayList<CharArrayBuffer>());
+
+            // Get Content-Type and Content-Transfer-Encoding
+            ContentType envelopedEntityContentType = null;
+            String envelopedEntityContentTransferEncoding = null;
+            for (Header header : headers) {
+                switch (header.getName()) {
+                case AS2Header.CONTENT_TYPE:
+                    envelopedEntityContentType = ContentType.parse(header.getValue());
+                    break;
+                case AS2Header.CONTENT_TRANSFER_ENCODING:
+                    envelopedEntityContentTransferEncoding = header.getValue();
+                    break;
+                default:
+                    continue;
+                }
+            }
+            if (envelopedEntityContentType == null) {
+                throw new HttpException("Failed to find Content-Type header in enveloped entity");
+            }
+
+            MimeEntity entity = parseEntityBody(inbuffer, null, envelopedEntityContentType, envelopedEntityContentTransferEncoding, headers);
+            entity.removeAllHeaders();
+            entity.setHeaders(headers);
+            
+            return entity;
+        } catch (Exception e) {
+            return null;
+        }
+    }
 
+    public static byte[] decryptData(byte[] encryptedData, PrivateKey privateKey) throws Exception {
+        // Create enveloped data from encrypted data
+        CMSEnvelopedData cmsEnvelopedData = new CMSEnvelopedData(encryptedData);
+        
+        // Extract recipient information form enveloped data.
+        RecipientInformationStore recipientsInformationStore = cmsEnvelopedData.getRecipientInfos();
+        Collection<RecipientInformation> recipients = recipientsInformationStore.getRecipients();
+        Iterator<RecipientInformation> it = recipients.iterator();
+        
+        // Decrypt if enveloped data contains recipient information
+        if (it.hasNext()) {
+            // Create recipient from private key.
+            Recipient recipient = new JceKeyTransEnvelopedRecipient(privateKey);
+
+            // Extract decrypted data from recipient information
+            RecipientInformation recipientInfo = it.next();
+            return recipientInfo.getContent(recipient);
+        }
+        
+        return null;
+    }
+    
     public static void parseMultipartSignedEntity(HttpMessage message)
             throws HttpException {
         MultipartSignedEntity multipartSignedEntity = null;
@@ -763,6 +837,39 @@ public final class EntityParser {
         }
     }
 
+    public static ApplicationPkcs7MimeEntity parseApplicationPkcs7MimeEntityBody(AS2SessionInputBuffer inbuffer,
+                                                                                      String boundary,
+                                                                                      ContentType contentType,
+                                                                                      String contentTransferEncoding)
+            throws ParseException {
+
+        CharsetDecoder previousDecoder = inbuffer.getCharsetDecoder();
+
+        try {
+            Charset charset = contentType.getCharset();
+            if (charset == null) {
+                charset = Charset.forName(AS2Charset.US_ASCII);
+            }
+            CharsetDecoder charsetDecoder = charset.newDecoder();
+
+            inbuffer.setCharsetDecoder(charsetDecoder);
+
+            String pkcs7EncryptedBodyContent = parseBodyPartText(inbuffer, boundary);
+
+            byte[] encryptedContent = EntityUtils.decode(pkcs7EncryptedBodyContent.getBytes(charset), contentTransferEncoding);
+
+            ApplicationPkcs7MimeEntity applicationPkcs7MimeEntity = new ApplicationPkcs7MimeEntity(
+                    encryptedContent, contentTransferEncoding, false);
+            return applicationPkcs7MimeEntity;
+        } catch (Exception e) {
+            ParseException parseException = new ParseException("failed to parse PKCS7 Mime entity");
+            parseException.initCause(e);
+            throw parseException;
+        } finally {
+            inbuffer.setCharsetDecoder(previousDecoder);
+        }
+    }
+
     public static String parseBodyPartText(final AS2SessionInputBuffer inbuffer,
                                            final String boundary)
             throws IOException {
diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2Utils.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2Utils.java
index d4c3161..00482e5 100644
--- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2Utils.java
+++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/AS2Utils.java
@@ -62,7 +62,7 @@ public final class AS2Utils {
      * Validates if the given <code>name</code> is a valid AS2 Name
      *
      * @param name - the name to validate.
-     * @throws InvalidAS2NameException
+     * @throws InvalidAS2NameException - If <code>name</code> is invalid.
      */
     public static void validateAS2Name(String name) throws InvalidAS2NameException {
         Matcher matcher = AS_NAME_PATTERN.matcher(name);
@@ -126,7 +126,7 @@ public final class AS2Utils {
      *            - the stream printed to.
      * @param request
      *            - the request printed.
-     * @throws IOException
+     * @throws IOException - If failed to print request.
      */
     public static void printRequest(PrintStream out, HttpRequest request) throws IOException {
         // Print request line
@@ -152,7 +152,7 @@ public final class AS2Utils {
      *
      * @param out - the stream printed to.
      * @param message - the request printed.
-     * @throws IOException
+     * @throws IOException - If failed to print message.
      */
     public static void printMessage(PrintStream out, HttpMessage message) throws IOException {
         // Print request line