mirror of
https://github.com/zkemail/zk-email-verify.git
synced 2026-01-07 20:54:05 -05:00
Merge pull request #251 from zkemail/optional_bh_check
Add skipBodyHash check flag in verifyDkimSignature function
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@zk-email/helpers",
|
||||
"version": "6.3.2",
|
||||
"version": "6.4.2",
|
||||
"license": "MIT",
|
||||
"main": "dist",
|
||||
"scripts": {
|
||||
|
||||
@@ -30,6 +30,7 @@ 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(
|
||||
@@ -37,17 +38,18 @@ export async function verifyDKIMSignature(
|
||||
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,
|
||||
})),
|
||||
@@ -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 {
|
||||
@@ -115,6 +118,7 @@ async function tryVerifyDKIM(
|
||||
|
||||
const dkimVerifier = new DkimVerifier({
|
||||
resolver,
|
||||
skipBodyHash,
|
||||
});
|
||||
|
||||
await writeToStream(dkimVerifier, email as any);
|
||||
|
||||
@@ -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 = [];
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -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}`;
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
15
packages/helpers/tests/test-data/email-bodyless.eml
Normal file
15
packages/helpers/tests/test-data/email-bodyless.eml
Normal file
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user