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:
$ 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:
$ 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):
$ 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):
$ 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):
$ 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:
$ 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:
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
:
$ 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:
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:
# 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.