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 d9046a5130c20ca7a86f30ad8fb705c22a957f97
Author: Benoit TELLIER <[email protected]>
AuthorDate: Tue Nov 3 08:39:36 2026 +0100

    [FIX] VerifierOptions: check only From for unsigned duplicates by default
    
    Rejecting by default a signature that leaves an instance of any RFC 5322
    single-instance field unsigned turned signatures valid under RFC 6376
    into PERMERROR: a message with two To: or References: fields, h= listing
    it once, failed, and a receiver answering that at SMTP refused
    legitimate mail. The default list is now From, the field RFC 6376
    section 8.15 is about; RFC_5322_SINGLE_INSTANCE_FIELDS opts into the
    full list.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 docs/modules/ROOT/pages/usage.adoc                    | 12 ++++++++----
 .../org/apache/james/jdkim/api/VerifierOptions.java   | 14 +++++++++++---
 .../test/java/org/apache/james/jdkim/DKIMTest.java    | 19 +++++++++++++++++--
 .../apache/james/jdkim/DKIMVerifierOptionsTest.java   |  4 ++--
 4 files changed, 38 insertions(+), 11 deletions(-)

diff --git a/docs/modules/ROOT/pages/usage.adoc 
b/docs/modules/ROOT/pages/usage.adoc
index 2ef4adf..2245155 100644
--- a/docs/modules/ROOT/pages/usage.adoc
+++ b/docs/modules/ROOT/pages/usage.adoc
@@ -250,10 +250,14 @@ Defaults to the 
`james.dkim.unsigned-duplicate-field.policy` system property, or
 than it appears (`h=from:from`).
 
 `withSingleInstanceFields(Collection<String>)`::
-The field names checked by the policy above, case-insensitive. Defaults to the
-fields https://datatracker.ietf.org/doc/html/rfc5322#section-3.6[RFC 5322 
section 3.6]
-allows once: `from`, `sender`, `reply-to`, `to`, `cc`, `bcc`, `subject`, 
`date`,
-`message-id`, `in-reply-to`, `references`.
+The field names checked by the policy above, case-insensitive. Defaults to
+`from` only, the field RFC 6376 section 8.15 is about.
+`VerifierOptions.RFC_5322_SINGLE_INSTANCE_FIELDS` holds every field
+https://datatracker.ietf.org/doc/html/rfc5322#section-3.6[RFC 5322 section 3.6]
+allows once (`from`, `sender`, `reply-to`, `to`, `cc`, `bcc`, `subject`, 
`date`,
+`message-id`, `in-reply-to`, `references`): real-world mail sometimes 
duplicates
+`To:`, `Cc:` or `References:`, and such a signature is valid under RFC 6376, so
+rejecting it with this list refuses legitimate mail.
 
 `withDnsResolver(Resolver)`::
 A dnsjava `Resolver` to query instead of the system default -- useful to point
diff --git a/main/src/main/java/org/apache/james/jdkim/api/VerifierOptions.java 
b/main/src/main/java/org/apache/james/jdkim/api/VerifierOptions.java
index ec14b9d..3c9dff9 100644
--- a/main/src/main/java/org/apache/james/jdkim/api/VerifierOptions.java
+++ b/main/src/main/java/org/apache/james/jdkim/api/VerifierOptions.java
@@ -53,10 +53,17 @@ public class VerifierOptions {
     public static final String UNSIGNED_DUPLICATE_FIELD_POLICY_PROPERTY = 
"james.dkim.unsigned-duplicate-field.policy";
     private static final UnsignedDuplicateFieldPolicy 
DEFAULT_UNSIGNED_DUPLICATE_FIELD_POLICY = UnsignedDuplicateFieldPolicy.REJECT;
     /**
-     * Default of {@link Builder#withSingleInstanceFields(Collection)}: the 
fields RFC 5322 section 3.6 allows once.
+     * The fields RFC 5322 section 3.6 allows once, for {@link 
Builder#withSingleInstanceFields(Collection)}.
+     * Real-world mail often duplicates some of them (To, Cc, References...) 
and a signature leaving the
+     * extra instance unsigned is still valid under RFC 6376: rejecting it 
refuses legitimate mail.
      */
-    public static final Set<String> DEFAULT_SINGLE_INSTANCE_FIELDS = 
Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
+    public static final Set<String> RFC_5322_SINGLE_INSTANCE_FIELDS = 
Collections.unmodifiableSet(new LinkedHashSet<>(Arrays.asList(
             "from", "sender", "reply-to", "to", "cc", "bcc", "subject", 
"date", "message-id", "in-reply-to", "references")));
+    /**
+     * Default of {@link Builder#withSingleInstanceFields(Collection)}: From, 
the field RFC 6376 section 8.15
+     * is about and the one DMARC relies on.
+     */
+    public static final Set<String> DEFAULT_SINGLE_INSTANCE_FIELDS = 
Collections.singleton("from");
 
     private final Duration clockDriftTolerance;
     private final int maxSignatures;
@@ -161,7 +168,8 @@ public class VerifierOptions {
         /**
          * Sets the names of the fields expected once per message, checked by
          * {@link 
#withUnsignedDuplicateFieldPolicy(UnsignedDuplicateFieldPolicy)}. 
Case-insensitive.
-         * Defaults to the fields RFC 5322 section 3.6 allows once ({@link 
#DEFAULT_SINGLE_INSTANCE_FIELDS}).
+         * Defaults to From only ({@link #DEFAULT_SINGLE_INSTANCE_FIELDS}): 
{@link #RFC_5322_SINGLE_INSTANCE_FIELDS}
+         * checks every field RFC 5322 allows once, at the cost of failing 
legitimate mail duplicating one of them.
          *
          * @param singleInstanceFields field names
          * @return {@link Builder}
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 c97abde..2575404 100644
--- a/main/src/test/java/org/apache/james/jdkim/DKIMTest.java
+++ b/main/src/test/java/org/apache/james/jdkim/DKIMTest.java
@@ -603,10 +603,25 @@ public class DKIMTest {
         String signature = signer.sign(new 
ByteArrayInputStream(message.getBytes(StandardCharsets.US_ASCII)));
         String replayed = "Reply-To: [email protected]\r\n" + signature 
+ "\r\n" + message;
 
-        assertThatThrownBy(() -> verifier.verify(new 
ByteArrayInputStream(replayed.getBytes(StandardCharsets.US_ASCII))))
+        DKIMVerifier rfc5322Verifier = new DKIMVerifier(new 
VerifierOptions.Builder()
+                .withPublicKeyRecordRetriever(keyRecordRetriever)
+                
.withSingleInstanceFields(VerifierOptions.RFC_5322_SINGLE_INSTANCE_FIELDS)
+                .build());
+
+        assertThatThrownBy(() -> rfc5322Verifier.verify(new 
ByteArrayInputStream(replayed.getBytes(StandardCharsets.US_ASCII))))
                 .isInstanceOf(FailException.class)
                 .hasMessageContaining("2 reply-to fields");
-        assertThat(verifier.hasAnyValidSignature()).isFalse();
+        assertThat(rfc5322Verifier.hasAnyValidSignature()).isFalse();
+    }
+
+    @Test
+    public void duplicatedToFieldSignedOnceShouldPassByDefault() throws 
Exception {
+        String message = "From: [email protected]\r\nTo: 
[email protected]\r\nTo: [email protected]\r\nSubject: hello\r\n\r\nbody\r\n";
+        DKIMSigner signer = new DKIMSigner("v=1; a=rsa-sha256; c=simple; 
d=messiah.edu; h=from:to:subject; q=dns/txt; s=selector2;", 
TestKeys.privateKey);
+        String signature = signer.sign(new 
ByteArrayInputStream(message.getBytes(StandardCharsets.US_ASCII)));
+        String signed = signature + "\r\n" + message;
+
+        assertThat(verifier.verify(new 
ByteArrayInputStream(signed.getBytes(StandardCharsets.US_ASCII)))).hasSize(1);
     }
 
     @Test
diff --git 
a/main/src/test/java/org/apache/james/jdkim/DKIMVerifierOptionsTest.java 
b/main/src/test/java/org/apache/james/jdkim/DKIMVerifierOptionsTest.java
index 9c1c396..5ef43f0 100644
--- a/main/src/test/java/org/apache/james/jdkim/DKIMVerifierOptionsTest.java
+++ b/main/src/test/java/org/apache/james/jdkim/DKIMVerifierOptionsTest.java
@@ -33,6 +33,7 @@ import java.net.InetSocketAddress;
 import java.net.UnknownHostException;
 import java.time.Duration;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.LinkedHashSet;
 
 import static org.junit.Assert.assertEquals;
@@ -139,8 +140,7 @@ public class DKIMVerifierOptionsTest {
     public void shouldRejectUnsignedDuplicateFieldsByDefault() {
         VerifierOptions opt = new VerifierOptions.Builder().build();
         assertEquals(UnsignedDuplicateFieldPolicy.REJECT, 
opt.getUnsignedDuplicateFieldPolicy());
-        assertEquals(new LinkedHashSet<>(Arrays.asList("from", "sender", 
"reply-to", "to", "cc", "bcc", "subject",
-                "date", "message-id", "in-reply-to", "references")), 
opt.getSingleInstanceFields());
+        assertEquals(Collections.singleton("from"), 
opt.getSingleInstanceFields());
     }
 
     @Test


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

Reply via email to