-
Notifications
You must be signed in to change notification settings - Fork 377
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Engine API: proposal for authentication #162
Comments
Would you mind to provide more details and examples of exact payloads during the handshake for the WebSocket case? It seems like we need to add a new error code that would signal the caller that it's not authenticated. |
The websocket handshake starts with the client performing a websocket upgrade request. This must be a regular http Example:
The response, if accepted, is
After that, the connection is kept open, and is now full duplex. |
After having investigated/tested this a bit, I've come across a couple of problems. From https://testdriven.io/courses/taxi-react/websockets-part-one/#H-3-authenticating-socket-connections :
So. the The suggested solution is to pass the token in url query parameters. This, however, means that the token will/can be exposed in proxy caches and/or server logs -- which further means that we can definitely not use the empty payload for initial connect, but rather something based on a nonce or a timestap. I'm not sure what the best solution is here. cc @fjl |
Engine API is planned to be used by CL client to interact with EL one. I don't see the case when browser plays a role of CL client |
Looks good! Regarding "key distribution" -- is the common flow to start one side (either EL or CL), grab the key, then start the other side with that key? And advanced usage is to generate in some independent flow and provide the key on both sides? |
There is a desire to see CL light clients in the browser but (1) I'm not sure if/when this will happen and (2) it's unclear if it would drive an external EL client in such a case |
That was my idea, yes. The main point I'm not happy about right now is the empty payload at ws connect. |
Perhaps it would be good to also include the |
This seems like a reasonable approach to me |
Is there any limits to what is a valid |
Is there any expectation to include |
Well, we can prohibit 0x00, but not sure what else we can do.
The idea with The canonical usecase for |
Is there anything else here to discuss? We're looking to get a release out early this week to keep engineering moving. Shall we write up a PR? |
Yup. I can get started tomorrow at the earliest, if anyone wants to get it going before that please go ahead
|
is there a conversation happening about different key sharing formats between CL and EL for the JWT signing key? For instance, Besu could trivially support loading it from a java keystore on the filesystem, but thats really only friendly to Teku. Is there a more agnostic format we want to support across combinations? Besu does this today https://besu.hyperledger.org/en/stable/HowTo/Interact/APIs/Authentication/#jwt-public-key-authentication |
I have concerns about JWT, it seems like a complex protocol with many options and so pitfalls. For instance:
Excerpts:
And another one
|
@mratsim Do you have a recommendation for a good alternative solution? One of the things I like about JWT is there is a library for every language (often multiple libraries). While I agree with some of the arguments made in the links you provided about JWTs being overkill (especially since we aren't leveraging capabilities), I would like to see a concrete proposal for an alternative that is either so trivially simple that a library isn't necessary, or has a large selection of libraries that could be used. |
TOTP? |
AFAICS, this articles are explaining why JWT is bad for user authentication and in-browser use cases. I haven't found any issues in application to our particular use case, please, correct me if it's been overlooked. Our use case seems very simple and I don't think that if we were designing auth by ourselves without relying on already existing schemas we wouldn't end up with a pretty similar scheme that we have with JWT utilization. |
The argument is basically that JWT is extremely unnecessary for our needs. We literally just need a preshared key and a signature over the payload (or similar). JWT provides things like capabilities, permissions, etc., All features we aren't going to use. |
I think a very reasonable question to ask would be, "what features exactly do we need from the authentication?". I haven't seen that listed anywhere, is it? |
That's exactly why I think JWT is overkill and exposing us to many implementation bugs in third-party libraries and/or supply chain attacks while our use-case and threat model is very simple.
I've been looking into very simple schemes that would be misuse-resistant and came across Branca: https://github.com/tuupola/branca-spec It only needs XChaCha20-Poly1305, has test vectors, has 3 libraries in Go, 2 in Java, 1 Javascript, 1 Python and 1 Rust among other languages. The Python impl by the spec for reference is just 50 lines of code, assuming XChaCha20-Poly1305 provided by a crypto lib. Tagging @asanso since he is mentioned by name in JWT best practices at https://tools.ietf.org/id/draft-ietf-oauth-jwt-bcp-02.html#insecure-use-of-elliptic-curve-encryption https://github.com/tuupola/pybranca/blob/79366150353727c0410e111eef7dbaf2ed97ec8e/branca.py """
Branca
Authenticated and encrypted API tokens using modern crypto.
"""
import base62
import calendar
import ctypes
import struct
from binascii import unhexlify
from datetime import datetime
from xchacha20poly1305 import generate_nonce
from xchacha20poly1305 import crypto_aead_xchacha20poly1305_ietf_encrypt
from xchacha20poly1305 import crypto_aead_xchacha20poly1305_ietf_decrypt
from xchacha20poly1305 import CRYPTO_AEAD_XHCACHA20POLY1305_IETF_NPUBBYTES
from xchacha20poly1305 import CRYPTO_AEAD_XHCACHA20POLY1305_IETF_KEYBYTES
class Branca:
VERSION = 0xBA
def __init__(self, key):
if isinstance(key, bytes):
self._key = key
else:
self._key = unhexlify(key)
if len(self._key) is not CRYPTO_AEAD_XHCACHA20POLY1305_IETF_KEYBYTES:
raise ValueError(
"Secrect key should be {} bytes long".format(
CRYPTO_AEAD_XHCACHA20POLY1305_IETF_KEYBYTES
)
)
self._nonce = None # Used only for unit testing!
def encode(self, payload, timestamp=None):
if not isinstance(payload, bytes):
payload = payload.encode()
if timestamp is None:
timestamp = calendar.timegm(datetime.utcnow().timetuple())
version = struct.pack("B", self.VERSION)
time = struct.pack(">L", timestamp)
if self._nonce is None:
nonce = generate_nonce()
else:
nonce = self._nonce
header = version + time + nonce
ciphertext = crypto_aead_xchacha20poly1305_ietf_encrypt(payload, header, nonce, self._key)
return base62.encodebytes(header + ciphertext)
def decode(self, token, ttl=None):
token = base62.decodebytes(token)
header = token[0:CRYPTO_AEAD_XHCACHA20POLY1305_IETF_NPUBBYTES + 5]
nonce = header[5:CRYPTO_AEAD_XHCACHA20POLY1305_IETF_NPUBBYTES + 5]
ciphertext = token[CRYPTO_AEAD_XHCACHA20POLY1305_IETF_NPUBBYTES + 5:]
version, time = struct.unpack(">BL", bytes(header[0:5]))
# Implementation should accept only current version.
if version is not self.VERSION:
raise RuntimeError("Invalid token version")
payload = crypto_aead_xchacha20poly1305_ietf_decrypt(ciphertext, header, nonce, self._key)
if ttl is not None:
future = time + ttl
timestamp = calendar.timegm(datetime.utcnow().timetuple())
if future < timestamp:
raise RuntimeError("Token is expired")
return payload
def timestamp(self, token):
token = base62.decodebytes(token)
version, time = struct.unpack(">BL", bytes(token[0:5]))
return time |
Note compared to the desired properties, Branca is also overkill in the following way:
|
Vulnerabilities in a 3rd party libraries is a good point. The spec is currently based on
Currently it is 1) be able to prove that CL knows a secret 2) basic replay protection for auth tokens (via "iat") 3) optional: versioning. I believe we don't need anything on top of that. cc @holiman |
Btw, could we implement current auth scheme avoiding usage of 3rd party JWT libs? We have crypto libs and |
Since I have been mentioned by @mratsim here my 2 cents. I actually already gave a look to this proposal (thanks @djrtwo for pointing this out) and while I share some of the concerns about JWT as a whole (as you can also see from some of my previous research) the subset used in this proposal seem to be immune to those vulnerabilities. So it might really just a quick win to just use JWT (specifically HMAC + SHA256) for this use case rather than invent yet another solution. |
I think this is reasonable, though for censorship resistance and also to enable more distributed use-cases in the future I do think adding encryption would be interesting (and leaving key negotiation out of scope). |
We have been discussing this and ended up with TLS as a solution for encrypting communication channel between CL and EL. In this case extra auth scheme becomes redundant. |
What about the certificates and the certificate authorities though? Or do we use self-signing certificates? In that case a pre-shared key is likely better because it allows end-users negotiating them with whatever they want, cli-config/copy-pasting as suggested in this RFC or Diffie-Hellman in more complex infra. |
I think there are options here and TLS with self-signed certificates is one of them. IMHO, more sophisticated encryption and auth schemas shouldn't be a part of client implementations and rather be set up as additional infra alongside with clients. For instance, it could be an HTTPS/WSS server in front of EL and a corresponding client on the other side that CL has access through |
We discussed a few different schemes. One was to just go with TLS, but that would put tls endpoints into the client - making it difficult to integrate with a production TLS terminator, in case someone wants to put the api behind an nginx or cloudflare terminator. Not to mention certificate handling, which is a bit cumbersome.
Definitely. This EIP mandates HMAC+SHA256, and I've said from the start that I'm not hell-bent on JWT, but I still think it's suitable for this purpose -- given the constraints put forth in this EIP. There is no ambiguity about cryptography schema used, and a 10-second validity window. It can easily be integrated with a regular off-the-shelf TLS-terminator/load balancer. I'm still open to being convinced otherwise, if there are any concrete arguments about why our use of it is insufficient/insecure. |
Although the concerns of JWT being a "kitchen-sink" approach that has valid criticisms, I do think the way we use it in the scope of the engine API authentication scheme is decent. It is extremely commonplace, has tons of library support, and it is also fairly simple to write a custom JWT handler that meets the needs of the spec here. The auth constraints are simple, and we are not building this for web. Upon first learning the spec was suggesting JWT, we were able to immediately add support for it in Prysm as almost every developer that has worked on http auth has heard of JWT. Branca, although simpler in scope, feels more niche. TL;DR: based on the constraints of the spec, JWT works fine as there is no serious downside that necessitates using a more niche schema. |
Btw -- I guess this ticket can be closed now, since the corresponding PR has been merged. Any further discussions on this should rather go to either a new issue or a PR replacing it with something else. |
This is an RFC for authenticated JSON rpc API
Authentication
The
engine
JSON-RPC interface, exposed by EL and consumed by CL, needs to be authenticated. The authentication scheme chosen for thus purpose is JWT.The type of attacks that this authentication scheme attempts to protect against are the following:
The authentication scheme is not designed to
Authentication is performed as follows:
HTTP
dialogue, eachjsonrpc
request is individually authenticated by supplyingJWT
token in the HTTP header.inproc
, a.k.a raw ipc communication, no authentication is required, under the assumption that a process able to accessipc
channels for the process, which usually means local file access, is already sufficiently permissioned that further authentication requirements do not add security.JWT specifications
alg
HMAC + SHA256
(HS256
)alg
none
.The HMAC algorithm means that symmetric encryption is used, thus several CL's will be able to use the same key, and, from an authentication perspective be able to impersonate each other. From a deployment perspective, it means that an EL does not need to be provisioned with individual keys for each caller.
Key distribution
The
EL
andCL
clients MUST accept a cli/config parameter:jwt-secret
, a256
bit key, to be used for verifying/generating jwt tokens.If such a parameter is not given, the client SHOULD generate such a token, valid for the duration of the execution, and show the token in the output, which the user can then use to provision the counterpart client with.
The text was updated successfully, but these errors were encountered: