libp2p / specs

Technical specifications for the libp2p networking stack
https://libp2p.io
1.54k stars 274 forks source link

Magiselect - Wire Transparent security « handshake » #608

Open Jorropo opened 5 months ago

Jorropo commented 5 months ago

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.

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:

Notes:

Expected usage.

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

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:

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 commented 5 months ago

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 commented 5 months ago

:+1: 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.