This post was written in 2015 and is badly outdated. It is mostly preserved in
its historical form with a few contemporaneous updates, but to see where we are
in 2024, please have a look at the update
post
Are you a programmer? Do you use a text editor? Do you install any 3rd-party
functionality into that text editor?
If you use Vim, you’ve probably installed a few
vimballs
from vim.org, a website only available over HTTP.
Vimballs are fairly opaque; if you’ve installed one, chances are you didn’t
audit the code.
If you use Emacs, you’ve probably installed some packages from
ELPA or MELPA using package.el
;
in Emacs’s default configuration, ELPA is accessed over HTTP, and until
recently MELPA’s documentation recommended HTTP as well.
When you install un-signed code into your editor that you downloaded over an
unencrypted, unauthenticated transport like HTTP, you might as well be
installing malware. This is not a joke or exaggeration: you really might
be. You have no assurance that you’re not being exploited by someone on
your local network, by someone on your ISP’s network, the NSA, the CIA, or
whoever else.
The solution for Vim is relatively simple: use
vim-plug, which fetches stuff from
GitHub exclusively via HTTPS. I haven’t audited it conclusively but its
relatively small codebase includes lots of https://
and no http://
or
git://
that I could see.
I’m relatively proud of my track record of being a
staunch
advocate
for improved security in text editor package installation. I’d like to think I
contributed a little to the fact that MELPA is now available over HTTPS and
instructs you to use HTTPS URLs.
But the situation still isn’t very good in Emacs-land. Even if you manage to
get your package sources from an authenticated source over HTTPS, it doesn’t
matter, because
Emacs won’t verify TLS.
Although package signing is implemented, practically speaking, none of the
packages are signed. Therefore, you absolutely cannot trust package signing
to save you. Plus, even if the packages were signed, why is it the NSA’s
business which packages you’re installing, anyway? TLS is shorthand for The
Least Security (that is acceptable); whatever other security mechanisms, like
package signing, are employed, you should always at least have HTTPS.
With that, here’s my unfortunately surprise-filled step-by-step guide to
actually securing Emacs downloads, on Windows, Mac, and Linux.
Step 1: Make Sure Your Package Sources Are HTTPS Only
By default, Emacs ships with its package-archives
list as '(("gnu"
. "http://elpa.gnu.org/packages/"))
, which is obviously no good. You will
want to both add MELPA (which you surely have done anyway, since it’s where all
the actually useful packages are) and change the ELPA URL itself to be HTTPS.
Use M-x customize-variable
to change package-archives
to:
| `(("gnu" . "https://elpa.gnu.org/packages/")
("melpa" . "https://melpa.org/packages/"))
|
Step 2: Turn On TLS Trust Checking
There’s another custom variable in Emacs, tls-checktrust
, which checks trust
on TLS connections. Go ahead and turn that on, again, via M-x
customize-variable tls-checktrust
.
Step 3: Set Your Trust Roots
Now that you’ve told Emacs to check that the peer’s certificate is valid, Emacs
can’t successfully fetch HTTPS URLs any more, because Emacs does not distribute
trust root certificates. Although the set of cabforum certificates are already
probably on your computer in
various
forms, you still have to acquire
them in a format usable by Emacs somehow. There are a variety of ways, but in
the interests of brevity and cross-platform compatibility, my preferred
mechanism is to get
the certifi
package from PyPI, with
python -m pip install --user certifi
or similar. (A tutorial on installing
Python packages is a little out of scope for this post, but hopefully
my little website about this will help you get started.)
At this point, M-x customize-variable
fails us, and we need to start just
writing elisp code; we need to set tls-program
to a string computed from the
output of running a program, and if we want this to work on Windows we can’t
use Bourne shell escapes. Instead, do something like this in your .emacs
or
wherever you like to put your start-up elisp:
| (let ((trustfile
(replace-regexp-in-string
"\\\\" "/"
(replace-regexp-in-string
"\n" ""
(shell-command-to-string "python -m certifi")))))
(setq tls-program
(list
(format "gnutls-cli%s --x509cafile %s -p %%p %%h"
(if (eq window-system 'w32) ".exe" "") trustfile))))
|
This will run gnutls-cli
on UNIX, and gnutls-cli.exe
on Windows.
You’ll need to install the gnutls-cli
command line tool, which of course
varies per platform:
- On OS X, of course, Homebrew is the best way to go about this:
brew install
gnutls
will install it.
- On Windows, the only way I know of to get GnuTLS itself over TLS is to go
directly to
this mirror.
Download one of these binaries and unzip it next to Emacs in its
bin
directory.
- On Debian (or derivatives),
apt-get install gnutls-bin
- On Fedora (or derivatives),
yum install gnutls-utils
Great! Now we’ve got all the pieces we need: a tool to make TLS connections,
certificates to verify against, and Emacs configuration to make it do those
things. We’re done, right?
Wrong!
Step 4: TRUST NO ONE
It turns out there are two ways to tell Emacs to really actually really
secure the connection (really), but before I tell you the second one or why you
need it, let’s first construct a little test to see if the connection is being
properly secured. If we make a bad connection, we want it to fail. Let’s make
sure it does.
This little snippet of elisp will use the helpful
BadSSL.com site to give you some known-bad and known-good
certificates (assuming nobody’s snooping on your connection):
1
2
3
4
5
6
7
8
9
10
11
12
13
14 | (let ((bad-hosts
(loop for bad
in `("https://wrong.host.badssl.com/"
"https://self-signed.badssl.com/")
if (condition-case e
(url-retrieve
bad (lambda (retrieved) t))
(error nil))
collect bad)))
(if bad-hosts
(error (format "tls misconfigured; retrieved %s ok"
bad-hosts))
(url-retrieve "https://badssl.com"
(lambda (retrieved) t))))
|
If you evaluate it and you get an error, either your trust roots aren’t set up
right and you can’t connect to a valid site, or Emacs is still blithely
trusting bad certificates. Why might it do that?
One of Emacs’s compile-time options is whether to link in GnuTLS or not. If
GnuTLS is not linked in, it will use whatever TLS program you give it (which
might be gnutls-cli
or openssl s_client
, but since only the most recent
version of openssl s_client
can even attempt to verify certificates, I’d
recommend against it). That is what’s configured via tls-checktrust
and
tls-program
above.
However, if GnuTLS is compiled in, it will totally ignore those custom
variables, and honor a different set: gnutls-verify-error
and
gnutls-trustfiles
. To make matters worse, installing the packages which
supply the gnutls-cli
program also install the packages which might satisfy
Emacs’s dynamic linking against the GnuTLS library, which means this code path
could get silently turned on because you tried to activate the other one.
To give these variables the correct values as well, we can re-visit the
previous trust setup:
1
2
3
4
5
6
7
8
9
10
11
12 | (let ((trustfile
(replace-regexp-in-string
"\\\\" "/"
(replace-regexp-in-string
"\n" ""
(shell-command-to-string "python -m certifi")))))
(setq tls-program
(list
(format "gnutls-cli%s --x509cafile %s -p %%p %%h"
(if (eq window-system 'w32) ".exe" "") trustfile)))
(setq gnutls-verify-error t)
(setq gnutls-trustfiles (list trustfile)))
|
Now it ought to be set up properly. Try the example again from Step 4 and it
ought to work. It probably will. Except, um...
Appendix A: Windows is Weird
As of November 2015, the official Windows builds of Emacs were linked against
version 3.3 of GnuTLS rather than the latest 3.4. You might need to download
the latest micro-version of 3.3 instead.
As far as I can tell, it’s supposed to work with the command-line
tools (and maybe it will for you) but for me, for some reason, Emacs could not
parse gnutls-cli.exe
’s output no matter what I did. This does not appear to
be a universal experience, others have reported success; your mileage may
vary.
Update: Thanks to astute reader Richard Copley, I’ve been informed that
command-line tools aren’t even really supposed to work on Windows; as
described in this bug
comment:
TLS connections on MS-Windows are supported via the GnuTLS library.
External TLS programs will never work correctly on Windows, since they
use signals to communicate with Emacs. So there's little sense in
fixing this issue, because the result will not work anyway.
Eli Zaretskii
Conclusion
We nerds sometimes mock the “normals” for not being as security-savvy as we
are. Even if we’re considerate enough not to voice these reactions, when we
hear someone got malware on their Windows machine, we think “should have used a
UNIX, not Windows”. Or “should have been up to date on your patches”, or
something along those lines.
Yet, nerdy tools that download and execute code - Emacs in particular - are
shockingly careless about running arbitrary unverified code from the Internet.
And we are often equally shockingly careless to use them, when we should know
better.
If you’re an Emacs user and you didn’t fully understand this post, or you
couldn’t get parts of it to work, stop using package.el
until you can get
the hang of it. Get a friend to help you get your environment configured
properly. Since a disproportionate number of Emacs users are programmers or
sysadmins, you are a high-value target, and you are risking not only your own
safety but that of your users if you don’t double-check that your editor
packages are coming from at least cursorily authenticated sources.
If you use another programmer’s text editor or nerdy development tool that is
routinely installing software onto your system, make sure that if it’s at
least securing those installations with properly verified TLS.