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]
