0 votes
by (120 points)

Applies to: Rebex Secure Mail
by (160 points)
Any update on DKIM feature - we also want to use it.
by (150k points)
Unfortunately, we have not added support for DKIM yes. It's still on our list of possible future enhancements, but so far we have been busy improving other parts of our libraries. Sorry!

1 Answer

0 votes
by (150k points)

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=");
}

...