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 bd989118a61 CAMEL-18917 camel-as2: add validating signature support to
server (#9219)
bd989118a61 is described below
commit bd989118a61a565a636e187f630825fdd54f19b1
Author: Dennis Schwarz <[email protected]>
AuthorDate: Fri Jan 27 15:40:14 2023 +0100
CAMEL-18917 camel-as2: add validating signature support to server (#9219)
* CAMEL-18917 - Implement Signature verfication 1st step
* CAMEL-18917 - Implement Signature verfication 2nd step
---
.../component/as2/api/AS2ServerConnection.java | 26 ++--
.../AS2MessageDispositionNotificationEntity.java | 7 +-
...spositionNotificationMultipartReportEntity.java | 8 +-
.../as2/api/entity/MultipartSignedEntity.java | 53 --------
.../component/as2/api/protocol/ResponseMDN.java | 8 +-
.../component/as2/api/util/HttpMessageUtils.java | 73 ++++++++---
.../camel/component/as2/api/util/MicUtils.java | 6 +-
.../camel/component/as2/api/util/SigningUtils.java | 63 ++++++++++
.../camel/component/as2/api/AS2MessageTest.java | 16 ++-
.../camel/component/as2/api/util/MicUtilsTest.java | 2 +-
.../component/as2/api/util/SigningUtilsTest.java | 110 ++++++++++++++++
...ientManagerEndpointConfigurationConfigurer.java | 7 ++
.../component/as2/AS2ConfigurationConfigurer.java | 7 ++
.../camel/component/as2/AS2EndpointConfigurer.java | 7 ++
.../camel/component/as2/AS2EndpointUriFactory.java | 3 +-
...rverManagerEndpointConfigurationConfigurer.java | 7 ++
.../org/apache/camel/component/as2/as2.json | 1 +
.../camel/component/as2/AS2Configuration.java | 14 +++
.../apache/camel/component/as2/AS2Consumer.java | 6 +-
.../as2/internal/AS2ConnectionHelper.java | 3 +-
.../camel/component/as2/AS2ClientManagerIT.java | 140 ++++++---------------
.../camel/component/as2/AS2ServerManagerIT.java | 95 +++++++++++++-
22 files changed, 459 insertions(+), 203 deletions(-)
diff --git
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java
index 1dd94f28483..1e82ac09142 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java
@@ -68,14 +68,16 @@ public class AS2ServerConnection {
Certificate[] signingCertificateChain,
PrivateKey signingPrivateKey,
PrivateKey decryptingPrivateKey,
- String mdnMessageTemplate)
- throws
IOException {
+ String mdnMessageTemplate,
+ Certificate[]
validateSigningCertificateChain)
+
throws IOException {
setName(REQUEST_LISTENER_THREAD_NAME_PREFIX + port);
serversocket = new ServerSocket(port);
// Set up HTTP protocol processor for incoming connections
final HttpProcessor inhttpproc = initProtocolProcessor(as2Version,
originServer, serverFqdn,
- signatureAlgorithm, signingCertificateChain,
signingPrivateKey, decryptingPrivateKey, mdnMessageTemplate);
+ signatureAlgorithm, signingCertificateChain,
signingPrivateKey, decryptingPrivateKey, mdnMessageTemplate,
+ validateSigningCertificateChain);
reqistry = new UriHttpRequestHandlerMapper();
@@ -194,6 +196,7 @@ public class AS2ServerConnection {
private PrivateKey signingPrivateKey;
private PrivateKey decryptingPrivateKey;
private String mdnMessageTemplate;
+ private Certificate[] validateSigningCertificateChain;
public AS2ServerConnection(String as2Version,
String originServer,
@@ -203,8 +206,9 @@ public class AS2ServerConnection {
Certificate[] signingCertificateChain,
PrivateKey signingPrivateKey,
PrivateKey decryptingPrivateKey,
- String mdnMessageTemplate)
- throws IOException {
+ String mdnMessageTemplate,
+ Certificate[] validateSigningCertificateChain)
+
throws IOException {
this.as2Version = ObjectHelper.notNull(as2Version, "as2Version");
this.originServer = ObjectHelper.notNull(originServer, "userAgent");
this.serverFqdn = ObjectHelper.notNull(serverFqdn, "serverFqdn");
@@ -214,15 +218,20 @@ public class AS2ServerConnection {
this.signingPrivateKey = signingPrivateKey;
this.decryptingPrivateKey = decryptingPrivateKey;
this.mdnMessageTemplate = mdnMessageTemplate;
+ this.validateSigningCertificateChain = validateSigningCertificateChain;
listenerThread = new RequestListenerThread(
this.as2Version, this.originServer, this.serverFqdn,
this.serverPortNumber, this.signingAlgorithm,
this.signingCertificateChain, this.signingPrivateKey,
- this.decryptingPrivateKey, this.mdnMessageTemplate);
+ this.decryptingPrivateKey, this.mdnMessageTemplate,
validateSigningCertificateChain);
listenerThread.setDaemon(true);
listenerThread.start();
}
+ public Certificate[] getValidateSigningCertificateChain() {
+ return validateSigningCertificateChain;
+ }
+
public PrivateKey getSigningPrivateKey() {
return signingPrivateKey;
}
@@ -267,12 +276,13 @@ public class AS2ServerConnection {
Certificate[] signingCertificateChain,
PrivateKey signingPrivateKey,
PrivateKey decryptingPrivateKey,
- String mdnMessageTemplate) {
+ String mdnMessageTemplate,
+ Certificate[] validateSigningCertificateChain) {
return HttpProcessorBuilder.create().add(new
ResponseContent(true)).add(new ResponseServer(originServer))
.add(new ResponseDate()).add(new
ResponseConnControl()).add(new ResponseMDN(
as2Version, serverFqdn,
signatureAlgorithm, signingCertificateChain,
signingPrivateKey, decryptingPrivateKey,
- mdnMessageTemplate))
+ mdnMessageTemplate, validateSigningCertificateChain))
.build();
}
diff --git
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java
index 31789498555..24591a849f6 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/AS2MessageDispositionNotificationEntity.java
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
+import java.security.cert.Certificate;
import java.util.HashMap;
import java.util.Map;
import java.util.Map.Entry;
@@ -78,7 +79,8 @@ public class AS2MessageDispositionNotificationEntity extends
MimeEntity {
Map<String, String>
extensionFields,
String charset,
boolean isMainBody,
- PrivateKey
decryptingPrivateKey) throws HttpException {
+ PrivateKey
decryptingPrivateKey,
+ Certificate[]
validateSigningCertificateChain) throws HttpException {
setMainBody(isMainBody);
setContentType(ContentType.create(AS2MimeType.MESSAGE_DISPOSITION_NOTIFICATION,
charset));
@@ -89,7 +91,8 @@ public class AS2MessageDispositionNotificationEntity extends
MimeEntity {
this.originalMessageId = HttpMessageUtils.getHeaderValue(request,
AS2Header.MESSAGE_ID);
- this.receivedContentMic = MicUtils.createReceivedContentMic(request,
decryptingPrivateKey);
+ this.receivedContentMic
+ = MicUtils.createReceivedContentMic(request,
validateSigningCertificateChain, decryptingPrivateKey);
this.reportingUA = HttpMessageUtils.getHeaderValue(response,
AS2Header.SERVER);
diff --git
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationMultipartReportEntity.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationMultipartReportEntity.java
index b94b8ab60e4..9da207edf94 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationMultipartReportEntity.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/DispositionNotificationMultipartReportEntity.java
@@ -18,6 +18,7 @@ package org.apache.camel.component.as2.api.entity;
import java.nio.charset.StandardCharsets;
import java.security.PrivateKey;
+import java.security.cert.Certificate;
import java.util.Map;
import org.apache.camel.component.as2.api.AS2Header;
@@ -51,8 +52,9 @@ public class DispositionNotificationMultipartReportEntity
extends MultipartRepor
String boundary,
boolean isMainBody,
PrivateKey
decryptingPrivateKey,
- String mdnMessage)
-
throws HttpException {
+ String mdnMessage,
+ Certificate[]
validateSigningCertificateChain)
+
throws HttpException {
super(charset, isMainBody, boundary);
removeHeaders(AS2Header.CONTENT_TYPE);
setContentType(getContentTypeValue(boundary));
@@ -64,7 +66,7 @@ public class DispositionNotificationMultipartReportEntity
extends MultipartRepor
addPart(new AS2MessageDispositionNotificationEntity(
request, response, dispositionMode, dispositionType,
dispositionModifier, failureFields, errorFields,
warningFields, extensionFields, charset, false,
- decryptingPrivateKey));
+ decryptingPrivateKey, validateSigningCertificateChain));
}
public String getMainMessageContentType() {
diff --git
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartSignedEntity.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartSignedEntity.java
index bf5f7d5685e..0b38996dd7f 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartSignedEntity.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/entity/MultipartSignedEntity.java
@@ -16,23 +16,8 @@
*/
package org.apache.camel.component.as2.api.entity;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.InputStream;
-import java.security.cert.X509Certificate;
-import java.util.Collection;
-
import org.apache.camel.component.as2.api.AS2SignedDataGenerator;
import org.apache.http.HttpException;
-import org.bouncycastle.cert.X509CertificateHolder;
-import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
-import org.bouncycastle.cms.CMSProcessable;
-import org.bouncycastle.cms.CMSProcessableByteArray;
-import org.bouncycastle.cms.CMSSignedData;
-import org.bouncycastle.cms.SignerInformation;
-import org.bouncycastle.cms.SignerInformationStore;
-import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
-import org.bouncycastle.util.Store;
public class MultipartSignedEntity extends MultipartMimeEntity {
@@ -51,44 +36,6 @@ public class MultipartSignedEntity extends
MultipartMimeEntity {
this.isMainBody = isMainBody;
}
- public boolean isValid() {
- MimeEntity signedEntity = getSignedDataEntity();
- ApplicationPkcs7SignatureEntity applicationPkcs7SignatureEntity =
getSignatureEntity();
-
- if (signedEntity == null || applicationPkcs7SignatureEntity == null) {
- return false;
- }
-
- try {
- ByteArrayOutputStream outstream = new ByteArrayOutputStream();
- signedEntity.writeTo(outstream);
- CMSProcessable signedContent = new
CMSProcessableByteArray(outstream.toByteArray());
-
- byte[] signature = applicationPkcs7SignatureEntity.getSignature();
- InputStream is = new ByteArrayInputStream(signature);
-
- CMSSignedData signedData = new CMSSignedData(signedContent, is);
-
- Store<X509CertificateHolder> store = signedData.getCertificates();
- SignerInformationStore signers = signedData.getSignerInfos();
-
- for (SignerInformation signer : signers.getSigners()) {
- @SuppressWarnings("unchecked")
- Collection<X509CertificateHolder> certCollection =
store.getMatches(signer.getSID());
-
- X509CertificateHolder certHolder =
certCollection.iterator().next();
- X509Certificate cert = new
JcaX509CertificateConverter().setProvider("BC").getCertificate(certHolder);
- if (!signer.verify(new
JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(cert))) {
- return false;
- }
- }
- } catch (Exception e) {
- return false;
- }
-
- return true;
- }
-
public MimeEntity getSignedDataEntity() {
if (getPartCount() > 0) {
return getPart(0);
diff --git
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java
index 1cb577e25a0..5cfb275ece0 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/protocol/ResponseMDN.java
@@ -83,12 +83,13 @@ public class ResponseMDN implements HttpResponseInterceptor
{
private PrivateKey signingPrivateKey;
private PrivateKey decryptingPrivateKey;
private String mdnMessageTemplate;
+ private Certificate[] validateSigningCertificateChain;
private VelocityEngine velocityEngine;
public ResponseMDN(String as2Version, String serverFQDN,
AS2SignatureAlgorithm signingAlgorithm,
Certificate[] signingCertificateChain, PrivateKey
signingPrivateKey, PrivateKey decryptingPrivateKey,
- String mdnMessageTemplate) {
+ String mdnMessageTemplate, Certificate[]
validateSigningCertificateChain) {
this.as2Version = as2Version;
this.serverFQDN = serverFQDN;
this.signingAlgorithm = signingAlgorithm;
@@ -102,6 +103,7 @@ public class ResponseMDN implements HttpResponseInterceptor
{
} else {
this.mdnMessageTemplate = DEFAULT_MDN_MESSAGE_TEMPLATE;
}
+ this.validateSigningCertificateChain = validateSigningCertificateChain;
}
@Override
@@ -145,7 +147,7 @@ public class ResponseMDN implements HttpResponseInterceptor
{
multipartReportEntity = new
DispositionNotificationMultipartReportEntity(
httpEntityEnclosingRequest, response,
DispositionMode.AUTOMATIC_ACTION_MDN_SENT_AUTOMATICALLY,
AS2DispositionType.FAILED, null, null, null, null, null,
StandardCharsets.US_ASCII.name(), boundary, true,
- decryptingPrivateKey, mdnMessage);
+ decryptingPrivateKey, mdnMessage,
validateSigningCertificateChain);
} else {
String mdnMessage =
createMdnDescription(httpEntityEnclosingRequest, response,
DispositionMode.AUTOMATIC_ACTION_MDN_SENT_AUTOMATICALLY,
@@ -155,7 +157,7 @@ public class ResponseMDN implements HttpResponseInterceptor
{
httpEntityEnclosingRequest, response,
DispositionMode.AUTOMATIC_ACTION_MDN_SENT_AUTOMATICALLY,
AS2DispositionType.PROCESSED, null, null, null, null,
null, StandardCharsets.US_ASCII.name(), boundary,
true,
- decryptingPrivateKey, mdnMessage);
+ decryptingPrivateKey, mdnMessage,
validateSigningCertificateChain);
}
DispositionNotificationOptions dispositionNotificationOptions =
DispositionNotificationOptionsParser
diff --git
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
index e8068e9f7ec..b2027569d4f 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
@@ -17,6 +17,7 @@
package org.apache.camel.component.as2.api.util;
import java.security.PrivateKey;
+import java.security.cert.Certificate;
import java.util.Objects;
import org.apache.camel.component.as2.api.AS2Header;
@@ -126,7 +127,8 @@ public final class HttpMessageUtils {
return null;
}
- public static ApplicationEDIEntity extractEdiPayload(HttpMessage message,
PrivateKey privateKey) throws HttpException {
+ public static ApplicationEDIEntity extractEdiPayload(HttpMessage message,
DecrpytingAndSigningInfo decrpytingAndSigningInfo)
+ throws HttpException {
String contentTypeString = getHeaderValue(message,
AS2Header.CONTENT_TYPE);
if (contentTypeString == null) {
@@ -144,17 +146,17 @@ public final class HttpMessageUtils {
break;
}
case AS2MimeType.MULTIPART_SIGNED: {
- ediEntity = extractMultipartSigned(message);
+ ediEntity = extractMultipartSigned(message,
decrpytingAndSigningInfo);
break;
}
case AS2MimeType.APPLICATION_PKCS7_MIME: {
switch (contentType.getParameter("smime-type")) {
case "compressed-data": {
- ediEntity = extractCompressedData(message);
+ ediEntity = extractCompressedData(message,
decrpytingAndSigningInfo);
break;
}
case "enveloped-data": {
- ediEntity = extractEnvelopedData(message, privateKey);
+ ediEntity = extractEnvelopedData(message,
decrpytingAndSigningInfo);
break;
}
default:
@@ -174,9 +176,11 @@ public final class HttpMessageUtils {
}
- private static ApplicationEDIEntity extractEnvelopedData(HttpMessage
message, PrivateKey privateKey) throws HttpException {
+ private static ApplicationEDIEntity extractEnvelopedData(
+ HttpMessage message, DecrpytingAndSigningInfo
decrpytingAndSigningInfo)
+ throws HttpException {
ApplicationEDIEntity ediEntity;
- if (privateKey == null) {
+ if (decrpytingAndSigningInfo.getDecryptingPrivateKey() == null) {
throw new HttpException(
"Failed to extract EDI payload: private key can not be
null for AS2 enveloped message");
}
@@ -185,11 +189,13 @@ public final class HttpMessageUtils {
Objects.requireNonNull(envelopedDataEntity,
"Failed to extract EDI payload: the enveloped data entity is
null");
- ediEntity = extractEdiPayloadFromEnvelopedEntity(envelopedDataEntity,
privateKey);
+ ediEntity = extractEdiPayloadFromEnvelopedEntity(envelopedDataEntity,
decrpytingAndSigningInfo);
return ediEntity;
}
- private static ApplicationEDIEntity extractCompressedData(HttpMessage
message) throws HttpException {
+ private static ApplicationEDIEntity extractCompressedData(
+ HttpMessage message, DecrpytingAndSigningInfo
decrpytingAndSigningInfo)
+ throws HttpException {
ApplicationEDIEntity ediEntity;
ApplicationPkcs7MimeCompressedDataEntity compressedDataEntity
= getEntity(message,
ApplicationPkcs7MimeCompressedDataEntity.class);
@@ -197,11 +203,13 @@ public final class HttpMessageUtils {
Objects.requireNonNull(compressedDataEntity,
"Failed to extract the EDI payload: the compressed data entity
is null");
- ediEntity =
extractEdiPayloadFromCompressedEntity(compressedDataEntity);
+ ediEntity =
extractEdiPayloadFromCompressedEntity(compressedDataEntity,
decrpytingAndSigningInfo);
return ediEntity;
}
- private static ApplicationEDIEntity extractMultipartSigned(HttpMessage
message) throws HttpException {
+ private static ApplicationEDIEntity extractMultipartSigned(
+ HttpMessage message, DecrpytingAndSigningInfo
decrpytingAndSigningInfo)
+ throws HttpException {
ApplicationEDIEntity ediEntity;
MultipartSignedEntity multipartSignedEntity = getEntity(message,
MultipartSignedEntity.class);
@@ -209,13 +217,18 @@ public final class HttpMessageUtils {
Objects.requireNonNull(multipartSignedEntity,
"Failed to extract EDI payload: the multipart signed entity is
null");
+ if (decrpytingAndSigningInfo.getValidateSigningCertificateChain() !=
null && !SigningUtils
+ .isValid(multipartSignedEntity,
decrpytingAndSigningInfo.getValidateSigningCertificateChain())) {
+ throw new HttpException("Failed to validate the signature");
+ }
+
MimeEntity mimeEntity = multipartSignedEntity.getSignedDataEntity();
if (mimeEntity instanceof ApplicationEDIEntity) {
ediEntity = (ApplicationEDIEntity) mimeEntity;
} else if (mimeEntity instanceof
ApplicationPkcs7MimeCompressedDataEntity) {
ApplicationPkcs7MimeCompressedDataEntity compressedDataEntity
= (ApplicationPkcs7MimeCompressedDataEntity) mimeEntity;
- ediEntity =
extractEdiPayloadFromCompressedEntity(compressedDataEntity);
+ ediEntity =
extractEdiPayloadFromCompressedEntity(compressedDataEntity,
decrpytingAndSigningInfo);
} else {
throw new HttpException(
"Failed to extract EDI payload: invalid content type '" +
mimeEntity.getContentTypeValue()
@@ -225,11 +238,11 @@ public final class HttpMessageUtils {
}
private static ApplicationEDIEntity extractEdiPayloadFromEnvelopedEntity(
- ApplicationPkcs7MimeEnvelopedDataEntity envelopedDataEntity,
PrivateKey privateKey)
+ ApplicationPkcs7MimeEnvelopedDataEntity envelopedDataEntity,
DecrpytingAndSigningInfo decrpytingAndSigningInfo)
throws HttpException {
ApplicationEDIEntity ediEntity = null;
- MimeEntity entity = envelopedDataEntity.getEncryptedEntity(privateKey);
+ MimeEntity entity =
envelopedDataEntity.getEncryptedEntity(decrpytingAndSigningInfo.getDecryptingPrivateKey());
String contentTypeString = entity.getContentTypeValue();
if (contentTypeString == null) {
throw new HttpException("Failed to extract EDI message: content
type missing from encrypted entity");
@@ -245,13 +258,18 @@ public final class HttpMessageUtils {
}
case AS2MimeType.MULTIPART_SIGNED: {
MultipartSignedEntity multipartSignedEntity =
(MultipartSignedEntity) entity;
+ if
(decrpytingAndSigningInfo.getValidateSigningCertificateChain() != null &&
!SigningUtils
+ .isValid(multipartSignedEntity,
decrpytingAndSigningInfo.getValidateSigningCertificateChain())) {
+ throw new HttpException("Failed to validate the
signature");
+ }
+
MimeEntity mimeEntity =
multipartSignedEntity.getSignedDataEntity();
if (mimeEntity instanceof ApplicationEDIEntity) {
ediEntity = (ApplicationEDIEntity) mimeEntity;
} else if (mimeEntity instanceof
ApplicationPkcs7MimeCompressedDataEntity) {
ApplicationPkcs7MimeCompressedDataEntity
compressedDataEntity
= (ApplicationPkcs7MimeCompressedDataEntity)
mimeEntity;
- ediEntity =
extractEdiPayloadFromCompressedEntity(compressedDataEntity);
+ ediEntity =
extractEdiPayloadFromCompressedEntity(compressedDataEntity,
decrpytingAndSigningInfo);
} else {
throw new HttpException(
@@ -268,7 +286,7 @@ public final class HttpMessageUtils {
}
ApplicationPkcs7MimeCompressedDataEntity compressedDataEntity
= (ApplicationPkcs7MimeCompressedDataEntity) entity;
- ediEntity =
extractEdiPayloadFromCompressedEntity(compressedDataEntity);
+ ediEntity =
extractEdiPayloadFromCompressedEntity(compressedDataEntity,
decrpytingAndSigningInfo);
break;
}
default:
@@ -281,7 +299,7 @@ public final class HttpMessageUtils {
}
public static ApplicationEDIEntity extractEdiPayloadFromCompressedEntity(
- ApplicationPkcs7MimeCompressedDataEntity compressedDataEntity)
+ ApplicationPkcs7MimeCompressedDataEntity compressedDataEntity,
DecrpytingAndSigningInfo decrpytingAndSigningInfo)
throws HttpException {
ApplicationEDIEntity ediEntity = null;
@@ -301,6 +319,11 @@ public final class HttpMessageUtils {
}
case AS2MimeType.MULTIPART_SIGNED: {
MultipartSignedEntity multipartSignedEntity =
(MultipartSignedEntity) entity;
+ if
(decrpytingAndSigningInfo.getValidateSigningCertificateChain() != null &&
!SigningUtils
+ .isValid(multipartSignedEntity,
decrpytingAndSigningInfo.getValidateSigningCertificateChain())) {
+ throw new HttpException("Failed to validate the
signature");
+ }
+
MimeEntity mimeEntity =
multipartSignedEntity.getSignedDataEntity();
if (mimeEntity instanceof ApplicationEDIEntity) {
ediEntity = (ApplicationEDIEntity) mimeEntity;
@@ -321,4 +344,22 @@ public final class HttpMessageUtils {
return ediEntity;
}
+ public static class DecrpytingAndSigningInfo {
+ private Certificate[] validateSigningCertificateChain;
+ private PrivateKey decryptingPrivateKey;
+
+ public DecrpytingAndSigningInfo(Certificate[]
validateSigningCertificateChain, PrivateKey decryptingPrivateKey) {
+ this.validateSigningCertificateChain =
validateSigningCertificateChain;
+ this.decryptingPrivateKey = decryptingPrivateKey;
+ }
+
+ public Certificate[] getValidateSigningCertificateChain() {
+ return validateSigningCertificateChain;
+ }
+
+ public PrivateKey getDecryptingPrivateKey() {
+ return decryptingPrivateKey;
+ }
+
+ }
}
diff --git
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java
index af84d5da6ad..87f42229fa1 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java
@@ -21,6 +21,7 @@ import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
+import java.security.cert.Certificate;
import org.apache.camel.component.as2.api.AS2Header;
import org.apache.camel.component.as2.api.AS2MicAlgorithm;
@@ -79,7 +80,7 @@ public final class MicUtils {
}
public static ReceivedContentMic createReceivedContentMic(
- HttpEntityEnclosingRequest request, PrivateKey
decryptingPrivateKey)
+ HttpEntityEnclosingRequest request, Certificate[]
validateSigningCertificateChain, PrivateKey decryptingPrivateKey)
throws HttpException {
String dispositionNotificationOptionsString
@@ -97,7 +98,8 @@ public final class MicUtils {
return null;
}
- HttpEntity entity = HttpMessageUtils.extractEdiPayload(request,
decryptingPrivateKey);
+ HttpEntity entity = HttpMessageUtils.extractEdiPayload(request,
+ new
HttpMessageUtils.DecrpytingAndSigningInfo(validateSigningCertificateChain,
decryptingPrivateKey));
byte[] content = EntityUtils.getContent(entity);
diff --git
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/SigningUtils.java
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/SigningUtils.java
index 7a17387a1ef..f2cdf8f44c0 100644
---
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/SigningUtils.java
+++
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/SigningUtils.java
@@ -16,6 +16,9 @@
*/
package org.apache.camel.component.as2.api.util;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
import java.security.PrivateKey;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
@@ -24,6 +27,9 @@ import java.util.Arrays;
import org.apache.camel.component.as2.api.AS2SignatureAlgorithm;
import org.apache.camel.component.as2.api.AS2SignedDataGenerator;
+import
org.apache.camel.component.as2.api.entity.ApplicationPkcs7SignatureEntity;
+import org.apache.camel.component.as2.api.entity.MimeEntity;
+import org.apache.camel.component.as2.api.entity.MultipartSignedEntity;
import org.apache.camel.util.ObjectHelper;
import org.apache.http.HttpException;
import org.bouncycastle.asn1.ASN1EncodableVector;
@@ -36,11 +42,21 @@ import
org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerId;
import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.SignerInformationVerifier;
+import org.bouncycastle.cms.SignerInformationVerifierProvider;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public final class SigningUtils {
+ private static final Logger LOG =
LoggerFactory.getLogger(SigningUtils.class);
+
private SigningUtils() {
}
@@ -93,4 +109,51 @@ public final class SigningUtils {
return gen;
}
+
+ public static boolean isValidSigned(byte[] signedContent, byte[]
signature, Certificate[] signingCertificateChain) {
+ if (signedContent == null || signature == null ||
signingCertificateChain == null) {
+ return false;
+ }
+
+ try {
+ CMSSignedData signedData
+ = new CMSSignedData(new
CMSProcessableByteArray(signedContent), new ByteArrayInputStream(signature));
+
+ SignerInformationVerifierProvider sivp = (SignerId sid) -> {
+ for (Certificate knownCert : signingCertificateChain) {
+ SignerInformationVerifier siv
+ = new
JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build((X509Certificate)
knownCert);
+ if
(siv.getAssociatedCertificate().getIssuer().equals(sid.getIssuer())
+ &&
siv.getAssociatedCertificate().getSerialNumber().equals(sid.getSerialNumber()))
{
+ return siv;
+ }
+ }
+ throw new RuntimeException("Signature was created with an
unknown certificate");
+ };
+
+ return signedData.verifySignatures(sivp);
+ } catch (CMSException e) {
+ //Probably the signature was created with an unknown certificate
or something else is wrong with the signature
+ LOG.debug(e.getMessage(), e);
+ } catch (Exception e) {
+ LOG.debug(e.getMessage(), e);
+ }
+ return false;
+ }
+
+ public static boolean isValid(MultipartSignedEntity multipartSignedEntity,
Certificate[] signingCertificateChain) {
+ MimeEntity signedEntity = multipartSignedEntity.getSignedDataEntity();
+ ApplicationPkcs7SignatureEntity applicationPkcs7SignatureEntity =
multipartSignedEntity.getSignatureEntity();
+ if (signedEntity == null || applicationPkcs7SignatureEntity == null) {
+ return false;
+ }
+
+ try (ByteArrayOutputStream o = new ByteArrayOutputStream()) {
+ signedEntity.writeTo(o);
+ return isValidSigned(o.toByteArray(),
applicationPkcs7SignatureEntity.getSignature(), signingCertificateChain);
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
}
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 d4c68c9dadc..5643af6957e 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
@@ -58,6 +58,7 @@ import org.apache.camel.component.as2.api.util.EntityUtils;
import org.apache.camel.component.as2.api.util.HttpMessageUtils;
import org.apache.camel.component.as2.api.util.MicUtils;
import org.apache.camel.component.as2.api.util.MicUtils.ReceivedContentMic;
+import org.apache.camel.component.as2.api.util.SigningUtils;
import org.apache.camel.test.AvailablePortFinder;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
@@ -140,6 +141,7 @@ public class AS2MessageTest {
private static final Duration HTTP_CONNECTION_TIMEOUT =
Duration.ofSeconds(5);
private static final Integer HTTP_CONNECTION_POOL_SIZE = 5;
private static final Duration HTTP_CONNECTION_POOL_TTL =
Duration.ofMinutes(15);
+ private static final Certificate[] VALIDATE_SIGNING_CERTIFICATE_CHAIN =
null;
private static final String RECIPIENT_DELIVERY_ADDRESS =
"http://localhost:" + TARGET_PORT + "/handle-receipts";
private static final String AS2_VERSION = "1.1";
private static final String USER_AGENT = "Camel AS2 Endpoint";
@@ -214,7 +216,8 @@ public class AS2MessageTest {
testServer = new AS2ServerConnection(
AS2_VERSION, "MyServer-HTTP/1.1", SERVER_FQDN, TARGET_PORT,
AS2SignatureAlgorithm.SHA256WITHRSA,
- certList.toArray(new Certificate[0]), signingKP.getPrivate(),
decryptingKP.getPrivate(), MDN_MESSAGE_TEMPLATE);
+ certList.toArray(new Certificate[0]), signingKP.getPrivate(),
decryptingKP.getPrivate(), MDN_MESSAGE_TEMPLATE,
+ VALIDATE_SIGNING_CERTIFICATE_CHAIN);
testServer.listen("*", new HttpRequestHandler() {
@Override
public void handle(HttpRequest request, HttpResponse response,
HttpContext context)
@@ -224,7 +227,8 @@ public class AS2MessageTest {
context.setAttribute(AS2ServerManager.SUBJECT, SUBJECT);
context.setAttribute(AS2ServerManager.FROM, AS2_NAME);
LOG.debug("{}", AS2Utils.printMessage(request));
- ediEntity = HttpMessageUtils.extractEdiPayload(request,
testServer.getDecryptingPrivateKey());
+ ediEntity = HttpMessageUtils.extractEdiPayload(request,
new HttpMessageUtils.DecrpytingAndSigningInfo(
+ testServer.getValidateSigningCertificateChain(),
testServer.getDecryptingPrivateKey()));
} catch (Exception e) {
throw new HttpException("Failed to parse AS2 Message
Entity", e);
}
@@ -658,7 +662,7 @@ public class AS2MessageTest {
assertNotNull(signatureEntity, "Multipart signed entity does not
contain signature entity");
// Validate Signature
- assertTrue(multipartSignedEntity.isValid(), "Signature is invalid");
+ assertTrue(SigningUtils.isValid(multipartSignedEntity, new
Certificate[] { signingCert }), "Signature is invalid");
}
@@ -699,7 +703,7 @@ public class AS2MessageTest {
assertNotNull(signatureEntity, "Signature Entity");
// Validate Signature
- assertTrue(responseSignedEntity.isValid(), "Signature is invalid");
+ assertTrue(SigningUtils.isValid(responseSignedEntity, new
Certificate[] { signingCert }), "Signature is invalid");
}
@Test
@@ -741,7 +745,7 @@ public class AS2MessageTest {
request,
response,
DispositionMode.AUTOMATIC_ACTION_MDN_SENT_AUTOMATICALLY,
AS2DispositionType.PROCESSED,
dispositionModifier, failureFields, errorFields,
warningFields, extensionFields, null, "boundary", true,
- null, "Got ya message!");
+ null, "Got ya message!", null);
// Send MDN
HttpCoreContext httpContext = mdnManager.send(mdn,
RECIPIENT_DELIVERY_ADDRESS);
@@ -766,7 +770,7 @@ public class AS2MessageTest {
assertArrayEquals(errorFields, mdnEntity.getErrorFields(), "Unexpected
value for Error Fields");
assertArrayEquals(warningFields, mdnEntity.getWarningFields(),
"Unexpected value for Warning Fields");
assertEquals(extensionFields, mdnEntity.getExtensionFields(),
"Unexpected value for Extension Fields");
- ReceivedContentMic expectedMic =
MicUtils.createReceivedContentMic(request, null);
+ ReceivedContentMic expectedMic =
MicUtils.createReceivedContentMic(request, null, null);
ReceivedContentMic mdnMic = mdnEntity.getReceivedContentMic();
assertEquals(expectedMic.getEncodedMessageDigest(),
mdnMic.getEncodedMessageDigest(),
"Unexpected value for Received Content Mic");
diff --git
a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java
index 84629b94893..ad85477c12d 100644
---
a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java
+++
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java
@@ -101,7 +101,7 @@ public class MicUtilsTest {
basicEntity.setContentType(CONTENT_TYPE_VALUE);
request.setEntity(basicEntity);
- ReceivedContentMic receivedContentMic =
MicUtils.createReceivedContentMic(request, null);
+ ReceivedContentMic receivedContentMic =
MicUtils.createReceivedContentMic(request, null, null);
assertNotNull(receivedContentMic, "Failed to create Received Content
MIC");
LOG.debug("Digest Algorithm: " +
receivedContentMic.getDigestAlgorithmId());
assertEquals(EXPECTED_MESSAGE_DIGEST_ALGORITHM,
receivedContentMic.getDigestAlgorithmId(),
diff --git
a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/SigningUtilsTest.java
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/SigningUtilsTest.java
new file mode 100644
index 00000000000..8533efe3b9a
--- /dev/null
+++
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/SigningUtilsTest.java
@@ -0,0 +1,110 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.as2.api.util;
+
+import java.nio.charset.StandardCharsets;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.SecureRandom;
+import java.security.Security;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+import org.apache.camel.component.as2.api.AS2SignatureAlgorithm;
+import org.apache.camel.component.as2.api.AS2SignedDataGenerator;
+import org.apache.camel.component.as2.api.Utils;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.SignerId;
+import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import org.bouncycastle.jce.provider.BouncyCastleProvider;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class SigningUtilsTest {
+
+ private static KeyPair signingKP;
+ private static X509Certificate signingCert;
+ private static X509Certificate evilSigningCert;
+ private static final String MESSAGE = "Test message to be signed";
+
+ @BeforeEach
+ public void setUp() throws Exception {
+ Security.addProvider(new BouncyCastleProvider());
+ setupKeysAndCertificates();
+ }
+
+ @AfterEach
+ public void tearDown() throws Exception {
+ }
+
+ @Test
+ public void createSigningGeneratorTest() throws Exception {
+ AS2SignedDataGenerator gen =
SigningUtils.createSigningGenerator(AS2SignatureAlgorithm.SHA1WITHRSA,
+ new Certificate[] { signingCert }, signingKP.getPrivate());
+ CMSProcessableByteArray sData = new
CMSProcessableByteArray(MESSAGE.getBytes(StandardCharsets.UTF_8));
+ CMSSignedData signedData = gen.generate(sData, true);
+
+ assertTrue(signedData.verifySignatures((SignerId sid) -> {
+ return new
JcaSimpleSignerInfoVerifierBuilder().setProvider("BC").build(signingCert);
+ }), "Message was wrongly signed");
+ }
+
+ @Test
+ public void isValidSignedTest() throws Exception {
+ AS2SignedDataGenerator gen =
SigningUtils.createSigningGenerator(AS2SignatureAlgorithm.SHA1WITHRSA,
+ new Certificate[] { signingCert }, signingKP.getPrivate());
+ CMSProcessableByteArray sData = new
CMSProcessableByteArray(MESSAGE.getBytes(StandardCharsets.UTF_8));
+ CMSSignedData signedData = gen.generate(sData, true);
+
assertTrue(SigningUtils.isValidSigned(MESSAGE.getBytes(StandardCharsets.UTF_8),
signedData.getEncoded(),
+ new Certificate[] { signingCert }), "Message must be valid");
+
assertFalse(SigningUtils.isValidSigned(MESSAGE.getBytes(StandardCharsets.UTF_8),
signedData.getEncoded(),
+ new Certificate[] { evilSigningCert }), "Message must be
invalid");
+ }
+
+ private static void setupKeysAndCertificates() throws Exception {
+ //
+ // set up our certificates
+ //
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+ kpg.initialize(1024, new SecureRandom());
+ //
+ // certificate we sign against
+ //
+ {
+ String signingDN = "O=AS2 Test Orgaisation, C=US";
+ signingKP = kpg.generateKeyPair();
+ signingCert = Utils.makeCertificate(
+ signingKP, signingDN, signingKP, signingDN);
+ }
+
+ //
+ // certificate some else signed against
+ //
+ {
+ String evilSigningDN = "O=Evil Haxor Coorp, C=RU";
+ KeyPair evilSigningKP = kpg.generateKeyPair();
+ evilSigningCert = Utils.makeCertificate(
+ evilSigningKP, evilSigningDN, evilSigningKP,
evilSigningDN);
+ }
+ }
+
+}
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
index 374bbfa4bef..25bfcad9f8e 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
@@ -55,6 +55,7 @@ public class AS2ClientManagerEndpointConfigurationConfigurer
extends org.apache.
map.put("TargetHostname", java.lang.String.class);
map.put("TargetPortNumber", java.lang.Integer.class);
map.put("UserAgent", java.lang.String.class);
+ map.put("ValidateSigningCertificateChain",
java.security.cert.Certificate[].class);
ALL_OPTIONS = map;
}
@@ -132,6 +133,8 @@ public class
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
case "TargetPortNumber":
target.setTargetPortNumber(property(camelContext, java.lang.Integer.class,
value)); return true;
case "useragent":
case "UserAgent": target.setUserAgent(property(camelContext,
java.lang.String.class, value)); return true;
+ case "validatesigningcertificatechain":
+ case "ValidateSigningCertificateChain":
target.setValidateSigningCertificateChain(property(camelContext,
java.security.cert.Certificate[].class, value)); return true;
default: return false;
}
}
@@ -214,6 +217,8 @@ public class
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
case "TargetPortNumber": return java.lang.Integer.class;
case "useragent":
case "UserAgent": return java.lang.String.class;
+ case "validatesigningcertificatechain":
+ case "ValidateSigningCertificateChain": return
java.security.cert.Certificate[].class;
default: return null;
}
}
@@ -292,6 +297,8 @@ public class
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
case "TargetPortNumber": return target.getTargetPortNumber();
case "useragent":
case "UserAgent": return target.getUserAgent();
+ case "validatesigningcertificatechain":
+ case "ValidateSigningCertificateChain": return
target.getValidateSigningCertificateChain();
default: return null;
}
}
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
index 46225f9739a..2924d31cacd 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
@@ -53,6 +53,7 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
map.put("TargetHostname", java.lang.String.class);
map.put("TargetPortNumber", java.lang.Integer.class);
map.put("UserAgent", java.lang.String.class);
+ map.put("ValidateSigningCertificateChain",
java.security.cert.Certificate[].class);
ALL_OPTIONS = map;
}
@@ -126,6 +127,8 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
case "TargetPortNumber":
target.setTargetPortNumber(property(camelContext, java.lang.Integer.class,
value)); return true;
case "useragent":
case "UserAgent": target.setUserAgent(property(camelContext,
java.lang.String.class, value)); return true;
+ case "validatesigningcertificatechain":
+ case "ValidateSigningCertificateChain":
target.setValidateSigningCertificateChain(property(camelContext,
java.security.cert.Certificate[].class, value)); return true;
default: return false;
}
}
@@ -204,6 +207,8 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
case "TargetPortNumber": return java.lang.Integer.class;
case "useragent":
case "UserAgent": return java.lang.String.class;
+ case "validatesigningcertificatechain":
+ case "ValidateSigningCertificateChain": return
java.security.cert.Certificate[].class;
default: return null;
}
}
@@ -278,6 +283,8 @@ public class AS2ConfigurationConfigurer extends
org.apache.camel.support.compone
case "TargetPortNumber": return target.getTargetPortNumber();
case "useragent":
case "UserAgent": return target.getUserAgent();
+ case "validatesigningcertificatechain":
+ case "ValidateSigningCertificateChain": return
target.getValidateSigningCertificateChain();
default: return null;
}
}
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
index a6252b60c7c..0222151dcde 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
@@ -54,6 +54,7 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
map.put("targetHostname", java.lang.String.class);
map.put("targetPortNumber", java.lang.Integer.class);
map.put("userAgent", java.lang.String.class);
+ map.put("validateSigningCertificateChain",
java.security.cert.Certificate[].class);
map.put("exceptionHandler",
org.apache.camel.spi.ExceptionHandler.class);
map.put("exchangePattern", org.apache.camel.ExchangePattern.class);
map.put("lazyStartProducer", boolean.class);
@@ -131,6 +132,8 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
case "targetPortNumber":
target.getConfiguration().setTargetPortNumber(property(camelContext,
java.lang.Integer.class, value)); return true;
case "useragent":
case "userAgent":
target.getConfiguration().setUserAgent(property(camelContext,
java.lang.String.class, value)); return true;
+ case "validatesigningcertificatechain":
+ case "validateSigningCertificateChain":
target.getConfiguration().setValidateSigningCertificateChain(property(camelContext,
java.security.cert.Certificate[].class, value)); return true;
default: return false;
}
}
@@ -210,6 +213,8 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
case "targetPortNumber": return java.lang.Integer.class;
case "useragent":
case "userAgent": return java.lang.String.class;
+ case "validatesigningcertificatechain":
+ case "validateSigningCertificateChain": return
java.security.cert.Certificate[].class;
default: return null;
}
}
@@ -285,6 +290,8 @@ public class AS2EndpointConfigurer extends
PropertyConfigurerSupport implements
case "targetPortNumber": return
target.getConfiguration().getTargetPortNumber();
case "useragent":
case "userAgent": return target.getConfiguration().getUserAgent();
+ case "validatesigningcertificatechain":
+ case "validateSigningCertificateChain": return
target.getConfiguration().getValidateSigningCertificateChain();
default: return null;
}
}
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
index 3d138d2b9d8..2528eb6bf04 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
@@ -21,7 +21,7 @@ public class AS2EndpointUriFactory extends
org.apache.camel.support.component.En
private static final Set<String> SECRET_PROPERTY_NAMES;
private static final Set<String> MULTI_VALUE_PREFIXES;
static {
- Set<String> props = new HashSet<>(40);
+ Set<String> props = new HashSet<>(41);
props.add("apiName");
props.add("as2From");
props.add("as2MessageStructure");
@@ -62,6 +62,7 @@ public class AS2EndpointUriFactory extends
org.apache.camel.support.component.En
props.add("targetHostname");
props.add("targetPortNumber");
props.add("userAgent");
+ props.add("validateSigningCertificateChain");
PROPERTY_NAMES = Collections.unmodifiableSet(props);
SECRET_PROPERTY_NAMES = Collections.emptySet();
MULTI_VALUE_PREFIXES = Collections.emptySet();
diff --git
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
index 7952b980868..e4ca3775905 100644
---
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
+++
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
@@ -54,6 +54,7 @@ public class AS2ServerManagerEndpointConfigurationConfigurer
extends org.apache.
map.put("TargetHostname", java.lang.String.class);
map.put("TargetPortNumber", java.lang.Integer.class);
map.put("UserAgent", java.lang.String.class);
+ map.put("ValidateSigningCertificateChain",
java.security.cert.Certificate[].class);
ALL_OPTIONS = map;
}
@@ -129,6 +130,8 @@ public class
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
case "TargetPortNumber":
target.setTargetPortNumber(property(camelContext, java.lang.Integer.class,
value)); return true;
case "useragent":
case "UserAgent": target.setUserAgent(property(camelContext,
java.lang.String.class, value)); return true;
+ case "validatesigningcertificatechain":
+ case "ValidateSigningCertificateChain":
target.setValidateSigningCertificateChain(property(camelContext,
java.security.cert.Certificate[].class, value)); return true;
default: return false;
}
}
@@ -209,6 +212,8 @@ public class
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
case "TargetPortNumber": return java.lang.Integer.class;
case "useragent":
case "UserAgent": return java.lang.String.class;
+ case "validatesigningcertificatechain":
+ case "ValidateSigningCertificateChain": return
java.security.cert.Certificate[].class;
default: return null;
}
}
@@ -285,6 +290,8 @@ public class
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
case "TargetPortNumber": return target.getTargetPortNumber();
case "useragent":
case "UserAgent": return target.getUserAgent();
+ case "validatesigningcertificatechain":
+ case "ValidateSigningCertificateChain": return
target.getValidateSigningCertificateChain();
default: return null;
}
}
diff --git
a/components/camel-as2/camel-as2-component/src/generated/resources/org/apache/camel/component/as2/as2.json
b/components/camel-as2/camel-as2-component/src/generated/resources/org/apache/camel/component/as2/as2.json
index 9c9f6756db1..d1e8b4de975 100644
---
a/components/camel-as2/camel-as2-component/src/generated/resources/org/apache/camel/component/as2/as2.json
+++
b/components/camel-as2/camel-as2-component/src/generated/resources/org/apache/camel/component/as2/as2.json
@@ -64,6 +64,7 @@
"targetHostname": { "kind": "parameter", "displayName": "Target Hostname",
"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 host name (IP or DNS name) of target
host." },
"targetPortNumber": { "kind": "parameter", "displayName": "Target 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 target host. -1 indicates
the scheme default port." },
"userAgent": { "kind": "parameter", "displayName": "User Agent", "group":
"common", "label": "", "required": false, "type": "string", "javaType":
"java.lang.String", "deprecated": false, "autowired": false, "secret": false,
"defaultValue": "Camel AS2 Client Endpoint", "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "The value included in the User-Agent message
header identifying the AS2 user agent." },
+ "validateSigningCertificateChain": { "kind": "parameter", "displayName":
"Validate Signing Certificate Chain", "group": "common", "label": "",
"required": false, "type": "object", "javaType":
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false,
"secret": false, "configurationClass":
"org.apache.camel.component.as2.AS2Configuration", "configurationField":
"configuration", "description": "Certifiates to validate the messages signature
against. If not supplied, v [...]
"exceptionHandler": { "kind": "parameter", "displayName": "Exception
Handler", "group": "consumer (advanced)", "label": "consumer,advanced",
"required": false, "type": "object", "javaType":
"org.apache.camel.spi.ExceptionHandler", "optionalPrefix": "consumer.",
"deprecated": false, "autowired": false, "secret": false, "description": "To
let the consumer use a custom ExceptionHandler. Notice if the option
bridgeErrorHandler is enabled then this option is not in use. By default the
con [...]
"exchangePattern": { "kind": "parameter", "displayName": "Exchange
Pattern", "group": "consumer (advanced)", "label": "consumer,advanced",
"required": false, "type": "object", "javaType":
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut",
"InOptionalOut" ], "deprecated": false, "autowired": false, "secret": false,
"description": "Sets the exchange pattern when the consumer creates an
exchange." },
"lazyStartProducer": { "kind": "parameter", "displayName": "Lazy Start
Producer", "group": "producer (advanced)", "label": "producer,advanced",
"required": false, "type": "boolean", "javaType": "boolean", "deprecated":
false, "autowired": false, "secret": false, "defaultValue": false,
"description": "Whether the producer should be started lazy (on the first
message). By starting lazy you can use this to allow CamelContext and routes to
startup in situations where a producer may other [...]
diff --git
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
index 1ce9ddfb9cd..aa01c91687c 100644
---
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
+++
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
@@ -108,6 +108,8 @@ public class AS2Configuration {
private Integer httpConnectionPoolSize = 5;
@UriParam(defaultValue = "15m")
private Duration httpConnectionPoolTtl = Duration.ofMinutes(15);
+ @UriParam
+ private Certificate[] validateSigningCertificateChain;
public AS2ApiName getApiName() {
return apiName;
@@ -490,4 +492,16 @@ public class AS2Configuration {
this.httpConnectionPoolTtl = httpConnectionPoolTtl;
}
+ public Certificate[] getValidateSigningCertificateChain() {
+ return validateSigningCertificateChain;
+ }
+
+ /**
+ * Certifiates to validate the messages signature against. If not
supplied, validation will not take place. Server:
+ * validates the received message. Client: not yet implemented, should
validate the MDN
+ */
+ public void setValidateSigningCertificateChain(Certificate[]
validateSigningCertificateChain) {
+ this.validateSigningCertificateChain = validateSigningCertificateChain;
+ }
+
}
diff --git
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java
index 2c6142b1eda..540fe9c0242 100644
---
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java
+++
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java
@@ -120,9 +120,11 @@ public class AS2Consumer extends
AbstractApiConsumer<AS2ApiName, AS2Configuratio
apiProxy.handleMDNResponse(context, getEndpoint().getSubject(),
ofNullable(getEndpoint().getFrom()).orElse(getEndpoint().getConfiguration().getServer()));
}
-
ApplicationEDIEntity ediEntity
- = HttpMessageUtils.extractEdiPayload(request,
as2ServerConnection.getDecryptingPrivateKey());
+ = HttpMessageUtils.extractEdiPayload(request,
+ new HttpMessageUtils.DecrpytingAndSigningInfo(
+
as2ServerConnection.getValidateSigningCertificateChain(),
+
as2ServerConnection.getDecryptingPrivateKey()));
// Set AS2 Interchange property and EDI message into body of input
message.
Exchange exchange = createExchange(false);
diff --git
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
index 13f5146ef18..da1b01e2eb4 100644
---
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
+++
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/internal/AS2ConnectionHelper.java
@@ -73,7 +73,8 @@ public final class AS2ConnectionHelper {
configuration.getAs2Version(),
configuration.getServer(),
configuration.getServerFqdn(),
configuration.getServerPortNumber(), configuration.getSigningAlgorithm(),
configuration.getSigningCertificateChain(),
configuration.getSigningPrivateKey(),
- configuration.getDecryptingPrivateKey(),
configuration.getMdnMessageTemplate());
+ configuration.getDecryptingPrivateKey(),
configuration.getMdnMessageTemplate(),
+ configuration.getValidateSigningCertificateChain());
serverConnections.put(configuration.getServerPortNumber(),
serverConnection);
}
return serverConnection;
diff --git
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientManagerIT.java
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientManagerIT.java
index f055938ca76..bb3eec87c38 100644
---
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientManagerIT.java
+++
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientManagerIT.java
@@ -24,9 +24,7 @@ import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
-import java.util.ArrayList;
import java.util.HashMap;
-import java.util.List;
import java.util.Map;
import org.apache.camel.CamelException;
@@ -43,7 +41,6 @@ import org.apache.camel.component.as2.api.AS2MimeType;
import org.apache.camel.component.as2.api.AS2ServerConnection;
import org.apache.camel.component.as2.api.AS2ServerManager;
import org.apache.camel.component.as2.api.AS2SignatureAlgorithm;
-import org.apache.camel.component.as2.api.AS2SignedDataGenerator;
import org.apache.camel.component.as2.api.entity.AS2DispositionModifier;
import org.apache.camel.component.as2.api.entity.AS2DispositionType;
import
org.apache.camel.component.as2.api.entity.AS2MessageDispositionNotificationEntity;
@@ -60,6 +57,7 @@ import org.apache.camel.component.as2.api.util.EntityUtils;
import org.apache.camel.component.as2.api.util.HttpMessageUtils;
import org.apache.camel.component.as2.api.util.MicUtils;
import org.apache.camel.component.as2.api.util.MicUtils.ReceivedContentMic;
+import org.apache.camel.component.as2.api.util.SigningUtils;
import org.apache.camel.component.as2.internal.AS2ApiCollection;
import org.apache.camel.component.as2.internal.AS2ClientManagerApiMethod;
import org.apache.camel.http.common.HttpMessage;
@@ -79,21 +77,10 @@ import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpCoreContext;
import org.apache.http.protocol.HttpDateGenerator;
import org.apache.http.protocol.HttpRequestHandler;
-import org.bouncycastle.asn1.ASN1EncodableVector;
-import org.bouncycastle.asn1.cms.AttributeTable;
-import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
-import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
-import org.bouncycastle.asn1.smime.SMIMECapability;
-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.JcaSimpleSignerInfoGeneratorBuilder;
import org.bouncycastle.cms.jcajce.ZlibExpanderProvider;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -164,63 +151,14 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
private static final String REPORTING_UA = "Server Responding with MDN";
private static AS2ServerConnection serverConnection;
- private static KeyPair serverSigningKP;
- private static List<X509Certificate> serverCertList;
+ private static KeyPair serverKP;
+ private static X509Certificate serverCert;
private static RequestHandler requestHandler;
private static final HttpDateGenerator DATE_GENERATOR = new
HttpDateGenerator();
- private KeyPair issueKP;
- private X509Certificate issueCert;
-
- private KeyPair signingKP;
- private KeyPair decryptingKP;
- private X509Certificate signingCert;
- private List<X509Certificate> certList;
- private AS2SignedDataGenerator gen;
-
- @Override
- @BeforeEach
- public void setUp() throws Exception {
- super.setUp();
- Security.addProvider(new BouncyCastleProvider());
-
- setupKeysAndCertificates();
-
- // Create and populate certificate store.
- JcaCertStore certs = new JcaCertStore(certList);
-
- // Create capabilities vector
- SMIMECapabilityVector capabilities = new SMIMECapabilityVector();
- capabilities.addCapability(SMIMECapability.dES_EDE3_CBC);
- capabilities.addCapability(SMIMECapability.rC2_CBC, 128);
- capabilities.addCapability(SMIMECapability.dES_CBC);
-
- // Create signing attributes
- ASN1EncodableVector attributes = new ASN1EncodableVector();
- attributes.add(new SMIMEEncryptionKeyPreferenceAttribute(
- new IssuerAndSerialNumber(new
X500Name(signingCert.getIssuerDN().getName()), signingCert.getSerialNumber())));
- attributes.add(new SMIMECapabilitiesAttribute(capabilities));
-
- for (String signingAlgorithmName : AS2SignedDataGenerator
-
.getSupportedSignatureAlgorithmNamesForKey(signingKP.getPrivate())) {
- try {
- this.gen = new AS2SignedDataGenerator();
- this.gen.addSignerInfoGenerator(new
JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC")
- .setSignedAttributeGenerator(new
AttributeTable(attributes))
- .build(signingAlgorithmName, signingKP.getPrivate(),
signingCert));
- this.gen.addCertificates(certs);
- break;
- } catch (Exception e) {
- this.gen = null;
- continue;
- }
- }
-
- if (this.gen == null) {
- throw new Exception("failed to create signing generator");
- }
- }
+ private static KeyPair clientKeyPair;
+ private static X509Certificate clientCert;
@Test
public void plainMessageSendTest() throws Exception {
@@ -409,7 +347,7 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
// parameter type is
org.apache.camel.component.as2.api.AS2EncryptionAlgorithm
headers.put("CamelAS2.encryptingAlgorithm",
AS2EncryptionAlgorithm.AES128_CBC);
// parameter type is java.security.cert.Certificate[]
- headers.put("CamelAS2.encryptingCertificateChain", certList);
+ headers.put("CamelAS2.encryptingCertificateChain", new Certificate[] {
clientCert });
// parameter type is String
headers.put("CamelAS2.attachedFileName", "");
@@ -428,7 +366,7 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
assertTrue(entity instanceof ApplicationPkcs7MimeEnvelopedDataEntity,
"Request body does not contain ApplicationPkcs7Mime entity");
MimeEntity envelopeEntity
- = ((ApplicationPkcs7MimeEnvelopedDataEntity)
entity).getEncryptedEntity(decryptingKP.getPrivate());
+ = ((ApplicationPkcs7MimeEnvelopedDataEntity)
entity).getEncryptedEntity(clientKeyPair.getPrivate());
assertTrue(envelopeEntity instanceof ApplicationEDIEntity, "Enveloped
entity is not an EDI entity");
String ediMessage = ((ApplicationEDIEntity)
envelopeEntity).getEdiMessage();
assertEquals(EDI_MESSAGE.replaceAll("[\n\r]", ""),
ediMessage.replaceAll("[\n\r]", ""), "EDI message is different");
@@ -499,9 +437,9 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
// parameter type is
org.apache.camel.component.as2.api.AS2SignatureAlgorithm
headers.put("CamelAS2.signingAlgorithm",
AS2SignatureAlgorithm.SHA512WITHRSA);
// parameter type is java.security.cert.Certificate[]
- headers.put("CamelAS2.signingCertificateChain", certList.toArray(new
Certificate[0]));
+ headers.put("CamelAS2.signingCertificateChain", new Certificate[] {
clientCert });
// parameter type is java.security.PrivateKey
- headers.put("CamelAS2.signingPrivateKey", signingKP.getPrivate());
+ headers.put("CamelAS2.signingPrivateKey", clientKeyPair.getPrivate());
// parameter type is String
headers.put("CamelAS2.dispositionNotificationTo", "[email protected]");
// parameter type is String[]
@@ -546,7 +484,8 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
assertNotNull(responseEntity, "Response entity");
assertTrue(responseEntity instanceof MultipartSignedEntity,
"Unexpected response entity type");
MultipartSignedEntity responseSignedEntity = (MultipartSignedEntity)
responseEntity;
- assertTrue(responseSignedEntity.isValid(), "Signature for response
entity is invalid");
+ assertTrue(SigningUtils.isValid(responseSignedEntity, new
Certificate[] { serverCert }),
+ "Signature for response entity is invalid");
MimeEntity responseSignedDataEntity =
responseSignedEntity.getSignedDataEntity();
assertTrue(responseSignedDataEntity instanceof
DispositionNotificationMultipartReportEntity,
"Signed entity wrong type");
@@ -579,7 +518,8 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
ReceivedContentMic receivedContentMic =
messageDispositionNotificationEntity.getReceivedContentMic();
ReceivedContentMic computedContentMic
- =
MicUtils.createReceivedContentMic((HttpEntityEnclosingRequest) request,
decryptingKP.getPrivate());
+ =
MicUtils.createReceivedContentMic((HttpEntityEnclosingRequest) request, new
Certificate[] { clientCert },
+ clientKeyPair.getPrivate());
assertEquals(computedContentMic.getEncodedMessageDigest(),
receivedContentMic.getEncodedMessageDigest(),
"Received content MIC does not match computed");
}
@@ -651,7 +591,8 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
assertNotNull(responseEntity, "Response entity");
assertTrue(responseEntity instanceof MultipartSignedEntity,
"Unexpected response entity type");
MultipartSignedEntity responseSignedEntity = (MultipartSignedEntity)
responseEntity;
- assertTrue(responseSignedEntity.isValid(), "Signature for response
entity is invalid");
+ assertTrue(SigningUtils.isValid(responseSignedEntity, new
Certificate[] { serverCert }),
+ "Signature for response entity is invalid");
MimeEntity responseSignedDataEntity =
responseSignedEntity.getSignedDataEntity();
assertTrue(responseSignedDataEntity instanceof
DispositionNotificationMultipartReportEntity,
"Signed entity wrong type");
@@ -684,7 +625,8 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
ReceivedContentMic receivedContentMic =
messageDispositionNotificationEntity.getReceivedContentMic();
ReceivedContentMic computedContentMic
- =
MicUtils.createReceivedContentMic((HttpEntityEnclosingRequest) request,
decryptingKP.getPrivate());
+ =
MicUtils.createReceivedContentMic((HttpEntityEnclosingRequest) request, new
Certificate[] { clientCert },
+ clientKeyPair.getPrivate());
assertEquals(computedContentMic.getEncodedMessageDigest(),
receivedContentMic.getEncodedMessageDigest(),
"Received content MIC does not match computed");
}
@@ -698,7 +640,7 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
private void runAsyncMDNTest() throws CamelException, HttpException {
AS2AsynchronousMDNManager mdnManager = new AS2AsynchronousMDNManager(
AS2_VERSION, ORIGIN_SERVER_NAME, SERVER_FQDN,
- certList.toArray(new X509Certificate[0]),
signingKP.getPrivate());
+ new Certificate[] { clientCert }, clientKeyPair.getPrivate());
// Create plain edi request message to acknowledge
ApplicationEDIEntity ediEntity =
EntityUtils.createEDIEntity(EDI_MESSAGE,
@@ -733,7 +675,7 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
request,
response,
DispositionMode.AUTOMATIC_ACTION_MDN_SENT_AUTOMATICALLY,
AS2DispositionType.PROCESSED,
dispositionModifier, failureFields, errorFields,
warningFields, extensionFields, null, "boundary",
- true, serverSigningKP.getPrivate(), "Got your message!");
+ true, serverKP.getPrivate(), "Got your message!", new
Certificate[] { clientCert });
// Send MDN
@SuppressWarnings("unused")
@@ -743,6 +685,7 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
@BeforeAll
public static void setupTest() throws Exception {
setupServerKeysAndCertificates();
+ setupClientKeysAndCertificates();
receiveTestMessages();
}
@@ -803,11 +746,15 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
}
};
// test route for send
- from("direct://SEND").to("as2://" + PATH_PREFIX +
"/send?inBody=ediMessage");
+ from("direct://SEND")
+ .to("as2://" + PATH_PREFIX +
"/send?inBody=ediMessage&httpSocketTimeout=5m&httpConnectionTimeout=5m");
// test route for send2
- from("direct://SEND2").toF("as2://" + PATH_PREFIX +
"/send?inBody=ediMessage&as2From=%s&as2To=%s", AS2_NAME,
- AS2_NAME);
+ from("direct://SEND2")
+ .toF("as2://" + PATH_PREFIX
+ +
"/send?inBody=ediMessage&as2From=%s&as2To=%s&httpSocketTimeout=5m&httpConnectionTimeout=5m",
+ AS2_NAME,
+ AS2_NAME);
from("jetty:http://localhost:" + MDN_TARGET_PORT +
"/handle-receipts").process(proc);
@@ -834,27 +781,21 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
// certificate we sign against
//
String signingDN = "CN=William J. Collins, [email protected],
O=Punkhorn Software, C=US";
- serverSigningKP = kpg.generateKeyPair();
- X509Certificate signingCert = Utils.makeCertificate(
- serverSigningKP, signingDN, issueKP, issueDN);
-
- serverCertList = new ArrayList<>();
-
- serverCertList.add(signingCert);
- serverCertList.add(issueCert);
+ serverKP = kpg.generateKeyPair();
+ serverCert = Utils.makeCertificate(serverKP, signingDN, issueKP,
issueDN);
}
private static void receiveTestMessages() throws IOException {
serverConnection = new AS2ServerConnection(
AS2_VERSION, ORIGIN_SERVER_NAME,
SERVER_FQDN, PARTNER_TARGET_PORT,
AS2SignatureAlgorithm.SHA256WITHRSA,
- serverCertList.toArray(new Certificate[0]),
serverSigningKP.getPrivate(), serverSigningKP.getPrivate(),
- MDN_MESSAGE_TEMPLATE);
+ new Certificate[] { serverCert }, serverKP.getPrivate(),
serverKP.getPrivate(),
+ MDN_MESSAGE_TEMPLATE, new Certificate[] { clientCert });
requestHandler = new RequestHandler();
serverConnection.listen("/", requestHandler);
}
- private void setupKeysAndCertificates() throws Exception {
+ private static void setupClientKeysAndCertificates() throws Exception {
//
// set up our certificates
//
@@ -863,24 +804,15 @@ public class AS2ClientManagerIT extends
AbstractAS2ITSupport {
kpg.initialize(1024, new SecureRandom());
String issueDN = "O=Punkhorn Software, C=US";
- issueKP = kpg.generateKeyPair();
- issueCert = Utils.makeCertificate(
+ KeyPair issueKP = kpg.generateKeyPair();
+ X509Certificate issueCert = Utils.makeCertificate(
issueKP, issueDN, issueKP, issueDN);
//
// certificate we sign against
//
String signingDN = "CN=William J. Collins, [email protected],
O=Punkhorn Software, C=US";
- signingKP = kpg.generateKeyPair();
- signingCert = Utils.makeCertificate(
- signingKP, signingDN, issueKP, issueDN);
-
- certList = new ArrayList<>();
-
- certList.add(signingCert);
- certList.add(issueCert);
-
- // keys used to encrypt/decrypt
- decryptingKP = signingKP;
+ clientKeyPair = kpg.generateKeyPair();
+ clientCert = Utils.makeCertificate(clientKeyPair, signingDN, issueKP,
issueDN);
}
}
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 3aab83ffeef..6d33a2b4a2d 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
@@ -289,7 +289,100 @@ public class AS2ServerManagerIT extends
AbstractAS2ITSupport {
assertFalse(signatureEntity.isMainBody(), "First mime type set as main
body of request");
// Validate Signature
- assertTrue(signedEntity.isValid(), "Signature is invalid");
+ assertTrue(SigningUtils.isValid(signedEntity, new Certificate[] {
signingCert }), "Signature is invalid");
+
+ String rcvdMessage = message.getBody(String.class);
+ assertEquals(EDI_MESSAGE.replaceAll("[\n\r]", ""),
rcvdMessage.replaceAll("[\n\r]", ""),
+ "Unexpected content for enveloped mime part");
+ }
+
+ @Test
+ public void receiveMultipartInvalidSignedMessageTest() 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);
+ AS2ClientManager clientManager = new
AS2ClientManager(clientConnection);
+
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+
+ kpg.initialize(1024, new SecureRandom());
+ String hackerIssueDN = "O=Hackers Unlimited Ltd., C=US";
+ var hackerIssueKP = kpg.generateKeyPair();
+ var hackerissueCert = Utils.makeCertificate(
+ hackerIssueKP, hackerIssueDN, hackerIssueKP, hackerIssueDN);
+ String hackerSigningDN = "CN=John Doe, [email protected], O=Self
Signed, C=US";
+ var hackerSigningKP = kpg.generateKeyPair();
+ var hackerSigningCert = Utils.makeCertificate(
+ hackerSigningKP, hackerSigningDN, hackerIssueKP,
hackerIssueDN);
+
+ clientManager.send(EDI_MESSAGE, REQUEST_URI, SUBJECT, FROM, AS2_NAME,
AS2_NAME, AS2MessageStructure.SIGNED,
+ ContentType.create(AS2MediaType.APPLICATION_EDIFACT,
StandardCharsets.US_ASCII), null,
+ AS2SignatureAlgorithm.SHA256WITHRSA,
+ new Certificate[] { hackerSigningCert },
hackerSigningKP.getPrivate(), null, DISPOSITION_NOTIFICATION_TO,
+ SIGNED_RECEIPT_MIC_ALGORITHMS, null, null, null);
+
+ MockEndpoint mockEndpoint = getMockEndpoint("mock:as2RcvMsgs");
+ mockEndpoint.expectedMinimumMessageCount(1);
+ mockEndpoint.setResultWaitTime(TimeUnit.MILLISECONDS.convert(30,
TimeUnit.SECONDS));
+ mockEndpoint.assertIsSatisfied();
+
+ final List<Exchange> exchanges = mockEndpoint.getExchanges();
+ assertNotNull(exchanges, "listen result");
+ assertFalse(exchanges.isEmpty(), "listen result");
+ LOG.debug("poll result: " + exchanges);
+
+ Exchange exchange = exchanges.get(0);
+ Message message = exchange.getIn();
+ assertNotNull(message, "exchange message");
+ HttpCoreContext coreContext =
exchange.getProperty(AS2Constants.AS2_INTERCHANGE, HttpCoreContext.class);
+ assertNotNull(coreContext, "context");
+ HttpRequest request = coreContext.getRequest();
+ assertNotNull(request, "request");
+ assertEquals(METHOD, request.getRequestLine().getMethod(), "Unexpected
method value");
+ assertEquals(REQUEST_URI, request.getRequestLine().getUri(),
"Unexpected request URI value");
+ assertEquals(HttpVersion.HTTP_1_1,
request.getRequestLine().getProtocolVersion(), "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");
+
assertTrue(request.getFirstHeader(AS2Header.CONTENT_TYPE).getValue().startsWith(AS2MediaType.MULTIPART_SIGNED),
+ "Unexpected content type for message");
+
+ assertTrue(request instanceof BasicHttpEntityEnclosingRequest,
"Request does not contain entity");
+ HttpEntity entity = ((BasicHttpEntityEnclosingRequest)
request).getEntity();
+ assertNotNull(entity, "Request does not contain entity");
+ assertTrue(entity instanceof MultipartSignedEntity, "Unexpected
request entity type");
+ MultipartSignedEntity signedEntity = (MultipartSignedEntity) entity;
+ assertTrue(signedEntity.isMainBody(), "Entity not set as main body of
request");
+ assertEquals(2, signedEntity.getPartCount(), "Request contains invalid
number of mime parts");
+
+ // Validated first mime part.
+ assertTrue(signedEntity.getPart(0) instanceof
ApplicationEDIFACTEntity, "First mime part incorrect type ");
+ ApplicationEDIFACTEntity ediEntity = (ApplicationEDIFACTEntity)
signedEntity.getPart(0);
+
assertTrue(ediEntity.getContentType().getValue().startsWith(AS2MediaType.APPLICATION_EDIFACT),
+ "Unexpected content type for first mime part");
+ assertFalse(ediEntity.isMainBody(), "First mime type set as main body
of request");
+
+ // Validate second mime part.
+ assertTrue(signedEntity.getPart(1) instanceof
ApplicationPkcs7SignatureEntity, "Second mime part incorrect type ");
+ ApplicationPkcs7SignatureEntity signatureEntity =
(ApplicationPkcs7SignatureEntity) signedEntity.getPart(1);
+
assertTrue(signatureEntity.getContentType().getValue().startsWith(AS2MediaType.APPLICATION_PKCS7_SIGNATURE),
+ "Unexpected content type for second mime part");
+ assertFalse(signatureEntity.isMainBody(), "First mime type set as main
body of request");
+
+ // Validate Signature
+ assertFalse(SigningUtils.isValid(signedEntity, new Certificate[] {
signingCert }), "Signature must be invalid");
String rcvdMessage = message.getBody(String.class);
assertEquals(EDI_MESSAGE.replaceAll("[\n\r]", ""),
rcvdMessage.replaceAll("[\n\r]", ""),