Skip to content

Instantly share code, notes, and snippets.

@janhebnes
Created December 18, 2015 11:41
Show Gist options
  • Save janhebnes/e117146f9b1eb4347f33 to your computer and use it in GitHub Desktop.
Save janhebnes/e117146f9b1eb4347f33 to your computer and use it in GitHub Desktop.
Allowing to sign an email with a private certificate and encrypting the email with a receivers public certificate key, taking care of handling potential issues arising when certificates are close to expiration or have expired
namespace FemtenNulOtte.Client.Logic.Utilities
{
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Mail;
using System.Net.Mime;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;
using System.Text;
using FemtenNulOtte.Core.Configuration;
using global::Sitecore.Diagnostics;
public class MailEncryption
{
#region Fields
/// <summary>
/// Encoding to use (ASCII does not support danish characters and UTF-8 results in strange chinese characters in the e-mail (not sure why but may have something to do with the application/pkcs7-mime format). ISO-8859-1 works though)
/// </summary>
private static Encoding EncryptedEmailEncoding = Encoding.GetEncoding("ISO-8859-1");
#endregion Fields
#region Properties
public static string MailCopy
{
get
{
return ApplicationSettings.GetAppSetting("MailSettings.MailCopy", string.Empty);
}
}
#endregion Properties
#region Methods
public static X509Certificate2 GetCert(string certificatePath)
{
return GetCert(certificatePath, string.Empty);
}
public static X509Certificate2 GetCert(MemoryStream stream)
{
return GetCert(stream, string.Empty);
}
public static X509Certificate2 GetCert(MemoryStream stream, string password)
{
X509Certificate2 cert = null;
if (!string.IsNullOrEmpty(password))
{
cert = new X509Certificate2(stream.ToArray(), password);
}
else
{
cert = new X509Certificate2(stream.ToArray());
}
return cert;
}
public static X509Certificate2 GetCert(string certificatePath, string password)
{
X509Certificate2 cert = null;
if (!string.IsNullOrEmpty(password))
{
cert = new X509Certificate2(certificatePath, password);
}
else
{
cert = new X509Certificate2(certificatePath);
}
return cert;
}
public static string GetCertificateDetailedInformation(X509Certificate2 cert)
{
var strb = new StringBuilder();
strb.AppendLine(string.Format("cert.Subject: \t{0}", cert.Subject));
strb.AppendLine(string.Format("cert.FriendlyName: \t{0}", cert.FriendlyName));
strb.AppendLine(string.Format("cert.GetPublicKeyString: \t{0}", cert.GetPublicKeyString()));
strb.AppendLine(string.Format("cert.GetSerialNumberString: \t{0}", cert.GetSerialNumberString()));
strb.AppendLine(string.Format("cert.Issuer: \t{0}", cert.Issuer));
strb.AppendLine(string.Format("cert.SignatureAlgorithm.FriendlyName: \t{0}", cert.SignatureAlgorithm.FriendlyName));
strb.AppendLine(string.Format("cert.Thumbprint: \t{0}", cert.Thumbprint));
strb.AppendLine(string.Format("cert.Version: \t{0}", cert.Version));
strb.AppendLine(string.Format("cert.NotAfter: \t{0}", cert.NotAfter));
strb.AppendLine(string.Format("cert.NotBefore: \t{0}", cert.NotBefore));
strb.AppendLine(string.Format("cert.GetExpirationDateString: \t{0}", cert.GetExpirationDateString()));
strb.AppendLine(string.Format("cert.DnsName: \t{0}", cert.GetNameInfo(X509NameType.DnsName, true)));
strb.AppendLine(string.Format("cert.EmailName: \t{0}", cert.GetNameInfo(X509NameType.EmailName, true)));
strb.AppendLine(string.Format("cert.SimpleName: \t{0}", cert.GetNameInfo(X509NameType.SimpleName, true)));
strb.AppendLine(string.Format("cert.UpnName: \t{0}", cert.GetNameInfo(X509NameType.UpnName, true)));
return strb.ToString();
}
public static string GetCertificateInformation(X509Certificate2 cert)
{
var strb = new StringBuilder();
strb.AppendLine(string.Format("cert.Subject: \t{0}", cert.Subject));
strb.AppendLine(string.Format("cert.FriendlyName: \t{0}", cert.FriendlyName));
strb.AppendLine(string.Format("cert.Thumbprint: \t{0}", cert.Thumbprint));
strb.AppendLine(string.Format("cert.Version: \t{0}", cert.Version));
strb.AppendLine(string.Format("cert.NotAfter: \t{0}", cert.NotAfter));
strb.AppendLine(string.Format("cert.NotBefore: \t{0}", cert.NotBefore));
return strb.ToString();
}
public static void SendEncryptedEmail(string from, string to, string subject, string msg, List<Attachment> attachments, X509Certificate2 encryptionCertificate, X509Certificate2 signingCertificate)
{
// VALIDATE Crypting Certificates are active
bool validCertificates = true;
string validationMessage = string.Empty;
if (encryptionCertificate.NotBefore > DateTime.Now)
{
validationMessage += string.Format("\n\n<br><br><strong><em>System message: Encryption Certificate on server valid from {0}, please notice Client administrator.</em></strong>", encryptionCertificate.NotBefore);
validationMessage += GetCertificateInformation(encryptionCertificate).Replace("\n", "<br>\n");
validCertificates = false;
}
if (DateTime.Now > encryptionCertificate.NotAfter)
{
validationMessage += string.Format("\n\n<br><br><strong><em>System message: Encryption Certificate on server expired on {0}, please send the new public key to Client administrator.</em></strong>", encryptionCertificate.NotAfter);
validationMessage += GetCertificateInformation(encryptionCertificate).Replace("\n", "<br>\n");
validCertificates = false;
}
// Soon to expire but not yet expired
if (DateTime.Now > encryptionCertificate.NotAfter.AddMonths(-1) && DateTime.Now < encryptionCertificate.NotAfter)
{
validationMessage += string.Format("\n\n<br><br><strong><em>System message: Encryption Certificate on server will expire on {0}, please renew and send the new public key to Client administrator.</em></strong>", encryptionCertificate.NotAfter);
validationMessage += GetCertificateInformation(encryptionCertificate).Replace("\n", "<br>\n");
}
// VALIDATE Signing Certificates are active
if (signingCertificate.NotBefore > DateTime.Now)
{
validationMessage += string.Format("\n\n<br><br><strong><em>System message: Signing Certificate on server valid from {0}, please notice Client administrator.</em></strong>", signingCertificate.NotBefore);
validationMessage += GetCertificateInformation(signingCertificate).Replace("\n", "<br>\n");
validCertificates = false;
}
if (DateTime.Now > signingCertificate.NotAfter)
{
validationMessage += string.Format("\n\n<br><br><strong><em>System message: Signing Certificate on server expired on {0}, please notice Client administrator.</em></strong>", signingCertificate.NotAfter);
validationMessage += GetCertificateInformation(signingCertificate).Replace("\n", "<br>\n");
validCertificates = false;
}
// Soon to expire but not yet expired
if (DateTime.Now > signingCertificate.NotAfter.AddMonths(-1) && DateTime.Now < signingCertificate.NotAfter)
{
validationMessage += string.Format("\n\n<br><br><strong><em>System message: Signing Certificate on server will expire on {0}, please notice Client administrator.</em></strong>", signingCertificate.NotAfter);
validationMessage += GetCertificateInformation(signingCertificate).Replace("\n", "<br>\n");
}
// Notify support function at 1508
if (!string.IsNullOrWhiteSpace(validationMessage))
{
using (var notice = new MailMessage())
{
notice.To.Add("[email protected]");
notice.From = new MailAddress(@from);
notice.Subject = "Client Certificate Warning";
notice.Body = validationMessage;
notice.IsBodyHtml = true;
using (var noticeClient = new SmtpClient())
{
noticeClient.Send(notice);
}
}
}
msg += validationMessage;
string messageContentToEncryptAndSign = BuildMessageContent(msg, attachments);
string signedMessageContent = signed(messageContentToEncryptAndSign, signingCertificate);
AlternateView encryptedContentAsAlternateView = encrypt(signedMessageContent, encryptionCertificate);
MailMessage message = new System.Net.Mail.MailMessage();
SmtpClient userRecieptClient = new SmtpClient();
// Bcc if provided in web.config
if (!string.IsNullOrWhiteSpace(MailCopy)
&& !message.To.Contains(new MailAddress(MailCopy)))
{
message.Bcc.Add(new MailAddress(MailCopy));
Log.Audit(string.Format("MailCopy active - sending encrypted mail with bcc to {0}", MailCopy), typeof(MailUtility));
}
message.AlternateViews.Add(encryptedContentAsAlternateView);
message.To.Add(to);
message.From = new MailAddress(from);
message.Subject = subject;
SetupMailRedirection(message);
userRecieptClient.Send(message);
// SEND COPY WITH DEBUG INFORMATION if invalid certificates and MailCopy is not present
if (validCertificates && string.IsNullOrWhiteSpace(MailCopy))
{
return;
}
// Resend the content with the body attached and with debug information
message.Subject = message.Subject + " (copy)";
message.AlternateViews.Clear();
message.Body = msg;
message.IsBodyHtml = true;
// Generated attachment with Debug information
var memoryStream = new MemoryStream();
TextWriter aWriter = new StreamWriter(memoryStream);
aWriter.WriteLine("\n\n ---------------UNENCRYPTED CONTENT-------------------\n");
aWriter.WriteLine(msg);
aWriter.WriteLine("\n\n ---------------ENCRYPTED CONTENT-------------------\n");
aWriter.WriteLine(messageContentToEncryptAndSign);
aWriter.WriteLine("\n\n ---------------Encryption Certificate INFORMATION -------------------\n");
aWriter.WriteLine(GetCertificateDetailedInformation(encryptionCertificate));
aWriter.WriteLine("\n\n ---------------Signing Certificate INFORMATION -------------------\n");
aWriter.WriteLine(GetCertificateDetailedInformation(signingCertificate));
memoryStream.Position = 0;
Attachment debug = new Attachment(memoryStream,"EncryptionDebugInformation.txt");
attachments.Add(debug);
foreach (Attachment attachment in attachments)
{
message.Attachments.Add(attachment);
}
userRecieptClient.Send(message);
}
public static void SendEncryptedEmail(string from, string to, string subject, string msg, List<Attachment> attachments, string encryptionCertificatePath, string signingCertificatePath, string signingCertificatePassword)
{
SendEncryptedEmail(from, to, subject, msg, attachments, GetCert(encryptionCertificatePath), GetCert(signingCertificatePath, signingCertificatePassword));
}
/// <summary>
/// Based on the environment configuration sets up redirect of the message we are sending
/// </summary>
/// <param name="mailMessage">The mail message.</param>
public static void SetupMailRedirection(MailMessage mailMessage)
{
if (!ApplicationSettings.RedirectMails)
{
return;
}
List<string> recipients = mailMessage.To.Select(address => address.Address).ToList();
recipients.AddRange(mailMessage.CC.Select(address => address.Address));
recipients.AddRange(mailMessage.Bcc.Select(address => address.Address));
StringBuilder recipientBuilder = new StringBuilder();
foreach (string recipient in recipients)
{
if (recipientBuilder.Length > 0)
{
recipientBuilder.Append(", ");
}
recipientBuilder.Append(recipient);
}
recipientBuilder.AppendFormat("[from:{0}]", mailMessage.From.Address);
mailMessage.To.Clear();
mailMessage.CC.Clear();
mailMessage.Bcc.Clear();
mailMessage.Subject = string.Format("REDIRECTED -- {0} -- [{1}]", mailMessage.Subject, recipientBuilder.ToString());
mailMessage.To.Add(ApplicationSettings.RedirectEmail);
mailMessage.From = new MailAddress(ApplicationSettings.RedirectMailFrom);
}
private static string BuildMessageContent(string bodyText, List<Attachment> attachments)
{
string messageBoundry = "--PTBoundry=2";
StringBuilder message = new StringBuilder();
message.Append("\r\n");
message.Append("\r\n");
message.Append("--");
message.Append(messageBoundry + "\r\n");
message.AppendFormat("Content-Type: text/html; charset={0}\r\n", EncryptedEmailEncoding.EncodingName); //could use text/html as well here if you want a html message
message.Append("Content-Transfer-Encoding: ");
message.Append("quoted - printable");
message.Append("\r\n\r\n");
message.Append(bodyText);
message.Append("\r\n");
//ADD file section
//could be filename or whatever
foreach (Attachment attachment in attachments)
{
long numBytes = attachment.ContentStream.Length;
byte[] bytes = new byte[numBytes];
attachment.ContentStream.Read(bytes, 0, (int)numBytes);
attachment.ContentStream.Position = 0;
//Setup filecontent
String filecontent = Convert.ToBase64String(bytes, Base64FormattingOptions.InsertLineBreaks);
message.Append("--");
message.Append(messageBoundry);
message.Append("\r\n");
message.Append("Content-Type: ");
message.Append(attachment.ContentType.ToString());
message.Append("\r\n");
message.Append("Content-Transfer-Encoding: base64\r\n\r\n");
message.Append(filecontent);
message.Append("\r\n\r\n");
}
//END FILSECTION
message.Append("--");
message.Append(messageBoundry);
message.Append("--\r\n");
return message.ToString();
}
private static byte[] DoEncrypt(string message, X509Certificate2 encryptionCertificates)
{
byte[] messageBytes = EncryptedEmailEncoding.GetBytes(message); //Encoding.ASCII.GetBytes(message);
EnvelopedCms envelopedCms = new EnvelopedCms(new ContentInfo(messageBytes));
CmsRecipient recipients = new CmsRecipient(SubjectIdentifierType.IssuerAndSerialNumber, encryptionCertificates);
envelopedCms.Encrypt(recipients);
return envelopedCms.Encode();
}
private static AlternateView encrypt(string content, X509Certificate2 encryptionCertificate)
{
string signatureBoundry2 = "--PTBoundry=3";
StringBuilder fullUnencryptedMessageBuilder = new StringBuilder();
fullUnencryptedMessageBuilder.Append("Content-Type: ");
fullUnencryptedMessageBuilder.Append("multipart/signed; ");
fullUnencryptedMessageBuilder.Append("boundary=\"");
fullUnencryptedMessageBuilder.Append(signatureBoundry2);
fullUnencryptedMessageBuilder.Append("\"; protocol=\"application/x-pkcs7-signature\"; micalg=SHA1; ");
fullUnencryptedMessageBuilder.Append("\r\n");
fullUnencryptedMessageBuilder.Append("Content-Transfer-Encoding: ");
fullUnencryptedMessageBuilder.Append(TransferEncoding.SevenBit);
fullUnencryptedMessageBuilder.Append("\r\n\r\n");
fullUnencryptedMessageBuilder.Append(content);
string fullUnencryptedMessage = fullUnencryptedMessageBuilder.ToString();
byte[] encryptedBytes = DoEncrypt(fullUnencryptedMessage, encryptionCertificate);
MemoryStream stream = new MemoryStream(encryptedBytes);
AlternateView view = new AlternateView(stream, "application/pkcs7-mime; smime-type=signed-data;name=smime.p7m");
view.TransferEncoding = TransferEncoding.Base64;
return view;
}
private static byte[] GetSignature(string message, X509Certificate2 signingCertificate, X509Certificate2 encryptionCertificate)
{
byte[] messageBytes = EncryptedEmailEncoding.GetBytes(message);// Encoding.ASCII.GetBytes(message);
SignedCms signedCms = new SignedCms(new ContentInfo(messageBytes), true);
CmsSigner cmsSigner = new CmsSigner(SubjectIdentifierType.IssuerAndSerialNumber, signingCertificate);
cmsSigner.IncludeOption = X509IncludeOption.WholeChain;
if (encryptionCertificate != null)
{
cmsSigner.Certificates.Add(encryptionCertificate);
}
Pkcs9SigningTime signingTime = new Pkcs9SigningTime();
cmsSigner.SignedAttributes.Add(signingTime);
signedCms.ComputeSignature(cmsSigner, false);
return signedCms.Encode();
}
private static string signed(string Content, X509Certificate2 signingCertificate)
{
string signatureBoundry = "--PTBoundry=2";
string signatureBoundry2 = "--PTBoundry=3";
StringBuilder fullUnsignedMessageBuilder = new StringBuilder();
fullUnsignedMessageBuilder.Append("Content-Type: ");
fullUnsignedMessageBuilder.Append("multipart/mixed;");
fullUnsignedMessageBuilder.Append(" boundary=\"");
fullUnsignedMessageBuilder.Append(signatureBoundry);
fullUnsignedMessageBuilder.Append("\"\r\n");
fullUnsignedMessageBuilder.Append("Content-Transfer-Encoding: ");
fullUnsignedMessageBuilder.Append("7bit");
fullUnsignedMessageBuilder.Append("\r\n");
fullUnsignedMessageBuilder.Append(Content);
string fullUnsignedMessage = fullUnsignedMessageBuilder.ToString();
byte[] signature = GetSignature(fullUnsignedMessage, signingCertificate, signingCertificate);
StringBuilder signedMessageBuilder = new StringBuilder();
signedMessageBuilder.Append("--");
signedMessageBuilder.Append(signatureBoundry2);
signedMessageBuilder.Append("\r\n");
signedMessageBuilder.Append(fullUnsignedMessage);
signedMessageBuilder.Append("\r\n");
signedMessageBuilder.Append("--");
signedMessageBuilder.Append(signatureBoundry2);
signedMessageBuilder.Append("\r\n");
signedMessageBuilder.Append("Content-Type: application/x-pkcs7-signature; name=\"smime.p7s\"\r\n");
signedMessageBuilder.Append("Content-Transfer-Encoding: base64\r\n");
signedMessageBuilder.Append("Content-Disposition: attachment; filename=\"smime.p7s\"\r\n\r\n");
signedMessageBuilder.Append(Convert.ToBase64String(signature));
signedMessageBuilder.Append("\r\n\r\n");
signedMessageBuilder.Append("--");
signedMessageBuilder.Append(signatureBoundry2);
signedMessageBuilder.Append("--\r\n");
return signedMessageBuilder.ToString();
}
#endregion Methods
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment