Created
December 18, 2015 11:41
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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