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