Skip to content

Commit a9e0365

Browse files
committed
Use time in the Received header as the verification time (#336)
1 parent 6a3820e commit a9e0365

9 files changed

+153
-3
lines changed

CHANGELOG.md

+4
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@ All notable changes to this project will be documented in this file.
66

77
## Unreleased
88

9+
### Enhancements
10+
11+
- Extract the received time from the last Received header and use it as the verification time (#336).
12+
913
### Fixes
1014

1115
- Fixed extension not working for attached or external messages (#216).

_locales/de/messages.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@
185185
"message": "Signatur-Zeitstempel-Tag hat falsches Format"
186186
},
187187
"DKIM_SIGERROR_TIMESTAMPS": {
188-
"message": "Signatur abgelaufen"
188+
"message": "Signatur-Ablaufdatum ist vor dem Signatur-Zeitstempel"
189189
},
190190
"DKIM_SIGERROR_ILLFORMED_X": {
191191
"message": "Signatur-Ablaufdatum-Tag hat falsches Format"

modules/dkim/verifier.mjs.js

+12-2
Original file line numberDiff line numberDiff line change
@@ -993,14 +993,24 @@ class DkimSignature {
993993
* @returns {void}
994994
*/
995995
_checkValidityPeriod() {
996-
const time = Math.round(Date.now() / 1000);
996+
let receivedTime = null;
997+
const receivedHeaders = this._msg.headerFields.get("received") ?? [];
998+
if (receivedHeaders[0]) {
999+
receivedTime = MsgParser.tryExtractReceivedTime(receivedHeaders[0]);
1000+
}
1001+
1002+
const verifyTime = receivedTime ?? new Date();
1003+
const time = Math.round(verifyTime.getTime() / 1000);
9971004
// warning if signature expired
9981005
if (this._header.x !== null && this._header.x < time) {
9991006
this._header.warnings.push({ name: "DKIM_SIGWARNING_EXPIRED" });
10001007
log.debug("Warning: DKIM_SIGWARNING_EXPIRED");
10011008
}
10021009
// warning if signature in future
1003-
if (this._header.t !== null && this._header.t > time) {
1010+
// We allow a difference of 15 min so small clock differenzess between
1011+
// sender and receiver are not causing any issues
1012+
const allowedDifference = 15 * 60;
1013+
if (this._header.t !== null && this._header.t > time + allowedDifference) {
10041014
this._header.warnings.push({ name: "DKIM_SIGWARNING_FUTURE" });
10051015
log.debug("Warning: DKIM_SIGWARNING_FUTURE");
10061016
}

modules/msgParser.mjs.js

+21
Original file line numberDiff line numberDiff line change
@@ -225,4 +225,25 @@ export default class MsgParser {
225225
}
226226
throw new Error("Cannot extract the list identifier from the List-Id header.");
227227
}
228+
229+
/**
230+
* Tries to extract the date time information from a Received header (RFC 2919).
231+
*
232+
* @param {string} header
233+
* @returns {Date|null}
234+
*/
235+
static tryExtractReceivedTime(header) {
236+
const dateTimeStart = header.lastIndexOf(";");
237+
if (dateTimeStart === -1) {
238+
log.warn("Could not find the date time in the Received header");
239+
return null;
240+
}
241+
const dateTimeStr = header.substring(dateTimeStart + 1);
242+
const dateTime = new Date(dateTimeStr);
243+
if (dateTime.toString() === "Invalid Date") {
244+
log.warn("Could not parse the date time in the Received header");
245+
return null;
246+
}
247+
return dateTime;
248+
}
228249
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
Received: from client1.football.example.com [192.0.2.1]
2+
by submitserver.example.com with SUBMISSION;
3+
Sun, 10 Jun 2018 15:00:00 +0000
4+
Received: from client1.football.example.com [192.0.2.1]
5+
by submitserver.example.com with SUBMISSION;
6+
Fri, 11 Jul 2003 21:01:54 -0700 (PDT)
7+
DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed;
8+
d=football.example.com; [email protected];
9+
q=dns/txt; s=brisbane; t=1528637909; h=from : to :
10+
subject : date : message-id : from : subject : date;
11+
bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
12+
b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus
13+
Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==
14+
From: Joe SixPack <[email protected]>
15+
To: Suzie Q <[email protected]>
16+
Subject: Is dinner ready?
17+
Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
18+
Message-ID: <[email protected]>
19+
20+
Hi.
21+
22+
We lost the game. Are you hungry yet?
23+
24+
Joe.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Received: from client1.football.example.com [192.0.2.1]
2+
by submitserver.example.com with SUBMISSION;
3+
Sun, 10 Jun 2018 13:30:00 +0000
4+
DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed;
5+
d=football.example.com; [email protected];
6+
q=dns/txt; s=brisbane; t=1528637909; h=from : to :
7+
subject : date : message-id : from : subject : date;
8+
bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
9+
b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus
10+
Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==
11+
From: Joe SixPack <[email protected]>
12+
To: Suzie Q <[email protected]>
13+
Subject: Is dinner ready?
14+
Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
15+
Message-ID: <[email protected]>
16+
17+
Hi.
18+
19+
We lost the game. Are you hungry yet?
20+
21+
Joe.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
Received: from client1.football.example.com [192.0.2.1]
2+
by submitserver.example.com with SUBMISSION;
3+
Fri, 11 Jul 2003 21:01:54 -0700 (PDT)
4+
DKIM-Signature: v=1; a=ed25519-sha256; c=relaxed/relaxed;
5+
d=football.example.com; [email protected];
6+
q=dns/txt; s=brisbane; t=1528637909; h=from : to :
7+
subject : date : message-id : from : subject : date;
8+
bh=2jUSOH9NhtVGCQWNr9BrIAPreKQjO6Sn7XIkfJVOzv8=;
9+
b=/gCrinpcQOoIfuHNQIbq4pgh9kyIK3AQUdt9OdqQehSwhEIug4D11Bus
10+
Fa3bT3FY5OsU7ZbnKELq+eXdp1Q1Dw==
11+
From: Joe SixPack <[email protected]>
12+
To: Suzie Q <[email protected]>
13+
Subject: Is dinner ready?
14+
Date: Fri, 11 Jul 2003 21:00:37 -0700 (PDT)
15+
Message-ID: <[email protected]>
16+
17+
Hi.
18+
19+
We lost the game. Are you hungry yet?
20+
21+
Joe.

test/unittest/msgParserSpec.mjs.js

+29
Original file line numberDiff line numberDiff line change
@@ -361,6 +361,35 @@ describe("Message parser [unittest]", function () {
361361
).to.throw();
362362
});
363363
});
364+
describe("Extracting the date from a Received header", function () {
365+
it("RFC 6376 Appendix A Example", function () {
366+
const received = "Received: from client1.football.example.com [192.0.2.1]\r\n" +
367+
" by submitserver.example.com with SUBMISSION;\r\n" +
368+
" Fri, 11 Jul 2003 21:01:54 -0700 (PDT)\r\n";
369+
expect(MsgParser.tryExtractReceivedTime(received)).
370+
to.be.deep.equal(new Date("2003-07-11T21:01:54.000-07:00"));
371+
});
372+
it("RFC 5322 Appendix A.4. Messages with Trace Fields", function () {
373+
const received = "Received: from node.example by x.y.test; 21 Nov 1997 10:01:22 -0600\r\n";
374+
expect(MsgParser.tryExtractReceivedTime(received)).
375+
to.be.deep.equal(new Date("1997-11-21T10:01:22.000-06:00"));
376+
});
377+
it("Time without seconds", function () {
378+
const received = "Received: from node.example by x.y.test; 21 Nov 1997 10:01 -0600\r\n";
379+
expect(MsgParser.tryExtractReceivedTime(received)).
380+
to.be.deep.equal(new Date("1997-11-21T10:01:00.000-06:00"));
381+
});
382+
it("Missing semicolon", function () {
383+
const received = "Received: from node.example by x.y.test 21 Nov 1997 10:01:22 -0600\r\n";
384+
expect(MsgParser.tryExtractReceivedTime(received)).
385+
to.be.null;
386+
});
387+
it("Invalid date", function () {
388+
const received = "Received: from node.example by x.y.test; 41 Nov 1997 10:01:22 -0600\r\n";
389+
expect(MsgParser.tryExtractReceivedTime(received)).
390+
to.be.null;
391+
});
392+
});
364393
describe("Internationalized Email", function () {
365394
it("Disabled by default", function () {
366395
expect(() => MsgParser.parseFromHeader(toBinaryString(

test/unittest/verifierSpec.mjs.js

+20
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,19 @@ describe("DKIM Verifier [unittest]", function () {
141141
expect(res.signatures[0]?.result).to.be.equal("SUCCESS");
142142
expect(res.signatures[0]?.warnings).to.be.empty;
143143
});
144+
it("Received time is after Signature Timestamp", async function () {
145+
const res = await verifyEmlFile("dkim/time-received_after_creation.eml");
146+
expect(res.signatures.length).to.be.equal(1);
147+
expect(res.signatures[0]?.result).to.be.equal("SUCCESS");
148+
expect(res.signatures[0]?.warnings).to.be.empty;
149+
});
150+
it("Received time is briefly before Signature Timestamp", async function () {
151+
// I.e. a small clock difference between sender and receiver should not result in an error
152+
const res = await verifyEmlFile("dkim/time-received_briefly_before_creation.eml");
153+
expect(res.signatures.length).to.be.equal(1);
154+
expect(res.signatures[0]?.result).to.be.equal("SUCCESS");
155+
expect(res.signatures[0]?.warnings).to.be.empty;
156+
});
144157
});
145158
describe("Syntax errors", function () {
146159
it("Missing v-tag in signature", async function () {
@@ -250,6 +263,13 @@ describe("DKIM Verifier [unittest]", function () {
250263
expect(res.signatures[0]?.warnings).to.be.an('array').
251264
that.deep.includes({ name: "DKIM_SIGWARNING_FROM_NOT_IN_SDID" });
252265
});
266+
it("Received time is before Signature Timestamp", async function () {
267+
const res = await verifyEmlFile("dkim/time-received_long_before_creation.eml");
268+
expect(res.signatures.length).to.be.equal(1);
269+
expect(res.signatures[0]?.result).to.be.equal("SUCCESS");
270+
expect(res.signatures[0]?.warnings).to.be.an('array').
271+
that.deep.includes({ name: "DKIM_SIGWARNING_FUTURE" });
272+
});
253273
});
254274
describe("Detect and prevent possible attacks", function () {
255275
describe("Additional unsigned header was added", function () {

0 commit comments

Comments
 (0)