Kushal Das

FOSS and life. Kushal Das talks here.

kushal76uaid62oup5774umh654scnu5dwzh4u2534qxhcbi4wbab3ad.onion

Setting up local mTLS environment using mkcert

mTLS or mutual TLS is a way of doing mutual authentication. When we talk about TLS in general, we only about TLS for the servers/services. There the clients can verify that they are connected to the right server. But, the server does not know much about the clients themselves. This can be done via mTLS, say for services talking to each other. To know more please read the Cloudflare writeup on mTLS.

In this blog post we will see how we can use the mkcert from Filippo Valsorda to setup a local environment, so that you can play-around & learn.

Install nss-tools package for your system

For Fedora, I installed it via dnf.

$ sudo dnf install nss-tools -y

Getting mkcert

I grabbed the latest release from the gitub release page.

$ wget https://github.com/FiloSottile/mkcert/releases/download/v1.4.3/mkcert-v1.4.3-linux-amd64
$ mv mkcert-v1.4.3-linux-amd64 ~/bin/mkcert
$ chmod +x ~/bin/mkcert

Setting up the local CA

$ mkcert -install
Created a new local CA 💥
The local CA is now installed in the system trust store! ⚡️
The local CA is now installed in the Firefox trust store (requires browser restart)! 🦊

This will create two important files inside of your user home directory.

❯ ls -l .local/share/mkcert/
.r--------@ 2.5k kdas 20 Dec 12:14 rootCA-key.pem
.rw-r--r--@ 1.8k kdas 20 Dec 12:14 rootCA.pem

Note:: The rootCA-key.pem is an important file and it can allow people to decrypt traffic from your system. Do not share or randomly copy it around.

The rootCA.pem file contains the public key, we can use the openssl tool to inspect it.

❯ openssl x509 -text -noout -in ~/.local/share/mkcert/rootCA.pem
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            91:71:92:a2:d0:ac:9a:27:88:85:e0:30:40:b0:1d:e9
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: O = mkcert development CA, OU = kdas@localhost.localdomain (Kushal Das), CN = mkcert kdas@localhost.localdomain (Kushal Das)
        Validity
            Not Before: Dec 20 11:14:33 2021 GMT
            Not After : Dec 20 11:14:33 2031 GMT
        Subject: O = mkcert development CA, OU = kdas@localhost.localdomain (Kushal Das), CN = mkcert kdas@localhost.localdomain (Kushal Das)
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (3072 bit)
                Modulus:
                    00:d4:67:ae:92:75:5e:ff:8b:26:0f:c4:e1:c5:61:
                    90:3a:4d:2e:1e:bf:bb:d5:77:2d:b0:fc:8d:10:5d:
                    1d:52:67:44:65:a0:1f:59:ee:37:69:39:d9:94:9c:
                    c2:0d:39:11:c3:8b:71:94:3a:75:a9:46:ad:5f:ed:
                    4f:7c:3b:6e:75:21:5a:41:70:e0:21:4b:dc:cf:2e:
                    b0:85:e6:29:db:3d:6a:50:71:0f:9f:63:bd:39:89:
                    53:d8:ae:ad:81:97:b3:8d:7b:95:95:18:d9:f2:9d:
                    7d:cb:71:27:b3:8e:62:1b:70:0f:03:2d:03:e5:9b:
                    9f:70:7e:db:3b:73:3a:c1:ea:d1:a7:0b:a9:1b:e0:
                    df:99:92:79:01:e1:db:22:9e:b2:3e:82:86:a7:8e:
                    8b:00:cf:0a:4f:be:81:4e:b4:a2:ef:b3:c4:4a:14:
                    6d:d2:28:ba:62:26:cf:13:3e:68:cd:96:3e:54:a5:
                    16:1d:6f:d4:a7:9b:7f:04:ac:b9:7b:8a:4e:73:5c:
                    a0:19:7d:0b:47:22:e0:2f:1a:88:68:c5:9d:84:b9:
                    1e:1e:45:58:15:bc:6a:cf:57:c4:a7:52:6f:92:70:
                    53:54:07:4b:35:4f:40:31:7b:86:fc:fa:95:b3:ce:
                    67:0a:ae:11:5f:e7:44:9a:6e:32:bf:89:63:26:88:
                    db:c4:50:bc:fa:67:cb:64:92:e3:9e:fa:f1:d3:b6:
                    f9:7f:1f:2c:16:15:24:7c:96:56:f6:b6:b6:bf:d1:
                    7a:88:5a:a4:03:3a:ca:91:ae:7e:1b:c4:84:20:96:
                    11:52:35:bb:10:eb:42:85:18:6d:7a:4c:3c:38:b7:
                    e6:04:97:5d:c3:ab:cf:ce:16:b1:7b:01:a5:92:6f:
                    f1:ee:82:8c:87:6b:a2:a4:dd:f6:bc:5b:3c:58:81:
                    0d:a8:1f:38:78:0e:d0:16:68:dd:c5:3a:5f:2b:f1:
                    b5:56:e4:9f:f5:ba:c5:08:66:89:55:53:8c:cd:c6:
                    09:d2:06:9d:33:17:98:12:fd:cf
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Key Usage: critical
                Certificate Sign
            X509v3 Basic Constraints: critical
                CA:TRUE, pathlen:0
            X509v3 Subject Key Identifier: 
                33:C5:DF:AA:92:BA:30:44:A0:04:96:72:81:D7:46:31:32:AC:30:D0
    Signature Algorithm: sha256WithRSAEncryption
         a9:f6:e7:50:46:e9:62:12:e3:97:c9:23:bd:5a:d6:50:eb:94:
         7f:7d:7a:3f:f9:c1:f2:37:3c:d0:e7:d8:1b:90:83:b6:77:ec:
         fa:a9:1c:5a:88:4a:8d:00:cc:0a:ff:8e:e6:5a:6e:ad:40:de:
         98:ba:73:b0:67:8a:37:93:ba:a0:c3:18:e0:37:6a:47:36:5c:
         a0:9e:7a:18:ca:ad:79:c9:ca:2d:3c:39:f6:38:a7:f4:0f:c8:
         86:25:a5:45:63:ce:66:d6:dc:8f:68:69:a5:bb:45:b8:85:4f:
         59:71:98:0a:c2:07:c9:88:6e:b5:26:3a:e4:2e:a7:94:e4:bf:
         5a:71:58:38:40:88:0d:7b:78:cc:f8:3f:a7:6c:dc:15:7d:55:
         e5:2d:42:22:a1:2d:d1:87:15:a8:58:99:26:20:58:7c:33:fd:
         74:c6:72:b0:57:fc:94:a5:36:64:5a:84:ba:44:ff:5f:00:f2:
         cb:b4:ac:79:34:5d:2e:78:0f:34:3b:ad:0d:12:5c:e3:d6:0e:
         0b:2a:61:43:21:72:47:2b:a3:2b:15:83:1a:eb:26:96:a1:05:
         36:83:75:7f:78:1d:b8:67:a0:e7:f5:29:c2:d1:1e:40:5e:5a:
         1c:92:4f:32:ae:c2:ca:43:8e:0b:16:5e:5f:28:5b:97:22:e5:
         c7:a3:a6:19:ca:cc:b7:31:8a:7d:0c:09:7f:09:18:6d:9c:21:
         34:b7:bf:65:ac:c3:d1:a2:aa:80:30:18:be:3e:fb:18:2d:de:
         cc:d3:61:e7:8b:26:b4:84:b6:74:c9:2f:4c:ca:b1:35:05:a3:
         87:54:11:4c:32:fb:ab:5e:20:45:8f:c2:52:3d:d6:45:08:43:
         23:f8:7b:29:85:5b:d5:4d:2f:94:ef:94:4f:b3:3d:6e:6b:7e:
         5d:4e:fc:8e:86:2f:fa:86:a4:ba:4a:71:f0:ac:3c:5e:b9:20:
         25:2b:43:f3:45:5d:86:d6:d6:25:b7:b5:d7:8b:2c:2e:2e:f5:
         76:eb:6c:ea:a4:83

If you look closely at the X509v3 extensions section of the output, you will notice two important things:

  • It is a CA certificate
  • pathlen 0 means it can not sign/create any new CA but only sign leaf certificates. Do man x509v3_config to learn more.

Setting up certificate for local development

❯ cd ~/code/mtls-example
❯ mkcert localhost 127.0.0.1 ::1

Created a new certificate valid for the following names 📜
 - "localhost"
 - "127.0.0.1"
 - "::1"

The certificate is at "./localhost+2.pem" and the key at "./localhost+2-key.pem" ✅

It will expire on 20 March 2024 🗓

Starting a nginx podman container with the certificate

Next we will start a podman nginx container to try to out the certificate. On my Fedora machine, I will also have take care of SELinux. First let us create a default.conf.

server {
  listen [::]:443 ssl http2 ipv6only=on;
  listen 443 ssl http2;
  server_name  localhost;
  ssl_protocols TLSv1.3;
  ssl_certificate /etc/nginx/conf.d/localhost+2.pem;
  ssl_certificate_key /etc/nginx/conf.d/localhost+2-key.pem;

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
  }
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}

Then, I will copy the rootCA.pem file in the current directory and start the container.

❯ cp ~/.local/share/mkcert/rootCA.pem .
❯ chcon -Rt svirt_sandbox_file_t .
❯ podman run --rm -p 8080:443 -v $PWD:/etc/nginx/conf.d/ nginx

and from another terminal I can verify the setup using curl.

❯ curl --tlsv1.3 https://localhost:8080
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

Same via Python httpx module (includes commands to create and activate the Python virtualenv).

❯ python3 -m venv .venv
❯ source .venv/bin/activate
❯ python3 -m pip install httpx
>>> import httpx
>>> r = httpx.get("https://localhost:8080/", verify="./rootCA.pem")
>>> r
<Response [200 OK]>

Now let us enable client side certificate and verification in nginx

We will modify the default.conf to the following.

server {
  listen [::]:443 ssl http2 ipv6only=on;
  listen 443 ssl http2;
  server_name  localhost;
  ssl_protocols TLSv1.3;
  ssl_certificate /etc/nginx/conf.d/localhost+2.pem;
  ssl_certificate_key /etc/nginx/conf.d/localhost+2-key.pem;
  ssl_client_certificate /etc/nginx/conf.d/rootCA.pem;
  ssl_verify_client on;
  ssl_verify_depth  3;

  location / {
    root   /usr/share/nginx/html;
    index  index.html index.htm;
  }
  error_page   500 502 503 504  /50x.html;
  location = /50x.html {
    root   /usr/share/nginx/html;
  }
}

and restart the podman container.

Now, let us try the same curl command and Python code.

❯ curl --tlsv1.3 https://localhost:8080
<html>
<head><title>400 No required SSL certificate was sent</title></head>
<body>
<center><h1>400 Bad Request</h1></center>
<center>No required SSL certificate was sent</center>
<hr><center>nginx/1.21.4</center>
</body>
</html>
>>> r = httpx.get("https://localhost:8080/", verify="./rootCA.pem")
>>> r
<Response [400 Bad Request]>

Creating a client side certificate and using the same

Here we are saying to use the name nehru in the client certificate. Note: I am running the commands in a different day, that is why the dates will not match with the CA certificate date :)

❯ mkcert -client nehru

Created a new certificate valid for the following names 📜
 - "nehru"

The certificate is at "./nehru-client.pem" and the key at "./nehru-client-key.pem" ✅

It will expire on 22 March 2024 🗓

If you use the openssl x509 -text -noout -in ./nehru-client.pem and check the details of the certificate, you will notice the following:

        X509v3 extensions:
            X509v3 Key Usage: critical
                Digital Signature, Key Encipherment
            X509v3 Extended Key Usage: 
                TLS Web Client Authentication, TLS Web Server Authentication

Next, we will use the same certificates in curl.

❯ curl --tlsv1.3 --key nehru-client-key.pem --cert nehru-client.pem https://localhost:8080

And then in Python.

>>> cert = ("./nehru-client.pem", "./nehru-client-key.pem")
>>> r = httpx.get("https://localhost:8080/", verify="./rootCA.pem", cert=cert)
>>> r
<Response [200 OK]>

I hope this will help you to start trying out mTLS on your local development environment. In future posts we will learn more in depth examples.