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.