Reputation: 361
I'm trying to develop my home email server (with NodeJS on server side but it's not important as I try to figure out principles). I use this documentation to guide myself through DKIM-Signature validation routine, but it requires some complicated steps and I can't figure out where is my mistake. For an email example I used one sent from Mail.ru server. It should be totally valid. There is it's header:
DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=mail.ru; s=mail2;
h=References:In-Reply-To:Content-Type:Message-ID:Reply-To:Date:MIME-Version:Subject:To:From; bh=gCWDSCJf58CbaR+wjAV9dydu9JTKkvo1o+0zkj8bNr0=;
b=pheltY+k/mio2x4CFQV8cXZxNiR7oSTkIsWTOZa1CGpEyK8KVSHY07OWSdZ1aFVtuaV32PbI0mNY0yliuqIbYTsnreFUYFM/iVR5PU74QHAe8yp46ydAYRbzLQu8dy+AkFhPtEdb8CAgoZKXgPLc888/Q6MsVAh6iH1L3SZj87Y=;
Received: by f427.i.mail.ru with local (envelope-from <[my name]@mail.ru>)
id 1dbP18-0003I9-L7
for madbr@[domain]; Sat, 29 Jul 2017 13:30:42 +0300
Received: by e.mail.ru with HTTP;
Sat, 29 Jul 2017 13:30:42 +0300
From: =?UTF-8?B?0KHQtdGA0LPQtdC5?= <[my name]@mail.ru>
To: madbr@[domain]
Subject: =?UTF-8?B?UmU6IA==?=
MIME-Version: 1.0
X-Mailer: Mail.Ru Mailer 1.0
Date: Sat, 29 Jul 2017 13:30:42 +0300
Reply-To: =?UTF-8?B?0KHQtdGA0LPQtdC5?= <[my name]@mail.ru>
X-Priority: 3 (Normal)
Message-ID: <[email protected]>
Content-Type: multipart/mixed;
boundary="----uEhsLqzDWmmGeA9EZ3XNsqSIGjlgVTmA-NI9QMhpqxNHWLEDT-1501324242"
Authentication-Results: f427.i.mail.ru; auth=pass smtp.auth=[my name]@mail.ru smtp.mailfrom=[my name]@mail.ru
X-7FA49CB5: 0D63561A33F958A58B4AE7CD4FB69874B38CA0D04717BA57612FFEEC28D99E31725E5C173C3A84C325A81A29FB5043FD044813140D6DB928F1C9CF18C8EB2269C4224003CC836476C0CAF46E325F83A50BF2EBBBDD9D6B0F2AF38021CC9F462D574AF45C6390F7469DAA53EE0834AAEE
X-Mailru-Sender: 080178E06F6B3F48806FD386034E228604900381AF51F7DD303A634C9E25199A8DFBC783E67F8C0305D8C6CDFE81985CCFB2E39DA8E91CCEEEC687A792225BA622DF1A08BD40178CA471C22AD050A14893AC9912533B2342AE208404248635DF
X-Mras: OK
X-Spam: undefined
In-Reply-To: <[email protected]>
References: <[email protected]>
Validation instruction says:
In hash step 1, the Signer/Verifier MUST hash the message body,
canonicalized using the body canonicalization algorithm specified in
the "c=" tag and then truncated to the length specified in the "l="
tag. That hash value is then converted to base64 form and inserted
into (Signers) or compared to (Verifiers) the "bh=" tag of the DKIM-
Signature header field.
In hash step 2, the Signer/Verifier MUST pass the following to the
hash algorithm in the indicated order.
1. The header fields specified by the "h=" tag, in the order
specified in that tag, and canonicalized using the header
canonicalization algorithm specified in the "c=" tag. Each
header field MUST be terminated with a single CRLF.
2. The DKIM-Signature header field that exists (verifying) or will
be inserted (signing) in the message, with the value of the "b="
tag (including all surrounding whitespace) deleted (i.e., treated
as the empty string), canonicalized using the header
canonicalization algorithm specified in the "c=" tag, and without
a trailing CRLF.
The first step is easy: I've get message body, canonicalized it using
relaxed: function (data) {
return data.replace(/[ \t]+\r\n/g, '\r\n').replace(/[ \t]+/g, ' ').replace(/\r\n{2,}$/g, CONST.CRLF);
}
and created sha256
(according to a=
tag) hash of it. It matched bh=
tag in DKIM-Signature
header and yet I'm happy.
For a next step I perform next actions:
1) Get all required headers from message in order given in h=
signature tag.
References: <[email protected]>
In-Reply-To: <[email protected]>
Content-Type: multipart/mixed;
boundary="----uEhsLqzDWmmGeA9EZ3XNsqSIGjlgVTmA-NI9QMhpqxNHWLEDT-1501324242"
Message-ID: <[email protected]>
Reply-To: =?UTF-8?B?0KHQtdGA0LPQtdC5?= <[my name]@mail.ru>
Date: Sat, 29 Jul 2017 13:30:42 +0300
MIME-Version: 1.0
Subject: =?UTF-8?B?UmU6IA==?=
To: madbr@[domain]
From: =?UTF-8?B?0KHQtdGA0LPQtdC5?= <[my name]@mail.ru>
2) Canonicalized it:
references:<[email protected]>
in-reply-to:<[email protected]>
content-type:multipart/mixed; boundary="----uEhsLqzDWmmGeA9EZ3XNsqSIGjlgVTmA-NI9QMhpqxNHWLEDT-1501324242"
message-id:<[email protected]>
reply-to:=?UTF-8?B?0KHQtdGA0LPQtdC5?= <[my name]@mail.ru>
date:Sat, 29 Jul 2017 13:30:42 +0300
mime-version:1.0
subject:=?UTF-8?B?UmU6IA==?=
to:madbr@[domain]
from:=?UTF-8?B?0KHQtdGA0LPQtdC5?= <[my name]@mail.ru>
3) Get DKIM-Signature, removed b=
tag and also canonalized it (trailing \r\n
was also removed according to documentation):
dkim-signature:v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=mail.ru; s=mail2; h=References:In-Reply-To:Content-Type:Message-ID:Reply-To:Date:MIME-Version:Subject:To:From; bh=gCWDSCJf58CbaR+wjAV9dydu9JTKkvo1o+0zkj8bNr0==;
4) Get public key from DNS TXT record and appended -----BEGIN PUBLIC KEY-----...-----END PUBLIC KEY-----
for PEM format compatibility.
5) At last I used standard RSA validation function to validate it:
crypto.createVerify('sha256')
.update(header + dkimHeader)
.verify(publicKey, Buffer.from(signature.b, CONST.BASE64));
But it failed, and I don't really know which actions to blame.
In last step I concatenated header and DKIM-Signature, because I don't really understand what does "pass the following to the
hash algorithm in the indicated order" mean. Tried to use .update(header).update(dkimHeader)
, but it made no difference.
Can someone explain please, what do I do wrong?
Upvotes: 7
Views: 3612
Reputation: 561
From section 3.7. Computing the Message Hashes of the RFC:
In hash step 2, the Signer/Verifier MUST pass the following to the hash algorithm in the indicated order.
- The header fields specified by the "h=" tag, in the order specified in that tag, and canonicalized using the header canonicalization algorithm specified in the "c=" tag. Each header field MUST be terminated with a single CRLF.
- The DKIM-Signature header field that exists (verifying) or will be inserted (signing) in the message, with the value of the "b=" tag (including all surrounding whitespace) deleted (i.e., treated as the empty string), canonicalized using the header canonicalization algorithm specified in the "c=" tag, and without a trailing CRLF.
I highlighted the important part: Only the value should be deleted, not the complete tag.
So the correct last line of the input is (note the b=;
at the end):
dkim-signature:v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=mail.ru; s=mail2; h=References:In-Reply-To:Content-Type:Message-ID:Reply-To:Date:MIME-Version:Subject:To:From; bh=gCWDSCJf58CbaR+wjAV9dydu9JTKkvo1o+0zkj8bNr0=; b=;
Upvotes: 9