Certificate-based authentication with your own CA
We've played things pretty by-the-book so far with our use of cryptographic certificates in this guide—we got a signed personal cryptographic certificate way back in part two, and we're using real SSL-TLS certs signed by a real Certificate Authority (StartCom). However, for this next bit, we're going to become our own certificate authority and mint some of our own certificates.
This might sound confusing at first, but you've actually got access to the same cryptographic tools as any of the main certificate authorities do—the difference between you and a real certificate authority is that a real CA is "trusted" (by virtue of having passed a number of certification requirements). They're the tippy-tops of the whole system of trust that underlies SSL-TLS. The fact that many of them have either proven untrustworthy or at least easily compromised is the horribly smelly elephant in the room and the major reason why SSL-TLS is a giant, rickety house of cards; but, hey, we're not going to be able to fix that in this article, so just do what everyone else does and ignore the elephant and let's press on.
We want to use something more secure than just regular passwords to log onto our fresh Roundcube instance: we want to use cryptographic certificate-based authentication. To do that, we need to be able to issue certs to each of our webmail users, and to do that, we need to establish a private root certificate authority.
It's actually pretty darn easy to do: we just need to make a key and then use it to sign a root certificate, and boom, we're a CA! So, let's generate that key file. Enter the following commands:
You'll be prompted for a password with which to encrypt the key, so supply something strong (and make sure to remember it!). After the key is generated, we'll create a root certificate and sign it with that key:
You'll first be prompted for the private key password you just created so that the system can decrypt the key and use it to sign the certificate. Then, you'll be presented with a number of prompts for information to be embedded into the certificate. If you were a real CA, this information would be vitally important; since you're just creating a self-signed root certificate, you can fill in pretty much whatever you'd like. When the process completes, you'll have a root certificate file that's valid for 3,065 days.
Once you've got a root certificate, the next step is to generate personal certificates for each of your virtual mail users. Since we've been bombing along with three virtual users ("firstname.lastname@example.org," "email@example.com," and "firstname.lastname@example.org"), we'll create three personal certificates. If you've got more, create more.
The process starts out similar to creating the root certificate: you'll create a private key and encrypt it the same way, but then we'll do something different and use the key to create a certificate signing request (or CSR), which we'll then combine with our CA certificate and private key to create a functional signed certificate.
A quick note: if you're not the only person who's going to be accessing your webmail—like, if you've got a buddy that you've granted an account—it's better to let each person create a private key and CSR for themselves. If you hold the private keys, you've got the potential to impersonate them—or at least generate a certificate and say that you're them. When they submit a CSR to you, you can sign off on it without having to know their private key.
Regardless of who does it, you'll want to first generate a private key like before, encrypt it, and then use it to create a CSR:
As with the root CA certificate, the CSR creation process will ask you for the details to embed into the resultant certificate; unlike the root CA process, this one is actually important. You'll want to make sure to enter your actual name as the Common Name and your actual e-mail address as the e-mail address. Don't worry about providing a challenge password or an optional company name; we won't be using those attributes.
This will spit out a CSR file, which we'll then sign using our root CA certificate and private key:
And voila, you have a valid (if self-signed) personal certificate file and key! The easiest way to get the certificate and private key installed on your main computers (or your smartphone, for that matter) is to package the certificate and key into a single file, like this:
You'll be asked for the password for your encrypted key file, and
you'll also be asked to specify an "export password" to wrap around the
you.pfx file. This is because
you.pfx contains both certificate and private key and needs to be protected. Take the
file and import it into your computer's main certificate store (which
you can usually do by double-clicking—though if you're using Firefox,
you'll have to import the certificate separately, because Firefox keeps
its own certificate store).
Repeat the certificate generation and signing steps for as many local virtual users as you'll have logging on to webmail. If you're having your other users generate their own CSRs, all you'll need to do is the final step where you sign and then send them back their finalized certificates (preferably by a secure method—hand carried on a USB stick is a good idea). They'll already have their own keys, so they just need the actual certificate from you.
Activating certificate authentication in Nginx
Now we need to turn on certificate-based authentication in Nginx. This'll be a snap, since we've already got all the configuration lines written out in our Web site's Nginx virtual host file—we just need to uncomment it and make it live.
/etc/nginx/sites-available/roundcube and un-comment the following lines:
(Obviously, you want to leave the first "Client auth via certs" comment commented out, since it's a legitimate comment.)
It's definitely worth pausing a moment and focusing on what we've told Nginx we want to do here. The first three lines specify that we're going to be doing certificate checking for the entire site—anyone that wants to access the HTTPS version of your site will have to supply a cryptographic certificate, and that certificate must be valid. We're going to use your domain's self-signed CA certificate to check the submitted certificates against.
This means that clients will have to present not just any old client certificate, but one specifically signed by your CA—in other words, only certificates that you have issued.
You could stop there, which would permit anyone with a valid
certificate signed by your CA to proceed. However, what if you only want
certain accounts to be able to use the webmail interface? When Nginx
validates a client certificate, it records a number of bits of
information from that certificate and stores them temporarily in a set
of variables. This gives you the option to sort through them and take various actions based on the validation result. We're using the
variable in the root location block to match the e-mail address out of
the Subject Distinguished Name variable (obviously, you'd want to
replace "email@example.com" with the address you actually want to
match). If you want to check for more than one e-mail address, you can
modify the root location regular expression like this:
The whole Subject Distinguished Name contains more information than simply the e-mail address—if you want to see your certificate's entire subject DN, you can use the following command:
You can match against anything in there—or in any of the other variables Nginx records.
But hold on—using "if" in Nginx location blocks is generally considered a bad idea, because "if" statements in Nginx can behave in ways that you might not expect. Fear not, though—in this particular case, using "if" is perfectly okay. For one, it's by far the easiest way to accomplish what we want to accomplish—we really do want a conditional check, and "if" is how you do it. More importantly, we're using our "if" in conjunction with a "return," which as noted in the documentation, is completely safe.
Speaking of that return—what else is happening in our location block?
We're doing an "if not equal" check, and so if the client certificate
is valid but doesn't contain the string we want in its subject DN, we
respond with a 301 HTTP redirect to Dennis Nedry's "AH AH AH! YOU DIDN'T
SAY THE MAGIC WORD!" video from Jurassic Park. Um...feel free
to use your own redirect there. Finally, we're also taking the standard
403 FORBIDDEN response (which would be generated by the server if the
client presents an invalid certificate, or no certificate at all) and
kicking them over to the same annoying site via the named
So, let's try it out! Save the file and restart Nginx with
service nginx restart,
and then access your webmail site. You should be immediately challenged
for a certificate. Present the correct one, and you'll be taken to your
webmail's logon page like normal.