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)