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);
+    }
+}

Reply via email to