Client Side Certificate Authentication in Nginx with Elliptic Curve Cryptography

We will setup a client side certificate authentication in Nginx with Elliptic curve cryptography using ECDSA (curve secp384r1) for certificates and a self signed Certificate Authority (CA).

Create Server Key and Certificate Signing Request (CSR) in PEM format:

1
2
$ openssl ecparam -out server_ecdsa.pem.key -name secp384r1 -genkey
$ openssl req -new -key server_ecdsa.pem.key -out server_ecdsa.pem.csr

Create CA Key and Certificate (CRT) in PEM format:

1
2
$ openssl ecparam -out ca_ecdsa.pem.key -name secp384r1 -genkey
$ openssl req -new -x509 -days 3652 -sha512 -key ca_ecdsa.pem.key -out ca_ecdsa.pem.crt

Sign server certificate with our own Certificate Authority (CA):

1
$ openssl x509 -req -days 365 -sha512 -in server_ecdsa.pem.csr -CA ca_ecdsa.pem.crt -CAkey ca_ecdsa.pem.key -set_serial 01 -out server_ecdsa.pem.crt

Create client Key and Certificate Signing Request (CSR):

1
2
$ openssl ecparam -out client_ecdsa.pem.key -name secp384r1 -genkey
$ openssl req -new -key client_ecdsa.pem.key -out client_ecdsa.pem.csr

Sign client certificate with our own Certificate Authority (CA):

1
$ openssl x509 -req -days 365 -sha512 -in client_ecdsa.pem.csr -CA ca_ecdsa.pem.crt -CAkey ca_ecdsa.pem.key -set_serial 01 -out client_ecdsa.pem.crt

Convert client Key and Certificate to PKCS:

1
$ openssl pkcs12 -export -clcerts -in client_ecdsa.pem.crt -inkey client_ecdsa.pem.key -out client_ecdsa.p12

Of course clients can generate their own key, send the CSR to the CA, the CA signs it and sends certificates to clients. Then the client can generates the PKCS himself for his browser.

An example of Nginx config is:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
server {
listen 443 ssl http2;
server_name example.org;
root /usr/share/nginx/html/example;
index index.php index.html index.htm;

##
# SSL
##

# Certificate
ssl_certificate /etc/nginx/ssl/server_ecdsa.pem.crt;
ssl_certificate_key /etc/nginx/ssl/server_ecdsa.pem.key;
ssl_client_certificate /etc/nginx/ssl/ca_ecdsa.pem.crt;
ssl_verify_client on;
# ECC
ssl_ecdh_curve secp384r1;
# Strong cryptography
ssl_protocols TLSv1.2;
ssl_ciphers EECDH+CHACHA20:EECDH+AESGCM:EECDH+AES;
ssl_prefer_server_ciphers on;
# ssl optimizations
ssl_session_timeout 5m;
ssl_session_cache shared:SSL:20m;
ssl_session_tickets on;
}

We can now import the client PKCS certificate (.p12) into a browser and try to reach our website or try it with openssl or curl:

1
2
3
$ openssl s_client -connect example.org:443 -key client_ecdsa.pem.key -cert client_ecdsa.pem.crt

$ curl -k --key client_ecdsa.pem.key --cert client_ecdsa.pem.crt https://example.org

Other algorithms#

To list supported curves by openSSL:

1
openssl ecparam -list_curves

ed25519 is not in the list of officially supported curves but it's still possible to generate a private key with it using another command:

1
2
3
4
# private key
openssl genpkey -out server_ed25519.pem.key -algorithm ed25519
# public key
openssl pkey -in server_ed25519.pem.key -pubout > server_ed25519.pem.key.pub

Nginx can support what openSSL but the issue web browsers are not compliant to RFC's (for example about TLS 1.3) and support a very few amount of curves.

So for example Firefox and Chromium don't support ed25519 or brainpoolP512r1 while curl and openssl client can.

It's possible to see what TLS related cryptography is supported by your browser with SSL Labs Client test.

Firefox 104.0.2 on Linux (2022):

  • Signature algorithms: SHA256/ECDSA, SHA384/ECDSA, SHA512/ECDSA, RSA_PSS_SHA256, RSA_PSS_SHA384, RSA_PSS_SHA512, SHA256/RSA, SHA384/RSA, SHA512/RSA, SHA1/ECDSA, SHA1/RSA
  • Named Groups: x25519, secp256r1, secp384r1, secp521r1, ffdhe2048, ffdhe3072

Chromium 105.0.5195.125 on Linux (2022):

  • Signature algorithms: SHA256/ECDSA, RSA_PSS_SHA256, SHA256/RSA, SHA384/ECDSA, RSA_PSS_SHA384, SHA384/RSA, RSA_PSS_SHA512, SHA512/RSA
  • Named Groups: tls_grease_9a9a, x25519, secp256r1, secp384r1

So unfortunately for a large support we are force to use secp384r1 or secp521r1 if we don't care about Chrome-based browser. It makes it especially hard to avoid using NIST supported algorithms.

Share