Emacs has a mechanism for client/server communication (and remote eval) that’s simultaneously too insecure and too secure at the same time.
Here’s the extremely convenient way to start a server:
(setq server-use-tcp t server-host (system-name) server-name (concat "foo-" (system-name))) (server-start)
This will create a file (if called on a machine named “stories”)
called ~/.emacs.d/server/foo-stories with the following content:
192.168.1.53:41929 4021 fH?D+M=u=r@N1O^L-`c"c_GYUj%zj,,hc&9QGF+0}0;c}M3>Evc2SdH_N\`SSV\t
If you then say (on the command line)
$ emacsclient --server-file=foo-stories --eval "(tellstick-switch 0)"
the emacsclient binary will read that file, find the IP address and port number there, connect to that address, output the second line (which is a security cookie), and then the form to be evalled. The server will check that the cookie is what it’s expecting, and then eval whatever it’s asked to eval, and will return the result.
Now, for this to work, the host that you’re running the emacsclient on has to have access to that file, which means that (in practice) the entire thing is based on a shared NFS or sshfs setup, which makes it impossible (or at least extremely awkward) to use in many situations. So it’s too secure.
On the other hand, if you do have that cookie, you can make the server eval anything. And the communication is in clear text, so you can man-in-the-middle as much as you’d like. So it’s really very insecure and can’t be used any other place other than a totally locked-down, trusted network.
So Emacs should have a different way to do an eval server. Specs:
- Don’t rely on a shared file system
- Communication should be encrypted
- The server should be able to limit the number of exposed endpoints
So, ideally starting the server should look something like:
(start-eval-server "lights" 8100 '(turn-on-lights turn-off-lights))
where the first parameter is the name of the service, the second is the port number it should listen to, and the third parameter is a list of functions it will allow the caller to have evaluated.
The client will typically look like:
(eval-at "lights" "stories" 8100 '(turn-on-lights kitchen))
For encryption, we could either do TLS or something home-brewed. And as all crypto experts tell us: Always roll your own crypto, so we’ll go with the latter one. But most of all because we’d really only be doing self-signed certificates, so TLS gives us a lot a problems without giving us security.
But we could rely on an ssh-like setup with pinned self-signed certificates to give the client security, and client certificates to give the server security, but it’s… difficult to see how that could be made to work without a lot of handholding. And Emacs doesn’t have built-in tools to create certificates, anyway…
Instead we’ll do symmetric encryption with a shared key. We’ll have ~/.authinfo entries like:
machine lights port 8100 password s0pers3cret
So… we’re not relying on a shared file system, but we are relying on pre-shared keys.
I’ve put the results on github, and in addition to the Emacs client/server bits, I’ve also added a shell script that relies on openssh to do the encryption.
$ ./eval-client lights stories 8710 '(+ 5 4)' 9 $ ./eval-client lights stories 8710 '(+ f 4)' Error: Got error message from server: (number-or-marker-p f)
Seems to work!
The client-server communication is plist-based, so they pass around things like:
I think AES-256-CBC with PKCS#7 padding is a reasonable cipher to use for this type of thing, but I’m not an encryption expert. Does anybody have any input on that bit?
The sexp specifies what cipher we’re using, so the protocol should be backwards/forwards compatible if we move to a different cipher in the future. The server should use the same cipher the client used, or if that’s not supportet any more, return an error.
The error message is also encrypted, of course.
Another design niggle: I’ve made the eval-at function throw errors when the server says an error has occurred, and if the error occurred when evalling the form, eval-at will throw the same error. Like:
(eval-at "lights" "stories" 8710 '(+ t 2)) -> (wrong-type-argument . "(number-or-marker-p t)")
Does that make sense? I mean, eval-at itself didn’t have a wrong-type argument: It made a network connection, squirted some data over, and got some data back. So it was a success! But…
What do you think?
When I find some time (and after using this for a while to see whether it makes sense), I’ll probably propose adding this to Emacs and deprecate the old emacsclient stuff.
Good idea, but I think deprecating the current emacsclient stuff is premature. What you have here is a better way of doing “emacsclient –eval” from a remote machine, but that is not the only way to use emacsclient. I would argue it’s not even the most common way. Not everyone has built a complete home automation system based on Emacs. Strangely.
For eval-server to be a replacement for emacsclient you need to cover the other uses as well, such as, you know, setting EDITOR=emacsclient to avoid starting new instances of emacs all the time, using emacsclient -nc to create new frames connected to a daemon mode emacs, handling client side environment variables, etc. One could probably build a replacement emacsclient based on eval-server, it’s just a small matter of defining a protocol that matches what emacsclient offers today.
But fudging around with shared secrets seems like overkill if you’re just wanting to avoid the startup overhead of your EDITOR, so you’d probably want to handle that automatically. Automatically messing with people’s ~/.authinfo will probably not be well received, so you’d have to fall back to something else. For instance a generated file in your configuration directory, which can be assumed to be shared between server and client because the usual case is between processes belonging to the same user on the same machine. (In fact, I believe someone told me once that the only reason emacsclient does TCP, not just local sockets, is because MS-DOS (and decendants?) don’t support AF_UNIX, and it was never really meant as a means for remote communication.)
So I’d be all in favor of eval-server in emacs, but not deprecating emacsclient. At least not until eval-server has progressed significantly.
Yeah, I just meant the –eval bit of emacsclient. 🙂
No crypto expert tell you to roll your own crypto. They tell you to rely on well established tools and procedures.