diff options
author | Ivan Nardi <12729895+IvanNardi@users.noreply.github.com> | 2024-01-24 09:57:28 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2024-01-24 09:57:28 +0100 |
commit | 7a83a8dc9122a730a74e5ac644413ae87f94e563 (patch) | |
tree | 4e387ad79b4301cddaa1b473e86a804a24af188b /src/lib/protocols/quic.c | |
parent | f04b4450a18e53cb53dbd08750eda518b0aeda4c (diff) |
QUIC: fix decryption with CH fragments with different Destination CID (#2278)
QUIC decryption fails when the Client Hello is split into multiple UDP
packets and these packets have different Destination Connection IDs
(because the server told the client to switch to a different CID; see
RFC 9000 7.2)
```
The Destination Connection ID field from the first Initial packet sent by
a client is used to determine packet protection keys for Initial packets.
[..]
Upon first receiving an Initial or Retry packet from the server, the
client uses the Source Connection ID supplied by the server as the
Destination Connection ID for subsequent packets
```
From a logical point of view, the ciphers used for decryption should be
initialized only once, with the first Initial pkt sent by the client and
kept for later usage with the following packets (if any).
However it seems that we can safely initialize them at each packet, if
we keep using the DCID of the **first** packet sent by the client.
Keep initializing the ciphers at each packet greatly simplifie this patch.
This issue has been undetected for so long because:
* in the vast majority of the cases we only decrypt one packet per flow;
* the available traces with the Client Hello split into multiple packets
(i.e. cases where we need to decrypt at least two packets per flow) were
created in a simple test environment to simulate Post-Quantum handshake,
and in that scenario the client sent all the packets (with the same
DCID) before any reply from the server.
However, in the last months all major browsers started supporting PQ
key, so it is now common to have split CH in real traffic.
Please note that in the attached example, the CH is split into 2
(in-order) fragments (in different UDP packets) and the second one in
turn is divided into 9 (out-of-order) CRYPTO frames; the reassembler
code works out-of-the-box even in this (new) scenario.
Diffstat (limited to 'src/lib/protocols/quic.c')
-rw-r--r-- | src/lib/protocols/quic.c | 18 |
1 files changed, 16 insertions, 2 deletions
diff --git a/src/lib/protocols/quic.c b/src/lib/protocols/quic.c index 0b8674f38..aafb365fe 100644 --- a/src/lib/protocols/quic.c +++ b/src/lib/protocols/quic.c @@ -980,6 +980,7 @@ static int quic_derive_initial_secrets(struct ndpi_detection_module_struct *ndpi static uint8_t *decrypt_initial_packet(struct ndpi_detection_module_struct *ndpi_struct, + const uint8_t *orig_dest_conn_id, uint8_t orig_dest_conn_id_len, const uint8_t *dest_conn_id, uint8_t dest_conn_id_len, uint8_t source_conn_id_len, uint32_t version, uint32_t *clear_payload_len) @@ -993,7 +994,7 @@ static uint8_t *decrypt_initial_packet(struct ndpi_detection_module_struct *ndpi uint8_t client_secret[HASH_SHA2_256_LENGTH]; memset(&ciphers, '\0', sizeof(ciphers)); - if(quic_derive_initial_secrets(ndpi_struct, version, dest_conn_id, dest_conn_id_len, + if(quic_derive_initial_secrets(ndpi_struct, version, orig_dest_conn_id, orig_dest_conn_id_len, client_secret) != 0) { NDPI_LOG_DBG(ndpi_struct, "Error quic_derive_initial_secrets\n"); return NULL; @@ -1320,6 +1321,7 @@ const uint8_t *get_crypto_data(struct ndpi_detection_module_struct *ndpi_struct, } static uint8_t *get_clear_payload(struct ndpi_detection_module_struct *ndpi_struct, + struct ndpi_flow_struct *flow, uint32_t version, uint32_t *clear_payload_len) { struct ndpi_packet_struct *packet = &ndpi_struct->packet; @@ -1355,7 +1357,19 @@ static uint8_t *get_clear_payload(struct ndpi_detection_module_struct *ndpi_stru source_conn_id_len = packet->payload[6 + dest_conn_id_len]; const u_int8_t *dest_conn_id = &packet->payload[6]; + + /* For initializing the ciphers we need the DCID of the very first Initial + sent by the client. This is quite important when CH is fragmented into multiple + packets and these packets have different DCID */ + if(flow->l4.udp.quic_orig_dest_conn_id_len == 0) { + memcpy(flow->l4.udp.quic_orig_dest_conn_id, + dest_conn_id, dest_conn_id_len); + flow->l4.udp.quic_orig_dest_conn_id_len = dest_conn_id_len; + } + clear_payload = decrypt_initial_packet(ndpi_struct, + flow->l4.udp.quic_orig_dest_conn_id, + flow->l4.udp.quic_orig_dest_conn_id_len, dest_conn_id, dest_conn_id_len, source_conn_id_len, version, clear_payload_len); @@ -1943,7 +1957,7 @@ static void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct, /* * 4) Extract the Payload from Initial Packets */ - clear_payload = get_clear_payload(ndpi_struct, version, &clear_payload_len); + clear_payload = get_clear_payload(ndpi_struct, flow, version, &clear_payload_len); if(!clear_payload) { NDPI_EXCLUDE_PROTO(ndpi_struct, flow); return; |