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]
