You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@camel.apache.org by da...@apache.org on 2026/10/07 13:09:27 UTC

(camel) 01/01: CAMEL-24440: camel-crypto - finalize and compare the MAC on a padding failure too

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

davsclaus pushed a commit to branch fix/CAMEL-24440-mac-on-padding-failure
in repository https://gitbox.apache.org/repos/asf/camel.git

commit f32a7312ee6867305297e96a93a92ffd287c94e3
Author: Claus Ibsen <cl...@gmail.com>
AuthorDate: Wed Oct 7 15:09:14 2026 +0200

    CAMEL-24440: camel-crypto - finalize and compare the MAC on a padding failure too
    
    A message with bad padding is now reported as the same authentication failure
    as a bad MAC, but it skipped the MAC finalization and constant-time compare that
    the bad-MAC path runs. The padding path now runs that same final step before
    failing, so the two failures also take the same final work.
    
    Co-Authored-By: Claude Opus 5.5 (1M context) <no...@anthropic.com>
    Signed-off-by: Claus Ibsen <cl...@gmail.com>
---
 .../apache/camel/converter/crypto/CryptoDataFormat.java   |  4 +++-
 .../apache/camel/converter/crypto/HMACAccumulator.java    | 13 ++++++++++++-
 .../camel/converter/crypto/HMACAccumulatorTest.java       | 15 +++++++++++++++
 3 files changed, 30 insertions(+), 2 deletions(-)

diff --git a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/CryptoDataFormat.java b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/CryptoDataFormat.java
index 09a0764b5a1e..ae5052b18057 100644
--- a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/CryptoDataFormat.java
+++ b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/CryptoDataFormat.java
@@ -206,7 +206,9 @@ public class CryptoDataFormat extends ServiceSupport implements DataFormat, Data
                         // shouldAppendHMAC=false nothing is authenticating, so calling it an authentication failure
                         // would misdescribe a plain padding error and drop its cause.
                         LOG.debug("Reporting cipher failure as an authentication failure", e);
-                        throw new IllegalStateException(HMACAccumulator.AUTHENTICATION_FAILED);
+                        // Still finalize and compare the MAC, as a bad MAC does, so the two failures also take the
+                        // same final work and cannot be told apart by timing. This always throws.
+                        hmac.validate(true);
                     }
                     throw e;
                 }
diff --git a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/HMACAccumulator.java b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/HMACAccumulator.java
index e0b7181fffa4..9cade39ef0ec 100644
--- a/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/HMACAccumulator.java
+++ b/components/camel-crypto/src/main/java/org/apache/camel/converter/crypto/HMACAccumulator.java
@@ -102,10 +102,21 @@ public class HMACAccumulator {
     static final String AUTHENTICATION_FAILED = "Message authentication failed";
 
     public void validate() {
+        validate(false);
+    }
+
+    /**
+     * Validates the appended MAC. When the cipher has already failed (bad padding) the MAC is still finalized and
+     * compared before failing, so that a padding failure costs the same final work as a MAC failure and the two cannot
+     * be told apart by timing either.
+     *
+     * @param cipherFailed whether decryption already failed, in which case this always fails
+     */
+    void validate(boolean cipherFailed) {
         byte[] actual = getCalculatedMac();
         byte[] expected = getAppendedMac();
         // Use a constant-time comparison to avoid leaking MAC-match progress through timing (side-channel).
-        if (!MessageDigest.isEqual(expected, actual)) {
+        if (!MessageDigest.isEqual(expected, actual) || cipherFailed) {
             // The computed MAC is HMAC_k over the plaintext that was just produced, so reporting it hands the
             // caller a value they could not otherwise compute. Neither MAC belongs in the message.
             throw new IllegalStateException(AUTHENTICATION_FAILED);
diff --git a/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/HMACAccumulatorTest.java b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/HMACAccumulatorTest.java
index 5438704d176d..a45e4ece942e 100644
--- a/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/HMACAccumulatorTest.java
+++ b/components/camel-crypto/src/test/java/org/apache/camel/converter/crypto/HMACAccumulatorTest.java
@@ -86,6 +86,21 @@ public class HMACAccumulatorTest {
         assertThrows(IllegalStateException.class, builder::validate);
     }
 
+    @Test
+    void testValidateAfterCipherFailureStillComputesMacAndFails() throws Exception {
+        int buffersize = 256;
+        byte[] buffer = initializeBuffer(buffersize);
+
+        HMACAccumulator builder = new HMACAccumulator(key, "HmacSHA1", null, buffersize);
+        builder.decryptUpdate(buffer, 40);
+        // fails although the appended MAC matches, with the same message as a MAC mismatch
+        IllegalStateException e = assertThrows(IllegalStateException.class, () -> builder.validate(true));
+        assertEquals(HMACAccumulator.AUTHENTICATION_FAILED, e.getMessage());
+        // the MAC was still finalized and compared, as on the MAC-mismatch path
+        assertMacs(expected, builder.getCalculatedMac());
+        assertMacs(expected, builder.getAppendedMac());
+    }
+
     @Test
     void testDecryptionWhereMacOverlaps() throws Exception {
         int buffersize = 32;