April 08, 2014

Self-Signed SSL Certificates

Testing sites for SSL certificates in development is important! Let's cover how to create and install self-signed SSL certificates in Apache and Nginx.

SSL Overview

As you're likely aware, being able to send data securely over a network (especially a public network) is of growing importance. To that end, many web applicates employ the use of SSL certificates to encrypt traffic between a client (often your web browser) and a server (someone's http server).

If you're interested on learning more about SSL certificates and the various mechanisms (such as "key certificates", "root certificates", "intermediate certificates" and more), jump to about ~51:45 of this edition of Tech Snap.

I really recommend watching that portion of that video. Seriously.

Using SSL in Your Application

In production, you will have to purchase an SSL certificate. When you purchase an SSL certificate, you are paying for a recognized and trusted-third parties (root or intermediate authority) to say that your SSL certificate is both valid and legitimately used by its owners. See how PayPal's SSL certificate was verified by VeriSign.

paypal cert

SSL certificates affect your application. When a site is visited under an SSL, browsers expect all resources (images, javascript, css and other static assets) to also be downloaded under an SSL certificate. Otherwise browsers either don't load the assets or show scary warning messages to your users. This means you need to be able to serve your assets and any third party assets with an SSL, including any not directly on your web server (images in your CDN of choice, for example).

That means you want to have a way to test your applications with an SSL certificate in development, instead of waiting for your site to launch to find out there are issues.

Creating Self-Signed Certificates

Unless there are some extenuating circumstances, you shouldn't need to buy an SSL certificate for use in development. Instead, you can create a "self-signed" certificate, which will work in your local computer. Your browsers will initially give you a warning for using an un-trusted certificate, but you can click passed that and test our your web application with your own SSL certificate.

The basic steps are this:

  1. Create a Private Key
  2. Create a Certificate Signing Request (CSR)
  3. Create a Self-Sign certificate using the Private Key and the CSR
    • Alternatively, if you purchased an SSL, the last step is accomplished by the certificate signing authority

To start, first make sure you have OpenSSL installed. Most flavors of Linux have this "out of the box", but you should be able to easily install it if not:

# Debian/Ubuntu:
sudo apt-get install openssl

# RedHat/CentOS:
yum install openssl

Next, we need a place to put our certificates. I usually put them in the same configuration directories as the web server I want to use the certificate with. For example, we can make a directory in the Apache or Nginx /etc directories:

# Apache:
sudo mkdir /etc/apache2/ssl
# Or:
sudo mkdir /etc/httpd/ssl

# Nginx:
sudo mkdir /etc/nginx/ssl

Sidenote:

At the time of this edition being released, there happens to be a nasty OpenSSL vulnerability called The Heartbleed Bug. OpenSSL 1.0.1 through 1.0.1f (inclusive) are vulnerable. This means 1.0.1g and greater are fixed. You should see if any of your servers need updating. You can use this site and this site to test if your site is vulnerable.

Once that's done, we can begin creating our certificate. First, we need a private key:

# Create a 2048 bit private key
# Change your -out filepath as needed
sudo openssl genrsa -out "/etc/[webserver]/ssl/example.key" 2048

Then we can generate the Certificate Signing Request. This uses the Private Key generated before:

sudo openssl req -new -key "/etc/[webserver]/ssl/example.key" \
                 -out "/etc/[webserver]/ssl/example.csr"

This will ask you a series of question. Here are the questions along with what I happen to fill in:

Country Name (2 letter code) [AU]:US
State or Province Name (full name) [Some-State]:Connecticut
Locality Name (eg, city) []:New Haven
Organization Name (eg, company) [Internet Widgits Pty Ltd]:Fideloper
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:example.local
Email Address []:

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

The Common Name option is the most important, as your domain used with the certificate needs to match it. If you use the "www" subdomain, this means specifying the "www" subdomain as well!

I left some blank. You can skip Organizational Unit Name and Email Address for a self-signed certificate. I also choose to leave the "optional company name" field blank. Finally, I elected NOT to add in a challenge password. This is something used by the certificate authority (if you're purchasing a certificate) in the case you need to regenerate an certificate.

So, we now have example.key and example.csr files created. Let's finish this up by creating the self-signed certificate.

sudo openssl x509 -req -days 365 -in "/etc/[webserver]/ssl/example.csr" \
                  -signkey "/etc/[webserver]/ssl/example.key"  \
                  -out "/etc/[webserver]/ssl/example.crt"

Here's what we did:

  • sudo openssl x509 - Create an SSL certificate following x509 specification
  • -req - State that we're generating a certificate
  • -days 365 - This certificate is valid for one year
  • -in "/etc/[webserver]/ssl/example.csr" - The CSR generated for this certificate
  • -signkey "/etc/[webserver]/ssl/example.key" - The Private Key used for this certificate
  • -out "/etc/[webserver]/ssl/example.crt" - Where to put the new certificate file

Creating a Wildcard Self-Signed Certificate

I use the xip.io service so that I can avoid editing my hosts file when I use local development servers. It's useful to automate the process of creating a self-signed wildcard xip.io certificate for my local servers. Let's create a wildcard subdomain. I'll also show you how to do it in a way that can be automated, eliminated the need for human interaction.

Let's begin! Create a new shell script and call it 'generate-ssl.sh`:

#!/usr/bin/env bash

# Specify where we will install
# the xip.io certificate
SSL_DIR="/etc/ssl/xip.io"

# Set the wildcarded domain
# we want to use
DOMAIN="*.xip.io"

# A blank passphrase
PASSPHRASE=""

# Set our CSR variables
SUBJ="
C=US
ST=Connecticut
O=
localityName=New Haven
commonName=$DOMAIN
organizationalUnitName=
emailAddress=
"

# Create our SSL directory
# in case it doesn't exist
sudo mkdir -p "$SSL_DIR"

# Generate our Private Key, CSR and Certificate
sudo openssl genrsa -out "$SSL_DIR/xip.io.key" 2048
sudo openssl req -new -subj "$(echo -n "$SUBJ" | tr "\n" "/")" -key "$SSL_DIR/xip.io.key" -out "$SSL_DIR/xip.io.csr" -passin pass:$PASSPHRASE
sudo openssl x509 -req -days 365 -in "$SSL_DIR/xip.io.csr" -signkey "$SSL_DIR/xip.io.key" -out "$SSL_DIR/xip.io.crt"

The above script follows all of our previous steps, except it does some fancy bash scripting so we can automate passing in the CSR generating variables using the -subj flag and some string parsing.

Once that's saved, you can run script with the following command:

sudo bash generate-ssl.sh

Then you can see those generated files in /etc/ssl/xip.io/.

Now that we've generated some certificates, let's see how to use them in our favorite web servers.

Apache Setup

The first thing to do on Apache is to make sure you have mod_ssl enabled. On Debian/Ubuntu, you can do this via:

sudo a2enmod ssl
# Then restart:
sudo service apache2 restart

Next, we need to modify our virtualhost to accept https traffic on port 443.

More information on configuring Apache Virtual Hosts is found on the First Edition under "Configuring Apache Virtual Hosts".

Usually we create a virtualhost to listen on port 80, a default http port. That might look like this:

# File: /etc/apache2/sites-available/example.conf
<VirtualHost *:80>
    ServerName example.local

    DocumentRoot /var/www

    ... and the rest ...
</VirtualHost>

To enable SSL for this site, we can create another virtualhost file, or add another virtualhost block to our example.conf file. This will listen on port 443, a default https port:

# File: /etc/apache2/sites-available/example-ssl.conf
<VirtualHost *:443>
    ServerName example.local

    DocumentRoot /var/www

    SSLEngine on

    SSLCertificateFile  /etc/apache2/ssl/example.crt
    SSLCertificateKeyFile /etc/apache2/ssl/example.key

    # And some extras, copied from Apache's default SSL conf virtualhost
    <FilesMatch "\.(cgi|shtml|phtml|php)$">
        SSLOptions +StdEnvVars
    </FilesMatch>

    BrowserMatch "MSIE [2-6]" \
        nokeepalive ssl-unclean-shutdown \
        downgrade-1.0 force-response-1.0
    # MSIE 7 and newer should be able to use keepalive
    BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown

    ... and the rest ...
</VirtualHost>

And that's it! Once you have that in place and enabled, you can reload Apache's config (sudo service apache2 reload) and try it out!

If you are using a self-signed certificate, you'll still need to click through the browser warning saying the Certificate is not trusted.

Xip.io Setup

Let's see what that looks like for the wildcard xip.io setup. The following virtualhost is for a web app located at project-a.192.168.33.10.xip.io, where "192.168.33.10" is the IP address of the server.

# File: /etc/apache2/sites-available/example-ssl.conf
<VirtualHost *:443>
    ServerName project-a.192.168.33.10.xip.io

    DocumentRoot /var/www

    SSLEngine on

    SSLCertificateFile  /etc/ssl/xip.io/xip.io.crt
    SSLCertificateKeyFile /etc/ssl/xip.io/xip.io.key

    # And some extras, copied from Apache's default SSL conf virtualhost
    <FilesMatch "\.(cgi|shtml|phtml|php)$">
        SSLOptions +StdEnvVars
    </FilesMatch>

    BrowserMatch "MSIE [2-6]" \
        nokeepalive ssl-unclean-shutdown \
        downgrade-1.0 force-response-1.0
    # MSIE 7 and newer should be able to use keepalive
    BrowserMatch "MSIE [17-9]" ssl-unclean-shutdown

    ... and the rest ...
</VirtualHost>

After that's setup, you can reload your Apache config (Debian/Ubuntu: sudo service apache2 reload) and test it out!

Here we can see the SSL certificate working, but of course our browser doesn't trust it since it's not certified by a trusted third party. That's fine tho, we can still test our application's use of SSL. xip.io wildcard subdomain

"Server's certificate does not match the URL." - And here we thought we made a wildcard certificate! Well, it turns out that matching wildcards isn't supported the same across implementations/browsers, especially "sub-sub-domains" we use with xip.io. Read here for some more information.

More Resources

Nginx Setup

For Nginx, we typically have a server "block" listening on port 80 (a default port for http). This will look something like this:

# File /etc/nginx/sites-available/sample
server {
    listen 80 default_server;

    server_name example.local;
    root /var/www;

    ... and the rest ...
}

For setting up an SSL, we want to listen on port 443 (a default port for https) instead:

# File /etc/nginx/sites-available/sample-ssl
server {
    listen 443;
    root /var/www;

    ... and the rest ...
}

These server blocks can be in the same configuration file or in separate ones. That's completely up to you. Just remember to symlink any configuration files to the /etc/nginx/sites-enabled directory if they need to be enabled.

To setup the https server block with our SSL certificate, we just need to add a few lines:

# File /etc/nginx/sites-available/sample-ssl
server {
    listen 443 ssl;

    server_name example.local;
    root /var/www;

    ssl on;
    ssl_certificate     /etc/nginx/ssl/example.crt;
    ssl_certificate_key /etc/nginx/ssl/example.key;

    ... and the rest ...
}

And that's it! Once you have that in place and enabled, you can reload ngin (sudo service nginx reload) and try it out!

If you are using a self-signed certificate, you'll still need to click through the browser warning saying the Certificate is not trusted.

Xip.io Setup

Similar to the Apache setup, for using xip.io you can adjust the server_name and certificate paths and be on your way:

# File /etc/nginx/sites-available/xipio
server {
    listen 443 ssl;

    server_name project-a.192.168.33.10.xip.io;
    root /var/www;

    ssl on;
    ssl_certificate     /etc/ssl/xip.io/xip.io.crt;
    ssl_certificate_key /etc/ssl/xip.io/xip.io.key;

    ... and the rest ...
}

One Server Block

As per the Nginx Admin Guide, you can usually define both http and http in one server block:

# File /etc/nginx/sites-available/xipio
server {
    listen 80;
    listen 443 ssl;

    server_name project-a.192.168.33.10.xip.io;
    root /var/www;

    ssl_certificate     /etc/ssl/xip.io/xip.io.crt;
    ssl_certificate_key /etc/ssl/xip.io/xip.io.key;

    ... and the rest ...
}

If you want to see a more complete Nginx server block, check out the Third Edition, Nginx Instead.

Tricks & Info

One-liner for generating an self-signed certificate:

sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
                 -keyout /etc/apache2/ssl/example.key \
                 -out /etc/apache2/ssl/example.crt

This will ask you for the same options as about to generate the CSR. Some explanation:

  • sudo openssl req - Req says we're generating a certificate
  • -x509 - Using the x509 specification
  • -nodes - Rather than "nodes", read this as "no des". Since we're doing this in one step, don't encrypt the Private Key (since it may require a password). Read more here.
  • -days 365 - Create a certificate valid for 1 year
  • rsa:2048 - Use a 2048 bit Private Key
  • -keyout /etc/apache2/ssl/example.key - Where to put the Private Key
  • -out /etc/apache2/ssl/example.crt - Where to put the generated Certificate

You can see the process of automating the creation an SSL certificate (no user input needed) in this shell script in the Vaprobash project.

Use curl with a self-signed certificate:

curl -K https://myapp.local
# -K is equivalent to --insecure:
curl --insecure https://myapp.local

Use wget with a self-signed certificate:

wget --no-check-certificate https://myapp.local/somefile

All Topics