After setting up a basic web server, I wanted to secure access to privileged resources using SSL. Normally, if I were dealing with customers or others who need to establish some kind of trust with me from the outside world, I would have to purchase a properly signed and verified SSL certificate from VeriSign or another Certificate Authority. However, because my goal is to secure internal resources that must be exposed publicly due to the nature of the internet (webmail and phpMyAdmin), it is cheaper (read: free) to create and sign my own certificates. End users do not need to trust me, and because I can personally verify the origin of my CA certificate, I do not need one signed by an external authority. I can then use this to sign additional certificates in order to create an area of my server that requires a user to present a certificate signed by my personal CA certificate in order to proceed, allowing very tight access control over sensitive areas.
Using the 64-bit Basic Linux AMI, OpenSSL is already installed. The configuration file,
openssl.cnf
is located at /etc/pki/tls/openssl.cnf
. Luckily, it has a pretty reasonable set of
defaults, and in particular the directory tree is already set up, with appropriate permissions
(0700) on the directory used for storing the CA private key. Because openssl.cnf
points to the CA
certificate and the private key directly, I chose to rename my keys from the default value and
entered modified the values for certificate
and private_key
to reflect this. With the basic
OpenSSL configuration complete, it was time to create a self-signed CA certificate, set up the
directory structure, and configure apache to use mod_ssl.
Signing the CA certificate
Creating a certificate that you can use to sign other requests is very straightforward:
[root@thelonepole /]# openssl req -newkey rsa:2048 -keyout tlpCA.key -x509 -days 365 -out tlpCA.crt
This creates a new X.509 format certificate along with its private key. It is automatically self-signed (this form of the command combines the key generation step, certificate request step, and self-signing step into a single command). My certificate requires that a password be entered when it is used to sign a certificate request, but for a small private server with exactly one trusted user the password can be removed by adding the -nodes flag, for convenience. You may also want to change the number of days for which the certificate is valid so that you don't have to recreate it every year (for a CA certificate, you might as well set this to 10 or more years).
Once the certificate and key were ready, I moved them to the locations earlier specified in
openssl.cnf
for certificate
and private_key
. Then, I had to create the file used to track the
next available serial number. For some reason this isn't created by default, possibly because of
some obscure vulnerability when the next available serial number is known, but for our purposes, we
are in a relatively low security situation, so starting at 1 is pretty reasonable:
[root@thelonepole /]# touch /etc/pki/CA/serial
[root@thelonepole /]# echo 1 > /etc/pki/CA/serial
If this step is omitted, openssl will complain when it comes time to sign certificate requests because it does not know which serial number to fill in.
Signing Other Certificates
At this point, with the CA certificate in place, it is easy to create and sign certificate requests. It is necessary to create a different certificate for each subdomain that you wish to be able to use SSL with, because the certificate will include the domain that it is valid for in its path. If this doesn't match the ServerName directive, it will generate a warning when starting apache, and users (even those who have added your CA certificate to their trusted store, i.e. you) will also see a warning in their browsers when using SSL. Creating and signing a certificate is done with two commands:
[root@thelonepole /]# openssl req -newkey rsa:2048 -nodes -keyout webmail.key -out webmail.csr
[root@thelonepole /]# openssl ca -in webmail.csr -out webmail.crt
Note that the certificates that are created for use with apache should be done WITHOUT a password,
using the -nodes
flag. If you don't do this, you'll be prompted for a password each time you start
apache, which is a huge problem if your server goes down and/or is automatically restarted and you
aren't available to enter the password. The certificates generated by the above command will also be
valid for the period of time specified in your openssl.cnf
. If you want them to last longer, be
sure to specify -days XXX
.
Copy these certificates to a known location, such as /etc/pki/tls
and note their location so that
it can be used when configuring apache. You will also want to create a certificate (in exactly the
same way) for yourself, which will be used for client authentication for SSL-enabled subdomains as
well. However, for a client certificate, there is an additional step: it must be converted from
X.509 format to the PKCS #12 format that is often the only one understood by browsers. The single
output file will combine both the signed certificate and the private key, and you will have it on
any computer or device that you expect to use to access your secure areas, so BE SURE to use a
strong export password when prompted during this step. The conversion can be done with one command,
which takes in your private key and signed certificate and exports it as a client certificate in
PKCS format:
[root@thelonepole /]# openssl pkcs12 -export -clcerts -out greg.p12 -in greg.crt -inkey greg.key
Then, transfer the PKCS file to your computer and install it in your web browser, usually with sftp. Under chrome, I had the option to install it specifically as a key for client authentication, but I am not sure if all browsers support the categorization in this manner. You will also need to install a copy of your CA certificate into the trusted root certificate store, or the browser will not accept the client certificate. Luckily you can simply download the X.509 format certificate from your server and tell your browser about it without converting it (which wouldn't make sense anyway because it'd require the private key…).
Configuring mod_ssl
Finally, there are several directives to add to the VirtualHost entries in order to configure them to use SSL. First, if you intend to only allow SSL to be used for a specific subdomain, like I do with my webmail, create an entry for normal HTTP that redirects all traffic to HTTPS:
<VirtualHost *:80>
ServerName webmail.thelonepole.com
Redirect permanent / https://webmail.thelonepole.com
</VirtualHost>
Next, a VirtualHost entry must be created that has the desired SSL settings, listening on port 443.
Obviously, it must include SSLEngine On
in order to enable SSL, but there are actually several
extra directives that should be included in order to have client authentication work. Be sure to set
SSLCACertificateFile
to point to your local CA certificate and enable client authentication with
SSLVerifyClient
. In order to require that clients use certificates directly signed by your CA
certificate, set SSLVerifyDepth
to 1. I've included a complete VirtualHost entry as an example,
based on the configuration I use here.
<VirtualHost *:443>
ServerAdmin greg@thelonepole.com
DocumentRoot /var/www/webmail
ServerName webmail.thelonepole.com
ErrorLog logs/webmail-error_log
CustomLog logs/webmail-access_log combined
SSLEngine On
SSLCACertificateFile /path/to/CA/tlpCA.crt
SSLCertificateFile /path/to/apache/ssl/webmail.crt
SSLCertificateKeyFile /path/to/apache/ssl/webmail.key
SSLVerifyClient require
SSLVerifyDepth 1
SetEnvIf User-Agent ".*MSIE.*" nokeepalive ssl-unclean-shutdown
CustomLog logs/webmail-ssl_request_log "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
</VirtualHost>
At this point, simply restart apache, and, if DNS was already set up, you should be prompted for a key when accessing your subdomain. If you have not added your key to the browser's store (or are interested in verifying that it requires a client-supplied key and use an unconfigured browser), you should see a message notifying you that an error with code "ssl_error_handshake_failure_alert" happened during SSL negotiation.