Hybrid ECDSA and RSA certificates with Nginx and Let’s Encrypt

By | 3rd October 2016

There are a myriad of resources on the internet on how to get this working but not as well compiled together as I’d like. That is the reason I am writing this post. ๐Ÿ™‚

I am not going to explain what Elliptic Curve Cryptography(ECC) is since Cloudflare has a very nice blog post on this. Basically you can get much higher security from smaller keys on ECC when compared to traditional RSA keys. A 384 bit elliptic curve key should provide as much protection as a 7680 bit asymmetric RSA key. This can help with faster and less resource intensive SSL negotiation with clients, the only caveat being slightly slower signature verification on client devices. However you would want RSA certificates for compatibility with legacy devices. So let’s dig in, shall we?

Requirements

  • Nginx 1.11 or above

Nginx 1.11 supports dual certificates so you will need that for hybrid RSA/ECDSA certificates to work. You can get this from a repository for your Linux distribution of choice from here. Confirm that you are running version >1.11 with Nginx -V.

I run this blog along with a reverse proxy in a separate LXC container so it is easier for me to fiddle with OpenSSL/Nginx versions.

  • Relatively recent version of OpenSSL

I don’t know the minimum version required for a certain curve but having an updated version of OpenSSL helps. openssl version tells you what version of OpenSSL you are using. I’m running OpenSSL 1.0.2g 1 Mar 2016 at the time of writing this article. You can use openssl ecparam -list_curve to list elliptical curves available to you. I will be going withย secp384r1 since it is relatively secure and widely supported by browsers. Also make sure Nginx is compiled against that version of OpenSSL. This shouldn’t be a problem unless you are compiling Nginx yourself.

You can probably use other cryptographic libraries as well like LibreSSL but I’m not going to cover them in this post.

  • Let’s Encrypt

Let’s Encrypt will give you free SSL certs(yay!). However support for ECDSA is planned by 31st March 2017. You can track the status here. Let’s Encrypt will automatically generate a RSA certificate but for now we have to manually sign the ECDSA certificate with their intermediate RSA certificate. Digitalocean has a nice primer on Let’s Encrypt here. Make sure you configure the webroot plugin how they say. I do SSL on my reverse proxy so the config for me is:

location ^~ /.well-known {
alias /var/www/blog/.well-known;
               allow all;
}

Where /var/www/blog/ is a directory I’ve created and assigned proper permissions for Nginx to read/write to with chown -R nginxuser:nginxgroup /var/www/blog.

RSA certificate

The Digitalocean blog post I linked earlier should help you out with this. For me it was:

letsencrypt certonly --hsts -a webroot --webroot-path /var/www/blog -d varunpriolkar.com -d www.varunpriolkar.com

Use the –hsts flag if you would like to enable HSTS.

ECDSA certificate

Firstly create the directory to save our private key and our certs with mkdir -p /etc/nginx/certificates and change directory to it by cd /etc/nginx/certificates. Generate your private key with:

openssl ecparam -genkey -name secp384r1 | sudo openssl ec -out ec.key

It will be saved as ec.key . I’m using the secp384r1 curve as previously mentioned. Now we need to generate a Certificate Signing Request(CSR). For this let’s create a config.

[req]
distinguished_name = req_distinguished_name
req_extensions = v3_req

[req_distinguished_name]

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

[alt_names]
DNS.1 = varunpriolkar.com
DNS.2 = www.varunpriolkar.com

Add in more domains that you may need with DNS.3, DNS.4 etc. Save that as somefile.cnf . Now use this command to generate your CSR:

openssl req -new -sha256 -key ec.key -out varunpriolkar.com.csr -subj "/C=IN/ST=Goa/L=Ponda/O=Priolkar Elf/CN=varunpriolkar.com" -config somefile.cnf

Where IN is your countrycode, Goa is your state, Ponda is your location, Priolkar is your organisation, varunpriolkar.com is your main domain name/FQDN and varunpriolkar.com.csr is your CSR name. Use this to generate your ECDSA certificate:

letsencrypt certonly --hsts -w /var/www/blog/ -d varunpriolkar.com -d www.varunpriolkar.com --email me@varunpriolkar.com --csr "/etc/nginx/certificates/varunpriolkar.com.csr"

Replace the fields accordingly.

Putting it all together

You need to specify the RSA and ECDSA certificates, the elliptical curve used and SSL ciphers in your Nginx virtual host. Make sure you protect against Logjam attacks and POODLE. You can choose to include more things like OCSP stapling, HSTS and SSL session cache. You can use openssl ciphers -V “ALL:COMPLEMENTOFALL” to list all compatible cipher suites.

ssl on;
#Protect against POODLE. Disable SSLv3
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_prefer_server_ciphers on;
#RSA certificates
ssl_certificate /etc/letsencrypt/live/varunpriolkar.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/varunpriolkar.com/privkey.pem;
#ECDSA certificates
ssl_certificate /etc/nginx/certificates/0001_chain.pem;
ssl_certificate_key /etc/nginx/certificates/ec.key;
#Elliptical curve used
ssl_ecdh_curve secp384r1;
#SSL ciphers
ssl_prefer_server_ciphers on;
ssl_ciphers "EECDH+ECDSA+AESGCM EECDH+aRSA+AESGCM EECDH+ECDSA+SHA384 EECDH+ECDSA+SHA256 EECDH+aRSA+SHA384 EECDH+aRSA+SHA256 EECDH EDH+aRSA !RC4 !aNULL !eNULL !LOW !3DES !MD5 !EXP !PSK !SRP !DSS";
#Logjam attack protect!
ssl_dhparam /etc/ssl/private/dhparams.pem;
#Optional
#SSL session cache
ssl_session_cache shared:SSL:10m;
ssl_session_tickets off;
#OCSP
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
#HSTS
# Disable preloading HSTS for now.  You can use the commented out header line that includes
# the "preload" directive if you understand the implications.
#add_header Strict-Transport-Security "max-age=63072000; includeSubdomains; preload";
add_header Strict-Transport-Security "max-age=63072000; includeSubdomains";
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;

Now simply test the configuration with nginx -t and restart Nginx with systemctl restart nginx if you are using Systemd.

Testing it out

The simplest way of testing is simply clicking the SSL icon on your browser and clicking for More Information. I use Firefox so this is what I get.

screenshot-from-2016-10-03-11-33-08

But let’s do a comprehensive test, shall we? So head on over to SSL Labs to test out our config. This is how my website fares in the test.

 

 

 

Cipher Suites should have ECDSA enabled.

screenshot-from-2016-10-03-11-39-24

Handshake Simulation should tell you the compatibility of our chosen cipher suite and what cipher a particular client would use.

screenshot-from-2016-10-03-11-41-48

I get a grade Aย  in the test since I do not have HSTS enabled yet. Enabling it should give you a A+ grade.

Automated certificate renewal

Again the Digitalocean article referenced above tells you how to do this beautifully for RSA certificates. Type in crontab -e and type the following text.

30 2 * * 1 /usr/bin/letsencrypt renew >> /var/log/le-renew.log
35 2 * * 1 /bin/systemctl reload nginx
36 2 * * 1 /bin/systemctl restart nginx

This will try to renew the certificates created every Monday at 2:30 am and reload/restart Nginx at 2:35 am. However this will NOT work for ECDSA certificates. You will have to renew them manually. I will update this post if I find a solution for this.

Future considerations

Using Curve25519 instead of secp384r1 is gaining traction because this is supposedly faster and others are “compromised by NSA”.ย  Take that how you like. ๐Ÿ˜›

cmwgg-swcaejoei

Curve25519 represent the right and secp256k1 represents the left

However this needs OpenSSL 1.1 and browser support is slowly coming together.

ChaCha20-Poly1305 cipher suites for encryption looks interesting too. But this again needs OpenSSL 1.1 and would be slower than AES-GCM on modern clients with AES-NI support. Android devices with ARMv8 do have AES-NI and I can only imagine more devices having AES-NI support in the future.

I hope you found this article useful. I do not claim to be an expert on cryptography. Do let me know if there are any errors in this post. ๐Ÿ™‚

6 thoughts on “Hybrid ECDSA and RSA certificates with Nginx and Let’s Encrypt

  1. Deandre91

    I see your site is in the same niche like my
    page. Do you allow guest posts? I can write excellent posts for you.
    Let me know if you are interested.

    Reply
    1. varunpriolkar Post author

      Hello Deandre91,
      Since this is my personal blog, I do not allow guest posts. Sorry!

      Reply
      1. Novakin

        Hey Varun,

        One question tho, would you allow me to publish a translation of your post on my blog with credits and a link ?

        Reply
        1. varunpriolkar Post author

          Sure! You are free to publish it on your blog. ๐Ÿ™‚

          Reply
  2. Novakin

    Hi Varun!

    Wonderfull post! Really usefull.

    One small issue on my side. I can’t managed to get the OSCP must stapled to work at all.

    Any idea ?

    Reply

Leave a Reply