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 6c3037f672e63bde97ec029809ca1c092e9aaabc
Author: Benoit TELLIER <[email protected]>
AuthorDate: Fri Oct 30 08:44:39 2026 +0100

    [FIX] Simple header canonicalization: hash the signature field name as 
written
    
    The DKIM-Signature field under verification was always hashed under
    the literal name "DKIM-Signature", so with c=simple a signature whose
    field is written "dkim-signature" (or with WSP before the colon) never
    verified. The verifier now hashes the field's own text up to the colon;
    the signer keeps the literal name, which it writes itself.
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../main/java/org/apache/james/jdkim/DKIMCommon.java    | 14 +++++++++++++-
 .../main/java/org/apache/james/jdkim/DKIMVerifier.java  |  8 +++++---
 main/src/test/java/org/apache/james/jdkim/DKIMTest.java | 17 +++++++++++++++++
 3 files changed, 35 insertions(+), 4 deletions(-)

diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java 
b/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java
index 72a1386..c504d43 100644
--- a/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java
+++ b/main/src/main/java/org/apache/james/jdkim/DKIMCommon.java
@@ -74,6 +74,18 @@ public abstract class DKIMCommon {
     protected static void signatureCheck(Headers h, SignatureRecord sign,
                                          List<CharSequence> headers, Signature 
signature)
             throws SignatureException, PermFailException {
+        signatureCheck(h, sign, headers, signature, "DKIM-Signature");
+    }
+
+    /**
+     * @param signatureFieldName the name of the DKIM-Signature field as 
written in
+     *                           the message, i.e. the text before its colon: 
simple
+     *                           canonicalization hashes it unchanged.
+     */
+    protected static void signatureCheck(Headers h, SignatureRecord sign,
+                                         List<CharSequence> headers, Signature 
signature,
+                                         String signatureFieldName)
+            throws SignatureException, PermFailException {
 
         boolean relaxedHeaders = 
SignatureRecord.RELAXED.equals(sign.getHeaderCanonicalisationMethod());
         if (!relaxedHeaders
@@ -106,7 +118,7 @@ public abstract class DKIMCommon {
             }
         }
 
-        String signatureStub = "DKIM-Signature:" + sign.toUnsignedString();
+        String signatureStub = signatureFieldName + ":" + 
sign.toUnsignedString();
         updateSignature(signature, relaxedHeaders, "dkim-signature", 
signatureStub);
     }
 
diff --git a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java 
b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java
index 086ccd1..9585369 100644
--- a/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java
+++ b/main/src/main/java/org/apache/james/jdkim/DKIMVerifier.java
@@ -316,7 +316,7 @@ public class DKIMVerifier {
                         signatureRecord.getBodyHash();
                         byte[] decoded = signatureRecord.getSignature();
                         signatureVerify(messageHeaders, signatureRecord, 
decoded,
-                                publicKeyRecord, signedHeadersList);
+                                publicKeyRecord, signedHeadersList, 
signatureField.substring(0, pos));
                     } catch (IllegalArgumentException e) {
                         throw new PermFailException("Invalid signature record: 
" + e.getMessage(), signatureRecord, e);
                     }
@@ -525,10 +525,12 @@ public class DKIMVerifier {
      * @param decoded the expected signature hash
      * @param key     the DKIM public key record
      * @param headers the list of signed headers
+     * @param signatureFieldName the name of the verified field, as written
      * @throws PermFailException If signature or public key validation fails
      */
     private void signatureVerify(Headers h, SignatureRecord sign,
-                                 byte[] decoded, PublicKeyRecord key, 
List<CharSequence> headers)
+                                 byte[] decoded, PublicKeyRecord key, 
List<CharSequence> headers,
+                                 String signatureFieldName)
             throws PermFailException {
         try {
             Signature signature = Signature.getInstance(sign.getHashMethod()
@@ -542,7 +544,7 @@ public class DKIMVerifier {
             }
             signature.initVerify(publicKey);
 
-            signatureCheck(h, sign, headers, signature);
+            signatureCheck(h, sign, headers, signature, signatureFieldName);
 
             if (!signature.verify(decoded))
                 throw new PermFailException("Header signature does not 
verify", sign);
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 1a3c219..3a30ca4 100644
--- a/main/src/test/java/org/apache/james/jdkim/DKIMTest.java
+++ b/main/src/test/java/org/apache/james/jdkim/DKIMTest.java
@@ -19,6 +19,7 @@
 
 package org.apache.james.jdkim;
 
+import java.security.MessageDigest;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
@@ -301,4 +302,20 @@ public class DKIMTest {
 
         assertThat(verified).hasSize(1);
     }
+
+    @Test
+    public void 
simpleCanonicalizationShouldKeepTheSignatureFieldNameAsWritten() throws 
Exception {
+        String body = "body\r\n";
+        String bh = Base64.getEncoder().encodeToString(
+                
MessageDigest.getInstance("SHA-256").digest(body.getBytes(StandardCharsets.US_ASCII)));
+        String tags = " b=; v=1; a=rsa-sha256; c=simple/simple; d=messiah.edu; 
s=selector2; h=from; bh=" + bh;
+        String from = "From: [email protected]";
+        Signature rsa = Signature.getInstance("SHA256withRSA");
+        rsa.initSign(TestKeys.privateKey);
+        rsa.update((from + "\r\n" + "dkim-signature:" + 
tags).getBytes(StandardCharsets.US_ASCII));
+        String b = Base64.getEncoder().encodeToString(rsa.sign());
+        String mail = "dkim-signature:" + tags.replace("b=;", "b=" + b + ";") 
+ "\r\n" + from + "\r\n\r\n" + body;
+
+        assertThat(verifier.verify(new 
ByteArrayInputStream(mail.getBytes(StandardCharsets.US_ASCII)))).hasSize(1);
+    }
 }


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

Reply via email to