Kushal Das

FOSS and life. Kushal Das talks here.

kushal76uaid62oup5774umh654scnu5dwzh4u2534qxhcbi4wbab3ad.onion

Using Python to access a Solid Pod

solid logo

From the project website:

Solid is a specification that lets people store their data securely in decentralized data stores called Pods. Pods are like secure personal web servers for your data.

We can host these Pods in personal servers or at any provider. Everything is tied up based on the user identity, called WebID. It is an HTTP URI described as RDF document.

You can decide who/what can access your data. Applications/humans can use Solid authentication and identify to the Pod server to access the data using open protocols.

How to get a Pod?

The website lists current vendors who provide Pod services. If you want to play around locally, you can run community server on your local system. Or just create an account at solidcommunity.net, and use that to learn more.

Using Python with Solid

We already have a solid-file Python module. You can install it via pip in a virtual environment.

$ python3 -m venv .venv
$ source .venv/bin/activate
$ python3 -m pip install solid-file

For the rest of the example code, I am going to use my Pod at the solidcommunity.net, feel free to replace the username/password and URL values in the code as required.

USERNAME = "kushaldas"
PASSWORD = "******************************************"

IDP = 'https://solidcommunity.net'
POD_ENDPOINT = "https://kushaldas.solidcommunity.net/public/"

from solid.auth import Auth
from solid.solid_api import SolidAPI

auth = Auth()
api = SolidAPI(auth)
auth.login(IDP, USERNAME, PASSWORD)

Here we are importing the module, creating an Auth object and identify using username and password.

Then we will check if a folder exist or not (it does not exist yet), and create the folder in this case.

folder_url = f"{POD_ENDPOINT}/languages/"
if not api.item_exists(folder_url):
    print(api.create_folder(folder_url))

The output is <Response [201 Created]>.

Next, we create two text files.

data = io.BytesIO("I ❤️ 🦀".encode("utf-8"))
file_url = f"{folder_url}hello.txt"
print(api.put_file(file_url, data, 'text/plain'))
data = io.BytesIO(b"Already 10 years of SOPA blackout")
msg_url = f"{folder_url}message.txt"
print(api.put_file(msg_url, data, 'text/plain'))

We can then list all of the items under our subfolder.

folder_data = api.read_folder(folder_url)
files = "\n".join(list(map(lambda x: x.name, folder_data.files)))
print(f'Files in the folder: \n{files}')

We can then try to read one of the files we just now created.

resp = api.get(file_url)
print(f"**{resp.text}**")

Output:

Files in the folder: 
hello.txt
message.txt

Why am I looking at Solid?

Solid as the specification is evolving along with the community. One usecase for any government organization would be if the citizens can control the access to their own data, and people can authorize who gets to access their data. Solid can become answer to that question. The specification is loose enough to allow building things easily on top of it.

I would love to see Python as a major part of this ecosystem. The solid-file project maintainers are doing a great job. But, we need more related projects, including proper examples of various usecases. Maybe a server too.

Trouble with signing and notarization on macOS for Tumpa

This week I released the first version of Tumpa on Mac. Though the actual changes required for building the Mac app and dmg file were small, but I had to reap apart those few remaining hairs on my head to get it working on any other Mac (than the building box). It was the classic case of Works on my laptop.

The issue

Tumpa is a Python application which uses PySide2 and also Johnnycanencrypt which is written in Rust.

I tried both briefcase tool and manual calling to codesign and create-dmg tools to create the tumpa.app and the tumpa-0.1.3.dmg.

After creating the dmg file, I had to submit it for notarisation to Apple, following:

xcrun /Applications/Xcode.app/Contents/Developer/usr/bin/altool --notarize-app --primary-bundle-id "in.kushaldas.Tumpa" -u "kushaldas@gmail.com" -p "@keychain:MYNOTARIZATION" -f macOS/tumpa-0.1.3.dmg

This worked successfully, after a few minutes I can see that the job passed. So, I can then staple the ticket on the dmg file.

xcrun stapler staple macOS/tumpa-0.1.3.dmg

I can install from the file, and run the application, sounds great.

But, whenever someone else tried to run the application after installing from dmg, it showed the following.

mac failure screenshot

Solution

It took me over 4 hours to keep trying all possible combinations, and finally I had to pass --options=runtime,library to the codesign tool, and that did the trick. Not being able to figure out how to get more logs on Mac was making my life difficult.

I had to patch briefcase to make sure I can keep using it (also created the upstream issue).

--- .venv/lib/python3.9/site-packages/briefcase/platforms/macOS/__init__.py	2022-01-07 08:48:12.000000000 +0100
+++ /tmp/__init__.py	2022-01-07 08:47:54.000000000 +0100
@@ -117,7 +117,7 @@
                     '--deep', str(path),
                     '--force',
                     '--timestamp',
-                    '--options', 'runtime',
+                    '--options', 'runtime,library',
                 ],
                 check=True,
             )

You can see my build script, which is based on input from Micah.

I want to thank all of my new friends inside of SUNET who were excellent helping hands to test the multiple builds of Tumpa. Later many folks from IRC also jumped in to help to test the tool.

Releasing Tumpa for Mac

I am happy to announce the release of Tumpa (The Usability Minded PGP Application) for Mac. This release contains the old UI (and the UI bugs), but creates RSA4096 keys by default. Right now Tumpa will allow the following:

  • Create new RSA4096 OpenPGP key. Remember to click on the “Authentication” subkey checkbox if you want to use the key for ssh.
  • Export the public key.
  • You can reset the Yubikey from the smartcard menu.
  • Allows to upload the subkeys to Yubikey (4 or 5).
  • Change the user pin/admin pin of the Yubikey.
  • Change the name and public key URL of the Yubikey.

The keys are stored at ~/.tumpa/ directory, you can back it up in an encrypted USB drive.

You can download the dmg file from my website.

$ wget https://kushaldas.in/tumpa-0.1.3.dmg
$ sha256sum ./tumpa-0.1.3.dmg 
6204cf3253fbe41ada91429684fccc0df87257f85345976d9468c8adf131c591  ./tumpa-0.1.3.dmg

Download & install from the dmg in the standard drag & drop style. If you are using one of the new M1 box, remember to click on “Open in Rosetta” for the application.

Tumpa opening on Mac

Click on “Open”.

Here is a GIF recorded on Linux, the functions are same in Mac.

Tumpa gif

Saptak (my amazing comaintainer) is working on a new website. He is also leading the development of the future UI, based on usability reports. We already saw a few UI issues on Mac (specially while generating a new key), those will be fixed in a future release.

Feel free to open issues as you find, find us in #tumpa channel on Libera.chat IRC network.

2021 blog review

Last year I wrote only a few blog posts, 19 exactly. That also reduced the views, to around 370k from 700k year before (iirc).

The post about Getting TLS certificates for Onion services was the highest read post in this year, 9506 views.

A major part of the year went to thinking if we can survive the year, India's medical system broke down completely (the doctors and staff did some amazing job using whatever was available). Everyone I know lost someone in COVID, including in our family. All 3 of us were down in COVID from the end of April & the recovery was long. For a few days in between I could not remember any name.

After the COVID issues came down in brain (that is after getting the vaccines), next we were also waiting for our move to Sweden.

At the beginning of 2022, things look a bit settled for us. In the last few weeks of 2021, I managed to start writing again. I am hoping to continue this. You can also read about the 2018 or 2017, 2016 reviews.

Johnnycanencrypt 0.6.0 released

A few days ago I released 0.6.0 of Johnnycanencrypt. It is a Python module written in Rust for OpenPGP using the amazing sequoia-pgp library. It allows you to access/use Yubikeys (without gpg-agent) directly from your code.

This release took almost an year. Though most of the work was done before, but I was not in a state to do a release.

Major changes

  • We can now sign and decrypt using both Curve25519 and RSA keys on the smartcard (we support only Yubikeys)
  • Changing of the password of the secret keys
  • Updating expiry date of the subkeys
  • Adding new user ID(s)
  • Revoking user ID(s)

I also released a new version of Tumpa which uses this. An updated package for Debian 11.

Using your OpenPGP key on Yubikey for ssh

Last week I wrote about how you can generate ssh keys on your Yubikeys and use them. There is another way of keeping your ssh keys secure, that is using your already existing OpenPGP key (along with authentication subkey) on a Yubikey and use it for ssh.

In this post I am not going to explain the steps on how to move your key to a Yubikey, but only the steps required to start using it for ssh access. Feel free to have a look at Tumpa if you want an easy way to upload keys to your card.

Enabling gpg-agent for ssh

First we have to add gpg-agent.conf file with correct configuration. Remember to use a different pinentry program if you are on Mac or KDE.

❯ echo "enable-ssh-support" >> ~/.gnupg/gpg-agent.conf
❯ echo "pinentry-program $(which pinentry-gnome)" >> ~/.gnupg/gpg-agent.conf
❯ echo "export SSH_AUTH_SOCK=$(gpgconf --list-dirs agent-ssh-socket)" >> ~/.bash_profile
❯ source ~/.bash_profile 
❯ gpg --export-ssh-key <KEYID> > ~/.ssh/id_rsa_yubikey.pub

At this moment your public key (for ssh usage) is at ~/.ssh/id_rsa_yubikey.pub file. You can use it in the ~/.ssh/authorized_keys file on the servers as required.

We can then restart the gpg-agent using the following command and then also verify that the card is attached and gpg-agent can find it.

❯ gpgconf --kill gpg-agent
❯ gpg --card-status

Enabling touch policy on the card

We should also enable touch policy on the card for authentication operation. This means every time you will try to ssh using the Yubikey, you will have to touch the interface (it will be flashing the light till you touch it).

❯ ykman openpgp keys set-touch aut On
Enter Admin PIN: 
Set touch policy of authentication key to on? [y/N]: y

If you still have servers where you have only the old key, ssh client will be smart enough to ask you the passphrase for those keys.

Using onion services over unix sockets and nginx

I have explained before about how to create Onion services, this provides an easy solution to expose any service from inside of your home network to the Internet, in a secured manner (authorized services). But, in all of those examples I used an IP/port combination to expose/talk to the internal service. Instead you can also use unix sockets to do the same.

To do so, use the following style in your torrc file, this example is from my blog.

HiddenServiceDir /var/lib/tor/hidden/
HiddenServiceVersion 3
HiddenServicePort 80 unix:/var/run/tor-hs-kushal.sock
HiddenServicePort 443 unix:/var/run/tor-hs-kushal-https.sock

And the corresponding nginx configuration parts:

server {
    listen unix:/var/run/tor-hs-kushal.sock;

    server_name kushal76uaid62oup5774umh654scnu5dwzh4u2534qxhcbi4wbab3ad.onion;
    access_log /var/log/nginx/kushal_onion-access.log;

    location / {
        return 301 https://$host$request_uri;
    }

}

server {
    listen unix:/var/run/tor-hs-kushal-https.sock ssl http2;

    server_name kushal76uaid62oup5774umh654scnu5dwzh4u2534qxhcbi4wbab3ad.onion;
    access_log /var/log/nginx/kushal_onion-access.log;
    ...

Now if you start tor and also nginx pointing to the same unix domain, things will go fine. But, nginx will fail to restart, you will have to remove the socket files by hand to restart. This happens due to a bug in nginx. You can edit the restart process and fix this issue.

systemctl restart nginx

Add the following the configuration file in the correct location (between the comments):

### Editing /etc/systemd/system/nginx.service.d/override.conf
### Anything between here and the comment below will become the new contents of the file

### Editing /etc/systemd/system/nginx.service.d/override.conf Anything between here and the comment below will become the new contents of the file
[Service]
ExecStop=
ExecStop=-/sbin/start-stop-daemon --quiet --stop --retry TERM/5 --pidfile /run/nginx.pid

### Lines below this comment will be discarded

If you go and read the original ExecStop value, you will find that it is using SIGQUIT, but that does not remove the socket files, only a SIGTERM does. You can read more in the []upstream bug](https://trac.nginx.org/nginx/ticket/753).

After this nginx should be able to restart without any trouble.

Thank you reader who emailed and asked for this.

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.

ssh authentication using FIDO/U2F hardware authenticators

From OpenSSH 8.2 release it supports authentication using FIDO/U2F. These tokens are required to implement the ECDSA-P256 "ecdsa-sk" key type, but some (say Yubikey) also supports Ed25519 (ed25519-sk) keys. In this example I am using a Yubikey 5.

I am going to generate a non-discoverable key on the card itself. Means along with the card, we will also have a key on disk, and one will need both to authenticate. If someone steals you Yubikey, they will not be able to login just via that.

✦ ❯ ssh-keygen -t ed25519-sk -f .ssh/id_ed25519_sk
Generating public/private ed25519-sk key pair.
You may need to touch your authenticator to authorize key generation.
Enter passphrase (empty for no passphrase): 
Enter same passphrase again: 
Your identification has been saved in .ssh/id_ed25519_sk
Your public key has been saved in .ssh/id_ed25519_sk.pub
The key fingerprint is:
SHA256:CoQKA0blJ8A1xOwri167mIDb7rHxr59TYwI25ChOZ4Y kdas@localhost.localdomain
The key's randomart image is:
+[ED25519-SK 256]-+
|++*=             |
|o.o+o            |
|o +*..           |
|oE.*B            |
|+.+.oo  S        |
|.o . ...+        |
|+ =.  .+ .       |
|o++=. ..         |
|o*=o+++.         |
+----[SHA256]-----+

Here we passed the type of the key using -t flag and saving the private key using -f. I pasted the public key in the server's ~/.ssh/authorized_keys file, and then also configured the ssh client on my laptop to use that specified key via the ~/.ssh/config file.

Host kushaldas.in
  HostName kushaldas.in
  User kushal
  IdentityFile ~/.ssh/id_ed25519_sk

Finally we can login via ssh.

✦ ❯ ssh kushaldas.in
Enter passphrase for key '/home/kdas/.ssh/id_ed25519_sk': 
Confirm user presence for key ED25519-SK SHA256:CoQKA0blJ8A1xOwri167mIDb7rHxr59TYwI25ChOZ4Y
User presence confirmed
$

You will notice that after asking for the passphrase of the key, ssh is asking to touch the Yubikey to confirm the user presence. You can read more in the tutorial from Yubico.

If you miss to touch the Yubikey on time, you will get an error like:

sign_and_send_pubkey: signing failed for ED25519-SK "/home/kdas/.ssh/id_ed25519_sk": invalid format