EFF recently updated our SSL certificate and configuration. This gave us an A+ rating on SSL Labs, a great jumping off point for reviewing a site's secure connection. What follows is a quick, technical guide to how we achieved this.

Generating a Certificate

First we generate an 4096-bit RSA private key with a strong passphrase. It's useful to have a "locked" version of the key for secure archival. The passphrase is stored separately in a KeePassX database.

openssl genrsa -aes256 -out example.com.aes 4096

Next we update a .cnf file to populate the fields we want filled into the final certificate. This is especially useful when the certificate uses the Subject Alternative Name field to specify additional domain names. Here's the format:

[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req

[req_distinguished_name]
countryName = Country Name (2 letter code)
countryName_default = XX
stateOrProvinceName = State or Province Name (full name)
stateOrProvinceName_default = Somewhere
localityName = Locality Name (eg, city)
localityName_default = Sometown
organizationalUnitName  = Organizational Unit Name (eg, section)
organizationalUnitName_default  = SomeCo
commonName = Common Name (ie hostname or username)
commonName_default = example.example.com
commonName_max  = 64

[ v3_req ]
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
subjectAltName = @alt_names

[alt_names]
DNS.1 = example.com
DNS.2 = www.example.com
DNS.3 = poorlykeptsecret.example.com

Next we generate a Certificate Signing Request (CSR), which a Certificate Authority will use as input for the final certificate:

openssl req -new -out example.com.csr -key example.com.aes -config example.com.cnf

We prove to our chosen Certificate Authority (CA) that we control all the DNS names in the certificate, then we send example.com.csr to the CA so it can sign a certificate using the provided public key and name information. Unfortunately, the process for doing these two steps varies greatly for each CA, so it's hard to provide universal advice. We are working with Mozilla and several other organizations on the ACME protocol to simplify the proof and issuance process, along with a new Certificate Authority, Let's Encrypt, that will issue certificates free of charge using the ACME protocol, making the process easier.

When choosing a Certificate Authority, you should double-check that your CA will issue you a certificate using the SHA256 hashing algorithm. SHA256 is rapidly replacing the deprecated SHA1 hashing algorithm, and is being phased out of support by browsers.

Most CAs provide your final certificate and their intermediate chain certificate  in two separate files, but nginx prefers them concatenated into a single .pem file (order is important):

cat example.com.crt My_CA_Intermedate.crt >> example.com.pem

In order for nginx to be able to start without entering a passphrase, we need to unlock the private key we created earlier:

sudo openssl rsa -in example.com.aes -out /etc/ssl/private/example.com.key

Nginx Configuration

Our configuration from April 2015 is documented here. Mozilla provides a configuration generator for nginx and other servers which may be more up to date by the time you read this.

Most of the heavy lifting is done in the file /etc/nginx/ssl.conf:

ssl_prefer_server_ciphers on;

ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:ECDH+3DES:RSA+AES:RSA+3DES:!ADH:!AECDH:!MD5:!DSS;

ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains";

ssl_prefer_server_ciphers is important to make sure the somewhat weaker ciphers lower on the list are only used if browsers don't support those listed first.

ssl_ciphers enables only ciphers without major known vulnerabilities, and lists ciphers with Perfect Forward Secrecy first.

ssl_protocols does not include SSLv3. Unfortunately, browser support for TLSv1.2 is not widespread enough to disable TLSv1 and TLSv1.1.

The Strict-Transport-Security header tells browsers to make only secure connections to this domain for the number of seconds specified by max-age. This is important to help prevent a variety of attacks that rely on downgrades or domain spoofing. The optional includesSubdomains flag will affect every subdomain as soon as a browser has visited this site once, so it should be enabled with care.

In the server block for the host (eg in sites-enabled), use an include statement for ssl.conf. We all redirect all plain HTTP visitors on port 80 to HTTPS, where they will receive the HSTS header and know to always try a secure connection in the future.

server {
  listen 80;
  server_name example.com;
  return 301 https://example.com$request_uri;
}

server {
  ssl_certificate /etc/ssl/certs/example.com.pem;
  ssl_certificate_key /etc/ssl/private/example.com.key;
  listen 443 ssl;
  server_name example.com;
  include /etc/nginx/ssl.conf;
  ...
  }
}

Providing SSL is an important step in protecting your users. Configuring SSL securely isn't easy, and keeping your server secure requires frequent updates, but we hope this guide will help more administrators get there.

Related Issues