Joar von Arndt: Automatically Find PGP Keys in Mu4e

Wait 5 sec.

I have found a peculiar delight in encryption, and particularly inusing pgp-based asymmetric cryptography. This is especially useful forsecuring email communications, but doing so requires remembering torun M-x mml-secure-sign or mml-secure-encrypt in Mail Utilities ForEmacs (Mu4e) before sending the message. For this reason I have beenwanting to write a quick method that automatically adds signingcapabilities and encryption only the recipient has a publickey. Thankfully, I did not have to do so because of NicolasCavigneaux’s recent post Sign Always, Encrypt When Possible where heshowcases his functins for doing exactly that. It prompted me to tweakit slightly, and to add the functionality to automatically discoverpublic keys that I have yet to import.Cavigneaux’s post sets up an elegant arrangement: email should alwaysbe signed with your pgp key, and you should “upgrade” from there toencryption only if your correspondent can handle encrypted mail andhas shared their public key with you. This is done by using Emacs’built-in Easy Privacy Guard (epg) tooling to compare the owners of theimported public keys on the machine with the list of recipients in anemail.1 If you have the public key for all recipients, the messagewill be automatically encrypted — otherwise it will merely be signedto prove that you are the author.This was exactly what I had wanted, and I quickly incorporated thisfunctionality into my own configuration. Testing it out, I felt a bituneasy sending encrypted mail automatically. Because it is not thedefault, I felt that fully automated encryption made me unsure whetherthe mail has been encrypted or not — even when I know I have a publickey. I thus made the slight modification of prompting for bothencryption and signing:(defun kudu/message-sign-encrypt-if-all-keys-available () "Add MML tag to encrypt message when there is a key for each recipient,sign it otherwise." (if (bounga/message-all-epg-keys-available-p) (if (y-or-n-p "Encrypt? ") (mml-secure-message-sign-encrypt) (when (y-or-n-p "Sign? ") (mml-secure-message-sign))) (when (y-or-n-p "Sign? ") (mml-secure-message-sign))))In practice I will probably always be pressing Y when prompted, butthis adds a level of certainty that the code is running correctly. Itreminds me of an advertising gimmick for housewives in the 1950s: whencake mix first became available, it was not appreciated because it wasperceived as being “too easy” — it did not feel like baking a cakeyourself. And so the recipe was tweaked to simply require an extra eggto be added. In practice this was a trivial change, but it meant thatyou felt more responsible for the finished work.But there is an additional step I want to automate that Cavigneauxalluded to, but did not implement. He writes:[T]he policy is only as good as my keyring. Encryption depends onhaving imported the right public keys; nothing here fetches them forme.I have recently been having more correspondence with people using theSwiss-based email provider Protonmail. Proton provides support for theWeb Key Directory (wkd) method of providing keys, where the dnssettings of a domain point to a file containing a public key for aspecific user. This allows the email itself to “provide” its ownpublic key for encryption. The public key of any @protonmail.com or@pm.me address can be quickly imported in a second using gpg--locate-external-keys email@pm.me. Proton will similarly use wkd toget your public key, and so you will receive encrypted mail as well.This is still easy to set up for any other email provider, as long asyou can edit the domain settings.2Sadly, epg does not come with any options for Gnupg’s --locate-keys or--locate-external-keys flags. This might be a good addition. In themeantime, we can write a quick and dirty function to do the job for usin the background when opening any new email:(defun kudu/message-locate-keys () "Tries to find the public keys of 'bounga/message-recipients' via WKD through --locate-keys." (dolist (recipient (string-to-list (bounga/message-recipients))) (let ((recipient-email (cadr recipient)) (proc (make-process :name "gpg-locate-keys" :command (list epg-gpg-program "--no-tty" "--locate-keys" (cadr recipient)) :connection-type 'pipe :filter (lambda (proc string) (process-put proc 'output (concat (or (process-get proc 'output) "") string))) :sentinel (lambda (proc event) (when (eq (process-status proc) 'exit) (let ((output (process-get proc 'output)) (email (process-get proc 'email))) (if (and output (string-match-p "imported: [1-9]" output)) (message "Public key imported for %s" email)))))))) (process-put proc 'email recipient-email))))We use --locate-keys because the stricter --locate-external-keys willcheck the domain even if a key already exists in the localkeyring. --locate-keys will instead instantly find local keys andfinish early, while only missing keys will be checked. This is doneasynchronously through make-process so that it does not cause the uito freeze as we wait for network requests to finish. gpg seems tocheck very quickly, so it is not a large performance hit regardless.We want to have imported any new keys before sending our email becauseCavigneaux’s functions are run on 'message-send-hook. We therefore runkudu/message-locate-keys much earlier:(add-hook 'mu4e-view-rendered-hook 'kudu/message-locate-keys)This adds any correspondents’ public keys on any opened email, and sodoes not have to go through your entire contacts list. A downside isthat it does not check for a public key when you are the personsending an unsolicited email, but it will when you get their reply.This is not the world’s most elegant function, and if someone morefamiliar with epg has any comments regarding ways to improve it I willhappily edit this post for improvements. Just send me a (pgpencrypted!) email. Run echo 'am9hcnhwYWJsb0B2b25hcm5kdC5zZQ' | base64--decode | xargs gpg --locate-external-keys to import my key. If youhave wkd set up, I will automatically fetch your public key. If not,this is a great opportunity to fix that!I will also note how wonderful Emacs and epg make working withencrypted files, messages, and/or text more generally. I must admitthat I used a plain-text .authinfo for an embarrassingly long time tostore some of my secrets, thinking it would be bothersome to use theencrypted .authinfo.gpg for auth-sources. If you are like me, do notworry — the peace of mind from being able to write down personalinformation and simply leave it scattered on your filesystem is wellworth just having to type your password.Footnotes: 1 From Cavigneaux’s post, the function to get all message recipients. Returns a list formatted like (("First recipient" "first@domain.tld") ("Second recpient" "second@domain.tld") ...):(defun bounga/message-recipients () "Return a list of all recipients in the message, looking at TO, CC and BCC.Each recipient is in the format of `mail-extract-address-components'." (mapcan (lambda (header) (let ((header-value (message-fetch-field header))) (and header-value (mail-extract-address-components header-value t)))) '("To" "Cc" "Bcc")))2 So no @gmail.com, @outlook.com, or @yahoo.com addresses. You canstill use these to service your mail; wkd does not interact with anyemail-related dns records.