From 6e86e6d924286491055608850e1df2db0c2322ad Mon Sep 17 00:00:00 2001 From: Ivan Nardi <12729895+IvanNardi@users.noreply.github.com> Date: Sat, 4 Dec 2021 13:29:30 +0100 Subject: QUIC: add support for QUICv2 (draft 00) (#1379) It is already time to start looking at the new QUIC version. See: https://datatracker.ietf.org/doc/html/draft-ietf-quic-v2-00 --- src/lib/protocols/quic.c | 59 +++++++++++++++++++++++++++---------- tests/do.sh.in | 2 +- tests/pcap/quic-v2-00.pcapng | Bin 0 -> 50416 bytes tests/result/quic-v2-00.pcapng.out | 12 ++++++++ 4 files changed, 57 insertions(+), 16 deletions(-) create mode 100644 tests/pcap/quic-v2-00.pcapng create mode 100644 tests/result/quic-v2-00.pcapng.out diff --git a/src/lib/protocols/quic.c b/src/lib/protocols/quic.c index 6bae70524..7d218efe9 100644 --- a/src/lib/protocols/quic.c +++ b/src/lib/protocols/quic.c @@ -83,7 +83,8 @@ static int is_version_quic(uint32_t version) return version == V_1 || ((version & 0xFFFFFF00) == 0xFF000000) /* IETF Drafts*/ || ((version & 0xFFFFF000) == 0xfaceb000) /* Facebook */ || - ((version & 0x0F0F0F0F) == 0x0a0a0a0a) /* Forcing Version Negotiation */; + ((version & 0x0F0F0F0F) == 0x0a0a0a0a) /* Forcing Version Negotiation */ || + ((version & 0xFFFFFF00) == 0xFF020000) /* V2 IETF Drafts */; } static int is_version_valid(uint32_t version) { @@ -112,6 +113,12 @@ static uint8_t get_u8_quic_ver(uint32_t version) only latest drafts... */ if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) return 29; + + /* QUIC Version 2 */ + /* For the time being use 100 + draft as a number for V2 */ + if ((version >> 8) == 0xff0200) + return 100 + (uint8_t)version; + return 0; } #ifdef HAVE_LIBGCRYPT @@ -181,6 +188,15 @@ int is_version_with_ietf_long_header(uint32_t version) ((version & 0xFFFFFF00) == 0x51303500) /* Q05X */ || ((version & 0xFFFFFF00) == 0x54303500) /* T05X */; } +#ifdef HAVE_LIBGCRYPT +int is_version_with_v1_labels(uint32_t version) +{ + if(((version & 0xFFFFFF00) == 0x51303500) /* Q05X */ || + ((version & 0xFFFFFF00) == 0x54303500)) /* T05X */ + return 1; + return is_quic_ver_less_than(version, 33); +} +#endif int quic_len(const uint8_t *buf, uint64_t *value) { @@ -531,29 +547,34 @@ static void quic_ciphers_reset(quic_ciphers *ciphers) * and initialize cipher with the new key. */ static int quic_hp_cipher_init(quic_hp_cipher *hp_cipher, int hash_algo, - uint8_t key_length, uint8_t *secret) + uint8_t key_length, uint8_t *secret, + uint32_t version) { uint8_t hp_key[256/8]; /* Maximum key size is for AES256 cipher. */ uint32_t hash_len = gcry_md_get_algo_dlen(hash_algo); + char *label = is_version_with_v1_labels(version) ? "quic hp" : "quicv2 hp"; - if(!quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic hp", hp_key, key_length)) { + if(!quic_hkdf_expand_label(hash_algo, secret, hash_len, label, hp_key, key_length)) { return 0; } return gcry_cipher_setkey(hp_cipher->hp_cipher, hp_key, key_length) == 0; } static int quic_pp_cipher_init(quic_pp_cipher *pp_cipher, int hash_algo, - uint8_t key_length, uint8_t *secret) + uint8_t key_length, uint8_t *secret, + uint32_t version) { uint8_t write_key[256/8]; /* Maximum key size is for AES256 cipher. */ uint32_t hash_len = gcry_md_get_algo_dlen(hash_algo); + char *key_label = is_version_with_v1_labels(version) ? "quic key" : "quicv2 key"; + char *iv_label = is_version_with_v1_labels(version) ? "quic iv" : "quicv2 iv"; if(key_length > sizeof(write_key)) { return 0; } - if(!quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic key", write_key, key_length) || - !quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic iv", pp_cipher->pp_iv, sizeof(pp_cipher->pp_iv))) { + if(!quic_hkdf_expand_label(hash_algo, secret, hash_len, key_label, write_key, key_length) || + !quic_hkdf_expand_label(hash_algo, secret, hash_len, iv_label, pp_cipher->pp_iv, sizeof(pp_cipher->pp_iv))) { return 0; } @@ -579,7 +600,7 @@ static int quic_get_pn_cipher_algo(int cipher_algo, int *hp_cipher_mode) * If the optional base secret is given, then its length MUST match the hash * algorithm output. */ -static int quic_hp_cipher_prepare(quic_hp_cipher *hp_cipher, int hash_algo, int cipher_algo, uint8_t *secret) +static int quic_hp_cipher_prepare(quic_hp_cipher *hp_cipher, int hash_algo, int cipher_algo, uint8_t *secret, u_int32_t version) { #if 0 /* Clear previous state (if any). */ @@ -604,7 +625,7 @@ static int quic_hp_cipher_prepare(quic_hp_cipher *hp_cipher, int hash_algo, int if(secret) { uint32_t cipher_keylen = (uint8_t)gcry_cipher_get_algo_keylen(cipher_algo); - if(!quic_hp_cipher_init(hp_cipher, hash_algo, cipher_keylen, secret)) { + if(!quic_hp_cipher_init(hp_cipher, hash_algo, cipher_keylen, secret, version)) { quic_hp_cipher_reset(hp_cipher); #ifdef DEBUG_CRYPT printf("Failed to derive key material for HP cipher\n"); @@ -615,7 +636,7 @@ static int quic_hp_cipher_prepare(quic_hp_cipher *hp_cipher, int hash_algo, int return 1; } -static int quic_pp_cipher_prepare(quic_pp_cipher *pp_cipher, int hash_algo, int cipher_algo, int cipher_mode, uint8_t *secret) +static int quic_pp_cipher_prepare(quic_pp_cipher *pp_cipher, int hash_algo, int cipher_algo, int cipher_mode, uint8_t *secret, u_int32_t version) { #if 0 /* Clear previous state (if any). */ @@ -632,7 +653,7 @@ static int quic_pp_cipher_prepare(quic_pp_cipher *pp_cipher, int hash_algo, int if(secret) { uint32_t cipher_keylen = (uint8_t)gcry_cipher_get_algo_keylen(cipher_algo); - if(!quic_pp_cipher_init(pp_cipher, hash_algo, cipher_keylen, secret)) { + if(!quic_pp_cipher_init(pp_cipher, hash_algo, cipher_keylen, secret, version)) { quic_pp_cipher_reset(pp_cipher); #ifdef DEBUG_CRYPT printf("Failed to derive key material for PP cipher\n"); @@ -643,10 +664,10 @@ static int quic_pp_cipher_prepare(quic_pp_cipher *pp_cipher, int hash_algo, int return 1; } -static int quic_ciphers_prepare(quic_ciphers *ciphers, int hash_algo, int cipher_algo, int cipher_mode, uint8_t *secret) +static int quic_ciphers_prepare(quic_ciphers *ciphers, int hash_algo, int cipher_algo, int cipher_mode, uint8_t *secret, u_int32_t version) { - return quic_hp_cipher_prepare(&ciphers->hp_cipher, hash_algo, cipher_algo, secret) && - quic_pp_cipher_prepare(&ciphers->pp_cipher, hash_algo, cipher_algo, cipher_mode, secret); + return quic_hp_cipher_prepare(&ciphers->hp_cipher, hash_algo, cipher_algo, secret, version) && + quic_pp_cipher_prepare(&ciphers->pp_cipher, hash_algo, cipher_algo, cipher_mode, secret, version); } /** * Given a header protection cipher, a buffer and the packet number offset, @@ -864,6 +885,10 @@ static int quic_derive_initial_secrets(uint32_t version, 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a }; + static const uint8_t handshake_salt_v2_draft_00[20] = { + 0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, + 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3 + }; gcry_error_t err; uint8_t secret[HASH_SHA2_256_LENGTH]; #ifdef DEBUG_CRYPT @@ -894,10 +919,14 @@ static int quic_derive_initial_secrets(uint32_t version, err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_29, sizeof(handshake_salt_draft_29), cid, cid_len, secret); - } else { + } else if (is_quic_ver_less_than(version, 33)) { err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_v1, sizeof(handshake_salt_v1), cid, cid_len, secret); + } else { + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_v2_draft_00, + sizeof(handshake_salt_v2_draft_00), + cid, cid_len, secret); } if(err) { #ifdef DEBUG_CRYPT @@ -947,7 +976,7 @@ static uint8_t *decrypt_initial_packet(struct ndpi_detection_module_struct *ndpi Initial packets are protected with AEAD_AES_128_GCM. */ if(!quic_ciphers_prepare(&ciphers, GCRY_MD_SHA256, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, - client_secret)) { + client_secret, version)) { NDPI_LOG_DBG(ndpi_struct, "Error quic_cipher_prepare\n"); return NULL; } diff --git a/tests/do.sh.in b/tests/do.sh.in index d04b483b6..692f432be 100755 --- a/tests/do.sh.in +++ b/tests/do.sh.in @@ -16,7 +16,7 @@ fi GCRYPT_ENABLED=@GCRYPT_ENABLED@ PCRE_ENABLED=@PCRE_ENABLED@ PCRE_PCAPS="WebattackRCE.pcap" -GCRYPT_PCAPS="gquic.pcap quic-23.pcap quic-24.pcap quic-27.pcap quic-28.pcap quic-29.pcap quic-mvfst-22.pcap quic-mvfst-27.pcapng quic-mvfst-exp.pcap quic_q50.pcap quic_t50.pcap quic_t51.pcap quic_0RTT.pcap quic_interop_V.pcapng quic-33.pcapng doq.pcapng doq_adguard.pcapng dlt_ppp.pcap os_detected.pcapng quic_frags_ch_out_of_order_same_packet_craziness.pcapng quic_frags_ch_in_multiple_packets.pcapng" +GCRYPT_PCAPS="gquic.pcap quic-23.pcap quic-24.pcap quic-27.pcap quic-28.pcap quic-29.pcap quic-mvfst-22.pcap quic-mvfst-27.pcapng quic-mvfst-exp.pcap quic_q50.pcap quic_t50.pcap quic_t51.pcap quic_0RTT.pcap quic_interop_V.pcapng quic-33.pcapng doq.pcapng doq_adguard.pcapng dlt_ppp.pcap os_detected.pcapng quic_frags_ch_out_of_order_same_packet_craziness.pcapng quic_frags_ch_in_multiple_packets.pcapng quic-v2-00.pcapng" READER="$VALGRIND ../example/ndpiReader -p ../example/protos.txt -c ../example/categories.txt -r ../example/risky_domains.txt -j ../example/ja3_fingerprints.csv -S ../example/sha1_fingerprints.csv" RC=0 diff --git a/tests/pcap/quic-v2-00.pcapng b/tests/pcap/quic-v2-00.pcapng new file mode 100644 index 000000000..146d3c935 Binary files /dev/null and b/tests/pcap/quic-v2-00.pcapng differ diff --git a/tests/result/quic-v2-00.pcapng.out b/tests/result/quic-v2-00.pcapng.out new file mode 100644 index 000000000..18c7c03e3 --- /dev/null +++ b/tests/result/quic-v2-00.pcapng.out @@ -0,0 +1,12 @@ +Guessed flow protos: 0 + +DPI Packets (UDP): 1 (1.00 pkts/flow) + +QUIC 30 27593 1 + +JA3 Host Stats: + IP Address # JA3C + 1 192.168.56.1 1 + + + 1 UDP 192.168.56.1:50277 <-> 192.168.56.198:4443 [proto: 188/QUIC][Encrypted][cat: Web/5][11 pkts/5450 bytes <-> 19 pkts/22143 bytes][Goodput ratio: 92/96][0.01 sec][ALPN: h3-34;hq-34;h3-33;hq-33;h3-32;hq-32;h3-31;hq-31;h3-29;hq-29;h3-30;hq-30;h3-28;hq-28;h3-27;hq-27;h3;hq-interop][TLS Supported Versions: TLSv1.3;TLSv1.3 (draft);TLSv1.3 (draft);TLSv1.3 (draft)][bytes ratio: -0.605 (Download)][IAT c2s/s2c min/avg/max/stddev: 0/0 0/0 3/2 1/1][Pkt Len c2s/s2c min/avg/max/stddev: 97/97 495/1165 1482/1482 539/528][Risk: ** Known protocol on non standard port **** SNI TLS extension was missing **][Risk Score: 100][TLSv1.3][JA3C: 0299b052ace53a14c3a04aceb5efd247][PLAIN TEXT (anezfN)][Plen Bins: 0,23,3,0,0,6,0,0,0,0,0,0,3,3,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,10,0,0,0,0,0,47,0,0] -- cgit v1.2.3