Alexey Samoshkin

@alexeysamoshkin

RSA and ECDSA hybrid Nginx setup with LetsEncrypt certificates

January 3rd 2018

RSA vs ECC comparison. Issuing LetsEncrypt certificates using certbot and acme.sh clients wrapped in Docker image. Nginx setup

Table of contents

  • RSA vs ECC comparison. Why you might need ECDSA certificate?
  • How to Generate RSA and EC keys/CSR using openssl.
  • What is LetsEncrypt CA? How to issue free domain validated certificates in automatic fashion?
  • How to generate RSA and/or ECDSA certificates through Docker image while still using certbot and acme.sh clients under the hood?
  • How to configure and test Nginx for hybrid RSA/ECDSA setup?

RSA vs ECC comparison

RSA is a most popular public-key cryptography algorithm. Certificates with RSA keys are the gold standard and the present of the current Internet PKI security. It’s old and battle tested technology, and that’s highly important from the security perspective. Elliptic curve cryptography is an alternative approach to public-key cryptography over the current RSA standard.

RSA algorithm can be used for encryption and digital signing, while ECC can only be used for signing.

The security of a key depends on its size and its algorithm. Some algorithms are easier to break than others. Breaking an RSA key requires to factor the product of two large numbers. Breaking an ECC key requires to find the discrete logarithm between points on an elliptic curve, and there is no progress so far to achieve this.

Algorithms require different key size for the same level of security. ECC can use smaller key sizes. Here is table with key size comparison:

+----------------------+-----------------+--------------------+
| Symmetric Key length | RSA key length | ECC key length |
+----------------------+---------- ------+--------------------+
| 80 | 1024 | 160 |
| 112 | 2048 | 224 |
| 128 | 3072 | 256 |
| 192 | 7680 | 384 |
| 256 | 15360 | 512 |
+----------------------+-----------------+--------------------+

For example, 256-bit ECC key is equivalent to RSA 3072-bit key, providing 128 bits of security:

Smaller keys are less computationally intensive for generating signatures because the math involves smaller numbers. However, while ECC is faster at signature generation, it’s slower than RSA at signature verification. Let’s measure this using openssl as a benchmarking tool:

$ openssl speed ecdsap256 rsa2048
                          sign/s     verify/s
rsa 2048 bits 679.0 23489.0
256 bit ecdsa (nistp256) 15581.9 6211.7

Smaller ECC public key means smaller certificate size — less data to pass around, quicker to download, and faster TLS handshake.

If you want more security, RSA does not scale well — you have to increase the RSA modulus size far faster than the ECDSA curve size. 1024 bit RSA keys are obsolete, 2048 are the current standard size. If you need to go farther, you’d stuck. First, if CA does not provide 4096 bit RSA keychain, signing your own 4096 bit RSA key with a 2048 RSA intermediary doesn’t make sense. Second, note that every doubling of an RSA private key degrades TLS handshake performance approximately by 6–7 times. So, if you need more security, choose ECC.

While ECC has some benefits, there are also some drawbacks. The technology is not that mature and tested as RSA. And there is a compatibility question, however seems it can be used on most operating systems and modern browsers. Check out these resources for ECC compatibility details.

There a lot of elliptic curves out there. Most popular and supported by major browsers are P-256, P-384, P-521, x25519. While P-256 and P-384 are part of NIST’s Suite B algorithms, P-521 and x25519 are not. Google Chrome has dropped support for the P-521 curve, same is going on regarding NSS/Firefox. To maximise interoperability with existing browsers and servers, stick to P-256 prime256v1 and P-384 secp384r1 curves.

To view list of all available ECC curves, that OpenSSL library supports:

openssl ecparam -list_curves

To view supported curves of your browser, use SSL Labs Client Test.

To conclude, ECDSA certificates pros and cons:

  • (+) smaller key and certificate size, faster TLS handshake
  • (+) better performance from server perspective, requires less CPU and memory
  • (+) stronger algorithm, harder to break
  • (+) scales better when you need more security
  • (-) not as mature and time-tested as RSA, ECC is relatively new compared to RSA
  • (-) lack of compatibility and lack of widespread support.
  • (-) ECC is faster and generating signatures, but signature verification is computationally intensive and slower that RSA

You don’t need to choose between RSA and ECC exclusively. You can setup hybrid configuration, serving ECDSA certificate first, with a fallback to RSA certificate for non-supporting clients.

Generate RSA and ECC keys/CSRs using openssl

Usually, before you send a request to a CA to issue a certificate, you need to generate private key and CSR (certificate signing request). It’s super easy with openssl tool.

Generate RSA key at a given length:

openssl genrsa -out example.key 2048

Generate EC key with a given curve:

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

Generate a CSR from existing private key with a given subject info:

openssl req -new  -key example.key -out example.csr -subj "/CN=example.com" -sha256

Or you can combine both key and CSR creation within a single command:

openssl req -nodes -newkey rsa:2048 -keyout example.key -out example.csr -sha256

LetsEncrypt CA

If you want to experiment with ECDSA and RSA certificates, the best option is to use LetsEncrypt Certificate Authority, which allows to generate free domain validated certificates in automated fashion.

In order to obtain a certificate, you need to prove the ownership of the domain.

Usually this process requires manual work: generate a private key and a CSR (Certificate Signing Request) with relevant subject info and common name, send a CSR to the CA, and finally prove the domain’s ownership using a selected challenge method:

  • email; CA sends email with a verification link one of the address like admin@example.com. You need to follow the link to complete the verification.
  • http; you need to download a file with a specially crafted token and then upload it to your’s domain root folder, so it’s accessible at URI http://example.com/.well-known/pki-validation
  • dns; you must create a special CNAME record in the DNS configuration for your domain

LetsEncrypt automates this process by using a client that can talk ACME protocol (Automatic Certificate Management Environment). Client typically runs on your web host, and communicates to LetsEncrypt CA or another ACME-compatible server. Client receives unique token from the server, generates key from it, starts a standalone web server listening on port 80 and serves key at special URI, like http://example.com/.well-known/acme-challenge. In case of DNS challenge method, client can automatically add CNAME record to your DNS configuration depending on DNS server/provider. LetsEncrypt CA then makes HTTP or DNS request to your domain to retrieve the key derived from the token. Successful response proves the domain ownership, and CA issues the requested certificate.

You don’t generate private key and CSR on your own, this is handled by the client software on your web host. Note, that private keys are never generated/leaked on CA servers, and stored on your web host exclusively. Clients typically provide some way to adjust some settings through client, like key length, subject alternative names, or if you need further customisation, you can fallback to providing custom CSR for more flexibility and control.

Note, that Lets Encrypt’s certificates are only valid for 90 days. This is to encourage users to automate certificate issuance and renewal process.

Rate limits and staging server

Be aware, that LetsEncrypt CA production servers put strict rate limits:

  • certificates per Registered Domain (20 per week)
  • up to 100 alternative names per certificate
  • duplicate certificate limit of 5 certificates per week

While you’re trying and experimenting, most likely you will hit the latter one, so it’s better to use LetsEncrypt staging environment with much relaxed limits.

The staging environment intermediate certificate (“Fake LE Intermediate X1”) is issued by a root certificate not present in browser/client trust stores, so you might want to add “Fake LE Root X1” as trusted one while testing.

Generate LetsEncrypt certificates using Docker image

There are variety of ACME clients: certbot, acme.sh, lego, and others. I’m not going to dive into client specifics here — anyway docs would explain better.

Personally, I’ve found that easier and faster way to get things done is to issue certs through some Docker image. This is for lazy people, and those, who don’t want to spend much time digging with LetsEncrypt and parsing docs.

I’ve prepare one: asamoshkin/letsencrypt-certgen on Docker Hub and Github link. This Docker image provides a simple single entrypoint to obtain and manage SSL certificates from LetsEncrypt CA. It encapsulates two popular ACME clients: certbot and acme.sh, which are used to obtain RSA and/or ECDSA certificates respectively. We need both, because certbot is not capable of issuing ECDSA certificates (to be more correct, only thru custom CSR, but then you lose the ability to renew, revoke and further manage such certificate).

Here is an example of issuing both ECDSA (prime256v1 curve) and RSA (2048) certificates for the single domain foobbz.site

docker run \
-v /var/ssl:/var/ssl \
-p 80:80 \
-e DOMAINS=foobbz.site \
--rm \
asamoshkin/letsencrypt-certgen issue

The requirement is to run this image on the server, configured for the domain you want to get certificates for (where your DNS A record points to). Server’s port 80 should be open by a firewall, so LetsEncrypt CA server can perform validation challenge.

Once done, certificates, keys and related files are stored at /var/ssl/$domain_common_name path, on a/var/ssl volume you’ve mounted into the container before.

# tree /var/ssl

/var/ssl
└── foobbz.site
├── certs
│ ├── cert.ecc.pem
│ ├── cert.rsa.pem
│ ├── chain.ecc.pem
│ ├── chain.rsa.pem
│ ├── fullchain.ecc.pem
│ └── fullchain.rsa.pem
└── private
├── privkey.ecc.pem
└── privkey.rsa.pem

All files are encoded in PEM format:

  • cert.rsa.pem, cert.ecc.pem - generated certificates (RSA or ECDSA)
  • chain.[type].pem - chain of intermediate CA certificates (e.g. Fake LE Intermediate X1)
  • fullchain.[type].pem - certificate bundled with any intermediate CA certificates. This is suitable for Nginx directive ssl_certificate, which requires a bundle, instead of leaf certificate.
  • privkey.[type].pem - private key file

You’re not limited to a single certificate for a single domain. You can issue several certificates for several domains, or single certificate covering multiple domains using SAN (X.509 subject alternative names extension). You can opt to generate ECDSA or RSA certificate only with custom key length or elliptic curve.

First, prepare a file with domains lists. Each line represents individual certificate to be issued. First name within each line is a common name, whereas subsequent comma-separated names are certificate alternative names (SAN).

# cat /root/domains.txt

foobbz.site,www.foobbz.site,web.foobbz.site
foobbz2.site,www.foobbz.site

Run a command to issue two ECDSA “secp384r1” certs only (no RSA cert) for foobbz.site and foobbz2.site domains:

docker run \
-v /var/ssl:/var/ssl \
-v /root/domains.txt:/etc/domains.txt \
-p 80:80 \
-e RSA_ENABLED=0 \
-e ECDSA_KEY_LENGTH=ec-384 \
-e DOMAINS=/etc/domains.txt \
--rm \
asamoshkin/letsencrypt-certgen issue

The main use case for the image is to trigger a one-shot command to issue certificates, but you can also further manage your certificates by renewing, revoking or deleting them using same image.

You can use this image ad-hoc at a build time, at a run-time prior to Nginx/Apache startup, or by running it from a cron job to renew certificates on regular basis. The idea is that LetsEncrypt stuff is encapsulated within a single container , and you don’t need to pollute your Nginx/Apache container.

More complete list of features:

  • automate issuance and managing LetsEncrypt SSL certificates
  • generate DV certificates for 1..N domains, support multi-domain SAN (Subject alternative names) certificates
  • generate RSA and/or ECDSA certificate with configurable key params: RSA key length (2048, 3072, 4096) and elliptic curve for EC key (prime256v1, secp384r1)
  • choose DV challenge verification method: standalone or webroot
  • renew certificates when they’re about to expire or force renewal
  • revoke certificates by contacting LetsEncrypt CA
  • use either LetsEncrypt staging or production server

For further details and examples, check out samoshkin/docker-letsencrypt-certgen on Github. You can also just check out the source code to learn how to consume certbot and acme.sh clients.

Also, there is a ZeroSSL docker image worth checking out.

Nginx configuration for hybrid RSA/ECDSA setup

Once you’ve generated certificates, it’s time to configure your web server. Nginx allows hybrid side by side RSA and ECDSA certificates, and will serve one or another during TLS handshake depending on agreed cipher suite.

server {
listen 443 ssl default_server;
server_name foobbz.site www.foobbz.site;
  # RSA certificates
ssl_certificate /var/ssl/foobbz.site/certs/fullchain.rsa.pem;
ssl_certificate_key /var/ssl/foobbz.site/private/privkey.rsa.pem;
  # ECDSA certificates
ssl_certificate /var/ssl/foobbz.site/certs/fullchain.ecc.pem;
ssl_certificate_key /var/ssl/foobbz.site/private/privkey.ecc.pem;

# Other directives
}

Also you need to tell Nginx to prefer ECDSA over RSA for authentication by correctly ordering your cipher suites (notice how suites with “ECDSA” go first, followed by suites with “aRSA”):

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:DHE+AESGCM:DHE:!RSA!aNULL:!eNULL:!LOW:!RC4:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS:!CAMELLIA:!SEED";

That’s it, now start your Nginx, and let’s test our configuration using openssl as a TLS client and extract certificate. Or you can use Qualys SSL Labs Server Test to view certificates.

$ openssl s_client -host foobbz.site -port 443 -cipher ECDHE-ECDSA-AES128-GCM-SHA256 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' | openssl x509 -noout -text

Looking at a public key, we can ensure that ECDSA certificate is served.

Subject Public Key Info:
Public Key Algorithm: id-ecPublicKey
Public-Key: (256 bit)
pub:
04:47:ee:86:9f:c9:5a:81:16:89:38:f7:5b:9d:ba:
6b:8b:d2:aa:8e:1e:54:67:f7:f1:44:84:5f:10:df:
7f:df:16:f4:2f:d6:c1:78:b8:71:68:e9:ee:78:82:
fc:2e:ae:96:e9:a3:b7:26:c0:ed:41:39:2a:48:f9:
0f:28:10:4e:15
ASN1 OID: prime256v1
NIST CURVE: P-256

Note, that leaf ECDSA certificates are still signed by LetsEncrypt’s RSA certificate chain (Let’s Encrypt Authority X3, DST Root CA X3). LetsEncrypt does not use dedicated EC certificates to sign to build complete EC chain.

0 s:/CN=foobbz.site
i:/C=US/O=Let’s Encrypt/CN=Let’s Encrypt Authority X3
1 s:/C=US/O=Let’s Encrypt/CN=Let’s Encrypt Authority X3
i:/O=Digital Signature Trust Co./CN=DST Root CA X3

Now, let’s use another cipher suite (ECDHE-RSA-AES128-GCM-SHA256) to test RSA configuration.

openssl s_client -host foobbz.site -port 443 -cipher ECDHE-RSA-AES128-GCM-SHA256 2>&1 < /dev/null | sed -n '/-----BEGIN/,/-----END/p' | openssl x509 -noout -text

We get 2048 bits RSA public key. By the way, notice the difference in length between them.

Subject Public Key Info:
Public Key Algorithm: rsaEncryption
Public-Key: (2048 bit)
Modulus:
00:ea:cb:5e:22:dd:93:fe:63:21:c4:bd:b9:07:78:
fc:ef:66:38:7a:19:bf:68:58:39:16:86:ee:f6:2c:
18:68:9c:32:c0:5b:a4:76:e8:e0:40:0e:6d:29:7c:
bc:04:67:b4:1b:05:e8:72:53:24:dc:4f:a6:3d:48:
41:2e:83:99:fa:13:20:88:b8:e5:5d:34:57:01:c6:
eb:fc:c1:67:e8:e4:ec:58:2c:a2:ce:51:ea:99:c0:
bb:de:61:8a:40:76:80:50:48:25:c6:7f:0e:a4:a6:
61:e7:25:67:b1:74:ee:1f:f1:75:e8:76:a0:c5:5d:
9c:40:48:8b:d3:95:e1:27:d2:d6:ca:14:e4:39:ac:
4d:0d:35:23:89:db:4b:ef:60:84:0b:4d:15:76:0e:
3c:f5:52:1c:20:ce:d8:03:25:22:7a:37:84:fb:d2:
1b:00:ff:31:69:55:65:7d:42:d1:31:99:0c:d6:29:
41:36:06:bf:0d:ab:31:1a:e6:b0:6a:76:67:2c:7b:
c0:5b:34:55:49:e2:4c:d9:e4:40:99:1c:c1:1d:6a:
88:c1:53:af:ee:ab:b5:2e:e6:76:ff:1c:33:e2:ca:
7e:d7:93:e6:23:df:cd:78:a6:39:f4:04:a2:44:d0:
a6:cc:f1:51:2f:5d:dc:5e:ea:ff:57:d7:f1:82:d4:
48:11
Exponent: 65537 (0x10001)

So, that’s it. Thank you for reading this post.

Edit #1

Reddit user wuunderbar pointed out that Docker container does not have enough entropy to generate key material. One solution I’m aware of is mounting /dev/urandom from host machine into /dev/random of the container. See this StackOverflow answer.

docker run -v /dev/urandom:/dev/random ...

Resources

asamoshkin/letsencrypt-certgen — Docker Hub — https://hub.docker.com/r/asamoshkin/letsencrypt-certgen/

samoshkin/docker-letsencrypt-certgen: Generate, renew, revoke RSA and/or ECDSA SSL certificates from LetsEncrypt CA using certbot and acme.sh clients in automated fashion — https://github.com/samoshkin/docker-letsencrypt-certgen

Elliptic Curve Cryptography (ECC Certificates) | DigiCert.com — https://www.digicert.com/ecc.htm

ECC — https://support.globalsign.com/customer/portal/articles/1994347-ecc

ECC Compatibility — https://support.globalsign.com/customer/portal/articles/1995283-ecc-compatibility

ECDSA: The digital signature algorithm of a better internet — https://blog.cloudflare.com/ecdsa-the-digital-signature-algorithm-of-a-better-internet/

Let’s Encrypt — Free SSL/TLS Certificates — https://letsencrypt.org/

Certbot — https://certbot.eff.org/

ZeroSSL: Free SSL — https://zerossl.com/

Neilpang/acme.sh: A pure Unix shell script implementing ACME client protocol — https://github.com/Neilpang/acme.sh

RSA and ECDSA performance | securitypitfalls — https://securitypitfalls.wordpress.com/2014/10/06/rsa-and-ecdsa-performance/

Testing out ECDSA certificates — https://scotthelme.co.uk/ecdsa-certificates/

More by Alexey Samoshkin

More Related Stories