Skip to content
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

Magiselect - Wire Transparent security « handshake » #608

Open
Jorropo opened this issue Mar 16, 2024 · 2 comments
Open

Magiselect - Wire Transparent security « handshake » #608

Jorropo opened this issue Mar 16, 2024 · 2 comments

Comments

@Jorropo
Copy link

Jorropo commented Mar 16, 2024

Requirements

With Encrypted-Client-Hello RFC in the work it would help to have plain TLS connections in Libp2p as we can then wrap them in ECH (*we will need to figure out KEM preshared keys).
The quic and webtransport transports are already ECH ready, however for TCP connections we still have the multistream protocol id which blow away any hope of stealthish libp2p.

Spec

Security in the maddr

Add meaning to some maddr components after tcp component indicating to the client they are allowed to skip the multistream part of the handshake.

  • tls allows to run TLS without handshake.
  • noise allows to run Noise without handshake.

Server side recommendations

This part of the spec is non normative because we can't check for it. We should evolve it if new things running on top of TCP are added.
A valid implementation is to use a different TCP Port for each protocol you want to support, for example:

/ip6/::1/tcp/4011/tls
/ip6/::1/tcp/4012/noise

and not doing selection, and this usecase should be guaranteed so you can use a TLS / HTTPs reverse proxy switching on the ALPN as your libp2p gateway (allowing to create an anonymity set when using ECH).

However TCP listeners are costy from a user configuration end, it is much easier if the same port can be upgraded to support plain TLS just by doing a bit of magic.

First read three bytes from the inbound stream.
If you failed to read three bytes within your timeout windows, this can be discarded as very likely not a libp2p connection.

If these three bytes are:

  • equal to \x13/m treat it as multistream.
  • equal to GET, HEA, POS, PUT, DEL, CON, OPT, TRA or PAT treat it as an HTTP1.x
    • next is implementation specific, may do HTTP muxing or treat as websocket transport
  • equal to the unhexed 160301, 160302, 160303 or 160304 treat it as a TLS connection.
    • 160301 and 160302 might be useless as they are only used for TLS1.0 and TLS1.1 which shouldn't be used, however handling them allows your TLS server to do a proper rejection and simplify the code complexity.
      Turns out go's crypto/tls always sends TLS1.0 with client_version to 1.2 or 1.3.
    • 160303 is used both by TLS1.2 and TLS1.3 and 160304 is TLS1.3, so they must be used as libp2p's TLS spec requires TLS1.3.
  • Let type be the the three lsb of the third byte as an unsigned integer.
    type must be <= 5 (as there is no 6 or 7 wire values).
    Let field be the the five msb of the third byte an unsigned integer shifted right three times.
    Depending on the value of field:
    • 1 (identity_key), 2 (identity_sig) or 3 (data removed in 4b052c0) type must be 2 (LEN), treat it as noise.
    • 4 (extensions) type must be 2 (LEN) or 3 (SGROUP deprecated), treat it as noise.
    • Something else, this is not a noise connection.

Notes:

  • The noise detection is currently dependent on noise's spec protobuf schema, if we improve it to allow any valid looking protobuf I think it would clash with HTTP and or multistream-select.
  • We can be stricter about noise by ensuring the first two length bytes are big enough to make a valid protobuf messages but that looks good enough for me (it would only help rule out non libp2p protocols earlier).
  • TLS and Noise don't clash because TLS versions decode with a 0 protobuf field ID which is not valid protobuf.
  • Neither HTTP nor multistream-select clash with noise because I exhaustively checked it: https://go.dev/play/p/N3RpeJ1tkrs

Expected usage.

A server implementing magiselect on one TCP port might announce theses maddrs:

  • /ip4/1.2.3.4/tcp/12345 backward compat multistream-select
  • /ip4/1.2.3.4/tcp/12345/tls straight tls
  • /ip4/1.2.3.4/tcp/12345/noise straight noise
  • /ip4/1.2.3.4/tcp/12345/ws websocket transport on the same port as tcp transport
  • /ip4/1.2.3.4/tcp/12345/wss websocket secure transport on the same port as tcp transport, differentiated from libp2p's TLS due to ALPN.

multistream backward compatibility

Currently go-libp2p's server sends /multistream/1.0.0 before having received anything.
This behavior would change as we need to run detection on the first bytes sent by the client.

https://github.com/libp2p/specs/tree/master/connections says:

Next, both peers will send the multistream protocol id to establish that they want to use multistream-select. Both sides may send the initial multistream protocol id simultaneously, without waiting to receive data from the other side. If either side receives anything other than the multistream protocol id as the first message, they abort the negotiation process.

But I don't see why this is not a compliant implementation:

  • Client open connection.
  • Client waits to receive /multistream/1.0.0.
  • Server receive connection.
  • Server waits to receive /multistream/1.0.0.
  • 🦗 deadlock

I read this part of the multiformats spec as understanding this as the client should go first:

 # open connection + send multistream headers, inc for a protocol not available
> /multistream/1.0.0
> /some-protocol-that-is-not-available

# open connection + signal protocol not available.
< /multistream/1.0.0
< na

I think this can be solved by reviewing known implementations and making sure the client always send /multistream/1.0.0 without waiting on the server and updating the multistream-select spec to reflect that (while still allowing servers to optionally send it early).

@MarcoPolo
Copy link
Contributor

MarcoPolo commented Mar 18, 2024

Multiplexing on the first set of bytes seems reasonable. But the Noise identification seems brittle.

The client dialing /ip4/1.2.3.4/tcp/12345/noise knows the endpoint supports noise without a handshake. What about introducing a magic byte sequence to help the server identify the noise connection? The client would send this, and then start the normal noise handshake. The server sees the magic byte sequence, then, knowning it's a noise handshake, start the noise handshake.

@Jorropo
Copy link
Author

Jorropo commented Mar 18, 2024

👍 for magic noise bytes, this would allow to update the noise protobuf schema without worrying about the protobuf serializer putting new fields first which would break existing magiselect implementations.

@dhuseby dhuseby moved this to Triage in libp2p Specs May 7, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
Status: Triage
Development

No branches or pull requests

2 participants