This is an automated email from the ASF dual-hosted git repository.

oscerd pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 1776cbf60ddf CAMEL-24431: camel-as2 - bound the expansion of a 
compressed entity (#26656)
1776cbf60ddf is described below

commit 1776cbf60ddf92b8780f92d921543584ee2e47d1
Author: Andrea Cosentino <[email protected]>
AuthorDate: Wed Sep 23 10:01:45 2026 +0200

    CAMEL-24431: camel-as2 - bound the expansion of a compressed entity (#26656)
    
    * CAMEL-24431: camel-as2 - bound the expansion of a compressed entity
    
    HttpMessageUtils.extractEdiPayloadFromCompressedEntity() expanded the entity
    with an unbounded expander:
    
        MimeEntity entity = compressedDataEntity.getCompressedEntity(new 
ZlibExpanderProvider());
    
    The expansion happens while the payload is extracted, which is before the
    signature has been established, so the work is done on behalf of a sender 
that
    is not yet authenticated, and zlib reaches roughly 1000:1.
    
    All four call sites are internal statics with no configuration threaded 
through,
    and passing an endpoint option down would mean changing several public
    signatures in camel-as2-api. Instead the bound is a constant,
    MAX_COMPRESSED_ENTITY_EXPANSION, set well above any realistic EDI payload, 
with
    a public overload taking an explicit bound for a deployment that needs a
    different value.
    
    Note on the test: it calls the new overload, so unlike the other fixes in 
this
    series it cannot show the behaviour before the change - there was no bound 
to
    pass. What it shows is that the mechanism works: refused at 1 KiB with
    "Failed to decompress data" from EntityParser.uncompressData, and 
successful at
    10 MiB.
    
    Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
    
    * CAMEL-24431: camel-as2 - bound the MIC-path decompression and address 
review nits
    
    Addresses review feedback on #26656:
    - MicUtils#findSignedDataEntity decompressed a compressed-data message with
      an unbounded ZlibExpanderProvider on the same pre-authentication MIC path;
      it now uses the MAX_COMPRESSED_ENTITY_EXPANSION bound, like 
HttpMessageUtils.
    - added a MIC-path test that a within-bound compressed message still
      decompresses and yields a MIC.
    - de-duplicated a comment and replaced a fully-qualified StandardCharsets
      with an import in the expansion-bound test.
    
    Co-Authored-By: Claude Opus 4.8 <[email protected]>
    Signed-off-by: Andrea Cosentino <[email protected]>
    
    ---------
    
    Signed-off-by: Andrea Cosentino <[email protected]>
    Co-authored-by: Claude Opus 5 (1M context) <[email protected]>
---
 .../component/as2/api/util/HttpMessageUtils.java   | 28 +++++++-
 .../camel/component/as2/api/util/MicUtils.java     |  3 +-
 .../util/CompressedEntityExpansionBoundTest.java   | 75 ++++++++++++++++++++++
 .../camel/component/as2/api/util/MicUtilsTest.java | 25 ++++++++
 4 files changed, 129 insertions(+), 2 deletions(-)

diff --git 
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
 
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
index 1cfc126efa2d..066ee8d9bfed 100644
--- 
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
+++ 
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/HttpMessageUtils.java
@@ -48,6 +48,13 @@ import org.slf4j.LoggerFactory;
 
 public final class HttpMessageUtils {
 
+    /**
+     * Largest expansion accepted for a compressed AS2 entity. Chosen to be 
far above any realistic EDI payload while
+     * still bounding the pathological case; the overload taking an explicit 
bound is available where a deployment needs
+     * a different value.
+     */
+    public static final long MAX_COMPRESSED_ENTITY_EXPANSION = 100L * 1024 * 
1024;
+
     private static final Logger LOG = 
LoggerFactory.getLogger(HttpMessageUtils.class);
 
     private HttpMessageUtils() {
@@ -335,9 +342,28 @@ public final class HttpMessageUtils {
             ApplicationPkcs7MimeCompressedDataEntity compressedDataEntity, 
DecrpytingAndSigningInfo decrpytingAndSigningInfo,
             boolean hasValidSignature)
             throws HttpException {
+        return extractEdiPayloadFromCompressedEntity(compressedDataEntity, 
decrpytingAndSigningInfo, hasValidSignature,
+                MAX_COMPRESSED_ENTITY_EXPANSION);
+    }
+
+    /**
+     * As
+     * {@link 
#extractEdiPayloadFromCompressedEntity(ApplicationPkcs7MimeCompressedDataEntity,
 DecrpytingAndSigningInfo, boolean)},
+     * with an explicit bound on how far the compressed entity may expand.
+     *
+     * @param maxExpandedSize the largest expansion to accept, in bytes
+     */
+    public static ApplicationEntity extractEdiPayloadFromCompressedEntity(
+            ApplicationPkcs7MimeCompressedDataEntity compressedDataEntity, 
DecrpytingAndSigningInfo decrpytingAndSigningInfo,
+            boolean hasValidSignature, long maxExpandedSize)
+            throws HttpException {
         ApplicationEntity ediEntity;
 
-        MimeEntity entity = compressedDataEntity.getCompressedEntity(new 
ZlibExpanderProvider());
+        // The expansion happens while extracting the payload, which is before 
the signature has been
+        // established, so the work is done on behalf of a sender that is not 
yet authenticated. zlib
+        // reaches roughly 1000:1, so an unbounded expander lets a small 
entity claim a large amount of
+        // memory. Bound it.
+        MimeEntity entity = compressedDataEntity.getCompressedEntity(new 
ZlibExpanderProvider(maxExpandedSize));
         String contentTypeString = entity.getContentType();
         if (contentTypeString == null) {
             throw new HttpException("Failed to extract EDI payload: content 
type missing from compressed entity");
diff --git 
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java
 
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java
index 5e11779f6ca4..294e83b9b189 100644
--- 
a/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java
+++ 
b/components/camel-as2/camel-as2-api/src/main/java/org/apache/camel/component/as2/api/util/MicUtils.java
@@ -176,7 +176,8 @@ public final class MicUtils {
                         = HttpMessageUtils.getEntity(request, 
ApplicationPkcs7MimeCompressedDataEntity.class);
                 if (compressedEntity != null) {
                     MimeEntity inner = compressedEntity
-                            .getCompressedEntity(new ZlibExpanderProvider());
+                            .getCompressedEntity(
+                                    new 
ZlibExpanderProvider(HttpMessageUtils.MAX_COMPRESSED_ENTITY_EXPANSION));
                     if (inner instanceof MultipartSignedEntity signedEntity) {
                         return signedEntity.getSignedDataEntity();
                     }
diff --git 
a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/CompressedEntityExpansionBoundTest.java
 
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/CompressedEntityExpansionBoundTest.java
new file mode 100644
index 000000000000..c7f22a16cda5
--- /dev/null
+++ 
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/CompressedEntityExpansionBoundTest.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.as2.api.util;
+
+import java.nio.charset.StandardCharsets;
+
+import org.apache.camel.component.as2.api.entity.ApplicationEDIFACTEntity;
+import org.apache.camel.component.as2.api.entity.ApplicationEntity;
+import 
org.apache.camel.component.as2.api.entity.ApplicationPkcs7MimeCompressedDataEntity;
+import org.apache.hc.core5.http.HttpException;
+import org.bouncycastle.cms.CMSCompressedDataGenerator;
+import org.bouncycastle.cms.jcajce.ZlibCompressor;
+import org.bouncycastle.operator.OutputCompressor;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * A compressed entity is expanded while the payload is extracted, which 
happens before the signature has been
+ * established. The expansion must therefore be bounded rather than 
proportional to whatever an unauthenticated sender
+ * claims.
+ */
+class CompressedEntityExpansionBoundTest {
+
+    /** Highly compressible, so the entity on the wire is a tiny fraction of 
what it expands to. */
+    private static final String PAYLOAD = "A".repeat(200_000);
+
+    private static final HttpMessageUtils.DecrpytingAndSigningInfo NO_SECURITY
+            = new HttpMessageUtils.DecrpytingAndSigningInfo(null, null);
+
+    @Test
+    void expansionBeyondTheBoundIsRefused() throws Exception {
+        ApplicationPkcs7MimeCompressedDataEntity compressed = 
compressedEntity();
+
+        // the failure must come from the expander refusing, not from anything 
downstream, so assert the type
+        HttpException thrown = assertThrows(HttpException.class,
+                () -> 
HttpMessageUtils.extractEdiPayloadFromCompressedEntity(compressed, NO_SECURITY, 
false, 1024L),
+                "expanding well past the bound must fail rather than 
allocate");
+        assertTrue(thrown.getMessage().contains("decompress"),
+                "expected the expander to refuse, but failed with: " + 
thrown.getMessage());
+    }
+
+    @Test
+    void expansionWithinTheBoundStillWorks() throws Exception {
+        ApplicationPkcs7MimeCompressedDataEntity compressed = 
compressedEntity();
+
+        ApplicationEntity entity = 
HttpMessageUtils.extractEdiPayloadFromCompressedEntity(
+                compressed, NO_SECURITY, false, 10L * 1024 * 1024);
+        assertInstanceOf(ApplicationEntity.class, entity);
+    }
+
+    private static ApplicationPkcs7MimeCompressedDataEntity compressedEntity() 
throws Exception {
+        ApplicationEDIFACTEntity ediEntity = new ApplicationEDIFACTEntity(
+                PAYLOAD.getBytes(StandardCharsets.US_ASCII), "US-ASCII", 
"7bit", false, null);
+        CMSCompressedDataGenerator generator = new 
CMSCompressedDataGenerator();
+        OutputCompressor compressor = new ZlibCompressor();
+        return new ApplicationPkcs7MimeCompressedDataEntity(ediEntity, 
generator, compressor, "base64", false);
+    }
+}
diff --git 
a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java
 
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java
index 6892871d9714..678e1cd71431 100644
--- 
a/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java
+++ 
b/components/camel-as2/camel-as2-api/src/test/java/org/apache/camel/component/as2/api/util/MicUtilsTest.java
@@ -25,10 +25,13 @@ import org.apache.camel.component.as2.api.AS2Header;
 import org.apache.camel.component.as2.api.AS2MimeType;
 import org.apache.camel.component.as2.api.AS2TransferEncoding;
 import org.apache.camel.component.as2.api.entity.ApplicationEDIFACTEntity;
+import 
org.apache.camel.component.as2.api.entity.ApplicationPkcs7MimeCompressedDataEntity;
 import org.apache.camel.component.as2.api.util.MicUtils.ReceivedContentMic;
 import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.io.entity.BasicHttpEntity;
 import org.apache.hc.core5.http.message.BasicClassicHttpRequest;
+import org.bouncycastle.cms.CMSCompressedDataGenerator;
+import org.bouncycastle.cms.jcajce.ZlibCompressor;
 import org.bouncycastle.jce.provider.BouncyCastleProvider;
 import org.junit.jupiter.api.AfterEach;
 import org.junit.jupiter.api.BeforeEach;
@@ -108,6 +111,9 @@ public class MicUtilsTest {
     private static final String EXPECTED_MESSAGE_DIGEST_ALGORITHM = "sha1";
     private static final String EXPECTED_ENCODED_MESSAGE_DIGEST = 
"0mGTGdBjQtu8VQ52506Coi0xHbc=";
 
+    /** Highly compressible, so the compressed entity on the wire is a tiny 
fraction of what it expands to. */
+    private static final String COMPRESSIBLE_EDI_PAYLOAD = "A".repeat(200_000);
+
     @BeforeEach
     public void setUp() {
         Security.addProvider(new BouncyCastleProvider());
@@ -142,6 +148,25 @@ public class MicUtilsTest {
                 "Unexpected encoded message digest value");
     }
 
+    @Test
+    public void createReceivedContentMicForCompressedMessageStaysWithinBound() 
throws Exception {
+        // A compressed-data message is decompressed on the MIC path 
(MicUtils#findSignedDataEntity) before the sender
+        // has been authenticated, so that expansion is now bounded 
(CAMEL-24431). A payload well within the bound must
+        // still decompress and yield a MIC rather than being refused - 
exercising the newly bounded call site.
+        BasicClassicHttpRequest request = new BasicClassicHttpRequest("POST", 
"/");
+        request.addHeader(AS2Header.DISPOSITION_NOTIFICATION_OPTIONS, 
DISPOSITION_NOTIFICATION_OPTIONS_VALUE);
+
+        ApplicationEDIFACTEntity ediEntity = new ApplicationEDIFACTEntity(
+                COMPRESSIBLE_EDI_PAYLOAD.getBytes(StandardCharsets.US_ASCII), 
"US-ASCII", "7bit", false, null);
+        ApplicationPkcs7MimeCompressedDataEntity compressed = new 
ApplicationPkcs7MimeCompressedDataEntity(
+                ediEntity, new CMSCompressedDataGenerator(), new 
ZlibCompressor(), "base64", false);
+        request.addHeader(AS2Header.CONTENT_TYPE, compressed.getContentType());
+        request.setEntity(compressed);
+
+        ReceivedContentMic receivedContentMic = 
MicUtils.createReceivedContentMic(request, null, null);
+        assertNotNull(receivedContentMic, "a within-bound compressed message 
must still produce a MIC");
+    }
+
     // verify that a MIC is calculated correctly for an EDI message containing 
non ASCII chars
     @Test
     public void createReceivedContentMicWithNonAsciiContentTest() throws 
Exception {

Reply via email to