Skip to content

Commit

Permalink
Merge pull request #251 from zkemail/optional_bh_check
Browse files Browse the repository at this point in the history
Add skipBodyHash check flag in verifyDkimSignature function
  • Loading branch information
Divide-By-0 authored Feb 19, 2025
2 parents 394f40f + e5b47e8 commit b193cf0
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 6 deletions.
2 changes: 1 addition & 1 deletion packages/helpers/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zk-email/helpers",
"version": "6.3.2",
"version": "6.4.2",
"license": "MIT",
"main": "dist",
"scripts": {
Expand Down
8 changes: 6 additions & 2 deletions packages/helpers/src/dkim/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,24 +30,26 @@ export interface DKIMVerificationResult {
* @param enableSanitization If true, email will be applied with various sanitization to try and pass DKIM verification
* @param fallbackToZKEmailDNSArchive If true, ZK Email DNS Archive (https://archive.prove.email/api-explorer) will
* be used to resolve DKIM public keys if we cannot resolve from HTTP DNS
* @param skipBodyHash If true, it bypass the dkim body hash check
* @returns
*/
export async function verifyDKIMSignature(
email: Buffer | string,
domain: string = '',
enableSanitization: boolean = true,
fallbackToZKEmailDNSArchive: boolean = false,
skipBodyHash = false,
): Promise<DKIMVerificationResult> {
const emailStr = email.toString();

let dkimResult = await tryVerifyDKIM(email, domain, fallbackToZKEmailDNSArchive);
let dkimResult = await tryVerifyDKIM(email, domain, fallbackToZKEmailDNSArchive, skipBodyHash);

// If DKIM verification fails, try again after sanitizing email
let appliedSanitization;
if (dkimResult.status.comment === 'bad signature' && enableSanitization) {
const results = await Promise.all(
sanitizers.map((sanitize) =>
tryVerifyDKIM(sanitize(emailStr), domain, fallbackToZKEmailDNSArchive).then((result) => ({
tryVerifyDKIM(sanitize(emailStr), domain, fallbackToZKEmailDNSArchive, skipBodyHash).then((result) => ({
result,
sanitizer: sanitize.name,
})),
Expand Down Expand Up @@ -98,6 +100,7 @@ async function tryVerifyDKIM(
email: Buffer | string,
domain: string = '',
fallbackToZKEmailDNSArchive: boolean = false,
skipBodyHash = false,
) {
const resolver = async (name: string, type: string) => {
try {
Expand All @@ -115,6 +118,7 @@ async function tryVerifyDKIM(

const dkimVerifier = new DkimVerifier({
resolver,
skipBodyHash,
});

await writeToStream(dkimVerifier, email as any);
Expand Down
9 changes: 6 additions & 3 deletions packages/helpers/src/lib/mailauth/dkim-verifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,14 @@ export class DkimVerifier extends MessageParser {
private arc: { chain: false };
private seal: { bodyHash: string };
private sealBodyHashKey: string = '';
private skipBodyHash: boolean = false;
constructor(options: Record<string, any>) {
super();

this.options = options || {};
this.resolver = this.options.resolver;
this.minBitLength = this.options.minBitLength;
this.skipBodyHash = this.options.skipBodyHash

this.results = [];

Expand Down Expand Up @@ -180,7 +182,7 @@ export class DkimVerifier extends MessageParser {

async finalChunk() {
try {
if (!this.headers || !this.bodyHashes.size) {
if (!this.headers || (!this.skipBodyHash && !this.bodyHashes.size)) {
return;
}

Expand Down Expand Up @@ -242,7 +244,7 @@ export class DkimVerifier extends MessageParser {

let bodyHashObj = this.bodyHashes.get(signatureHeader.bodyHashKey);
let bodyHash = bodyHashObj?.hash;
if (signatureHeader.parsed?.bh?.value !== bodyHash) {
if ((signatureHeader.parsed?.bh?.value !== bodyHash) && !this.skipBodyHash) {
status.result = 'neutral';
status.comment = `body hash did not verify`;
} else {
Expand Down Expand Up @@ -332,7 +334,8 @@ export class DkimVerifier extends MessageParser {

if (
typeof signatureHeader.maxBodyLength === 'number' &&
signatureHeader.maxBodyLength !== signatureHeader.bodyHashedBytes
signatureHeader.maxBodyLength !== signatureHeader.bodyHashedBytes &&
!this.skipBodyHash
) {
status.result = 'fail';
status.comment = `invalid body length ${signatureHeader.bodyHashedBytes}`;
Expand Down
22 changes: 22 additions & 0 deletions packages/helpers/tests/dkim.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,28 @@ describe('DKIM signature verification', () => {
expect(e.message).toBe('DKIM signature not found for domain domain.com');
}
});

it('should skip body-hash verification for body-less emails', async () => {
// From address domain is icloud.com
const email = fs.readFileSync(path.join(__dirname, 'test-data/email-bodyless.eml'));

// Should pass with default domain
const result = await verifyDKIMSignature(email, "", true, false, true);
expect.assertions(1);
expect(result.signingDomain).toBe('icloud.com');
});

it('should pass for tampered body if skipBodyHash=true', async () => {
const email = fs.readFileSync(path.join(__dirname, 'test-data/email-body-tampered.eml'));

try {
await verifyDKIMSignature(email, '', true, false, true);
} catch (e) {
expect(e.message).toBe(
'DKIM signature verification failed for domain icloud.com. Reason: body hash did not verify',
);
}
});
});

it('should fallback to ZK Email Archive if DNS over HTTP fails', async () => {
Expand Down
15 changes: 15 additions & 0 deletions packages/helpers/tests/test-data/email-bodyless.eml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=icloud.com; s=1a1hai; t=1693038337; bh=7xQMDuoVVU4m0W0WRVSrVXMeGSIASsnucK9dJsrc+vU=; h=from:Content-Type:Mime-Version:Subject:Message-Id:Date:to; b=EhLyVPpKD7d2/+h1nrnu+iEEBDfh6UWiAf9Y5UK+aPNLt3fAyEKw6Ic46v32NOcZD
M/zhXWucN0FXNiS0pz/QVIEy8Bcdy7eBZA0QA1fp8x5x5SugDELSRobQNbkOjBg7Mx
VXy7h4pKZMm/hKyhvMZXK4AX9fSoXZt4VGlAFymFNavfdAeKgg/SHXLds4lOPJV1wR
2E21g853iz5m/INq3uK6SQKzTnz/wDkdyiq90gC0tHQe8HpDRhPIqgL5KSEpuvUYmJ
wjEOwwHqP6L3JfEeROOt6wyuB1ah7wgRvoABOJ81+qLYRn3bxF+y1BC+PwFd5yFWH5
Ry43lwp1/3+sA==
from: runnier.leagues.0j@icloud.com
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit
Mime-Version: 1.0 (Mac OS X Mail 16.0 \(3731.500.231\))
Subject: Hello
Message-Id: <8F819D32-B6AC-489D-977F-438BBC4CAB27@me.com>
Date: Sat, 26 Aug 2023 12:25:22 +0400
to: zkewtest@gmail.com

0 comments on commit b193cf0

Please sign in to comment.