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 61801be2bf6632ecd33f997cb4062649e7deb0b2 Author: Benoit TELLIER <[email protected]> AuthorDate: Fri Oct 30 08:44:04 2026 +0100 [FIX] SignatureRecord: empty the b= tag itself in toUnsignedString() toUnsignedString() emptied the first "b=" substring of the field, so a tag such as xb=1, an h= entry holding "b=" or a DKIM-Quoted-Printable value with "b=XX" before the real b= tag was emptied instead, and the signature value stayed in the hashed data. Valid signatures then failed, including jDKIM's own when the template had such a tag. The tag-list is now walked tag by tag and only the value of the tag named b is deleted, with the whitespace around it. Co-Authored-By: Claude Opus 5.5 <[email protected]> --- .../james/jdkim/tagvalue/SignatureRecordImpl.java | 2 +- .../jdkim/tagvalue/SignatureRecordTemplate.java | 2 +- .../org/apache/james/jdkim/tagvalue/TagValue.java | 20 ++++++++++++++++++++ .../test/java/org/apache/james/jdkim/DKIMTest.java | 12 ++++++++++++ .../apache/james/jdkim/SignatureRecordImplTest.java | 8 ++++++++ 5 files changed, 42 insertions(+), 2 deletions(-) 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 75104e9..5f42991 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 @@ -336,6 +336,6 @@ public class SignatureRecordImpl extends TagValue implements SignatureRecord { } public String toUnsignedString() { - return toString().replaceFirst("b=[^;]*", "b="); + return emptyTagValue(toString(), "b"); } } 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 e7f27ee..e4c2594 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 @@ -325,6 +325,6 @@ public class SignatureRecordTemplate extends TagValue implements SignatureRecord } public String toUnsignedString() { - return toString().replaceFirst("b=[^;]*", "b="); + return emptyTagValue(toString(), "b"); } } diff --git a/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java b/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java index 991a854..3782c4e 100644 --- a/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java +++ b/main/src/main/java/org/apache/james/jdkim/tagvalue/TagValue.java @@ -237,6 +237,26 @@ public class TagValue { return Base64.getDecoder().decode(fwsPattern.matcher(value).replaceAll("")); } + /** + * Deletes the value of a tag, with the whitespace around it, and leaves the + * rest of the tag-list as is (RFC 6376 section 3.7 for b=). + */ + protected static String emptyTagValue(String tagList, String tag) { + int start = 0; + while (start <= tagList.length()) { + int end = tagList.indexOf(';', start); + if (end == -1) + end = tagList.length(); + int equal = tagList.indexOf('=', start); + if (equal != -1 && equal < end + && tag.equals(fwsPattern.matcher(tagList.subSequence(start, equal)).replaceAll(""))) { + return tagList.substring(0, equal + 1) + tagList.substring(end); + } + start = end + 1; + } + return tagList; + } + protected void setValue(String tag, String value) { stringRepresentation = null; tagValues.put(tag, value); diff --git a/main/src/test/java/org/apache/james/jdkim/DKIMTest.java b/main/src/test/java/org/apache/james/jdkim/DKIMTest.java index ffd45c5..1a3c219 100644 --- a/main/src/test/java/org/apache/james/jdkim/DKIMTest.java +++ b/main/src/test/java/org/apache/james/jdkim/DKIMTest.java @@ -289,4 +289,16 @@ public class DKIMTest { assertThat(verifier.verify(in)).hasSize(1); } + + @Test + public void signatureWithATagNameEndingInBShouldVerify() throws Exception { + DKIMSigner signer = new DKIMSigner("v=1; a=rsa-sha256; c=simple; d=messiah.edu; h=from; s=selector2; ab=x;", TestKeys.privateKey); + byte[] mail = "From: [email protected]\r\n\r\nbody\r\n".getBytes(StandardCharsets.US_ASCII); + String field = signer.sign(new ByteArrayInputStream(mail)); + + List<SignatureRecord> verified = verifier.verify(new SequenceInputStream( + new ByteArrayInputStream((field + "\r\n").getBytes(StandardCharsets.US_ASCII)), new ByteArrayInputStream(mail))); + + assertThat(verified).hasSize(1); + } } 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 6d54bf4..4312957 100644 --- a/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java +++ b/main/src/test/java/org/apache/james/jdkim/SignatureRecordImplTest.java @@ -279,4 +279,12 @@ public class SignatureRecordImplTest { assertThat(record.getBodyHash()).hasSize(32); assertThat(record.getSignature()).containsExactly(0); } + + @Test + public void toUnsignedStringShouldEmptyOnlyTheBTag() { + assertThat(new SignatureRecordImpl("v=1; a=rsa-sha256; d=example.com; s=s; h=from; bh=AA==; xb=1; b=AA==").toUnsignedString()) + .isEqualTo("v=1; a=rsa-sha256; d=example.com; s=s; h=from; bh=AA==; xb=1; b="); + assertThat(new SignatureRecordImpl("v=1; b = AA\r\n BB ; a=rsa-sha256; d=example.com; s=s; h=from:b=:x; bh=AA==").toUnsignedString()) + .isEqualTo("v=1; b =; a=rsa-sha256; d=example.com; s=s; h=from:b=:x; bh=AA=="); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
