Encrypting your electronic communication is a must-have in the 21st century. Since E-mail is still around, messages over SMTP should not be excluded from this rule. In this blog post I demonstrate how WebAssembly and the Go implementation of age encryption can work together to provide alternative encryption in Thunderbird. Please note, that in the current state of development the addon is decrypt-only with one private key.
Das Projekt wurde unter dem Titel Moderne Verschlüsselung in Web und Mail mit WebAssembly bei den Chemnitzer Linux-Tagen 2022 vorgestellt.
WebAssembly? Go in Thunderbird?
Go has a native implementation to compile to WebAssembly. A good starting point is the golangbot.com "Introduction to WebAssembly using Go" article. It covers compilation, glue code and running the program inside the browser.
Now, why do we talk about browsers if the goal of this project is to run it in Thunderbird? That's because Mozilla, the developers of Thunderbird, switched the underlying engine in Thunderbird 78 in 2020. All that you see in the GUI is one big rendered web UI, crafted for mailing. This move also killed most of the previously developed addons, which were based on the XUL interface. Moving to the new platform, a new addon API was enforced: MailExtensions. These are the same specs as WebExtensions use in Firefox.
Current state of encryption in Thunderbird
One of the popular and useful security addons was Enigmail for OpenPGP. Mozilla decided to integrate OpenPGP into Thunderbird. Sadly they did not just port Enigmail to MailExtensions and built from the existing source code - they rebuilt the functionality from the ground up. It's still missing crucial features and does not use the system-wide PGP key store (keys you have managed through gnupg are not re-used inside Thunderbird).
Another well known technology stack is S/MIME. S/MIME uses public key infrastructure to provide users with certificates. Senders of messages then use the public cert of their intended receivers to encrypt mail, receivers decrypt with their secret key.
At the end of 2019, Filippo Valsorda started the age encryption project, standing for "actually good encryption" or Italian "aghe". The Go code lives on github.com/FiloSottile/age, the spec at Google docs. The keys are X25519 elliptic curve key pairs wrapped inside the age key format.
The tooling has many features (see man page), but let's focus on what is needed in this scope:
- Creating keys with
age-keygen -o key.txt
- Encrypting a file with
age -o example.jpg.age -r age1ql3z7hjy54... example.jpg, where
-ois the output file and
-rthe receipient's X25519 public key
- Decrypting a file with
age -d -o data.tar.gz -i key.txt data.tar.gz.age, where
-dis decryption mode and
-ithe secret key file
Compiling age to wasm
We need two things to build age for our WebExtension: a Go compiler and a "wrapper" Go program.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
Compile the program with this script:
1 2 3 4 5 6 7 8 9 10 11
The resulting wasm binary file is then located in
TinyGo could also be used to compile the program. Using it in the addon sadly did not work because it was missing some APIs for age. If you have more success, let me know!
Bundling WebAssembly into our MailExtension
The WebAssembly file is loaded by the background.js addon script. In the manifest.json, create an entry for background scripts:
1 2 3 4 5 6 7
The wasm_exec.js file is the "official" wasm Go glue script from the standard installation: wasm_exec.js or via
cp "$(go env GOROOT)/misc/wasm/wasm_exec.js" ./. Create a folder called "wasm" beside your manifest and put acus.wasm inside it. The background script then picks it up and loads it with the following code:
1 2 3 4 5 6 7 8 9 10 11
As soon as "initiate" is finished, the background script can use the
Running the code from inside Thunderbird
You should know how to create a WebExtension (or MailExtension in this case). If you do not, head over to developer.thunderbird.net.
For development, Thunderbird provides a "load temporary addons" function. You can find it on the addon page or via Extras -> Developer Tools -> Debug addons. Loading the addon enables its functionality in Thunderbird and allows to enter the developer console from the addons debug page.
Integrating decryption in the workflow
So finally, as the first fully working feature, automatic decryption of sent email content will be our goal. We already have the background script. To successfully decrypt ciphertext inside our mail, an additional foreground script is needed. It will run inside the mail view window when you open an email and can access the DOM of the message. It cannot run wasm files itself, that's why messaging is used to exchange data with the background script.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
The manifest must request permissions for messagesRead and messagesModify to allow loading a messageDisplayScript inside the viewer window. The foreground script should be saved as e.g. "displayscript.js" and must be activated from the background script:
1 2 3 4 5 6
Also add the message handler for decryption to the background script:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
To set up your own secret key to be able to decrypt messages, you can for example use an input form on the options page. The current implementation does this.
If everything works, try to open a plain text email with an age ciphertext text block. The text will automatically be parsed, decrypted and the ciphertext replaced with clear text 🎉
What about Firefox?
It is also important to acknowledge that this technique also works in Firefox, as Thunderbird more or less inherited the WebExtension API from the Firefox browser. Nevertheless, the funtionality of encryption (and probably decryption) inside the web browser follows another workflow. We will explore this use case later.