Is there any plan to implement DKIM Signing

0 votes
asked Jun 29, 2016 by cantyco (120 points)

We know from the following question(http://forum.rebex.net/6269/dkim-email-authentication-supported-in-rebex-how-to-do-that) that DKIM is currently not implemented, I was wondering if there was any plans for it to be included in the future?

Its just we are having issues sending emails to some email providers becuase the emails we are sending through Rebex Smtp secure mail components are not signed by DKIM?

Applies to: Rebex Secure Mail

1 Answer

0 votes
answered Jun 30, 2016 by Lukas Pokorny (85,050 points)

DKIM is on our list of possible future enhancements, but not very high at the moment. DKIM signatures are usually added by mail transfer agents (such as mail servers), while Rebex SMTP is most often used as an SMTP client that sends e-mail through a server (which can usually be set up quite easily to add DKIM signatures).

However, we wrote a proof-of-concept code that shows how to add DKIM signature using Rebex Secure Mail with a bit of custom code:

string privateKeyPath = "..."; // path to domain private key
string domain = "rebex.net"; // domain name
string selector = "jun2016"; // selector

string from = "support@rebex.net";
string to = "someone@example.org";
string subject = "DKIM test email";
string body =
    "\r\n\r\nThis is the body of the message, with more than\r\n" +
    "one line at <B>the END and START</B>.\r\n" +
    "\r\n";

// create new MailMessage
MailMessage mail = new MailMessage();
mail.From = from;
mail.To.Add(to);
mail.Subject = subject;
mail.BodyText = body;
mail.BodyHtml = body;

// save message into MemoryStream in final MIME format
MemoryStream ms = new MemoryStream();

// save it using MimeMessage to be able to omit Preamble (not part of canonicalized data)
MimeMessage message = mail.ToMimeMessage();
message.Preamble = null; // no preamble 
message.Save(ms);

// load message from MemoryStream using DoNotParseMimeTree option
ms.Position = 0;
message.Options = MimeOptions.DoNotParseMimeTree;
message.Load(ms);

// read the body part
using (Stream s = message.GetContentStream())
{
        byte[] b = new byte[s.Length];
        int n = b.Length;
        while (n > 0)
                n -= s.Read(b, b.Length - n, n);
        body = Encoding.UTF8.GetString(b);
        body = RemoveCharset(body); // remove charsets (not part of canonicalized data)
}

// compute body hash
HashAlgorithm hash = new SHA256Managed();
// use desired Body Canonicalization Algorithm (see RFC 4871)
string hashBody = BodyRelaxedCanonicalization(body);
byte[] bodyBytes = Encoding.UTF8.GetBytes(hashBody);
string hashout = Convert.ToBase64String(hash.ComputeHash(bodyBytes));

// prepare Timestamp - seconds since 00:00:00 on January 1, 1970 UTC 
TimeSpan t = DateTime.Now.ToUniversalTime() - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

// prepare data to sign
string signatureHeader =
        "v=1; " +
        "a=rsa-sha256; " +
        "c=relaxed/relaxed; " +
        "d=" + domain + "; " +
        "s=" + selector + "; " +
        "t=" + Convert.ToInt64(t.TotalSeconds) + "; " +
        "bh=" + hashout + "; " +
        //"h=From:To:Subject:Content-Type:Content-Transfer-Encoding; " +
        "h=From:To:Subject; " +
        "b=";

string canonicalizedHeaders =
        "from:" + message.Headers["From"].Raw + Environment.NewLine +
        "to:" + message.Headers["To"].Raw + Environment.NewLine +
        "subject:" + message.Headers["Subject"].Raw + Environment.NewLine +
        "dkim-signature:" + signatureHeader;

// load RSA private key
Rebex.Security.Cryptography.Pkcs.PrivateKeyInfo pkinfo = new Rebex.Security.Cryptography.Pkcs.PrivateKeyInfo();
using (FileStream fs = File.OpenRead(privateKeyPath))
{
        pkinfo.Load(fs, null);
}

// sign prepared header data using RSA-SHA256
using (RSACryptoServiceProvider signer = new RSACryptoServiceProvider())
{
        signer.ImportParameters(pkinfo.GetRSAParameters());

        byte[] plaintext = Encoding.UTF8.GetBytes(canonicalizedHeaders);
        byte[] signature = signer.SignData(plaintext, "SHA256");
        signatureHeader += Convert.ToBase64String(signature);
}

// add the DKIM-Signature header
message.Headers.Add(new MimeHeader("DKIM-Signature", new Unparsed(signatureHeader)));

// save the message (for debugging purposes)
message.Save(targetMailPath);

// send the message
...

public static string BodySimpleCanonicalization(string body)
{
    // Ignores all empty lines at the end of the message body
    return body.TrimEnd(' ', '\t', '\r', '\n') + "\r\n";
}

public static string BodyRelaxedCanonicalization(string body)
{
    // Ignores all empty lines at the end of the message body
    body = body.TrimEnd(' ', '\t', '\r', '\n') + "\r\n";

    // Reduces all sequences of WSP within a line to a single SP character
    body = body.Replace('\t', ' ');

    int originalLength = body.Length;
    body = body.Replace("  ", " ");
    while (body.Length != originalLength)
    {
        originalLength = body.Length;
        body = body.Replace("  ", " ");
    }

    // Ignores all whitespace at the end of lines
    body = body.Replace(" \r\n", "\r\n");

    return body;
}

public static string RemoveCharset(string body)
{
    return System.Text.RegularExpressions.Regex.Replace(body, ";\r\n[ \t]*charset=", "; charset=");
}

Although this code is certainly not production-quality, it demonstrates that adding DKIM signature using a bit of custom code is possible. Please note that in addition to this, you need to add proper DKIM DNS records as well.

...