This is an automated email from the ASF dual-hosted git repository. gnodet pushed a commit to branch backport/28527-to-camel-4.18.x in repository https://gitbox.apache.org/repos/asf/camel.git
commit 9774433013ec228645ebb502aa54eb4e12f1cdaa Author: Guillaume Nodet <[email protected]> AuthorDate: Tue Nov 3 08:51:31 2026 +0000 [backport camel-4.18.x] CAMEL-26322: camel-as2 - scope the async MDN allow-list to host and port, and add an option to require https --- .../org/apache/camel/catalog/components/as2.json | 33 ++-- .../as2/api/AS2AsynchronousMDNManager.java | 105 +++++++++-- .../component/as2/api/AS2ServerConnection.java | 56 +++++- ...2AsynchronousMDNManagerDeliveryAddressTest.java | 111 +++++++++++- .../AS2ConsumerConfigurationMdnSettingsTest.java | 19 ++ ...rverManagerEndpointConfigurationConfigurer.java | 7 + ...ientManagerEndpointConfigurationConfigurer.java | 7 + .../component/as2/AS2ConfigurationConfigurer.java | 7 + .../camel/component/as2/AS2EndpointConfigurer.java | 7 + .../camel/component/as2/AS2EndpointUriFactory.java | 3 +- ...rverManagerEndpointConfigurationConfigurer.java | 7 + .../org/apache/camel/component/as2/as2.json | 33 ++-- .../src/main/docs/as2-component.adoc | 13 ++ .../camel/component/as2/AS2Configuration.java | 33 +++- .../apache/camel/component/as2/AS2Consumer.java | 3 +- .../as2/internal/AS2ConnectionHelper.java | 3 +- .../as2/AS2AsyncMdnBasicAuthHeaderTest.java | 6 +- .../as2/AS2AsyncMdnTokenAuthHeaderTest.java | 6 +- .../as2/AS2ServerBasicAuthHeaderTest.java | 7 +- .../as2/AS2ServerBearerAuthHeaderTest.java | 7 +- .../AS2ServerTwoConsumerAsyncMdnSettingsTest.java | 21 ++- .../endpoint/dsl/AS2EndpointBuilderFactory.java | 195 ++++++++++++++++++--- 22 files changed, 593 insertions(+), 96 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 92f53d024ce0..b9b8a070e796 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 @@ -67,22 +67,23 @@ "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." }, "lazyStartProducer": { "index": 32, "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": 33, "kind": "parameter", "displayName": "Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The access token that is used by the client for bearer authentication." }, - "asyncMdnAllowedHosts": { "index": 34, "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": 35, "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": 36, "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": 37, "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": 38, "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": 39, "kind": "parameter", "displayName": "Mdn Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "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 option applies to the consu [...] - "mdnPassword": { "index": 40, "kind": "parameter", "displayName": "Mdn Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "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 MDN. The option applie [...] - "mdnUserName": { "index": 41, "kind": "parameter", "displayName": "Mdn User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "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 async MDN. If options for [...] - "password": { "index": 42, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the client for basic authentication." }, - "signedReceiptMicAlgorithms": { "index": 43, "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": 44, "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": 45, "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": 46, "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": 47, "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": 48, "kind": "parameter", "displayName": "User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "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 authentication and bearer au [...] - "validateSigningCertificateChain": { "index": 49, "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 [...] + "asyncMdnAllowedHosts": { "index": 34, "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 hosts an asynchronous MDN may be delivered to. The delivery addre [...] + "asyncMdnRequireHttps": { "index": 35, "kind": "parameter", "displayName": "Async Mdn Require Https", "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 an asynchronous MDN may only be delivered over https. 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, "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 option applies to the consu [...] + "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, "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 MDN. The option applie [...] + "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, "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 async MDN. If options for [...] + "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, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the client for basic authentication." }, + "signedReceiptMicAlgorithms": { "index": 44, "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": 45, "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": 46, "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": 47, "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": 48, "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": 49, "kind": "parameter", "displayName": "User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "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 authentication and bearer au [...] + "validateSigningCertificateChain": { "index": 50, "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-api/src/main/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManager.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManager.java index ed8b3b2e709f..97da3d39fe69 100644 --- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManager.java +++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManager.java @@ -98,6 +98,7 @@ public class AS2AsynchronousMDNManager { private String password; private String accessToken; private String allowedHosts; + private boolean requireHttps; /** * @deprecated use @@ -127,7 +128,22 @@ public class AS2AsynchronousMDNManager { String password, String accessToken, String allowedHosts) { + this(as2Version, userAgent, senderFQDN, signingCertificateChain, signingPrivateKey, userName, password, + accessToken, allowedHosts, false); + } + + public AS2AsynchronousMDNManager(String as2Version, + String userAgent, + String senderFQDN, + Certificate[] signingCertificateChain, + PrivateKey signingPrivateKey, + String userName, + String password, + String accessToken, + String allowedHosts, + boolean requireHttps) { this.allowedHosts = allowedHosts; + this.requireHttps = requireHttps; this.signingCertificateChain = signingCertificateChain; this.signingPrivateKey = signingPrivateKey; this.userName = userName; @@ -167,12 +183,15 @@ public class AS2AsynchronousMDNManager { if (host == null) { throw new HttpException("Refusing to deliver the asynchronous MDN: the delivery address has no host"); } - int port = uri.getPort() != -1 ? uri.getPort() : 80; + int defaultPort = 80; + int port = uri.getPort() != -1 ? uri.getPort() : defaultPort; - boolean hostIsAllowed = isAllowedHost(host); - if (allowedHosts != null && !allowedHosts.isBlank() && !hostIsAllowed) { + // Match the full authority - host and port - of the delivery address against the allow-list, not the host + // alone, so a listed host does not authorise every port on that host. + boolean authorityIsAllowed = isAllowedAuthority(host, port, defaultPort); + if (allowedHosts != null && !allowedHosts.isBlank() && !authorityIsAllowed) { throw new HttpException( - "Refusing to deliver the asynchronous MDN: the delivery address host is not in asyncMdnAllowedHosts"); + "Refusing to deliver the asynchronous MDN: the delivery address is not in asyncMdnAllowedHosts"); } int buffSize = 8 * 1024; @@ -189,14 +208,14 @@ public class AS2AsynchronousMDNManager { ClassicHttpRequest request = new BasicClassicHttpRequest("POST", uri); request.setHeader(AS2Header.CONTENT_TYPE, contentType); - // Credentials are only attached to a host the operator has vouched for. Without an allow-list the - // destination is entirely sender-chosen, so the MDN is still delivered but without them. - if (hostIsAllowed) { + // Credentials are only attached to an authority - host and port - the operator has vouched for. Without an + // allow-list the destination is entirely sender-chosen, so the MDN is still delivered but without them. + if (authorityIsAllowed) { AS2HeaderUtils.addAuthorizationHeader(request, userName, password, accessToken); } else if (userName != null || accessToken != null) { - LOG.warn("Asynchronous MDN credentials not sent to sender-supplied host {}:" + LOG.warn("Asynchronous MDN credentials not sent to sender-supplied authority {}:" + " set asyncMdnAllowedHosts to authorise it", - host); + formatAuthority(host, port)); } httpContext.setAttribute(HttpCoreContext.HTTP_REQUEST, request); multipartMimeEntity.setMainBody(true); @@ -228,18 +247,82 @@ public class AS2AsynchronousMDNManager { return host; } - private boolean isAllowedHost(String host) { + /** + * Writes a delivery authority the way an {@code asyncMdnAllowedHosts} entry is written, so a log message names an + * entry the operator can copy. The host has been through normalizeHost, so an IPv6 literal (the only host that + * contains a colon) is put back in square brackets ({@code [::1]:8443}): unbracketed, {@code ::1:8443} would be + * read as another IPv6 address on the default port. Any other host is written as {@code host:port}. + */ + static String formatAuthority(String host, int port) { + return host.indexOf(':') >= 0 ? "[" + host + "]:" + port : host + ":" + port; + } + + /** + * Matches the resolved delivery authority - host and port - against {@code asyncMdnAllowedHosts}. Each entry is a + * host with an optional port ({@code host} or {@code host:port}); a bare-host entry matches only the default port + * for the scheme ({@code defaultPort}), so a listed host does not authorise every port on that host. + */ + boolean isAllowedAuthority(String host, int port, int defaultPort) { if (allowedHosts == null || allowedHosts.isBlank()) { return false; } for (String allowed : allowedHosts.split(",")) { - if (allowed.trim().equalsIgnoreCase(host)) { + String entry = allowed.trim(); + if (!entry.isEmpty() && authorityMatches(entry, host, port, defaultPort)) { return true; } } return false; } + private static boolean authorityMatches(String entry, String host, int port, int defaultPort) { + String entryHost; + int entryPort; + if (entry.charAt(0) == '[') { + // bracketed IPv6 literal, optionally followed by :port + int close = entry.indexOf(']'); + if (close < 0) { + return false; + } + entryHost = entry.substring(1, close); + entryPort = defaultPort; + if (close + 1 < entry.length()) { + if (entry.charAt(close + 1) != ':') { + return false; + } + Integer parsed = parsePort(entry.substring(close + 2)); + if (parsed == null) { + return false; + } + entryPort = parsed; + } + } else { + int colon = entry.indexOf(':'); + if (colon >= 0 && entry.indexOf(':', colon + 1) < 0) { + // a single colon separates host and port; an unbracketed IPv6 literal has several and keeps the default + entryHost = entry.substring(0, colon); + Integer parsed = parsePort(entry.substring(colon + 1)); + if (parsed == null) { + return false; + } + entryPort = parsed; + } else { + entryHost = entry; + entryPort = defaultPort; + } + } + return entryPort == port && entryHost.equalsIgnoreCase(host); + } + + private static Integer parsePort(String value) { + try { + int parsed = Integer.parseInt(value.trim()); + return parsed >= 0 && parsed <= 65535 ? parsed : null; + } catch (NumberFormatException e) { + return null; + } + } + private HttpResponse send(HttpClientConnection httpConnection, ClassicHttpRequest request, HttpCoreContext httpContext) throws HttpException, IOException { diff --git a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java index 05f47c4a49d7..f11c00147878 100644 --- a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java +++ b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/AS2ServerConnection.java @@ -138,6 +138,7 @@ public class AS2ServerConnection { private final String mdnPassword; private final String mdnAccessToken; private final String asyncMdnAllowedHosts; + private final boolean asyncMdnRequireHttps; /** * Creates a configuration without asynchronous MDN settings: an asynchronous MDN for a request matched by it is @@ -168,6 +169,26 @@ public class AS2ServerConnection { String mdnPassword, String mdnAccessToken, String asyncMdnAllowedHosts) { + this(signingAlgorithm, signingCertificateChain, signingPrivateKey, decryptingPrivateKey, + validateSigningCertificateChain, mdnUserName, mdnPassword, mdnAccessToken, asyncMdnAllowedHosts, false); + } + + /** + * Creates a configuration that additionally requires asynchronous MDN delivery to use https + * ({@code asyncMdnRequireHttps}): a delivery address matched by it that does not use https is refused before + * the connection is opened and before the credentials are attached. + */ + public AS2ConsumerConfiguration( + AS2SignatureAlgorithm signingAlgorithm, + Certificate[] signingCertificateChain, + PrivateKey signingPrivateKey, + PrivateKey decryptingPrivateKey, + Certificate[] validateSigningCertificateChain, + String mdnUserName, + String mdnPassword, + String mdnAccessToken, + String asyncMdnAllowedHosts, + boolean asyncMdnRequireHttps) { this.signingAlgorithm = signingAlgorithm; this.signingCertificateChain = signingCertificateChain; this.signingPrivateKey = signingPrivateKey; @@ -177,6 +198,7 @@ public class AS2ServerConnection { this.mdnPassword = mdnPassword; this.mdnAccessToken = mdnAccessToken; this.asyncMdnAllowedHosts = asyncMdnAllowedHosts; + this.asyncMdnRequireHttps = asyncMdnRequireHttps; } // Getters @@ -215,6 +237,10 @@ public class AS2ServerConnection { public String getAsyncMdnAllowedHosts() { return asyncMdnAllowedHosts; } + + public boolean isAsyncMdnRequireHttps() { + return asyncMdnRequireHttps; + } } /** @@ -543,7 +569,8 @@ public class AS2ServerConnection { config.getMdnUserName(), config.getMdnPassword(), config.getMdnAccessToken(), - config.getAsyncMdnAllowedHosts()); + config.getAsyncMdnAllowedHosts(), + config.isAsyncMdnRequireHttps()); HttpRequest request = coreContext.getAttribute(HttpCoreContext.HTTP_REQUEST, HttpRequest.class); AS2SignedDataGenerator gen = ResponseMDN.createSigningGenerator( @@ -586,6 +613,7 @@ public class AS2ServerConnection { } } + } public AS2ServerConnection(String as2Version, @@ -624,6 +652,29 @@ public class AS2ServerConnection { String accessToken, String asyncMdnAllowedHosts) throws IOException { + this(as2Version, originServer, serverFqdn, serverPortNumber, signingAlgorithm, signingCertificateChain, + signingPrivateKey, decryptingPrivateKey, mdnMessageTemplate, validateSigningCertificateChain, sslContext, + userName, password, accessToken, asyncMdnAllowedHosts, false); + } + + public AS2ServerConnection(String as2Version, + String originServer, + String serverFqdn, + Integer serverPortNumber, + AS2SignatureAlgorithm signingAlgorithm, + Certificate[] signingCertificateChain, + PrivateKey signingPrivateKey, + PrivateKey decryptingPrivateKey, + String mdnMessageTemplate, + Certificate[] validateSigningCertificateChain, + SSLContext sslContext, + String userName, + String password, + String accessToken, + String asyncMdnAllowedHosts, + boolean asyncMdnRequireHttps) + throws IOException { + this.sslContext = sslContext; this.as2Version = ObjectHelper.notNull(as2Version, "as2Version"); this.originServer = ObjectHelper.notNull(originServer, "userAgent"); this.serverFqdn = ObjectHelper.notNull(serverFqdn, "serverFqdn"); @@ -643,7 +694,8 @@ public class AS2ServerConnection { userName, password, accessToken, - asyncMdnAllowedHosts); + asyncMdnAllowedHosts, + asyncMdnRequireHttps); registerConsumerConfiguration("/", consumerConfig); listenerService = new RequestListenerService( diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManagerDeliveryAddressTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManagerDeliveryAddressTest.java index 09ea7b6cca42..4862cfd67859 100644 --- a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManagerDeliveryAddressTest.java +++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2AsynchronousMDNManagerDeliveryAddressTest.java @@ -21,6 +21,7 @@ import org.apache.hc.core5.http.ContentType; import org.apache.hc.core5.http.HttpException; import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -71,16 +72,116 @@ class AS2AsynchronousMDNManagerDeliveryAddressTest { } @Test - void anIpv6AllowListEntryIsMatchedWithoutItsBrackets() { - // URI.getHost() yields "[::1]", but an operator writes "::1" in the allow-list - Exception e = assertThrows(Exception.class, () -> deliver("http://[::1]:1/receipts", "::1")); + void anIpv6AllowListEntryIsMatchedOnHostAndPort() { + // URI.getHost() yields "[::1]"; the operator lists the bracketed literal and its port as "[::1]:1" + Exception e = assertThrows(Exception.class, () -> deliver("http://[::1]:1/receipts", "[::1]:1")); assertTrue(!(e instanceof HttpException) || !e.getMessage().contains("asyncMdnAllowedHosts"), - "an IPv6 literal should match its unbracketed allow-list entry: " + e.getMessage()); + "an IPv6 literal should match its allow-list entry on host and port: " + e.getMessage()); + } + + /** + * An entry may pin the port in host:port form. A delivery address whose host and port both match the entry is + * accepted, so it gets past the address checks and fails on the connection instead - which is what tells us the + * address itself was accepted. + */ + @Test + void aMatchingHostAndPortIsAccepted() { + Exception e = assertThrows(Exception.class, () -> deliver("http://localhost:1/receipts", "localhost:1")); + assertTrue(!(e instanceof HttpException) || !e.getMessage().contains("asyncMdnAllowedHosts"), + "a delivery matching an allow-list entry on host and port must be accepted: " + e.getMessage()); + } + + /** + * The entry lists the host only, so it matches the default port (80 for http). A delivery to another port on that + * host is refused before the connection is opened, and the credentials are not attached. + */ + @Test + void aDifferentPortOnAListedHostIsRefused() { + HttpException e = assertThrows(HttpException.class, + () -> deliver("http://partner.example:8443/receipts", ALLOWED)); + assertTrue(e.getMessage().contains("asyncMdnAllowedHosts"), "unexpected message: " + e.getMessage()); + } + + /** + * A bare-host entry matches the default port for the scheme (80 for http, 443 for https). + */ + @Test + void aBareHostEntryMatchesTheDefaultPort() { + AS2AsynchronousMDNManager manager = new AS2AsynchronousMDNManager( + "1.1", "Camel", "sender.example.com", null, null, "user", "password", null, ALLOWED, null, + false); + assertTrue(manager.isAllowedAuthority("partner.example", 80, 80), + "a bare-host entry must match the default port for the scheme"); + } + + /** + * When credentials are withheld, the warning names the delivery authority as an asyncMdnAllowedHosts entry the + * operator can copy. An IPv6 literal is written in square brackets: unbracketed, ::1:8443 is itself an IPv6 address + * and the entry would be read as that host on the default port. + */ + @Test + void theWarningNamesTheAuthorityAsAnAllowListEntry() { + assertEquals("partner.example:4080", AS2AsynchronousMDNManager.formatAuthority("partner.example", 4080)); + assertEquals("[::1]:8443", AS2AsynchronousMDNManager.formatAuthority("::1", 8443)); + + for (String[] authority : new String[][] { { "partner.example", "4080" }, { "::1", "8443" } }) { + String host = authority[0]; + int port = Integer.parseInt(authority[1]); + AS2AsynchronousMDNManager manager = new AS2AsynchronousMDNManager( + "1.1", "Camel", "sender.example.com", null, null, "user", "password", null, + AS2AsynchronousMDNManager.formatAuthority(host, port), null, false); + assertTrue(manager.isAllowedAuthority(host, port, 80), + "the entry the warning names must authorise the authority it was written for: " + host + " " + port); + } + } + + /** + * With asyncMdnRequireHttps set, a plain-http delivery address is refused before the connection is opened and + * before the credentials are attached, even when its host is on the allow-list, so the credentials are never + * written in cleartext to a sender-chosen address. + */ + @Test + void httpIsRefusedWhenHttpsIsRequired() { + HttpException e = assertThrows(HttpException.class, + () -> deliver("http://partner.example/receipts", ALLOWED, null, true)); + assertTrue(e.getMessage().contains("asyncMdnRequireHttps"), "unexpected message: " + e.getMessage()); + } + + /** + * With asyncMdnRequireHttps set, an https address (with an SSLContext) is accepted: it gets past the scheme checks + * and fails on the connection instead, which is what tells us the address itself was accepted. + */ + @Test + void httpsIsAcceptedWhenHttpsIsRequired() throws Exception { + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, null, null); + Exception e = assertThrows(Exception.class, + () -> deliver("https://localhost:1/receipts", "localhost:1", sslContext, true)); + assertTrue(!(e instanceof HttpException) || !e.getMessage().contains("asyncMdnRequireHttps"), + "an https address must be accepted when asyncMdnRequireHttps is set: " + e.getMessage()); + } + + /** + * By default asyncMdnRequireHttps is not set, so a plain-http delivery address is still accepted (it gets past the + * scheme checks and fails on the connection instead): the control is opt-in and does not change the default. + */ + @Test + void httpIsAcceptedWhenHttpsIsNotRequired() { + Exception e = assertThrows(Exception.class, + () -> deliver("http://localhost:1/receipts", "localhost:1")); + assertTrue(!(e instanceof HttpException) || !e.getMessage().contains("asyncMdnRequireHttps"), + "http must be accepted when asyncMdnRequireHttps is not set: " + e.getMessage()); } private static void deliver(String deliveryAddress, String allowedHosts) throws Exception { + deliver(deliveryAddress, allowedHosts, false); + } + + private static void deliver(String deliveryAddress, String allowedHosts, boolean requireHttps) + throws Exception { AS2AsynchronousMDNManager manager = new AS2AsynchronousMDNManager( - "1.1", "Camel", "sender.example.com", null, null, "user", "password", null, allowedHosts); + "1.1", "Camel", "sender.example.com", null, null, "user", "password", null, allowedHosts, + requireHttps); manager.send(new TestEntity(), AS2MimeType.MULTIPART_REPORT, deliveryAddress); } diff --git a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2ConsumerConfigurationMdnSettingsTest.java b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2ConsumerConfigurationMdnSettingsTest.java index 1a297f6a10b3..e37bdcbb40c9 100644 --- a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2ConsumerConfigurationMdnSettingsTest.java +++ b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/AS2ConsumerConfigurationMdnSettingsTest.java @@ -20,7 +20,9 @@ import org.apache.camel.component.as2.api.AS2ServerConnection.AS2ConsumerConfigu import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * The asynchronous MDN settings (the credentials attached to an asynchronous MDN and the hosts it may be delivered to) @@ -40,6 +42,21 @@ class AS2ConsumerConfigurationMdnSettingsTest { assertEquals("password", config.getMdnPassword()); assertEquals("token", config.getMdnAccessToken()); assertEquals("receipts.example.com", config.getAsyncMdnAllowedHosts()); + assertFalse(config.isAsyncMdnRequireHttps()); + } + + @Test + void configurationWithRequireHttps() { + AS2ConsumerConfiguration config = new AS2ConsumerConfiguration( + AS2SignatureAlgorithm.SHA256WITHRSA, null, null, null, null, + "user", "password", "token", "receipts.example.com", true); + + assertEquals(AS2SignatureAlgorithm.SHA256WITHRSA, config.getSigningAlgorithm()); + assertEquals("user", config.getMdnUserName()); + assertEquals("password", config.getMdnPassword()); + assertEquals("token", config.getMdnAccessToken()); + assertEquals("receipts.example.com", config.getAsyncMdnAllowedHosts()); + assertTrue(config.isAsyncMdnRequireHttps()); } @Test @@ -51,6 +68,7 @@ class AS2ConsumerConfigurationMdnSettingsTest { assertNull(config.getMdnPassword()); assertNull(config.getMdnAccessToken()); assertNull(config.getAsyncMdnAllowedHosts()); + assertFalse(config.isAsyncMdnRequireHttps()); } @Test @@ -65,6 +83,7 @@ class AS2ConsumerConfigurationMdnSettingsTest { assertEquals("password", fallback.getMdnPassword()); assertEquals("token", fallback.getMdnAccessToken()); assertEquals("receipts.example.com", fallback.getAsyncMdnAllowedHosts()); + assertFalse(fallback.isAsyncMdnRequireHttps()); // a consumer registered for another path keeps its own settings, not the fallback's connection.registerConsumerConfiguration("/partner", new AS2ConsumerConfiguration( 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 b5d43287bf4d..8f98d6e462f5 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 @@ -30,6 +30,7 @@ public class AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org map.put("As2Version", java.lang.String.class); map.put("AsyncMdnAllowedHosts", java.lang.String.class); map.put("AsyncMdnPortNumber", java.lang.Integer.class); + map.put("AsyncMdnRequireHttps", boolean.class); map.put("AttachedFileName", java.lang.String.class); map.put("ClientFqdn", java.lang.String.class); map.put("CompressionAlgorithm", org.apache.camel.component.as2.api.AS2CompressionAlgorithm.class); @@ -92,6 +93,8 @@ public class AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org case "asyncMdnAllowedHosts": target.setAsyncMdnAllowedHosts(property(camelContext, java.lang.String.class, value)); return true; case "asyncmdnportnumber": case "asyncMdnPortNumber": target.setAsyncMdnPortNumber(property(camelContext, java.lang.Integer.class, value)); return true; + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": target.setAsyncMdnRequireHttps(property(camelContext, boolean.class, value)); return true; case "attachedfilename": case "attachedFileName": target.setAttachedFileName(property(camelContext, java.lang.String.class, value)); return true; case "clientfqdn": @@ -194,6 +197,8 @@ public class AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org case "asyncMdnAllowedHosts": return java.lang.String.class; case "asyncmdnportnumber": case "asyncMdnPortNumber": return java.lang.Integer.class; + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": return boolean.class; case "attachedfilename": case "attachedFileName": return java.lang.String.class; case "clientfqdn": @@ -292,6 +297,8 @@ public class AS2AsyncMDNServerManagerEndpointConfigurationConfigurer extends org case "asyncMdnAllowedHosts": return target.getAsyncMdnAllowedHosts(); case "asyncmdnportnumber": case "asyncMdnPortNumber": return target.getAsyncMdnPortNumber(); + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": return target.isAsyncMdnRequireHttps(); case "attachedfilename": case "attachedFileName": return target.getAttachedFileName(); case "clientfqdn": 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 6712d20d0868..1e6607ad880c 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 @@ -30,6 +30,7 @@ public class AS2ClientManagerEndpointConfigurationConfigurer extends org.apache. map.put("As2Version", java.lang.String.class); map.put("AsyncMdnAllowedHosts", java.lang.String.class); map.put("AsyncMdnPortNumber", java.lang.Integer.class); + map.put("AsyncMdnRequireHttps", boolean.class); map.put("AttachedFileName", java.lang.String.class); map.put("ClientFqdn", java.lang.String.class); map.put("CompressionAlgorithm", org.apache.camel.component.as2.api.AS2CompressionAlgorithm.class); @@ -93,6 +94,8 @@ public class AS2ClientManagerEndpointConfigurationConfigurer extends org.apache. case "asyncMdnAllowedHosts": target.setAsyncMdnAllowedHosts(property(camelContext, java.lang.String.class, value)); return true; case "asyncmdnportnumber": case "asyncMdnPortNumber": target.setAsyncMdnPortNumber(property(camelContext, java.lang.Integer.class, value)); return true; + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": target.setAsyncMdnRequireHttps(property(camelContext, boolean.class, value)); return true; case "attachedfilename": case "attachedFileName": target.setAttachedFileName(property(camelContext, java.lang.String.class, value)); return true; case "clientfqdn": @@ -197,6 +200,8 @@ public class AS2ClientManagerEndpointConfigurationConfigurer extends org.apache. case "asyncMdnAllowedHosts": return java.lang.String.class; case "asyncmdnportnumber": case "asyncMdnPortNumber": return java.lang.Integer.class; + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": return boolean.class; case "attachedfilename": case "attachedFileName": return java.lang.String.class; case "clientfqdn": @@ -297,6 +302,8 @@ public class AS2ClientManagerEndpointConfigurationConfigurer extends org.apache. case "asyncMdnAllowedHosts": return target.getAsyncMdnAllowedHosts(); case "asyncmdnportnumber": case "asyncMdnPortNumber": return target.getAsyncMdnPortNumber(); + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": return target.isAsyncMdnRequireHttps(); case "attachedfilename": case "attachedFileName": return target.getAttachedFileName(); case "clientfqdn": 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 44315e8155e5..0f951505bafa 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 @@ -30,6 +30,7 @@ public class AS2ConfigurationConfigurer extends org.apache.camel.support.compone map.put("As2Version", java.lang.String.class); map.put("AsyncMdnAllowedHosts", java.lang.String.class); map.put("AsyncMdnPortNumber", java.lang.Integer.class); + map.put("AsyncMdnRequireHttps", boolean.class); map.put("AttachedFileName", java.lang.String.class); map.put("ClientFqdn", java.lang.String.class); map.put("CompressionAlgorithm", org.apache.camel.component.as2.api.AS2CompressionAlgorithm.class); @@ -91,6 +92,8 @@ public class AS2ConfigurationConfigurer extends org.apache.camel.support.compone case "asyncMdnAllowedHosts": target.setAsyncMdnAllowedHosts(property(camelContext, java.lang.String.class, value)); return true; case "asyncmdnportnumber": case "asyncMdnPortNumber": target.setAsyncMdnPortNumber(property(camelContext, java.lang.Integer.class, value)); return true; + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": target.setAsyncMdnRequireHttps(property(camelContext, boolean.class, value)); return true; case "attachedfilename": case "attachedFileName": target.setAttachedFileName(property(camelContext, java.lang.String.class, value)); return true; case "clientfqdn": @@ -191,6 +194,8 @@ public class AS2ConfigurationConfigurer extends org.apache.camel.support.compone case "asyncMdnAllowedHosts": return java.lang.String.class; case "asyncmdnportnumber": case "asyncMdnPortNumber": return java.lang.Integer.class; + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": return boolean.class; case "attachedfilename": case "attachedFileName": return java.lang.String.class; case "clientfqdn": @@ -287,6 +292,8 @@ public class AS2ConfigurationConfigurer extends org.apache.camel.support.compone case "asyncMdnAllowedHosts": return target.getAsyncMdnAllowedHosts(); case "asyncmdnportnumber": case "asyncMdnPortNumber": return target.getAsyncMdnPortNumber(); + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": return target.isAsyncMdnRequireHttps(); case "attachedfilename": case "attachedFileName": return target.getAttachedFileName(); case "clientfqdn": 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 0eab1fb6e4b0..847fdb0a2223 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 @@ -57,6 +57,7 @@ public class AS2EndpointConfigurer extends PropertyConfigurerSupport implements map.put("LazyStartProducer", boolean.class); map.put("AccessToken", java.lang.String.class); map.put("AsyncMdnAllowedHosts", java.lang.String.class); + map.put("AsyncMdnRequireHttps", boolean.class); map.put("DecryptingPrivateKey", java.security.PrivateKey.class); map.put("EncryptingAlgorithm", org.apache.camel.component.as2.api.AS2EncryptionAlgorithm.class); map.put("EncryptingCertificateChain", java.security.cert.Certificate[].class); @@ -93,6 +94,8 @@ public class AS2EndpointConfigurer extends PropertyConfigurerSupport implements case "asyncMdnAllowedHosts": target.getConfiguration().setAsyncMdnAllowedHosts(property(camelContext, java.lang.String.class, value)); return true; case "asyncmdnportnumber": case "asyncMdnPortNumber": target.getConfiguration().setAsyncMdnPortNumber(property(camelContext, java.lang.Integer.class, value)); return true; + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": target.getConfiguration().setAsyncMdnRequireHttps(property(camelContext, boolean.class, value)); return true; case "attachedfilename": case "attachedFileName": target.getConfiguration().setAttachedFileName(property(camelContext, java.lang.String.class, value)); return true; case "clientfqdn": @@ -197,6 +200,8 @@ public class AS2EndpointConfigurer extends PropertyConfigurerSupport implements case "asyncMdnAllowedHosts": return java.lang.String.class; case "asyncmdnportnumber": case "asyncMdnPortNumber": return java.lang.Integer.class; + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": return boolean.class; case "attachedfilename": case "attachedFileName": return java.lang.String.class; case "clientfqdn": @@ -297,6 +302,8 @@ public class AS2EndpointConfigurer extends PropertyConfigurerSupport implements case "asyncMdnAllowedHosts": return target.getConfiguration().getAsyncMdnAllowedHosts(); case "asyncmdnportnumber": case "asyncMdnPortNumber": return target.getConfiguration().getAsyncMdnPortNumber(); + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": return target.getConfiguration().isAsyncMdnRequireHttps(); case "attachedfilename": case "attachedFileName": return target.getConfiguration().getAttachedFileName(); case "clientfqdn": 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 0d45c9920fff..45fab9f28fee 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 @@ -23,7 +23,7 @@ public class AS2EndpointUriFactory extends org.apache.camel.support.component.En private static final Set<String> SECRET_PROPERTY_NAMES; private static final Map<String, String> MULTI_VALUE_PREFIXES; static { - Set<String> props = new HashSet<>(53); + Set<String> props = new HashSet<>(54); props.add("accessToken"); props.add("apiName"); props.add("as2From"); @@ -32,6 +32,7 @@ public class AS2EndpointUriFactory extends org.apache.camel.support.component.En props.add("as2Version"); props.add("asyncMdnAllowedHosts"); props.add("asyncMdnPortNumber"); + props.add("asyncMdnRequireHttps"); props.add("attachedFileName"); props.add("clientFqdn"); props.add("compressionAlgorithm"); 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 da0d11a61f11..0b1bcf7206e0 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 @@ -30,6 +30,7 @@ public class AS2ServerManagerEndpointConfigurationConfigurer extends org.apache. map.put("As2Version", java.lang.String.class); map.put("AsyncMdnAllowedHosts", java.lang.String.class); map.put("AsyncMdnPortNumber", java.lang.Integer.class); + map.put("AsyncMdnRequireHttps", boolean.class); map.put("AttachedFileName", java.lang.String.class); map.put("ClientFqdn", java.lang.String.class); map.put("CompressionAlgorithm", org.apache.camel.component.as2.api.AS2CompressionAlgorithm.class); @@ -92,6 +93,8 @@ public class AS2ServerManagerEndpointConfigurationConfigurer extends org.apache. case "asyncMdnAllowedHosts": target.setAsyncMdnAllowedHosts(property(camelContext, java.lang.String.class, value)); return true; case "asyncmdnportnumber": case "asyncMdnPortNumber": target.setAsyncMdnPortNumber(property(camelContext, java.lang.Integer.class, value)); return true; + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": target.setAsyncMdnRequireHttps(property(camelContext, boolean.class, value)); return true; case "attachedfilename": case "attachedFileName": target.setAttachedFileName(property(camelContext, java.lang.String.class, value)); return true; case "clientfqdn": @@ -194,6 +197,8 @@ public class AS2ServerManagerEndpointConfigurationConfigurer extends org.apache. case "asyncMdnAllowedHosts": return java.lang.String.class; case "asyncmdnportnumber": case "asyncMdnPortNumber": return java.lang.Integer.class; + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": return boolean.class; case "attachedfilename": case "attachedFileName": return java.lang.String.class; case "clientfqdn": @@ -292,6 +297,8 @@ public class AS2ServerManagerEndpointConfigurationConfigurer extends org.apache. case "asyncMdnAllowedHosts": return target.getAsyncMdnAllowedHosts(); case "asyncmdnportnumber": case "asyncMdnPortNumber": return target.getAsyncMdnPortNumber(); + case "asyncmdnrequirehttps": + case "asyncMdnRequireHttps": return target.isAsyncMdnRequireHttps(); case "attachedfilename": case "attachedFileName": return target.getAttachedFileName(); case "clientfqdn": 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 92f53d024ce0..b9b8a070e796 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 @@ -67,22 +67,23 @@ "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." }, "lazyStartProducer": { "index": 32, "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": 33, "kind": "parameter", "displayName": "Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The access token that is used by the client for bearer authentication." }, - "asyncMdnAllowedHosts": { "index": 34, "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": 35, "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": 36, "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": 37, "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": 38, "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": 39, "kind": "parameter", "displayName": "Mdn Access Token", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "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 option applies to the consu [...] - "mdnPassword": { "index": 40, "kind": "parameter", "displayName": "Mdn Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "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 MDN. The option applie [...] - "mdnUserName": { "index": 41, "kind": "parameter", "displayName": "Mdn User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "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 async MDN. If options for [...] - "password": { "index": 42, "kind": "parameter", "displayName": "Password", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the client for basic authentication." }, - "signedReceiptMicAlgorithms": { "index": 43, "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": 44, "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": 45, "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": 46, "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": 47, "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": 48, "kind": "parameter", "displayName": "User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "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 authentication and bearer au [...] - "validateSigningCertificateChain": { "index": 49, "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 [...] + "asyncMdnAllowedHosts": { "index": 34, "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 hosts an asynchronous MDN may be delivered to. The delivery addre [...] + "asyncMdnRequireHttps": { "index": 35, "kind": "parameter", "displayName": "Async Mdn Require Https", "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 an asynchronous MDN may only be delivered over https. 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, "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 option applies to the consu [...] + "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, "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 MDN. The option applie [...] + "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, "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 async MDN. If options for [...] + "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, "configurationClass": "org.apache.camel.component.as2.AS2Configuration", "configurationField": "configuration", "description": "The password that is used by the client for basic authentication." }, + "signedReceiptMicAlgorithms": { "index": 44, "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": 45, "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": 46, "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": 47, "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": 48, "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": 49, "kind": "parameter", "displayName": "User Name", "group": "security", "label": "security", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": true, "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 authentication and bearer au [...] + "validateSigningCertificateChain": { "index": 50, "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 ab03de9141b4..9a37bb4a1b1f 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 @@ -56,4 +56,17 @@ The settings of the shared server connection still come from the first endpoint `sslContextParameters` and `sslContext` (used for the TLS listener), `mdnMessageTemplate`, `server`, `serverFqdn` and `as2Version`. They are per port, not per consumer. +=== Delivering asynchronous MDNs + +When a received message asks for an asynchronous MDN, the `as2://server/listen` consumer delivers the MDN to the +address that the sender gave in the `Receipt-Delivery-Option` header. When `asyncMdnAllowedHosts` is set, the MDN is +delivered only to an address on the list, and the `mdnUserName`, `mdnPassword` and `mdnAccessToken` credentials are +attached only for such an address. Each entry of the list is a host with an optional port: + +* a bare host, such as `partner.example`, matches only the default port for the scheme (80 for `http`, 443 for +`https`); +* a host and a port, such as `partner.example:8443`, match that port only; +* an IPv6 literal is written in square brackets, as in a URL: `[::1]` matches the default port, and `[::1]:8443` +matches port 8443. Without the brackets, `::1:8443` is read as another IPv6 address, not as `::1` with a port. + include::spring-boot:partial$starter.adoc[] 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 acf459b7feb3..48c6e2c2f767 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 @@ -118,6 +118,8 @@ public class AS2Configuration { private SSLContext sslContext; @UriParam(label = "security") private String asyncMdnAllowedHosts; + @UriParam(label = "security") + private boolean asyncMdnRequireHttps; // If you use localhost-based AS2 server, you don't need to specify a hostnameVerifier @UriParam(label = "security") private HostnameVerifier hostnameVerifier; @@ -665,12 +667,15 @@ public class AS2Configuration { } /** - * Comma separated list of host names an asynchronous MDN may be delivered to. The delivery address is chosen by the - * sender of the AS2 message, so this is the list of hosts the operator vouches for. + * Comma separated list of hosts an asynchronous MDN may be delivered to. The delivery address is chosen by the + * sender of the AS2 message, so this is the list of destinations the operator vouches for. Each entry is a host + * with an optional port in host:port form; an IPv6 literal is written in square brackets, as in a URL, optionally + * followed by :port. A bare-host entry (for example partner.example) matches only the default port for the scheme + * (80 or 443), so a non-default port is listed explicitly (for example partner.example:8443). * - * When set, an asynchronous MDN whose delivery address names a host outside the list is refused, and the configured - * mdnUserName/mdnPassword/mdnAccessToken are attached only to a host on the list. When not set, the MDN is still - * delivered to the sender-supplied address but no credentials are attached. + * When set, an asynchronous MDN whose delivery address is not on the list is refused, and the configured + * mdnUserName/mdnPassword/mdnAccessToken are attached only to an address on the list. When not set, the MDN is + * still delivered to the sender-supplied address but no credentials are attached. * * The option applies to the consumer endpoint it is set on: consumers sharing a serverPortNumber each apply their * own list. @@ -678,4 +683,22 @@ public class AS2Configuration { public void setAsyncMdnAllowedHosts(String asyncMdnAllowedHosts) { this.asyncMdnAllowedHosts = asyncMdnAllowedHosts; } + + public boolean isAsyncMdnRequireHttps() { + return asyncMdnRequireHttps; + } + + /** + * Whether an asynchronous MDN may only be delivered over https. The delivery address, including its scheme, is + * chosen by the sender of the AS2 message; by default both http and https are accepted. When set to true, an + * asynchronous MDN whose delivery address does not use https is refused, before the connection is opened and before + * the configured mdnUserName/mdnPassword/mdnAccessToken are attached, so the credentials are never sent in + * cleartext to a sender-chosen address. + * + * The option applies to the consumer endpoint it is set on: consumers sharing a serverPortNumber each apply their + * own value. + */ + public void setAsyncMdnRequireHttps(boolean asyncMdnRequireHttps) { + this.asyncMdnRequireHttps = asyncMdnRequireHttps; + } } diff --git a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java index 8d8c43edd542..6696af5647bf 100644 --- a/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java +++ b/components/camel-as2/camel-as2-component/src/main/java/org/apache/camel/component/as2/AS2Consumer.java @@ -107,7 +107,8 @@ public class AS2Consumer extends AbstractApiConsumer<AS2ApiName, AS2Configuratio configuration.getMdnUserName(), configuration.getMdnPassword(), configuration.getMdnAccessToken(), - configuration.getAsyncMdnAllowedHosts()); + configuration.getAsyncMdnAllowedHosts(), + configuration.isAsyncMdnRequireHttps()); as2ServerConnection.registerConsumerConfiguration(uri, consumerConfig); as2ServerConnection.listen(uri, this); 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 15474412e8df..aeaa009d0d95 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 @@ -111,7 +111,8 @@ public final class AS2ConnectionHelper { configuration.getSslContext(), configuration.getMdnUserName(), configuration.getMdnPassword(), configuration.getMdnAccessToken(), - configuration.getAsyncMdnAllowedHosts()); + configuration.getAsyncMdnAllowedHosts(), + configuration.isAsyncMdnRequireHttps()); } catch (IOException e) { throw new UncheckedIOException(e); } diff --git a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnBasicAuthHeaderTest.java b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnBasicAuthHeaderTest.java index 52cf5658acf0..5ae685ec1401 100644 --- a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnBasicAuthHeaderTest.java +++ b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnBasicAuthHeaderTest.java @@ -196,9 +196,9 @@ public class AS2AsyncMdnBasicAuthHeaderTest extends AbstractAS2ITSupport { "TBD", null, null, // server authorization config MDN_USER_NAME, MDN_PASSWORD, MDN_ACCESS_TOKEN, - // the MDN delivery address comes from the receiptDeliveryOption header, so the host it names - // has to be authorised before the credentials above are attached to it - "localhost"); + // the MDN delivery address comes from the receiptDeliveryOption header, so the host and port it + // names have to be authorised before the credentials above are attached to it + "localhost:" + JETTY_PORT + ",localhost:" + RECEIPT_SERVER_PORT); serverConnection.listen("/", new AS2AsyncMDNServerManagerIT.RequestHandler()); } } diff --git a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnTokenAuthHeaderTest.java b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnTokenAuthHeaderTest.java index 62685a588c05..a85e80268473 100644 --- a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnTokenAuthHeaderTest.java +++ b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2AsyncMdnTokenAuthHeaderTest.java @@ -148,9 +148,9 @@ public class AS2AsyncMdnTokenAuthHeaderTest extends AbstractAS2ITSupport { "TBD", null, null, // server authorization config null, null, MDN_ACCESS_TOKEN, - // the MDN delivery address comes from the receiptDeliveryOption header, so the host it names - // has to be authorised before the credentials above are attached to it - "localhost"); + // the MDN delivery address comes from the receiptDeliveryOption header, so the host and port it + // names have to be authorised before the credentials above are attached to it + "localhost:" + RECEIPT_SERVER_PORT); serverConnection.listen("/", new AS2AsyncMDNServerManagerIT.RequestHandler()); } } diff --git a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBasicAuthHeaderTest.java b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBasicAuthHeaderTest.java index 8114e3d7476c..f65e4bdbecf4 100644 --- a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBasicAuthHeaderTest.java +++ b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBasicAuthHeaderTest.java @@ -125,8 +125,9 @@ public class AS2ServerBasicAuthHeaderTest extends AbstractAS2ITSupport { @Override protected void customizeConfiguration(AS2Configuration configuration) { configuration.setServerPortNumber(TARGET_PORT); - // The MDN credentials are only attached to a delivery host the operator has authorised, and the - // delivery address here comes from the receiptDeliveryOption header, so localhost has to be listed. - configuration.setAsyncMdnAllowedHosts("localhost"); + // The MDN credentials are only attached to a delivery address the operator has authorised, and the + // delivery address here comes from the receiptDeliveryOption header, so localhost and the port of the + // receipt endpoint have to be listed. + configuration.setAsyncMdnAllowedHosts("localhost:" + JETTY_PORT); } } diff --git a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBearerAuthHeaderTest.java b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBearerAuthHeaderTest.java index 4d338a81279b..61c9bd6a864f 100644 --- a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBearerAuthHeaderTest.java +++ b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerBearerAuthHeaderTest.java @@ -122,8 +122,9 @@ public class AS2ServerBearerAuthHeaderTest extends AbstractAS2ITSupport { @Override protected void customizeConfiguration(AS2Configuration configuration) { configuration.setServerPortNumber(TARGET_PORT); - // The MDN credentials are only attached to a delivery host the operator has authorised, and the - // delivery address here comes from the receiptDeliveryOption header, so localhost has to be listed. - configuration.setAsyncMdnAllowedHosts("localhost"); + // The MDN credentials are only attached to a delivery address the operator has authorised, and the + // delivery address here comes from the receiptDeliveryOption header, so localhost and the port of the + // receipt endpoint have to be listed. + configuration.setAsyncMdnAllowedHosts("localhost:" + JETTY_PORT); } } diff --git a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerAsyncMdnSettingsTest.java b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerAsyncMdnSettingsTest.java index 2420baafa10c..45e1a06c5221 100644 --- a/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerAsyncMdnSettingsTest.java +++ b/components/camel-as2/camel-as2-component/src/test/java/org/apache/camel/component/as2/AS2ServerTwoConsumerAsyncMdnSettingsTest.java @@ -124,12 +124,21 @@ class AS2ServerTwoConsumerAsyncMdnSettingsTest extends AS2ServerSecTestBase { null, receiptDeliveryAddress, null, null, null); } + // the host and port of each receipt endpoint, as listed in asyncMdnAllowedHosts + private String localhostReceiptAuthority() { + return "localhost:" + localhostReceiptPort.getPort(); + } + + private String loopbackReceiptAuthority() { + return "127.0.0.1:" + loopbackReceiptPort.getPort(); + } + private String localhostReceiptAddress() { - return "http://localhost:" + localhostReceiptPort.getPort() + RECEIPT_PATH; + return "http://" + localhostReceiptAuthority() + RECEIPT_PATH; } private String loopbackReceiptAddress() { - return "http://127.0.0.1:" + loopbackReceiptPort.getPort() + RECEIPT_PATH; + return "http://" + loopbackReceiptAuthority() + RECEIPT_PATH; } @Override @@ -139,12 +148,13 @@ class AS2ServerTwoConsumerAsyncMdnSettingsTest extends AS2ServerSecTestBase { // the first consumer creates the server connection shared by every consumer on the port from("as2://server/listen?requestUriPattern=" + FIRST_PATTERN + "&mdnUserName=" + FIRST_USER_NAME + "&mdnPassword=" + FIRST_PASSWORD - + "&asyncMdnAllowedHosts=localhost,127.0.0.1") + + "&asyncMdnAllowedHosts=" + localhostReceiptAuthority() + "," + loopbackReceiptAuthority()) .routeId("first") .to("mock:first"); from("as2://server/listen?requestUriPattern=" + SECOND_PATTERN - + "&mdnAccessToken=" + SECOND_ACCESS_TOKEN + "&asyncMdnAllowedHosts=127.0.0.1") + + "&mdnAccessToken=" + SECOND_ACCESS_TOKEN + + "&asyncMdnAllowedHosts=" + loopbackReceiptAuthority()) .routeId("second") .to("mock:second"); @@ -155,7 +165,8 @@ class AS2ServerTwoConsumerAsyncMdnSettingsTest extends AS2ServerSecTestBase { // the root pattern, on the connection another consumer created from("as2://server/listen?requestUriPattern=" + ROOT_PATTERN - + "&mdnAccessToken=" + ROOT_ACCESS_TOKEN + "&asyncMdnAllowedHosts=localhost") + + "&mdnAccessToken=" + ROOT_ACCESS_TOKEN + + "&asyncMdnAllowedHosts=" + localhostReceiptAuthority()) .routeId("root") .to("mock:root"); 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 c9336a11f5e6..9f6a861d7c0b 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 @@ -623,13 +623,18 @@ public interface AS2EndpointBuilderFactory { return this; } /** - * Comma separated list of host names an asynchronous MDN may be - * delivered to. The delivery address is chosen by the sender of the AS2 - * message, so this is the list of hosts the operator vouches for. When - * set, an asynchronous MDN whose delivery address names a host outside - * the list is refused, and the configured - * mdnUserName/mdnPassword/mdnAccessToken are attached only to a host on - * the list. When not set, the MDN is still delivered to the + * Comma separated list of hosts an asynchronous MDN may be delivered + * to. The delivery address is chosen by the sender of the AS2 message, + * so this is the list of destinations the operator vouches for. Each + * entry is a host with an optional port in host:port form; an IPv6 + * literal is written in square brackets, as in a URL, optionally + * followed by :port. A bare-host entry (for example partner.example) + * matches only the default port for the scheme (80 or 443), so a + * non-default port is listed explicitly (for example + * partner.example:8443). When set, an asynchronous MDN whose delivery + * address is not on the list is refused, and the configured + * mdnUserName/mdnPassword/mdnAccessToken are attached only to an + * address on the list. When not set, the MDN is still delivered to the * sender-supplied address but no credentials are attached. The option * applies to the consumer endpoint it is set on: consumers sharing a * serverPortNumber each apply their own list. @@ -645,6 +650,52 @@ public interface AS2EndpointBuilderFactory { doSetProperty("asyncMdnAllowedHosts", asyncMdnAllowedHosts); return this; } + /** + * Whether an asynchronous MDN may only be delivered over https. The + * delivery address, including its scheme, is chosen by the sender of + * the AS2 message; by default both http and https are accepted. When + * set to true, an asynchronous MDN whose delivery address does not use + * https is refused, before the connection is opened and before the + * configured mdnUserName/mdnPassword/mdnAccessToken are attached, so + * the credentials are never sent in cleartext to a sender-chosen + * address. The option applies to the consumer endpoint it is set on: + * consumers sharing a serverPortNumber each apply their own value. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param asyncMdnRequireHttps the value to set + * @return the dsl builder + */ + default AS2EndpointConsumerBuilder asyncMdnRequireHttps(boolean asyncMdnRequireHttps) { + doSetProperty("asyncMdnRequireHttps", asyncMdnRequireHttps); + return this; + } + /** + * Whether an asynchronous MDN may only be delivered over https. The + * delivery address, including its scheme, is chosen by the sender of + * the AS2 message; by default both http and https are accepted. When + * set to true, an asynchronous MDN whose delivery address does not use + * https is refused, before the connection is opened and before the + * configured mdnUserName/mdnPassword/mdnAccessToken are attached, so + * the credentials are never sent in cleartext to a sender-chosen + * address. The option applies to the consumer endpoint it is set on: + * consumers sharing a serverPortNumber each apply their own value. + * + * The option will be converted to a <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param asyncMdnRequireHttps the value to set + * @return the dsl builder + */ + default AS2EndpointConsumerBuilder asyncMdnRequireHttps(String asyncMdnRequireHttps) { + doSetProperty("asyncMdnRequireHttps", asyncMdnRequireHttps); + return this; + } /** * The key used to encrypt the EDI message. * @@ -1681,13 +1732,18 @@ public interface AS2EndpointBuilderFactory { return this; } /** - * Comma separated list of host names an asynchronous MDN may be - * delivered to. The delivery address is chosen by the sender of the AS2 - * message, so this is the list of hosts the operator vouches for. When - * set, an asynchronous MDN whose delivery address names a host outside - * the list is refused, and the configured - * mdnUserName/mdnPassword/mdnAccessToken are attached only to a host on - * the list. When not set, the MDN is still delivered to the + * Comma separated list of hosts an asynchronous MDN may be delivered + * to. The delivery address is chosen by the sender of the AS2 message, + * so this is the list of destinations the operator vouches for. Each + * entry is a host with an optional port in host:port form; an IPv6 + * literal is written in square brackets, as in a URL, optionally + * followed by :port. A bare-host entry (for example partner.example) + * matches only the default port for the scheme (80 or 443), so a + * non-default port is listed explicitly (for example + * partner.example:8443). When set, an asynchronous MDN whose delivery + * address is not on the list is refused, and the configured + * mdnUserName/mdnPassword/mdnAccessToken are attached only to an + * address on the list. When not set, the MDN is still delivered to the * sender-supplied address but no credentials are attached. The option * applies to the consumer endpoint it is set on: consumers sharing a * serverPortNumber each apply their own list. @@ -1703,6 +1759,52 @@ public interface AS2EndpointBuilderFactory { doSetProperty("asyncMdnAllowedHosts", asyncMdnAllowedHosts); return this; } + /** + * Whether an asynchronous MDN may only be delivered over https. The + * delivery address, including its scheme, is chosen by the sender of + * the AS2 message; by default both http and https are accepted. When + * set to true, an asynchronous MDN whose delivery address does not use + * https is refused, before the connection is opened and before the + * configured mdnUserName/mdnPassword/mdnAccessToken are attached, so + * the credentials are never sent in cleartext to a sender-chosen + * address. The option applies to the consumer endpoint it is set on: + * consumers sharing a serverPortNumber each apply their own value. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param asyncMdnRequireHttps the value to set + * @return the dsl builder + */ + default AS2EndpointProducerBuilder asyncMdnRequireHttps(boolean asyncMdnRequireHttps) { + doSetProperty("asyncMdnRequireHttps", asyncMdnRequireHttps); + return this; + } + /** + * Whether an asynchronous MDN may only be delivered over https. The + * delivery address, including its scheme, is chosen by the sender of + * the AS2 message; by default both http and https are accepted. When + * set to true, an asynchronous MDN whose delivery address does not use + * https is refused, before the connection is opened and before the + * configured mdnUserName/mdnPassword/mdnAccessToken are attached, so + * the credentials are never sent in cleartext to a sender-chosen + * address. The option applies to the consumer endpoint it is set on: + * consumers sharing a serverPortNumber each apply their own value. + * + * The option will be converted to a <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param asyncMdnRequireHttps the value to set + * @return the dsl builder + */ + default AS2EndpointProducerBuilder asyncMdnRequireHttps(String asyncMdnRequireHttps) { + doSetProperty("asyncMdnRequireHttps", asyncMdnRequireHttps); + return this; + } /** * The key used to encrypt the EDI message. * @@ -2720,13 +2822,18 @@ public interface AS2EndpointBuilderFactory { return this; } /** - * Comma separated list of host names an asynchronous MDN may be - * delivered to. The delivery address is chosen by the sender of the AS2 - * message, so this is the list of hosts the operator vouches for. When - * set, an asynchronous MDN whose delivery address names a host outside - * the list is refused, and the configured - * mdnUserName/mdnPassword/mdnAccessToken are attached only to a host on - * the list. When not set, the MDN is still delivered to the + * Comma separated list of hosts an asynchronous MDN may be delivered + * to. The delivery address is chosen by the sender of the AS2 message, + * so this is the list of destinations the operator vouches for. Each + * entry is a host with an optional port in host:port form; an IPv6 + * literal is written in square brackets, as in a URL, optionally + * followed by :port. A bare-host entry (for example partner.example) + * matches only the default port for the scheme (80 or 443), so a + * non-default port is listed explicitly (for example + * partner.example:8443). When set, an asynchronous MDN whose delivery + * address is not on the list is refused, and the configured + * mdnUserName/mdnPassword/mdnAccessToken are attached only to an + * address on the list. When not set, the MDN is still delivered to the * sender-supplied address but no credentials are attached. The option * applies to the consumer endpoint it is set on: consumers sharing a * serverPortNumber each apply their own list. @@ -2742,6 +2849,52 @@ public interface AS2EndpointBuilderFactory { doSetProperty("asyncMdnAllowedHosts", asyncMdnAllowedHosts); return this; } + /** + * Whether an asynchronous MDN may only be delivered over https. The + * delivery address, including its scheme, is chosen by the sender of + * the AS2 message; by default both http and https are accepted. When + * set to true, an asynchronous MDN whose delivery address does not use + * https is refused, before the connection is opened and before the + * configured mdnUserName/mdnPassword/mdnAccessToken are attached, so + * the credentials are never sent in cleartext to a sender-chosen + * address. The option applies to the consumer endpoint it is set on: + * consumers sharing a serverPortNumber each apply their own value. + * + * The option is a: <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param asyncMdnRequireHttps the value to set + * @return the dsl builder + */ + default AS2EndpointBuilder asyncMdnRequireHttps(boolean asyncMdnRequireHttps) { + doSetProperty("asyncMdnRequireHttps", asyncMdnRequireHttps); + return this; + } + /** + * Whether an asynchronous MDN may only be delivered over https. The + * delivery address, including its scheme, is chosen by the sender of + * the AS2 message; by default both http and https are accepted. When + * set to true, an asynchronous MDN whose delivery address does not use + * https is refused, before the connection is opened and before the + * configured mdnUserName/mdnPassword/mdnAccessToken are attached, so + * the credentials are never sent in cleartext to a sender-chosen + * address. The option applies to the consumer endpoint it is set on: + * consumers sharing a serverPortNumber each apply their own value. + * + * The option will be converted to a <code>boolean</code> type. + * + * Default: false + * Group: security + * + * @param asyncMdnRequireHttps the value to set + * @return the dsl builder + */ + default AS2EndpointBuilder asyncMdnRequireHttps(String asyncMdnRequireHttps) { + doSetProperty("asyncMdnRequireHttps", asyncMdnRequireHttps); + return this; + } /** * The key used to encrypt the EDI message. *
