Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7e0004d
First commit
agrinchenko Oct 7, 2025
508fbc2
Update arc/src/main/java/org/apache/james/arc/ArcSealVerifyData.java
agrinchenko Oct 8, 2025
0718f68
Update arc/src/main/java/org/apache/james/arc/ARCChainValidator.java
agrinchenko Oct 8, 2025
353434e
Update arc/src/main/java/org/apache/james/arc/ARCChainValidator.java
agrinchenko Oct 8, 2025
2bdeeca
Update arc/src/main/java/org/apache/james/arc/ARCChainValidator.java
agrinchenko Oct 8, 2025
ce7b5ba
Refactor ARC to minimize dependencies and address some initial PR fee…
agrinchenko Oct 9, 2025
28386ff
Rework DMARK and reduce dependencies
agrinchenko Oct 17, 2025
471e608
Adding DMARC pom.xml that was missing in the previous commit
agrinchenko Oct 17, 2025
620e388
Remove maven compiler plugin in DMARC pom.xml causing build to fail
agrinchenko Oct 17, 2025
9714b4e
Add to the maven-compiler-plugin in DMARC pom.xml to fix the auto bu…
agrinchenko Oct 17, 2025
016bed6
Add to the maven-compiler-plugin in ARC pom.xml to fix the auto buil…
agrinchenko Oct 17, 2025
16c1fdf
Updating ARC pom to make sure it can see DMARC test dependencies on J…
agrinchenko Oct 17, 2025
dbe3301
Fixing the Jenkins build. Realigning to JDK 11
agrinchenko Oct 17, 2025
c8a688b
Add missing license info. Reorder 'dmarc' and 'arc' modules in the pa…
agrinchenko Oct 20, 2025
3d75e62
Rewrote PSL matching to handle exceptions/wildcards plus:
agrinchenko Nov 4, 2025
3bddd76
Updated README to reflect the purpose of the fork
agrinchenko Mar 27, 2026
bdca306
Update README.adoc
agrinchenko Mar 27, 2026
6e69d2a
Fix site build and ARC/DMARC test support
agrinchenko Mar 28, 2026
2fa964f
Add cv_fail_i1_ams_invalid test: assert cv=fail when ARC-Message-Sign…
agrinchenko Apr 16, 2026
399e245
Add cv_fail_i1_as_invalid test: assert cv=fail when ARC-Seal b= is cr…
agrinchenko Apr 17, 2026
101c79c
Fix ARCChainValidator to return cv=fail instead of throwing when ARC …
agrinchenko Apr 17, 2026
aa008e5
Add 10 cv_fail_i2_* tests: assert cv=fail for all structural and sign…
agrinchenko Apr 17, 2026
db78f24
Fix a bug in ARC-Seal to cover all prior hop headers as per RFC 8617;…
agrinchenko Apr 17, 2026
d2eba43
Add AMS structural validation tests and fix uncaught exceptions for m…
agrinchenko Apr 17, 2026
af9ee17
Add AMS tag format validation tests and fix null-safe DNS result hand…
agrinchenko Apr 17, 2026
0f048c2
Add canonicalization tests for body whitespace and header normalization
agrinchenko Apr 17, 2026
e3d8df4
Add ARC AMS formatting and duplicate instance tests:
agrinchenko Apr 17, 2026
c9fd4d1
Expand ARC validation coverage and fix header parsing:
agrinchenko Apr 18, 2026
813fe12
Add ARC-Seal field validation tests:
agrinchenko Apr 18, 2026
a158e3c
Add ARC-Seal b= validation test coverage:
agrinchenko Apr 18, 2026
520d504
Add the ARC-Seal b= group test cases:
agrinchenko Apr 18, 2026
280356f
Added more AMS field validation coverage:
agrinchenko Apr 18, 2026
8be878e
Add ARC body hash and key fixture coverage:
agrinchenko Apr 18, 2026
b6baca4
Added ValiMail coverage tests in ARCTest for "c=" edge cases:
agrinchenko Apr 18, 2026
99d1325
Added AMS tag validation in ARCVerifier:
agrinchenko Apr 18, 2026
02bef53
Add remaining ARC h= and first-hop signing test coverage:
agrinchenko Apr 18, 2026
3fa047f
Harden ARC validation and replace bundled PSL:
agrinchenko Apr 26, 2026
510f6be
Address ARC and DMARC review hardening
agrinchenko Apr 27, 2026
7723098
Update dmarc/src/main/java/org/apache/james/dmarc/DMARCVerifier.java
agrinchenko Apr 29, 2026
39a6531
Harden ARC timestamp and instance validation:
agrinchenko Apr 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 134 additions & 11 deletions README.adoc
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
= JAMES jDKIM library


Library dealing with parsing and crytography to sign and verify DKIM signatures.
Library dealing with parsing and cryptography to sign and verify DKIM signatures.
It also provides DMARC verification and ARC (Authenticated Received Chain)
support for Java-based mail processing workflows.

The mailet has been moved to James project: https://github.com/apache/james-project/tree/master/server/mailet/dkim

Expand Down Expand Up @@ -56,33 +58,154 @@ List<SignatureRecord> verifiedSignatures = verifier.verify(stream);
List<Result> results = verifier.getResults();
----

=== Checking DMARC

DMARC verification combines SPF and DKIM results with the RFC5322 `From`
domain and the domain DMARC policy.

[source,java]
----
import org.apache.james.dmarc.DMARCVerifier;
import org.apache.james.dmarc.DmarcValidationResult;
import org.apache.james.dmarc.PublicKeyRecordRetrieverDmarc;
import org.apache.james.mime4j.dom.Message;

PublicKeyRecordRetrieverDmarc recordRetriever = null;
Message message = null;

DMARCVerifier dmarcVerifier = new DMARCVerifier(recordRetriever);
DmarcValidationResult dmarcResult = dmarcVerifier.runDmarcCheck(
message,
"pass client-ip=192.0.2.1; envelope-from=sender@example.org",
"example.org",
"pass",
"example.org");
Copy link
Copy Markdown
Contributor

@chibenwa chibenwa Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is a lot of string arguments that I would be annoyend to distinguish from each other and to actually come up with. Suggestion: syntactic sugar

// I know no record but for interface writing the corresponding POJO is worth it.
enum DkimStatus  {FAIL, PASS, NEUTRAL} // better discoverability that strings ! We can have a convenience parse method to bridge the gap, and getters.
record DmarcDataInput(Message message,  String spfHeaderText, String  spfDomain, String dkimResult, String dkimDomain);

// Then we could play with builders

dmarcVerifier.verify(DmarcDataInput.builder()
   .message(message)
   .spfHeaderValue("pass client-ip=192.0.2.1; envelope-from=sender@example.org")
   .ehlo("exemple.org")
   .dkimResult(PASS)
   .dkimDToken("example.org")
   .build());

Would already be a nice enhancement.

We could have a typed alternative for SPF. Also We likely should offer a wauy to work directly with DKIM signature records.

dmarcVerifier.verify(DmarcDataInput.builder()
   .message(message)
   .spfResult(spfResult)
   .ehlo("exemple.org") // This eases the user in understanding how to get that horrible SPF string and not get it himself
   .dkimSignatures(signatureRecords) // Again we would offweight the user from dealing himself with interpreting the DKIM results -> DMARC job. Type: List<SignatureRecord>
   .build());

To be honnest I do not care (here) about the internal, just about the outer layers. IMO we can easily do the last kilometer to make it easy to use.

Does this make sense?


String authenticationResult = dmarcResult.toString();
----

More complete DMARC examples can be found in
https://github.com/apache/james-jdkim/blob/master/dmarc/src/main/test/java/org/apache/james/dmarc/DMARCTest.java[DMARCTest].

== ARC Support

ARC support adds RFC 8617 signing and validation for intermediaries that need to
preserve authentication results across forwarding hops.

=== Building An ARC Set

Generating ARC headers for a MIME message can be achieved using the following
snippet.

[source,java]
----
import java.io.InputStream;
import java.security.PrivateKey;
import java.util.Map;

import org.apache.james.arc.ArcSetBuilder;
import org.apache.james.arc.PublicKeyRetrieverArc;
import org.apache.james.mime4j.dom.Message;
import org.apache.james.mime4j.message.DefaultMessageBuilder;

String amsTemplate = "i=; a=rsa-sha256; c=relaxed/relaxed; d=example.org; s=arc; t=; h=Subject:From:To; bh=; b=";
String sealTemplate = "i=; cv=; a=rsa-sha256; d=example.org; s=arc; t=; b=";

PrivateKey privateKey = null;
InputStream stream = null;
Message message = new DefaultMessageBuilder().parseMessage(stream);

ArcSetBuilder arcSetBuilder = new ArcSetBuilder(
privateKey,
amsTemplate,
sealTemplate,
"mx.example.org",
System.currentTimeMillis() / 1000);

PublicKeyRetrieverArc keyRecordRetriever = null;
Map<String, String> arcSet = arcSetBuilder.buildArcSet(
message,
"mail.example.org",
"sender@example.org",
"192.0.2.1",
keyRecordRetriever);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here again some interface cosmetic fixes would go a long way. Same principal: builders, and POJO extracted...

Suggested change
keyRecordRetriever);
ArcSetBuilder arcSetBuilder = new ArcSetBuilder(
privateKey,
keyRecordRetriever,
amsTemplate,
sealTemplate,
"mx.example.org",
System.currentTimeMillis() / 1000);
PublicKeyRetrieverArc keyRecordRetriever = null;
record ArcSigningInput(Message message, String helo, String mailFrom, String ip);
Map<String, String> arcSet = arcSetBuilder.buildArcSet(ArcSigningInput.builder()
.message(message)
.ehlo("mail.example.org")
.mailFrom("sender@example.org")
.ip("192.0.2.1")
.build());
  1. IMO keyRecordRetriever is better handled as a class field as there is as far as I
  2. Builder.


String authenticationResults = arcSet.get("Authentication-Results");
String arcAuthenticationResults = arcSet.get("ARC-Authentication-Results");
String arcMessageSignature = arcSet.get("ARC-Message-Signature");
String arcSeal = arcSet.get("ARC-Seal");
----

The generated map contains the ARC headers for the current hop, ready to be
added to the message.

=== Validating An ARC Chain

Validating ARC headers on a MIME message can be achieved using the following
snippet.

[source,java]
----
import java.io.InputStream;

import org.apache.james.arc.ARCChainValidator;
import org.apache.james.arc.ArcValidationOutcome;
import org.apache.james.arc.PublicKeyRetrieverArc;
import org.apache.james.mime4j.dom.Message;
import org.apache.james.mime4j.message.DefaultMessageBuilder;

PublicKeyRetrieverArc keyRecordRetriever = null;
InputStream stream = null;
Message message = new DefaultMessageBuilder().parseMessage(stream);

ARCChainValidator arcChainValidator = new ARCChainValidator(keyRecordRetriever);
ArcValidationOutcome validation = arcChainValidator.validateArcChain(message);

String chainValidation = validation.getResult().toString();
String description = validation.getDescription();
----

More complete ARC usage can be found in
https://github.com/apache/james-jdkim/blob/master/arc/src/test/java/org/apache/james/arc/ARCTest.java[ARCTest].

== Test Coverage

ARC functionality is covered by tests in
https://github.com/apache/james-jdkim/blob/master/arc/src/test/java/org/apache/james/arc/ARCTest.java[ARCTest].
The coverage is based on the ARC protocol requirements from
https://datatracker.ietf.org/doc/html/rfc8617[RFC 8617] and on the public
https://github.com/ValiMail/arc_test_suite[ValiMail ARC test suite]. The tests
exercise ARC set creation, chain validation, ARC-Seal verification,
ARC-Message-Signature canonicalization, body hash handling, required tag
validation, and malformed or tampered ARC header cases.

== Cryptography Notice

----
This distribution includes cryptographic software. The country in
which you currently reside may have restrictions on the import,
possession, use, and/or re-export to another country, of
encryption software. BEFORE using any encryption software, please
This distribution includes cryptographic software. The country in
which you currently reside may have restrictions on the import,
possession, use, and/or re-export to another country, of
encryption software. BEFORE using any encryption software, please
check your country's laws, regulations and policies concerning the
import, possession, or use, and re-export of encryption software, to
import, possession, or use, and re-export of encryption software, to
see if this is permitted. See http://www.wassenaar.org for more
information.

The U.S. Government Department of Commerce, Bureau of Industry and
Security (BIS), has classified this software as Export Commodity
Security (BIS), has classified this software as Export Commodity
Control Number (ECCN) 5D002.C.1, which includes information security
software using or performing cryptographic functions with asymmetric
algorithms. The form and manner of this Apache Software Foundation
distribution makes it eligible for export under the License Exception
ENC Technology Software Unrestricted (TSU) exception (see the BIS
Export Administration Regulations, Section 740.13) for both object
ENC Technology Software Unrestricted (TSU) exception (see the BIS
Export Administration Regulations, Section 740.13) for both object
code and source code.

The following provides more details on the included cryptographic
software:

- jDKIM includes code designed to work with Java SE Security

Export classifications and source links can be found
at http://www.apache.org/licenses/exports/.
----
----
85 changes: 85 additions & 0 deletions arc/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<artifactId>apache-jdkim-project</artifactId>
<groupId>org.apache.james.jdkim</groupId>
<version>0.6-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

<artifactId>apache-arc-library</artifactId>

<name>Apache James :: ARC</name>
<description>A Java implementation for the ARC specification.</description>
<url>http://james.apache.org/jdkim/main/</url>
<inceptionYear>2008</inceptionYear>

<dependencies>
<dependency>
<groupId>org.apache.james.jdkim</groupId>
<artifactId>apache-dmarc-library</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.james.jdkim</groupId>
<artifactId>apache-dmarc-library</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.james.jdkim</groupId>
<artifactId>apache-jdkim-library</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.james.jdkim</groupId>
<artifactId>apache-jdkim-library</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.james.jspf</groupId>
<artifactId>apache-jspf-resolver</artifactId>
<version>${jspf-resolver.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>org.apache.james</groupId>
<artifactId>apache-mime4j-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.james</groupId>
<artifactId>apache-mime4j-dom</artifactId>
</dependency>
<dependency>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
Loading