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]
