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 a4c0bd0da80c [CAMEL-22557] AS2: Fix configuration isolation and enable
port sharing for inbound routes (#19768)
a4c0bd0da80c is described below
commit a4c0bd0da80cfa397d254b391a32bbc7828c024d
Author: Bruno Gonçalves <[email protected]>
AuthorDate: Fri Oct 31 08:25:45 2025 +0000
[CAMEL-22557] AS2: Fix configuration isolation and enable port sharing for
inbound routes (#19768)
* CAMEL-22557 - AS2: Fix configuration isolation and enable port sharing
for inbound routes
* removed unsused variables
* Revert changes on model.properties
---
.../component/as2/api/AS2ServerConnection.java | 415 ++++++++++++++++-----
.../component/as2/api/protocol/ResponseMDN.java | 36 +-
.../apache/camel/component/as2/AS2Consumer.java | 41 +-
.../apache/camel/component/as2/AS2Endpoint.java | 16 +
.../as2/internal/AS2ConnectionHelper.java | 9 +-
.../component/as2/AS2ServerTwoConsumerBase.java | 246 ++++++++++++
.../as2/AS2ServerTwoConsumerSecEncryptedIT.java | 70 ++++
7 files changed, 717 insertions(+), 116 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 b6210d7f3637..5af6b02fa636 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
@@ -21,8 +21,12 @@ import java.io.InterruptedIOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
+import java.net.URI;
import java.security.PrivateKey;
import java.security.cert.Certificate;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
@@ -30,14 +34,16 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLServerSocketFactory;
import
org.apache.camel.component.as2.api.entity.DispositionNotificationMultipartReportEntity;
-import org.apache.camel.component.as2.api.entity.MultipartMimeEntity;
import org.apache.camel.component.as2.api.entity.MultipartSignedEntity;
import org.apache.camel.component.as2.api.io.AS2BHttpServerConnection;
import org.apache.camel.component.as2.api.protocol.ResponseMDN;
import org.apache.camel.util.ObjectHelper;
+import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ConnectionClosedException;
+import org.apache.hc.core5.http.EntityDetails;
import org.apache.hc.core5.http.HttpException;
import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.HttpRequestInterceptor;
import org.apache.hc.core5.http.config.Http1Config;
import org.apache.hc.core5.http.impl.io.HttpService;
import org.apache.hc.core5.http.io.HttpRequestHandler;
@@ -64,57 +70,269 @@ public class AS2ServerConnection {
private static final String REQUEST_LISTENER_THREAD_NAME_PREFIX =
"AS2Svr-";
private static final String REQUEST_HANDLER_THREAD_NAME_PREFIX =
"AS2Hdlr-";
- class RequestListenerThread extends Thread {
+ public static final String AS2_DECRYPTING_PRIVATE_KEY =
"AS2_DECRYPTING_PRIVATE_KEY";
+ public static final String AS2_VALIDATE_SIGNING_CERTIFICATE_CHAIN =
"AS2_VALIDATE_SIGNING_CERTIFICATE_CHAIN";
+ public static final String AS2_SIGNING_PRIVATE_KEY =
"AS2_SIGNING_PRIVATE_KEY";
+ public static final String AS2_SIGNING_CERTIFICATE_CHAIN =
"AS2_SIGNING_CERTIFICATE_CHAIN";
+ public static final String AS2_SIGNING_ALGORITHM = "AS2_SIGNING_ALGORITHM";
+
+ private ServerSocket serversocket;
+ private RequestListenerService listenerService;
+ private RequestAcceptorThread acceptorThread;
+ private final Lock lock = new ReentrantLock();
+
+ private final String as2Version;
+ private final String originServer;
+ private final String serverFqdn;
+ private final String userName;
+ private final String password;
+ private final String accessToken;
+
+ /**
+ * Stores the configuration for each consumer endpoint path (e.g.,
"/consumerA")
+ */
+ private final Map<String, AS2ConsumerConfiguration> consumerConfigurations
= new ConcurrentHashMap<>();
+
+ /**
+ * Simple wrapper class to associate the AS2ConsumerConfiguration with the
specific request URI path that was
+ * matched. Used exclusively by the ThreadLocal state.
+ */
+ private static class ThreadLocalConfigWrapper {
+ final AS2ConsumerConfiguration config;
+ final String requestUriPath;
+
+ ThreadLocalConfigWrapper(AS2ConsumerConfiguration config, String
requestUriPath) {
+ this.config = config;
+ this.requestUriPath = requestUriPath;
+ }
+ }
+
+ /**
+ * Stores the request-specific AS2ConsumerConfiguration and path. Used for
post-processing logic (like asynchronous
+ * MDN) after the main HttpService handling is complete.
+ */
+ private static final ThreadLocal<ThreadLocalConfigWrapper>
CURRENT_CONSUMER_CONFIG = new ThreadLocal<>();
+
+ /**
+ * Configuration data holding all necessary security material (signing
keys/certs and decryption keys/certs) for a
+ * single AS2 consumer endpoint. This immutable object is looked up per
request URI.
+ */
+ public static class AS2ConsumerConfiguration {
+ private final Certificate[] signingCertificateChain;
+ private final PrivateKey signingPrivateKey;
+ private final PrivateKey decryptingPrivateKey;
+ private final Certificate[] validateSigningCertificateChain;
+ private final AS2SignatureAlgorithm signingAlgorithm;
+
+ public AS2ConsumerConfiguration(
+ AS2SignatureAlgorithm signingAlgorithm,
+ Certificate[] signingCertificateChain,
+ PrivateKey signingPrivateKey,
+ PrivateKey decryptingPrivateKey,
+ Certificate[]
validateSigningCertificateChain) {
+ this.signingAlgorithm = signingAlgorithm;
+ this.signingCertificateChain = signingCertificateChain;
+ this.signingPrivateKey = signingPrivateKey;
+ this.decryptingPrivateKey = decryptingPrivateKey;
+ this.validateSigningCertificateChain =
validateSigningCertificateChain;
+ }
+
+ // Getters
+ public Certificate[] getValidateSigningCertificateChain() {
+ return validateSigningCertificateChain;
+ }
+
+ public Certificate[] getSigningCertificateChain() {
+ return signingCertificateChain;
+ }
+
+ public AS2SignatureAlgorithm getSigningAlgorithm() {
+ return signingAlgorithm;
+ }
+
+ public PrivateKey getSigningPrivateKey() {
+ return signingPrivateKey;
+ }
+
+ public PrivateKey getDecryptingPrivateKey() {
+ return decryptingPrivateKey;
+ }
+ }
+
+ /**
+ * Retrieves the specific AS2 consumer configuration associated with the
given request path.
+ *
+ * @param path The canonical request URI path (e.g., "/consumerA").
+ * @return An Optional containing the configuration if a match is
found, otherwise empty.
+ */
+ public Optional<AS2ConsumerConfiguration> getConfigurationForPath(String
path) {
+ return Optional.ofNullable(consumerConfigurations.get(path));
+ }
+
+ /**
+ * Dynamically determines and injects the AS2 security configuration
(keys, certificates, and algorithm) for the
+ * incoming HTTP request.
+ *
+ * This method performs three main tasks: 1. Looks up the correct
AS2ConsumerConfiguration based on the request URI
+ * path. 2. Injects the decryption and signing security material into the
HttpContext for use by downstream
+ * processors (like the AS2Consumer and ResponseMDN). 3. Stores the
configuration in a ThreadLocal for use by
+ * asynchronous MDN logic.
+ *
+ * @param request The incoming HTTP request.
+ * @param context The shared execution context for the request lifecycle.
+ * @return The AS2ConsumerConfiguration object found, or null if
none was matched.
+ */
+ private AS2ConsumerConfiguration
setupConfigurationForRequest(ClassicHttpRequest request, HttpContext context) {
+ String requestUri = request.getRequestUri();
+ String requestUriPath = cleanUpPath(requestUri);
+
+ // 1. LOOKUP: Find the specific consumer configuration
+ AS2ConsumerConfiguration config = AS2ServerConnection.this
+ .getConfigurationForPath(requestUriPath).orElse(null);
+
+ // 2. Logging BEFORE injection (CRITICAL for debugging path issues)
+ LOG.debug("Processing request. Incoming URI: {}, Canonical Path: {}.
Config Found: {}",
+ requestUri, requestUriPath, (config != null));
+
+ // 3. Handle missing config
+ if (config == null) {
+ LOG.warn("No AS2 consumer configuration found for canonical path:
{}. Encrypted messages will likely fail.",
+ requestUriPath);
+ return null;
+ }
+
+ // 4. INJECTION: Inject dynamic security keys into the HttpContext
+ context.setAttribute(AS2_DECRYPTING_PRIVATE_KEY,
config.getDecryptingPrivateKey());
+ context.setAttribute(AS2_VALIDATE_SIGNING_CERTIFICATE_CHAIN,
config.getValidateSigningCertificateChain());
+ context.setAttribute(AS2_SIGNING_PRIVATE_KEY,
config.getSigningPrivateKey());
+ context.setAttribute(AS2_SIGNING_CERTIFICATE_CHAIN,
config.getSigningCertificateChain());
+ context.setAttribute(AS2_SIGNING_ALGORITHM,
config.getSigningAlgorithm());
+
+ // 5. CRITICAL READ-BACK CHECK: Immediately check if the key is
retrievable from the context
+ Object checkKey = context.getAttribute(AS2_DECRYPTING_PRIVATE_KEY);
+
+ if (checkKey == null) {
+ LOG.error(
+ "FATAL: Decrypting Private Key failed to be read back from
HttpContext immediately after injection for path: {}",
+ requestUriPath);
+ } else if (!(checkKey instanceof PrivateKey)) {
+ LOG.error("FATAL: Key in HttpContext is not a PrivateKey object!
Found type: {}", checkKey.getClass().getName());
+ } else {
+ LOG.debug("Context injection confirmed: Decrypting Key set
successfully into HttpContext. Key type: {}",
+ checkKey.getClass().getName());
+ }
+
+ // 6. Set ThreadLocal for later MDN processing
+ ThreadLocalConfigWrapper wrapper = new
ThreadLocalConfigWrapper(config, requestUriPath);
+ CURRENT_CONSUMER_CONFIG.set(wrapper);
+
+ return config;
+ }
+
+ /**
+ * Extracts and normalizes the path component from the request URI.
+ *
+ * This ensures consistency by stripping query parameters and
scheme/authority, and defaults to "/" if the path is
+ * empty or parsing fails.
+ *
+ * @param requestUri The full request URI string from the HTTP request
line.
+ * @return The canonical path, starting with a "/", without
query parameters.
+ */
+ private String cleanUpPath(String requestUri) {
+ try {
+ URI uri = new URI(requestUri);
+ String path = uri.getPath();
+ // Ensure path is not null and normalize to "/" if it is
empty/null after parsing
+ if (path == null || path.isEmpty()) {
+ return "/";
+ }
+ return path;
+ } catch (Exception e) {
+ // Should not happen for a valid HTTP request line
+ LOG.warn("Error parsing request URI: {}", requestUri, e);
+ return "/"; // Default to root path in case of error
+ }
+ }
+
+ /**
+ * Interceptor that executes early in the request processing chain to find
the correct
+ * {@link AS2ConsumerConfiguration} for the incoming request URI and
injects its security material
+ * (keys/certs/algorithm) into the {@link HttpContext} and {@link
ThreadLocal} storage.
+ */
+ private class AS2ConsumerConfigInterceptor implements
HttpRequestInterceptor {
+
+ @Override
+ public void process(HttpRequest request, EntityDetails entityDetails,
HttpContext context)
+ throws HttpException, IOException {
+ if (request instanceof ClassicHttpRequest) {
+ // Now safely calling the method on the outer class instance
(AS2ServerConnection.this)
+
AS2ServerConnection.this.setupConfigurationForRequest((ClassicHttpRequest)
request, context);
+ }
+ }
+ }
+
+ class RequestListenerService {
- private final ServerSocket serversocket;
private final HttpService httpService;
private final RequestHandlerRegistry registry;
- private final HttpServerRequestHandler handler;
-
- public RequestListenerThread(String as2Version,
- String originServer,
- String serverFqdn,
- int port,
- AS2SignatureAlgorithm signatureAlgorithm,
- Certificate[] signingCertificateChain,
- PrivateKey signingPrivateKey,
- PrivateKey decryptingPrivateKey,
- String mdnMessageTemplate,
- Certificate[]
validateSigningCertificateChain,
- SSLContext sslContext)
- throws IOException
{
- setName(REQUEST_LISTENER_THREAD_NAME_PREFIX + port);
- if (sslContext == null) {
- serversocket = new ServerSocket(port);
- } else {
- SSLServerSocketFactory factory =
sslContext.getServerSocketFactory();
- serversocket = factory.createServerSocket(port);
- }
+ public RequestListenerService(String as2Version,
+ String originServer,
+ String serverFqdn,
+ String mdnMessageTemplate)
+ throws
IOException {
// Set up HTTP protocol processor for incoming connections
- final HttpProcessor inhttpproc = initProtocolProcessor(as2Version,
originServer, serverFqdn,
- signatureAlgorithm, signingCertificateChain,
signingPrivateKey, decryptingPrivateKey, mdnMessageTemplate,
- validateSigningCertificateChain);
+ final HttpProcessor inhttpproc = initProtocolProcessor(
+ as2Version, originServer, serverFqdn,
+ mdnMessageTemplate);
registry = new RequestHandlerRegistry<>();
- handler = new BasicHttpServerRequestHandler(registry);
+ HttpServerRequestHandler handler = new
BasicHttpServerRequestHandler(registry);
// Set up the HTTP service
httpService = new HttpService(inhttpproc, handler);
}
+ void registerHandler(String requestUriPattern, HttpRequestHandler
httpRequestHandler) {
+ registry.register(null, requestUriPattern, httpRequestHandler);
+ }
+
+ void unregisterHandler(String requestUriPattern) {
+ // we cannot remove from http registry, but we can replace with a
not found to simulate 404
+ registry.register(null, requestUriPattern, new
NotFoundHttpRequestHandler());
+ }
+ }
+
+ class RequestAcceptorThread extends Thread {
+
+ private final RequestListenerService service;
+
+ public RequestAcceptorThread(int port, SSLContext sslContext,
RequestListenerService service)
+
throws IOException {
+ setName(REQUEST_LISTENER_THREAD_NAME_PREFIX + port);
+ this.service = service;
+
+ if (sslContext == null) {
+ serversocket = new ServerSocket(port);
+ } else {
+ SSLServerSocketFactory factory =
sslContext.getServerSocketFactory();
+ serversocket = factory.createServerSocket(port);
+ }
+ }
+
@Override
public void run() {
- LOG.info("Listening on port {}", this.serversocket.getLocalPort());
+ // serversocket is now a field of the outer AS2ServerConnection
class
+ LOG.info("Listening on port {}", serversocket.getLocalPort());
while (!Thread.interrupted()) {
try {
// Set up incoming HTTP connection
- final Socket inSocket = this.serversocket.accept();
+ final Socket inSocket = serversocket.accept();
- // Start worker thread
- final Thread t = new
RequestHandlerThread(this.httpService, inSocket);
+ // Start worker thread, using the service's HttpService
+ final Thread t = new
RequestHandlerThread(this.service.httpService, inSocket);
t.setDaemon(true);
t.start();
} catch (final InterruptedIOException | SocketException ex) {
@@ -126,15 +344,6 @@ public class AS2ServerConnection {
}
}
}
-
- void registerHandler(String requestUriPattern, HttpRequestHandler
httpRequestHandler) {
- registry.register(null, requestUriPattern, httpRequestHandler);
- }
-
- void unregisterHandler(String requestUriPattern) {
- // we cannot remove from http registry, but we can replace with a
not found to simulate 404
- registry.register(null, requestUriPattern, new
NotFoundHttpRequestHandler());
- }
}
class RequestHandlerThread extends Thread {
@@ -167,25 +376,38 @@ public class AS2ServerConnection {
this.httpService.handleRequest(this.serverConnection,
context);
HttpCoreContext coreContext =
HttpCoreContext.adapt(context);
+
+ // Safely retrieve the AS2 consumer configuration and path
from ThreadLocal storage.
+ AS2ConsumerConfiguration config =
Optional.ofNullable(CURRENT_CONSUMER_CONFIG.get())
+ .map(w -> w.config)
+ .orElse(null);
+
String recipientAddress =
coreContext.getAttribute(AS2AsynchronousMDNManager.RECIPIENT_ADDRESS,
String.class);
- if (recipientAddress != null) {
+ if (recipientAddress != null && config != null) {
// Send the MDN asynchronously.
DispositionNotificationMultipartReportEntity
multipartReportEntity = coreContext.getAttribute(
AS2AsynchronousMDNManager.ASYNCHRONOUS_MDN,
DispositionNotificationMultipartReportEntity.class);
AS2AsynchronousMDNManager asynchronousMDNManager = new
AS2AsynchronousMDNManager(
- as2Version,
- originServer, serverFqdn,
signingCertificateChain, signingPrivateKey,
- userName, password, accessToken);
+ AS2ServerConnection.this.as2Version,
+ AS2ServerConnection.this.originServer,
+ AS2ServerConnection.this.serverFqdn,
+ config.getSigningCertificateChain(),
+ config.getSigningPrivateKey(),
+ AS2ServerConnection.this.userName,
+ AS2ServerConnection.this.password,
+ AS2ServerConnection.this.accessToken);
HttpRequest request =
coreContext.getAttribute(HttpCoreContext.HTTP_REQUEST, HttpRequest.class);
AS2SignedDataGenerator gen =
ResponseMDN.createSigningGenerator(
- request, signingAlgorithm,
signingCertificateChain, signingPrivateKey);
+ request,
+ config.getSigningAlgorithm(),
+ config.getSigningCertificateChain(),
+ config.getSigningPrivateKey());
- MultipartMimeEntity asyncReceipt =
multipartReportEntity;
if (gen != null) {
// send a signed MDN
MultipartSignedEntity multipartSignedEntity = null;
@@ -222,20 +444,6 @@ public class AS2ServerConnection {
}
- private RequestListenerThread listenerThread;
- private final Lock lock = new ReentrantLock();
- private final String as2Version;
- private final String originServer;
- private final String serverFqdn;
- private final Certificate[] signingCertificateChain;
- private final PrivateKey signingPrivateKey;
- private final PrivateKey decryptingPrivateKey;
- private final Certificate[] validateSigningCertificateChain;
- private final AS2SignatureAlgorithm signingAlgorithm;
- private final String userName;
- private final String password;
- private final String accessToken;
-
public AS2ServerConnection(String as2Version,
String originServer,
String serverFqdn,
@@ -255,45 +463,68 @@ public class AS2ServerConnection {
this.originServer = ObjectHelper.notNull(originServer, "userAgent");
this.serverFqdn = ObjectHelper.notNull(serverFqdn, "serverFqdn");
final Integer parserServerPortNumber =
ObjectHelper.notNull(serverPortNumber, "serverPortNumber");
- this.signingCertificateChain = signingCertificateChain;
- this.signingPrivateKey = signingPrivateKey;
- this.decryptingPrivateKey = decryptingPrivateKey;
- this.validateSigningCertificateChain = validateSigningCertificateChain;
this.userName = userName;
this.password = password;
this.accessToken = accessToken;
- this.signingAlgorithm = signingAlgorithm;
- listenerThread = new RequestListenerThread(
- this.as2Version, this.originServer, this.serverFqdn,
- parserServerPortNumber, signingAlgorithm,
this.signingCertificateChain, this.signingPrivateKey,
- this.decryptingPrivateKey, mdnMessageTemplate,
validateSigningCertificateChain, sslContext);
- listenerThread.setDaemon(true);
- listenerThread.start();
+ // Create and register a default consumer configuration for the root
path ('/').
+ // This ensures that all incoming requests have a fallback
configuration for decryption
+ // and MDN signing, even if they don't match a specific Camel route
path.
+ AS2ServerConnection.AS2ConsumerConfiguration consumerConfig = new
AS2ServerConnection.AS2ConsumerConfiguration(
+ signingAlgorithm,
+ signingCertificateChain,
+ signingPrivateKey,
+ decryptingPrivateKey,
+ validateSigningCertificateChain);
+ registerConsumerConfiguration("/", consumerConfig);
+
+ listenerService = new RequestListenerService(
+ this.as2Version,
+ this.originServer,
+ this.serverFqdn,
+ mdnMessageTemplate);
+
+ acceptorThread = new RequestAcceptorThread(parserServerPortNumber,
sslContext, listenerService);
+ acceptorThread.setDaemon(true);
+ acceptorThread.start();
}
public Certificate[] getValidateSigningCertificateChain() {
- return validateSigningCertificateChain;
+ return Optional.ofNullable(CURRENT_CONSUMER_CONFIG.get())
+ .map(w -> w.config.getValidateSigningCertificateChain())
+ .orElse(null);
}
public PrivateKey getSigningPrivateKey() {
- return signingPrivateKey;
+ return Optional.ofNullable(CURRENT_CONSUMER_CONFIG.get())
+ .map(w -> w.config.getSigningPrivateKey())
+ .orElse(null);
}
public PrivateKey getDecryptingPrivateKey() {
- return decryptingPrivateKey;
+ return Optional.ofNullable(CURRENT_CONSUMER_CONFIG.get())
+ .map(w -> w.config.getDecryptingPrivateKey())
+ .orElse(null);
+ }
+
+ public void registerConsumerConfiguration(String path,
AS2ConsumerConfiguration config) {
+ consumerConfigurations.put(path, config);
}
public void close() {
- if (listenerThread != null) {
+ if (acceptorThread != null) {
lock.lock();
try {
try {
- listenerThread.serversocket.close();
+ // 3. Close the shared ServerSocket
+ if (serversocket != null) {
+ serversocket.close();
+ }
} catch (IOException e) {
LOG.debug(e.getMessage(), e);
} finally {
- listenerThread = null;
+ acceptorThread = null;
+ listenerService = null;
}
} finally {
lock.unlock();
@@ -302,10 +533,10 @@ public class AS2ServerConnection {
}
public void listen(String requestUri, HttpRequestHandler handler) {
- if (listenerThread != null) {
+ if (listenerService != null) {
lock.lock();
try {
- listenerThread.registerHandler(requestUri, handler);
+ listenerService.registerHandler(requestUri, handler);
} finally {
lock.unlock();
}
@@ -313,10 +544,11 @@ public class AS2ServerConnection {
}
public void unlisten(String requestUri) {
- if (listenerThread != null) {
+ if (listenerService != null) {
lock.lock();
try {
- listenerThread.unregisterHandler(requestUri);
+ listenerService.unregisterHandler(requestUri);
+ consumerConfigurations.remove(requestUri);
} finally {
lock.unlock();
}
@@ -327,17 +559,14 @@ public class AS2ServerConnection {
String as2Version,
String originServer,
String serverFqdn,
- AS2SignatureAlgorithm signatureAlgorithm,
- Certificate[] signingCertificateChain,
- PrivateKey signingPrivateKey,
- PrivateKey decryptingPrivateKey,
- 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, validateSigningCertificateChain))
+ String mdnMessageTemplate) {
+ return HttpProcessorBuilder.create()
+ .addFirst(new AS2ConsumerConfigInterceptor()) // Sets up the
request-specific keys and certificates in the HttpContext
+ .add(new ResponseContent(true))
+ .add(new ResponseServer(originServer))
+ .add(new ResponseDate())
+ .add(new ResponseConnControl())
+ .add(new ResponseMDN(as2Version, serverFqdn,
mdnMessageTemplate))
.build();
}
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 e4849b9bab2e..b4dbf24885f2 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
@@ -30,6 +30,7 @@ import java.util.concurrent.locks.ReentrantLock;
import org.apache.camel.component.as2.api.AS2AsynchronousMDNManager;
import org.apache.camel.component.as2.api.AS2Constants;
import org.apache.camel.component.as2.api.AS2Header;
+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;
@@ -85,16 +86,29 @@ public class ResponseMDN implements HttpResponseInterceptor
{
private final String as2Version;
private final String serverFQDN;
- private final AS2SignatureAlgorithm signingAlgorithm;
- private final Certificate[] signingCertificateChain;
- private final PrivateKey signingPrivateKey;
- private final PrivateKey decryptingPrivateKey;
private final String mdnMessageTemplate;
- private final Certificate[] validateSigningCertificateChain;
+
+ private AS2SignatureAlgorithm signingAlgorithm;
+ private Certificate[] signingCertificateChain;
+ private PrivateKey signingPrivateKey;
+ private PrivateKey decryptingPrivateKey;
+ private Certificate[] validateSigningCertificateChain;
+ private boolean keysAreDynamic = false; // Flag indicating if security
keys/certs must be dynamically fetched from the HttpContext
private final Lock lock = new ReentrantLock();
private VelocityEngine velocityEngine;
+ public ResponseMDN(String as2Version, String serverFQDN, String
mdnMessageTemplate) {
+ this.as2Version = as2Version;
+ this.serverFQDN = serverFQDN;
+ if (!StringUtils.isBlank(mdnMessageTemplate)) {
+ this.mdnMessageTemplate = mdnMessageTemplate;
+ } else {
+ this.mdnMessageTemplate = DEFAULT_MDN_MESSAGE_TEMPLATE;
+ }
+ this.keysAreDynamic = true;
+ }
+
public ResponseMDN(String as2Version, String serverFQDN,
AS2SignatureAlgorithm signingAlgorithm,
Certificate[] signingCertificateChain, PrivateKey
signingPrivateKey, PrivateKey decryptingPrivateKey,
String mdnMessageTemplate, Certificate[]
validateSigningCertificateChain) {
@@ -126,6 +140,18 @@ public class ResponseMDN implements
HttpResponseInterceptor {
return;
}
+ if (this.keysAreDynamic) {
+ // Dynamically load path-specific security material from the
HttpContext,
+ // which was populated by AS2ConsumerConfigInterceptor.
+ this.signingAlgorithm = (AS2SignatureAlgorithm)
context.getAttribute(AS2ServerConnection.AS2_SIGNING_ALGORITHM);
+ this.signingCertificateChain
+ = (Certificate[])
context.getAttribute(AS2ServerConnection.AS2_SIGNING_CERTIFICATE_CHAIN);
+ this.signingPrivateKey = (PrivateKey)
context.getAttribute(AS2ServerConnection.AS2_SIGNING_PRIVATE_KEY);
+ this.decryptingPrivateKey = (PrivateKey)
context.getAttribute(AS2ServerConnection.AS2_DECRYPTING_PRIVATE_KEY);
+ this.validateSigningCertificateChain
+ = (Certificate[])
context.getAttribute(AS2ServerConnection.AS2_VALIDATE_SIGNING_CERTIFICATE_CHAIN);
+ }
+
HttpCoreContext coreContext = HttpCoreContext.adapt(context);
HttpRequest request =
coreContext.getAttribute(HttpCoreContext.HTTP_REQUEST, HttpRequest.class);
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 e05dacc1f39c..553b636cfc6f 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
@@ -32,9 +32,6 @@ import
org.apache.camel.component.as2.api.util.HttpMessageUtils;
import org.apache.camel.component.as2.internal.AS2ApiName;
import org.apache.camel.component.as2.internal.AS2Constants;
import org.apache.camel.support.component.AbstractApiConsumer;
-import org.apache.camel.support.component.ApiConsumerHelper;
-import org.apache.camel.support.component.ApiMethod;
-import org.apache.camel.support.component.ApiMethodHelper;
import org.apache.hc.core5.http.ClassicHttpRequest;
import org.apache.hc.core5.http.ClassicHttpResponse;
import org.apache.hc.core5.http.HttpEntityContainer;
@@ -58,18 +55,14 @@ public class AS2Consumer extends
AbstractApiConsumer<AS2ApiName, AS2Configuratio
private static final Logger LOG =
LoggerFactory.getLogger(AS2Consumer.class);
private static final String HANDLER_PROPERTY = "handler";
- private static final String REQUEST_URI_PROPERTY = "requestUri";
private AS2ServerConnection as2ServerConnection;
private AS2ServerManager apiProxy;
- private final ApiMethod apiMethod;
private final Map<String, Object> properties;
public AS2Consumer(AS2Endpoint endpoint, Processor processor) {
super(endpoint, processor);
- apiMethod = ApiConsumerHelper.findMethod(endpoint, this);
-
properties = new HashMap<>();
properties.putAll(endpoint.getEndpointProperties());
properties.put(HANDLER_PROPERTY, this);
@@ -98,18 +91,36 @@ public class AS2Consumer extends
AbstractApiConsumer<AS2ApiName, AS2Configuratio
as2ServerConnection = getEndpoint().getAS2ServerConnection();
apiProxy = new AS2ServerManager(as2ServerConnection);
- // invoke the API method to start listening
- ApiMethodHelper.invokeMethod(apiProxy, apiMethod, properties);
+ String uri = properties.computeIfAbsent("requestUriPattern", param ->
"/").toString();
+
+ // Check if the configuration for this specific URI path has already
been registered
+ // (e.g., by the default "/" fallback or another consumer).
+ // If not, create and register it now using the endpoint's configured
keys/certs.
+ if (as2ServerConnection.getConfigurationForPath(uri).isEmpty()) {
+ AS2ServerConnection.AS2ConsumerConfiguration consumerConfig = new
AS2ServerConnection.AS2ConsumerConfiguration(
+ getEndpoint().getSigningAlgorithm(),
+ getEndpoint().getSigningCertificateChain(),
+ getEndpoint().getSigningPrivateKey(),
+ getEndpoint().getDecryptingPrivateKey(),
+ getEndpoint().getValidateSigningCertificateChain());
+ as2ServerConnection.registerConsumerConfiguration(uri,
consumerConfig);
+ }
+
+ as2ServerConnection.listen(uri, this);
}
@Override
protected void doStop() throws Exception {
- super.doStop();
- if (apiProxy != null) {
- String uri = properties.get("requestUriPattern").toString();
- apiProxy.unlisten(uri);
+ if (as2ServerConnection != null) {
+ // Resolve the unique URI pattern for this consumer
+ String uri = properties.computeIfAbsent("requestUriPattern", param
-> "/").toString();
+
+ // Unregister this consumer from the shared AS2ServerConnection
+ as2ServerConnection.unlisten(uri);
}
+
+ super.doStop();
}
@Override
@@ -125,8 +136,8 @@ public class AS2Consumer extends
AbstractApiConsumer<AS2ApiName, AS2Configuratio
ApplicationEntity ediEntity
= HttpMessageUtils.extractEdiPayload(request,
new HttpMessageUtils.DecrpytingAndSigningInfo(
-
as2ServerConnection.getValidateSigningCertificateChain(),
-
as2ServerConnection.getDecryptingPrivateKey()));
+
getEndpoint().getValidateSigningCertificateChain(),
+ getEndpoint().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/AS2Endpoint.java
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Endpoint.java
index b2326a7629df..b8c1ccfa6321 100644
---
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Endpoint.java
+++
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Endpoint.java
@@ -198,6 +198,22 @@ public class AS2Endpoint extends
AbstractApiEndpoint<AS2ApiName, AS2Configuratio
configuration.setSigningCertificateChain(signingCertificateChain);
}
+ public Certificate[] getValidateSigningCertificateChain() {
+ return configuration.getValidateSigningCertificateChain();
+ }
+
+ public void setValidateSigningCertificateChain(Certificate[]
validateSigningCertificateChain) {
+
configuration.setValidateSigningCertificateChain(validateSigningCertificateChain);
+ }
+
+ public PrivateKey getDecryptingPrivateKey() {
+ return configuration.getDecryptingPrivateKey();
+ }
+
+ public void setDecryptingPrivateKey(PrivateKey decryptingPrivateKey) {
+ configuration.setDecryptingPrivateKey(decryptingPrivateKey);
+ }
+
public PrivateKey getSigningPrivateKey() {
return configuration.getSigningPrivateKey();
}
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 3342da15bd95..c8d405f9982e 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
@@ -103,9 +103,12 @@ public final class AS2ConnectionHelper {
configuration.getAs2Version(),
configuration.getServer(),
configuration.getServerFqdn(),
configuration.getServerPortNumber(),
configuration.getSigningAlgorithm(),
-
configuration.getSigningCertificateChain(),
configuration.getSigningPrivateKey(),
- configuration.getDecryptingPrivateKey(),
configuration.getMdnMessageTemplate(),
-
configuration.getValidateSigningCertificateChain(),
configuration.getSslContext(),
+ configuration.getSigningCertificateChain(),
+ configuration.getSigningPrivateKey(),
+ configuration.getDecryptingPrivateKey(),
+ configuration.getMdnMessageTemplate(),
+
configuration.getValidateSigningCertificateChain(),
+ configuration.getSslContext(),
configuration.getMdnUserName(),
configuration.getMdnPassword(),
configuration.getMdnAccessToken());
} catch (IOException e) {
diff --git
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerBase.java
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerBase.java
new file mode 100644
index 000000000000..9a31b1d44ed3
--- /dev/null
+++
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerBase.java
@@ -0,0 +1,246 @@
+/*
+ * 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;
+
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.SecureRandom;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+
+import org.apache.camel.CamelContext;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.as2.api.*;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+
+public class AS2ServerTwoConsumerBase extends AS2ServerSecTestBase {
+
+ public enum ConsumerConfig {
+ CONSUMER_A("AS2_SERVER_A", "keyPairA", "signingKeyA", "/consumerA"),
+ CONSUMER_B("AS2_SERVER_B", "keyPairB", "signingKeyB", "/consumerB");
+
+ private final String as2To;
+ private final String decryptingKey;
+ private final String signingKey;
+ private final String requestUriPattern; // New field
+
+ ConsumerConfig(String as2To, String decryptingKey, String signingKey,
String requestUriPattern) {
+ this.as2To = as2To;
+ this.decryptingKey = decryptingKey;
+ this.signingKey = signingKey;
+ this.requestUriPattern = requestUriPattern;
+ }
+
+ public String getAs2To() {
+ return as2To;
+ }
+
+ public String getDecryptingKey() {
+ return decryptingKey;
+ }
+
+ public String getSigningKey() {
+ return signingKey;
+ }
+
+ public String getRequestUriPattern() {
+ return requestUriPattern;
+ } // New getter
+ }
+
+ private KeyPair decryptingKPA;
+ private KeyPair decryptingKPB;
+ private X509Certificate signingCertA;
+ private X509Certificate signingCertB;
+
+ @Override
+ protected CamelContext createCamelContext() throws Exception {
+ // 1. Let the base class create the context instance
+ CamelContext context = super.createCamelContext();
+
+ // 2. Generate and assign distinct keys for Consumer A
+ Object[] setA =
generateNewKeyPairSet(ConsumerConfig.CONSUMER_A.getAs2To());
+ decryptingKPA = (KeyPair) setA[1];
+ signingCertA = (X509Certificate) setA[2];
+
+ // 3. Generate and assign distinct keys for Consumer B
+ Object[] setB =
generateNewKeyPairSet(ConsumerConfig.CONSUMER_B.getAs2To());
+ decryptingKPB = (KeyPair) setB[1];
+ signingCertB = (X509Certificate) setB[2];
+
+ // 4. Register private keys in the registry for the AS2 consumers to
find.
+ // The 'context' object is guaranteed to be non-null here.
+
context.getRegistry().bind(ConsumerConfig.CONSUMER_A.getDecryptingKey(),
decryptingKPA.getPrivate());
+
context.getRegistry().bind(ConsumerConfig.CONSUMER_B.getDecryptingKey(),
decryptingKPB.getPrivate());
+
+ context.getRegistry().bind(ConsumerConfig.CONSUMER_A.getSigningKey(),
decryptingKPA.getPrivate());
+ context.getRegistry().bind(ConsumerConfig.CONSUMER_B.getSigningKey(),
decryptingKPB.getPrivate());
+
+ // 5. Return the configured context
+ return context;
+ }
+
+ @Override
+ protected RouteBuilder createRouteBuilder() {
+ // Define both consumers listening on the same component but different
URIs
+ return new RouteBuilder() {
+ public void configure() {
+ // Consumer A: Uses keys registered as 'keyPairA'
+
from("as2://server/listen?requestUriPattern=/consumerA&decryptingPrivateKey=#"
+ + ConsumerConfig.CONSUMER_A.getDecryptingKey() +
"&signingPrivateKey=#"
+ + ConsumerConfig.CONSUMER_A.getSigningKey())
+ .to("mock:consumerA");
+
+ // Consumer B: Uses keys registered as 'keyPairB'
+
from("as2://server/listen?requestUriPattern=/consumerB&decryptingPrivateKey=#"
+ + ConsumerConfig.CONSUMER_B.getDecryptingKey() +
"&signingPrivateKey=#"
+ + ConsumerConfig.CONSUMER_B.getSigningKey())
+ .to("mock:consumerB");
+ }
+ };
+ }
+
+ protected HttpCoreContext sendToConsumerA(AS2MessageStructure structure)
throws Exception {
+ // For testing, we use Consumer A's signing key as the sender's key
(spk)
+ // and Consumer A's cert as the encryption cert (ec).
+ return sendWithIsolatedKeys(
+ structure,
+ "/consumerA",
+ ConsumerConfig.CONSUMER_A.getAs2To(),
+ signingCertA,
+ decryptingKPA.getPrivate(),
+ signingCertA);
+ }
+
+ protected HttpCoreContext sendToConsumerB(AS2MessageStructure structure)
throws Exception {
+ // For testing, we use Consumer B's signing key as the sender's key
(spk)
+ // and Consumer B's cert as the encryption cert (ec).
+ return sendWithIsolatedKeys(
+ structure,
+ "/consumerB",
+ ConsumerConfig.CONSUMER_B.getAs2To(),
+ signingCertB,
+ decryptingKPB.getPrivate(),
+ signingCertB);
+ }
+
+ protected HttpCoreContext sendWithIsolatedKeys(
+ AS2MessageStructure structure,
+ String targetUri,
+ String as2To,
+ X509Certificate signingCert,
+ PrivateKey signingPrivateKey,
+ X509Certificate encryptionCert)
+ throws Exception {
+
+ // This relies on the flexible send method (with requestUri, as2To,
as2From params)
+ // being present in AS2ServerSecTestBase
+ return send(
+ structure,
+ targetUri,
+ as2To,
+ AS2_NAME, // AS2-From header can safely reuse the base's
static AS2_NAME
+ new Certificate[] { signingCert },
+ signingPrivateKey,
+ new Certificate[] { encryptionCert });
+ }
+
+ protected HttpCoreContext send(
+ AS2MessageStructure structure,
+ String requestUri,
+ String as2To,
+ String as2From,
+ Certificate[] sc,
+ PrivateKey spk,
+ Certificate[] ec)
+ throws Exception {
+
+ // Use provided arguments or fall back to base class statics for
non-overridden parts
+ Certificate[] signingCertificate = sc == null ? new Certificate[] {
this.signingCert } : sc;
+ PrivateKey signingPrivateKey = spk == null ?
this.signingKP.getPrivate() : spk;
+ Certificate[] encryptingCertificate = ec == null ? new Certificate[] {
this.signingCert } : ec;
+
+ AS2SignatureAlgorithm signingAlgorithm = structure.isSigned() ?
AS2SignatureAlgorithm.SHA256WITHRSA : null;
+ signingCertificate = structure.isSigned() ? signingCertificate : null;
+ signingPrivateKey = structure.isSigned() ? signingPrivateKey : null;
+ AS2EncryptionAlgorithm encryptionAlgorithm = structure.isEncrypted() ?
AS2EncryptionAlgorithm.AES128_CBC : null;
+ encryptingCertificate = structure.isEncrypted() ?
encryptingCertificate : null;
+ AS2CompressionAlgorithm compressionAlgorithm =
structure.isCompressed() ? AS2CompressionAlgorithm.ZLIB : null;
+
+ return clientConnection().send(
+ EDI_MESSAGE,
+ requestUri,
+ SUBJECT,
+ FROM,
+ as2To,
+ as2From,
+ structure,
+ AS2MediaType.APPLICATION_EDIFACT,
+ null,
+ null,
+ signingAlgorithm,
+ signingCertificate,
+ signingPrivateKey,
+ compressionAlgorithm,
+ DISPOSITION_NOTIFICATION_TO,
+ SIGNED_RECEIPT_MIC_ALGORITHMS,
+ encryptionAlgorithm,
+ encryptingCertificate,
+ null,
+ null,
+ null,
+ null,
+ null);
+ }
+
+ protected Object[] generateNewKeyPairSet(String commonName) throws
Exception {
+ // set up our certificates
+ KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA", "BC");
+ kpg.initialize(1024, new SecureRandom());
+
+ String issueDN = "O=AS2 Test Issuer, C=US";
+ KeyPair issueKeyPair = kpg.generateKeyPair();
+
+ String signingDN = "CN=" + commonName + ", [email protected], O=AS2
Test, C=US";
+ KeyPair signingKeyPair = kpg.generateKeyPair();
+
+ X509Certificate signingCert = Utils.makeCertificate(
+ signingKeyPair,
+ signingDN,
+ issueKeyPair,
+ issueDN);
+
+ return new Object[] { issueKeyPair, signingKeyPair, signingCert };
+ }
+
+ protected PrivateKey getSigningPrivateKeyByRequestUri(String requestUri) {
+ for (ConsumerConfig config : ConsumerConfig.values()) {
+ if (config.getRequestUriPattern().equals(requestUri)) {
+ // Lookup the PrivateKey bound to the Registry under the
signing key name
+ Object key =
context.getRegistry().lookupByName(config.getSigningKey());
+ if (key instanceof PrivateKey) {
+ return (PrivateKey) key;
+ }
+ // Key should always be a PrivateKey based on the
AS2ServerTwoConsumerBase setup
+ throw new IllegalStateException("Registry entry for key '" +
config.getSigningKey() + "' is not a PrivateKey.");
+ }
+ }
+ throw new IllegalArgumentException("No consumer configuration found
for URI: " + requestUri);
+ }
+
+}
diff --git
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerSecEncryptedIT.java
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerSecEncryptedIT.java
new file mode 100644
index 000000000000..a3ee43481668
--- /dev/null
+++
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerSecEncryptedIT.java
@@ -0,0 +1,70 @@
+/*
+ * 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;
+
+import java.security.PrivateKey;
+
+import org.apache.camel.component.as2.api.AS2MessageStructure;
+import org.apache.camel.component.as2.api.entity.AS2DispositionModifier;
+import org.apache.camel.component.as2.api.util.MicUtils;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.protocol.HttpCoreContext;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.EnumSource;
+
+/**
+ * Tests an AS2 server configured with a decryption key to decrypt AS2
Messages. <br>
+ * Only messages with sufficient encryption will be processed, for instance,
'signed-encrypted',
+ * 'compressed-signed-encrypted', and 'signed-compressed-encrypted'. <br>
+ * All other message structures will return an 'insufficient-message-security'
error due to insufficient encryption,
+ * e.g. 'plain', 'plain-compressed' etc. <br>
+ * Any decryption failure will return an 'decryption-failed' error.
+ */
+public class AS2ServerTwoConsumerSecEncryptedIT extends
AS2ServerTwoConsumerBase {
+
+ // verify message types that fail with insufficient security due to lack
of encryption
+ @ParameterizedTest
+ @EnumSource(value = AS2MessageStructure.class,
+ names = { "PLAIN", "SIGNED", "PLAIN_COMPRESSED",
"COMPRESSED_SIGNED", "SIGNED_COMPRESSED" })
+ public void insufficientEncryptionFailureTest(AS2MessageStructure
messageStructure) throws Exception {
+ HttpCoreContext context = sendToConsumerB(messageStructure);
+ verifyOkResponse(context);
+ verifyMdnErrorDisposition(context,
AS2DispositionModifier.ERROR_INSUFFICIENT_MESSAGE_SECURITY);
+ }
+
+ // verify message types that are successfully decrypted
+ @ParameterizedTest
+ @EnumSource(value = AS2MessageStructure.class,
+ names = {
+ "ENCRYPTED", "SIGNED_ENCRYPTED",
"ENCRYPTED_COMPRESSED", "ENCRYPTED_COMPRESSED_SIGNED",
+ "ENCRYPTED_SIGNED_COMPRESSED" })
+ public void successfullyProcessedTest(AS2MessageStructure
messageStructure) throws Exception {
+ HttpCoreContext context = sendToConsumerB(messageStructure);
+ verifyOkResponse(context);
+ verifyMdnSuccessDisposition(context);
+ }
+
+ // utility method to reproduce the MIC and compare against the MIC
received in MDN.
+ @Override
+ protected MicUtils.ReceivedContentMic createReceivedContentMic(HttpRequest
request) throws HttpException {
+ final String requestUri = request.getPath();
+ final PrivateKey currentSigningKey =
getSigningPrivateKeyByRequestUri(requestUri);
+ return MicUtils.createReceivedContentMic((ClassicHttpRequest) request,
null, currentSigningKey);
+ }
+}