-
Notifications
You must be signed in to change notification settings - Fork 23
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
QUIC Initial Packet Decryption and Parsing #37
Conversation
Currently, this PR has the ability to decrypt at least some portion of QUIC client initial traffic. I will test further with a variety of client traces to ensure that no edge cases have been missed. I have gone ahead and opened this PR to get feedback from @thegwan and @thearossman on the direction that you guys were planning to go with QUIC processing at the QUIC frame, TLS, and application layer. |
Hey! Initial thoughts on the WIP:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Two clarifying q's, thank you!
@@ -0,0 +1,295 @@ | |||
// This is heavily based on Cloudflare's Rust implementation of QUIC, known as Quiche. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
similar to general comment -- at some point, header comment would be helpful for us (I'm only surface-level familiar with inner workings of QUIC)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will do!
@@ -39,6 +40,9 @@ pub struct QuicPacket { | |||
|
|||
/// The number of bytes contained in the estimated payload | |||
pub payload_bytes_count: Option<u64>, | |||
|
|||
// The decrypted QUIC packet payload | |||
pub decrypted_payload: Option<Vec<u8>>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We had a few questions out of curiosity - not feedback -
I ran this, and got a dump of raw payload (just bytes). Can you clarify what is expected to be in that payload? Is there anything that should be parsed from it, for example that a researcher might reasonably want to filter on? If not, what kinds of possible use-cases are there for collecting the raw bytes?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, raw bytes are definitely not helpful!
For background, I have an ongoing project looking at specific features of the QUIC Client Init that can be used to develop a fingerprint and identify QUIC clients. I already have a functioning QUIC parser that was built on top of TLS Fingerprint. However, I have been wanting to rebuild the QUIC version on top of Retina.
Before this is ready to merge I intend to add support for a fully parsed QUIC Client Init including the TLS ClientHello. I added that field temporarily to ensure that the decrypted payload was as expected, a list of QUIC frames.
To answer your question, I envision a researcher being able to filter on the same things one could filter in TLS like SNI.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The latest commit introduces frame parsing. I have replaced the decrypted_payload
with a frames
vector on the QuicPacket
struct. Once I have the CRYPTO frame reassembly and TLS ClientHello parsing implemented I will remove the data field from the serialized CRYPTO frame output which will help clean things up further.
I will gladly add more detail as it comes together! At this stage, the PR does decrypt the contents of the first packet. This packet, which I've been referring to as the QUIC Client Init, is encrypted using keys derived from a version specific publicly available salt. The plaintext contains QUIC frames that can be parsed to produce a TLS 1.3 ClientHello. The same process can be applied to the QUIC Server Init which will produce a TLS ServerHello. Once the key exchange has been completed the connection uses the new keys and we can no longer see into the packet. |
Here are a couple of links for more details: |
Hi @thearossman, |
Thank you!! I agree re: connection-level quic data making sense. Going to run this in the afternoon when there's more traffic on our network with a few different types of subscribed data (quic, connection/flow features, frames). One high-level question -- is there any point in the QuicStream when the connection could be reasonably considered ParseResult::Done, given that this PR is parsing connection-level data? Being "done" parsing a session, at some point, would help performance. |
I'm glad you asked about that, I thought for a while about how to handle this situation and I'm open to whatever you think makes the most sense. Unfortunately, it is difficult to tell when connection has ended as the frames that the hosts send to close a connection are in the encrypted payload. I decided to always leave the connection open so that additional stats on connection size and shape could be collected. I figured that somewhere further up the stack Retina has a timeout on flows and would eventually consider the connection closed. However, I have noticed an increase in drops after the application has been running on live traffic for a few minutes. So if this is a persistent issue, and related to the lack of connection closure, I see a couple of ways it could be handled:
Let me know what you think and I am happy to make changes accordingly! |
This is really helpful, thank you! I agree with all of this. Two thoughts -- (1)
So, for a QUIC-specific experiment, decreasing the connection timeout (see the config file) makes a lot of sense and is definitely sufficient! (2) Maybe one clarification is that "stop tracking the connection" is different from "stop parsing". The former is dependent on both the filter and the subscribable type. The latter is dependent on the filter only (match_state, nomatch_state, ParseResult). Some examples: (1) If a user filters for TLS and subscribes to a type that is Level::Connection, then the connection will still be tracked until termination or timeout -- that's when the subscribable type is ready to stop tracking the data. You still get reasonable performance because parsing is expensive, and TLS is considered "done parsing" after the handshake. I'm thinking for QUIC, we can stop parsing if we don't think there will be more interesting information (ParseResult::Done) and once we have the first session (match/nomatch state). Lots of info here - does that make sense to you? |
That makes sense to me! Thank you for the clarification, I am still trying to wrap my head around all of the nuances of Retina. The latest commits add a return state for when the first short header packet is seen. This packet can't be sent until at least one of the hosts has completed the handshake. So it will be well after the initial packets are sent. Let me know what you think of this solution. |
This makes sense based on my understanding of QUIC, which is admittedly not super deep. Trusting you for the deeper QUIC expertise and experience with what's useful from QUIC parsing, but the output and perf look good to us. Going to merge! |
Summary
The standard QUIC handshake between two hosts who do not have a pre shared key involves the use of QUIC Initial packets to share a TLS 1.3 client and server hello. After these two messages have been received, the hosts are able to change to new keys unknown to network observers. This PR add the ability to parse QUIC Initial packets, decrypt the protected payloads, parse the QUIC frames, and parse the TLS client and server hello. This gives users the ability to filter QUIC traffic on many of the same features as TLS, such as SNI.
Features
crypto.rs
) for initial key derivation, based on Cloudflare's Quicheframe.rs
QuicConn
for tracking multi-packet QUIC connectionsQuicConn
objectTesting
quic_kyber.pcapng
andquic_xargs.pcap
for testing QUIC parsingtraces/