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

chibenwa pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-jdkim.git

commit 5e84be7cb466479491bef10dad63a22457acd808
Author: Benoit TELLIER <[email protected]>
AuthorDate: Fri Oct 30 08:32:25 2026 +0100

    [FIX] SignatureRecord: reject an l= tag that is not 1*76DIGIT
    
    getBodyHashLimit() used Integer.parseInt with no prior validation:
    - an empty or non-numeric l= threw NumberFormatException;
    - a signed value was accepted: l=-1 collided with the internal "whole
      body" sentinel and any other negative value hashed no byte at all;
    - a value beyond the int range threw.
    
    validate() now enforces the RFC 6376 grammar, values too large for an
    int are clamped, and BodyHasherImpl refuses negative limits other than
    the sentinel.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../apache/james/jdkim/impl/BodyHasherImpl.java    |  3 +++
 .../james/jdkim/tagvalue/SignatureRecordImpl.java  | 18 +++++++++++++-
 .../jdkim/tagvalue/SignatureRecordTemplate.java    |  7 +++++-
 .../james/jdkim/SignatureRecordImplTest.java       | 29 ++++++++++++++++++++++
 .../james/jdkim/SignatureRecordTemplateTest.java   |  6 +++++
 5 files changed, 61 insertions(+), 2 deletions(-)

diff --git a/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java 
b/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java
index eb69126..713434f 100644
--- a/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java
+++ b/main/src/main/java/org/apache/james/jdkim/impl/BodyHasherImpl.java
@@ -49,6 +49,9 @@ public class BodyHasherImpl implements BodyHasher {
         }
 
         int limit = sign.getBodyHashLimit();
+        if (limit < -1) {
+            throw new PermFailException("Invalid body length limit: " + limit, 
sign);
+        }
 
         // TODO enhance this to use a lookup service.
         boolean relaxedBody = SignatureRecord.RELAXED.equals(sign
diff --git 
a/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java 
b/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java
index cfaa3a8..cb7ab22 100644
--- 
a/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java
+++ 
b/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordImpl.java
@@ -34,6 +34,7 @@ public class SignatureRecordImpl extends TagValue implements 
SignatureRecord {
     // %d59-126
     private static final Pattern hdrNamePattern = Pattern.compile("^[^: 
\r\n\t]+$");
     private static final Pattern timestampPattern = 
Pattern.compile("^[0-9]{1,12}$");
+    private static final Pattern bodyLengthPattern = 
Pattern.compile("^[0-9]{1,76}$");
 
     public SignatureRecordImpl(String data) {
         super(data);
@@ -85,6 +86,10 @@ public class SignatureRecordImpl extends TagValue implements 
SignatureRecord {
         if (getValue("t") != null && 
!timestampPattern.matcher(getValue("t")).matches())
             throw new IllegalStateException("Invalid signature timestamp (t=): 
" + getValue("t"));
 
+        // RFC 6376 section 3.5: sig-l-tag = %x6c [FWS] "=" [FWS] 1*76DIGIT
+        if (!ALL.equals(getValue("l")) && 
!bodyLengthPattern.matcher(getValue("l")).matches())
+            throw new IllegalStateException("Invalid body length (l=): " + 
getValue("l"));
+
         // when "x=" exists and signature expired then return PERMFAIL
         // (signature expired)
         if (getValue("x") != null) {
@@ -239,7 +244,18 @@ public class SignatureRecordImpl extends TagValue 
implements SignatureRecord {
         if (ALL.equals(limit))
             return -1;
         else
-            return Integer.parseInt(limit);
+            return parseBodyHashLimit(limit);
+    }
+
+    /**
+     * Parses a validated l= value. Values beyond Integer.MAX_VALUE exceed any
+     * body we can hash and are clamped.
+     */
+    static int parseBodyHashLimit(String limit) {
+        String digits = limit.replaceFirst("^0+(?=.)", "");
+        if (digits.length() > 10)
+            return Integer.MAX_VALUE;
+        return (int) Math.min(Long.parseLong(digits), Integer.MAX_VALUE);
     }
 
     public Long getSignatureTimestamp() {
diff --git 
a/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordTemplate.java
 
b/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordTemplate.java
index 34bb92e..0701e30 100644
--- 
a/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordTemplate.java
+++ 
b/main/src/main/java/org/apache/james/jdkim/tagvalue/SignatureRecordTemplate.java
@@ -36,6 +36,7 @@ public class SignatureRecordTemplate extends TagValue 
implements SignatureRecord
     // %d59-126
     private static final Pattern hdrNamePattern = Pattern.compile("^[^: 
\r\n\t]+$");
     private static final Pattern timestampPattern = 
Pattern.compile("^[0-9]{1,12}$");
+    private static final Pattern bodyLengthPattern = 
Pattern.compile("^[0-9]{1,76}$");
 
     public SignatureRecordTemplate(String data) {
         super(data);
@@ -94,6 +95,10 @@ public class SignatureRecordTemplate extends TagValue 
implements SignatureRecord
                 && !timestampPattern.matcher(getValue("t")).matches())
             throw new IllegalStateException("Invalid signature timestamp (t=): 
" + getValue("t"));
 
+        // RFC 6376 section 3.5: sig-l-tag = %x6c [FWS] "=" [FWS] 1*76DIGIT
+        if (!ALL.equals(getValue("l")) && 
!bodyLengthPattern.matcher(getValue("l")).matches())
+            throw new IllegalStateException("Invalid body length (l=): " + 
getValue("l"));
+
         // when "x=" exists and signature expired then return PERMFAIL
         // (signature expired)
         if (getValue("x") != null) {
@@ -248,7 +253,7 @@ public class SignatureRecordTemplate extends TagValue 
implements SignatureRecord
         if (ALL.equals(limit))
             return -1;
         else
-            return Integer.parseInt(limit);
+            return SignatureRecordImpl.parseBodyHashLimit(limit);
     }
 
     public Long getSignatureTimestamp() {
diff --git 
a/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java 
b/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java
index d41515f..533160d 100644
--- a/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java
+++ b/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java
@@ -189,4 +189,33 @@ public class SignatureRecordImplTest {
                 .isInstanceOf(IllegalStateException.class);
         assertThat(new SignatureRecordImpl(base + 
"t=1234567890").getSignatureTimestamp()).isEqualTo(1234567890L);
     }
+
+    @Test
+    public void nonNumericBodyLengthShouldBeASyntaxError() {
+        String base = "v=1; a=rsa-sha256; d=example.com; s=s; h=from; bh=AA==; 
b=AA==; ";
+
+        assertThatThrownBy(() -> new SignatureRecordImpl(base + "l="))
+                .isInstanceOf(IllegalStateException.class);
+        assertThatThrownBy(() -> new SignatureRecordImpl(base + "l=a"))
+                .isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    public void signedBodyLengthShouldBeASyntaxError() {
+        String base = "v=1; a=rsa-sha256; d=example.com; s=s; h=from; bh=AA==; 
b=AA==; ";
+
+        assertThatThrownBy(() -> new SignatureRecordImpl(base + 
"l=-1")).isInstanceOf(IllegalStateException.class);
+        assertThatThrownBy(() -> new SignatureRecordImpl(base + 
"l=-5")).isInstanceOf(IllegalStateException.class);
+        assertThatThrownBy(() -> new SignatureRecordImpl(base + 
"l=+5")).isInstanceOf(IllegalStateException.class);
+        assertThatThrownBy(() -> new SignatureRecordImpl(base + 
"l=-0")).isInstanceOf(IllegalStateException.class);
+    }
+
+    @Test
+    public void bodyLengthBeyondIntegerRangeShouldBeClamped() {
+        String base = "v=1; a=rsa-sha256; d=example.com; s=s; h=from; bh=AA==; 
b=AA==; ";
+
+        assertThat(new SignatureRecordImpl(base + 
"l=99999999999").getBodyHashLimit()).isEqualTo(Integer.MAX_VALUE);
+        assertThat(new SignatureRecordImpl(base + 
"l=0000000000000000042").getBodyHashLimit()).isEqualTo(42);
+        assertThat(new SignatureRecordImpl(base + 
"l=0").getBodyHashLimit()).isEqualTo(0);
+    }
 }
diff --git 
a/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java 
b/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java
index 5adf7a7..00f76f5 100644
--- a/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java
+++ b/main/src/test/java/org/apache/james/jdkim/SignatureRecordTemplateTest.java
@@ -176,4 +176,10 @@ public class SignatureRecordTemplateTest {
     public void emptyTimestampShouldBeAccepted() {
         new SignatureRecordTemplate("v=1; a=rsa-sha256; d=example.com; s=s; 
h=from; t=");
     }
+
+    @Test
+    public void signedBodyLengthShouldBeASyntaxError() {
+        assertThatThrownBy(() -> new SignatureRecordTemplate("v=1; 
a=rsa-sha256; d=example.com; s=s; h=from; l=-1"))
+                .isInstanceOf(IllegalStateException.class);
+    }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to