[
https://issues.apache.org/jira/browse/CAMEL-23067?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Reto Peter updated CAMEL-23067:
-------------------------------
Summary: Async MDN signature verification fails — receipt API modifies raw
body bytes before signature validation (was: camel-as2 - Async MDN signature
verification fails — receipt API modifies raw body bytes before signature
validation)
> 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
> Priority: Minor
>
> 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)