Enabling Secure Business Operations

Tutorial – Sending S/MIME E-Mail from .NET Code

Applications, specifically web applications, often rely on e-mail to send out error reports to administrators and developers.  While e-mail can be somewhat unreliable in terms of delivering messages in a timely fashion, it is also insecure.  If your application’s error reports contain identifying information about users or sensitive details about your code and what made it break, you should be delivering these messages using encrypted S/MIME e-mail.

This tutorial will show how to send an encrypted message from a .NET application. To perform this task, you will need to implement two classes – a simple object class for the e-mail message, and a utility class to handle the mail server configuration and send the messages out.  These classes rely on the following namespaces:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net.Mail;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;

The System.Security.Cryptography.Pkcs namespace is not included in default projects – you have to explicitly add a project reference to the System.Security assembly.
First, we’ll show the e-mail message object.  This class will contain properties that should be set when the object is created:

public class EncryptedMessage
{
public string Subject { get; set; }
public string ReplyAddress { get; set; }
public string Body { get; set; }
public bool IsHtml { get; set; }
private List<X509Certificate2> _recipients;
public IEnumerable<X509Certificate2> Recipients
{
     get { return _recipients; }
}
}

Next, add constructors so that the object may be instantiated:

public class EncryptedMessage
{
   // ...
   public EncryptedMessage(string subject, string replyAddress,
                           string body, bool isHtml,
                            X509Certificate2 recipient)
   {
      Init(subject, replyAddress, body, isHtml,
             new X509Certificate2[]{ recipient });
   }
   public EncryptedMessage(string subject, string replyAddress,
                           string body, bool isHtml,
                           IEnumerable<X509Certificate2> recipients)
   {
      Init(subject, replyAddress, body, isHtml,
           recipients);
   }
   // call this initialization function from any constructors
   private void Init(string subject, string replyAddress,
                     string body, bool isHtml,
                     IEnumerable<X509Certificate2> recipients)
   {
      this._recipients = new List<X509Certificate2>(recipients);
      if (_recipients.Count == 0)
      {
         throw new ArgumentException("No recipients listed");
      }
      this.Subject = subject;
      this.ReplyAddress = replyAddress;
      this.Body =
@"Content-Type: text/plain;charset=""iso-8859-1""
Content-Transfer-Encoding: quoted-printable
" + body;
      this.IsHtml = isHtml;
   }
}

Now, we also want to validate the recipient certificates to check for expiration and revocation when the EncryptedMessage object is created:

public class EncryptedMessage
{
   // ...
   private void Init(string subject, string replyAddress,
                     string body, bool isHtml,
                     IEnumerable<X509Certificate2> recipients)
   {
      this._recipients = new List<X509Certificate2>(recipients);
      if (_recipients.Count == 0)
      {
           throw new ArgumentException("No recipients listed");
      }
      this.Subject = subject;
      this.ReplyAddress = replyAddress;
      this.Body =
@"Content-Type: text/plain;charset=""iso-8859-1""
Content-Transfer-Encoding: quoted-printable
" + body;
      this.IsHtml = IsHtml;
      if (!ValidateRecipientCertificates())
      {
           throw new CryptographicException(@"One or more
recipient certificates is untrusted or not allowed for encryption");
      }
   }

   private bool ValidateRecipientCertificates()
   {
      const X509KeyUsageFlags encryptionKu =
              X509KeyUsageFlags.KeyEncipherment |
              X509KeyUsageFlags.DataEncipherment;
      X509Chain chain = new X509Chain();
      chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
      chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromMinutes(1);
      foreach (X509Certificate2 cert in this.Recipients)
      {
         if (!chain.Build(cert))
         {
            // one of the certificates is untrusted
            // or revoked
            return false;
         }
         // check the key usage to make sure the recipient
         // certificate is meant for encryption
         foreach (X509Extension ext in cert.Extensions)
         {
            if (ext is X509KeyUsageExtension)
            {
               X509KeyUsageExtension kuExt =
                        (X509KeyUsageExtension)ext;
               if (X509KeyUsageFlags.None ==
                        (kuExt.KeyUsages & encryptionKu) )
               {
                  // cert does not express encryption
                  // key usage bits
                  return false;
               }
            }
         }
      }
      return true;
   }
}

…and that’s it for the EncryptedMessage class.  Now, we need to implement a utility class to send the e-mail message.  First, construct the class so that it contains configuration information for authenticating to the SMTP server.  Depending on your e-mail architecture, this may vary for different applications.  This should typically be handled in a .settings file, but for brevity’s sake, we’ll just hardcode it all here:

public class EmailProvider
{
   private static readonly string Server = "your_server";
   private static readonly string MailUsername = "your_account";
   private static readonly string MailPassword = your_password";
   private static readonly bool UseTLS = true;
   private static readonly string EncryptionOid =
            "2.16.840.1.101.3.4.1.42"; // AES-256 CBC
}

Next, add some utility functions to extract the e-mail addresses of the recipients from the certificates and create the encrypted body content.  The e-mail address can also be read from the Subject Alternative Name extension, but in this example we’ll require the address to be present in the subject distinguished name:

public class EmailProvider
{
   // ...
   private static string GetEmailFromCert(X509Certificate2 cert)
   {
      string decodedDn = cert.SubjectName.Decode(
        X500DistinguishedNameFlags.UseNewLines);
      string[] parts = decodedDn.Split('\r', '\n');
      foreach (string p in parts)
      {
         if (p.ToUpper().StartsWith("E="))
         {
            return p.Substring("E=".Length);
         }
      }
      return null;
   }
   private static byte[] CreateBodyContent(EncryptedMessage msg)
   {
      ContentInfo content = new ContentInfo(
             ASCIIEncoding.ASCII.GetBytes(msg.Body));
      EnvelopedCms cms = new EnvelopedCms(content,
               new AlgorithmIdentifier(new Oid(EncryptionOid)));
      CmsRecipientCollection collection = new CmsRecipientCollection();
      foreach (X509Certificate2 cert in msg.Recipients)
      {
         collection.Add(new CmsRecipient(cert));
      }
      cms.Encrypt(collection);
      return cms.Encode();
   }
}

Finally, add the function to send the e-mail message:

public class EmailProvider
{
   // ...
   public static void SendMail(EncryptedMessage msg)
   {
      List<string> recipientAddresses = new List<string>();
      foreach (X509Certificate2 cert in msg.Recipients)
      {
         string recipient = GetEmailFromCert(cert);
         if (null == recipient)
         {
            // replace this exception type with a
            // custom exception class that makes sense
            // to your application
            throw new Exception(@"One or more certs does not
contain an e-mail address");
         }
         recipientAddresses.Add(recipient);
      }
      MailMessage email = new MailMessage(msg.ReplyAddress,
                                          recipientAddresses[0]);
      email.Subject = msg.Subject;
      email.IsBodyHtml = msg.IsHtml;
      for (int i = 1; i < recipientAddresses.Count; i++)
      {
           email.To.Add(recipientAddresses[i]);
      }
      // add the CMS content as an alternate view
      MemoryStream ms = new MemoryStream(CreateBodyContent(msg));
      AlternateView av = new AlternateView(ms,
"application/pkcs7-mime; smime-type=enveloped-data;name=smime.p7m");
      email.AlternateViews.Add(av);
      // send the message
      SmtpClient client = new SmtpClient(Server);
      client.Credentials = new System.Net.NetworkCredential(
                             MailUsername, MailPassword);
      client.EnableSsl = UseTLS;
      client.Send(email);
   }
}

And now your application is set up to send encrypted e-mail messages!  To send a message using these classes only takes a few lines of code:

X509Certificate2 cert; // retrieve the certificate however you want
EncryptedMessage msg = new EncryptedMessage("Test Subject",
             "no-reply@example.org",
             "test body",
             false,
             cert);
EmailProvider.SendMail(msg);

I apologize for the poor formatting of the code in this tutorial.  The site design is far from optimal for pasting source code.

Applications, specifically web applications, often rely on e-mail to send out error reports to administrators and developers.  While e-mail can be somewhat unreliable in terms of delivering messages in a timely fashion, it is also insecure.  If your application’s error reports contain identifying information about users or sensitive details about your code and what made it break, you should be delivering these messages using encrypted S/MIME e-mail.

This tutorial will show how to send an encrypted message from a .NET application.  To perform this task, you will need to implement two classes – a simple object class for the e-mail message, and a utility class to handle the mail server configuration and send the messages out.  These classes rely on the following namespaces:

using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Net.Mail;
using System.Security.Cryptography;
using System.Security.Cryptography.Pkcs;
using System.Security.Cryptography.X509Certificates;

The System.Security.Cryptography.Pkcs namespace is not included in default projects – you have to explicitly add a project reference to the System.Security assembly.
First, we’ll show the e-mail message object.  This class will contain properties that should be set when the object is created:

public class EncryptedMessage
{

public string Subject { get; set; }
public string ReplyAddress { get; set; }
public string Body { get; set; }
public bool IsHtml { get; set; }
private List<X509Certificate2> _recipients;
public IEnumerable<X509Certificate2> Recipients
{
get { return _recipients; }
}

}

Next, add constructors so that the object may be instantiated:

public class EncryptedMessage
{
// …

public EncryptedMessage(string subject, string replyAddress,
string body, bool isHtml,
X509Certificate2 recipient)
{
Init(subject, replyAddress, body, isHtml,
new X509Certificate2[]{ recipient });
}

public EncryptedMessage(string subject, string replyAddress,
string body, bool isHtml,
IEnumerable<X509Certificate2> recipients)
{
Init(subject, replyAddress, body, isHtml,
recipients);
}

// call this initialization function from any constructors
private void Init(string subject, string replyAddress,
string body, bool isHtml,
IEnumerable<X509Certificate2> recipients)
{
this._recipients = new List<X509Certificate2>(recipients);
if (_recipients.Count == 0)
{
throw new ArgumentException(“No recipients listed”);
}
this.Subject = subject;
this.ReplyAddress = replyAddress;
this.Body =
@”Content-Type: text/plain;charset=”"iso-8859-1″”
Content-Transfer-Encoding: quoted-printable

” + body;
this.IsHtml = IsHtml;
}
}

Now, we also want to validate the recipient certificates to check for expiration and revocation when the EncryptedMessage object is created:

public class EncryptedMessage
{
// …

private void Init(string subject, string replyAddress,
string body, bool isHtml,
IEnumerable<X509Certificate2> recipients)
{
this._recipients = new List<X509Certificate2>(recipients);
if (_recipients.Count == 0)
{
throw new ArgumentException(“No recipients listed”);
}
this.Subject = subject;
this.ReplyAddress = replyAddress;
this.Body =
@”Content-Type: text/plain;charset=”"iso-8859-1″”
Content-Transfer-Encoding: quoted-printable

” + body;
this.IsHtml = IsHtml;
if (!ValidateRecipientCertificates())
{
throw new CryptographicException(@”One or more
recipient certificates is untrusted or not allowed for encryption”);
}
}

private bool ValidateRecipientCertificates()
{
const X509KeyUsageFlags encryptionKu =
X509KeyUsageFlags.KeyEncipherment |
X509KeyUsageFlags.DataEncipherment;
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.Online;
chain.ChainPolicy.UrlRetrievalTimeout = TimeSpan.FromMinutes(1);
foreach (X509Certificate2 cert in this.Recipients)
{
if (!chain.Build(cert))
{
// one of the certificates is untrusted
// or revoked
return false;
}

// check the key usage to make sure the recipient
// certificate is meant for encryption
foreach (X509Extension ext in cert.Extensions)
{
if (ext is X509KeyUsageExtension)
{
X509KeyUsageExtension kuExt =
(X509KeyUsageExtension)ext;
if (X509KeyUsageFlags.None ==
(kuExt.KeyUsages & encryptionKu) )
{
// cert does not express encryption
// key usage bits
return false;
}
}
}
}
return true;
}
}

…and that’s it for the EncryptedMessage class.  Now, we need to implement a utility class to send the e-mail message.  First, construct the class so that it contains configuration information for authenticating to the SMTP server.  Depending on your e-mail architecture, this may vary for different applications.  This should typically be handled in a .settings file, but for brevity’s sake, we’ll just hardcode it all here:

public class EmailProvider
{
private static readonly string Server = “your_server”;
private static readonly string MailUsername = “your_account”;
private static readonly string MailPassword = your_password”;
private static readonly bool UseTLS = true; // or false, depending on your setup
private static readonly string EncryptionOid =
“2.16.840.1.101.3.4.1.42″; // AES-256 CBC
}

Next, add some utility functions to extract the e-mail addresses of the recipients from the certificates and create the encrypted body content.  The e-mail address can also be read from the Subject Alternative Name extension, but in this example we’ll require the address to be present in the subject distinguished name:

public class EmailProvider
{
// …

private static string GetEmailFromCert(X509Certificate2 cert)
{
string decodedDn = cert.SubjectName.Decode(X500DistinguishedNameFlags.UseNewLines);
string[] parts = decodedDn.Split(‘\r’, ‘\n’);
foreach (string p in parts)
{
if (p.ToUpper().StartsWith(“E=”))
{
return p.Substring(“E=”.Length);
}
}
return null;
}

private static byte[] CreateBodyContent(EncryptedMessage msg)
{
ContentInfo content = new ContentInfo(
ASCIIEncoding.ASCII.GetBytes(msg.Body));
EnvelopedCms cms = new EnvelopedCms(content,
new AlgorithmIdentifier(new Oid(EncryptionOid)));
CmsRecipientCollection collection = new CmsRecipientCollection();
foreach (X509Certificate2 cert in msg.Recipients)
{
collection.Add(new CmsRecipient(cert));
}
cms.Encrypt(collection);
return cms.Encode();
}
}

Finally, add the function to send the e-mail message:

public class EmailProvider
{
// …

public static void SendMail(EncryptedMessage msg)
{
List<string> recipientAddresses = new List<string>();
foreach (X509Certificate2 cert in msg.Recipients)
{
string recipient = GetEmailFromCert(cert);
if (null == recipient)
{
// replace this exception type with a
// custom exception class that makes sense
// to your application
throw new Exception(@”One or more certs does not
contain an e-mail address”);
}
recipientAddresses.Add(recipient);
}

MailMessage email = new MailMessage(msg.ReplyAddress,
recipientAddresses[0]);
email.Subject = msg.Subject;
email.IsBodyHtml = msg.IsHtml;
for (int i = 1; i < recipientAddresses.Count; i++)
{
email.To.Add(recipientAddresses[i]);
}

// add the CMS content as an alternate view
MemoryStream ms = new MemoryStream(CreateBodyContent(msg));
AlternateView av = new AlternateView(ms,
“application/pkcs7-mime; smime-type=enveloped-data;name=smime.p7m”);
email.AlternateViews.Add(av);

// send the message
SmtpClient client = new SmtpClient(Server);
client.Credentials = new System.Net.NetworkCredential(MailUsername, MailPassword);
client.EnableSsl = UseTLS;
client.Send(email);
}
}

And now your application is set up to send encrypted e-mail messages!  To send a message using these classes only takes a few lines of code:

X509Certificate2 cert; // retrieve the certificate however you want
EncryptedMessage msg = new EncryptedMessage(“Test Subject”, “no-reply@example.org”, “test body”, false, cert);
EmailProvider.SendMail(msg);

Post to Twitter Post to Facebook

One Response to “Tutorial – Sending S/MIME E-Mail from .NET Code”

  1. Santos Lovera Says:

    Thanks for sharing informations and writing this article. Looking forward to more of your stuff. Hopefully you continually update your site often since you have found a loyal visitor .