Reto Peter created CAMEL-23067:
----------------------------------

             Summary: Async MDN signature verification fails — receipt API 
modifies raw body bytes before signature validation
                 Key: CAMEL-23067
                 URL: https://issues.apache.org/jira/browse/CAMEL-23067
             Project: Camel
          Issue Type: Bug
          Components: camel-as2
    Affects Versions: 4.18.0
            Reporter: Reto Peter


When receiving async MDNs via the Camel AS2 receipt API 
(\{{as2://receipt/receive}}), the HTTP body is parsed and processed by Camel's 
MIME parser before the application can access it. During parsing, the original 
byte representation is modified (header
  folding, whitespace normalization, line ending changes). This means the exact 
bytes that were signed by the sender can no longer be reconstructed, causing 
S/MIME signature verification to fail with a digest mismatch.

  This affects all signed async MDNs. The signature was computed by the sender 
over the exact raw HTTP body bytes. If even a single byte changes (e.g., a 
whitespace or line ending normalization), the digest no longer matches and the 
signature is invalid.
  \{panel}

  h3. Background

  In the AS2 protocol, when async MDN is configured:
  Sender sends an AS2 message to the receiver

  Receiver returns HTTP 200 immediately (no MDN in response)

  Receiver processes the message and sends the MDN back to the sender via a 
separate HTTP POST to the sender's MDN callback URL

  Sender receives the async MDN and validates the signature to confirm it was 
not tampered with

  The async MDN is typically a \{{multipart/signed}} MIME message. The 
signature covers the exact bytes of the MDN body. To verify the signature, the 
receiver (in this case Camel) must have access to the exact raw bytes of the 
HTTP body as they arrived over the
  wire.

  h3. Root Cause

  Camel's AS2 receipt API endpoint (\{{as2://receipt/receive}}) uses 
HttpCore5's \{{HttpService}} to receive the HTTP request. The request body is 
parsed through Camel's \{{EntityParser}} which processes the MIME structure. 
During this parsing:

  - MIME headers may be folded/unfolded differently
  - Whitespace may be normalized
  - Line endings may be changed (\{{\r\n}} vs \{{\n}})
  - The parsed entity's \{{writeTo()}} output differs from the original raw 
bytes

  By the time the application's route processor receives the exchange, the body 
is already a parsed \{{MultipartSignedEntity}} or 
\{{DispositionNotificationMultipartReportEntity}} object — the original raw 
bytes are lost. When attempting S/MIME signature
  verification on these parsed objects, the digest computed over the 
re-serialized bytes does not match the digest in the signature.

  h3. Steps to Reproduce

  Configure a Camel AS2 server with async MDN enabled (separate MDN callback 
URL)

  Send a message to a partner that returns signed async MDNs (e.g., OpenAS2)

  Receive the async MDN via the Camel receipt API route

  Attempt to verify the MDN signature

  Observe: \{{CMSSignerDigestMismatchException}} — the digest computed over the 
parsed/re-serialized body does not match the digest in the signature

  h3. Expected Behavior

  The Camel AS2 receipt API should provide access to the raw HTTP body bytes 
before MIME parsing, so that signature verification can be performed on the 
original bytes.

  h3. Proposed Fix

  Provide a mechanism to capture the raw HTTP body bytes before any parsing 
occurs. This could be implemented as:

  Option A: Raw body capture in HttpCore5 handler

  Intercept the HTTP request body at the \{{HttpService}} / 
\{{BasicHttpServerRequestHandler}} level, capture the raw bytes into a buffer, 
and expose them via an exchange property (e.g., \{{CamelAs2.rawMdnBody}}) 
alongside the parsed entity.

  \{code:java}
  // In the request handler, before parsing:
  byte[] rawBody = inputStream.readAllBytes();
  httpContext.setAttribute("CamelAs2.rawMdnBody", rawBody);

  // Then parse from the captured bytes:
  InputStream parseStream = new ByteArrayInputStream(rawBody);
  // ... continue with existing MIME parsing ...
  \{code}

  Option B: Expose raw body on the exchange

  After Camel processes the receipt, set the raw bytes as an exchange property:

  \{code:java}
  exchange.setProperty("CamelAs2.asyncMdnRawBody", rawBodyBytes);
  \{code}

  This would allow application code to verify the signature using the original 
bytes:

  \{code:java}
  byte[] rawBody = exchange.getProperty("CamelAs2.asyncMdnRawBody", 
byte[].class);
  // Use rawBody for S/MIME signature verification via BouncyCastle 
SMIMESignedParser
  \{code}

  h3. Our Workaround

  We had to bypass Camel's receipt API entirely and implement a Spring 
\{{@RestController}} that receives async MDNs on the same HTTP port. We use a 
servlet filter (\{{RawBodyCaptureFilter}}) to capture the raw HTTP body bytes 
before any processing, then perform
  signature verification manually using BouncyCastle's \{{SMIMESignedParser}} 
on the captured raw bytes.

  This works but requires:
  - A separate Spring controller duplicating MDN processing logic
  - A servlet filter to intercept and cache the raw request body
  - Manual MIME parsing and MDN field extraction (Original-Message-ID, 
Disposition, Received-Content-MIC)
  - Manual signature verification via BouncyCastle

  The entire Camel receipt API with its built-in MDN processing, signature 
handling, and exchange properties is unused because the raw body is not 
accessible.

  h3. Test Scenario

  Tested with OpenAS2 sending signed async MDNs.

  Via Camel receipt API: \{{CMSSignerDigestMismatchException}} — signature 
verification always fails because the re-serialized body bytes differ from the 
original signed bytes.

  Via Spring controller with raw body capture: Signature verification passes 
because we use the exact bytes the sender signed.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to