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 <punkhor...@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