MadBrozzeR
MadBrozzeR

Reputation: 361

Proper way to validate DKIM-Signature (b= part)

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

Answers (1)

user228011
user228011

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.

  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.
  1. 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

Related Questions