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

gnodet pushed a commit to branch fix/as2-issues
in repository https://gitbox.apache.org/repos/asf/camel.git

commit 356cff07d479232699ff10f561f372588e09f10f
Author: Guillaume Nodet <[email protected]>
AuthorDate: Mon Mar 9 11:04:21 2026 +0100

    CAMEL-23065: Fix MIC computation for compress-before-sign scenarios
    
    Navigate the entity hierarchy to find the correct entity for MIC
    computation per RFC 5402. For compress-before-sign, the MIC must cover
    the compressed entity (what was signed), not the decompressed EDI
    payload.
    
    Co-Authored-By: Claude Opus 4.6 <[email protected]>
---
 .../camel/component/as2/api/util/MicUtils.java     | 96 +++++++++++++++++++++-
 1 file changed, 93 insertions(+), 3 deletions(-)

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 b1699058d8a9..1852f5d530de 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
@@ -25,9 +25,16 @@ import java.security.cert.Certificate;
 
 import org.apache.camel.component.as2.api.AS2Header;
 import org.apache.camel.component.as2.api.AS2MicAlgorithm;
+import org.apache.camel.component.as2.api.AS2MimeType;
+import 
org.apache.camel.component.as2.api.entity.ApplicationPkcs7MimeCompressedDataEntity;
+import 
org.apache.camel.component.as2.api.entity.ApplicationPkcs7MimeEnvelopedDataEntity;
 import 
org.apache.camel.component.as2.api.entity.DispositionNotificationOptions;
 import 
org.apache.camel.component.as2.api.entity.DispositionNotificationOptionsParser;
+import org.apache.camel.component.as2.api.entity.EntityParser;
+import org.apache.camel.component.as2.api.entity.MimeEntity;
+import org.apache.camel.component.as2.api.entity.MultipartSignedEntity;
 import org.apache.hc.core5.http.ClassicHttpRequest;
+import org.apache.hc.core5.http.ContentType;
 import org.apache.hc.core5.http.HttpEntity;
 import org.apache.hc.core5.http.HttpException;
 import org.slf4j.Logger;
@@ -98,10 +105,12 @@ public final class MicUtils {
             return null;
         }
 
-        HttpEntity entity = HttpMessageUtils.extractEdiPayload(request,
-                new 
HttpMessageUtils.DecrpytingAndSigningInfo(validateSigningCertificateChain, 
decryptingPrivateKey));
+        // Compute MIC over the correct content per RFC 5402:
+        // - For compress-before-sign: MIC covers the compressed entity (what 
was signed)
+        // - For sign-before-compress or no compression: MIC covers the EDI 
payload
+        HttpEntity micEntity = findMicEntity(request, 
validateSigningCertificateChain, decryptingPrivateKey);
 
-        byte[] content = EntityUtils.getContent(entity);
+        byte[] content = EntityUtils.getContent(micEntity);
 
         String micAS2AlgorithmName = 
AS2MicAlgorithm.getAS2AlgorithmName(micJdkAlgorithmName);
         byte[] mic = createMic(content, micJdkAlgorithmName);
@@ -112,6 +121,87 @@ public final class MicUtils {
         }
     }
 
+    /**
+     * Finds the correct entity to compute the MIC over, per RFC 5402.
+     * <p>
+     * For compress-before-sign, the MIC must cover the compressed entity 
(what was signed). For sign-before-compress or
+     * no compression, the MIC covers the EDI payload.
+     */
+    private static HttpEntity findMicEntity(
+            ClassicHttpRequest request, Certificate[] 
validateSigningCertificateChain, PrivateKey decryptingPrivateKey)
+            throws HttpException {
+
+        EntityParser.parseAS2MessageEntity(request);
+
+        String contentTypeString = HttpMessageUtils.getHeaderValue(request, 
AS2Header.CONTENT_TYPE);
+        if (contentTypeString == null) {
+            throw new HttpException("Failed to create MIC: content type 
missing from request");
+        }
+        ContentType contentType = ContentType.parse(contentTypeString);
+
+        // Navigate the message structure to find the signed data entity
+        HttpEntity entity = findSignedDataEntity(request, contentType, 
decryptingPrivateKey);
+        if (entity != null) {
+            return entity;
+        }
+
+        // No multipart/signed found, fall back to extracting the EDI payload
+        return HttpMessageUtils.extractEdiPayload(request,
+                new 
HttpMessageUtils.DecrpytingAndSigningInfo(validateSigningCertificateChain, 
decryptingPrivateKey));
+    }
+
+    /**
+     * Navigate the entity hierarchy to find the signed data entity (part 0 of 
multipart/signed). Returns the signed
+     * data entity which is what the MIC should be computed over, or null if 
no multipart/signed is found.
+     */
+    private static HttpEntity findSignedDataEntity(
+            ClassicHttpRequest request, ContentType contentType, PrivateKey 
decryptingPrivateKey)
+            throws HttpException {
+
+        String mimeType = contentType.getMimeType().toLowerCase();
+
+        if (AS2MimeType.MULTIPART_SIGNED.equals(mimeType)) {
+            // Top-level signed: MIC is over the signed data entity (part 0)
+            MultipartSignedEntity multipartSignedEntity
+                    = HttpMessageUtils.getEntity(request, 
MultipartSignedEntity.class);
+            if (multipartSignedEntity != null) {
+                return multipartSignedEntity.getSignedDataEntity();
+            }
+        } else if (AS2MimeType.APPLICATION_PKCS7_MIME.equals(mimeType)) {
+            String smimeType = contentType.getParameter("smime-type");
+            if ("compressed-data".equals(smimeType)) {
+                // Sign-before-compress: decompress first, then find the 
signed entity inside
+                ApplicationPkcs7MimeCompressedDataEntity compressedEntity
+                        = HttpMessageUtils.getEntity(request, 
ApplicationPkcs7MimeCompressedDataEntity.class);
+                if (compressedEntity != null) {
+                    MimeEntity inner = compressedEntity
+                            .getCompressedEntity(new 
org.bouncycastle.cms.jcajce.ZlibExpanderProvider());
+                    if (inner instanceof MultipartSignedEntity signedEntity) {
+                        return signedEntity.getSignedDataEntity();
+                    }
+                }
+            } else if ("enveloped-data".equals(smimeType) && 
decryptingPrivateKey != null) {
+                // Encrypted message: decrypt first, then look for signed 
entity
+                ApplicationPkcs7MimeEnvelopedDataEntity envelopedEntity
+                        = HttpMessageUtils.getEntity(request, 
ApplicationPkcs7MimeEnvelopedDataEntity.class);
+                if (envelopedEntity != null) {
+                    MimeEntity decryptedEntity = 
envelopedEntity.getEncryptedEntity(decryptingPrivateKey);
+                    String decryptedContentType = 
decryptedEntity.getContentType();
+                    if (decryptedContentType != null) {
+                        ContentType decryptedCt = 
ContentType.parse(decryptedContentType);
+                        String decryptedMime = 
decryptedCt.getMimeType().toLowerCase();
+                        if (AS2MimeType.MULTIPART_SIGNED.equals(decryptedMime)
+                                && decryptedEntity instanceof 
MultipartSignedEntity signedEntity) {
+                            return signedEntity.getSignedDataEntity();
+                        }
+                    }
+                }
+            }
+        }
+
+        return null;
+    }
+
     public static String getMicJdkAlgorithmName(String[] micAs2AlgorithmNames) 
{
         if (micAs2AlgorithmNames == null) {
             return AS2MicAlgorithm.SHA_1.getJdkAlgorithmName();

Reply via email to