Sign and encrypt e-mail with S/MIME

This guide describes using security certificates, certificate stores, public and private keys to sign and encrypt e-mails with S/MIME.


Introduction

To securely transfer an e-mail between the client and the server, SSL connection can be used. However, to protect the message from unauthorized access on all the steps of its lifecycle, you should encrypt the message itself.

Signing the message with S/MIME assures nobody has altered it during the transmission. You can additionally sign the message with DomainKeys/DKIM if you wish (but their certificates are different as S/MIME and DomainKeys/DKIM serve different purposes and use certificates of a different format).

S/MIME is all about certificates, so you'll usually need to purchase a certificate from a well-known security provider (Certification Authority, CA) unless you already have one.

Please note that MailBee.NET SMTP license does not include the license for S/MIME. You need the full MailBee.NET Objects license in order to send S/MIME signed or encrypted e-mails with MailBee.NET.

ASP.NET note. Many operations with certificates involve using Windows Registry so S/MIME functionality is very sensitive to the current user context. For instance, system-level certificate stores are practically useless for web applications as users have their certificates on their computers, in their own system-level stores. While a desktop application which runs on your computer under your user context can get your certificate, a web application running elsewhere can get access to this certificate only if you somehow upload the certificate file there.


Sign e-mail with S/MIME to prove authenticity of sender

Before sending S/MIME signed e-mail with MailBee.NET, make sure of the following:

The sample below signs the message with S/MIME and DKIM signatures (the private keys for both are taken from files), and sends the message:

Smtp mailer = new Smtp();

// Set the message properties.
mailer.Message.From.Email = "john.doe@company.com";
mailer.Message.To.Add("jane.doe@example.com");
mailer.Message.Subject = "Hello";
mailer.Message.BodyPlainText = "Hello, Jane, can we meet today?";

// Load the private key of john.doe@company.com from .PFX file.
Certificate signingCert = new Certificate(@"C:\Docs\private.pfx",
    CertFileType.Pfx, "secret");

// Sign the message with S/MIME.
Smime secureMime = new Smime();
mailer.Message = secureMime.Sign(mailer.Message, signingCert);

// Also sign the message with DKIM. It's not required for S/MIME,
// we just demonstrate you can use S/MIME and DKIM together.
// Comment the next two lines out if you don't need DKIM signature.
mailer.Message.DomainKeysSign(false, null, @"C:\Docs\rsa512.private",
    true, "dk", DomainKeysTypes.DKIM);

// Send the message via SMTP server (authentication is enabled).
mailer.SmtpServers.Add("mail.company.com", "john.doe", "secret");
mailer.Send();
Dim mailer As Smtp = New Smtp()

' Set the message properties.
mailer.Message.From.Email = "john.doe@company.com"
mailer.Message.To.Add("jane.doe@example.com")
mailer.Message.Subject = "Hello"
mailer.Message.BodyPlainText = "Hello, Jane, can we meet today?"

' Load the private key of john.doe@company.com from .PFX file.
Dim signingCert As Certificate = New Certificate("C:\Docs\private.pfx", _
    CertFileType.Pfx, "secret")

' Sign the message with S/MIME.
Dim secureMime As Smime = New Smime()
mailer.Message = secureMime.Sign(mailer.Message, signingCert)

' Also sign the message with DKIM. It's not required for S/MIME,
' we just demonstrate you can use S/MIME and DKIM together.
' Comment the next two lines out if you don't need DKIM signature.
mailer.Message.DomainKeysSign(False, Nothing, "C:\Docs\rsa512.private", _
    True, "dk", DomainKeysTypes.DKIM)

' Send the message via SMTP server (authentication is enabled).
mailer.SmtpServers.Add("mail.company.com", "john.doe", "secret")
mailer.Send()

We assume that MailBee, MailBee.Mime, MailBee.SmtpMail and MailBee.Security namespaces are already imported, MailBee.Global.LicenseKey is specified (directly in the code, or in app.config or web.config file). This license key must include MailBee.NET SMTP and MailBee.NET Security licenses (usually, MailBee.NET Objects unified key).

The sample above loaded the certificates from files. You can also load certificates from memory, or from certificate stores which in turn can be located in Windows Registry (also called System store), files or LDAP directory.

Note that in case of a web application, you cannot use System store unless you've added all the required certificates into the System store of the user whose context is used to run your ASP.NET application.

The sample below is for .NET Framework and .NET Core 1.0/1.1 (because it depends on Win32 API). In case of .NET Standard 2.0 (.NET Core 2.0 and newer, modern UWP apps, etc), working with certificate stores is different, see below.

Let's say your private certificate resides in Personal folder of System store (you can find it at Personal tab in "Internet Options/Content/Certificates" dialog of Internet Explorer). The sample below uses S/MIME to sign the message with the private key taken from System/Personal storage and sends the e-mail out:

// .NET Framework version (won't work in .NET Standard 2.0 edition).

// Open the System store (it actually resides in Windows Registry).
CertificateStore store = new CertificateStore(CertificateStore.Personal,
    CertStoreType.System, null);

// Find the certificates of john.doe@company.com
CertificateCollection certs = store.FindCertificates("john.doe@company.com",
    CertificateFields.EmailAddress);

// We can now close the certificate store (it's unmanaged resource,
// that's why we must do this manually to avoid having the resource
// opened until GC eventually frees it). Or we could simply wrap the
// section where we accessed 'store' object in C# 'using' section.
store.Dispose();

if (certs.Count == 0)
{
    Console.WriteLine("Certificate not found in the store");
    return;
}

Certificate cert = certs[0];

Smtp mailer = new Smtp();

// Set the message properties.
mailer.Message.From = new EmailAddress("john.doe@company.com", "John Doe");
mailer.Message.To.Add("jane.doe@example.com", "Jane Doe");
mailer.Message.Subject = "Hello";
mailer.Message.BodyPlainText = "Hello, Jane, can we meet today?";

// Sign the message with S/MIME.
Smime secureMime = new Smime();
mailer.Message = secureMime.Sign(mailer.Message, cert);

// Send the message via SMTP server (authentication is enabled).
mailer.SmtpServers.Add("mail.company.com", "john.doe", "secret");
mailer.Send();
' .NET Framework version (won't work in .NET Standard 2.0 edition).

' Open the System store (it actually resides in Windows Registry).
Dim store As CertificateStore = New CertificateStore(CertificateStore.Personal, _
    CertStoreType.System, Nothing)

' Find the certificates of john.doe@company.com
Dim certs As CertificateCollection = store.FindCertificates("john.doe@company.com", _
    CertificateFields.EmailAddress)

' We can now close the certificate store (it's unmanaged resource,
' that's why we must do this manually to avoid having the resource
' opened until GC eventually frees it).
store.Dispose()

If certs.Count = 0 Then
    Console.WriteLine("Certificate not found in the store")
    Return
End If

Dim cert As Certificate = New Certificate(certs(0))

Dim mailer As Smtp = New Smtp()

' Set the message properties.
mailer.Message.From = New EmailAddress("john.doe@company.com", "John Doe")
mailer.Message.To.Add("jane.doe@example.com", "Jane Doe")
mailer.Message.Subject = "Hello"
mailer.Message.BodyPlainText = "Hello, Jane, can we meet today?"

' Sign the message with S/MIME.
Dim secureMime As Smime = New Smime()
mailer.Message = secureMime.Sign(mailer.Message, cert)

' Send the message via SMTP server (authentication is enabled).
mailer.SmtpServers.Add("mail.company.com", "john.doe", "secret")
mailer.Send()

Now the version of the same code written in .NET Standard 2.0 way (only the section which gets the signing certificate as the rest of the code is the same). Note that you can use this code in .NET Framework version as well (it supports both ways, via MailBee's CertificateStore and .NET SDK's X509Store):

// Alternate way of finding the certificate in Personal certificate store, .NET Standard 2.0 compatible.

using System.Security.Cryptography.X509Certificates;

...

X509Store personalX509Store = new X509Store(StoreName.My, StoreLocation.CurrentUser);
personalX509Store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
X509Certificate2Collection x509Certs = personalX509Store.Certificates.Find(X509FindType.FindBySubjectName, "john.doe@company.com", false);
personalX509Store.Close();
if (x509Certs.Count == 0)
{
    Console.WriteLine("Certificate not found in the store");
    return;
}
Certificate cert = new Certificate(x509Certs[0]);
' Alternate way of finding the certificate in Personal certificate store, .NET Standard 2.0 compatible.

Imports System.Security.Cryptography.X509Certificates

...

Dim personalX509Store As X509Store = New X509Store(StoreName.My, StoreLocation.CurrentUser)
personalX509Store.Open(OpenFlags.OpenExistingOnly Or OpenFlags.ReadOnly)
Dim x509Certs As X509Certificate2Collection = personalX509Store.Certificates.Find(X509FindType.FindBySubjectName, "john.doe@company.com", False)
personalX509Store.Close()

If x509Certs.Count = 0 Then
	Console.WriteLine("Certificate not found in the store")
	Return
End If

Dim cert As Certificate = New Certificate(x509Certs(0))

Sign and encrypt e-mail with S/MIME to ensure privacy and security

In order to encrypt an e-mail, you need to make sure your recipients will be able to decrypt it. This makes encryption and decryption procedure quite different from the signing and verifying the signature.

When you sign the message, you use your private key. But when you encrypt it, you use the public key of your recipient. If there are many recipients, you'll need to obtain the public key of every recipient.

When the recipient receives a signed message, he/she uses the public key encapsulated into the message to verify it. In other words, signing does not require the opposite party to have any security keys in order to complete the signature verification process.

When the recipient receives an encrypted message, he/she uses one's private key to decrypt it. Of course, this private key must match the public key this person previously gave you to let you encrypt e-mails for this person.

No need to transfer the public key from the recipient to the sender only via a secure channel, you can even publish it on the web. Only private keys must be kept in safety.

The .NET Framework sample below creates an e-mail, signs it with your private key taken from Personal folder of System store, encrypts it with the public keys of two people, and sends it out. For demonstration purposes, we take one public key from a file and another public key from your Address Book that resides in System store:

// Open Personal folder of System store.
CertificateStore store = new CertificateStore(CertificateStore.Personal,
    CertStoreType.System, null);

// Find the certificates of john.doe@company.com (sender).
CertificateCollection certs = store.FindCertificates("john.doe@company.com",
    CertificateFields.EmailAddress);

// Close the certificate store to free unmanaged resources.
store.Dispose();

if (certs.Count == 0)
{
    Console.WriteLine("Sender's certificate not found in the store");
    return;
}

Certificate senderCert = certs[0];

// Open Address Book.
store = new CertificateStore(CertificateStore.OtherPeople,
    CertStoreType.System, null);

// Find the certificates of jane.doe@example.com (one of recipients).
certs = store.FindCertificates("jane.doe@example.com",
    CertificateFields.EmailAddress);

// Close the certificate store to free unmanaged resources.
store.Dispose();

if (certs.Count == 0)
{
    Console.WriteLine("Recipient's certificate not found in the store");
    return;
}

// The public key of the recipient from Address Book.
Certificate recipientCert1 = certs[0];

// Load the public key of another recipient from .CER file.
Certificate recipientCert2 = new Certificate(@"C:\Docs\alice.cer",
    CertFileType.Cer, null);

// Make up the list of public certificates of all recipients.
certs.Clear();
certs.Add(recipientCert1);
certs.Add(recipientCert2);

Smtp mailer = new Smtp();

// Set the message properties (and demonstrate different methods
// of setting email addresses in From and To).
mailer.Message.From = new EmailAddress("john.doe@company.com", "John Doe");
mailer.Message.To.Add("jane.doe@example.com", "Jane Doe");
mailer.Message.To.AddFromString("Alice <alice@there.com>");
mailer.Message.Subject = "Hello";
mailer.Message.BodyPlainText = "Hello, Jane and Alice, can we meet today?";

// Sign and encrypt the message with S/MIME.
Smime secureMime = new Smime();
mailer.Message = secureMime.SignAndEncrypt(mailer.Message, senderCert, certs);

// Send the message via SMTP server (authentication is enabled).
mailer.SmtpServers.Add("mail.company.com", "john.doe", "secret");
mailer.Send();
' Open Personal folder of System store.
Dim store As CertificateStore = New CertificateStore(CertificateStore.Personal, _
    CertStoreType.System, Nothing)

' Find the certificates of john.doe@company.com (sender).
Dim certs As CertificateCollection = store.FindCertificates("john.doe@company.com", _
    CertificateFields.EmailAddress)

' Close the certificate store to free unmanaged resources.
store.Dispose()

If certs.Count = 0 Then
    Console.WriteLine("Sender's certificate not found in the store")
    Return
End If

Dim senderCert As Certificate = certs(0)

' Open Address Book.
store = New CertificateStore(CertificateStore.OtherPeople, _
    CertStoreType.System, Nothing)

' Find the certificates of jane.doe@example.com (one of recipients).
certs = store.FindCertificates("jane.doe@example.com", _
    CertificateFields.EmailAddress)

' Close the certificate store to free unmanaged resources.
store.Dispose()

If certs.Count = 0 Then
    Console.WriteLine("Recipient's certificate not found in the store")
    Return
End If

' The public key of the recipient from Address Book.
Dim recipientCert1 As Certificate = certs(0)

' Load the public key of another recipient from .CER file.
Dim recipientCert2 As Certificate = New Certificate("C:\Docs\alice.cer", _
    CertFileType.Cer, Nothing)

' Make up the list of public certificates of all recipients.
certs.Clear()
certs.Add(recipientCert1)
certs.Add(recipientCert2)

Dim mailer As Smtp = New Smtp()

' Set the message properties (and demonstrate different methods
' of setting email addresses in From and To).
mailer.Message.From = New EmailAddress("john.doe@company.com", "John Doe")
mailer.Message.To.Add("jane.doe@example.com", "Jane Doe")
mailer.Message.To.AddFromString("Alice <alice@there.com>")
mailer.Message.Subject = "Hello"
mailer.Message.BodyPlainText = "Hello, Jane and Alice, can we meet today?"

' Sign and encrypt the message with S/MIME.
Dim secureMime As Smime = New Smime()
mailer.Message = secureMime.SignAndEncrypt(mailer.Message, senderCert, certs)

' Send the message via SMTP server (authentication is enabled).
mailer.SmtpServers.Add("mail.company.com", "john.doe", "secret")
mailer.Send()

The sample above uses CertificateStore class which is not fully supported in .NET Standard 2.0 edition. The below is the version of the sample which you can use in .NET Core 2.0 and newer. It contains only the section where certificates are being read, the rest of the sample is the same as before.

// Alternate way of finding the certificate in Personal and AddressBook certificate stores, .NET Standard 2.0 compatible.

using System.Security.Cryptography.X509Certificates;

...

Certificate senderCert = null;

// Open Personal folder of System store.
using (X509Store personalX509Store = new X509Store(StoreName.My, StoreLocation.CurrentUser))
{
    personalX509Store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
    X509Certificate2Collection x509Certs = personalX509Store.Certificates.Find(X509FindType.FindBySubjectName, "john.doe@company.com", false);
    personalX509Store.Close();
    Certificate cer = new Certificate(x509Certs[0]);

    if (x509Certs.Count == 0)
    {
        Console.WriteLine("Sender's certificate not found in the store");
        return;
    }

    senderCert = new Certificate(x509Certs[0]);
}

Certificate recipientCert1 = null;

// Open AddressBook folder of System store.
using (X509Store personalX509Store = new X509Store(StoreName.AddressBook, StoreLocation.CurrentUser))
{
    personalX509Store.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
    X509Certificate2Collection x509Certs = personalX509Store.Certificates.Find(X509FindType.FindBySubjectName, "jane.doe@company.com", false);
    personalX509Store.Close();
    Certificate cer = new Certificate(x509Certs[0]);

    if (x509Certs.Count == 0)
    {
        Console.WriteLine("Recipient's certificate not found in the store");
        return;
    }

    // The public key of the recipient from Address Book.
    recipientCert1 = new Certificate(x509Certs[0]);
}

// Load the public key of another recipient from .CER file.
Certificate recipientCert2 = new Certificate(@"C:\Docs\alice.cer", CertFileType.Cer, null);

// Make up the list of public certificates of all recipients.
CertificateCollection certs = new CertificateCollection();
certs.Add(recipientCert2);
certs.Add(recipientCert1);
' Alternate way of finding the certificate in Personal and AddressBook certificate stores, .NET Standard 2.0 compatible.

Imports System.Security.Cryptography.X509Certificates

...

Dim senderCert As Certificate = Nothing

' Open Personal folder of System store.
Using personalX509Store As X509Store = New X509Store(StoreName.My, StoreLocation.CurrentUser)
	personalX509Store.Open(OpenFlags.OpenExistingOnly Or OpenFlags.ReadOnly)
	Dim x509Certs As X509Certificate2Collection = personalX509Store.Certificates.Find(X509FindType.FindBySubjectName, "john.doe@company.com", False)
	personalX509Store.Close
	Dim cer As Certificate = New Certificate(x509Certs(0))

	If x509Certs.Count = 0 Then
		Console.WriteLine("Sender's certificate not found in the store")
		Return
	End If

	senderCert = New Certificate(x509Certs(0))
End Using

Dim recipientCert1 As Certificate = Nothing

' Open AddressBook folder of System store.
Using personalX509Store As X509Store = New X509Store(StoreName.AddressBook, StoreLocation.CurrentUser)
	personalX509Store.Open(OpenFlags.OpenExistingOnly Or OpenFlags.ReadOnly)
	Dim x509Certs As X509Certificate2Collection = personalX509Store.Certificates.Find(X509FindType.FindBySubjectName, "jane.doe@company.com", False)
	personalX509Store.Close
	Dim cer As Certificate = New Certificate(x509Certs(0))

	If x509Certs.Count = 0 Then
		Console.WriteLine("Recipient's certificate not found in the store")
		Return
	End If

	recipientCert1 = New Certificate(x509Certs(0))
End Using

' Load the public key of another recipient from .CER file.
Dim recipientCert2 As Certificate = New Certificate("C:\Docs\alice.cer", CertFileType.Cer, Nothing)

' Make up the list of public certificates of all recipients.
Dim certs As CertificateCollection = New CertificateCollection
certs.Add(recipientCert2)
certs.Add(recipientCert1)

To view which certificates you have in Address Book, open Other People tab in "Internet Options/Content/Certificates" dialog of Internet Explorer or in Other People folder of "Manage user certificates" in Control Panel.

However, this does not make sense for a web application as it usually runs on another computer (web server), not on the user's machine. And even when you run the web application on your own computer, it's usually executed under the context of a system user, not your own user. This means your web app won't be able to see/access anything in Other People storage. In case of web apps, you can store the required certificates elsewhere, such as in files or database. Certificate class provides a number of constructors which let you read and write certificates from/to files or memory (e.g. for storing this data in database).


Send feedback to AfterLogic

Copyright © 2006-2023 AfterLogic Corporation. All rights reserved.