Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

An S/MIME Library for Sending Signed and Encrypted E-mail

4.96/5 (51 votes)
15 Jul 2010CPOL8 min read 246K   5K  
Send signed and encrypted e-mail in accordance with RFC 2633
SignedEmail.png

Overview

E-mail (in its simplest form) is inherently insecure. There's no built-in encryption, so it's easy to snoop on people's messages as they're sent across the network, and there's no built-in validation, so it's easy to spoof a message to make it look like it came from somebody else. If you're a mischievous college student, you can exploit these vulnerabilities to read your roommate's mail, or send fake love letters from your roommate to the creepy goth chick down the hall. But if you're an actual criminal, you can exploit these vulnerabilities to commit fraud, industrial espionage, or treason. S/MIME solves these problems by providing certain cryptographic guarantees, including authentication, message integrity, and privacy.

Why S/MIME?

Guaranteeing authentication, message integrity, and privacy (and making it all easy-to-use) is actually a really hard thing to do. A search of the web for "encrypted e-mail" turns up solutions like this one, but these types of applications tend to suffer from the same types of problems.

  1. Key Distribution: Home-grown systems tend to use a single symmetric key that all trusted parties share. But trying to let the trusted parties know what the key is results in a chicken-and-egg scenario. There's no secure channel without the secret key, so that means that you have to send the secret key to all trusted parties in an insecure way. A better system would allow you to negotiate a channel in a secure way, without having to distribute any secret information ahead of time.
  2. Key Management: A single shared key creates two classes of people. If you know the key, you're trusted; if not, you're untrusted. That's a pretty coarse distinction. Here's an imaginary scenario. You have 5 friends who all have the shared key, and you're planning a surprise party for one of them. You want to send a message that can be read by everyone except the guy you're throwing the party for. A single shared key doesn't allow for that type of distinction. A better system would allow you to control which users can decrypt an encrypted message on a user-by-user basis.
  3. Sender Authentication: If the secret key is shared by, say, 3 people, then you have a guarantee that if you receive an encrypted message, it came from one of those 3 people. But you don't have any assurances beyond that. If Alice, Bob, and Charlie all share a key, and Alice receives an encrypted message that seems to come from Bob, she can be sure that an untrusted person (like Dorothy) didn't spoof the message, but she can't be sure that a trusted person (like Charlie) didn't spoof it. A better system would provide for strong sender authentication to ensure that nobody can convincingly spoof a message.
  4. Implementation: Home-grown systems tend to cut corners in their implementations. The above-referenced example solution is password-based, which almost certainly means that the derived encryption keys won't actually be as strong as they should be (most passwords don't have anywhere near the strength of a 128-bit encryption key), and it handles initialization vectors poorly. Whenever possible, it's better to use security protocols that were designed by people who actually know what they're doing, rather than growing your own.

S/MIME is designed to solve all of these problems. It's a well-defined standard (RFC 2633), and it's implemented in all the major mail clients (although I only tested Outlook, Outlook Express, and Mozilla Thunderbird). It is not, however, natively supported by .NET. That's where this library comes in.

Assumptions

I'm going to assume that you already know something about public-key cryptography in general, and about S/MIME in particular. I'm also going to assume that you already have a signing certificate for yourself (complete with private key), and the encryption certificates of anybody you want to send encrypted mail to (without the private key, of course). If you and your friends don't already have certificates, they're pretty easy to come by. I got my personal signing/encryption certificate from Verisign (at about $20/year, they're pretty cheap).

Using the Code

The object model was designed to resemble the System.Net.Mail classes pretty closely, so you should find it to be pretty intuitive. The primary differences just involve certificate handling.

If you're going to send a signed message, you need to create a SecureMailAddress that has a signing cert associated with it. Your cert will either be in the certificate store, or it'll be in a file (like a .pfx file). If you're reading the certificate out of a file, the code to create a SecureMailAddress for the sender will look something like this:

C#
X509Certificate2 myCert = 
	new X509Certificate2(@"c:\certs\myCert.pfx", "SomeSecretPassword");

Reading a certificate out of the cert store is a bit more work, so I've included a helper method to do it for you. Just specify the serial number, and it'll retrieve the certificate from your local store.

C#
X509Certificate2 myCertFromStore = CryptoHelper.FindCertificate("1B37D3");

Once you've loaded your certificates, you need to attach them to a SecureMailAddress, like this:

C#
SecureMailAddress senderAddress = new SecureMailAddress
	("alice@cynicalpirate.com", "Alice", aliceEncryptionCert, aliceSigningCert);
 
SecureMailAddress recipientAddress = new SecureMailAddress
	("bob@cynicalpirate.com", "Bob", bobEncryptionCert);

Notice that we specified a signing cert for the sender, but we only need one if we're planning on sending a signed message. For all the recipients, we only need the encryption certs. Also note that we need to specify the sender's encryption cert when we send an encrypted message, in addition to each of the recipients' encryption certs. If we didn't, then the sender wouldn't be able to read his own message, even though he's the one who sent it.

The SecureMailMessage class supports most of the stuff that the regular System.Net.Mail.MailMessage class can do. You can CC and Bcc recipients, set a ReplyTo address, send attachments, and send HTML mail. Here's a full example which demonstrates, front-to-back, how to send a signed and encrypted message.

C#
SecureMailMessage message = new SecureMailMessage();
 
// Look up your signing cert by serial number in your cert store
X509Certificate2 signingCert = CryptoHelper.FindCertificate("1B37D3");
// Look up your encryption cert the same way
X509Certificate2 encryptionCert = CryptoHelper.FindCertificate("22C590");
 
// Load the recipient's encryption cert from a file.
X509Certificate2 recipientCert = new X509Certificate2(@"c:\certs\bob.cer");
 
message.From = new SecureMailAddress
	("alice@cynicalpirate.com", "Alice", encryptionCert, signingCert);
message.To.Add(new SecureMailAddress
	("bob@cynicalpirate.com", "Bob", recipientCert));
 
message.Subject = "This is a signed and encrypted message";
 
message.Body = "<h2>Sent from the Cpi.Net.SecureMail library!</h2>";
message.IsBodyHtml = true;
 
message.IsSigned = true;
 
message.IsEncrypted = true;
 
// Instantiate a good old-fashioned SmtpClient to send your message
System.Net.Mail.SmtpClient client = 
		new System.Net.Mail.SmtpClient("mymailserver", 25);
 
// If your SMTP server requires you to authenticate, you need to specify your
// username and password here.
client.Credentials = new NetworkCredential("YourSmtpUserName", "YourSmtpPassword");
 
client.Send(message);

Notice that you can use the regular System.Net.Mail.SmtpClient class to send the message. That's because the SecureMailMessage class has an implicit casting operator which turns it into a System.Net.Mail.MailMessage object.

Technical Notes

  • This library signs, encrypts, and formats the message in accordance with RFC 2633, but it's important to note that I didn't personally implement the actual cryptographic stuff involved in the signing and encryption. (I'm not qualified to implement cryptographic algorithms securely. Neither are you, in all likelihood. Don't try it.) The cryptographic stuff is handled by the classes in the System.Security.Cryptography.Pkcs namespace, which was introduced in .NET 2.0. Thanks, Microsoft, for doing a lot of the heavy lifting here.
  • The library is, to the best of my knowledge, a compliant implementation of RFC 2633, but it isn't a complete implementation. It implements all of the MUSTs in the RFC, but not all of the SHOULDs. This means that any compliant mail client should be able to read the messages that this library sends. (It's been tested in Outlook, Outlook Express, and Mozilla Thunderbird.)
  • I built this library partially by reading the appropriate specifications, and partially by setting up a homegrown TCP proxy and snooping on the traffic that existing mail clients send. (The TCP proxy isn't ready for prime-time, but it'll probably make an appearance in a future article.) There are some places in the spec that allow certain freedoms to individual implementations (such as whether to sign first, then encrypt, or encrypt first, then sign. The spec allows arbitrarily nested layers of signing and encryption.) In these cases, I just copied what the existing implementations do. (In this particular case, sign first, then encrypt.)
  • One of the constructors for SecureMailMessage accepts both a signing certificate and an encryption certificate. It's possible that you have a single certificate that you use for both signing and encryption. (That's how my personal certificate works.) The reason that they're separated into two separate parameters is because some organizations (like my workplace) issue separate certificates for signing and encryption. That way, the organization can keep a backup of your encryption certificate (so they can read your mail if you quit, or die, or if they just feel like it), but they don't keep a backup of your signing certificate (because if they did, then a malicious sysadmin could still send mail signed as you, which defeats the whole purpose of cryptographic signatures). If you have separate certificates for signing and encryption, then supply both of them to the corresponding SecureMailAddress constructor parameters. If you only have one, though, just supply it as both the signing cert and the encryption cert.

Personal Notes

  • This is a pretty serious topic, and is closely related to the type of stuff I do professionally, so I've treated it a bit more seriously than some of my previous articles, which were just for fun. (There are, for example, no toy airplanes or dead fish.) Sorry about that.
  • Whenever you talk about cryptography, there's a standard set of imaginary users that you reference (Alice, Bob, Charlie, etc.) in your examples. I'll admit that I sometimes feel closer to these imaginary people than I do to my own friends and co-workers. (Except for Mallory, of course...I hate her so much!)

History

  • August 23, 2009 - Initial posting
  • February 19, 2010 - Fixed a bug which prevented non-ASCII characters from being encoded correctly in encrypted e-mail
  • March 12, 2010 - Fixed a bug which caused a NullReferenceException if you sent a message with an empty body
  • July 14, 2010 - Fixed a bug which caused certain messages to become corrupted if any lines start with a period. Stupid SMTP protocol...

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)