Dr. Lawlor's Code, Robots, & Things

August 20, 2014

Setting up a HTTPS server via Apache mod_ssl: start to finish

Filed under: Linux, Servers, Sysadmin — Dr. Lawlor @ 1:42 am

If you’re like me, you’re already running a Linux web server.  It uses port 80 by default, which sends everything unencrypted across the network.  This is a bad idea, because anybody watching your users’ network traffic can see everything sent in either direction.  So you want to use HTTPS, which runs over TCP port 443.  Here’s how to set it up!

Step 0: Save your config files

I’ve realized that when setting up Apache, it’s very easy to trash the config files in /etc/apache2 badly enough that the server won’t even start any more.  I’ve had some annoying experiences trying to back out incomplete and poorly understood changes, so “step zero” is always to make a git backup of all the config files.  I usually do:

sudo su
apt-get install git # install git (in Ubuntu)
cd /etc
git init . # make a git repo for config files
chmod 700 .git # make repo only readable by root (security)
git add * # save everything into the repo
git commit .

OK, now you can use “git diff” to see what you changed, delete and “git checkout foo.conf” to recover files, etc.  Every time stuff works, I “git add *” and “git commit .” so they’re saved. I feel better already!

Step 1: Install Apache and mod_ssl

You now need apache and mod_ssl. 

sudo su
apt-get install apache2 openssl
a2enmod mod_ssl
service apache2 restart

Verify that apache came up correctly. I like watching the error log in real time with:

tail -f /var/log/apache2/error.log &

Step 2: Make a Self-signed Certificate

To use SSL, you need a certificate that identifies the server.  This prevents “man in the middle” attacks, where some evil entity pretends to be your server.  The easiest and least secure way to get a certificate is to make your own, like this:

sudo su
cd /etc/apache2
mkdir ssl
cd ssl
openssl req -new -x509 -days 735 -keyout YOU_private.key -out YOU_public.crt -nodes \
-subj  '/O=Your Organization/OU=Your Department/L=Your City/ST=Your State/C=US/CN=www.you.com'

This makes two PEM certificate files, your private key YOU_private.key which you keep secret on your server, and your public certificate YOU_public.crt which is handed out to your web clients.  

The “-subj” line identifies you; this will be more important with a real certificate later.  The “CN” line does need to match your DNS name.

Step 3: Enable HTTPS in Apache

We now need to edit the Apache config files to use mod_ssl and HTTPS.   You can use the text editor of your choice here:

sudo su
cd /etc/apache2/sites-enabled
gedit 000-default.conf &
service apache2 restart

The basic structure of the 000-default.conf file is a list of directories and options, wrapped in a “VirtualHost” tag:

<VirtualHost *>
... lists of permissions and directories ...
</VirtualHost>

You need to modify this to add an SSL virtual host on port 443.  Rather than duplicating the list of permissions and directories, it’s cleaner to put them in a separate file and Include it in both places.

ServerName www.you.com

<VirtualHost *:80>

… lists of permissions and directories …

</VirtualHost>

<VirtualHost *:443>

… lists of permissions and directories …

SSLEngine On
SSLCertificateFile /etc/apache2/ssl/YOU_public.crt
SSLCertificateKeyFile /etc/apache2/ssl/YOU_private.key

# This is for a real CA certificate (prior to Apache 2.4.8)
# SSLCertificateChainFile /etc/apache2/ssl/YOU_chain.crt

<Location />

SSLRequireSSL On

</Location>

</VirtualHost>

OK, restart the server and check if it works with https in a web browser!  You’ll get a browser warning about an untrusted self-signed certificate, and a slash through the https, but it should work.

Step 4: Get a real certificate from a certificate authority

The self-signed or “x509” option above means we’ve made a self-signed certificate, which is signed with our own private key.  This isn’t very secure, because anybody else could make their own certificate saying they’re us, and a web client has no way to tell which is the “real” us.  The solution is to get a certificate authority to sign your key, which by design requires you to semi-manually authenticate with some higher power.  My university uses InCommon, which requires a university email account and per-department authorization code.  InCommon in turn is trusted because they’re authenticated by AddTrust, which is trusted by most browsers.

The basic “chain of trust” here consists of three certificates:

  • Server “leaf” certificate: my server, similar to the self-signed certificate above.  It’s signed by InCommon.  I keep the private key.
  • InCommon’s “intermediate” certificate: authenticates InCommon to a “root” certificate authority.  It’s signed by the root.  The private key is kept on InCommon servers.  (Sometimes, like with certificate resellers, there are several layers of intermediates between the root and your server.)
  • AddTrust “root” certificate: the public key is already installed in your browser.  The private key is kept in a vault at Comodo.

To get the intermediate authority to sign your key, you need a “Certificate Signing Request” file, which you can generate from your self-signed key like this:

openssl x509 -x509toreq -in YOU_public.crt -signkey YOU_private.key -out YOU_request.csr

You send this csr file off to the intermediate to get it signed (you NEVER send your private key).  Normally the organization, location, and DNS name you provided when making your key need to exactly match the intermediate’s records, for them to actually sign it.

The intermediate can send you back the signed certificate chain in a bewildering number of possible formats.  For Apache mod_ssl you just want PEM.  One PEM certificate looks like this base64-encoded binary garbage:

-----BEGIN CERTIFICATE-----
MIIENjCCAx6gAwIBAgIBATANBgkqhkiG9w0BAQUFADBvMQswCQYDVQQGEwJTRTEU
MBIGA1UEChMLQWRkVHJ1c3QgQUIxJjAkBgNVBAsTHUFkZFRydXN0IEV4dGVybmFs
IFRUUCBOZXR3b3JrMSIwIAYDVQQDExlBZGRUcnVzdCBFeHRlcm5hbCBDQSBSb290
MB4XDTAwMDUzMDEwNDgzOFoXDTIwMDUzMDEwNDgzOFowbzELMAkGA1UEBhMCU0Ux
FDASBgNVBAoTC0FkZFRydXN0IEFCMSYwJAYDVQQLEx1BZGRUcnVzdCBFeHRlcm5h
bCBUVFAgTmV0d29yazEiMCAGA1UEAxMZQWRkVHJ1c3QgRXh0ZXJuYWwgQ0EgUm9v
dDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALf3GjPm8gAELTngTlvt
H7xsD821+iO2zt6bETOXpClMfZOfvUq8k+0DGuOPz+VtUFrWlymUWoCwSXrbLpX9
uMq/NzgtHj6RQa1wVsfwTz/oMp50ysiQVOnGXw94nZpAPA6sYapeFI+eh6FqUNzX
mk6vBbOmcZSccbNQYArHE504B4YCqOmoaSYYkKtMsE8jqzpPhNjfzp/haW+710LX
a0Tkx63ubUFfclpxCDezeWWkWaCUN/cALw3CknLa0Dhy2xSoRcRdKn23tNbE7qzN
E0S3ySvdQwAl+mG5aWpYIxG3pzOPVnVZ9c0p10a3CitlttNCbxWyuHv77+ldU9U0
WicCAwEAAaOB3DCB2TAdBgNVHQ4EFgQUrb2YejS0Jvf6xCZU7wO94CTLVBowCwYD
VR0PBAQDAgEGMA8GA1UdEwEB/wQFMAMBAf8wgZkGA1UdIwSBkTCBjoAUrb2YejS0
Jvf6xCZU7wO94CTLVBqhc6RxMG8xCzAJBgNVBAYTAlNFMRQwEgYDVQQKEwtBZGRU
cnVzdCBBQjEmMCQGA1UECxMdQWRkVHJ1c3QgRXh0ZXJuYWwgVFRQIE5ldHdvcmsx
IjAgBgNVBAMTGUFkZFRydXN0IEV4dGVybmFsIENBIFJvb3SCAQEwDQYJKoZIhvcN
AQEFBQADggEBALCb4IUlwtYj4g+WBpKdQZic2YR5gdkeWxQHIzZlj7DYd7usQWxH
YINRsPkyPef89iYTx4AWpb9a/IfPeHmJIZriTAcKhjW88t5RxNKWt9x+Tu5w/Rw5
6wwCURQtjr0W4MHfRnXnJK3s9EK0hZNwEGe6nQY1ShjTK3rMUUKhemPR5ruhxSvC
Nr4TDea9Y355e6cJDUCrat2PisP29owaQgVR1EX1n6diIWgVIEM8med8vSTYqZEX
c4g/VhsxOBi0cQ+azcgOno4uG+GMmIPLHzHxREzGBHNJdmAPx/i9F4BrLunMTA5a
mnkPIAou1Z5jJh5VkpTYghdae9C8x49OhgQ=
-----END CERTIFICATE-----

Awesome, right?  Often you get back a stack of PEM certificates capturing each certificate in the trust chain, from leaf to root, but they all look like gibberish, and the separate certificates are often just stuck together in the file back-to-back.  You can dump a human-readable version of the topmost PEM in a file with an online PEM decoder tool, or locally with:

openssl x509 -in whut.crt -noout -text

This at least can clue you in to which certificate you’ve got on top.  Often you need to manually pick the chain file apart into separate certificate files, figure out what you’ve got, and sometimes shuffle them around so they’re in the right order, from leaf to root.

With our self-signed certificate, we only need SSLCertificateFile, which has our public self-signed certificate, and SSLCertificateKeyFile, which has our private key.  A real certificate authority will give us an updated public certificate, and we use the same private key, but we need a “chain” to connect our leaf certificate with their root.  In my case, the chain has only one link, the InCommon intermediate CA.

As of Apache 2.4.8, Apache can digest a whole chain of PEM directly via SSLCertificateFile, which sounds like it would make life easier.  Prior to that version (and Ubuntu 14.04 ships with Apache 2.4.7, sadly!), Apache only parses the top PEM in the file and ignores the rest.  For me, the first PEM listed was the root, which is totally useless since every browser already has the AddTrust root.  So I had to hand-extract my server leaf PEM certificate for SSLCertificateFile, then put the rest of the chain in a separate file accessed with the SSLCertificateChainFile directive.  Actually, it’s most efficient if you only put the intermediate PEM in SSLCertificateChainFile: putting in the root doesn’t seem to help anything because the root is self-signed, putting the server leaf in is redundant, and a longer chain just wastes bytes at every connection startup.

You need to “service apache2 restart” after changing your certificate files.  Apache will error at startup if it doesn’t like the certificate formats.

Be sure to git commit your new version once everything works!  Doesn’t it feel good to keep your user’s data safe in transit?

Step 5: Record when to renew your certificate

Certificates eventually expire, usually after a year or two.  You need a systematic way to remember to renew your certificates, or else your server will just spontaneously stop working one day, and you’ll be explaining the unexpected downtime to the CEO right before you’re fired.  Complacency is not your friend: this has happened to the biggest and best IT companies in the world.

I put the certificate renewal into my long-term calendar, a week before the renewal.  

Validation

You can validate your server setup using openssl s_client, like this.  The byte count is useful to determine setup speed.

echo | openssl s_client -showcerts -CApath /etc/ssl/certs/ -connect www.you.com:443

Another good check is the browser itself, which turns the https part of the address bar green once the certificate is properly signed.  Google Chrome complains a little about “verified, but CA does not have public audit records” if your CA doesn’t publish a log of transactions using Google’s new Certificate Transparency scheme.  There’s not much you can do about this, since using certificate transparency is up to your intermediate CA.

You should also doublecheck with gnutls, which is pickier about both names and certificate order in the file, and it’s important because it’s used by tools like git.  Gnutls wants the PEM chain file to list the certificates in order from intermediates up to the root.

sudo apt-get install gnutls-bin
echo | gnutls-cli --x509cafile /etc/ssl/certs/ca-certificates.crt -p https www.you.com

 There are also some simple web tools to verify your site is accessible from outside, like SSL Checker, which is short and sweet, and SSL Test, which is exhaustive and complete.   If these tools lock up for a while and then error out, but HTTPS works from inside your network, port 443 traffic might be firewalled from outside.

Advertisements

Leave a Comment »

No comments yet.

RSS feed for comments on this post. TrackBack URI

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: