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 4c4f6fb96190 CAMEL-25941: camel-as2 - support sending through an HTTP 
proxy (#28265)
4c4f6fb96190 is described below

commit 4c4f6fb9619045cc4117d306de28279824a1cd9e
Author: seshuthota <[email protected]>
AuthorDate: Sat Oct 24 19:14:10 2026 +0530

    CAMEL-25941: camel-as2 - support sending through an HTTP proxy (#28265)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/camel/catalog/components/as2.json   |  40 +-
 .../apache/camel/catalog/docs/as2-component.adoc   |  36 ++
 .../component/as2/api/AS2ClientConnection.java     |  95 ++++-
 ...rverManagerEndpointConfigurationConfigurer.java |  28 ++
 ...ientManagerEndpointConfigurationConfigurer.java |  28 ++
 .../component/as2/AS2ConfigurationConfigurer.java  |  28 ++
 .../camel/component/as2/AS2EndpointConfigurer.java |  28 ++
 .../camel/component/as2/AS2EndpointUriFactory.java |  10 +-
 ...rverManagerEndpointConfigurationConfigurer.java |  28 ++
 .../org/apache/camel/component/as2/as2.json        |  40 +-
 .../src/main/docs/as2-component.adoc               |  36 ++
 .../camel/component/as2/AS2Configuration.java      |  53 +++
 .../as2/internal/AS2ConnectionHelper.java          |   3 +-
 .../camel/component/as2/AS2ClientProxyTest.java    | 452 +++++++++++++++++++++
 .../apache/camel/component/as2/TestHttpProxy.java  | 238 +++++++++++
 .../endpoint/dsl/AS2EndpointBuilderFactory.java    |  75 ++++
 16 files changed, 1177 insertions(+), 41 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
index 81730d1281b5..1e5fa00800bf 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/components/as2.json
@@ -72,24 +72,28 @@
     "exchangePattern": { "index": 31, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
     "expectContinue": { "index": 32, "kind": "parameter", "displayName": 
"Expect Continue", "group": "producer", "label": "producer", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Controls whether the Expect: 100-Continue 
header is included in outbound AS2 messages. W [...]
     "lazyStartProducer": { "index": 33, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
-    "accessToken": { "index": 34, "kind": "parameter", "displayName": "Access 
Token", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The access token that is used by the client 
for bearer authentication." },
-    "asyncMdnAllowedHosts": { "index": 35, "kind": "parameter", "displayName": 
"Async Mdn Allowed Hosts", "group": "security", "label": "security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Comma separated list of host names an 
asynchronous MDN may be delivered to. The delivery  [...]
-    "decryptingPrivateKey": { "index": 36, "kind": "parameter", "displayName": 
"Decrypting Private Key", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated": 
false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The key used to encrypt the EDI message." },
-    "encryptingAlgorithm": { "index": 37, "kind": "parameter", "displayName": 
"Encrypting Algorithm", "group": "security", "label": "security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.as2.api.AS2EncryptionAlgorithm", "enum": [ 
"AES128_CBC", "AES192_CBC", "AES256_CBC", "AES128_CCM", "AES192_CCM", 
"AES256_CCM", "AES128_GCM", "AES192_GCM", "AES256_GCM", "CAMELLIA128_CBC", 
"CAMELLIA192_CBC", "CAMELLIA256_CBC", "CAST5_CBC", "DES_CBC", "DES_EDE3_CBC", 
"GOST28 [...]
-    "encryptingCertificateChain": { "index": 38, "kind": "parameter", 
"displayName": "Encrypting Certificate Chain", "group": "security", "label": 
"security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The chain of certificates used to encrypt EDI 
message." },
-    "hostnameVerifier": { "index": 39, "kind": "parameter", "displayName": 
"Hostname Verifier", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Set hostname verifier for SSL session." },
-    "mdnAccessToken": { "index": 40, "kind": "parameter", "displayName": "Mdn 
Access Token", "group": "security", "label": "security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The access token that is used by the server 
when it sends an async MDN. The optio [...]
-    "mdnPassword": { "index": 41, "kind": "parameter", "displayName": "Mdn 
Password", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The password that is used by the server for 
basic authentication when it sends an async  [...]
-    "mdnUserName": { "index": 42, "kind": "parameter", "displayName": "Mdn 
User Name", "group": "security", "label": "security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The user-name that is used by the server for 
basic authentication when it sends an asyn [...]
-    "password": { "index": 43, "kind": "parameter", "displayName": "Password", 
"group": "security", "label": "security", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The password that is used by the client for 
basic authentication." },
-    "signatureVerificationRequired": { "index": 44, "kind": "parameter", 
"displayName": "Signature Verification Required", "group": "security", "label": 
"security", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", 
"configurationField": "configuration", "description": "Whether to reject an 
inbound signed AS2 message that cann [...]
-    "signedReceiptMicAlgorithms": { "index": 45, "kind": "parameter", 
"displayName": "Signed Receipt Mic Algorithms", "group": "security", "label": 
"security", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"configurationClass": "org.apache.camel.component.as2.AS2Configuration", 
"configurationField": "configuration", "description": "The list of algorithms, 
in order of preference, requested to generate a messa [...]
-    "signingAlgorithm": { "index": 46, "kind": "parameter", "displayName": 
"Signing Algorithm", "group": "security", "label": "security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.as2.api.AS2SignatureAlgorithm", "enum": [ 
"SHA3_224WITHRSA", "SHA3_256WITHRSA", "SHA3_384withRSA", "SHA3_512WITHRSA", 
"MD5WITHRSA", "SHA1WITHRSA", "MD2WITHRSA", "SHA224WITHRSA", "SHA256WITHRSA", 
"SHA384WITHRSA", "SHA512WITHRSA", "RIPEMD128WITHRSA", "RIPEMD160WITHRSA", 
"RIPEMD256 [...]
-    "signingCertificateChain": { "index": 47, "kind": "parameter", 
"displayName": "Signing Certificate Chain", "group": "security", "label": 
"security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The chain of certificates used to sign EDI 
message." },
-    "signingPrivateKey": { "index": 48, "kind": "parameter", "displayName": 
"Signing Private Key", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated": 
false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The key used to sign the EDI message." },
-    "sslContext": { "index": 49, "kind": "parameter", "displayName": "Ssl 
Context", "group": "security", "label": "security", "required": false, "type": 
"object", "javaType": "javax.net.ssl.SSLContext", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Set SSL context for connection to remote 
server." },
-    "userName": { "index": 50, "kind": "parameter", "displayName": "User 
Name", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The user-name that is used by the client for 
basic authentication. If options for basic authen [...]
-    "validateSigningCertificateChain": { "index": 51, "kind": "parameter", 
"displayName": "Validate Signing Certificate Chain", "group": "security", 
"label": "security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Certificates to validate the message's 
signature ag [...]
+    "proxyAuthPassword": { "index": 34, "kind": "parameter", "displayName": 
"Proxy Auth Password", "group": "proxy", "label": "producer,proxy", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The password for basic authentication with the 
HTTP proxy." },
+    "proxyAuthUsername": { "index": 35, "kind": "parameter", "displayName": 
"Proxy Auth Username", "group": "proxy", "label": "producer,proxy", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The user name for basic authentication with 
the HTTP proxy. The credenti [...]
+    "proxyHost": { "index": 36, "kind": "parameter", "displayName": "Proxy 
Host", "group": "proxy", "label": "producer,proxy", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The host name of an HTTP proxy the client 
sends its AS2 messages through. The connection to the proxy is plain [...]
+    "proxyPort": { "index": 37, "kind": "parameter", "displayName": "Proxy 
Port", "group": "proxy", "label": "producer,proxy", "required": false, "type": 
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The port number of the HTTP proxy." },
+    "accessToken": { "index": 38, "kind": "parameter", "displayName": "Access 
Token", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The access token that is used by the client 
for bearer authentication." },
+    "asyncMdnAllowedHosts": { "index": 39, "kind": "parameter", "displayName": 
"Async Mdn Allowed Hosts", "group": "security", "label": "security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Comma separated list of host names an 
asynchronous MDN may be delivered to. The delivery  [...]
+    "decryptingPrivateKey": { "index": 40, "kind": "parameter", "displayName": 
"Decrypting Private Key", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated": 
false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The key used to encrypt the EDI message." },
+    "encryptingAlgorithm": { "index": 41, "kind": "parameter", "displayName": 
"Encrypting Algorithm", "group": "security", "label": "security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.as2.api.AS2EncryptionAlgorithm", "enum": [ 
"AES128_CBC", "AES192_CBC", "AES256_CBC", "AES128_CCM", "AES192_CCM", 
"AES256_CCM", "AES128_GCM", "AES192_GCM", "AES256_GCM", "CAMELLIA128_CBC", 
"CAMELLIA192_CBC", "CAMELLIA256_CBC", "CAST5_CBC", "DES_CBC", "DES_EDE3_CBC", 
"GOST28 [...]
+    "encryptingCertificateChain": { "index": 42, "kind": "parameter", 
"displayName": "Encrypting Certificate Chain", "group": "security", "label": 
"security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The chain of certificates used to encrypt EDI 
message." },
+    "hostnameVerifier": { "index": 43, "kind": "parameter", "displayName": 
"Hostname Verifier", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Set hostname verifier for SSL session." },
+    "mdnAccessToken": { "index": 44, "kind": "parameter", "displayName": "Mdn 
Access Token", "group": "security", "label": "security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The access token that is used by the server 
when it sends an async MDN. The optio [...]
+    "mdnPassword": { "index": 45, "kind": "parameter", "displayName": "Mdn 
Password", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The password that is used by the server for 
basic authentication when it sends an async  [...]
+    "mdnUserName": { "index": 46, "kind": "parameter", "displayName": "Mdn 
User Name", "group": "security", "label": "security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The user-name that is used by the server for 
basic authentication when it sends an asyn [...]
+    "password": { "index": 47, "kind": "parameter", "displayName": "Password", 
"group": "security", "label": "security", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The password that is used by the client for 
basic authentication." },
+    "signatureVerificationRequired": { "index": 48, "kind": "parameter", 
"displayName": "Signature Verification Required", "group": "security", "label": 
"security", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", 
"configurationField": "configuration", "description": "Whether to reject an 
inbound signed AS2 message that cann [...]
+    "signedReceiptMicAlgorithms": { "index": 49, "kind": "parameter", 
"displayName": "Signed Receipt Mic Algorithms", "group": "security", "label": 
"security", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"configurationClass": "org.apache.camel.component.as2.AS2Configuration", 
"configurationField": "configuration", "description": "The list of algorithms, 
in order of preference, requested to generate a messa [...]
+    "signingAlgorithm": { "index": 50, "kind": "parameter", "displayName": 
"Signing Algorithm", "group": "security", "label": "security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.as2.api.AS2SignatureAlgorithm", "enum": [ 
"SHA3_224WITHRSA", "SHA3_256WITHRSA", "SHA3_384withRSA", "SHA3_512WITHRSA", 
"MD5WITHRSA", "SHA1WITHRSA", "MD2WITHRSA", "SHA224WITHRSA", "SHA256WITHRSA", 
"SHA384WITHRSA", "SHA512WITHRSA", "RIPEMD128WITHRSA", "RIPEMD160WITHRSA", 
"RIPEMD256 [...]
+    "signingCertificateChain": { "index": 51, "kind": "parameter", 
"displayName": "Signing Certificate Chain", "group": "security", "label": 
"security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The chain of certificates used to sign EDI 
message." },
+    "signingPrivateKey": { "index": 52, "kind": "parameter", "displayName": 
"Signing Private Key", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated": 
false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The key used to sign the EDI message." },
+    "sslContext": { "index": 53, "kind": "parameter", "displayName": "Ssl 
Context", "group": "security", "label": "security", "required": false, "type": 
"object", "javaType": "javax.net.ssl.SSLContext", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Set SSL context for connection to remote 
server." },
+    "userName": { "index": 54, "kind": "parameter", "displayName": "User 
Name", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The user-name that is used by the client for 
basic authentication. If options for basic authen [...]
+    "validateSigningCertificateChain": { "index": 55, "kind": "parameter", 
"displayName": "Validate Signing Certificate Chain", "group": "security", 
"label": "security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Certificates to validate the message's 
signature ag [...]
   },
   "apis": {
     "client": { "consumerOnly": false, "producerOnly": true, "description": 
"Sends EDI Messages over HTTP", "methods": { "send": { "description": "Send 
ediMessage to trading partner", "signatures": [ 
"org.apache.hc.core5.http.protocol.HttpCoreContext send(Object ediMessage, 
String requestUri, String subject, String from, String as2From, String as2To, 
org.apache.camel.component.as2.api.AS2MessageStructure as2MessageStructure, 
String ediMessageContentType, String ediMessageCharset, String  [...]
diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc
index 8b3988308f01..b437954ec5e2 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/as2-component.adoc
@@ -107,3 +107,39 @@ consumer applies its own keys and certificates and its own 
asynchronous MDN sett
 The settings of the shared server connection still come from the first 
endpoint created on the port:
 `sslContextParameters` and `sslContext` (used for the TLS listener and for the 
https delivery of asynchronous MDNs),
 `mdnMessageTemplate`, `server`, `serverFqdn` and `as2Version`. They are per 
port, not per consumer.
+
+=== Sending through an HTTP proxy
+
+When outbound traffic must go through an HTTP forward proxy, set `proxyHost` 
and `proxyPort` on the
+`as2://client/send` endpoint. The client connects to the proxy over plain http:
+
+* without an `sslContext`, the AS2 message is sent to the proxy with an 
absolute request URI;
+* with an `sslContext`, the client asks the proxy to open a `CONNECT` tunnel 
to `targetHostname` and
+`targetPortNumber`, and the TLS handshake and hostname verification are made 
with the target through the tunnel.
+
+If the proxy requires authentication, set `proxyAuthUsername` and 
`proxyAuthPassword`. They are sent to the
+proxy as a preemptive Basic `Proxy-Authorization` header, on the `CONNECT` 
request when tunnelling, and are not
+sent to the AS2 partner. Setting only one of them is a configuration error: 
the endpoint fails to start. If the
+proxy refuses the request, for example with `407 Proxy Authentication 
Required`, the send fails with an exception,
+as the message has not reached the partner. The proxy options apply to the AS2 
client only: asynchronous MDNs are
+delivered directly.
+
+[source,yaml]
+----
+- route:
+    from:
+      uri: direct:sendToPartner
+      steps:
+        - to:
+            uri: as2://client/send
+            parameters:
+              inBody: ediMessage
+              targetHostname: partner.example.com
+              targetPortNumber: 443
+              sslContext: "#partnerSslContext"
+              proxyHost: proxy.internal
+              proxyPort: 3128
+              proxyAuthUsername: "{{proxy.user}}"
+              proxyAuthPassword: "{{proxy.password}}"
+              # ... the AS2 message options
+----
diff --git 
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java
 
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java
index dafaa6c63d2b..570ffc7561e1 100644
--- 
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java
+++ 
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ClientConnection.java
@@ -18,7 +18,9 @@ package org.apache.camel.component.as2.api;
 
 import java.io.IOException;
 import java.net.Socket;
+import java.nio.charset.StandardCharsets;
 import java.time.Duration;
+import java.util.Base64;
 import java.util.UUID;
 import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeoutException;
@@ -50,7 +52,10 @@ import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
 import org.apache.hc.core5.http.io.HttpClientConnection;
 import org.apache.hc.core5.http.io.HttpConnectionFactory;
 import org.apache.hc.core5.http.io.SocketConfig;
+import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
+import org.apache.hc.core5.http.message.BasicHttpRequest;
 import org.apache.hc.core5.http.message.MessageSupport;
+import org.apache.hc.core5.http.message.StatusLine;
 import org.apache.hc.core5.http.protocol.HttpContext;
 import org.apache.hc.core5.http.protocol.HttpCoreContext;
 import org.apache.hc.core5.http.protocol.HttpProcessor;
@@ -61,6 +66,7 @@ import org.apache.hc.core5.http.protocol.RequestDate;
 import org.apache.hc.core5.http.protocol.RequestExpectContinue;
 import org.apache.hc.core5.http.protocol.RequestTargetHost;
 import org.apache.hc.core5.http.protocol.RequestUserAgent;
+import org.apache.hc.core5.io.CloseMode;
 import org.apache.hc.core5.net.URIAuthority;
 import org.apache.hc.core5.util.TimeValue;
 import org.apache.hc.core5.util.Timeout;
@@ -70,6 +76,8 @@ public class AS2ClientConnection {
     private static final int RETRIEVE_CONNECTION_TIMEOUT_SECONDS = 5;
 
     private HttpHost targetHost;
+    private HttpRoute route;
+    private String proxyAuthorization;
     private HttpProcessor httpProcessor;
     private String as2Version;
     private String userAgent;
@@ -91,6 +99,16 @@ public class AS2ClientConnection {
                                Integer connectionPoolMaxSize, Duration 
connectionPoolTtl,
                                SSLContext sslContext, HostnameVerifier 
hostnameVerifier,
                                boolean expectContinue) throws IOException {
+        this(as2Version, userAgent, clientFqdn, targetHostName, 
targetPortNumber, socketTimeout, connectionTimeout,
+             connectionPoolMaxSize, connectionPoolTtl, sslContext, 
hostnameVerifier, expectContinue, null, null, null, null);
+    }
+
+    public AS2ClientConnection(String as2Version, String userAgent, String 
clientFqdn, String targetHostName,
+                               Integer targetPortNumber, Duration 
socketTimeout, Duration connectionTimeout,
+                               Integer connectionPoolMaxSize, Duration 
connectionPoolTtl,
+                               SSLContext sslContext, HostnameVerifier 
hostnameVerifier,
+                               boolean expectContinue, String proxyHost, 
Integer proxyPort,
+                               String proxyAuthUsername, String 
proxyAuthPassword) throws IOException {
 
         this.as2Version = ObjectHelper.notNull(as2Version, "as2Version");
         this.userAgent = ObjectHelper.notNull(userAgent, "userAgent");
@@ -99,6 +117,21 @@ public class AS2ClientConnection {
                 sslContext != null ? "https" : "http",
                 ObjectHelper.notNull(targetHostName, "targetHostName"),
                 ObjectHelper.notNull(targetPortNumber, "targetPortNumber"));
+        if (proxyHost != null) {
+            // the connection to the proxy itself is plain http, whatever the 
target scheme: an https target is
+            // reached through a CONNECT tunnel
+            HttpHost proxy = new HttpHost("http", proxyHost, 
ObjectHelper.notNull(proxyPort, "proxyPort"));
+            this.route = new HttpRoute(targetHost, null, proxy, sslContext != 
null);
+            if (proxyAuthUsername != null && proxyAuthPassword != null) {
+                this.proxyAuthorization = "Basic " + 
Base64.getEncoder().encodeToString(
+                        (proxyAuthUsername + ":" + 
proxyAuthPassword).getBytes(StandardCharsets.UTF_8));
+            } else if (proxyAuthUsername != null || proxyAuthPassword != null) 
{
+                // one credential alone is a configuration mistake: report it 
now rather than as a 407 on the first send
+                throw new IllegalArgumentException("proxyAuthUsername and 
proxyAuthPassword must be set together");
+            }
+        } else {
+            this.route = new HttpRoute(targetHost);
+        }
         ObjectHelper.notNull(socketTimeout, "socketTimeout");
         this.connectionTimeoutMilliseconds = (int) 
ObjectHelper.notNull(connectionTimeout, "connectionTimeout").toMillis();
         ObjectHelper.notNull(connectionPoolMaxSize, "connectionPoolMaxSize");
@@ -191,8 +224,17 @@ public class AS2ClientConnection {
     public HttpResponse send(ClassicHttpRequest request, HttpCoreContext 
httpContext)
             throws HttpException, IOException, InterruptedException, 
ExecutionException, TimeoutException {
 
-        HttpRoute route = new HttpRoute(targetHost);
         request.setAuthority(new URIAuthority(targetHost));
+        if (route.getProxyHost() != null && !route.isTunnelled()) {
+            // a proxy forwarding plain http needs the absolute request-URI, 
and its credentials on every request
+            request.setScheme(targetHost.getSchemeName());
+            if (request instanceof BasicHttpRequest basicRequest) {
+                basicRequest.setAbsoluteRequestUri(true);
+            }
+            if (proxyAuthorization != null) {
+                request.setHeader(HttpHeaders.PROXY_AUTHORIZATION, 
proxyAuthorization);
+            }
+        }
         LeaseRequest leaseRequest = 
connectionPoolManager.lease(UUID.randomUUID().toString(), route, null);
 
         ConnectionEndpoint endpoint = null;
@@ -200,7 +242,18 @@ public class AS2ClientConnection {
         try {
             endpoint = 
leaseRequest.get(Timeout.ofSeconds(RETRIEVE_CONNECTION_TIMEOUT_SECONDS));
             if (!endpoint.isConnected()) {
-                connectionPoolManager.connect(endpoint, 
TimeValue.ofMilliseconds(connectionTimeoutMilliseconds), httpContext);
+                try {
+                    connectionPoolManager.connect(endpoint, 
TimeValue.ofMilliseconds(connectionTimeoutMilliseconds),
+                            httpContext);
+                    if (route.isTunnelled()) {
+                        createTunnel(endpoint);
+                        connectionPoolManager.upgrade(endpoint, httpContext);
+                    }
+                } catch (IOException | HttpException | RuntimeException e) {
+                    // a connection without its tunnel or its TLS layer must 
not go back to the pool
+                    endpoint.close(CloseMode.IMMEDIATE);
+                    throw e;
+                }
             }
             // Execute Request
             ConnectionEndpoint.RequestExecutor requestExecutor = new 
ConnectionEndpoint.RequestExecutor() {
@@ -215,6 +268,15 @@ public class AS2ClientConnection {
                 }
             };
             response = endpoint.execute(UUID.randomUUID().toString(), request, 
requestExecutor, httpContext);
+            if (route.getProxyHost() != null && !route.isTunnelled()
+                    && response.getCode() == 
HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED) {
+                // the proxy may keep the connection open with the body of its 
407 unread: it must not be reused
+                endpoint.close(CloseMode.IMMEDIATE);
+                // only the proxy answers 407: the message has not reached the 
AS2 partner
+                throw new HttpException(
+                        "Proxy " + route.getProxyHost().toHostString() + " 
refused to forward the request to "
+                                        + targetHost.toHostString() + ": " + 
new StatusLine(response));
+            }
         } finally {
             if (endpoint != null) {
                 TimeValue keepAlive = null;
@@ -239,9 +301,38 @@ public class AS2ClientConnection {
         return response;
     }
 
+    /**
+     * Asks the proxy to open a tunnel to the target. The proxy credentials go 
on the CONNECT request only, so they are
+     * never seen by the AS2 partner at the other end of the tunnel.
+     */
+    private void createTunnel(ConnectionEndpoint endpoint) throws IOException, 
HttpException {
+        ClassicHttpRequest connect = new 
BasicClassicHttpRequest(Method.CONNECT, targetHost, targetHost.toHostString());
+        connect.setVersion(HttpVersion.HTTP_1_1);
+        connect.setHeader(HttpHeaders.HOST, targetHost.toHostString());
+        connect.setHeader(HttpHeaders.USER_AGENT, userAgent);
+        if (proxyAuthorization != null) {
+            connect.setHeader(HttpHeaders.PROXY_AUTHORIZATION, 
proxyAuthorization);
+        }
+        // a separate context, so the caller's context keeps the AS2 request 
and response
+        ClassicHttpResponse response = 
endpoint.execute(UUID.randomUUID().toString(), connect,
+                (request, conn, context) -> new 
HttpRequestExecutor().execute(request, conn, context),
+                HttpCoreContext.create());
+        int status = response.getCode();
+        if (status < HttpStatus.SC_SUCCESS || status >= 
HttpStatus.SC_REDIRECTION) {
+            throw new HttpException(
+                    "Proxy " + route.getProxyHost().toHostString() + " refused 
to open a tunnel to "
+                                    + targetHost.toHostString() + ": " + new 
StatusLine(response));
+        }
+    }
+
     private HttpConnection createTestConnection(
             SSLContext sslContext, 
HttpConnectionFactory<ManagedHttpClientConnection> connFactory)
             throws IOException {
+        HttpHost proxy = route.getProxyHost();
+        if (proxy != null) {
+            // the target is only reached through the proxy once a request is 
sent, so check the proxy can be reached
+            return connFactory.createConnection(new 
Socket(proxy.getHostName(), proxy.getPort()));
+        }
         if (sslContext == null) {
             return connFactory.createConnection(new 
Socket(targetHost.getHostName(), targetHost.getPort()));
         } else {
diff --git 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
index beb0ce63526f..2ec8edb4920e 100644
--- 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
+++ 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2AsyncMDNServerManagerEndpointConfigurationConfigurer.java
@@ -53,6 +53,10 @@ public class 
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
         map.put("MdnUserName", java.lang.String.class);
         map.put("MethodName", java.lang.String.class);
         map.put("Password", java.lang.String.class);
+        map.put("ProxyAuthPassword", java.lang.String.class);
+        map.put("ProxyAuthUsername", java.lang.String.class);
+        map.put("ProxyHost", java.lang.String.class);
+        map.put("ProxyPort", java.lang.Integer.class);
         map.put("ReceiptDeliveryOption", java.lang.String.class);
         map.put("RequestUri", java.lang.String.class);
         map.put("RequestUriPattern", java.lang.String.class);
@@ -138,6 +142,14 @@ public class 
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
         case "methodname":
         case "methodName": target.setMethodName(property(camelContext, 
java.lang.String.class, value)); return true;
         case "password": target.setPassword(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyauthpassword":
+        case "proxyAuthPassword": 
target.setProxyAuthPassword(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "proxyauthusername":
+        case "proxyAuthUsername": 
target.setProxyAuthUsername(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "proxyhost":
+        case "proxyHost": target.setProxyHost(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyport":
+        case "proxyPort": target.setProxyPort(property(camelContext, 
java.lang.Integer.class, value)); return true;
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": 
target.setReceiptDeliveryOption(property(camelContext, java.lang.String.class, 
value)); return true;
         case "requesturi":
@@ -244,6 +256,14 @@ public class 
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
         case "methodname":
         case "methodName": return java.lang.String.class;
         case "password": return java.lang.String.class;
+        case "proxyauthpassword":
+        case "proxyAuthPassword": return java.lang.String.class;
+        case "proxyauthusername":
+        case "proxyAuthUsername": return java.lang.String.class;
+        case "proxyhost":
+        case "proxyHost": return java.lang.String.class;
+        case "proxyport":
+        case "proxyPort": return java.lang.Integer.class;
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": return java.lang.String.class;
         case "requesturi":
@@ -346,6 +366,14 @@ public class 
AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org
         case "methodname":
         case "methodName": return target.getMethodName();
         case "password": return target.getPassword();
+        case "proxyauthpassword":
+        case "proxyAuthPassword": return target.getProxyAuthPassword();
+        case "proxyauthusername":
+        case "proxyAuthUsername": return target.getProxyAuthUsername();
+        case "proxyhost":
+        case "proxyHost": return target.getProxyHost();
+        case "proxyport":
+        case "proxyPort": return target.getProxyPort();
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": return target.getReceiptDeliveryOption();
         case "requesturi":
diff --git 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
index 96a5158d8e56..395cb3eca76d 100644
--- 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
+++ 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ClientManagerEndpointConfigurationConfigurer.java
@@ -55,6 +55,10 @@ public class AS2ClientManagerEndpointConfigurationConfigurer 
extends org.apache.
         map.put("MdnUserName", java.lang.String.class);
         map.put("MethodName", java.lang.String.class);
         map.put("Password", java.lang.String.class);
+        map.put("ProxyAuthPassword", java.lang.String.class);
+        map.put("ProxyAuthUsername", java.lang.String.class);
+        map.put("ProxyHost", java.lang.String.class);
+        map.put("ProxyPort", java.lang.Integer.class);
         map.put("ReceiptDeliveryOption", java.lang.String.class);
         map.put("RequestUri", java.lang.String.class);
         map.put("Server", java.lang.String.class);
@@ -143,6 +147,14 @@ public class 
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
         case "methodname":
         case "methodName": target.setMethodName(property(camelContext, 
java.lang.String.class, value)); return true;
         case "password": target.setPassword(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyauthpassword":
+        case "proxyAuthPassword": 
target.setProxyAuthPassword(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "proxyauthusername":
+        case "proxyAuthUsername": 
target.setProxyAuthUsername(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "proxyhost":
+        case "proxyHost": target.setProxyHost(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyport":
+        case "proxyPort": target.setProxyPort(property(camelContext, 
java.lang.Integer.class, value)); return true;
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": 
target.setReceiptDeliveryOption(property(camelContext, java.lang.String.class, 
value)); return true;
         case "requesturi":
@@ -251,6 +263,14 @@ public class 
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
         case "methodname":
         case "methodName": return java.lang.String.class;
         case "password": return java.lang.String.class;
+        case "proxyauthpassword":
+        case "proxyAuthPassword": return java.lang.String.class;
+        case "proxyauthusername":
+        case "proxyAuthUsername": return java.lang.String.class;
+        case "proxyhost":
+        case "proxyHost": return java.lang.String.class;
+        case "proxyport":
+        case "proxyPort": return java.lang.Integer.class;
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": return java.lang.String.class;
         case "requesturi":
@@ -355,6 +375,14 @@ public class 
AS2ClientManagerEndpointConfigurationConfigurer extends org.apache.
         case "methodname":
         case "methodName": return target.getMethodName();
         case "password": return target.getPassword();
+        case "proxyauthpassword":
+        case "proxyAuthPassword": return target.getProxyAuthPassword();
+        case "proxyauthusername":
+        case "proxyAuthUsername": return target.getProxyAuthUsername();
+        case "proxyhost":
+        case "proxyHost": return target.getProxyHost();
+        case "proxyport":
+        case "proxyPort": return target.getProxyPort();
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": return target.getReceiptDeliveryOption();
         case "requesturi":
diff --git 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
index 0af682c05337..135bbbe25b06 100644
--- 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
+++ 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ConfigurationConfigurer.java
@@ -53,6 +53,10 @@ public class AS2ConfigurationConfigurer extends 
org.apache.camel.support.compone
         map.put("MdnUserName", java.lang.String.class);
         map.put("MethodName", java.lang.String.class);
         map.put("Password", java.lang.String.class);
+        map.put("ProxyAuthPassword", java.lang.String.class);
+        map.put("ProxyAuthUsername", java.lang.String.class);
+        map.put("ProxyHost", java.lang.String.class);
+        map.put("ProxyPort", java.lang.Integer.class);
         map.put("ReceiptDeliveryOption", java.lang.String.class);
         map.put("RequestUri", java.lang.String.class);
         map.put("Server", java.lang.String.class);
@@ -137,6 +141,14 @@ public class AS2ConfigurationConfigurer extends 
org.apache.camel.support.compone
         case "methodname":
         case "methodName": target.setMethodName(property(camelContext, 
java.lang.String.class, value)); return true;
         case "password": target.setPassword(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyauthpassword":
+        case "proxyAuthPassword": 
target.setProxyAuthPassword(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "proxyauthusername":
+        case "proxyAuthUsername": 
target.setProxyAuthUsername(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "proxyhost":
+        case "proxyHost": target.setProxyHost(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyport":
+        case "proxyPort": target.setProxyPort(property(camelContext, 
java.lang.Integer.class, value)); return true;
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": 
target.setReceiptDeliveryOption(property(camelContext, java.lang.String.class, 
value)); return true;
         case "requesturi":
@@ -241,6 +253,14 @@ public class AS2ConfigurationConfigurer extends 
org.apache.camel.support.compone
         case "methodname":
         case "methodName": return java.lang.String.class;
         case "password": return java.lang.String.class;
+        case "proxyauthpassword":
+        case "proxyAuthPassword": return java.lang.String.class;
+        case "proxyauthusername":
+        case "proxyAuthUsername": return java.lang.String.class;
+        case "proxyhost":
+        case "proxyHost": return java.lang.String.class;
+        case "proxyport":
+        case "proxyPort": return java.lang.Integer.class;
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": return java.lang.String.class;
         case "requesturi":
@@ -341,6 +361,14 @@ public class AS2ConfigurationConfigurer extends 
org.apache.camel.support.compone
         case "methodname":
         case "methodName": return target.getMethodName();
         case "password": return target.getPassword();
+        case "proxyauthpassword":
+        case "proxyAuthPassword": return target.getProxyAuthPassword();
+        case "proxyauthusername":
+        case "proxyAuthUsername": return target.getProxyAuthUsername();
+        case "proxyhost":
+        case "proxyHost": return target.getProxyHost();
+        case "proxyport":
+        case "proxyPort": return target.getProxyPort();
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": return target.getReceiptDeliveryOption();
         case "requesturi":
diff --git 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
index 69a06454c0db..50f3fc057ef5 100644
--- 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
+++ 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointConfigurer.java
@@ -56,6 +56,10 @@ public class AS2EndpointConfigurer extends 
PropertyConfigurerSupport implements
         map.put("ExchangePattern", org.apache.camel.ExchangePattern.class);
         map.put("ExpectContinue", boolean.class);
         map.put("LazyStartProducer", boolean.class);
+        map.put("ProxyAuthPassword", java.lang.String.class);
+        map.put("ProxyAuthUsername", java.lang.String.class);
+        map.put("ProxyHost", java.lang.String.class);
+        map.put("ProxyPort", java.lang.Integer.class);
         map.put("AccessToken", java.lang.String.class);
         map.put("AsyncMdnAllowedHosts", java.lang.String.class);
         map.put("DecryptingPrivateKey", java.security.PrivateKey.class);
@@ -145,6 +149,14 @@ public class AS2EndpointConfigurer extends 
PropertyConfigurerSupport implements
         case "mdnusername":
         case "mdnUserName": 
target.getConfiguration().setMdnUserName(property(camelContext, 
java.lang.String.class, value)); return true;
         case "password": 
target.getConfiguration().setPassword(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyauthpassword":
+        case "proxyAuthPassword": 
target.getConfiguration().setProxyAuthPassword(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyauthusername":
+        case "proxyAuthUsername": 
target.getConfiguration().setProxyAuthUsername(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyhost":
+        case "proxyHost": 
target.getConfiguration().setProxyHost(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyport":
+        case "proxyPort": 
target.getConfiguration().setProxyPort(property(camelContext, 
java.lang.Integer.class, value)); return true;
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": 
target.getConfiguration().setReceiptDeliveryOption(property(camelContext, 
java.lang.String.class, value)); return true;
         case "requesturi":
@@ -253,6 +265,14 @@ public class AS2EndpointConfigurer extends 
PropertyConfigurerSupport implements
         case "mdnusername":
         case "mdnUserName": return java.lang.String.class;
         case "password": return java.lang.String.class;
+        case "proxyauthpassword":
+        case "proxyAuthPassword": return java.lang.String.class;
+        case "proxyauthusername":
+        case "proxyAuthUsername": return java.lang.String.class;
+        case "proxyhost":
+        case "proxyHost": return java.lang.String.class;
+        case "proxyport":
+        case "proxyPort": return java.lang.Integer.class;
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": return java.lang.String.class;
         case "requesturi":
@@ -357,6 +377,14 @@ public class AS2EndpointConfigurer extends 
PropertyConfigurerSupport implements
         case "mdnusername":
         case "mdnUserName": return target.getConfiguration().getMdnUserName();
         case "password": return target.getConfiguration().getPassword();
+        case "proxyauthpassword":
+        case "proxyAuthPassword": return 
target.getConfiguration().getProxyAuthPassword();
+        case "proxyauthusername":
+        case "proxyAuthUsername": return 
target.getConfiguration().getProxyAuthUsername();
+        case "proxyhost":
+        case "proxyHost": return target.getConfiguration().getProxyHost();
+        case "proxyport":
+        case "proxyPort": return target.getConfiguration().getProxyPort();
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": return 
target.getConfiguration().getReceiptDeliveryOption();
         case "requesturi":
diff --git 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
index 08910fb590dc..bbf65d808ee4 100644
--- 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
+++ 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2EndpointUriFactory.java
@@ -24,7 +24,7 @@ public class AS2EndpointUriFactory extends 
org.apache.camel.support.component.En
     private static final Set<String> ENDPOINT_IDENTITY_PROPERTY_NAMES;
     private static final Map<String, String> MULTI_VALUE_PREFIXES;
     static {
-        Set<String> props = new HashSet<>(55);
+        Set<String> props = new HashSet<>(59);
         props.add("accessToken");
         props.add("apiName");
         props.add("as2From");
@@ -62,6 +62,10 @@ public class AS2EndpointUriFactory extends 
org.apache.camel.support.component.En
         props.add("mdnUserName");
         props.add("methodName");
         props.add("password");
+        props.add("proxyAuthPassword");
+        props.add("proxyAuthUsername");
+        props.add("proxyHost");
+        props.add("proxyPort");
         props.add("receiptDeliveryOption");
         props.add("requestUri");
         props.add("requestUriPattern");
@@ -81,12 +85,14 @@ public class AS2EndpointUriFactory extends 
org.apache.camel.support.component.En
         props.add("userName");
         props.add("validateSigningCertificateChain");
         PROPERTY_NAMES = Collections.unmodifiableSet(props);
-        Set<String> secretProps = new HashSet<>(6);
+        Set<String> secretProps = new HashSet<>(8);
         secretProps.add("accessToken");
         secretProps.add("mdnAccessToken");
         secretProps.add("mdnPassword");
         secretProps.add("mdnUserName");
         secretProps.add("password");
+        secretProps.add("proxyAuthPassword");
+        secretProps.add("proxyAuthUsername");
         secretProps.add("userName");
         SECRET_PROPERTY_NAMES = Collections.unmodifiableSet(secretProps);
         ENDPOINT_IDENTITY_PROPERTY_NAMES = Collections.emptySet();
diff --git 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
index a3580c9594e2..d8a55e239a3b 100644
--- 
a/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
+++ 
b/components/camel-as2/camel-as2-component/src/generated/java/org/apache/camel/component/as2/AS2ServerManagerEndpointConfigurationConfigurer.java
@@ -53,6 +53,10 @@ public class AS2ServerManagerEndpointConfigurationConfigurer 
extends org.apache.
         map.put("MdnUserName", java.lang.String.class);
         map.put("MethodName", java.lang.String.class);
         map.put("Password", java.lang.String.class);
+        map.put("ProxyAuthPassword", java.lang.String.class);
+        map.put("ProxyAuthUsername", java.lang.String.class);
+        map.put("ProxyHost", java.lang.String.class);
+        map.put("ProxyPort", java.lang.Integer.class);
         map.put("ReceiptDeliveryOption", java.lang.String.class);
         map.put("RequestUri", java.lang.String.class);
         map.put("RequestUriPattern", java.lang.String.class);
@@ -138,6 +142,14 @@ public class 
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
         case "methodname":
         case "methodName": target.setMethodName(property(camelContext, 
java.lang.String.class, value)); return true;
         case "password": target.setPassword(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyauthpassword":
+        case "proxyAuthPassword": 
target.setProxyAuthPassword(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "proxyauthusername":
+        case "proxyAuthUsername": 
target.setProxyAuthUsername(property(camelContext, java.lang.String.class, 
value)); return true;
+        case "proxyhost":
+        case "proxyHost": target.setProxyHost(property(camelContext, 
java.lang.String.class, value)); return true;
+        case "proxyport":
+        case "proxyPort": target.setProxyPort(property(camelContext, 
java.lang.Integer.class, value)); return true;
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": 
target.setReceiptDeliveryOption(property(camelContext, java.lang.String.class, 
value)); return true;
         case "requesturi":
@@ -244,6 +256,14 @@ public class 
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
         case "methodname":
         case "methodName": return java.lang.String.class;
         case "password": return java.lang.String.class;
+        case "proxyauthpassword":
+        case "proxyAuthPassword": return java.lang.String.class;
+        case "proxyauthusername":
+        case "proxyAuthUsername": return java.lang.String.class;
+        case "proxyhost":
+        case "proxyHost": return java.lang.String.class;
+        case "proxyport":
+        case "proxyPort": return java.lang.Integer.class;
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": return java.lang.String.class;
         case "requesturi":
@@ -346,6 +366,14 @@ public class 
AS2ServerManagerEndpointConfigurationConfigurer extends org.apache.
         case "methodname":
         case "methodName": return target.getMethodName();
         case "password": return target.getPassword();
+        case "proxyauthpassword":
+        case "proxyAuthPassword": return target.getProxyAuthPassword();
+        case "proxyauthusername":
+        case "proxyAuthUsername": return target.getProxyAuthUsername();
+        case "proxyhost":
+        case "proxyHost": return target.getProxyHost();
+        case "proxyport":
+        case "proxyPort": return target.getProxyPort();
         case "receiptdeliveryoption":
         case "receiptDeliveryOption": return target.getReceiptDeliveryOption();
         case "requesturi":
diff --git 
a/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
 
b/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
index 81730d1281b5..1e5fa00800bf 100644
--- 
a/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
+++ 
b/components/camel-as2/camel-as2-component/src/generated/resources/META-INF/org/apache/camel/component/as2/as2.json
@@ -72,24 +72,28 @@
     "exchangePattern": { "index": 31, "kind": "parameter", "displayName": 
"Exchange Pattern", "group": "consumer (advanced)", "label": 
"consumer,advanced", "required": false, "type": "enum", "javaType": 
"org.apache.camel.ExchangePattern", "enum": [ "InOnly", "InOut" ], 
"deprecated": false, "autowired": false, "secret": false, "description": "Sets 
the exchange pattern when the consumer creates an exchange." },
     "expectContinue": { "index": 32, "kind": "parameter", "displayName": 
"Expect Continue", "group": "producer", "label": "producer", "required": false, 
"type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": 
false, "secret": false, "defaultValue": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Controls whether the Expect: 100-Continue 
header is included in outbound AS2 messages. W [...]
     "lazyStartProducer": { "index": 33, "kind": "parameter", "displayName": 
"Lazy Start Producer", "group": "producer (advanced)", "label": 
"producer,advanced", "required": false, "type": "boolean", "javaType": 
"boolean", "deprecated": false, "autowired": false, "secret": false, 
"defaultValue": false, "description": "Whether the producer should be started 
lazy (on the first message). By starting lazy you can use this to allow 
CamelContext and routes to startup in situations where a produ [...]
-    "accessToken": { "index": 34, "kind": "parameter", "displayName": "Access 
Token", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The access token that is used by the client 
for bearer authentication." },
-    "asyncMdnAllowedHosts": { "index": 35, "kind": "parameter", "displayName": 
"Async Mdn Allowed Hosts", "group": "security", "label": "security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Comma separated list of host names an 
asynchronous MDN may be delivered to. The delivery  [...]
-    "decryptingPrivateKey": { "index": 36, "kind": "parameter", "displayName": 
"Decrypting Private Key", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated": 
false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The key used to encrypt the EDI message." },
-    "encryptingAlgorithm": { "index": 37, "kind": "parameter", "displayName": 
"Encrypting Algorithm", "group": "security", "label": "security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.as2.api.AS2EncryptionAlgorithm", "enum": [ 
"AES128_CBC", "AES192_CBC", "AES256_CBC", "AES128_CCM", "AES192_CCM", 
"AES256_CCM", "AES128_GCM", "AES192_GCM", "AES256_GCM", "CAMELLIA128_CBC", 
"CAMELLIA192_CBC", "CAMELLIA256_CBC", "CAST5_CBC", "DES_CBC", "DES_EDE3_CBC", 
"GOST28 [...]
-    "encryptingCertificateChain": { "index": 38, "kind": "parameter", 
"displayName": "Encrypting Certificate Chain", "group": "security", "label": 
"security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The chain of certificates used to encrypt EDI 
message." },
-    "hostnameVerifier": { "index": 39, "kind": "parameter", "displayName": 
"Hostname Verifier", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Set hostname verifier for SSL session." },
-    "mdnAccessToken": { "index": 40, "kind": "parameter", "displayName": "Mdn 
Access Token", "group": "security", "label": "security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The access token that is used by the server 
when it sends an async MDN. The optio [...]
-    "mdnPassword": { "index": 41, "kind": "parameter", "displayName": "Mdn 
Password", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The password that is used by the server for 
basic authentication when it sends an async  [...]
-    "mdnUserName": { "index": 42, "kind": "parameter", "displayName": "Mdn 
User Name", "group": "security", "label": "security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The user-name that is used by the server for 
basic authentication when it sends an asyn [...]
-    "password": { "index": 43, "kind": "parameter", "displayName": "Password", 
"group": "security", "label": "security", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The password that is used by the client for 
basic authentication." },
-    "signatureVerificationRequired": { "index": 44, "kind": "parameter", 
"displayName": "Signature Verification Required", "group": "security", "label": 
"security", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", 
"configurationField": "configuration", "description": "Whether to reject an 
inbound signed AS2 message that cann [...]
-    "signedReceiptMicAlgorithms": { "index": 45, "kind": "parameter", 
"displayName": "Signed Receipt Mic Algorithms", "group": "security", "label": 
"security", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"configurationClass": "org.apache.camel.component.as2.AS2Configuration", 
"configurationField": "configuration", "description": "The list of algorithms, 
in order of preference, requested to generate a messa [...]
-    "signingAlgorithm": { "index": 46, "kind": "parameter", "displayName": 
"Signing Algorithm", "group": "security", "label": "security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.as2.api.AS2SignatureAlgorithm", "enum": [ 
"SHA3_224WITHRSA", "SHA3_256WITHRSA", "SHA3_384withRSA", "SHA3_512WITHRSA", 
"MD5WITHRSA", "SHA1WITHRSA", "MD2WITHRSA", "SHA224WITHRSA", "SHA256WITHRSA", 
"SHA384WITHRSA", "SHA512WITHRSA", "RIPEMD128WITHRSA", "RIPEMD160WITHRSA", 
"RIPEMD256 [...]
-    "signingCertificateChain": { "index": 47, "kind": "parameter", 
"displayName": "Signing Certificate Chain", "group": "security", "label": 
"security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The chain of certificates used to sign EDI 
message." },
-    "signingPrivateKey": { "index": 48, "kind": "parameter", "displayName": 
"Signing Private Key", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated": 
false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The key used to sign the EDI message." },
-    "sslContext": { "index": 49, "kind": "parameter", "displayName": "Ssl 
Context", "group": "security", "label": "security", "required": false, "type": 
"object", "javaType": "javax.net.ssl.SSLContext", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Set SSL context for connection to remote 
server." },
-    "userName": { "index": 50, "kind": "parameter", "displayName": "User 
Name", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The user-name that is used by the client for 
basic authentication. If options for basic authen [...]
-    "validateSigningCertificateChain": { "index": 51, "kind": "parameter", 
"displayName": "Validate Signing Certificate Chain", "group": "security", 
"label": "security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Certificates to validate the message's 
signature ag [...]
+    "proxyAuthPassword": { "index": 34, "kind": "parameter", "displayName": 
"Proxy Auth Password", "group": "proxy", "label": "producer,proxy", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The password for basic authentication with the 
HTTP proxy." },
+    "proxyAuthUsername": { "index": 35, "kind": "parameter", "displayName": 
"Proxy Auth Username", "group": "proxy", "label": "producer,proxy", "required": 
false, "type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The user name for basic authentication with 
the HTTP proxy. The credenti [...]
+    "proxyHost": { "index": 36, "kind": "parameter", "displayName": "Proxy 
Host", "group": "proxy", "label": "producer,proxy", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The host name of an HTTP proxy the client 
sends its AS2 messages through. The connection to the proxy is plain [...]
+    "proxyPort": { "index": 37, "kind": "parameter", "displayName": "Proxy 
Port", "group": "proxy", "label": "producer,proxy", "required": false, "type": 
"integer", "javaType": "java.lang.Integer", "deprecated": false, "autowired": 
false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The port number of the HTTP proxy." },
+    "accessToken": { "index": 38, "kind": "parameter", "displayName": "Access 
Token", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The access token that is used by the client 
for bearer authentication." },
+    "asyncMdnAllowedHosts": { "index": 39, "kind": "parameter", "displayName": 
"Async Mdn Allowed Hosts", "group": "security", "label": "security", 
"required": false, "type": "string", "javaType": "java.lang.String", 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Comma separated list of host names an 
asynchronous MDN may be delivered to. The delivery  [...]
+    "decryptingPrivateKey": { "index": 40, "kind": "parameter", "displayName": 
"Decrypting Private Key", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated": 
false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The key used to encrypt the EDI message." },
+    "encryptingAlgorithm": { "index": 41, "kind": "parameter", "displayName": 
"Encrypting Algorithm", "group": "security", "label": "security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.as2.api.AS2EncryptionAlgorithm", "enum": [ 
"AES128_CBC", "AES192_CBC", "AES256_CBC", "AES128_CCM", "AES192_CCM", 
"AES256_CCM", "AES128_GCM", "AES192_GCM", "AES256_GCM", "CAMELLIA128_CBC", 
"CAMELLIA192_CBC", "CAMELLIA256_CBC", "CAST5_CBC", "DES_CBC", "DES_EDE3_CBC", 
"GOST28 [...]
+    "encryptingCertificateChain": { "index": 42, "kind": "parameter", 
"displayName": "Encrypting Certificate Chain", "group": "security", "label": 
"security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The chain of certificates used to encrypt EDI 
message." },
+    "hostnameVerifier": { "index": 43, "kind": "parameter", "displayName": 
"Hostname Verifier", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "javax.net.ssl.HostnameVerifier", 
"deprecated": false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Set hostname verifier for SSL session." },
+    "mdnAccessToken": { "index": 44, "kind": "parameter", "displayName": "Mdn 
Access Token", "group": "security", "label": "security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The access token that is used by the server 
when it sends an async MDN. The optio [...]
+    "mdnPassword": { "index": 45, "kind": "parameter", "displayName": "Mdn 
Password", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The password that is used by the server for 
basic authentication when it sends an async  [...]
+    "mdnUserName": { "index": 46, "kind": "parameter", "displayName": "Mdn 
User Name", "group": "security", "label": "security", "required": false, 
"type": "string", "javaType": "java.lang.String", "deprecated": false, 
"autowired": false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The user-name that is used by the server for 
basic authentication when it sends an asyn [...]
+    "password": { "index": 47, "kind": "parameter", "displayName": "Password", 
"group": "security", "label": "security", "required": false, "type": "string", 
"javaType": "java.lang.String", "deprecated": false, "autowired": false, 
"secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The password that is used by the client for 
basic authentication." },
+    "signatureVerificationRequired": { "index": 48, "kind": "parameter", 
"displayName": "Signature Verification Required", "group": "security", "label": 
"security", "required": false, "type": "boolean", "javaType": "boolean", 
"deprecated": false, "autowired": false, "secret": false, "defaultValue": 
false, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", 
"configurationField": "configuration", "description": "Whether to reject an 
inbound signed AS2 message that cann [...]
+    "signedReceiptMicAlgorithms": { "index": 49, "kind": "parameter", 
"displayName": "Signed Receipt Mic Algorithms", "group": "security", "label": 
"security", "required": false, "type": "string", "javaType": 
"java.lang.String", "deprecated": false, "autowired": false, "secret": false, 
"configurationClass": "org.apache.camel.component.as2.AS2Configuration", 
"configurationField": "configuration", "description": "The list of algorithms, 
in order of preference, requested to generate a messa [...]
+    "signingAlgorithm": { "index": 50, "kind": "parameter", "displayName": 
"Signing Algorithm", "group": "security", "label": "security", "required": 
false, "type": "enum", "javaType": 
"org.apache.camel.component.as2.api.AS2SignatureAlgorithm", "enum": [ 
"SHA3_224WITHRSA", "SHA3_256WITHRSA", "SHA3_384withRSA", "SHA3_512WITHRSA", 
"MD5WITHRSA", "SHA1WITHRSA", "MD2WITHRSA", "SHA224WITHRSA", "SHA256WITHRSA", 
"SHA384WITHRSA", "SHA512WITHRSA", "RIPEMD128WITHRSA", "RIPEMD160WITHRSA", 
"RIPEMD256 [...]
+    "signingCertificateChain": { "index": 51, "kind": "parameter", 
"displayName": "Signing Certificate Chain", "group": "security", "label": 
"security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The chain of certificates used to sign EDI 
message." },
+    "signingPrivateKey": { "index": 52, "kind": "parameter", "displayName": 
"Signing Private Key", "group": "security", "label": "security", "required": 
false, "type": "object", "javaType": "java.security.PrivateKey", "deprecated": 
false, "autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The key used to sign the EDI message." },
+    "sslContext": { "index": 53, "kind": "parameter", "displayName": "Ssl 
Context", "group": "security", "label": "security", "required": false, "type": 
"object", "javaType": "javax.net.ssl.SSLContext", "deprecated": false, 
"autowired": false, "secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Set SSL context for connection to remote 
server." },
+    "userName": { "index": 54, "kind": "parameter", "displayName": "User 
Name", "group": "security", "label": "security", "required": false, "type": 
"string", "javaType": "java.lang.String", "deprecated": false, "autowired": 
false, "secret": true, "security": "secret", "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "The user-name that is used by the client for 
basic authentication. If options for basic authen [...]
+    "validateSigningCertificateChain": { "index": 55, "kind": "parameter", 
"displayName": "Validate Signing Certificate Chain", "group": "security", 
"label": "security", "required": false, "type": "object", "javaType": 
"java.security.cert.Certificate[]", "deprecated": false, "autowired": false, 
"secret": false, "configurationClass": 
"org.apache.camel.component.as2.AS2Configuration", "configurationField": 
"configuration", "description": "Certificates to validate the message's 
signature ag [...]
   },
   "apis": {
     "client": { "consumerOnly": false, "producerOnly": true, "description": 
"Sends EDI Messages over HTTP", "methods": { "send": { "description": "Send 
ediMessage to trading partner", "signatures": [ 
"org.apache.hc.core5.http.protocol.HttpCoreContext send(Object ediMessage, 
String requestUri, String subject, String from, String as2From, String as2To, 
org.apache.camel.component.as2.api.AS2MessageStructure as2MessageStructure, 
String ediMessageContentType, String ediMessageCharset, String  [...]
diff --git 
a/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc 
b/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc
index 8b3988308f01..b437954ec5e2 100644
--- a/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc
+++ b/components/camel-as2/camel-as2-component/src/main/docs/as2-component.adoc
@@ -107,3 +107,39 @@ consumer applies its own keys and certificates and its own 
asynchronous MDN sett
 The settings of the shared server connection still come from the first 
endpoint created on the port:
 `sslContextParameters` and `sslContext` (used for the TLS listener and for the 
https delivery of asynchronous MDNs),
 `mdnMessageTemplate`, `server`, `serverFqdn` and `as2Version`. They are per 
port, not per consumer.
+
+=== Sending through an HTTP proxy
+
+When outbound traffic must go through an HTTP forward proxy, set `proxyHost` 
and `proxyPort` on the
+`as2://client/send` endpoint. The client connects to the proxy over plain http:
+
+* without an `sslContext`, the AS2 message is sent to the proxy with an 
absolute request URI;
+* with an `sslContext`, the client asks the proxy to open a `CONNECT` tunnel 
to `targetHostname` and
+`targetPortNumber`, and the TLS handshake and hostname verification are made 
with the target through the tunnel.
+
+If the proxy requires authentication, set `proxyAuthUsername` and 
`proxyAuthPassword`. They are sent to the
+proxy as a preemptive Basic `Proxy-Authorization` header, on the `CONNECT` 
request when tunnelling, and are not
+sent to the AS2 partner. Setting only one of them is a configuration error: 
the endpoint fails to start. If the
+proxy refuses the request, for example with `407 Proxy Authentication 
Required`, the send fails with an exception,
+as the message has not reached the partner. The proxy options apply to the AS2 
client only: asynchronous MDNs are
+delivered directly.
+
+[source,yaml]
+----
+- route:
+    from:
+      uri: direct:sendToPartner
+      steps:
+        - to:
+            uri: as2://client/send
+            parameters:
+              inBody: ediMessage
+              targetHostname: partner.example.com
+              targetPortNumber: 443
+              sslContext: "#partnerSslContext"
+              proxyHost: proxy.internal
+              proxyPort: 3128
+              proxyAuthUsername: "{{proxy.user}}"
+              proxyAuthPassword: "{{proxy.password}}"
+              # ... the AS2 message options
+----
diff --git 
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
 
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
index 3bc6aec16704..fe023dd40c2b 100644
--- 
a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
+++ 
b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Configuration.java
@@ -135,6 +135,14 @@ public class AS2Configuration {
     private String accessToken;
     @UriParam(defaultValue = "false", label = "producer")
     private boolean expectContinue;
+    @UriParam(label = "producer,proxy")
+    private String proxyHost;
+    @UriParam(label = "producer,proxy")
+    private Integer proxyPort;
+    @UriParam(label = "producer,proxy", security = "secret")
+    private String proxyAuthUsername;
+    @UriParam(label = "producer,proxy", security = "secret")
+    private String proxyAuthPassword;
     @UriParam(label = "security", security = "secret")
     private String mdnUserName;
     @UriParam(label = "security", security = "secret")
@@ -655,6 +663,51 @@ public class AS2Configuration {
         this.expectContinue = expectContinue;
     }
 
+    public String getProxyHost() {
+        return proxyHost;
+    }
+
+    /**
+     * The host name of an HTTP proxy the client sends its AS2 messages 
through. The connection to the proxy is plain
+     * http; when an SSL context is configured, the AS2 message goes through a 
CONNECT tunnel to the target.
+     */
+    public void setProxyHost(String proxyHost) {
+        this.proxyHost = proxyHost;
+    }
+
+    public Integer getProxyPort() {
+        return proxyPort;
+    }
+
+    /**
+     * The port number of the HTTP proxy.
+     */
+    public void setProxyPort(Integer proxyPort) {
+        this.proxyPort = proxyPort;
+    }
+
+    public String getProxyAuthUsername() {
+        return proxyAuthUsername;
+    }
+
+    /**
+     * The user name for basic authentication with the HTTP proxy. The 
credentials are sent to the proxy only.
+     */
+    public void setProxyAuthUsername(String proxyAuthUsername) {
+        this.proxyAuthUsername = proxyAuthUsername;
+    }
+
+    public String getProxyAuthPassword() {
+        return proxyAuthPassword;
+    }
+
+    /**
+     * The password for basic authentication with the HTTP proxy.
+     */
+    public void setProxyAuthPassword(String proxyAuthPassword) {
+        this.proxyAuthPassword = proxyAuthPassword;
+    }
+
     public String getMdnUserName() {
         return mdnUserName;
     }
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 ceacff93c682..85b02e78d7a3 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
@@ -58,7 +58,8 @@ public final class AS2ConnectionHelper {
                 configuration.getTargetHostname(), 
configuration.getTargetPortNumber(), configuration.getHttpSocketTimeout(),
                 configuration.getHttpConnectionTimeout(), 
configuration.getHttpConnectionPoolSize(),
                 configuration.getHttpConnectionPoolTtl(), 
configuration.getSslContext(),
-                configuration.getHostnameVerifier(), 
configuration.isExpectContinue());
+                configuration.getHostnameVerifier(), 
configuration.isExpectContinue(), configuration.getProxyHost(),
+                configuration.getProxyPort(), 
configuration.getProxyAuthUsername(), configuration.getProxyAuthPassword());
     }
 
     /**
diff --git 
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientProxyTest.java
 
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientProxyTest.java
new file mode 100644
index 000000000000..4efd60b8c0ad
--- /dev/null
+++ 
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ClientProxyTest.java
@@ -0,0 +1,452 @@
+/*
+ * 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.io.IOException;
+import java.io.InputStream;
+import java.net.ConnectException;
+import java.net.ServerSocket;
+import java.nio.charset.StandardCharsets;
+import java.security.KeyStore;
+import java.time.Duration;
+import java.util.ArrayList;
+import java.util.Base64;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.TrustManagerFactory;
+
+import org.apache.camel.CamelExecutionException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.component.as2.api.AS2ClientConnection;
+import org.apache.camel.component.as2.api.AS2MediaType;
+import org.apache.camel.component.as2.api.AS2MessageStructure;
+import org.apache.camel.component.as2.api.AS2ServerConnection;
+import org.apache.camel.component.as2.api.AS2SignatureAlgorithm;
+import org.apache.camel.component.as2.api.entity.ApplicationEntity;
+import 
org.apache.camel.component.as2.api.entity.DispositionNotificationMultipartReportEntity;
+import org.apache.camel.spi.Registry;
+import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ClassicHttpResponse;
+import org.apache.hc.core5.http.HttpEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.apache.hc.core5.http.HttpRequest;
+import org.apache.hc.core5.http.protocol.HttpContext;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * The AS2 client sends through an HTTP proxy: plain http in absolute form, 
https through a CONNECT tunnel.
+ */
+public class AS2ClientProxyTest extends AbstractAS2ITSupport {
+
+    private static final String PROXY_USER = "proxyUser";
+    private static final String PROXY_PASSWORD = "proxyPassword";
+    private static final String PROXY_AUTHORIZATION = "Basic " + 
Base64.getEncoder()
+            .encodeToString((PROXY_USER + ":" + 
PROXY_PASSWORD).getBytes(StandardCharsets.UTF_8));
+    // a name the client cannot resolve (RFC 2606): only the proxy knows where 
it is
+    private static final String PARTNER_BEHIND_PROXY = "as2-partner.invalid";
+    private static final String EDI_MESSAGE = """
+            UNB+UNOA:1+005435656:1+006415160:1+060515:1434+00000000000778'
+            UNH+00000000000117+INVOIC:D:97B:UN'
+            BGM+380+342459+9'
+            DTM+3:20060515:102'
+            UNT+23+00000000000117'
+            UNZ+1+00000000000778'
+            """;
+
+    private SSLContext sslContext;
+    private TestHttpProxy proxy;
+    private AS2ServerConnection httpServer;
+    private AS2ServerConnection httpsServer;
+    private CountingRequestHandler httpRequestHandler;
+    private CountingRequestHandler httpsRequestHandler;
+
+    @Override
+    public void setupResources() throws Exception {
+        sslContext = createSslContext();
+        proxy = new TestHttpProxy(PROXY_AUTHORIZATION);
+        proxy.addHostAlias(PARTNER_BEHIND_PROXY, "localhost");
+
+        httpServer = new AS2ServerConnection(
+                "1.1", "AS2ClientProxyTest Server", "server.example.com", 0, 
AS2SignatureAlgorithm.SHA256WITHRSA,
+                null, null, null, "TBD", null, null, null, null, null);
+        httpRequestHandler = new CountingRequestHandler();
+        httpServer.listen("/", httpRequestHandler);
+
+        httpsServer = new AS2ServerConnection(
+                "1.1", "AS2ClientProxyTest Server", "server.example.com", 0, 
AS2SignatureAlgorithm.SHA256WITHRSA,
+                null, null, null, "TBD", null, sslContext, null, null, null);
+        httpsRequestHandler = new CountingRequestHandler();
+        httpsServer.listen("/", httpsRequestHandler);
+    }
+
+    @Override
+    public void cleanupResources() {
+        if (httpServer != null) {
+            httpServer.close();
+        }
+        if (httpsServer != null) {
+            httpsServer.close();
+        }
+        if (proxy != null) {
+            proxy.close();
+        }
+    }
+
+    @Override
+    protected void bindToRegistry(Registry registry) {
+        registry.bind("tlsContext", sslContext);
+    }
+
+    @Test
+    public void sendsHttpThroughProxy() {
+        HttpEntity mdn = requestBodyAndHeaders("direct://HTTP", EDI_MESSAGE, 
as2Headers());
+
+        assertInstanceOf(DispositionNotificationMultipartReportEntity.class, 
mdn);
+        assertEquals(List.of("POST http://localhost:"; + 
httpServer.getLocalPort() + "/ HTTP/1.1"),
+                proxy.getRequestLines());
+        assertEquals(List.of(PROXY_AUTHORIZATION), 
proxy.getProxyAuthorizations());
+        assertReceived(httpRequestHandler);
+    }
+
+    @Test
+    public void reachesPartnerOnlyResolvableByTheProxy() {
+        HttpEntity mdn = 
requestBodyAndHeaders("direct://HTTP_PARTNER_BEHIND_PROXY", EDI_MESSAGE, 
as2Headers());
+
+        assertInstanceOf(DispositionNotificationMultipartReportEntity.class, 
mdn);
+        assertEquals(List.of("POST http://"; + PARTNER_BEHIND_PROXY + ":" + 
httpServer.getLocalPort() + "/ HTTP/1.1"),
+                proxy.getRequestLines());
+        assertReceived(httpRequestHandler);
+    }
+
+    @Test
+    public void refusedHttpRequestFailsTheSend() {
+        CamelExecutionException e = assertThrows(CamelExecutionException.class,
+                () -> requestBodyAndHeaders("direct://HTTP_WRONG_PASSWORD", 
EDI_MESSAGE, as2Headers()));
+
+        assertTrue(rootMessage(e).contains("407"), rootMessage(e));
+        assertEquals(0, httpRequestHandler.getCount());
+    }
+
+    @Test
+    public void refusedHttpConnectionIsNotReused() {
+        assertEachSendIsRefused("direct://HTTP_WRONG_PASSWORD", 
httpRequestLine());
+        assertEquals(0, httpRequestHandler.getCount());
+    }
+
+    @Test
+    public void refusedHttpConnectionIsNotReusedWhenTheProxyKeepsItOpen() {
+        // the 407 then has a body and the proxy waits for a retry on the same 
connection: it must not be reused
+        proxy.setKeepAliveAfterRefusal(true);
+
+        assertEachSendIsRefused("direct://HTTP_WRONG_PASSWORD", 
httpRequestLine());
+        assertEquals(0, httpRequestHandler.getCount());
+    }
+
+    @Test
+    public void sendsHttpsThroughProxyTunnel() {
+        HttpEntity mdn = requestBodyAndHeaders("direct://HTTPS", EDI_MESSAGE, 
as2Headers());
+
+        assertInstanceOf(DispositionNotificationMultipartReportEntity.class, 
mdn);
+        assertEquals(List.of(httpsConnectLine()), proxy.getRequestLines());
+        assertEquals(List.of(PROXY_AUTHORIZATION), 
proxy.getProxyAuthorizations());
+        HttpRequest request = assertReceived(httpsRequestHandler);
+        // the proxy credentials are for the proxy only: they must not travel 
through the tunnel to the partner
+        assertNull(request.getFirstHeader("Proxy-Authorization"));
+    }
+
+    @Test
+    public void sendsSeveralMessagesThroughTheTunnel() {
+        for (int i = 0; i < 3; i++) {
+            HttpEntity mdn = requestBodyAndHeaders("direct://HTTPS", 
EDI_MESSAGE, as2Headers());
+            
assertInstanceOf(DispositionNotificationMultipartReportEntity.class, mdn);
+        }
+
+        assertEquals(3, httpsRequestHandler.getCount());
+        // a connection kept alive by the pool is already tunnelled: it must 
not be asked to CONNECT again
+        
assertTrue(proxy.getRequestLines().stream().allMatch(httpsConnectLine()::equals),
 proxy.getRequestLines()::toString);
+        assertTrue(proxy.getRequestLines().size() <= 3, 
proxy.getRequestLines()::toString);
+    }
+
+    @Test
+    public void sendsConcurrentlyThroughTheTunnel() throws Exception {
+        int messages = 16;
+        ExecutorService executor = Executors.newFixedThreadPool(8);
+        try {
+            List<Future<Object>> mdns = new ArrayList<>();
+            for (int i = 0; i < messages; i++) {
+                mdns.add(executor.submit(() -> 
requestBodyAndHeaders("direct://HTTPS", EDI_MESSAGE, as2Headers())));
+            }
+            for (Future<Object> mdn : mdns) {
+                
assertInstanceOf(DispositionNotificationMultipartReportEntity.class, 
mdn.get(30, TimeUnit.SECONDS));
+            }
+        } finally {
+            executor.shutdownNow();
+        }
+
+        assertEquals(messages, httpsRequestHandler.getCount());
+        
assertTrue(proxy.getRequestLines().stream().allMatch(httpsConnectLine()::equals),
 proxy.getRequestLines()::toString);
+    }
+
+    @Test
+    public void refusedTunnelFailsTheSend() {
+        CamelExecutionException e = assertThrows(CamelExecutionException.class,
+                () -> requestBodyAndHeaders("direct://HTTPS_WRONG_PASSWORD", 
EDI_MESSAGE, as2Headers()));
+
+        assertTrue(rootMessage(e).contains("407"), rootMessage(e));
+        assertEquals(0, httpsRequestHandler.getCount());
+    }
+
+    @Test
+    public void refusedTunnelIsNotReused() {
+        // a connection without its tunnel must not go back to the pool, or 
the next send skips CONNECT and TLS
+        assertEachSendIsRefused("direct://HTTPS_WRONG_PASSWORD", 
httpsConnectLine());
+        assertEquals(0, httpsRequestHandler.getCount());
+    }
+
+    @Test
+    public void refusedTunnelIsNotReusedWhenTheProxyKeepsItOpen() {
+        proxy.setKeepAliveAfterRefusal(true);
+
+        assertEachSendIsRefused("direct://HTTPS_WRONG_PASSWORD", 
httpsConnectLine());
+        assertEquals(0, httpsRequestHandler.getCount());
+    }
+
+    @Test
+    public void verifiesTheTargetHostNameThroughTheTunnel() {
+        // the certificate names localhost, which is also the proxy's name: 
the handshake must check the target's
+        for (int i = 0; i < 2; i++) {
+            CamelExecutionException e = 
assertThrows(CamelExecutionException.class,
+                    () -> requestBodyAndHeaders("direct://HTTPS_IP_TARGET", 
EDI_MESSAGE, as2Headers()));
+            assertTrue(hasCause(e, SSLException.class), rootMessage(e));
+        }
+
+        // a failed handshake leaves no connection in the pool: the second 
send opens a new tunnel
+        String connectLine = "CONNECT 127.0.0.1:" + httpsServer.getLocalPort() 
+ " HTTP/1.1";
+        assertEquals(List.of(connectLine, connectLine), 
proxy.getRequestLines());
+        assertEquals(0, httpsRequestHandler.getCount());
+    }
+
+    @Test
+    public void unreachableProxyFailsAtStartup() throws Exception {
+        int closedPort;
+        try (ServerSocket socket = new ServerSocket(0)) {
+            closedPort = socket.getLocalPort();
+        }
+
+        assertThrows(ConnectException.class, () -> 
createClientConnection("localhost", closedPort));
+    }
+
+    @Test
+    public void proxyPortIsRequired() {
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class,
+                () -> createClientConnection("localhost", null));
+
+        assertTrue(e.getMessage().contains("proxyPort"), e.getMessage());
+    }
+
+    @Test
+    public void proxyAuthUsernameAloneIsRefused() {
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class,
+                () -> createClientConnection("localhost", proxy.getPort(), 
PROXY_USER, null));
+
+        assertEquals("proxyAuthUsername and proxyAuthPassword must be set 
together", e.getMessage());
+    }
+
+    @Test
+    public void proxyAuthPasswordAloneIsRefused() {
+        IllegalArgumentException e = 
assertThrows(IllegalArgumentException.class,
+                () -> createClientConnection("localhost", proxy.getPort(), 
null, PROXY_PASSWORD));
+
+        assertEquals("proxyAuthUsername and proxyAuthPassword must be set 
together", e.getMessage());
+    }
+
+    @Test
+    public void proxyAuthUsernameAloneFailsTheRouteStartup() {
+        // the endpoint creates its client connection when it is configured, 
so the mistake is reported at startup
+        Exception e = assertThrows(Exception.class,
+                () -> 
context.getEndpoint("as2://client/send?inBody=ediMessage&targetPortNumber=" + 
httpServer.getLocalPort()
+                                          + "&proxyHost=localhost&proxyPort=" 
+ proxy.getPort() + "&proxyAuthUsername="
+                                          + PROXY_USER));
+
+        assertTrue(hasCause(e, IllegalArgumentException.class), 
rootMessage(e));
+        assertEquals("proxyAuthUsername and proxyAuthPassword must be set 
together", rootMessage(e));
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() {
+        return new RouteBuilder() {
+            public void configure() {
+                from("direct://HTTP")
+                        
.toF("as2://client/send?inBody=ediMessage&targetPortNumber=%d&proxyHost=localhost&proxyPort=%d"
+                             + "&proxyAuthUsername=%s&proxyAuthPassword=%s",
+                                httpServer.getLocalPort(), proxy.getPort(), 
PROXY_USER, PROXY_PASSWORD);
+
+                from("direct://HTTP_PARTNER_BEHIND_PROXY")
+                        
.toF("as2://client/send?inBody=ediMessage&targetHostname=%s&targetPortNumber=%d"
+                             + 
"&proxyHost=localhost&proxyPort=%d&proxyAuthUsername=%s&proxyAuthPassword=%s",
+                                PARTNER_BEHIND_PROXY, 
httpServer.getLocalPort(), proxy.getPort(), PROXY_USER,
+                                PROXY_PASSWORD);
+
+                from("direct://HTTP_WRONG_PASSWORD")
+                        
.toF("as2://client/send?inBody=ediMessage&targetPortNumber=%d&proxyHost=localhost&proxyPort=%d"
+                             + "&proxyAuthUsername=%s&proxyAuthPassword=wrong",
+                                httpServer.getLocalPort(), proxy.getPort(), 
PROXY_USER);
+
+                from("direct://HTTPS")
+                        
.toF("as2://client/send?inBody=ediMessage&targetPortNumber=%d&sslContext=#tlsContext"
+                             + 
"&proxyHost=localhost&proxyPort=%d&proxyAuthUsername=%s&proxyAuthPassword=%s",
+                                httpsServer.getLocalPort(), proxy.getPort(), 
PROXY_USER, PROXY_PASSWORD);
+
+                from("direct://HTTPS_WRONG_PASSWORD")
+                        
.toF("as2://client/send?inBody=ediMessage&targetPortNumber=%d&sslContext=#tlsContext"
+                             + 
"&proxyHost=localhost&proxyPort=%d&proxyAuthUsername=%s&proxyAuthPassword=wrong",
+                                httpsServer.getLocalPort(), proxy.getPort(), 
PROXY_USER);
+
+                from("direct://HTTPS_IP_TARGET")
+                        
.toF("as2://client/send?inBody=ediMessage&targetHostname=127.0.0.1&targetPortNumber=%d"
+                             + 
"&sslContext=#tlsContext&proxyHost=localhost&proxyPort=%d"
+                             + "&proxyAuthUsername=%s&proxyAuthPassword=%s",
+                                httpsServer.getLocalPort(), proxy.getPort(), 
PROXY_USER, PROXY_PASSWORD);
+            }
+        };
+    }
+
+    /**
+     * Sends twice through a route the proxy refuses: each send must get the 
407 on a new connection to the proxy.
+     */
+    private void assertEachSendIsRefused(String route, String requestLine) {
+        for (int i = 0; i < 2; i++) {
+            CamelExecutionException e = 
assertThrows(CamelExecutionException.class,
+                    () -> requestBodyAndHeaders(route, EDI_MESSAGE, 
as2Headers()));
+            assertTrue(rootMessage(e).contains("407"), rootMessage(e));
+        }
+
+        assertEquals(List.of(requestLine, requestLine), 
proxy.getRequestLines());
+        assertEquals(2, proxy.getConnectionCount());
+    }
+
+    private String httpRequestLine() {
+        return "POST http://localhost:"; + httpServer.getLocalPort() + "/ 
HTTP/1.1";
+    }
+
+    private String httpsConnectLine() {
+        return "CONNECT localhost:" + httpsServer.getLocalPort() + " HTTP/1.1";
+    }
+
+    private AS2ClientConnection createClientConnection(String proxyHost, 
Integer proxyPort) throws IOException {
+        return createClientConnection(proxyHost, proxyPort, null, null);
+    }
+
+    private AS2ClientConnection createClientConnection(
+            String proxyHost, Integer proxyPort, String proxyAuthUsername, 
String proxyAuthPassword)
+            throws IOException {
+        return new AS2ClientConnection(
+                "1.1", "Camel AS2 Endpoint", "example.org", "localhost", 
httpServer.getLocalPort(), Duration.ofSeconds(5),
+                Duration.ofSeconds(5), 5, Duration.ofMinutes(15), null, null, 
false, proxyHost, proxyPort,
+                proxyAuthUsername, proxyAuthPassword);
+    }
+
+    private static HttpRequest assertReceived(CountingRequestHandler 
requestHandler) {
+        assertEquals(1, requestHandler.getCount());
+        HttpRequest request = requestHandler.getRequest();
+        assertNotNull(request);
+        HttpEntity entity = ((ClassicHttpRequest) request).getEntity();
+        assertInstanceOf(ApplicationEntity.class, entity);
+        assertEquals(EDI_MESSAGE, ((String) ((ApplicationEntity) 
entity).getEdiMessage()).replaceAll("\r", ""));
+        return request;
+    }
+
+    private static boolean hasCause(Throwable e, Class<? extends Throwable> 
type) {
+        for (Throwable cause = e; cause != null; cause = cause.getCause()) {
+            if (type.isInstance(cause)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    private static String rootMessage(Throwable e) {
+        Throwable cause = e;
+        while (cause.getCause() != null) {
+            cause = cause.getCause();
+        }
+        return String.valueOf(cause.getMessage());
+    }
+
+    private static Map<String, Object> as2Headers() {
+        Map<String, Object> headers = new HashMap<>();
+        headers.put("CamelAs2.requestUri", "/");
+        headers.put("CamelAs2.subject", "Test Case");
+        headers.put("CamelAs2.from", "[email protected]");
+        headers.put("CamelAs2.as2From", "878051556");
+        headers.put("CamelAs2.as2To", "878051556");
+        headers.put("CamelAs2.as2MessageStructure", AS2MessageStructure.PLAIN);
+        headers.put("CamelAs2.ediMessageContentType", 
AS2MediaType.APPLICATION_EDIFACT);
+        headers.put("CamelAs2.ediMessageTransferEncoding", "7bit");
+        headers.put("CamelAs2.dispositionNotificationTo", "[email protected]");
+        headers.put("CamelAs2.attachedFileName", "");
+        return headers;
+    }
+
+    private static SSLContext createSslContext() throws Exception {
+        char[] password = "changeit".toCharArray();
+        KeyStore keyStore = KeyStore.getInstance("PKCS12");
+        try (InputStream is = 
AS2ClientProxyTest.class.getResourceAsStream("/jsse/localhost.p12")) {
+            keyStore.load(is, password);
+        }
+        KeyManagerFactory kmf = 
KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+        kmf.init(keyStore, password);
+        TrustManagerFactory tmf = 
TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+        tmf.init(keyStore);
+        SSLContext context = SSLContext.getInstance("TLS");
+        context.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
+        return context;
+    }
+
+    private static final class CountingRequestHandler extends 
AS2ClientManagerIT.RequestHandler {
+
+        private final AtomicInteger count = new AtomicInteger();
+
+        @Override
+        public void handle(ClassicHttpRequest request, ClassicHttpResponse 
response, HttpContext context)
+                throws HttpException, IOException {
+            count.incrementAndGet();
+            super.handle(request, response, context);
+        }
+
+        int getCount() {
+            return count.get();
+        }
+    }
+}
diff --git 
a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/TestHttpProxy.java
 
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/TestHttpProxy.java
new file mode 100644
index 000000000000..f93f79ad68b7
--- /dev/null
+++ 
b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/TestHttpProxy.java
@@ -0,0 +1,238 @@
+/*
+ * 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.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.Closeable;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.URI;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import org.apache.camel.util.IOHelper;
+
+/**
+ * A minimal HTTP forward proxy for tests. It forwards plain http requests 
sent in absolute form and opens CONNECT
+ * tunnels, and records the request line and the Proxy-Authorization header of 
every request it receives.
+ * <p>
+ * It refuses a request without the required credentials with a 407. By 
default it then closes the connection; it can
+ * also keep the connection open with a body in the 407, like a proxy waiting 
for the client to retry with credentials.
+ */
+final class TestHttpProxy implements Closeable {
+
+    private final ServerSocket serverSocket;
+    private final String requiredAuthorization;
+    private final ExecutorService executor = Executors.newCachedThreadPool();
+    private final List<Socket> sockets = new CopyOnWriteArrayList<>();
+    private final List<String> requestLines = new CopyOnWriteArrayList<>();
+    private final List<String> proxyAuthorizations = new 
CopyOnWriteArrayList<>();
+    private final Map<String, String> hostAliases = new ConcurrentHashMap<>();
+    private final AtomicInteger connectionCount = new AtomicInteger();
+    private volatile boolean keepAliveAfterRefusal;
+
+    /**
+     * @param requiredAuthorization the Proxy-Authorization value the proxy 
requires, or null to require none
+     */
+    TestHttpProxy(String requiredAuthorization) throws IOException {
+        this.requiredAuthorization = requiredAuthorization;
+        this.serverSocket = new ServerSocket(0, 50, 
InetAddress.getLoopbackAddress());
+        executor.execute(this::acceptConnections);
+    }
+
+    int getPort() {
+        return serverSocket.getLocalPort();
+    }
+
+    List<String> getRequestLines() {
+        return requestLines;
+    }
+
+    List<String> getProxyAuthorizations() {
+        return proxyAuthorizations;
+    }
+
+    /**
+     * The number of connections on which the clients sent a request to the 
proxy. A client's startup check opens a
+     * connection without sending anything, so it is not counted.
+     */
+    int getConnectionCount() {
+        return connectionCount.get();
+    }
+
+    /**
+     * Keeps the connection open after a 407, which carries a body, instead of 
closing it.
+     */
+    void setKeepAliveAfterRefusal(boolean keepAliveAfterRefusal) {
+        this.keepAliveAfterRefusal = keepAliveAfterRefusal;
+    }
+
+    /**
+     * Makes the proxy resolve a host name the client itself cannot resolve, 
like a partner only reachable through the
+     * proxy.
+     */
+    void addHostAlias(String host, String actualHost) {
+        hostAliases.put(host, actualHost);
+    }
+
+    @Override
+    public void close() {
+        IOHelper.close(serverSocket);
+        sockets.forEach(IOHelper::close);
+        executor.shutdownNow();
+    }
+
+    private void acceptConnections() {
+        while (!serverSocket.isClosed()) {
+            try {
+                Socket client = serverSocket.accept();
+                sockets.add(client);
+                executor.execute(() -> handle(client));
+            } catch (IOException e) {
+                // closed
+            }
+        }
+    }
+
+    private void handle(Socket client) {
+        try {
+            InputStream in = new BufferedInputStream(client.getInputStream());
+            OutputStream out = client.getOutputStream();
+            // a refused request is followed by the next one on the same 
connection when it is kept alive
+            boolean firstRequest = true;
+            while (handleRequest(in, out, firstRequest)) {
+                firstRequest = false;
+            }
+        } catch (IOException e) {
+            // connection closed
+        } finally {
+            IOHelper.close(client);
+        }
+    }
+
+    /**
+     * @return true when the connection stays open for another request
+     */
+    private boolean handleRequest(InputStream in, OutputStream out, boolean 
firstRequest) throws IOException {
+        String requestLine = readLine(in);
+        if (requestLine == null) {
+            return false;
+        }
+        if (firstRequest) {
+            connectionCount.incrementAndGet();
+        }
+        List<String> headers = new ArrayList<>();
+        String proxyAuthorization = null;
+        long contentLength = 0;
+        for (String header = readLine(in); header != null && 
!header.isEmpty(); header = readLine(in)) {
+            String name = header.substring(0, Math.max(header.indexOf(':'), 
0)).trim().toLowerCase(Locale.ROOT);
+            String value = header.substring(header.indexOf(':') + 1).trim();
+            if ("proxy-authorization".equals(name)) {
+                proxyAuthorization = value;
+            } else {
+                if ("content-length".equals(name)) {
+                    contentLength = Long.parseLong(value);
+                }
+                headers.add(header);
+            }
+        }
+        requestLines.add(requestLine);
+        proxyAuthorizations.add(String.valueOf(proxyAuthorization));
+
+        if (requiredAuthorization != null && 
!requiredAuthorization.equals(proxyAuthorization)) {
+            if (keepAliveAfterRefusal) {
+                // read the refused request's body, so the next request on 
this connection can be read
+                in.skipNBytes(contentLength);
+                String body = "<html><body>Proxy authentication 
required</body></html>";
+                write(out, "HTTP/1.1 407 Proxy Authentication 
Required\r\nProxy-Authenticate: Basic realm=\"test\"\r\n"
+                           + "Content-Type: text/html\r\nContent-Length: " + 
body.length() + "\r\n\r\n" + body);
+                return true;
+            }
+            write(out, "HTTP/1.1 407 Proxy Authentication 
Required\r\nProxy-Authenticate: Basic realm=\"test\"\r\n"
+                       + "Content-Length: 0\r\nConnection: close\r\n\r\n");
+            return false;
+        }
+
+        String[] parts = requestLine.split(" ");
+        if ("CONNECT".equals(parts[0])) {
+            String[] hostPort = parts[1].split(":");
+            Socket target = connect(hostPort[0], 
Integer.parseInt(hostPort[1]));
+            write(out, "HTTP/1.1 200 Connection established\r\n\r\n");
+            relay(in, out, target);
+        } else {
+            // forward in origin form, without the proxy's own credentials
+            URI uri = URI.create(parts[1]);
+            Socket target = connect(uri.getHost(), uri.getPort());
+            StringBuilder head = new StringBuilder(parts[0] + " " + 
uri.getRawPath() + " " + parts[2] + "\r\n");
+            headers.forEach(h -> head.append(h).append("\r\n"));
+            write(target.getOutputStream(), head.append("\r\n").toString());
+            relay(in, out, target);
+        }
+        return false;
+    }
+
+    private Socket connect(String host, int port) throws IOException {
+        Socket target = new Socket(hostAliases.getOrDefault(host, host), port);
+        sockets.add(target);
+        return target;
+    }
+
+    private void relay(InputStream clientIn, OutputStream clientOut, Socket 
target) throws IOException {
+        InputStream targetIn = target.getInputStream();
+        executor.execute(() -> {
+            try {
+                targetIn.transferTo(clientOut);
+            } catch (IOException e) {
+                // connection closed
+            }
+        });
+        try {
+            clientIn.transferTo(target.getOutputStream());
+        } finally {
+            IOHelper.close(target);
+        }
+    }
+
+    private static void write(OutputStream out, String s) throws IOException {
+        out.write(s.getBytes(StandardCharsets.ISO_8859_1));
+        out.flush();
+    }
+
+    private static String readLine(InputStream in) throws IOException {
+        ByteArrayOutputStream line = new ByteArrayOutputStream();
+        int b;
+        while ((b = in.read()) != -1 && b != '\n') {
+            if (b != '\r') {
+                line.write(b);
+            }
+        }
+        return b == -1 && line.size() == 0 ? null : 
line.toString(StandardCharsets.ISO_8859_1);
+    }
+}
diff --git 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java
 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java
index 97e5a5dc260e..f5e5086106c8 100644
--- 
a/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java
+++ 
b/dsl/camel-endpointdsl/src/generated/java/org/apache/camel/builder/endpoint/dsl/AS2EndpointBuilderFactory.java
@@ -1749,6 +1749,81 @@ public interface AS2EndpointBuilderFactory {
             doSetProperty("expectContinue", expectContinue);
             return this;
         }
+        /**
+         * The password for basic authentication with the HTTP proxy.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Group: proxy
+         * 
+         * @param proxyAuthPassword the value to set
+         * @return the dsl builder
+         */
+        default AS2EndpointProducerBuilder proxyAuthPassword(String 
proxyAuthPassword) {
+            doSetProperty("proxyAuthPassword", proxyAuthPassword);
+            return this;
+        }
+        /**
+         * The user name for basic authentication with the HTTP proxy. The
+         * credentials are sent to the proxy only.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Group: proxy
+         * 
+         * @param proxyAuthUsername the value to set
+         * @return the dsl builder
+         */
+        default AS2EndpointProducerBuilder proxyAuthUsername(String 
proxyAuthUsername) {
+            doSetProperty("proxyAuthUsername", proxyAuthUsername);
+            return this;
+        }
+        /**
+         * The host name of an HTTP proxy the client sends its AS2 messages
+         * through. The connection to the proxy is plain http; when an SSL
+         * context is configured, the AS2 message goes through a CONNECT tunnel
+         * to the target.
+         * 
+         * The option is a: <code>java.lang.String</code> type.
+         * 
+         * Group: proxy
+         * 
+         * @param proxyHost the value to set
+         * @return the dsl builder
+         */
+        default AS2EndpointProducerBuilder proxyHost(String proxyHost) {
+            doSetProperty("proxyHost", proxyHost);
+            return this;
+        }
+        /**
+         * The port number of the HTTP proxy.
+         * 
+         * The option is a: <code>java.lang.Integer</code> type.
+         * 
+         * Group: proxy
+         * 
+         * @param proxyPort the value to set
+         * @return the dsl builder
+         */
+        default AS2EndpointProducerBuilder proxyPort(Integer proxyPort) {
+            doSetProperty("proxyPort", proxyPort);
+            return this;
+        }
+        /**
+         * The port number of the HTTP proxy.
+         * 
+         * The option will be converted to a <code>java.lang.Integer</code>
+         * type.
+         * 
+         * Group: proxy
+         * 
+         * @param proxyPort the value to set
+         * @return the dsl builder
+         */
+        default AS2EndpointProducerBuilder proxyPort(String proxyPort) {
+            doSetProperty("proxyPort", proxyPort);
+            return this;
+        }
         /**
          * The access token that is used by the client for bearer
          * authentication.

Reply via email to