S/MIME Tutorial

Applies to: Rebex Total Pack, Rebex Secure Mail

Namespaces and assemblies 

To be able to sign, validate, encrypt or decrypt S/MIME e-mail messages, several assemblies are needed: Rebex.Mail.dll, Rebex.Common.dll and Rebex.Networking.dll. Rebex.Mail.dll contains classes that enable you to create, read, process and save e-mail messages in MIME format using the MailMessage and related classes that reside in Rebex.Mail namespace. Also, it contains the Rebex.Mime.Headers namespace with a number of classes that represent mail message headers.

To gain access to all described functionality, reference the Rebex.Mail.dll and Rebex.Common.dll assemblies from your project and import the following namespaces in your source files:

C#

using Rebex.Mail;
using Rebex.Mime.Headers;
using Rebex.Security.Certificates;

VB.NET

Imports Rebex.Mail
Imports Rebex.Mime.Headers
Imports Rebex.Security.Certificates

Creating a signed mail message 

Creating a signed mail message is simple. To be able to sign it, we need a X509 certificate and an associated private key. The certificate is issued by a certification authority and is bound to a specific person. It is not secret - quite the opposite in fact, because the recipient needs your certificate to be able to validate your signature. To validate a signature, only the certificate is needed. To sign a message, you need the private key as well.

To sign a message, we can either load the certificate from a PKCS#12 encrypted file (.pfx or .p12 extensions) that contains the certificate and its private key, or retrieve it from the Windows certificate store. Other files that might contain certificates are .cer or .der, but these only contain the certificate without the private key, and cannot be used to sign mail.

Please note that only the message content is signed - this does NOT include the top-level headers, which are regarded as an envelope information anyone can read and modify.

C#

using Rebex.Net;
using Rebex.Mime.Headers;
using Rebex.Security.Certificates;
...

// load the certificate and associated private key from a file
Certificate signer = Certificate.LoadPfx("hugo.pfx", "password");

// create an instance of MailMessage
MailMessage message = new MailMessage();

// set its properties to desired values
message.From = "hugo@example.com";
message.To = "joe@example.com";
message.Subject = "This is a simple message";
message.BodyText = "Hello, Joe!";
message.BodyHtml = "Hello, <b>Joe</b>!";

// and sign it using Hugo's certificate
message.Sign(signer);

VB.NET

Imports Rebex.Mail
Imports Rebex.Mime.Headers
Imports Rebex.Security.Certificates
...

' load the certificate and associated private key from a file
Dim signer As Certificate = Certificate.LoadPfx("hugo.pfx", "password")

'create an instance of MailMessage
Dim message As New MailMessage

'and set its properties to desired values
message.From = New MailAddressCollection("hugo@example.com")
message.To = New MailAddressCollection("joe@example.com")
message.Subject = "This is a simple message"
message.BodyText = "Hello, Joe!"
message.BodyHtml = "Hello, <b>Joe</b>!"

' and sign it using Hugo's certificate
message.Sign(signer)

And what if you wanted to load the certificate from your personal store instead of a disk file? Check out the next section.

Certificate stores 

There are several built-in and well-known certificate stores in Windows. They might already contain certificates (both with or without a private key) used by Internet Explorer, Outlook Express or other applications. These stores are per-user and can be managed by a MMC snap-in called "Certificates" or using Internet Explorer - just select "Tools->Internet Options...->Content->Certificates..." from its menu.

These stores are accessible using our CertificateStore class from Rebex.Security.Certificates namespace.

C#

// first, open the user's personal certificate store
CertificateStore my = new CertificateStore(CertificateStoreName.My);

// search the store for certificates matching the desired e-mail address
// (and make sure the certificate has an associated private key and is not expired)
CertificateFindOptions options =
    CertificateFindOptions.HasPrivateKey |
    CertificateFindOptions.IsTimeValid;
Certificate[] certificates =
    my.FindCertificatesForMailAddress("hugo@example.com", options);

// checking whether we actually found any suitable certificate is a good idea
if (certificates.Length == 0)
    throw new ApplicationException("Hugo's certificate was not found.");

// and if we did, use it to sign an e-mail message, as seen in the previous section
Certificate signer = certificates[0];

VB.NET

' first, open the user's personal certificate store
Dim my As New CertificateStore(CertificateStoreName.My)

' search the store for certificates matching the desired e-mail address
' (and make sure the certificate has an associated private key and is not expired)
Dim options As CertificateFindOptions = _
 CertificateFindOptions.HasPrivateKey Or _
 CertificateFindOptions.IsTimeValid
Dim certificates() As Certificate = _
 my.FindCertificatesForMailAddress("hugo@example.com", options)

' checking whether we actually found any suitable certificate is a good idea
If certificates.Length = 0 Then
    Throw New ApplicationException("Hugo's certificate was not found.")
End If

' and if we did, use it to sign an e-mail message, as seen in the previous section
Dim signer As Certificate = certificates(0)

Validating signatures 

Once you receive a signed mail message, you should validate the signature to make sure the message was indeed composed by the signer. The MailMessage class has a method called ValidateSignature to facilitate this. Be sure to check whether the message is indeed signed (using the IsSigned property), because it is not possible to validate a signature of a message that doesn't have any, obviously.

C#

// create an instance of MailMessage
MailMessage message = new MailMessage();

// load the message from a local disk file
message.Load("c:\\message.eml");

// validate the signature if the message is signed
if (message.IsSigned)
{
    MailSignatureValidity result = message.ValidateSignature();
    if (result.Valid)
        Console.WriteLine("The message is signed and the signature is valid.");
    else
        Console.WriteLine("The message is signed, but the signature is not valid.");
}
else
{
    Console.WriteLine("The message is not signed.");
}

VB.NET

'create an instance of MailMessage
Dim message As New MailMessage

'load the message from a local disk file
message.Load("c:\message.eml")

'validate the signature if the message is signed
If message.IsSigned Then
    Dim result As MailSignatureValidity = message.ValidateSignature()
    If result.Valid Then
        Console.WriteLine("The message is signed and the signature is valid.")
    Else
        Console.WriteLine("The message is signed, but the signature is not valid.")
    End If
Else
    Console.WriteLine("The message is not signed.")
End If

The MailSignatureValidity class returned by ValidateSignature also contains various flags that can be used to determine why exactly the validation failed. Consult the reference for more information.

Creating an encrypted mail message 

Creating an encrypted message is similar to creating a signed message. However, to encrypt a message, only an X509 certificate is needed, and no private key. The private key is only needed to be able to decrypt the message. This means that anyone can enrypt a message for you, and only you are able to decrypt it.

To encrypt a message, we can either load the certificate from a certificate file (.cer or .der extensions), from a PKCS#12 encrypted file (.pfx or .p12 extensions) that also contains the private key (not needed for encryption though), or retrieve it from the Windows certificate store.

Please note that only the message content is encrypted - this does NOT include the top-level headers, which are regarded as an envelope information anyone can read and modify.

C#

using Rebex.Mail;
using Rebex.Mime.Headers;
using Rebex.Security.Certificates;
...

// load the certificates
Certificate sender = Certificate.LoadDer("hugo.cer");
Certificate recipient = Certificate.LoadDer("joe.cer");

// create an instance of MailMessage
MailMessage message = new MailMessage();

// set its properties to desired values
message.From = "hugo@example.com";
message.To = "joe@example.com";
message.Subject = "This is a simple message";
message.BodyText = "Hello, Joe!";
message.BodyHtml = "Hello, <b>Joe</b>!";

// Encrypt it using both Joe's and Hugo's certificates.
// When using an alternate message.Encrypt(recipient) call,
// only the reciepient will be able to decrypt the message.
// Following code will allow the sender to decrypt it later as well.
message.Encrypt(recipient, sender);

VB.NET

Imports Rebex.Mail
Imports Rebex.Mime.Headers
Imports Rebex.Security.Certificates
...

' load the certificates and associated private key from a file
Dim sender As Certificate = Certificate.LoadPfx("hugo.pfx", "password")
Dim recipient As Certificate = Certificate.LoadPfx("joe.pfx", "password")

'create an instance of MailMessage
Dim message As New MailMessage

'and set its properties to desired values
message.From = New MailAddressCollection("hugo@example.com")
message.To = New MailAddressCollection("joe@example.com")
message.Subject = "This is a simple message"
message.BodyText = "Hello, Joe!"
message.BodyHtml = "Hello, <b>Joe</b>!"

' Encrypt it using both Joe's and Hugo's certificates.
' When using an alternate message.Encrypt(recipient) call,
' only the reciepient will be able to decrypt the message.
' Following code will allow the sender to decrypt it later as well.
message.Encrypt(recipient, sender)

Once you encrypt the message using another person's certificate, you won't be able to decrypt it yourself. For this reason, encrypted messages are usually encrypted not only using the recipient's certificate, but the signer's certificate as well. Check out Signed and encrypted message section for more information

Decrypting an encrypted mail message 

Once you receive an encrypted message, you have to decrypt it to be able to access its content. The MailMessage class has a Decrypt method to facilitate this. Be sure to check whether the message is indeed encrypted (using the IsEncrypted property), because it is not possible to decrypt a message that has not been encrypted, obviously. Also, you will only be able to decrypt the message if you have a private key for one of the certificates for which the message was encrypted. Use the CanDecrypt property to check whether decryption is indeed possible. By default, MailMessage searches for the private key in your certificate store, but you can make it use a .p12/.pfx file instead if needed.

Please note that you don't need to decrypt the message to be able to access its top-level headers, which are regarded as an envelope information anyone can read and modify.

C#

// create an instance of MailMessage
MailMessage message = new MailMessage();

// add this line if the private key to decrypt the message is not
// in your certificate store, but in a .P12/.PFX file
//Certificate certificate = Certificate.LoadPfx("PrivateKey.pfx", "password");
//message.CertificateFinder = CertificateFinder.CreateFinder(certificate);

// load the message from a local disk file
message.Load("c:\\message.eml");

// decrypt the message if it is encrypted
if (message.IsEncrypted)
{
    if (!message.CanDecrypt)
        throw new ApplicationException
        (
            "Message cannot be decrypted. You do not have the private key."
        );

    message.Decrypt();
}

// you can access the message content now

VB.NET

'create an instance of MailMessage
Dim message As New MailMessage

'add this line if the private key to decrypt the message is not
'in your certificate store, but in a .p12/.pfx file
' Dim certificate As Certificate = certificate.LoadPfx("c:\PrivateKey.pfx", "password")
' message.CertificateFinder = CertificateFinder.CreateFinder(Certificate)

'load the message from a local disk file
message.Load("c:\message.eml")

'decrypt the message if it is encrypted
If message.IsEncrypted Then
    If Not message.CanDecrypt Then
        Throw New ApplicationException _
        ( _
          "Message cannot be decrypted. You do not have the private key." _
        )
    End If

    message.Decrypt()
End If

'you can access the message content now

Creating a message that is both encrypted and signed 

Please read the Creating a signed message and Creating an encrypted message sections unless you are already familiar with these topics.

A message can be both encrypted and signed. This ensures to the sender that only the intended recipients are able to read the message content, and to the recipient that the message was indeed sent by the sender.

There are two ways to produce a message that is both encrypted and sign:

  • Sign the message first, then encrypt the signed message.
  • Encrypt the message first, then sign the encrypted message.

In the first case, no one but the recipient will be able to validate the signature and access the message content. In the second case, no one but the recipient will be able to access the message content, but anyone will be able to validate the signature. Both ways have their pros and cons, but there is a very strong reason to prefer the first way: Outlook Express does not handle the second way correctly, and reports the correct signature is not valid.

C#

using Rebex.Mail;
using Rebex.Mime.Headers;
using Rebex.Security.Certificates;
...

// load the sender's certificate and
// associated private key from a file
Certificate signer = Certificate.LoadPfx("hugo.pfx", "password");

// load the recipient's certificate
Certificate recipient = Certificate.LoadDer("joe.cer");

// create an instance of MailMessage
MailMessage message = new MailMessage();

// set its properties to desired values
message.From = "hugo@example.com";
message.To = "joe@example.com";
message.Subject = "This is a simple message";
message.BodyText = "Hello, Joe!";
message.BodyHtml = "Hello, <b>Joe</b>!";

// sign the message using Hugo's certificate
message.Sign(signer);

// and encrypt it using Joe's certificate
message.Encrypt(recipient);

// if you wanted Hugo to be able to read the message later as well,
// you can encrypt it for Hugo as well instead - comment out the previous
// encrypt and uncomment this one:
// message.Encrypt(recipient, signer)

VB.NET

Imports Rebex.Mail
Imports Rebex.Mime.Headers
Imports Rebex.Security.Certificates
...

'load the sender's certificate and
'associated private key from a file
Dim signer As Certificate = Certificate.LoadPfx("hugo.pfg", "password")

'load the recipient's certificate
Dim recipient As Certificate = Certificate.LoadDer("joe.cer")

'create an instance of MailMessage
Dim message As New MailMessage

'and set its properties to desired values
message.From = New MailAddressCollection("hugo@example.com")
message.To = New MailAddressCollection("joe@example.com")
message.Subject = "This is a simple message"
message.BodyText = "Hello, Joe!"
message.BodyHtml = "Hello, <b>Joe</b>!"

'sign the message using Hugo's certificate
message.Sign(signer)

'and encrypt it using Joe's certificate
message.Encrypt(recipient)

'if you wanted Hugo to be able to read the message later as well,
'you can encrypt it for Hugo as well instead - comment out the previous
'encrypt and uncomment this one:
'message.Encrypt(recipient, signer)

Once you receive a message that is both signed and encrypted, you can both decrypt it and validate the signature. Check out Decrypting an encrypted mail message and Validating signatures sections for details.

However, for a message that is signed first and then encrypted, you have to decrypt it first to be able to validate the signature, because the signature is encrypted as well as the message content. In fact, unless a message is decrypted, we cannot know whether it is signed as well or not, and the IsSigned property is actually set to false at this time.

Back to tutorial list...