Kushal Das

FOSS and life. Kushal Das talks here.


Introducing Tugpgp

At Sunet, we have heavy OpenPGP usage. But, every time a new employee joins, it takes hours (and sometime days for some remote folks) to have their Yubikey + OpenPGP setup ready.

Final screen

Tugpgp is a small application built with these specific requirements for creating OpenPGP keys & uploading to Yubikeys as required in Sunet. The requirements are the following:

  • It will create RSA 4096 Key
  • There will be a primacy key with Signing & Certification capability.
  • There will be an encryption and one authentication subkey.
  • All keys have 1 year expiry date.
  • During the process the secret key will not be written to the disk.
  • Encryption & signing has touch policy fixed in the Yubikey (it can not be changed).
  • Authentication has touch policy on (means it can be turned off by the user).
  • The OTP application in the Yubikey will be disabled at the end.

We have an Apple Silicon dmg and AppImage (for Ubuntu 20.04 onwards) in the release page. This is my first ever AppImage build, the application still needs pcscd running on the host system. I tested it on Debian 11, Fedora 37 with Yubikey 4 & Yubikey 5.

Oh, there is also a specific command line argument if you really want to save the private key :) But, you will have to find it yourself :).

demo gif

If you are looking for the generic all purpose application which will allow everyone of us to deal with OpenPGP keys and Yubikeys, then you should check the upcoming release of Tumpa, we have a complete redesign done there (after proper user research done by professionals).

Johnnycanencrypt 0.13.0 released

I just now released v0.13.0 of my johnnycanencrypt project. It is a Python module written in Rust, which provides OpenPGP functionality including allows usage of Yubikey 4/5 as smartcards. From 0.12.0 it is now licensed as LGPL-3.0-or-later.

Major updates in this release (and in the previous one are):

  • Adds enable_otp_usb in rjce.
  • Adds disable_otp_usb in rjce.
  • Changed license to LGPL-3.0-or-later
  • We can now disable OTP for both YubiKey4/5 #131.

For many folks disabling the OTP application in the Yubikey is must, otherwise we all saw random bytes dropping on the shell/document/chat thanks to a touch to the key. I tested the code in both Yubikey 4 and 5. I hope this will work well in the field.

Startup/execution time for a specific command line tool

Generally I don’t have to bother about the startup time of any command line tool. For a human eye and normal day to day usage, if a command takes half a second to finish the job, it is not much of a problem. But, the story changes the moment we talk about a command which we have to run multiple times. What about about a command which have to execute multiple times every minute? This is the time when the startup and execution time matters.

A few weeks ago I was looking at a Python script which was executing as part of Nagios run, it was doing an API call to a remote server with JSON data coming in as command line arguments. Now, to make it scale more the first thought was to move the actual API call to a different process and get the original script to load things into a Redis queue. But, the other issue was the startup time for the Python script, having something with lesser startup time would be more help in this case, where nagios may execute the script/command over a few hundred times in every minute.

So, first I rewrote the code in Rust, and that made things multiple times faster. But, just for fun I wanted to see if writing it in golang will help or not. And I am kind of surprised to see the startup/execution time difference. I am using hyperfine for benchmark.

Python script

  Time (mean ± σ):     195.5 ms ±  11.3 ms    [User: 173.6 ms, System: 19.7 ms]
  Range (min … max):   184.5 ms … 228.8 ms    12 runs

Rust code

  Time (mean ± σ):      31.1 ms ±   8.4 ms    [User: 27.3 ms, System: 3.0 ms]
  Range (min … max):    24.6 ms …  79.0 ms    37 runs

View the code.

Golang code

  Time (mean ± σ):       3.2 ms ±   1.6 ms    [User: 1.0 ms, System: 1.7 ms]
  Range (min … max):     2.6 ms …  19.6 ms    140 runs

The code.

For now, we will go with the golang based code to do the work. But, if someone can explain the different ways Rust/Golang code starts up, that would be helpful to learn why such a speed difference.

Oh, here is the result of a quick poll on Fediverse about startup time.

40% people cares about startup time in cli


I received a PR for the Rust code from, which removes the async POST requests, and made the whole thing even simpler and faster (30x).

  Time (mean ± σ):       1.0 ms ±   0.6 ms    [User: 0.5 ms, System: 0.3 ms]
  Range (min … max):     0.5 ms …   4.3 ms    489 runs

Congratulations Anwesha

The year 2022 gave me one of the happiest moments in my life, and I also felt proud as Anwesha joined the Ansible community team as a software engineer in Red Hat. Proud because she became the best example of someone to whom I taught things about computers (she has multiple mentors/friends who helped her during this journey). Though sometimes that created trouble at home, the output is super lovely. From a Masters in Law to a software engineer in Red Hat is a good story.

Also, Red Hat has a special place in our home. I left Red Hat more than 5 years ago, but still, you will notice how Red Hat (the friends there and the culture) changed my life and later the life of our family. Oh, the other special thing is that Anwesha will have her own Red Fedora now :)

Johnnycanencrypt 0.11.0 released

A couple of days ago I released Johnnycanencrypt v0.11.0. It is a Python module for OpenPGP written in Rust.

The most interesting update is for Linux users, now we have pre-built wheels for Python 3.8, 3.9, 3.10 & 3.11. You can just install that via python3 -m pip install johnnycanencrypt. You can also do the same on Intel Macs (for Python 3.10 and 3.11). But, I failed to build for Apple Silicon systems. I will work on it in the coming weeks.

To know the Yubikey card version we can call get_card_version function written in Rust.

>>> rjce.get_card_version()
(4, 3, 1)

Or directly use the function get_card_touch_policies to get a list of policies for the current Yubikey.

>>> import johnnycanencrypt as jce
>>> jce.get_card_touch_policies()
[TouchMode.On, TouchMode.Off, TouchMode.Fixed]

Then we now can also set the touch policies for a given Yubikey. For example:

    b"12345678", rjce.KeySlot.Authentication, rjce.TouchMode.On

It will be nice to see what people will build on top this. I also missed to blog about the v0.10.0 release. You should that changelog too.

Introducing pyarti, python module for the Tor Project

python3 -m pip install pyarti

pyarti is a Python module written in Rust using Arti from the Tor Project. Right now pyarti is in the initial stage, and you can create a SOCKS5 proxy object, and pass any Python connection via it. The following example creates a default proxy at port 9150 , and then verifies that the connection works. Finally we fetch a web page and print the text.

from pyarti import OnionProxy
import httpx
from httpx_socks import SyncProxyTransport

URL = "https://httpbin.org/get"

p = OnionProxy()
assert p.verify(blocking=True)
# Now we can use the proxy
transport = SyncProxyTransport.from_url("socks5://")
with httpx.Client(transport=transport) as client:
    res = client.get(URL)
    assert res.status_code == 200

In the coming months I am hoping to add more detailed API to the module. For now you can read the documentation.

khata, under WASI

While I am learning about WebAssembly slowly, I was also trying to figure out where all I can use it. That is the way I general learn all new things. So, as a start, I thought of compiling my static blogging tool khata into WASM and then run it under WASI.

While trying to do that, the first thing I noticed that I can not just create a subprocess (I was doing it to call rsync internally), so that was top priority thing to fix in the list. First, I tried to use rusync crate, but then it failed at runtime as it is using threads internally. After asking around a bit more in various discord channels, I understood that the easiest way would be to just write that part of the code myself.

Which is now done, means this blog post is actually rendered using wasmtime.

wasmtime --dir=. khata.wasm

I am very slowly learning about the SPEC, and the limitations. But, this is very interesting and exciting learning journey so far.

Johnnycanencrypt 0.9.0 release

3 days ago I released Johnnycanencrypt 0.9.0. Here is the changelog:

- Adds `setuptools-rust` as build system.
- Key.uids now contains the certification details of each user id.
- `merge_keys` in rjce now takes a force boolean argument.
- `certify_key` can sign/certify another key by both card and on disk primary key.

The first biggest change is related to build system, now we are using setuptools-rust to build. This change happened as dkg is working towards packaging the module for Debian.

The other big change is about certifying someone's key. We can use the primary key (either on disk or on Yubikey) to do the signing.

k = ks.certify_key(
    ["Kushal Das <kushaldas@gmail.com>", "Kushal Das <kushal@fedoraproject.org>"],

In the above example I am signing two user ids of the key k using my_key with a PositiveCertification.

johnnycanencrypt 0.7.0 released

Today I released Johnnycanencrypt 0.7.0. It has breaking change of some function names.

  • create_newkey renamed to create_key
  • import_cert renamed to import_key

But, the major work done are in few different places:

  • Handling errors better, no more normal Rust panics, instead providing better Python exceptions as CryptoError.
  • We can now sign bytes/files in both detached & in normal compressed binary form.
  • Signature can be done via smartcards, and verification works as usual.

In the Github release page you can find an OpenPGP signature, which you can use to verify the release. You can also verify via sigstore.

SIGSTORE_LOGLEVEL=debug python -m sigstore verify --cert-email mail@kushaldas.in --cert-oidc-issuer https://github.com/login/oauth johnnycanencrypt-0.7.0.tar.gz
DEBUG:sigstore._cli:parsed arguments Namespace(subcommand='verify', certificate=None, signature=None, cert_email='mail@kushaldas.in', cert_oidc_issuer='https://github.com/login/oauth', rekor_url='https://rekor.sigstore.dev', staging=False, files=[PosixPath('johnnycanencrypt-0.7.0.tar.gz')])
DEBUG:sigstore._cli:Using certificate from: johnnycanencrypt-0.7.0.tar.gz.crt
DEBUG:sigstore._cli:Using signature from: johnnycanencrypt-0.7.0.tar.gz.sig
DEBUG:sigstore._cli:Verifying contents from: johnnycanencrypt-0.7.0.tar.gz
DEBUG:sigstore._verify:Successfully verified signing certificate validity...
DEBUG:sigstore._verify:Successfully verified signature...
DEBUG:urllib3.connectionpool:Starting new HTTPS connection (1): rekor.sigstore.dev:443
DEBUG:urllib3.connectionpool:https://rekor.sigstore.dev:443 "POST /api/v1/index/retrieve/ HTTP/1.1" 200 85
DEBUG:urllib3.connectionpool:https://rekor.sigstore.dev:443 "GET /api/v1/log/entries/362f8ecba72f4326972bc321d658ba3c9197b29bb8015967e755a97e1fa4758c13222bc07f26d27c HTTP/1.1" 200 None
DEBUG:sigstore._verify:Successfully verified Rekor entry...
OK: johnnycanencrypt-0.7.0.tar.gz

I took 8 months for this release, now time to write some tools to use it in more places :)