Automatically Find PGP Keys in Mu4e
I have found a peculiar delight in encryption, and particularly in
using pgp-based asymmetric cryptography. This is especially useful for
securing email communications, but doing so requires remembering to
run M-x mml-secure-sign or mml-secure-encrypt in Mail Utilities For
Emacs (Mu4e) before sending the message. For this reason I have been
wanting to write a quick method that automatically adds signing
capabilities and encryption only the recipient has a public
key. Thankfully, I did not have to do so because of Nicolas
Cavigneaux’s recent post Sign Always, Encrypt When Possible where he
showcases his functins for doing exactly that. It prompted me to tweak
it slightly, and to add the functionality to automatically discover
public keys that I have yet to import.
Cavigneaux’s post sets up an elegant arrangement: email should always be signed with your pgp key, and you should “upgrade” from there to encryption only if your correspondent can handle encrypted mail and has shared their public key with you. This is done by using Emacs’ built-in Easy Privacy Guard (epg) tooling to compare the owners of the imported public keys on the machine with the list of recipients in an email.1 If you have the public key for all recipients, the message will be automatically encrypted — otherwise it will merely be signed to prove that you are the author.
This was exactly what I had wanted, and I quickly incorporated this functionality into my own configuration. Testing it out, I felt a bit uneasy sending encrypted mail automatically. Because it is not the default, I felt that fully automated encryption made me unsure whether the mail has been encrypted or not — even when I know I have a public key. I thus made the slight modification of prompting for both encryption 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, but
this adds a level of certainty that the code is running correctly. It
reminds me of an advertising gimmick for housewives in the 1950s: when
cake mix first became available, it was not appreciated because it was
perceived as being “too easy” — it did not feel like baking a cake
yourself. And so the recipe was tweaked to simply require an extra egg
to be added. In practice this was a trivial change, but it meant that
you felt more responsible for the finished work.
But there is an additional step I want to automate that Cavigneaux alluded to, but did not implement. He writes:
[T]he policy is only as good as my keyring. Encryption depends on having imported the right public keys; nothing here fetches them for me.
I have recently been having more correspondence with people using the
Swiss-based email provider Protonmail. Proton provides support for the
Web Key Directory (wkd) method of providing keys, where the dns
settings of a domain point to a file containing a public key for a
specific user. This allows the email itself to “provide” its own
public 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 to
get 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 as
you can edit the domain settings.2
Sadly, epg does not come with any options for Gnupg’s --locate-keys or
--locate-external-keys flags. This might be a good addition. In the
meantime, we can write a quick and dirty function to do the job for us
in 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 will
check the domain even if a key already exists in the local
keyring. --locate-keys will instead instantly find local keys and
finish early, while only missing keys will be checked. This is done
asynchronously through make-process so that it does not cause the ui
to freeze as we wait for network requests to finish. gpg seems to
check very quickly, so it is not a large performance hit regardless.
We want to have imported any new keys before sending our email because
Cavigneaux’s functions are run on 'message-send-hook. We therefore run
kudu/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 so does not have to go through your entire contacts list. A downside is that it does not check for a public key when you are the person sending an unsolicited email, but it will when you get their reply.
This is not the world’s most elegant function, and if someone more
familiar with epg has any comments regarding ways to improve it I will
happily edit this post for improvements. Just send me a (pgp
encrypted!) email. Run echo 'am9hcnhwYWJsb0B2b25hcm5kdC5zZQ' | base64
--decode | xargs gpg --locate-external-keys to import my key. If you
have 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 with
encrypted files, messages, and/or text more generally. I must admit
that I used a plain-text .authinfo for an embarrassingly long time to
store some of my secrets, thinking it would be bothersome to use the
encrypted .authinfo.gpg for auth-sources. If you are like me, do not
worry — the peace of mind from being able to write down personal
information and simply leave it scattered on your filesystem is well
worth just having to type your password.
Footnotes:
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")))
So no @gmail.com, @outlook.com, or @yahoo.com addresses. You can
still use these to service your mail; wkd does not interact with any
email-related dns records.
