[
https://issues.apache.org/jira/browse/CAMEL-25179?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
shashank updated CAMEL-25179:
-----------------------------
Priority: Major (was: Minor)
> camel-crypto - CryptoDataFormat with the default HMAC cannot unmarshal a
> message larger than its buffer size (4096 bytes):
> ArrayIndexOutOfBoundsException in HMACAccumulator.CircularBuffer
> -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
> Key: CAMEL-25179
> URL: https://issues.apache.org/jira/browse/CAMEL-25179
> Project: Camel
> Issue Type: Bug
> Components: camel-core
> Reporter: shashank
> Priority: Major
>
> {{CryptoDataFormat}} appends an HMAC by default ({{shouldAppendHMAC=true}}).
> On unmarshal, {{HMACAccumulator.decryptUpdate}} holds back the last
> {{maclength}} bytes in a {{CircularBuffer}} of {{bufferSize + maclength}}
> bytes. Both {{write}} and {{read}} of that buffer get the wrap-around at the
> end of the array wrong:
> {code:java}
> public void write(byte[] data, int pos, int len) {
> if (available >= len) {
> if (write + len > buffer.length) {
> int overlap = write + len % buffer.length; // is write +
> (len % length)
> System.arraycopy(data, 0, buffer, write, len - overlap); //
> length is -write
> System.arraycopy(data, len - overlap, buffer, 0, overlap);
> } else { ... }
> ...
> public int read(byte[] dest, int position, int len) {
> ...
> if (read > write) {
> int x = buffer.length - read;
> System.arraycopy(buffer, read, dest, position, buffer.length
> - read); // copies length - read bytes, not len
> System.arraycopy(buffer, 0, dest, position + x, overlap);
> } else {
> System.arraycopy(buffer, read, dest, position, len);
> // no wrap-around at all
> }
> {code}
> Every {{write}} that has to wrap throws (the first copy gets a negative
> length), and a {{read}} throws or copies the wrong bytes as soon as the read
> position has passed the write position. {{CipherInputStream}} hands the
> decrypted data out in chunks of about 512 bytes (GCM: in chunks of
> {{bufferSize}}), so the buffer wraps as soon as a message is larger than
> {{bufferSize}}: unmarshal of any message of 4096 bytes or more fails with the
> default settings:
> {noformat}
> java.lang.ArrayIndexOutOfBoundsException: arraycopy: length -4088 is negative
> at
> org.apache.camel.converter.crypto.HMACAccumulator$CircularBuffer.write(HMACAccumulator.java:138)
> at
> org.apache.camel.converter.crypto.HMACAccumulator.decryptUpdate(HMACAccumulator.java:71)
> at
> org.apache.camel.converter.crypto.CryptoDataFormat.unmarshal(CryptoDataFormat.java:197)
> {noformat}
> The data format documentation example ({{new CryptoDataFormat("DES", key)}},
> marshal then unmarshal) fails for a 5000 byte body. The existing tests only
> use a 49 byte payload, and {{HMACAccumulatorTest}} never wraps the buffer.
> The workarounds are {{shouldAppendHMAC=false}} (no integrity check) or a
> {{bufferSize}} larger than every message.
> h3. Reproduction
> {code:java}
> CryptoDataFormat crypto = new CryptoDataFormat("DES",
> KeyGenerator.getInstance("DES").generateKey());
> from("direct:basic").marshal(crypto).unmarshal(crypto).to("mock:unencrypted");
> template.sendBody("direct:basic", new byte[5000]); // fails; 100 bytes works
> {code}
> Unit tests round-trip 100, 4096, 5000 and 100000 bytes through the data
> format with the default options, and exercise the two {{CircularBuffer}}
> cases and a 1000 byte input fed to {{HMACAccumulator}} in 24 byte chunks: on
> main every case from 4096 bytes up fails, the 100 byte control passes
> (checked again on main {{c3632125413b}}, 2026-09-30). The buffer code comes
> from the original contribution (CAMEL-2482, 2010) and is the same in 3.0.0,
> 4.0.0, 4.14.0, 4.18.0 and 4.22.0; later changes to the class (CAMEL-19194,
> CAMEL-23766, CAMEL-24440) did not touch it.
> A small formal model (Lean 4) of the two methods shows that every {{write}}
> which has to wrap throws, that a non-wrapping {{read}} with {{read > write}}
> throws when the destination only has room for {{len}} bytes (the
> {{decryptUpdate}} case), and that the fixed methods implement a FIFO queue;
> together with a model of {{decryptUpdate}} this gives: for every way the
> input is split into chunks of at most {{bufferSize}} bytes, the bytes passed
> on are the input without its last {{maclength}} bytes, and the bytes kept
> back for the check are exactly those last {{maclength}} bytes.
> h3. Proposed fix
> Split both copies at the end of the array:
> {code:java}
> public void write(byte[] data, int pos, int len) {
> if (available >= len) {
> int first = Math.min(len, buffer.length - write);
> System.arraycopy(data, pos, buffer, write, first);
> System.arraycopy(data, pos + first, buffer, 0, len - first);
> write = (write + len) % buffer.length;
> available -= len;
> }
> }
> public int read(byte[] dest, int position, int len) {
> if (dest.length - position >= len) {
> if (buffer.length - available >= len) {
> int first = Math.min(len, buffer.length - read);
> System.arraycopy(buffer, read, dest, position, first);
> System.arraycopy(buffer, 0, dest, position + first, len - first);
> read = (read + len) % buffer.length;
> available += len;
> return len;
> }
> }
> return 0;
> }
> {code}
> The format of the data is unchanged (marshal is not affected), so messages
> written by earlier versions can be read. With the fix the new tests pass, and
> so does the whole camel-crypto suite (84 tests).
> This is a failure, not a weakness in the MAC check: the MAC is still compared
> over exactly the bytes that are passed on.
> Duplicate check (2026-09-30): JIRA "HMACAccumulator", "CircularBuffer",
> "CryptoDataFormat" with "large" or "ArrayIndexOutOfBoundsException": only
> CAMEL-19194, CAMEL-23766 and CAMEL-24440, none about the buffer. No open or
> closed pull request touches the circular buffer.
> _Filed with Claude Code on behalf of allthingssecurity._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)