Certificate Pinning for GNU Emacs

GNU Emacs is where is spend most of my computer time. Using Emacs, I’m writing texts in general and this post in particular, I’m programming, I’m reading RSS feeds and news articles, I’m reading and writing e-mails. Emacs is highly customizable and extensible which is great, in general. However, in the past Emacs valued convenience over security, and by default it does not protect against man-in-the-middle (MITM) attacks. This is about to change with upcoming releases.

In a previous post, I explained how certificate pinning protects against MITM attacks and I recommended GnuTLS with its command line tool gnutls-cli for do-it-yourself certificate pinning based on trust-on-first-use (TOFU). In this post, I explain my Emacs configuration for certificate pinning with GnuTLS. The lisp code shown in the following is executed from ~/.emacs, as usual.

Emacs comes with at least three different libraries to establish SSL/TLS connections (net/gnutls.el, net/tls.el, gnus/starttls.el), and some libraries implement their own approach (e.g., net/imap.el). Which approach is used when does not seem to be documented; in general, it depends on what software is installed on your machine. By default, various Emacs libraries will use net/gnutls.el, which is vulnerable to MITM attacks. So I disable that library if it is available:

(if (fboundp 'gnutls-available-p)
    (fmakunbound 'gnutls-available-p))

Without net/gnutls.el, lots of code falls back to net/tls.el, which uses gnutls-cli, but with the switch --insecure. Clearly, that option is called “insecure” for a reason, and by replacing that option certificate pinning based on TOFU can be activated:

(setq tls-program '("gnutls-cli --strict-tofu -p %p %h")

(Recall from my previous post that you need a recent version of GnuTLS for this to work.)

In particular, the above change enables certificate pinning for news servers via NNTPS. E.g., I’m using the following for

(setq gnus-select-method
      '(nntp ""
	     (nntp-open-connection-function nntp-open-tls-stream)
	     (nntp-port-number 563)
	     (nntp-address "")))

(Recall from my previous post that you need to pin the server’s certificate first, e.g., via gnutls-cli --tofu -p 563 The same holds for every server.)

I’m sending e-mails via mail/smtpmail.el, which also defaults to net/gnutls.el but falls back to gnus/starttls.el. In my case, that library uses gnutls-cli, and option --strict-tofu can be added via a variable, starttls-extra-arguments:

(setq starttls-extra-arguments '("--strict-tofu"))

I’m reading e-mails via net/imap.el, which does not use net/gnutls.el but its own approach based on Openssl’s s_client. While s_client is great to debug SSL/TLS connections, it is useless for everyday encryption as it prints an error message if certificates cannot be verified, but it opens the connection anyways. And, those errors are not shown in Emacs. So, switch to gnutls-cli with certificate pinning:

(setq imap-ssl-program '("gnutls-cli --strict-tofu -p %p %s"))

To summarize, this is what I’m currently using in and recommending for Emacs:

(if (fboundp 'gnutls-available-p)
    (fmakunbound 'gnutls-available-p))
(setq tls-program '("gnutls-cli --strict-tofu -p %p %h")
      imap-ssl-program '("gnutls-cli --strict-tofu -p %p %s")
      smtpmail-stream-type 'starttls
      starttls-extra-arguments '("--strict-tofu")

5 comments to Certificate Pinning for GNU Emacs

  • chrisb

    Thanks for addressing how to defeat MITM. Independent certificate fingerprints are available here:

    This is helpful in the case when TOFU cannot be assumed, or when certs change legitimately.

    Also, stunnel has had support for pinning and recently added the ability to specify PFS ciphers and tls 1.2. The mew emacs mailreader uses stunnel for secure imap and smtp rather than gnutls.

    Here is a project for certificate fingerprint comparison automation with client/server architecture:

    • Jens Lechtenbörger

      Many thanks for your input. I don’t know anything about mew so far, but I’ll definitely check out pinning with stunnel.
      Concerning convergences, last time I tried that did not seem to work. Perspectives is an older approach which still seems to work. Then, there is TACK. Lots of action going on…

      • Jens Lechtenbörger

        Here is how to perform certificate pinning with stunnel to listen on port 4443, to be redirected to

        stunnel -c -f -d 4443 -P /tmp/ -r -v 3 -A cert-chain.pem

        In that invocation, “-v 3” is essential, to “verify peer with locally installed certificate,” and cert-chain.pem needs to contain the entire certificate chain. Starttls for selected protocols is supported via option “-n” but I don’t see how I could report verification errors to the client app.

  • victor

    Thanks for the post, and for pushing the issue with upstream.

    I wanted to comment on `s_client.` Even though s_client(1) suggests that the connection should always continue, I have found that using both `-verify` and `-verify_return_error` will cause s_client to abort on a failed verification. For example, my system lacks the SPI-Inc CA cert, so connecting to OFTC should fail

    $ openssl s_client -state -debug -no_ssl2 -verify -verify_return_error -CAfile /etc/ssl/certs/ca-certificates.crt -connect

    verify error:num=19:self signed certificate in certificate chain
    verify return:0
    write to 0x21254b0 [0x22309b0] (7 bytes => 7 (0×7))
    0000 – 15 03 01 00 02 02 30 ……0
    SSL3 alert write:fatal:unknown CA
    SSL_connect:error in SSLv3 read server certificate B
    SSL_connect:error in SSLv3 read server certificate B
    140445611918992:error:14090086:SSL routines:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed:s3_clnt.c:1167:

    exit 1

    • Jens Lechtenbörger

      Thanks for your feedback. I’m not sure what is happening in your case, though, as option -verify expects a depth argument.