Skip to content

Commit

Permalink
fix(dkim): Added new output property mimeStructureStart
Browse files Browse the repository at this point in the history
  • Loading branch information
andris9 committed Feb 12, 2024
1 parent 68a3a5c commit 8f25353
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 4 deletions.
7 changes: 4 additions & 3 deletions examples/authenticate.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@ const fs = require('fs');
const main = async () => {
let message = await fs.promises.readFile(process.argv[2] || __dirname + '/../test/fixtures/message4.eml');
let res = await authenticate(message, {
ip: '217.146.67.33',
helo: 'uvn-67-33.tll01.zonevs.eu',
//ip: '217.146.67.33',
//helo: 'uvn-67-33.tll01.zonevs.eu',
mta: 'mx.ethereal.email',
sender: '[email protected]',
//sender: '[email protected]',
trustReceived: true,
// optional. add ARC seal if possible
seal: {
signingDomain: 'tahvel.info',
Expand Down
16 changes: 16 additions & 0 deletions lib/dkim/body/relaxed.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
'use strict';

const crypto = require('crypto');
const { MimeStructureStartFinder } = require('../mime-structure-start-finder');

const CHAR_CR = 0x0d;
const CHAR_LF = 0x0a;
Expand Down Expand Up @@ -39,9 +40,20 @@ class RelaxedHash {
this.maxSizeReached = maxBodyLength === 0;

this.emptyLinesQueue = [];

this.mimeStructureStartFinder = new MimeStructureStartFinder();
}

setContentType(contentTypeObj) {
if (/^multipart\//i.test(contentTypeObj.value) && contentTypeObj.params.boundary) {
this.mimeStructureStartFinder.setBoundary(contentTypeObj.params.boundary);
}
}

_updateBodyHash(chunk) {
// serach through the entire document, not just signed part
this.mimeStructureStartFinder.update(chunk);

this.canonicalizedLength += chunk.length;

if (this.maxSizeReached) {
Expand Down Expand Up @@ -270,6 +282,10 @@ class RelaxedHash {
// finalize
return this.bodyHash.digest(encoding);
}

getMimeStructureStart() {
return this.mimeStructureStartFinder.getMimeStructureStart();
}
}

module.exports = { RelaxedHash };
16 changes: 16 additions & 0 deletions lib/dkim/body/simple.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use strict';

const crypto = require('crypto');
const { MimeStructureStartFinder } = require('../mime-structure-start-finder');

/**
* Class for calculating body hash of an email message body stream
Expand Down Expand Up @@ -30,9 +31,20 @@ class SimpleHash {
this.maxSizeReached = maxBodyLength === 0;

this.lastNewline = false;

this.mimeStructureStartFinder = new MimeStructureStartFinder();
}

setContentType(contentTypeObj) {
if (/^multipart\//i.test(contentTypeObj.value) && contentTypeObj.params.boundary) {
this.mimeStructureStartFinder.setBoundary(contentTypeObj.params.boundary);
}
}

_updateBodyHash(chunk) {
// serach through the entire document, not just signed part
this.mimeStructureStartFinder.update(chunk);

this.canonicalizedLength += chunk.length;

if (this.maxSizeReached) {
Expand Down Expand Up @@ -115,6 +127,10 @@ class SimpleHash {

return this.bodyHash.digest(encoding);
}

getMimeStructureStart() {
return this.mimeStructureStartFinder.getMimeStructureStart();
}
}

module.exports = { SimpleHash };
22 changes: 21 additions & 1 deletion lib/dkim/dkim-verifier.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const { getARChain } = require('../arc');
const addressparser = require('nodemailer/lib/addressparser');
const crypto = require('crypto');
const { v4: uuidv4 } = require('uuid');
const libmime = require('libmime');

class DkimVerifier extends MessageParser {
constructor(options) {
Expand Down Expand Up @@ -147,6 +148,18 @@ class DkimVerifier extends MessageParser {
if (!this.bodyHashes.has(signatureHeader.bodyHashKey)) {
this.bodyHashes.set(signatureHeader.bodyHashKey, dkimBody(signatureHeader.bodyCanon, signatureHeader.hashAlgo, signatureHeader.maxBodyLength));
}

let contentTypeHeader = this.headers.parsed.findLast(header => header.key === 'content-type');
if (contentTypeHeader) {
let line = contentTypeHeader.line.toString();
if (line.indexOf(':') >= 0) {
line = line.substring(line.indexOf(':') + 1).trim();
}
const parsedContentType = libmime.parseHeaderValue(line);
for (let hasher of this.bodyHashes.values()) {
hasher.setContentType(parsedContentType);
}
}
}
}

Expand All @@ -165,6 +178,7 @@ class DkimVerifier extends MessageParser {
// convert bodyHashes from hash objects to base64 strings
for (let [key, bodyHash] of this.bodyHashes.entries()) {
this.bodyHashes.get(key).hash = bodyHash.digest('base64');
this.bodyHashes.get(key).mimeStructureStart = bodyHash.getMimeStructureStart();
}

for (let signatureHeader of this.signatureHeaders) {
Expand Down Expand Up @@ -210,7 +224,9 @@ class DkimVerifier extends MessageParser {
: false;
}

let bodyHash = this.bodyHashes.get(signatureHeader.bodyHashKey)?.hash;
const bodyHash = this.bodyHashes.get(signatureHeader.bodyHashKey)?.hash;
const mimeStructureStart = this.bodyHashes.get(signatureHeader.bodyHashKey)?.mimeStructureStart;

if (signatureHeader.parsed?.bh?.value !== bodyHash) {
status.result = 'neutral';
status.comment = `body hash did not verify`;
Expand Down Expand Up @@ -344,6 +360,10 @@ class DkimVerifier extends MessageParser {
result.canonBodyLengthLimited = false;
}

if (typeof mimeStructureStart === 'number') {
result.mimeStructureStart = mimeStructureStart;
}

if (publicKey) {
result.publicKey = publicKey.toString();
}
Expand Down
85 changes: 85 additions & 0 deletions lib/dkim/mime-structure-start-finder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
'use strict';

class MimeStructureStartFinder {
constructor() {
this.byteCache = [];

this.matchFound = false;
this.noMatch = false;
this.lineStart = -1;

this.prevChunks = 0;

this.mimeStructureStart = -1;
}

setBoundary(boundary) {
this.boundary = (boundary || '').toString().trim();

this.boundaryBuf = Array.from(Buffer.from(`--${this.boundary}`));
this.boundaryBufLen = this.boundaryBuf.length;
}

update(chunk) {
if (this.matchFound || !this.boundary) {
return;
}

for (let i = 0, bufLen = chunk.length; i < bufLen; i++) {
let c = chunk[i];

// check ending
if (c === 0x0a || c === 0x0d) {
if (!this.noMatch && this.byteCache.length === this.boundaryBufLen) {
// match found
this.matchFound = true;
this.mimeStructureStart = this.lineStart;
break;
}
// reset counter
this.lineStart = -1;
this.noMatch = false;
this.byteCache = [];
continue;
}

if (this.noMatch) {
// no need to look
continue;
}

if (this.lineStart < 0) {
this.lineStart = this.prevChunks + i;
}

if (this.byteCache.length >= this.boundaryBufLen) {
this.noMatch = true;
continue;
}

const expectingByte = this.boundaryBuf[this.byteCache.length];
if (expectingByte !== c) {
this.noMatch = true;
continue;
}
this.byteCache[this.byteCache.length] = c;
}

this.prevChunks += chunk.length;
}

getMimeStructureStart() {
if (!this.boundary) {
return 0;
}

if (!this.matchFound && !this.noMatch && this.byteCache.length === this.boundaryBufLen) {
this.matchFound = true;
this.mimeStructureStart = this.lineStart;
}

return this.mimeStructureStart;
}
}

module.exports = { MimeStructureStartFinder };

0 comments on commit 8f25353

Please sign in to comment.