This is an automated email from the ASF dual-hosted git repository.
davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git
The following commit(s) were added to refs/heads/main by this push:
new 477eb74e8a7 Camel 20759 allow client compression before signing
(#14110)
477eb74e8a7 is described below
commit 477eb74e8a754c749a7cf81a0cf27e0f0896d480
Author: Jono Morris <[email protected]>
AuthorDate: Mon May 13 16:49:58 2024 +1200
Camel 20759 allow client compression before signing (#14110)
* CAMEL-20759 allow client compression for signing
* CAMEL-20759 add generated changes
---------
Co-authored-by: Claus Ibsen <[email protected]>
---
.../org/apache/camel/catalog/components/as2.json | 4 +-
.../camel/component/as2/api/AS2ClientManager.java | 68 ++++++++-
.../component/as2/api/AS2MessageStructure.java | 4 +-
.../camel/component/as2/api/AS2MessageTest.java | 119 ++++++++++++++-
.../org/apache/camel/component/as2/as2.json | 4 +-
.../camel/component/as2/AS2ServerManagerIT.java | 166 +++++++++++++++++++++
.../ROOT/pages/camel-4x-upgrade-guide-4_7.adoc | 6 +
7 files changed, 360 insertions(+), 11 deletions(-)
diff --git
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
index a4c4ba24786..45dffabaaba 100644
---
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
+++
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
@@ -33,7 +33,7 @@
"apiName": { "index": 0, "kind": "path", "displayName": "Api Name",
"group": "common", "label": "", "required": true, "type": "object", "javaType":
"org.apache.camel.component.as2.internal.AS2ApiName", "enum": [ "CLIENT",
"SERVER", "RECEIPT" ], "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "What kind of operation to perform" },
"methodName": { "index": 1, "kind": "path", "displayName": "Method Name",
"group": "common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "What sub operation to use for the selected
operation" },
"as2From": { "index": 2, "kind": "parameter", "displayName": "As2 From",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The value of the AS2From header of AS2
message." },
- "as2MessageStructure": { "index": 3, "kind": "parameter", "displayName":
"As2 Message Structure", "group": "common", "label": "", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.as2.api.AS2MessageStructure", "enum": [ "PLAIN",
"SIGNED", "ENCRYPTED", "SIGNED_ENCRYPTED", "PLAIN_COMPRESSED",
"SIGNED_COMPRESSED", "ENCRYPTED_COMPRESSED", "ENCRYPTED_COMPRESSED_SIGNED" ],
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache [...]
+ "as2MessageStructure": { "index": 3, "kind": "parameter", "displayName":
"As2 Message Structure", "group": "common", "label": "", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.as2.api.AS2MessageStructure", "enum": [ "PLAIN",
"SIGNED", "ENCRYPTED", "SIGNED_ENCRYPTED", "PLAIN_COMPRESSED",
"COMPRESSED_SIGNED", "SIGNED_COMPRESSED", "ENCRYPTED_COMPRESSED",
"ENCRYPTED_COMPRESSED_SIGNED", "ENCRYPTED_SIGNED_COMPRESSED" ], "deprecated":
false, "autowired": false [...]
"as2To": { "index": 4, "kind": "parameter", "displayName": "As2 To",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The value of the AS2To header of AS2 message."
},
"as2Version": { "index": 5, "kind": "parameter", "displayName": "As2
Version", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "enum": [ "1.0", "1.1" ], "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "1.1",
"configurationClass": "org.apache.camel.component.as2.AS2Configuration",
"configurationField": "configuration", "description": "The version of the AS2
protocol." },
"asyncMdnPortNumber": { "index": 6, "kind": "parameter", "displayName":
"Async Mdn Port Number", "group": "common", "label": "", "required": false,
"type": "integer", "javaType": "java.lang.Integer", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The port number of asynchronous MDN server." },
@@ -79,7 +79,7 @@
"server": { "consumerOnly": true, "producerOnly": false, "description":
"Receives EDI Messages over HTTP", "methods": { "listen": { "description": "",
"signatures": [ "void listen(String requestUriPattern,
org.apache.hc.core5.http.io.HttpRequestHandler handler)" ] } } }
},
"apiProperties": {
- "client": { "methods": { "send": { "properties": { "as2From": { "index":
0, "kind": "parameter", "displayName": "As2 From", "group": "producer",
"label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "AS2 name of sender", "optional": false },
"as2MessageStructure": { "index": 1, "kind": "parameter", "displayName": "As2
Message Structure", "group": "producer", "label": "", "required": f [...]
+ "client": { "methods": { "send": { "properties": { "as2From": { "index":
0, "kind": "parameter", "displayName": "As2 From", "group": "producer",
"label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "AS2 name of sender", "optional": false },
"as2MessageStructure": { "index": 1, "kind": "parameter", "displayName": "As2
Message Structure", "group": "producer", "label": "", "required": f [...]
"receipt": { "methods": { "receive": { "properties": {
"requestUriPattern": { "index": 0, "kind": "parameter", "displayName": "Request
Uri Pattern", "group": "consumer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "description": "", "optional": false } } } } },
"server": { "methods": { "listen": { "properties": { "requestUriPattern":
{ "index": 0, "kind": "parameter", "displayName": "Request Uri Pattern",
"group": "consumer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "", "optional": false } } } } }
}
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 eb8ec12e346..e2188dcd577 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
@@ -288,6 +288,10 @@ public class AS2ClientManager {
signedCompressed(httpContext, applicationEntity, request);
break;
}
+ case COMPRESSED_SIGNED: {
+ compressedSigned(httpContext, applicationEntity, request);
+ break;
+ }
case ENCRYPTED_COMPRESSED: {
encryptedCompressed(httpContext, applicationEntity, request);
break;
@@ -296,6 +300,10 @@ public class AS2ClientManager {
encryptedCompressedSigned(httpContext, applicationEntity,
request);
break;
}
+ case ENCRYPTED_SIGNED_COMPRESSED: {
+ encryptedSignedCompressed(httpContext, applicationEntity,
request);
+ break;
+ }
default:
throw new HttpException("Unknown AS2 Message Structure");
}
@@ -324,13 +332,44 @@ public class AS2ClientManager {
return response;
}
+ // payload is compressed before being signed and encrypted
private void encryptedCompressedSigned(
HttpCoreContext httpContext, ApplicationEntity applicationEntity,
BasicClassicHttpRequest request)
throws HttpException {
+
+ // Create Compressed Entity containing the EDI Entity
+ CMSCompressedDataGenerator compressedDataGenerator =
createCompressorGenerator();
+ OutputCompressor compressor = createCompressor(httpContext);
+ ApplicationPkcs7MimeCompressedDataEntity pkcs7MimeCompressedDataEntity
+ = new ApplicationPkcs7MimeCompressedDataEntity(
+ applicationEntity, compressedDataGenerator,
compressor, AS2TransferEncoding.BASE64, false);
+
+ // Create Multipart Signed Entity containing the Compressed Entity
+ AS2SignedDataGenerator signingGenerator =
createSigningGenerator(httpContext);
+ MultipartSignedEntity multipartSignedEntity = new
MultipartSignedEntity(
+ pkcs7MimeCompressedDataEntity, signingGenerator,
+ StandardCharsets.US_ASCII.name(), AS2TransferEncoding.BASE64,
false, null);
+
+ // Create Enveloped Entity containing th Signed Entity
+ CMSEnvelopedDataGenerator envelopedDataGenerator =
createEncryptingGenerator(httpContext);
+ OutputEncryptor encryptor = createEncryptor(httpContext);
+ ApplicationPkcs7MimeEnvelopedDataEntity pkcs7MimeEnvelopedDataEntity
+ = new ApplicationPkcs7MimeEnvelopedDataEntity(
+ multipartSignedEntity, envelopedDataGenerator,
encryptor, AS2TransferEncoding.BASE64,
+ true);
+
+ // Add Enveloped Entity to main body of request
+ EntityUtils.setMessageEntity(request, pkcs7MimeEnvelopedDataEntity);
+ }
+
+ // payload is signed before being compressed and encrypted.
+ private void encryptedSignedCompressed(
+ HttpCoreContext httpContext, ApplicationEntity applicationEntity,
BasicClassicHttpRequest request)
+ throws HttpException {
// Create Multipart Signed Entity containing EDI Entity
- AS2SignedDataGenerator signingGenrator =
createSigningGenerator(httpContext);
+ AS2SignedDataGenerator signingGenerator =
createSigningGenerator(httpContext);
MultipartSignedEntity multipartSignedEntity = new
MultipartSignedEntity(
- applicationEntity, signingGenrator,
+ applicationEntity, signingGenerator,
StandardCharsets.US_ASCII.name(), AS2TransferEncoding.BASE64,
false, null);
// Create Compressed Entity containing Multipart Signed Entity
@@ -374,14 +413,35 @@ public class AS2ClientManager {
EntityUtils.setMessageEntity(request, pkcs7MimeEnvelopedDataEntity);
}
+ private void compressedSigned(
+ HttpCoreContext httpContext, ApplicationEntity applicationEntity,
BasicClassicHttpRequest request)
+ throws HttpException {
+
+ // Create Compressed Entity containing the EDI Entity
+ CMSCompressedDataGenerator compressedDataGenerator =
createCompressorGenerator();
+ OutputCompressor compressor = createCompressor(httpContext);
+ ApplicationPkcs7MimeCompressedDataEntity pkcs7MimeCompressedDataEntity
+ = new ApplicationPkcs7MimeCompressedDataEntity(
+ applicationEntity, compressedDataGenerator,
compressor, AS2TransferEncoding.BASE64, false);
+
+ // Create Multipart Signed Entity containing the Compressed Entity
+ AS2SignedDataGenerator signingGenerator =
createSigningGenerator(httpContext);
+ MultipartSignedEntity multipartSignedEntity = new
MultipartSignedEntity(
+ pkcs7MimeCompressedDataEntity,
+ signingGenerator, StandardCharsets.US_ASCII.name(),
AS2TransferEncoding.BASE64, true, null);
+
+ // Add Compressed Entity to main body of request.
+ EntityUtils.setMessageEntity(request, multipartSignedEntity);
+ }
+
private void signedCompressed(
HttpCoreContext httpContext, ApplicationEntity applicationEntity,
BasicClassicHttpRequest request)
throws HttpException {
// Create Multipart Signed Entity containing EDI Entity
- AS2SignedDataGenerator signingGenrator =
createSigningGenerator(httpContext);
+ AS2SignedDataGenerator signingGenerator =
createSigningGenerator(httpContext);
MultipartSignedEntity multipartSignedEntity = new
MultipartSignedEntity(
applicationEntity,
- signingGenrator, StandardCharsets.US_ASCII.name(),
AS2TransferEncoding.BASE64, false, null);
+ signingGenerator, StandardCharsets.US_ASCII.name(),
AS2TransferEncoding.BASE64, false, null);
// Create Compressed Entity containing Multipart Signed Entity
CMSCompressedDataGenerator compressedDataGenerator =
createCompressorGenerator();
diff --git
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MessageStructure.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MessageStructure.java
index dc73ded378f..d40877f433c 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MessageStructure.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2MessageStructure.java
@@ -22,9 +22,11 @@ public enum AS2MessageStructure {
ENCRYPTED(false, true, false),
SIGNED_ENCRYPTED(true, true, false),
PLAIN_COMPRESSED(false, false, true),
+ COMPRESSED_SIGNED(true, false, true),
SIGNED_COMPRESSED(true, false, true),
ENCRYPTED_COMPRESSED(false, true, true),
- ENCRYPTED_COMPRESSED_SIGNED(true, true, true);
+ ENCRYPTED_COMPRESSED_SIGNED(true, true, true),
+ ENCRYPTED_SIGNED_COMPRESSED(true, true, true);
private final boolean isSigned;
private final boolean isEncrypted;
diff --git
a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java
index 6e11be2ee8f..1e8c1937b53 100644
---
a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java
+++
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2MessageTest.java
@@ -19,6 +19,7 @@ package org.apache.camel.component.as2.api;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
+import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.security.KeyPair;
@@ -836,7 +837,7 @@ public class AS2MessageTest {
}
@Test
- public void compressedAndSignedMessageTest() throws Exception {
+ public void signedAndCompressedMessageTest() throws Exception {
AS2ClientManager clientManager = createDefaultClientManager();
LOG.info("Key Algorithm: {}", signingKP.getPrivate().getAlgorithm());
@@ -960,6 +961,67 @@ public class AS2MessageTest {
"Unexpected content for enveloped mime part");
}
+ // Verify that the payload is compressed before being signed.
+ @Test
+ public void compressedAndSignedMessageTest() throws Exception {
+ AS2ClientManager clientManager = createDefaultClientManager();
+ HttpCoreContext httpContext = clientManager.send(EDI_MESSAGE,
REQUEST_URI, SUBJECT, FROM, AS2_NAME, AS2_NAME,
+ AS2MessageStructure.COMPRESSED_SIGNED,
+ ContentType.create(AS2MediaType.APPLICATION_EDIFACT,
StandardCharsets.US_ASCII), "base64",
+ AS2SignatureAlgorithm.SHA256WITHRSA, certList.toArray(new
Certificate[0]), signingKP.getPrivate(),
+ AS2CompressionAlgorithm.ZLIB,
+ DISPOSITION_NOTIFICATION_TO, SIGNED_RECEIPT_MIC_ALGORITHMS,
null,
+ null, "file.txt", null);
+
+ HttpRequest request = httpContext.getRequest();
+ verifyRequest(request);
+
+
assertTrue(request.getFirstHeader(AS2Header.CONTENT_TYPE).getValue().startsWith(AS2MimeType.MULTIPART_SIGNED),
+ "Unexpected content type for message");
+ assertTrue(request instanceof ClassicHttpRequest, "Request does not
contain entity");
+ HttpEntity entity = ((ClassicHttpRequest) request).getEntity();
+ assertNotNull(entity, "Request does not contain entity");
+ assertTrue(entity instanceof MultipartSignedEntity, "Unexpected
request entity type");
+ MultipartSignedEntity multipartSignedEntity = (MultipartSignedEntity)
entity;
+ assertTrue(multipartSignedEntity.isMainBody(), "Entity not set as main
body of request");
+
+ verifyCompressedSignedEntity(multipartSignedEntity);
+ }
+
+ // Verify that the payload is compressed before being signed and encrypted.
+ @Test
+ public void envelopedSignedAndCompressedMessageTest() throws Exception {
+ AS2ClientManager clientManager = createDefaultClientManager();
+ HttpCoreContext httpContext = clientManager.send(EDI_MESSAGE,
REQUEST_URI, SUBJECT, FROM, AS2_NAME, AS2_NAME,
+ AS2MessageStructure.ENCRYPTED_COMPRESSED_SIGNED,
+ ContentType.create(AS2MediaType.APPLICATION_EDIFACT,
StandardCharsets.US_ASCII), null,
+ AS2SignatureAlgorithm.SHA256WITHRSA, certList.toArray(new
Certificate[0]), signingKP.getPrivate(),
+ AS2CompressionAlgorithm.ZLIB, DISPOSITION_NOTIFICATION_TO,
SIGNED_RECEIPT_MIC_ALGORITHMS,
+ AS2EncryptionAlgorithm.AES128_CBC, certList.toArray(new
Certificate[0]), "file.txt", null);
+
+ HttpRequest request = httpContext.getRequest();
+ verifyRequest(request);
+
+
assertTrue(request.getFirstHeader(AS2Header.CONTENT_TYPE).getValue().startsWith(AS2MimeType.APPLICATION_PKCS7_MIME),
+ "Unexpected content type for message");
+ assertTrue(request instanceof ClassicHttpRequest, "Request does not
contain entity");
+ HttpEntity entity = ((ClassicHttpRequest) request).getEntity();
+ assertNotNull(entity, "Request does not contain entity");
+ assertTrue(entity instanceof ApplicationPkcs7MimeEnvelopedDataEntity,
"Unexpected request entity type");
+ ApplicationPkcs7MimeEnvelopedDataEntity envelopedEntity =
(ApplicationPkcs7MimeEnvelopedDataEntity) entity;
+ assertTrue(envelopedEntity.isMainBody(), "Entity not set as main body
of request");
+
+ // Validated enveloped part.
+ MimeEntity decryptedEntity =
envelopedEntity.getEncryptedEntity(signingKP.getPrivate());
+ assertTrue(decryptedEntity instanceof MultipartSignedEntity,
"Enveloped mime part incorrect type ");
+ MultipartSignedEntity multipartSignedEntity = (MultipartSignedEntity)
decryptedEntity;
+
assertTrue(multipartSignedEntity.getContentType().startsWith(AS2MimeType.MULTIPART_SIGNED),
+ "Unexpected content type for compressed mime part");
+ assertFalse(multipartSignedEntity.isMainBody(), "Enveloped mime type
set as main body of request");
+
+ verifyCompressedSignedEntity(multipartSignedEntity);
+ }
+
@Test
public void envelopedCompressedAndSignedMessageTest() throws Exception {
AS2ClientManager clientManager = createDefaultClientManager();
@@ -967,7 +1029,7 @@ public class AS2MessageTest {
LOG.info("Key Algorithm: {}", signingKP.getPrivate().getAlgorithm());
HttpCoreContext httpContext = clientManager.send(EDI_MESSAGE,
REQUEST_URI, SUBJECT, FROM, AS2_NAME, AS2_NAME,
- AS2MessageStructure.ENCRYPTED_COMPRESSED_SIGNED,
+ AS2MessageStructure.ENCRYPTED_SIGNED_COMPRESSED,
ContentType.create(AS2MediaType.APPLICATION_EDIFACT,
StandardCharsets.US_ASCII), null,
AS2SignatureAlgorithm.SHA256WITHRSA, certList.toArray(new
Certificate[0]), signingKP.getPrivate(),
AS2CompressionAlgorithm.ZLIB, DISPOSITION_NOTIFICATION_TO,
SIGNED_RECEIPT_MIC_ALGORITHMS,
@@ -1076,6 +1138,59 @@ public class AS2MessageTest {
assertEquals(EDI_MESSAGE, ediEntity.getEdiMessage().replaceAll("\r",
""));
}
+ private void verifyRequest(HttpRequest request) throws URISyntaxException {
+ assertEquals(METHOD, request.getMethod(), "Unexpected method value");
+ assertEquals(REQUEST_URI, request.getUri().getPath(), "Unexpected
request URI value");
+ assertEquals(HttpVersion.HTTP_1_1, request.getVersion(), "Unexpected
HTTP version value");
+
+ assertEquals(SUBJECT,
request.getFirstHeader(AS2Header.SUBJECT).getValue(), "Unexpected subject
value");
+ assertEquals(FROM, request.getFirstHeader(AS2Header.FROM).getValue(),
"Unexpected from value");
+ assertEquals(AS2_VERSION,
request.getFirstHeader(AS2Header.AS2_VERSION).getValue(), "Unexpected AS2
version value");
+ assertEquals(AS2_NAME,
request.getFirstHeader(AS2Header.AS2_FROM).getValue(), "Unexpected AS2 from
value");
+ assertEquals(AS2_NAME,
request.getFirstHeader(AS2Header.AS2_TO).getValue(), "Unexpected AS2 to value");
+
assertTrue(request.getFirstHeader(AS2Header.MESSAGE_ID).getValue().endsWith(CLIENT_FQDN
+ ">"),
+ "Unexpected message id value");
+ assertEquals(TARGET_HOST + ":" + TARGET_PORT,
request.getFirstHeader(AS2Header.TARGET_HOST).getValue(),
+ "Unexpected target host value");
+ assertEquals(USER_AGENT,
request.getFirstHeader(AS2Header.USER_AGENT).getValue(), "Unexpected user agent
value");
+ assertNotNull(request.getFirstHeader(AS2Header.DATE), "Date value
missing");
+ assertNotNull(request.getFirstHeader(AS2Header.CONTENT_LENGTH),
"Content length value missing");
+ }
+
+ private void verifyCompressedSignedEntity(MultipartSignedEntity
multipartSignedEntity) throws HttpException {
+ assertEquals(2, multipartSignedEntity.getPartCount(), "Request
contains invalid number of mime parts");
+
+ // Verify first mime part.
+ assertTrue(multipartSignedEntity.getPart(0) instanceof
ApplicationPkcs7MimeCompressedDataEntity,
+ "First mime part incorrect type ");
+ ApplicationPkcs7MimeCompressedDataEntity compressedDataEntity
+ = (ApplicationPkcs7MimeCompressedDataEntity)
multipartSignedEntity.getPart(0);
+
assertTrue(compressedDataEntity.getContentType().startsWith(AS2MediaType.APPLICATION_PKCS7_MIME_COMPRESSED),
+ "Unexpected content type for first mime part");
+ assertFalse(compressedDataEntity.isMainBody(), "First mime type set as
main body of request");
+
+ // Verify compressed entity.
+ verifyEdiFactEntity(compressedDataEntity.getCompressedEntity(new
ZlibExpanderProvider()));
+
+ // Verify second mime part.
+ assertTrue(multipartSignedEntity.getPart(1) instanceof
ApplicationPkcs7SignatureEntity,
+ "Second mime part incorrect type ");
+ ApplicationPkcs7SignatureEntity signatureEntity =
(ApplicationPkcs7SignatureEntity) multipartSignedEntity.getPart(1);
+
assertTrue(signatureEntity.getContentType().startsWith(AS2MediaType.APPLICATION_PKCS7_SIGNATURE),
+ "Unexpected content type for second mime part");
+ assertFalse(signatureEntity.isMainBody(), "First mime type set as main
body of request");
+
+ // Verify Signature
+ assertTrue(SigningUtils.isValid(multipartSignedEntity, new
Certificate[] { signingCert }), "Signature must be invalid");
+ }
+
+ private void verifyEdiFactEntity(MimeEntity entity) {
+ assertTrue(entity instanceof ApplicationEDIFACTEntity, "Enveloped mime
part incorrect type ");
+ ApplicationEDIFACTEntity ediEntity = (ApplicationEDIFACTEntity) entity;
+
assertTrue(ediEntity.getContentType().startsWith(AS2MediaType.APPLICATION_EDIFACT),
+ "Unexpected content type for compressed entity");
+ }
+
private AS2ClientManager createDefaultClientManager() throws IOException {
AS2ClientConnection clientConnection = new AS2ClientConnection(
AS2_VERSION, USER_AGENT, CLIENT_FQDN,
diff --git
a/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
b/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
index a4c4ba24786..45dffabaaba 100644
---
a/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
+++
b/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
@@ -33,7 +33,7 @@
"apiName": { "index": 0, "kind": "path", "displayName": "Api Name",
"group": "common", "label": "", "required": true, "type": "object", "javaType":
"org.apache.camel.component.as2.internal.AS2ApiName", "enum": [ "CLIENT",
"SERVER", "RECEIPT" ], "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "What kind of operation to perform" },
"methodName": { "index": 1, "kind": "path", "displayName": "Method Name",
"group": "common", "label": "", "required": true, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "deprecationNote": "", "autowired":
false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "What sub operation to use for the selected
operation" },
"as2From": { "index": 2, "kind": "parameter", "displayName": "As2 From",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The value of the AS2From header of AS2
message." },
- "as2MessageStructure": { "index": 3, "kind": "parameter", "displayName":
"As2 Message Structure", "group": "common", "label": "", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.as2.api.AS2MessageStructure", "enum": [ "PLAIN",
"SIGNED", "ENCRYPTED", "SIGNED_ENCRYPTED", "PLAIN_COMPRESSED",
"SIGNED_COMPRESSED", "ENCRYPTED_COMPRESSED", "ENCRYPTED_COMPRESSED_SIGNED" ],
"deprecated": false, "autowired": false, "secret": false, "configurationClass":
"org.apache [...]
+ "as2MessageStructure": { "index": 3, "kind": "parameter", "displayName":
"As2 Message Structure", "group": "common", "label": "", "required": false,
"type": "object", "javaType":
"org.apache.camel.component.as2.api.AS2MessageStructure", "enum": [ "PLAIN",
"SIGNED", "ENCRYPTED", "SIGNED_ENCRYPTED", "PLAIN_COMPRESSED",
"COMPRESSED_SIGNED", "SIGNED_COMPRESSED", "ENCRYPTED_COMPRESSED",
"ENCRYPTED_COMPRESSED_SIGNED", "ENCRYPTED_SIGNED_COMPRESSED" ], "deprecated":
false, "autowired": false [...]
"as2To": { "index": 4, "kind": "parameter", "displayName": "As2 To",
"group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The value of the AS2To header of AS2 message."
},
"as2Version": { "index": 5, "kind": "parameter", "displayName": "As2
Version", "group": "common", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "enum": [ "1.0", "1.1" ], "deprecated": false,
"autowired": false, "secret": false, "defaultValue": "1.1",
"configurationClass": "org.apache.camel.component.as2.AS2Configuration",
"configurationField": "configuration", "description": "The version of the AS2
protocol." },
"asyncMdnPortNumber": { "index": 6, "kind": "parameter", "displayName":
"Async Mdn Port Number", "group": "common", "label": "", "required": false,
"type": "integer", "javaType": "java.lang.Integer", "deprecated": false,
"autowired": false, "secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The port number of asynchronous MDN server." },
@@ -79,7 +79,7 @@
"server": { "consumerOnly": true, "producerOnly": false, "description":
"Receives EDI Messages over HTTP", "methods": { "listen": { "description": "",
"signatures": [ "void listen(String requestUriPattern,
org.apache.hc.core5.http.io.HttpRequestHandler handler)" ] } } }
},
"apiProperties": {
- "client": { "methods": { "send": { "properties": { "as2From": { "index":
0, "kind": "parameter", "displayName": "As2 From", "group": "producer",
"label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "AS2 name of sender", "optional": false },
"as2MessageStructure": { "index": 1, "kind": "parameter", "displayName": "As2
Message Structure", "group": "producer", "label": "", "required": f [...]
+ "client": { "methods": { "send": { "properties": { "as2From": { "index":
0, "kind": "parameter", "displayName": "As2 From", "group": "producer",
"label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"description": "AS2 name of sender", "optional": false },
"as2MessageStructure": { "index": 1, "kind": "parameter", "displayName": "As2
Message Structure", "group": "producer", "label": "", "required": f [...]
"receipt": { "methods": { "receive": { "properties": {
"requestUriPattern": { "index": 0, "kind": "parameter", "displayName": "Request
Uri Pattern", "group": "consumer", "label": "", "required": false, "type":
"string", "javaType": "java.lang.String", "deprecated": false, "autowired":
false, "secret": false, "description": "", "optional": false } } } } },
"server": { "methods": { "listen": { "properties": { "requestUriPattern":
{ "index": 0, "kind": "parameter", "displayName": "Request Uri Pattern",
"group": "consumer", "label": "", "required": false, "type": "string",
"javaType": "java.lang.String", "deprecated": false, "autowired": false,
"secret": false, "description": "", "optional": false } } } } }
}
diff --git
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerManagerIT.java
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerManagerIT.java
index 846b824e72e..83e040a2c76 100644
---
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerManagerIT.java
+++
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerManagerIT.java
@@ -16,6 +16,7 @@
*/
package org.apache.camel.component.as2;
+import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
@@ -37,6 +38,7 @@ import org.apache.camel.Processor;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.as2.api.AS2ClientConnection;
import org.apache.camel.component.as2.api.AS2ClientManager;
+import org.apache.camel.component.as2.api.AS2CompressionAlgorithm;
import org.apache.camel.component.as2.api.AS2EncryptionAlgorithm;
import org.apache.camel.component.as2.api.AS2Header;
import org.apache.camel.component.as2.api.AS2MediaType;
@@ -45,6 +47,7 @@ import org.apache.camel.component.as2.api.AS2MimeType;
import org.apache.camel.component.as2.api.AS2SignatureAlgorithm;
import org.apache.camel.component.as2.api.AS2SignedDataGenerator;
import org.apache.camel.component.as2.api.entity.ApplicationEDIFACTEntity;
+import
org.apache.camel.component.as2.api.entity.ApplicationPkcs7MimeCompressedDataEntity;
import
org.apache.camel.component.as2.api.entity.ApplicationPkcs7MimeEnvelopedDataEntity;
import
org.apache.camel.component.as2.api.entity.ApplicationPkcs7SignatureEntity;
import org.apache.camel.component.as2.api.entity.ApplicationXMLEntity;
@@ -64,6 +67,7 @@ import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.ContentType;
import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpRequest;
import org.apache.hc.core5.http.HttpResponse;
import org.apache.hc.core5.http.HttpStatus;
@@ -77,6 +81,7 @@ import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.jcajce.JcaCertStore;
+import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.util.io.Streams;
import org.junit.jupiter.api.BeforeAll;
@@ -486,6 +491,167 @@ public class AS2ServerManagerIT extends
AbstractAS2ITSupport {
"Unexpected content for enveloped mime part");
}
+ // Verify that the payload is compressed before being signed.
+ @Test
+ public void receiveMultipartCompressedAndSignedMessageTest() throws
Exception {
+ AS2ClientConnection clientConnection
+ = new AS2ClientConnection(
+ AS2_VERSION, USER_AGENT, CLIENT_FQDN, TARGET_HOST,
TARGET_PORT, HTTP_SOCKET_TIMEOUT,
+ HTTP_CONNECTION_TIMEOUT, HTTP_CONNECTION_POOL_SIZE,
HTTP_CONNECTION_POOL_TTL, clientSslContext,
+ null);
+ AS2ClientManager clientManager = new
AS2ClientManager(clientConnection);
+
+ clientManager.send(EDI_MESSAGE, REQUEST_URI, SUBJECT, FROM, AS2_NAME,
AS2_NAME, AS2MessageStructure.COMPRESSED_SIGNED,
+ ContentType.create(AS2MediaType.APPLICATION_EDIFACT,
StandardCharsets.US_ASCII), null,
+ AS2SignatureAlgorithm.SHA256WITHRSA,
+ certList.toArray(new Certificate[0]), signingKP.getPrivate(),
AS2CompressionAlgorithm.ZLIB,
+ DISPOSITION_NOTIFICATION_TO,
+ SIGNED_RECEIPT_MIC_ALGORITHMS, null, null, null, null);
+
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:as2RcvMsgs");
+ verifyMock(mockEndpoint);
+
+ final List<Exchange> exchanges = mockEndpoint.getExchanges();
+ verifyExchanges(exchanges);
+
+ HttpCoreContext coreContext =
exchanges.get(0).getProperty(AS2Constants.AS2_INTERCHANGE,
HttpCoreContext.class);
+ assertNotNull(coreContext, "context");
+
+ HttpRequest request = coreContext.getRequest();
+ verifyRequest(request);
+
+
assertTrue(request.getFirstHeader(AS2Header.CONTENT_TYPE).getValue().startsWith(AS2MediaType.MULTIPART_SIGNED),
+ "Unexpected content type for message");
+ assertTrue(request instanceof ClassicHttpRequest, "Request does not
contain entity");
+ HttpEntity entity = ((ClassicHttpRequest) request).getEntity();
+ assertNotNull(entity, "Request does not contain entity");
+ assertTrue(entity instanceof MultipartSignedEntity, "Unexpected
request entity type");
+ MultipartSignedEntity multipartSignedEntity = (MultipartSignedEntity)
entity;
+ assertTrue(multipartSignedEntity.isMainBody(), "Entity not set as main
body of request");
+
+ verifyCompressedSignedEntity(multipartSignedEntity);
+ }
+
+ // Verify that the payload is compressed before being signed and encrypted.
+ @Test
+ public void receiveEnvelopedCompressedAndSignedMessageTest() throws
Exception {
+ AS2ClientConnection clientConnection
+ = new AS2ClientConnection(
+ AS2_VERSION, USER_AGENT, CLIENT_FQDN, TARGET_HOST,
TARGET_PORT, HTTP_SOCKET_TIMEOUT,
+ HTTP_CONNECTION_TIMEOUT, HTTP_CONNECTION_POOL_SIZE,
HTTP_CONNECTION_POOL_TTL, clientSslContext,
+ null);
+ AS2ClientManager clientManager = new
AS2ClientManager(clientConnection);
+
+ clientManager.send(EDI_MESSAGE, REQUEST_URI, SUBJECT, FROM, AS2_NAME,
AS2_NAME,
+ AS2MessageStructure.ENCRYPTED_COMPRESSED_SIGNED,
+ ContentType.create(AS2MediaType.APPLICATION_EDIFACT,
StandardCharsets.US_ASCII), null,
+ AS2SignatureAlgorithm.SHA256WITHRSA, certList.toArray(new
Certificate[0]), signingKP.getPrivate(),
+ AS2CompressionAlgorithm.ZLIB, DISPOSITION_NOTIFICATION_TO,
SIGNED_RECEIPT_MIC_ALGORITHMS,
+ AS2EncryptionAlgorithm.AES128_CBC,
+ certList.toArray(new Certificate[0]), null, null);
+
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:as2RcvMsgs");
+ verifyMock(mockEndpoint);
+
+ final List<Exchange> exchanges = mockEndpoint.getExchanges();
+ verifyExchanges(exchanges);
+
+ HttpCoreContext coreContext =
exchanges.get(0).getProperty(AS2Constants.AS2_INTERCHANGE,
HttpCoreContext.class);
+ assertNotNull(coreContext, "context");
+
+ HttpRequest request = coreContext.getRequest();
+ verifyRequest(request);
+
+
assertTrue(request.getFirstHeader(AS2Header.CONTENT_TYPE).getValue().startsWith(AS2MimeType.APPLICATION_PKCS7_MIME),
+ "Unexpected content type for message");
+ assertTrue(request instanceof ClassicHttpRequest, "Request does not
contain entity");
+ HttpEntity entity = ((ClassicHttpRequest) request).getEntity();
+ assertNotNull(entity, "Request does not contain entity");
+ assertTrue(entity instanceof ApplicationPkcs7MimeEnvelopedDataEntity,
"Unexpected request entity type");
+ ApplicationPkcs7MimeEnvelopedDataEntity envelopedEntity =
(ApplicationPkcs7MimeEnvelopedDataEntity) entity;
+ assertTrue(envelopedEntity.isMainBody(), "Entity not set as main body
of request");
+
+ // Validated enveloped part.
+ MimeEntity decryptedEntity =
envelopedEntity.getEncryptedEntity(decryptingKP.getPrivate());
+ assertTrue(decryptedEntity instanceof MultipartSignedEntity,
"Enveloped mime part incorrect type ");
+ MultipartSignedEntity multipartSignedEntity = (MultipartSignedEntity)
decryptedEntity;
+
assertTrue(multipartSignedEntity.getContentType().startsWith(AS2MediaType.MULTIPART_SIGNED),
+ "Unexpected content type for enveloped mime part");
+ assertFalse(multipartSignedEntity.isMainBody(), "Enveloped mime type
set as main body of request");
+
+ verifyCompressedSignedEntity(multipartSignedEntity);
+ }
+
+ private void verifyRequest(HttpRequest request) throws URISyntaxException {
+ assertNotNull(request, "request");
+ assertEquals(METHOD, request.getMethod(), "Unexpected method value");
+ assertEquals(REQUEST_URI, request.getUri().getPath(), "Unexpected
request URI value");
+ assertEquals(HttpVersion.HTTP_1_1, request.getVersion(), "Unexpected
HTTP version value");
+ assertEquals(SUBJECT,
request.getFirstHeader(AS2Header.SUBJECT).getValue(), "Unexpected subject
value");
+ assertEquals(FROM, request.getFirstHeader(AS2Header.FROM).getValue(),
"Unexpected from value");
+ assertEquals(AS2_VERSION,
request.getFirstHeader(AS2Header.AS2_VERSION).getValue(), "Unexpected AS2
version value");
+ assertEquals(AS2_NAME,
request.getFirstHeader(AS2Header.AS2_FROM).getValue(), "Unexpected AS2 from
value");
+ assertEquals(AS2_NAME,
request.getFirstHeader(AS2Header.AS2_TO).getValue(), "Unexpected AS2 to value");
+
assertTrue(request.getFirstHeader(AS2Header.MESSAGE_ID).getValue().endsWith(CLIENT_FQDN
+ ">"),
+ "Unexpected message id value");
+ assertEquals(TARGET_HOST + ":" + TARGET_PORT,
request.getFirstHeader(AS2Header.TARGET_HOST).getValue(),
+ "Unexpected target host value");
+ assertEquals(USER_AGENT,
request.getFirstHeader(AS2Header.USER_AGENT).getValue(), "Unexpected user agent
value");
+ assertNotNull(request.getFirstHeader(AS2Header.DATE), "Date value
missing");
+ assertNotNull(request.getFirstHeader(AS2Header.CONTENT_LENGTH),
"Content length value missing");
+ }
+
+ private void verifyMock(MockEndpoint mockEndpoint) throws
InterruptedException {
+ mockEndpoint.expectedMinimumMessageCount(1);
+ mockEndpoint.setResultWaitTime(TimeUnit.MILLISECONDS.convert(30,
TimeUnit.SECONDS));
+ mockEndpoint.assertIsSatisfied();
+ }
+
+ private void verifyExchanges(List<Exchange> exchanges) {
+ assertNotNull(exchanges, "listen result");
+ assertFalse(exchanges.isEmpty(), "listen result");
+ Exchange exchange = exchanges.get(0);
+ Message message = exchange.getIn();
+ assertNotNull(message, "exchange message");
+ String rcvdMessage = message.getBody(String.class);
+ assertEquals(EDI_MESSAGE.replaceAll("[\n\r]", ""),
rcvdMessage.replaceAll("[\n\r]", ""),
+ "Unexpected content for enveloped mime part");
+ }
+
+ private void verifyCompressedSignedEntity(MultipartSignedEntity
multipartSignedEntity) throws HttpException {
+ assertEquals(2, multipartSignedEntity.getPartCount(), "Request
contains invalid number of mime parts");
+
+ // Verify first mime part.
+ assertTrue(multipartSignedEntity.getPart(0) instanceof
ApplicationPkcs7MimeCompressedDataEntity,
+ "First mime part incorrect type ");
+ ApplicationPkcs7MimeCompressedDataEntity compressedDataEntity
+ = (ApplicationPkcs7MimeCompressedDataEntity)
multipartSignedEntity.getPart(0);
+
assertTrue(compressedDataEntity.getContentType().startsWith(AS2MediaType.APPLICATION_PKCS7_MIME_COMPRESSED),
+ "Unexpected content type for first mime part");
+ assertFalse(compressedDataEntity.isMainBody(), "First mime type set as
main body of request");
+
+ // Verify compressed entity.
+ verifyEdiFactEntity(compressedDataEntity.getCompressedEntity(new
ZlibExpanderProvider()));
+
+ // Verify second mime part.
+ assertTrue(multipartSignedEntity.getPart(1) instanceof
ApplicationPkcs7SignatureEntity,
+ "Second mime part incorrect type ");
+ ApplicationPkcs7SignatureEntity signatureEntity =
(ApplicationPkcs7SignatureEntity) multipartSignedEntity.getPart(1);
+
assertTrue(signatureEntity.getContentType().startsWith(AS2MediaType.APPLICATION_PKCS7_SIGNATURE),
+ "Unexpected content type for second mime part");
+ assertFalse(signatureEntity.isMainBody(), "First mime type set as main
body of request");
+
+ // Verify Signature
+ assertTrue(SigningUtils.isValid(multipartSignedEntity, new
Certificate[] { signingCert }), "Signature must be invalid");
+ }
+
+ private void verifyEdiFactEntity(MimeEntity entity) {
+ assertTrue(entity instanceof ApplicationEDIFACTEntity, "Enveloped mime
part incorrect type ");
+ ApplicationEDIFACTEntity ediEntity = (ApplicationEDIFACTEntity) entity;
+
assertTrue(ediEntity.getContentType().startsWith(AS2MediaType.APPLICATION_EDIFACT),
+ "Unexpected content type for compressed entity");
+ }
+
@Test
public void receiveEnvelopedMessageTest() throws Exception {
AS2ClientConnection clientConnection
diff --git
a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_7.adoc
b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_7.adoc
index a595510a771..0ff669f76da 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_7.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-4x-upgrade-guide-4_7.adoc
@@ -81,6 +81,11 @@
authorizationPolicy.setAuthorizationManager(AuthorityAuthorizationManager.hasRol
This new pattern supports a more expressive language to define your own
authorization rules, exposing the full power of the Spring Security framework
to Camel route policies.
See the
https://docs.spring.io/spring-security/reference/5.8/migration/servlet/authorization.html#servlet-replace-permissionevaluator-bean-with-methodsecurityexpression-handler[spring
documentation] for further details on how to migrate your custom code from
`AccessDecisionManager` to `AuthorizationManager`.
+=== camel-as2
+
+The `camel-as2` component has been updated so that the client can compress a
MIME body before signing or compress a MIME body before signing and encrypting
as described in
+sections https://datatracker.ietf.org/doc/html/rfc5402/#section-3.2[3.2] and
https://datatracker.ietf.org/doc/html/rfc5402/#section-3.5[3.5] of
https://datatracker.ietf.org/doc/html/rfc5402/[rfc 5402].
+
== Camel Spring Boot
@@ -88,3 +93,4 @@ See the
https://docs.spring.io/spring-security/reference/5.8/migration/servlet/a
Using camel debugger with Spring Boot is now moved from `camel-spring-boot`
into `camel-debug-starter` where you can configure the debugger
via `camel.debug.` options in `application.properties`.
+