-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
noise: define API for early data #1537
Comments
Thank you writing this up @yusefnapora. Not sure if this is the right repo to have this discussion, it seems like the API we're defining applies to all transports. Maybe we should move this to go-libp2p (or go-libp2p-core?). I recently implemented Early Data in quic-go, and also started work on implementing a 0-RTT API API for quic-go (which doesn't yet correctly deal with 0-RTT rejections though, see below). Early DataI chose to expose the whole For the receiver, early data is not that different from post-handshake data. More specifically, on the receiver side, in the case of TLS 1.3, early data uses the same encryption key anyway, so there's the client has no reliable way to distinguish between early data and post-handshake data anyway. In my understanding, the same applies in Noise, at least as long as we're not trying to send unencrypted data (which we are not). On the sender side, it's nice to have the fully-fledged connection object available, since you're getting connection metadata (like I thought it would be a good idea to design an API as close to the standard library's TLS implementation. There type EarlyConn interface {
net.Conn
// Blocks until the handshake completes (or fails).
// Data sent before completion of the handshake is encrypted with 1-RTT keys.
// Note that the client's identity hasn't been verified yet.
HandshakeComplete() context.Context
} The way you would use this (replacing conn, _ := earlyListener.Accept()
_, _ = conn.Write(/* early data */)
// Block until we've verified the client's identity.
<-conn.HandshakeComplete().Done()
_, _ = conn.Write(/* application data */) Equivalently, in the case of QUIC, you'd be able to open (multiple) streams on the the 0-RTT0-RTT is a tricky beast. Conceptually, the common case is easy: We need an libp2p/specs#227 defines how we want to use 0-RTT in libp2p: A client is required to remember the stream multiplexer for a 0-RTT connection. A client then can send multiplexed application data in its first flight. This makes sense because we don't expect stream multiplexers to change frequently, and we would just reject 0-RTT and fall back to the normal handshake in this corner case. Rejecting 0-RTT is defined in section 4.2.10 of RFC 8446. It is very instructive to read that section of the RFC. What makes 0-RTT complicated is the fact that the server might not accept 0-RTT. There are mutliple reasons why a server would do so, and none of them is predictable by the client. One reason could be that the server forgot the key that it used to encrypt the session ticket. Another reason might be that the application protocol changed, or some parameters of the application protocol (in our case, the stream multiplexer might not available any more). In the general case (see the TLS RFC linked above), all the data written becomes invalid, and a TLS stack can't retransmit that data (consider the case where a client 0-RTTs a connection using application protocol We need to decide if there will ever be a case like that in libp2p. We might be able to argue that since libp2p doesn't use ALPN (we might just use a fake ALPN value to mask our traffic), the above mentioned scenario does not apply. For example, if 0-RTT is rejected because the stream multiplexer change, our stack can just retransmit the data that the application wrote using the newly negotiated stream multiplexer. This would simplify our API and application logic significantly, since 0-RTT rejects are then something that the application would never have to deal with. Proposal for a libp2p APIIf we decide to take a similar approach in libp2p, we'd probably have to extend the type EarlySecureConn interface {
SecureConn
HandshakeComplete() context.Context
} Since 0-RTT resumes a connection including the multiplexer, we need to expose a multiplexed connection as soon as the client calls type EarlyMuxedConn interface {
MuxedConn
HandshakeComplete() context.Context
} Of course, we also have to extend / modify the respective listener and dialer interfaces that will return the |
Thanks @marten-seemann, and sorry I forgot to tag you. I agree that we should probably move this to either go-libp2p or go-libp2p-core. I like how the It seems like data sent through an |
cc @raulk, since he may have opinions about this sort of thing 😄 |
@yusefnapora Maybe this resolves @raulk's comment in https://github.com/libp2p/specs/pull/227/files#r351329177? For an implementation that doesn't support sending in early data, it's perfectly valid to send the list of stream multiplexers one roundtrip later. |
Closing this as it's addressed by libp2p/specs#453 and implemented by #1813 |
We want to be able to send "early data" in our handshake message payloads, so that we can advertise supported stream multiplexers without adding round-trip negotiations after the handshake completes.
So far, we've defined the protobuf field in the handshake payload that will carry the early data, but we also need a way to provide it when configuring the transport, and a way to retrieve the early data sent by the remote party.
For setting the early data, we could have a new interface that early data capable
SecureTransport
s would implement, e.g.Then when go-libp2p instantiates the security modules for a Host, it can type assert to see if they support this interface and inject the payload with the set of multiplexers.
We also need another interface that early data capable
SecureConn
implementations would conform to, something likeThen multiselect 2 could type assert on the connection & try to get the set of muxers out if the conn supports early data.
The actual interfaces should probably be defined elsewhere, since they'll be common to any crypto channel that supports early data (e.g. TLS 1.3).
When implementing, we need to take care that the early data is sent in a handshake message that supports encrypted payloads. This essentially just means we can't send it in the initiator's first
XX
message.Now that I've typed this up, I'm starting to question whether the method to set the early data payload belongs on the transport itself, or whether it should be specialized for each connection. I was thinking that it doesn't need to be specialized, since the set of muxers is fairly static. But QUIC doesn't need a muxer, so we might want to skip sending the payload for QUIC connections. In that case, we'd need to define a new flavor of the
SecureTransport
interface that allows passing in the early data payload for each connection.The text was updated successfully, but these errors were encountered: