Automatically encrypt emails using WKD and Postfix
Posted on January 16, 2022 • 3 minutes • 573 words
Table of contents
Exchanging public PGP keys has always been hard to get right. Can you really be sure that the key you are fetching from a public keyserver over HKP belongs to the person you are trying to contact?
A relatively new way of distributing public PGP keys, called the Web Key Directory (WKD), makes it easy to obtain a current public key for a given email address over HTTPS. The public keys are published under the .well-known directory on the domain name of the recipient (e.g., gauthier.dk/.well-known/openpgpkey/hu/[…]) for william@gauthier.dk). Because WKD leverages the HTTPS protocol and fetches the public key directly from the recipient’s domain name, we can be reasonably certain that the key hasn’t been tampered with and does indeed belong to the recipient.
Protonmail has implemented external key discovery via WKD, ensuring that all outgoing emails to supported recipients are encrypted. To achieve a similar setup on my own email server, I wrote a small Python script that encrypts all outgoing emails to recipients who have published their public key using WKD. This guide provides step-by-step instructions on configuring this feature on your own email server:
1. Create a dedicated system user and setup the Python script
First, we will create an unprivileged system account that the script will run under:
adduser --system wkd-user
Next, download the script, place it under the system user’s home folder, and make it executable:
wget https://raw.githubusercontent.com/wjgauthier/postfix-wkd/main/postfix-wkd.py -O /home/wkd-user/postfix-wkd.py
chmod +x /home/wkd-user/postfix-wkd.py
Although this is the default for gpg, I still like to explicitly state that we only want to fetch keys over WKD (unless it already exists locally). Set this in /home/wkd-user/.gnupg/gpg.conf:
auto-key-locate local,wkd
Install the required dependency:
apt install python3-gpg
2. Configure Postfix Milter
Now, set up a content filter using the Postfix pipe delivery agent, and then we configure an after-filter on localhost:10026, where emails will be sent back after processing by the script.
In /etc/postfix/master.cf, make the following additions/changes:
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (no) (never) (100)
# ==========================================================================
+ localhost:10026 inet n - n - 10 smtpd
+ -o content_filter=
+ -o receive_override_options=no_unknown_recipient_checks,no_header_body_checks,no_milters
+ -o smtpd_helo_restrictions=
+ -o smtpd_client_restrictions=
+ -o smtpd_sender_restrictions=
+ # Postfix 2.10 and later: specify empty smtpd_relay_restrictions.
+ -o smtpd_relay_restrictions=
+ -o smtpd_recipient_restrictions=permit_mynetworks,reject
+ -o mynetworks=127.0.0.0/8
+ -o smtpd_authorized_xforward_hosts=127.0.0.0/8
smtp inet n - y - - smtpd
+ -o content_filter=filter:dummy
[...]
+filter unix - n n - 10 pipe
+ flags=Rq user=wkd-user null_sender=
+ argv=/home/wkd-user/postfix-wkd.py -f ${sender} -- ${recipient}
Then, reload Postfix:
systemctl reload postfix
That’s it. Every time an email is sent, Postfix will attempt to lookup the recipient’s public key using WKD. A good way to test that everything is working is to send an email to a protonmail.com address that you own, as all public keys of their users are published in WKD. You should see a green padlock in the webmail interface.
I believe the decentralized trust model, where users bind a public key to its owner using the web of trust, is a lost cause. WKD can make the encryption process almost completely invisible to the end user, which is probably the only way we will see widespread usage of PGP, if ever.
I would love to hear from you if you got the script working on your own email server or if you have any suggestions or questions. Feel free to reach out. My public key is published in WKD ;-)