Working with S/MIME |
This topic contains the following sections:
Before you can begin using MimeKit's S/MIME support, you will need to decide which database to use for certificate storage.
If you are targetting any of the Xamarin platforms (or Linux), you won't need to do anything (although you certainly can if you want to) because, by default, MimeKit will automatically use the Mono.Data.Sqlite binding to SQLite.
If you are, however, on any of the Windows platforms, you'll need to pick a System.Data provider such as System.Data.SQLite. Once you've made your choice and installed it (via NuGet or however), you'll need to implement your own SecureMimeContext subclass. Luckily, it's very simple to do. Assuming you've chosen System.Data.SQLite, here's how you'd implement your own SecureMimeContext class:
public class MySecureMimeContext : DefaultSecureMimeContext { public MySecureMimeContext () : base (OpenDatabase ("C:\\wherever\\certdb.sqlite")) { } static IX509CertificateDatabase OpenDatabase (string fileName) { var builder = new SQLiteConnectionStringBuilder (); builder.DateTimeFormat = SQLiteDateFormats.Ticks; builder.DataSource = fileName; if (!File.Exists (fileName)) SQLiteConnection.CreateFile (fileName); var sqlite = new SQLiteConnection (builder.ConnectionString); sqlite.Open (); return new SqliteCertificateDatabase (sqlite, "password"); } }
To register your class, you can use the following code snippet:
// Note: by registering our custom context it becomes the default S/MIME context // instantiated by MimeKit when methods such as Encrypt(), Decrypt(), Sign(), and // Verify() are used without an explicit context. CryptographyContext.Register (typeof (MySecureMimeContext));
Now you are ready to encrypt, decrypt, sign and verify messages using S/MIME!
Instead of using a multipart/encrypted MIME part to encapsulate encrypted content like OpenPGP, S/MIME uses application/pkcs7-mime. To encrypt any MimeEntity, use the ApplicationPkcs7MimeEncrypt method:
public void Encrypt (MimeMessage message) { // encrypt our message body using our custom S/MIME cryptography context using (var ctx = new MySecureMimeContext ()) { // Note: this assumes that each of the recipients has an S/MIME certificate // with an X.509 Subject Email identifier that matches their email address. // // If this is not the case, you can use SecureMailboxAddresses instead of // normal MailboxAddresses which would allow you to specify the fingerprint // of their certificates. You could also choose to use one of the Encrypt() // overloads that take a list of CmsRecipients, instead. message.Body = ApplicationPkcs7Mime.Encrypt (ctx, message.To.Mailboxes, message.Body); } }
Tip |
---|
When you know that you will be encrypting a message, it may be a good idea to use a SecureMailboxAddress instead of a MailboxAddress for each of the recipients, allowing you to specify the unique fingerprint of each recipient's X.509 certificate. |
As mentioned earlier, S/MIME uses an application/pkcs7-mime part with an smime-type parameter with a value of enveloped-data to encapsulate the encrypted content.
The first thing you must do is find the ApplicationPkcs7Mime part (see the section on Working with messages).
public MimeEntity Decrypt (MimeMessage message) { var pkcs7 = message.Body as ApplicationPkcs7Mime; if (pkcs7 != null && pkcs7.SecureMimeType == SecureMimeType.EnvelopedData) { // the top-level MIME part of the message is encrypted using S/MIME return pkcs7.Decrypt (); } else { // the top-level MIME part is not encrypted return message.Body; } }
S/MIME can use either a multipart/signed MIME part or a application/pkcs7-mime MIME part for signed data.
To digitally sign a MimeEntity using a multipart/signed MIME part, it works exactly the same as it does for OpenPGP using MultipartSignedCreate
public void MultipartSign (MimeMessage message) { // digitally sign our message body using our custom S/MIME cryptography context using (var ctx = new MySecureMimeContext ()) { // Note: this assumes that the Sender address has an S/MIME signing certificate // and private key with an X.509 Subject Email identifier that matches the // sender's email address. var sender = message.From.Mailboxes.FirstOrDefault (); message.Body = MultipartSigned.Create (ctx, sender, DigestAlgorithm.Sha1, message.Body); } }
You can also do your own certificate lookups instead of relying on email addresses to match up with the user's certificate.
public void MultipartSign (MimeMessage message, X509Certificate2 certificate) { // digitally sign our message body using our custom S/MIME cryptography context using (var ctx = new MySecureMimeContext ()) { var signer = new CmsSigner (certificate) { DigestAlgorithm = DigestAlgorithm.Sha1 }; message.Body = MultipartSigned.Create (ctx, signer, message.Body); } }
You can also choose to digitially sign a MimeEntity using the application/pkcs7-mime format using ApplicationPkcs7MimeSign
public void Pkcs7Sign (MimeMessage message) { // digitally sign our message body using our custom S/MIME cryptography context using (var ctx = new MySecureMimeContext ()) { // Note: this assumes that the Sender address has an S/MIME signing certificate // and private key with an X.509 Subject Email identifier that matches the // sender's email address. var sender = message.From.Mailboxes.FirstOrDefault (); message.Body = ApplicationPkcs7Mime.Sign (ctx, sender, DigestAlgorithm.Sha1, message.Body); } }
Tip |
---|
When you know that you will be signing a message, it may be a good idea to use a SecureMailboxAddress instead of a MailboxAddress for the sender, allowing you to specify the unique fingerprint of the sender's X.509 certificate. |
As mentioned earlier, S/MIME typically uses a multipart/signed part to contain the signed content and the detached signature data.
A multipart/signed contains exactly 2 parts: the first MimeEntity is the signed content while the second MimeEntity is the detached signature and, by default, will be an ApplicationPkcs7Signature part.
Because the multipart/signed part may have been signed by multiple signers, it is important to verify each of the digital signatures (one for each signer) that are returned by the Verify method:
public void VerifyMultipartSigned (MimeMessage message) { if (message.Body is MultipartSigned) { var signed = (MultipartSigned) message.Body; foreach (var signature in signed.Verify ()) { try { bool valid = signature.Verify (); // If valid is true, then it signifies that the signed content // has not been modified since this particular signer signed the // content. // // However, if it is false, then it indicates that the signed // content has been modified. } catch (DigitalSignatureVerifyException) { // There was an error verifying the signature. } } } }
It should be noted, however, that while most S/MIME clients will use the preferred multipart/signed approach, it is possible that you may encounter an application/pkcs7-mime part with an smime-type parameter set to signed-data. Luckily, MimeKit can handle this format as well:
public void VerifyPkcs7 (MimeMessage message) { var pkcs7 = message.Body as ApplicationPkcs7Mime; if (pkcs7 != null && pkcs7.SecureMimeType == SecureMimeType.SignedData) { // extract the original content and get a list of signatures MimeEntity original; // Note: if you are rendering the message, you'll want to render the // original mime part rather than the application/pkcs7-mime part. foreach (var signature in pkcs7.Verify (out original)) { try { bool valid = signature.Verify (); // If valid is true, then it signifies that the signed content // has not been modified since this particular signer signed the // content. // // However, if it is false, then it indicates that the signed // content has been modified. } catch (DigitalSignatureVerifyException) { // There was an error verifying the signature. } } } }