Skip to content

Instantly share code, notes, and snippets.

@ronau
Last active September 2, 2024 01:19
Show Gist options
  • Save ronau/f78dfef5c496e4240708bbedc6ca512d to your computer and use it in GitHub Desktop.
Save ronau/f78dfef5c496e4240708bbedc6ca512d to your computer and use it in GitHub Desktop.
A simple nginx configuration for a demo of client certificate authentication

Client certificate authentication with nginx

Below is a simple nginx configuration file enabling client certificate authentication, i.e. the user/client has to present a certificate in order to get access.

It is the result of a small proof of concept solution for the following task:

  • A certain web server directory should be used as file storage
  • Transport security (i.e. TLS)
  • No public access, user/client authentication required, 2 options
    1. HTTP basic authentication (i.e. username/password)
    2. Client certificate authentication
  • When using client certificate authentication, file upload is allowed
  • When using basic authentication (username/password), file upload is not allowed, it's only allowed to access the already uploaded files

Depending on the subdomain, a different authentication method is required, e.g.

  • pass.upload.domain.tld will require username and password
  • cert.upload.domain.tld will require a client certificate

Managing the certificates:

For the certificate authentication you need signed client certificates as well as the certificate of the signing CA.

For example, you can create your own root CA and create an intermediate CA certificate. Then you create one or more client certificates and sign them with the intermediate CA. Just search the interwebs on how to create and manage your own CA.

The client certificates can be handed out to your users/clients then.

Nginx config file

Here are the different sections of the config file.

Unencrypted HTTP will be redirected to HTTPS:

server {
    listen 80;
    listen [::]:80;
    server_name cert.upload.domain.tld pass.upload.domain.tld;
    return 301 https://$host$request_uri;
}

A new server block for the client certificate authentication including the transport encryption stuff (i.e. TLS encryption):

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name cert.upload.domain.tld;


    ## Here comes the TLS configuration
    ## Powered by https://bettercrypto.org/static/applied-crypto-hardening.pdf and
    ##            https://mozilla.github.io/server-side-tls/ssl-config-generator/

    ssl_certificate /var/www/upload-poc-certs/cert.upload.domain.tld.pem; 
    ssl_certificate_key /var/www/upload-poc-certs/cert.upload.domain.tld.key; 
    ssl_session_timeout 5m;
    ssl_session_cache shared:SSL:20m;
    ssl_session_tickets off;

    # Modern and strict configuration
    ssl_protocols TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
    ssl_prefer_server_ciphers on;

    # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits or more
    ssl_dhparam /etc/letsencrypt/group16.pem;

    # HSTS
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";

    # Some other security related headers
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Robots-Tag none;
    add_header X-Download-Options noopen;
    add_header X-Permitted-Cross-Domain-Policies none;

    ##
    ## End of TLS configuration

It just needs two lines to enable client certificate authentication. The certificate file contains one or more CA certificates which are used to verify the certificates presented by the clients. In our case it would contain the certificates of our intermediate CA as well as our root CA:

    # Client certificate authentication
    ssl_client_certificate /var/www/upload-poc-certs/Intermediate_CA.pem;
    ssl_verify_client on;

The rest is only about the configuration of the directories. Note that we switch on PUT and DELETE, which allows clients to upload files:

    # Path to the root directory
    root /var/www/upload-poc/;

    # set max upload size
    client_max_body_size 20M;

    location / {
        autoindex on;

        client_body_temp_path /tmp;
        dav_methods PUT DELETE;
    }

}

Now comes another server block for the basic authentication part. It is almost the same as the one before. The only differences are:

  • different server_name (i.e. different subdomain for access with username/password)
  • instead of client certificate stuff we have the two auth_basic lines (first one is the "realm", i.e. kind of namespace, second one is the path to the password file)
  • location block is different, since we don't allow uploads

The htpasswd file containing usernames/password can be created using the corresponding commandline tool htpasswd (just search online for help).

server {
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name pass.upload.domain.tld;


    ## Here comes the TLS configuration
    ## Powered by https://bettercrypto.org/static/applied-crypto-hardening.pdf and
    ##            https://mozilla.github.io/server-side-tls/ssl-config-generator/

    ssl_certificate /var/www/upload-poc-certs/pass.upload.domain.tld.pem; 
    ssl_certificate_key /var/www/upload-poc-certs/pass.upload.domain.tld.key; 
    ssl_session_timeout 5m;
    ssl_session_cache shared:SSL:20m;
    ssl_session_tickets off;

    # Modern and strict configuration
    ssl_protocols TLSv1.2;
    ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256';
    ssl_prefer_server_ciphers on;

    # Diffie-Hellman parameter for DHE ciphersuites, recommended 2048 bits or more
    ssl_dhparam /etc/letsencrypt/group16.pem;

    # HSTS
    add_header Strict-Transport-Security "max-age=15768000; includeSubDomains; preload;";

    # Some other security related headers
    add_header X-Content-Type-Options nosniff;
    add_header X-Frame-Options DENY;
    add_header X-XSS-Protection "1; mode=block";
    add_header X-Robots-Tag none;
    add_header X-Download-Options noopen;
    add_header X-Permitted-Cross-Domain-Policies none;

    ##
    ## End of TLS configuration

    # HTTP basic authentication (username/password)
    auth_basic "upload-poc";
    auth_basic_user_file /var/www/htpasswd;

    # Path to the root directory
    root /var/www/upload-poc/;

    location / {
        autoindex on;
    }

}

Testing

You can open cert.upload.domain.tld in your browser and you will be prompted to select a client certificate for authentication. Depending on your browser and system, you might have to import the client certificate into the system's (or browser's) certificate store first. If you want to test uploading a file, you can do that using curl:

curl -v --cert client.crt.pem --key client.key.pem https://cert.upload.domain.tld --upload-file example.pdf

If you want to remove the file, you can do that as well:

curl -v --cert client.crt.pem --key client.key.pem -X "DELETE" https://cert.upload.domain.tld/example.pdf

You can now open pass.upload.domain.tld in your browser and you will be required to authenticate with username and password. Once you did that you will be able to see all the files that sit in the directory (and have been uploaded before). Uploading a file to pass.upload.domain.tld will not work.

-- Have fun!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment