This issue is an RFC regarding communications between the validator client and the upcoming GUI.
See https://github.com/sigp/lighthouse/issues/1269#issuecomment-649879855 for the latest proposal.
"The GUI" (a single-page browser application) will connect to a validator client (VC) using a HTTP API hosted by the VC (e.g., hyper).
There are two desirable attributes about this communication channel:
We're basically looking for attributes that are common across most web apps:
However, our scenario is rather constrained. A public SSL cert is not really viable, we don't want users to have to purchase a certificate and install it on the VC. A login system is doable in Rust, but it seems a little overkill (we only really need one "user").
What we're looking for is basically what Parity-Ethereum used for their now-deprecated UI.
So it's fairly easy to imagine some system that uses some asymmetric crypto to establish the identity of the VC, then Diffie-Hellman to encrypt the comms between them. We could then run that on top of HTTP (i.e., inside the HTTP message body), however that's (a) a pain in the ass and (b) going to be even more of a pain in the ass once you start to harden it against replay attacks, etc. Also, it feels like this problem should already be solved and we would be "rolling our own crypto" in a way.
What would be great is to implement the auth/encryption on the transport layer, like TLS. We can very easily implement whatever we want in Rust, but I think browser support is going to be our biggest challenge.
I suspect there's a pretty simple solution out there that I just haven't discovered yet.
Keen to hear suggestions.
I would recommend using JSON web tokens which is a standard for frontend backend authentication and communication on the web.
The client could store a authentication token -- possibly also with some expiration.
Note: this proposal has been replaced by https://github.com/sigp/lighthouse/issues/1269#issuecomment-649879855. Please refer to it instead.
Alright, after some discussion with @AgeManning and @zedt3ster we have the first workable proposal.
Setup: Assume we have some asymmetric signature scheme. The VC assigns itself a keypair according to this scheme and we call these keys vc_pk and vc_sk.
The proposal goes:
Signature HTTP header where;vc_pk.vc_sk.This proposal provides authentication such that;
vc_pk.As such, vc_pk becomes a very basic "access token" to the VC.
This proposal does not provide any encryption. Instead, I propose we use transport level security, when required.
Users running the GUI and VC on the same machine can communicate via localhost, no encryption required.
Users running the GUI and VC on separate computers, across an untrusted network can use:
I feel like the JWT really shines where you need to track users and sessions, that's where I've used them in the past. In our scenario, our authentication requirements are very simple: one access token to rule them all. As such, I think the Signature header is a simpler solution here.
You can't feasibly encrypt the HTTP headers, so even if you encrypt the body you're going to be leaking info via headers.
Encrypting the body seems like a solution that is:
Using the vc_pk is super simple and mean we don't need to store state to track users, sessions, etc.
I think we really want to avoid complexity in the VC. Adding the state to store users, sessions, etc. is bad enough, but if we do that then we also need some interface to revoke/grant tokens. This is more complexity that I just really don't think we need.
I think there is no need to sign with the public key, as it is public anyone would be able to sign with it. I'm assuming here that anyone can get access to vc_pk.
We could simply attach vc_pk as plain text (which is encrypted at the transport layer) to act as an ID token access / ID token to ensure that the VC is the intended receiver. That would mean we don't need to have an algorithm that allows for signing with the public key. The VC would then sign the response with vc_sk.
Then we can use any of the public key signing algorithms already in use e.g.
Use ECIES + AES with ephemeral keys for each request. ECIES will be used to generate an ephemeral AES key which will encrypt one request and one response.
This would cover both the encryption and singing of messages.
Prelude:
vc_pk, vc_sk) and share vc_pkRequest
gui_pk, gui_sk)aes_sk = gui_sk * vc_pkenc_request using aes_skgui_pk, enc_request)aes_sk = vc_sk * gui_pkaes_sk for response)Response
enc_response using aes_skenc_response enc_response using aes_skBoth parties may now drop aes_sk.
This proposal provides authentication such that;
vc_pk. Else they would not be able to encode a request.All messages are AES encrypted
Any elliptic curve can be used for this, but again likely one of the two we already use:
I think there is no need to sign with the public key, as it is public anyone would be able to sign with it.
This is an excellent point.
I'm assuming here that anyone can get access to vc_pk.
Weirdly, the vc_pk is still intended to be somewhat of a secret in this system. If you reveal your vc_pk then anyone (with HTTP access) can control your validator.
We should be very careful of calling it a "public key" in user-facing scenarios. Perhaps we should use something else, but it just seems so convenient to use the vc_pk as the access token.
Proposal (2)
This is a great scheme, but I'm still not convinced that we should be encrypting the HTTP body.
If we do encryption on the body we are, in a way, communicating that this message is safe to travel across a public network. However, since we can't feasibly encrypt the HTTP headers we're leaking information about that request, most notably the fact that we're talking to a validator client. I think that would make it unsafe to travel across a public network.
So, this is what lead me to state that encrypting the body is either insufficient on its own or redundant when combined with transport-layer security.
Public key is secret to any one listening in.
This is cool, but I think to prevent replay attacks we'd need to store state in the VC and ensure uniqueness.
Weirdly, the vc_pk is still intended to be somewhat of a secret in this system. If you reveal your vc_pk then anyone (with HTTP access) can control your validator.
Sounds like SECP256k1 would do the trick then if you've got transport layer encryption.
So, this is what lead me to state that encrypting the body is either insufficient on its own or redundant when combined with transport-layer security.
That's a good point, is HTTP a must? I'm not sure what the alternatives would be but I would say proposal 2 would work best on a direct connection.
Public key is secret to any one listening in.
This is cool, but I think to prevent replay attacks we'd need to store state in the VC and ensure uniqueness.
You're right there would need to be some kind of persistent nonce system. It could be a random nonce that is stored in the VC and common between all users say u64 or u128 which would be extremely improbable that any two nodes would randomly select the same nonce (and if they do they could just send a new request).
That's a good point, is HTTP a must? I'm not sure what the alternatives would be but I would say proposal 2 would work best on a direct connection.
I would love to use a direct TLS connection, but unfortunately the client needs to be a browser. I think our options are:
JSON-RPC is tempting, but all the other APIs are REST so it seems consistent to follow that unfortunately.
With regard to @kirk-baird's comment about not needing to sign requests, I provide this updated proposal:
Setup: The VC assigns itself a secp256k1 keypair and we call these keys vc_pk and vc_sk.
The proposal goes:
Signature HTTP header where;Authorization: Basic <vc_pk> header.Signature <sig> header, where sig is a signature using vc_sk across the sha256 of the response headers + body.This proposal provides authentication such that;
vc_pk.As such, vc_pk becomes a basic "access token" to the VC. As such, we should not present it to users as a "public key"; instead more like "secret access token".
This proposal does not provide any encryption. Instead, I propose we use transport level security, when required.
Users running the GUI and VC on the same machine can communicate via localhost, no encryption required.
Users running the GUI and VC on separate computers, across an untrusted network can use:
As mentioned above, the VC assigns itself a secp256k1 keypair. This keypair:
Proposal 1.1 looks good to me :)
Proposal 1.1 was implemented in #1657
Most helpful comment
Proposal 1.1 looks good to me :)