Lighthouse: Encrypted comms between VC & GUI

Created on 15 Jun 2020  路  10Comments  路  Source: sigp/lighthouse

Description

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.

Problem Statement

"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:

  • The GUI knows that it is communicating with the correct VC.
  • The VC knows it is talking to an authorized GUI.
  • The comms are encrypted.

We're basically looking for attributes that are common across most web apps:

  • The client (GUI) knows that the server (VC) is legit, thanks to a public SSL\TLS certificate.
  • The server (VC) knows the client (GUI) is legit thanks to some login system.
  • TLS encrypts the channel.

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.

Potential Solutions

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.

RFC t API

Most helpful comment

Proposal 1.1 looks good to me :)

All 10 comments

I would recommend using JSON web tokens which is a standard for frontend backend authentication and communication on the web.

https://jwt.io/introduction/

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.

Proposal (1)

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:

  • The API between the VC and GUI is HTTP/REST.
  • Any sensitive endpoints require a Signature HTTP header where;

    • requests are signed using vc_pk.

    • responses are signed using vc_sk.

Authentication

This proposal provides authentication such that;

  • The GUI knows it is talking to the correct VC.
  • The VC knows the GUI has been granted access to vc_pk.

As such, vc_pk becomes a very basic "access token" to the VC.

Encryption

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:

  • SSL/TLS (nginx + letsencrypt, etc)
  • If getting a domain is too hard/not applicable, then reverse-SSH can be used to encrypt the traffic.

Reasoning

Why not JWT?

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.

Why not encrypt the HTTP body and avoid relying on SSL or reverse-SSH?

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:

  • Incomplete on it's own.
  • Redundant when implemented properly (with some transport-level security).

Why not have multiple access tokens for revocable access control?

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.

Open questions

  1. Which signature scheme?
  2. Which signing algorithm?

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.

  • SECP256k1 (should be faster for signing/verifying single signatures)
  • BLS12-381

Proposal (2)

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 create a key pair (vc_pk, vc_sk) and share vc_pk

Request

  • GUI - make an ephemeral key for a request (gui_pk, gui_sk)
  • GUI - Using ECIES create an AES key aes_sk = gui_sk * vc_pk
  • GUI - AES Encrypt request: enc_request using aes_sk
  • GUI - Send (gui_pk, enc_request)
  • VC - computes aes_sk = vc_sk * gui_pk
  • VC - decodes and processes request (keep aes_sk for response)

Response

  • VC - AES Encode response enc_response using aes_sk
  • VC - Sends enc_response
  • GUI - decodes enc_response using aes_sk

Both parties may now drop aes_sk.

Authentication

This proposal provides authentication such that;

  • The GUI knows it is talking to the correct VC. Else they would not be able to decode the request nor send a valid response.
  • The VC knows the GUI has been granted access to vc_pk. Else they would not be able to encode a request.

Encryption

All messages are AES encrypted

ECIES

Any elliptic curve can be used for this, but again likely one of the two we already use:

  • SECP256k1
  • BLS12-381

Benefits

  • We do not need to store state.
  • All messages are encrypted at the application layer.
  • Public key is secret to any one listening in.

Cons

  • Requires additional encryption (which may be unnecessary if we already do this at the application layer).
  • AES and ECIES are required (although we already use AES and ECIES is trivial).
  • Anyone with the public key could send requests. To revoke a malicious node would require generating and sharing a new public key.

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:

  • HTTP REST
  • RPC

    • via HTTP

    • via Websocket

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:

Proposal (1.1)

Setup: The VC assigns itself a secp256k1 keypair and we call these keys vc_pk and vc_sk.

The proposal goes:

  • The API between the VC and GUI is HTTP/REST.
  • Any sensitive endpoints require a Signature HTTP header where;

Authentication

This proposal provides authentication such that;

  • The GUI (client) knows that the responses are from the VC (server).
  • The VC (server) knows the GUI (client) has been granted access via knowledge of 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".

Encryption

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:

  • SSL/TLS (nginx + letsencrypt, etc)
  • If getting a domain is too hard/not applicable, then reverse-SSH can be used to encrypt the traffic.

VC Key Generation

As mentioned above, the VC assigns itself a secp256k1 keypair. This keypair:

  • Should be stored on-disk (unencrypted) with the configuration files of the VC.
  • Generated randomly if it does not already exist.

Proposal 1.1 looks good to me :)

Proposal 1.1 was implemented in #1657

Was this page helpful?
0 / 5 - 0 ratings

Related issues

paulhauner picture paulhauner  路  4Comments

paulhauner picture paulhauner  路  3Comments

paulhauner picture paulhauner  路  5Comments

JustinDrake picture JustinDrake  路  3Comments

jrhea picture jrhea  路  4Comments