-
Notifications
You must be signed in to change notification settings - Fork 275
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
proposal: x509 extension registry #454
Comments
@julian88110 take a look at this. This proposal would mean that we should prefer to send the early muxer info in this registry rather than the ALPN. |
Hi Marco, |
This is meant to be the TLS part of the solution. The Noise part is #450. In both cases we are answering "how do we share data in the security layer?".
I don't think we should use the payload in the first message of the XX noise handshake unless we need to. I think for early muxer negotiation we should use the 2nd and 3rd message. Even though we don't consider supported muxers private information, we should still avoid passing data that can identify the connection as libp2p in plaintext. Some more discussion here: #453 (comment)
A unified way to send early encrypted data between TLS and noise. We can then add our extensions (e.g. send a list of supported protocols) to this registry and get it working on both TLS and Noise with minimal changes. w.r.t #446 this allows us to not commit ourselves to using the ALPN and thus not identifying the connection as a libp2p connection in plaintext. |
Thank you for this excellent writeup @MarcoPolo! To add a bit more context here: The better solution would be to a proper TLS extension, not an extension to the x509 certificate. We'd then be able to specify which handshake messages to attach data to. Unfortunately, most TLS implementations don't allow easy access to TLS extensions. They use TLS extensions internally, but don't expose them to the application layer. By (ab-) using the certificate, we gain easy access to the information.
Not sure if we need any prefixing. We can just define 1.3.6.1.4.1.53594.2.1 to mean "the protobuf we specified here". If we ever want to switch to a different, non backwards-compatible encoding, we can use 1.3.6.1.4.1.53594.3.1.
I have mixed feelings about approach. It feels wrong to roll our own solution given that there's a widely accepted and implemented RFC for this problem: ALPN. Despite that, I'm leaning ever so slightly towards using the proposed extension registry. There's certainly the advantage of having this information encrypted (without relying on ECH, which might or might not be implement by the standard library). Being consistent with Noise is nice as well. |
Agreed, this is a clever idea. The one concern I have is using a certificate extension for protocol selection purposes, that is not a straightforward function alignment. We run the risk of turning a certificate extension into a general purpose carrier. |
Just to be clear, what's the issue with doing that? |
Thanks to everyone taking time to review and discuss this. As we synced briefly today, here are some thoughts on the options of using TLS extension instead. Given the limited time and exposure to the code base, I am sure I might make some inaccurate assumptions, please correct me if I do. Hoping to provoke more thoughts that can drive this open issue to close soon.
Thanks! |
I wonder if we can do something simpler here. In TLS 1.3 the server can send application data in the second handshake message (which is what we're doing in Noise). We can have the server send this encoded registry in the second handshake message. The hard part here is that the server has to know that the client can handle this application data. Using ALPN could help, but could make it easy to identify libp2p connections. We could set the ALPN to some commonly used values to signify this version (it would be a hack; but it would mean our initial TLS handshake is indistinguishable from any other common TLS connection). |
TLS Extensions
I don’t think we need formal adoption to get a TLS extension code point. Registration should be pretty straightforward, https://www.iana.org/assignments/tls-extensiontype-values/tls-extensiontype-values.xhtml says:
That said, it seems like the only way to attach extensions to the handshake in an encrypted way is Encrypted Extensions. Apparently there's no way for the client to send encrypted extensions (other than ECH at some point in the future). 0.5-RTT data
This is what's commonly referred to as 0.5-RTT data, right? Technically, this wouldn't be data sent in TLS, but this would already be part of the encrypted byte stream that TLS provides. I don't think that
For the client, this would be indistinguishable from normal application data:
Note that this is getting very close to what Protocol Select is trying to do, where both sides send a list of supported protocols and we decide for one by intersecting these lists. It would be really nice to avoid this.
I'm really happy we didn't use this hack (setting ALPN to HTTP) when we first rolled out libp2p TLS, otherwise optimizations like this one wouldn't be possible now. |
I propose thinking about this problem from the other side. QUICLet’s first analyze QUIC, which is (once we have proper Happy Eyeballs-style dialing) our most common and most performant transport (>90% of connections). Would we want to use an x509 extension or a TLS extension with QUIC? No! TLS allows us to use 0.5-RTT data, so the server can just open streams and send data right after receiving the first packet from the client. In particular, the server could open the Identify stream right away and send its list of supported protocols. TLSThere are 2 options:
Having the muxers in ALPN would allow the server to choose the muxer right after receiving the client’s first flight. It could then start opening streams and send application data in 0.5-RTT data, exactly like QUIC does. Putting muxer information in an x509 or TLS extension would cost us an entire roundtrip, since the server would have to wait until completion of the handshake. We can work around this by putting application data into the x509 extension (like the list of Identify protocols), but it would definitely be nicer to just use a regular libp2p stream. It’s not clear if crypto/tls supports 0.5-RTT data, and if it’s worth the effort forking the standard library to add this feature. The nice thing is that this can be added in the future and is completely backwards-compatible. NoiseNoise XX doesn’t allow sending of 0.5-RTT data, so the round trip we’re saving with QUIC’s 0.5 RTT data is already lost. We wouldn’t gain anything by attaching the This is not ideal, but all we can hope to achieve with XX. We might want to consider offering a different handshake pattern in cases where we’re using inlined keys. That would allow us to send 0-RTT and 0.5-RTT data in those cases. What about censorship resistance?First of all, are we the only ones concerned about sending values (SNI, ALPN) unencrypted in the ClientHello? Clearly we aren’t, the IETF has been working on ECH (Encrypted ClientHello) for a few years now. It doesn’t seem like a crazy idea to live with the current situation for a little bit longer, until the RFC is published and implementations have caught up. For immediate solutions, WebTransport and WebSocket Secure. Both protocols use an HTTPS connection for the WebSocket / WebTransport upgrade request, so they’ll naturally (and correctly!) use the HTTP / H3 ALPN. There’s a slight cost of using WebSocket / Webtransport instead of TCP / QUIC, but that’s a small price to pay if you’re on a censored internet connection. And only until ECH is widely deployed. |
@marten-seemann thanks for the nice summary and comprehensive review. |
After a synchronous discussion with @julian88110 and @marten-seemann here's our shared understanding: Instead of thinking "How can we make tcp+tls look like Noise" maybe we should consider "How can we make tcp+tls look like QUIC". QUIC is the most common transport right now between go-libp2p nodes, and will be even more common in the future. With QUIC the server can send encrypted data to an unauthenticated client after 2nd handshake message. It can open new streams and run user protocols right away (with the caveat that the client is not yet authenticated). In this case, we don't need to define a new "Extension Registry" that defines a new format of sharing data to the client. We can use all our existing abstractions and methods of sending data (with the caveat, again, that this data is sent to an unauthenticated peer). We can make tcp+tls behave similarly if we negotiate which muxer to use. If the server knows which muxer to use, it can go ahead and open a new stream after the 2nd handshake message and open new streams and run user protocols right away (with the same caveat the the client is not yet authenticated). The benefit, again, is that existing user protocols don't need to change a whole lot. They just need to say they're okay sending this data to an unauthenticated party, but otherwise open and use a stream like normal. Contrast this to an "Extension Registry" where each protocol would have to register a code point and use this protobuf in order to make use of this early encrypted yet unauthenticated data. The easiest and standard way of defining which muxer the server should use in tcp+tls is to have the client negotiate this with ALPN. This is the intent behind ALPN so we aren't doing anything non-standard. The current recommendation then is to negotiate the muxer of ALPN and use the existing stream abstractions to send early-encrypted-yet-unauthenticated data. And to not implement a x509 extension registry or a TLS extension for this extension registry. To quickly summarize this issue and its points:
I hope this is a good summary of our discussion on a good overview of how we ended up with the decision to negotiate the muxer via ALPN. Please let me know if I'm missing something. |
Reasoning and plan makes sense to me. Thanks @MarcoPolo for the summary and thanks everyone for exploring this in depth. |
@MarcoPolo : I didn't read in depth, but in quickly skimming, it wasn't clear to me what the next steps are for this issue. Can you specify them if they aren't already stated? |
This issue was original created to propose an extension registry for x509 certs. After discussion, we don't think we want this. So I'm closing this issue. |
This proposal is in the similar vein as #450 but can be applied to our TLS security layer. All points in that issue apply here as well.
The general problem is that in some cases we want to pass data early. By doing this in the security handshake we can save some time and potentially round trips (see #446). Another example is passing in the list of supported protocols early. This is also something that protocol select #349 would make use of.
The way we'd include this early data is by attaching another extension to the x509 certificate. Similar to the libp2p public key extension we already do today. We could use the Object identifier number
1.3.6.1.4.1.53594.2.1
with the last2
distinguishing it from the existing public key extension. The53594
is allocated to libp2p by IANA. This data should be a varint prefixed (maybe multicodec) protobuf (e.g. `). The specific protobuf can be defined by this spec, but should be in line with the protobuf used in #450 so that we can reuse this definition in multiple places (and we get the same benefits whether we are on top of noise or tls). Even if some things in the registry only currently make sense within Noise (e.g. webtransport certhashes), the benefits of having one unified registry outweigh the costs of potentially unused fields.Why not ALPN?
A solution to #446 is to use the ALPN to negotiate the muxer. This works well, but it means our ALPNs will be sent in clear text and can serve as a way to identify the TLS handshake as a libp2p handshake.
ALPN also works well for solving the problem specific to #446, but doesn't generalize to our other problems. ALPN is meant to negotiate a protocol rather than exchange data.
Using this registry will allow us to unify this logic with Noise. Thus presenting a unified abstraction on top of our two security layers.
The text was updated successfully, but these errors were encountered: