Creating a Self-Signed SSL/TLS Certificate

Introduction

SSL (aka "Secure Sockets Layer") is a great idea. The concept is simple: using Public Key Cryptography, a website can provide information to all its clients encrypted in such a way that no one else can decrypt that information - and yet it's transparent to the user. Unfortunately, it's a but more complicated than it sounds - prone to security problems and a pain to administer. Since I'm in need of multiple SSL certificates, it's time to dig in to understand the process and potential problems.

Note that when I say "SSL," I'm using the same conflation that pretty much everybody makes. All versions of SSL are deprecated, and have almost entirely been replaced by TLS ("Transport Layer Security "). "SSL" is a catch-all for both - in fact for most any form of encryption between the server and the browser.

Public Key Cryptography seems like a pretty good solution: its available with encryption schemes that are essentially unbreakable. So why isn't this just PKC? Because anyone can have a key that claims it's from "Shopping Site X" - then they take your credit card number and disappear. How to prevent that? Certificate Authorities sign the keys for the websites, effectively saying "I guarantee that this key you've received is from Shopping Site X." And how can you trust that? Because your browser comes with a stack of keys from trusted Certificate Authorities.

Recently, Certificate Authorities have been a favourite point of attack for scammers: this is both behind-the-scenes and black magic to end users, so if you can get your hands on a Certificate Authority's private key, or slip your certificate into their processing stream, you can make any scam website look legitimate. This gets us into the politics of Mozilla or Microsoft (browser makers) or Asus or Lenovo (computer makers) adding and removing Certificate Authority keys from their browsers/machines - did the CA sign that bad cert because they're just taking money, or have they lost control of their private key, or is the browser-maker trying to exert political pressure because they don't like the CA ... it goes on and on. But that's not what I'm here to look at today, just pointing out that this is a politically complex area as well as a technically complex one. But security usually is.

Why or Why Not Self-Signed

To quote https://devcenter.heroku.com/articles/ssl-certificate-self , "... you can avoid the costs associated with the SSL certificate by using a self-signed SSL certificate. Though the certificate implements full encryption, visitors to your site will see a browser warning indicating that the certificate should not be trusted." SSL Certificates for web sites have traditionally been very expensive. https://letsencrypt.org/ may well be changing that - we'll see (I'd strongly encourage you to look at that if you're reading this). To better understand the process and all the moving parts, I'm going to walk through making a self-signed cert and getting it working on both an Apache and an Nginx server.

Self-signed certs are useful in a closed or controlled environment - one where you can say to the users "it's okay: that warning you got isn't an issue." It's not a good idea on the public internet, but if you have five or ten users connecting to a small site it could be quite practical.

Private Key and Certificate Signing Request

You'll need openssl installed to do this (if you need help installing that on your OS, you probably shouldn't be reading this tutorial yet). It's useful to realize that each of openssl's multitude of "STANDARD COMMANDS" (so characterized in the openssl man page) has its own man page (at least in Debian). So when you see openssl req ... below, you can type man req to find out more about this openssl subcommand.

It's possible to generate the key and cert in one step (although that doesn't include installation on the web server), but I'm going to go in smaller steps to better understand the moving parts involved. I'll show the single step version at the end. The first step initially appeared to be to generate a CSR, a Certificate Signing Request - even though the only person we're going to "request" to sign the cert is ourselves. To quote Wikipedia, a CSR is "a message sent from an applicant to a certificate authority in order to apply for a digital identity certificate." A CSR is your calling card to the CA saying "this is who I am:" it's a bundle of corporate information that gets nailed on the the certificate, and that's why we need to make it. But to make sure that neither the application nor the signed certificate that will presumably be returned to you is tampered with, the process below first creates a private-public key-pair to sign the CSR. Then it asks you questions about you and your organization to identify you on the request and cert. Should you try to use an empty PEM ("Privacy Enhanced Mail," no longer a common standard ... but they all live on somewhere) passphrase below, the process is terminated.

$ openssl req -new > cert.csr
Generating a 2048 bit RSA private key
................+++
.......+++
writing new private key to 'privkey.pem'
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Verify failure
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
Verify failure
Enter PEM pass phrase:
Verifying - Enter PEM pass phrase:
-----
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [AU]:CA
State or Province Name (full name) [Some-State]:Ontario
Locality Name (eg, city) []:Toronto
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Shopping Site X
Organizational Unit Name (eg, section) []:eSecurity
Common Name (e.g. server FQDN or YOUR name) []:admin.example.com
Email Address []:giles@example.com

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:passwd
An optional company name []:.
$

At the end of this you'll find yourself with two files: the Private key for PEM called privkey.pem and the cert request that we chose to name cert.csr on the command line. This is arguably two steps as you're generating the PEM key (which can be done separately) and the CSR, but it's commonly done in one step.

I was curious about the process here, so I broke it down further:

$ openssl genpkey -algorithm rsa -pkeyopt rsa_keygen_bits:4096 -out key.pem
......................................................................................++
...........................................................................................++

This generates a 4096 bit RSA key - and doesn't ask for/insist on a passphrase.

$ openssl req -new -key key.pem  -out server.csr
You are about to be asked to enter information that will be incorporated
into your certificate request.
...

This then proceeds as we've seen previously. So either of these techniques works to A) generate a private key, and B) generate a CSR.

Key Passphrase = Bad Idea

http://www.akadia.com/services/ssh_test_certificate.html reminds me that having a passphrase on the key is actually a really bad idea (I remember running into this years ago). Generally, passphrases on private keys are considered a security essential, but in this one narrow circumstance, you probably don't want it. The trick is, if the key for the cert that is the final target of this process is encrypted with a passphrase ... you have to type the passphrase every time Apache starts (I'd assume the same is true for nginx) so the cert can be loaded into memory. This is generally (and possibly extremely) undesirable. If you used the method above that forced you to make a key with a passphrase, then do this to remove it:

$ openssl rsa -in privkey.pem -out privkey.nopass.pem

It would then be a VERY good idea to make the resulting key not readable by anyone else:

$ chmod 600 privkey.nopass.pem

Although now that I say it, you should hide even passphrase-locked keys as well as you can.

Generating the Certificate

It appears to be possible to start our next invocation with either of the following formats for generating the cert:

$ openssl req -x509 ... # OR
$ openssl x509 -req ...

As seen above, the openssl req command is usually used to make CSRs, but by adding -x509 we're saying "give me a self-signed certificate instead." The alternative is to use openssl x509 which puts openssl in certificate generation mode, and then -req says "I'll give you the request/CSR next." I'm explaining the distinction because I found both online and wanted to know what the difference was: practically speaking, very little. So:

$ openssl x509 -req -days 365 -in cert.csr -signkey privkey.nopass.pem -out server.crt
Signature ok
subject=/C=CA/ST=Ontario/L=Toronto/O=Shopping Site X/OU=eSecurity/CN=admin.example.com/emailAddress=giles@example.com
Getting Private key

You now have the precious file called "server.crt" - the server certificate you wanted (hold on to the key too, we need both). Note that it's valid for one year, as specified by -days above. You're free to set that count higher or lower: 365 days is traditional because that's how long CAs generally validate certificates for. I'm inclined to suggest you stick with the number as a reminder to yourself to look at system security occasionally and regenerate keys: perpetual keys aren't a particularly good idea (if they're stolen they'll never fail).

And ... All in One Step

To generate your key and cert in one single step, bypassing all the wrangling above:

$ openssl req -x509 -nodes -days 365 -newkey rsa:4096 -keyout priv.nopass.key -out server.crt

-nodes specifies "if a private key is created it will not be encrypted." -newkey "creates a new certificate request and a new private key," with rsa:4096 specifying the key type and size. -keyout specifies the filename to put the new key in. Again, the result we're most interested in is the file server.crt. It would probably be wise to rename the key and cert if you have them (or will be creating them) for several sites:

$ mv -vi server.crt admin.example.com.crt
$ mv -vi privkey.nopass.pem admin.example.com.key

Helps me remember what site these are for.

Using Your Certificate with Apache

Where you copy your two new files to for Apache depends both on what OS you're using and how you've set up your Apache configuration. Debian's defaults, indicated in their example /etc/apache2/site-available/default-ssl.conf file:

SSLCertificateFile  /etc/ssl/certs/ssl-cert-snakeoil.pem
SSLCertificateKeyFile /etc/ssl/private/ssl-cert-snakeoil.key

I saw an Nginx tutorial recommending /etc/ssl for both, but this exposes the unsigned key in a world-readable directory whereas /etc/ssl/private/ is only readable by root.

Going with that scheme:

# cp -vi privkey.nopass.pem /etc/ssl/private/selfsigned.key
‘privkey.nopass.pem’ -> ‘/etc/ssl/private/selfsigned.key’
# cp -vi server.crt /etc/ssl/certs/selfsigned.pem
‘server.crt’ -> ‘/etc/ssl/certs/selfsigned.pem’

I then updated the lines in the Apache config shown above. I didn't stick with my naming convention, but this is just a test run ...

Note that I'm glossing over a bunch of stuff about SSL and its use in Apache. What I was most forced to notice was that there's a lot in the default configs about making older versions of IE work properly once SSL is turned on: you should look into this before thinking what's presented here is a complete solution.

To enable mod_ssl:

# cd /etc/apache2/mods-enabled/
# ln -s ../mods-available/ssl.conf .
# ln -s ../mods-available/ssl.load .

On Apache restart (I don't trust Apache's own restart, I use apachectl stop ; sleep 1 ; apachectl start) I got:

AH00526: Syntax error on line 43 of /etc/apache2/mods-enabled/ssl.conf:
SSLSessionCache: 'shmcb' session cache not supported (known names: ). Maybe you need to load the appropriate socache module (mod_socache_shmcb?).
Action 'start' failed.
The Apache error log may have more information.

In fact, the error.log didn't say anything. So (more guesswork):

# ln -s mods-available/socache_shmcb.load .

Apache restart then worked fine.

And that's it! https://localhost now gets the infamous "This Connection is Untrusted." Who knew I'd ever be so happy to see that?! From there it's easy: just agree to use the dubious certificate because you made it yourself.

Further notes on Apache SSL (which you should read, especially since I didn't before writing this) are here: https://httpd.apache.org/docs/2.4/ssl/ssl_howto.html

Using Your Certificate with Nginx

Generating the certificate is exactly the same for Nginx (or you can use the same one you generated for Apache, as I did), the only change is how to tell Nginx where to find it - and even that is quite similar. Add the following somewhere in the server { stanza of the host in question:

listen   443;
ssl    on;
ssl_certificate    /etc/ssl/certs/selfsigned.pem;
ssl_certificate_key    /etc/ssl/private/selfsigned.key;

Voila: nginx is now doing SSL.