123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117 |
- 'use strict';
- const punycode = require('punycode');
- const mimeFuncs = require('../mime-funcs');
- const crypto = require('crypto');
- /**
- * Returns DKIM signature header line
- *
- * @param {Object} headers Parsed headers object from MessageParser
- * @param {String} bodyHash Base64 encoded hash of the message
- * @param {Object} options DKIM options
- * @param {String} options.domainName Domain name to be signed for
- * @param {String} options.keySelector DKIM key selector to use
- * @param {String} options.privateKey DKIM private key to use
- * @return {String} Complete header line
- */
- module.exports = (headers, hashAlgo, bodyHash, options) => {
- options = options || {};
- // all listed fields from RFC4871 #5.5
- let defaultFieldNames =
- 'From:Sender:Reply-To:Subject:Date:Message-ID:To:' +
- 'Cc:MIME-Version:Content-Type:Content-Transfer-Encoding:Content-ID:' +
- 'Content-Description:Resent-Date:Resent-From:Resent-Sender:' +
- 'Resent-To:Resent-Cc:Resent-Message-ID:In-Reply-To:References:' +
- 'List-Id:List-Help:List-Unsubscribe:List-Subscribe:List-Post:' +
- 'List-Owner:List-Archive';
- let fieldNames = options.headerFieldNames || defaultFieldNames;
- let canonicalizedHeaderData = relaxedHeaders(headers, fieldNames, options.skipFields);
- let dkimHeader = generateDKIMHeader(options.domainName, options.keySelector, canonicalizedHeaderData.fieldNames, hashAlgo, bodyHash);
- let signer, signature;
- canonicalizedHeaderData.headers += 'dkim-signature:' + relaxedHeaderLine(dkimHeader);
- signer = crypto.createSign(('rsa-' + hashAlgo).toUpperCase());
- signer.update(canonicalizedHeaderData.headers);
- try {
- signature = signer.sign(options.privateKey, 'base64');
- } catch (E) {
- return false;
- }
- return dkimHeader + signature.replace(/(^.{73}|.{75}(?!\r?\n|\r))/g, '$&\r\n ').trim();
- };
- module.exports.relaxedHeaders = relaxedHeaders;
- function generateDKIMHeader(domainName, keySelector, fieldNames, hashAlgo, bodyHash) {
- let dkim = [
- 'v=1',
- 'a=rsa-' + hashAlgo,
- 'c=relaxed/relaxed',
- 'd=' + punycode.toASCII(domainName),
- 'q=dns/txt',
- 's=' + keySelector,
- 'bh=' + bodyHash,
- 'h=' + fieldNames
- ].join('; ');
- return mimeFuncs.foldLines('DKIM-Signature: ' + dkim, 76) + ';\r\n b=';
- }
- function relaxedHeaders(headers, fieldNames, skipFields) {
- let includedFields = new Set();
- let skip = new Set();
- let headerFields = new Map();
- (skipFields || '')
- .toLowerCase()
- .split(':')
- .forEach(field => {
- skip.add(field.trim());
- });
- (fieldNames || '')
- .toLowerCase()
- .split(':')
- .filter(field => !skip.has(field.trim()))
- .forEach(field => {
- includedFields.add(field.trim());
- });
- for (let i = headers.length - 1; i >= 0; i--) {
- let line = headers[i];
- // only include the first value from bottom to top
- if (includedFields.has(line.key) && !headerFields.has(line.key)) {
- headerFields.set(line.key, relaxedHeaderLine(line.line));
- }
- }
- let headersList = [];
- let fields = [];
- includedFields.forEach(field => {
- if (headerFields.has(field)) {
- fields.push(field);
- headersList.push(field + ':' + headerFields.get(field));
- }
- });
- return {
- headers: headersList.join('\r\n') + '\r\n',
- fieldNames: fields.join(':')
- };
- }
- function relaxedHeaderLine(line) {
- return line
- .substr(line.indexOf(':') + 1)
- .replace(/\r?\n/g, '')
- .replace(/\s+/g, ' ')
- .trim();
- }
|