Personal S/MIME

PUBLISHED ON SEP 21, 2014 / 7 MIN READ — ENCRYPTION

The protection of privacy emerged to a major topic recently. Consequently, it seemed obvious to encrypt e-mails as much as possible. My only requirement was virtually anyone being able to use this, including grandma.

While there are several options to choose from, I decided to go with S/MIME. It fits the bill quite nicely by being supported across a wide range of e-mail clients. Instead of simply applying for a certificate at, for example, Comodo, I decided to roll my own for the following reasons:

  • no restrictions on
    • encryption algorithm
    • key size
    • hash algorithm
  • full control over the user base
  • getting to know S/MIME under the hood :-)

Especially the restrictions are a key argument, as Comodo would issue only 2048-bit RSA certificates using SHA-1 at the time. Knowing your users may also be of advantage, but I’ll leave that for another post. As always, there is a price tag:

  • time-consuming initial setup
  • make sure you get your root certificate to your users securely (no, not by e-mail)
  • anyone without your root certificate cannot verify the S/MIME signatures and may see a warning about an untrusted signature
  • any other trusted certificate authority can still be (ab)used to create fake certificates that your e-mail client will trust

The last point is actually a general problem of the hierarchical public key infrastructure and also applies to COMODO-created certificates or even server certificates (think about DigiNotar in July 2011).

In the following, general knowledge about how asymmetric encryption works is assumed.

So how does it all work?

In short, you need to create a certificate authority (CA), use that to sign all client certificates, get all the certificates on the devices of your users, and you are done. The actual work is done by OpenSSL. To make life a bit easier, I created a Bash script which provides some functions that wrap the most common tasks. So there’s no need to fiddle with OpenSSL directly, if you don’t want to. You can find it on GitHub. Source it into your current shell:

$ . ca_lib.sh

and you will be able to call those functions.

In addition to the Bash script, you will also find a set of OpenSSL configuration files in that Git repository. The following will always use those configurations and script functions to create keys and certificates. For a start, you may print a list of all functions including their help by invoking cahelp. You may also pass the name of one specific function to get help on that particular function only:

$ cahelp createCA

Additionally, trying to invoke a function with the wrong number of parameters will also display the help for that function.

Initial setup

First off, you need set the name for the “organisation” your certificates are for. This is necessary in order to easily reuse configuration files for different organisations. The term “organisation” originates from OpenSSL, by the way. In my case, it is simply the family name.

$ export ORGA=<your "organisation" name>

There are also some other variables worth mentioning, which are used by the script functions and/or provided configurations. Most important are probably CA_KEYSIZE and DEFAULT_KEYSIZE. They control the size of keys of CAs and of the client certificates, respectively. I chose both to be the same.

In simple terms, the length of the key determines how easy it is for an adversary to “guess” the private key when they only have the public certificate. The implications of breaking one or the other are rather different:

What if a private key is compromised

If someone else can get a hand on your CA key, all previously encrypted messages of your clients are still safe. For future messages, matters are a bit more complicated. But generally, future messages of all your clients are no longer 100% safe.

If someone else can break a client key, all previous messages sent to that particular client can be decrypted. Also, future messages to and from that client are no longer safe. In other words, messages can be read and modified by the adversary without anyone noticing. Additionally, the adversary can send authentic messages in the name of that client.

Create a root certificate authority

This initial setup is actually the most time consuming: set up a Linux system that will never connect to any network (no LAN, no Internet, nothing). I used a Raspberry Pi with a dedicated fully encrypted SD card with, e.g., Raspbian. In my case, the Raspberry is usually used elsewhere. But whenever I need to access the CA, I simply unplug any ethernet cable/WiFi stick and swap the SD cards.

Before we can create a CA, we need to create a configuration file for it. You can already find several configurations in the repository. Those can be used to build a CA hierarchy (or any subset thereof) like the following:

alt text

For the sake of simplicity, we will just create one self-signed CA for signing client certificates (which corresponds to “client CA” in the green box). The matching configuration file is ca.client.conf.

To create the CA, we can invoke createCA and pass the name of that configuration:

$ createCA client

This generates a new private key (e.g., 4096-bit RSA) and a so-called “certificate signing request” (CSR). This request is used in the next step to create a signed CA certificate that matches the key we just generated. As stated before, we want to use the same key for signing that we just generated. This will result in a self-signed certificate:

$ selfsignCA client

At this point, we already have a working CA that we can use to sign certificates. Inside the directory client you will find the following:

  • private/: directory that contains the secret key for the CA
  • db/: directory that contains database files used by OpenSSL to keep track of signed certificates
  • client.crt: the public CA certificate
  • client.csr: the certificate signing request used to create the CA certificate

After you signed a client certificate, there will also be a directory certs/ that will contain all certificates that have been signed in the past.

Create a client certificate

Similar to creating the CA above, creating a client certificate is also a two-step process. In the first step, you create a client key and a corresponding CSR:

$ createClientKey myCertName client

Note the second parameter: here you also pass the name of the CA that shall create the signed certificate out of the CSR. Using this parameter, the CSR is created in a way that is accepted by the CA. That is, the CA poses restrictions on the type of certificates it wants to issue. If the CSR does not meet those requirements, the signing process will fail. OpenSSL provides several options for this, which are controlled by the configuration files.

The second step will create the actual client certificate. This requires the CSR file and the CA key:

$ signCertificate myCertName client

The result is a client certificate that can be used to sign and encrypt e-mails!

What else

Now that we have the basics covered, there are some things left.

Clients are not the CA operator

The steps above for creating a client certificate do not draw a clear line between the CA operator and the client. Ideally, the client carries out the first step on their own device. Then the CSR is transferred to the CA operator, where the second step can be carried out.

But some scenarios may require a different approach: you (as the CA operator) generate the secret client key (and corresponding signed certificate) and transfer both to the actual device of the client. One such scenario might be setting up client certificates for a family, where the above steps cannot be expected to be carried out by every member. Obviously, the client needs to have great trust into the CA operator, which is probably the case in family scenarios.

Whichever method you chose, the “transfer” requires a secure channel to ensure a waterproof design. The CSR should be transfered securely, because the CA operator needs to ensure that the CSR really belongs to the client’s actual key (i.e. was not exchanged with some other CSR on the way). In the second case, the secret key obvisously cannot be simply e-mailed but also needs a secure channel. One such secure channel could be the USB stick that you personally bring with you on your next visit.

Expand the CA hierarchy

In the example above, the client CA is actually a self-signed CA, which can be used to create client certificates. The same can be done for server certificates, which allow your devices to verify the authenticity of your server(s), e.g. an Apache2 web server. Note that the way these CAs are configured by default, they will only be able to issue either client certificates (usable for signing e-mails) or server certificates (usable by web servers). Generally, you may want your CA to sign as few types of certificates as possible. What a CA can be used for, or rather which restrictions it poses on the certificates it signs, is controlled by the extendedKeyUsage specified in the configuration files.

Having different CAs for different key usages allows users to decide which kinds of certificates (signed by your CA) they want to trust, by only installing the required CA. Note that some devices already allow you to specifically restrict a CA certificate.

One common root CA can still be used to avoid having to install two or more different CA certificates. Or the common root CA (with no or fewer restrictions) could allow for future sub-CAs, that provide new key usages. Those new CAs would automatically be trusted, if the common root CA is trusted.

Comments?

Some of this has been written from memory without practically going through everything once again. If you spot any mistakes or things do not work as described, feel free to drop me line!