Nginx + SSL Client Certificate Verification: Manage access to a site

Access control is a fundamental part of security. Most entities rely on the combination of username and password, sometimes with additional multi-factor authentication to improve security. Some entities also use the SSL client certificate verification to manage access to specific resources. One of the use cases where SSL client certificate verification fits perfectly is managing access to internet-facing development or staging servers. In this post, I'll share how to set up the certificates and configure nginx to verify users based on their certificates.

Preparing the certificates

There are two certificates we are going to create. The first one is the root certificate. It will be placed in the Nginx server. The second one is the client certificate. It will be installed in the client machine/browsers.

Root CA

For generating a root CA, execute these two steps:

Generate RSA Key

openssl genrsa -aes256 -out ca.key 4096

Create Root CA crt file.

openssl req -new -x509 -days 3650 -key ca.key -out ca.crt

Setup CA configuration

This is an optional step, but if you want to be able to revoke access you previously granted, you need to do this step.

Create a file named ca.cnf in the same directory as the ca.key and ca.crt.

[ ca ] default_ca = gca [ crl_ext ] authorityKeyIdentifier=keyid:always [ gca ] dir = ./ new_certs_dir = $dir unique_subject = no certificate = $dir/ca.crt database = $dir/certindex ; mark(1[9:11]) dimgrey private_key = $dir/ca.key serial = $dir/certserial default_days = 365 default_md = sha256 policy = gca_policy x509_extensions = gca_extensions crlnumber = $dir/crlnumber default_crl_days = 365 [ gca_policy ] commonName = supplied stateOrProvinceName = supplied countryName = optional emailAddress = optional organizationName = supplied organizationUnitName = optional [ gca_extensions ] basicConstraints = CA:false subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always keyUsage = digitalSignature,keyEncipherment extendedKeyUsage = serverAuth crlDistributionPoints = URI:http://example.com/root.crl subjectAltName = @alt_names [ alt_names ] DNS.1 = example.com DNS.2 = *.example.com

Initialize an empty file for the CA database.

touch certindex

Initialize value for certserial and crlnumber

echo 01 > certserial echo 01 > crlnumber

User Certificates

Generate the user RSA key.

openssl genrsa -aes256 -out client01/user.key 4096

Create Certificate-Signing Request (CSR)

openssl req -new -key client01/user.key -out client01/user.csr

Sign the CSR.

If you did the setup CA configuration step, sign the CSR file by running this command.

openssl ca -config ca.cnf -in client01/user.csr -out client01/user.crt

If you skipped the setup CA configuration step, sign the CSR file by running this command.

openssl x509 -req -days 365 -in client01/user.csr -CA ca.crt -CAkey ca.key -set_serial 01 -out client01/user.crt

Convert the crt file to pfx/p12 file.

Most of the time, browsers/client machines only accept a certificate in the pfx format. Run this command to convert the crt file to the pfx/p12 format.

openssl pkcs12 -export -out client01/user.pfx -inkey client01/user.key -in client01/user.crt -certfile ca.crt

You'll be prompted to enter an export password. You must input the exact password when adding the certificate to a browser.


Setting up nginx with client certificates verification

Add these lines to a server block in your nginx configuration

ssl_client_certificate /path/to/client/verfication/ca.crt; ssl_verify_client optional; ssl_verify_depth 2;

You can do location-based access control. Location-based here refers to a location block in your nginx configuration, for example:

location /private { # mark(1[13:41]) dimgrey if ($ssl_client_verify != SUCCESS) { return 403; } .... }

Here is a complete example of a server block in the nginx configuration

server { listen 443 ssl http2; listen [::]:443 ssl http2; server_name www.example.com; ssl_certificate /path/to/your/https/certificate.pem; ssl_certificate_key /path/to/your/https/private-key.pem; include snippets/ssl-params.conf; # mark(1:3) dimgrey ssl_client_certificate /path/to/client/verification/ca.crt; ssl_verify_client optional; ssl_verify_depth 2; root /usr/share/nginx/html; index index.html index.htm; location / { # mark(1[13:41]) dimgrey if ($ssl_client_verify != SUCCESS) { return 403; } } }

Adding the User Certificates to the client machine/browsers