From 59638765e3eacdf0eac3d96e7941fb710d3a0590 Mon Sep 17 00:00:00 2001 From: liuxueli Date: Fri, 3 Sep 2021 14:51:29 +0800 Subject: TSG-7627: 解析加密GQUIC048 SNI导致watchdog timeout MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gquic_process.cpp | 35 ++- src/parser-quic.cpp | 813 -------------------------------------------------- src/parser-quic.h | 24 -- src/parser_quic.cpp | 813 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/parser_quic.h | 24 ++ src/quic_analysis.cpp | 9 + src/quic_analysis.h | 1 + 7 files changed, 880 insertions(+), 839 deletions(-) delete mode 100644 src/parser-quic.cpp delete mode 100644 src/parser-quic.h create mode 100644 src/parser_quic.cpp create mode 100644 src/parser_quic.h (limited to 'src') diff --git a/src/gquic_process.cpp b/src/gquic_process.cpp index e84e7eb..2f3ce62 100644 --- a/src/gquic_process.cpp +++ b/src/gquic_process.cpp @@ -13,7 +13,7 @@ #include "gquic_process.h" #include "quic_analysis.h" -#include "parser-quic.h" +#include "parser_quic.h" int is_iquic(enum _QUIC_VERSION quic_version) { @@ -60,6 +60,32 @@ int is_iquic(enum _QUIC_VERSION quic_version) return FALSE; } +int is_quic_port(struct streaminfo *pstream) +{ + switch(pstream->addr.addrtype) + { + case ADDR_TYPE_IPV4: + case __ADDR_TYPE_IP_PAIR_V4: + if(ntohs(pstream->addr.ipv4->source)!=443 && ntohs(pstream->addr.ipv4->dest)!=443) + { + return 0; + } + break; + case ADDR_TYPE_IPV6: + case __ADDR_TYPE_IP_PAIR_V6: + if(ntohs(pstream->addr.ipv6->source)!=443 && ntohs(pstream->addr.ipv6->dest)!=443) + { + return 0; + } + break; + default: + return 0; + break; + } + + return 1; +} + static int get_value(unsigned char *payload, int *offset, int len) { switch(len) @@ -1108,7 +1134,7 @@ int quic_process(struct streaminfo *pstream, struct _quic_context* _context, int break; default: if( ((is_gquic>=MVFST_VERSION_00 && is_gquic<=MVFST_VERSION_0F) || - (is_gquic>=GQUIC_VERSION_Q047 && is_gquic<=GQUIC_VERSION_Q059) || + (is_gquic>=GQUIC_VERSION_Q049 && is_gquic<=GQUIC_VERSION_Q059) || (is_gquic>=GQUIC_VERSION_T050 && is_gquic<=GQUIC_VERSION_T059) || (is_gquic>=GQUIC_VERSION_T050 && is_gquic<=GQUIC_VERSION_T059) || (is_gquic>=IQUIC_VERSION_I022 && is_gquic<=IQUIC_VERSION_I029) || @@ -1166,6 +1192,11 @@ int quic_protocol_identify(struct streaminfo *a_stream, void *a_packet, char *ou void *pme=NULL; char *sni=NULL; struct _quic_context *_context=NULL; + + if(!is_quic_port(a_stream)) + { + return len; + } quic_init_stream(&pme, a_stream->threadnum); _context=(struct _quic_context *)pme; diff --git a/src/parser-quic.cpp b/src/parser-quic.cpp deleted file mode 100644 index 421e06e..0000000 --- a/src/parser-quic.cpp +++ /dev/null @@ -1,813 +0,0 @@ -/** - * parser-quic.c - * - * Created on 2020-11-26 - * @author: qyc - * - * @explain: QUIC解析 - */ -#include -#include -#include - -#include "parser-quic.h" -#include "wsgcrypt.h" -#include "utils.h" -#include "pint.h" -#include "gcrypt.h" - -// #define DEBUG_PARSER_QUIC - -int gcry_init() -{ - gcry_check_version("1.8.7"); - //gcry_control(GCRYCTL_SET_THREAD_CBS,&gcry_threads_pthread); - return 0; -} - - -#define QUIC_LPT_INITIAL 0x0 -#define QUIC_LPT_0RTT 0x1 -#define QUIC_LPT_HANDSHAKE 0x2 -#define QUIC_LPT_RETRY 0x3 -/* Version Negotiation packets don't have any real packet type */ -#define QUIC_LPT_VER_NEG 0xfe -/* dummy value that is definitely not LPT */ -#define QUIC_SHORT_PACKET 0xff - - -/* - * Although the QUIC SCID/DCID length field can store at most 255, v1 limits the - * CID length to 20. - */ -#define QUIC_MAX_CID_LENGTH 20 -typedef struct _quic_cid { - unsigned char len; - unsigned char cid[QUIC_MAX_CID_LENGTH]; -} quic_cid_t; - -/* - * PROTECTED PAYLOAD DECRYPTION (done in first pass) - * - * Long packet types always use a single cipher depending on packet type. - * Short packet types always use 1-RTT secrets for packet protection (pp). - * - * Considerations: - * - QUIC packets might appear out-of-order (short packets before handshake - * message is captured), lost or retransmitted/duplicated. - * - During live capture, keys might not be immediately be available. 1-RTT - * client keys will be ready while client proceses Server Hello (Handshake). - * 1-RTT server keys will be ready while server creates Handshake message in - * response to Initial Handshake. - * - So delay cipher creation until first short packet is received. - * - * Required input from TLS dissector: TLS-Exporter 0-RTT/1-RTT secrets and - * cipher/hash algorithms. - * - * QUIC payload decryption requires proper reconstruction of the packet number - * which requires proper header decryption. The different states are: - * - * Packet type Packet number space Secrets - * Long: Initial Initial Initial secrets - * Long: Handshake Handshake Handshake - * Long: 0-RTT 0/1-RTT (appdata) 0-RTT - * Short header 0/1-RTT (appdata) 1-RTT (KP0 / KP1) - * - * Important to note is that Short Header decryption requires TWO ciphers (one - * for each key phase), but that header protection uses only KP0. Total state - * needed for each peer (client and server): - * - 3 packet number spaces: Initial, Handshake, 0/1-RTT (appdata). - * - 4 header protection ciphers: initial, 0-RTT, HS, 1-RTT. - * - 5 payload protection ciphers: initial, 0-RTT, HS, 1-RTT (KP0), 1-RTT (KP1). - */ - -typedef struct _quic_decrypt_result { - // Error message or NULL for success. - const guchar *error; - // Decrypted result on success (file-scoped). - const guint8 *data; - // Size of decrypted data. - guint data_len; -} quic_decrypt_result_t; - -/** QUIC decryption context. */ - -typedef struct _quic_hp_cipher { - // Header protection cipher. - gcry_cipher_hd_t hp_cipher; -} quic_hp_cipher; - -typedef struct _quic_pp_cipher { - // Packet protection cipher. - gcry_cipher_hd_t pp_cipher; - guint8 pp_iv[TLS13_AEAD_NONCE_LENGTH]; -} quic_pp_cipher; - -typedef struct _quic_ciphers { - quic_hp_cipher hp_cipher; - quic_pp_cipher pp_cipher; -} quic_ciphers; - -/** - * State for a single QUIC connection, identified by one or more Destination - * Connection IDs (DCID). - */ -typedef struct _quic_info_data { - guint32 version; - quic_ciphers client_initial_ciphers; - quic_ciphers server_initial_ciphers; - // Packet number spaces for Initial, Handshake and appdata. - guint64 max_client_pkn[3]; - guint64 max_server_pkn[3]; -} quic_info_data_t; - -/** Per-packet information about QUIC, populated on the first pass. */ -typedef struct _quic_packet_info { - // Reconstructed full packet number. - guint64 packet_number; - quic_decrypt_result_t decryption; - // Length of PKN (1/2/3/4) or unknown (0). - guint8 pkn_len; - // Decrypted flag byte, valid only if pkn_len is non-zero. - guint8 first_byte; -} quic_packet_info_t; - -/** - * Given a QUIC message (header + non-empty payload), the actual packet number, - * try to decrypt it using the PP cipher. - * As the header points to the original buffer with an encrypted packet number, - * the (encrypted) packet number length is also included. - * - * The actual packet number must be constructed according to - * https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-12.3 - */ -static void quic_decrypt_message(quic_pp_cipher *pp_cipher, const char *payload, guint length, guint header_length, - guint8 first_byte, guint pkn_len, guint64 packet_number, quic_decrypt_result_t *result) -{ - gcry_error_t err; - guint8 *header; - guint8 nonce[TLS13_AEAD_NONCE_LENGTH]; - guint8 *buffer; - guint8 atag[16]; - guint buffer_length; - const guchar **error = &result->error; - - g_assert(pp_cipher != NULL); - g_assert(pp_cipher->pp_cipher != NULL); - g_assert(pkn_len < header_length); - g_assert(1 <= pkn_len && pkn_len <= 4); - // copy header, but replace encrypted first byte and PKN by plaintext. - header = (guint8 *)g_malloc(header_length); - memcpy(header, payload, header_length); - header[0] = first_byte; - guint i; - for (i = 0; i < pkn_len; i++) - header[header_length - 1 - i] = (guint8)(packet_number >> (8 * i)); - - // Input is "header || ciphertext (buffer) || auth tag (16 bytes)" - // buffer_length = length - (header_length + 16); - // buffer_length = 297 - (2 + 16); - buffer_length = length - (pkn_len + 16); - if (buffer_length == 0) { - *error = (const guchar *)"Decryption not possible, ciphertext is too short"; - return; - } - buffer = (guint8 *)g_malloc(buffer_length); - memcpy(buffer, payload + header_length, buffer_length); - memcpy(atag, payload + header_length + buffer_length, 16); - - memcpy(nonce, pp_cipher->pp_iv, TLS13_AEAD_NONCE_LENGTH); - // Packet number is left-padded with zeroes and XORed with write_iv - phton64(nonce + sizeof(nonce) - 8, pntoh64(nonce + sizeof(nonce) - 8) ^ packet_number); - - gcry_cipher_reset(pp_cipher->pp_cipher); - err = gcry_cipher_setiv(pp_cipher->pp_cipher, nonce, TLS13_AEAD_NONCE_LENGTH); - if (err) { - //printf("Decryption (setiv) failed: %s\n", gcry_strerror(err)); - *error = (const guchar *)"Decryption (setiv) failed"; - return; - } - - // associated data (A) is the contents of QUIC header - err = gcry_cipher_authenticate(pp_cipher->pp_cipher, header, header_length); - if (err) { - //printf("Decryption (authenticate) failed: %s\n", gcry_strerror(err)); - *error = (const guchar *)"Decryption (authenticate) failed"; - return; - } - - // Output ciphertext (C) - err = gcry_cipher_decrypt(pp_cipher->pp_cipher, buffer, buffer_length, NULL, 0); - if (err) { - //printf("Decryption (decrypt) failed: %s\n", gcry_strerror(err)); - *error = (const guchar *)"Decryption (decrypt) failed"; - return; - } - - err = gcry_cipher_checktag(pp_cipher->pp_cipher, atag, 16); - if (err) { - //printf("Decryption (checktag) failed: %s\n", gcry_strerror(err)); - *error = (const guchar *)"Decryption (checktag) failed"; - return; - } - - g_free(header); - - result->error = NULL; - result->data = buffer; - result->data_len = buffer_length; -} - -static gboolean quic_is_pp_cipher_initialized(quic_pp_cipher *pp_cipher) -{ - return pp_cipher && pp_cipher->pp_cipher; -} - -/** - * Process (protected) payload, adding the encrypted payload to the tree. If - * decryption is possible, frame dissection is also attempted. - * - * The given offset must correspond to the end of the QUIC header and begin of - * the (protected) payload. Dissected frames are appended to "tree" and expert - * info is attached to "ti" (the field with the encrypted payload). - */ -static void quic_process_payload(const char *payload, guint length, guint offset, quic_info_data_t *quic_info, - quic_packet_info_t *quic_packet, gboolean from_server, quic_pp_cipher *pp_cipher, guint8 first_byte, guint pkn_len) -{ - /* - * If no decryption error has occurred yet, try decryption on the first - * pass and store the result for later use. - */ - if (quic_is_pp_cipher_initialized(pp_cipher)) - quic_decrypt_message(pp_cipher, payload, length, offset, first_byte, pkn_len, quic_packet->packet_number, &quic_packet->decryption); -} - -/* Inspired from ngtcp2 */ -static guint64 quic_pkt_adjust_pkt_num(guint64 max_pkt_num, guint64 pkt_num, size_t n) -{ - guint64 k = max_pkt_num == G_MAXUINT64 ? max_pkt_num : max_pkt_num + 1; - guint64 u = k & ~((G_GUINT64_CONSTANT(1) << n) - 1); - guint64 a = u | pkt_num; - guint64 b = (u + (G_GUINT64_CONSTANT(1) << n)) | pkt_num; - guint64 a1 = k < a ? a - k : k - a; - guint64 b1 = k < b ? b - k : k - b; - - if (a1 < b1) - return a; - return b; -} - -/** - * Retrieve the maximum valid packet number space for a peer. - */ -static guint64 *quic_max_packet_number(quic_info_data_t *quic_info, gboolean from_server, guint8 first_byte) -{ - int pkn_space; - if ((first_byte & 0x80) && (first_byte & 0x30) >> 4 == QUIC_LPT_INITIAL) - // Long header, Initial - pkn_space = 0; - else if ((first_byte & 0x80) && (first_byte & 0x30) >> 4 == QUIC_LPT_HANDSHAKE) - // Long header, Handshake - pkn_space = 1; - else - // Long header (0-RTT) or Short Header (1-RTT appdata). - pkn_space = 2; - if (from_server) - return &quic_info->max_server_pkn[pkn_space]; - else - return &quic_info->max_client_pkn[pkn_space]; -} - -/** - * Calculate the full packet number and store it for later use. - */ -static void quic_set_full_packet_number(quic_info_data_t *quic_info, quic_packet_info_t *quic_packet, gboolean from_server, guint8 first_byte, guint32 pkn32) -{ - guint pkn_len = (first_byte & 3) + 1; - guint64 pkn_full; - guint64 max_pn = *quic_max_packet_number(quic_info, from_server, first_byte); - - // Sequential first pass, try to reconstruct full packet number. - pkn_full = quic_pkt_adjust_pkt_num(max_pn, pkn32, 8 * pkn_len); - quic_packet->pkn_len = pkn_len; - quic_packet->packet_number = pkn_full; -} - -/** - * Given a header protection cipher, a buffer and the packet number offset, - * return the unmasked first byte and packet number. - */ -static gboolean quic_decrypt_header(const char *payload, guint pn_offset, quic_hp_cipher *hp_cipher, int hp_cipher_algo, guint8 *first_byte, guint32 *pn) -{ - if (!hp_cipher->hp_cipher) - // need to know the cipher. - return FALSE; - gcry_cipher_hd_t h = hp_cipher->hp_cipher; - - // Sample is always 16 bytes and starts after PKN (assuming length 4). - // https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.2 - guint8 sample[16]; - memcpy(sample, payload + pn_offset + 4, 16); - - guint8 mask[5] = { 0 }; - switch (hp_cipher_algo) { - case GCRY_CIPHER_AES128: - case GCRY_CIPHER_AES256: - // Encrypt in-place with AES-ECB and extract the mask. - if (gcry_cipher_encrypt(h, sample, sizeof(sample), NULL, 0)) - return FALSE; - memcpy(mask, sample, sizeof(mask)); - break; -#ifdef HAVE_LIBGCRYPT_CHACHA20 - case GCRY_CIPHER_CHACHA20: - // If Gcrypt receives a 16 byte IV, it will assume the buffer to be - // counter || nonce (in little endian), as desired. */ - if (gcry_cipher_setiv(h, sample, 16)) - return FALSE; - // Apply ChaCha20, encrypt in-place five zero bytes. - if (gcry_cipher_encrypt(h, mask, sizeof(mask), NULL, 0)) - return FALSE; - break; -#endif // HAVE_LIBGCRYPT_CHACHA20 - default: - return FALSE; - } - - // https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.1 - guint8 packet0 = payload[0]; - if ((packet0 & 0x80) == 0x80) - // Long header: 4 bits masked - packet0 ^= mask[0] & 0x0f; - else - // Short header: 5 bits masked - packet0 ^= mask[0] & 0x1f; - guint pkn_len = (packet0 & 0x03) + 1; - - guint8 pkn_bytes[4]; - memcpy(pkn_bytes, payload + pn_offset, pkn_len); - guint32 pkt_pkn = 0; - guint i; - for (i = 0; i < pkn_len; i++) - pkt_pkn |= (pkn_bytes[i] ^ mask[1 + i]) << (8 * (pkn_len - 1 - i)); - *first_byte = packet0; - *pn = pkt_pkn; - - return TRUE; -} - -static gboolean quic_hkdf_expand_label(int hash_algo, guint8 *secret, guint secret_len, const char *label, guint8 *out, guint out_len) -{ - const StringInfo secret_si = { secret, secret_len }; - guchar *out_mem = NULL; - - if (tls13_hkdf_expand_label(hash_algo, &secret_si, "tls13 ", label, out_len, &out_mem)) { - memcpy(out, out_mem, out_len); - g_free(out_mem); - return TRUE; - } - - return FALSE; -} - -/** - * Expands the secret (length MUST be the same as the "hash_algo" digest size) - * and initialize cipher with the new key. - */ -static gboolean quic_hp_cipher_init(quic_hp_cipher *hp_cipher, int hash_algo, guint8 key_length, guint8 *secret) -{ - guchar hp_key[256/8]; - guint hash_len = gcry_md_get_algo_dlen(hash_algo); - - if (!quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic hp", hp_key, key_length)) - return FALSE; - - return gcry_cipher_setkey(hp_cipher->hp_cipher, hp_key, key_length) == 0; -} - -static gboolean quic_pp_cipher_init(quic_pp_cipher *pp_cipher, int hash_algo, guint8 key_length, guint8 *secret) -{ - // Maximum key size is for AES256 cipher. - guchar write_key[256/8]; - guint hash_len = gcry_md_get_algo_dlen(hash_algo); - - if (key_length > sizeof(write_key)) - return FALSE; - - 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))) - return FALSE; - - return gcry_cipher_setkey(pp_cipher->pp_cipher, write_key, key_length) == 0; -} - -static void quic_hp_cipher_reset(quic_hp_cipher *hp_cipher) -{ - gcry_cipher_close(hp_cipher->hp_cipher); - memset(hp_cipher, 0, sizeof(*hp_cipher)); -} - -static void quic_pp_cipher_reset(quic_pp_cipher *pp_cipher) -{ - gcry_cipher_close(pp_cipher->pp_cipher); - memset(pp_cipher, 0, sizeof(*pp_cipher)); -} - -/** - * Maps a Packet Protection cipher to the Packet Number protection cipher. - * See https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.3 - */ -static gboolean quic_get_pn_cipher_algo(int cipher_algo, int *hp_cipher_mode) -{ - switch (cipher_algo) { - case GCRY_CIPHER_AES128: - case GCRY_CIPHER_AES256: - *hp_cipher_mode = GCRY_CIPHER_MODE_ECB; - return TRUE; -#ifdef HAVE_LIBGCRYPT_CHACHA20 - case GCRY_CIPHER_CHACHA20: - *hp_cipher_mode = GCRY_CIPHER_MODE_STREAM; - return TRUE; -#endif // HAVE_LIBGCRYPT_CHACHA20 - default: - return FALSE; - } -} - -/* - * (Re)initialize the PNE/PP ciphers using the given cipher algorithm. - * If the optional base secret is given, then its length MUST match the hash - * algorithm output. - */ -static gboolean quic_hp_cipher_prepare(quic_hp_cipher *hp_cipher, int hash_algo, int cipher_algo, guint8 *secret, const char **error) -{ - // Clear previous state (if any). - quic_hp_cipher_reset(hp_cipher); - - int hp_cipher_mode; - if (!quic_get_pn_cipher_algo(cipher_algo, &hp_cipher_mode)) { - *error = "Unsupported cipher algorithm"; - return FALSE; - } - - if (gcry_cipher_open(&hp_cipher->hp_cipher, cipher_algo, hp_cipher_mode, 0)) { - quic_hp_cipher_reset(hp_cipher); - *error = "Failed to create HP cipher"; - return FALSE; - } - - if (secret) { - guint cipher_keylen = (guint8)gcry_cipher_get_algo_keylen(cipher_algo); - if (!quic_hp_cipher_init(hp_cipher, hash_algo, cipher_keylen, secret)) { - quic_hp_cipher_reset(hp_cipher); - *error = "Failed to derive key material for HP cipher"; - return FALSE; - } - } - - return TRUE; -} - -static gboolean quic_pp_cipher_prepare(quic_pp_cipher *pp_cipher, int hash_algo, int cipher_algo, int cipher_mode, guint8 *secret, const char **error) -{ - // Clear previous state (if any). - quic_pp_cipher_reset(pp_cipher); - - int hp_cipher_mode; - if (!quic_get_pn_cipher_algo(cipher_algo, &hp_cipher_mode)) { - *error = "Unsupported cipher algorithm"; - return FALSE; - } - - if (gcry_cipher_open(&pp_cipher->pp_cipher, cipher_algo,cipher_mode, 0)) { - quic_pp_cipher_reset(pp_cipher); - *error = "Failed to create PP cipher"; - return FALSE; - } - - if (secret) { - guint cipher_keylen = (guint8) gcry_cipher_get_algo_keylen(cipher_algo); - if (!quic_pp_cipher_init(pp_cipher, hash_algo, cipher_keylen, secret)) { - quic_pp_cipher_reset(pp_cipher); - *error = "Failed to derive key material for PP cipher"; - return FALSE; - } - } - - return TRUE; -} - -static gboolean quic_ciphers_prepare(quic_ciphers *ciphers, int hash_algo, int cipher_algo, int cipher_mode, guint8 *secret, const char **error) -{ - return quic_hp_cipher_prepare(&ciphers->hp_cipher, hash_algo, cipher_algo, secret, error) && - quic_pp_cipher_prepare(&ciphers->pp_cipher, hash_algo, cipher_algo, cipher_mode, secret, error); -} - -/* Returns the QUIC draft version or 0 if not applicable. */ -static inline guint8 quic_draft_version(guint32 version) { - if ((version >> 8) == 0xff0000) - return (guint8) version; - - // Facebook mvfst, based on draft -22. - if (version == 0xfaceb001) - return 22; - - // Facebook mvfst, based on draft -27. - if (version == 0xfaceb002 || version == 0xfaceb00e) - return 27; - - // GQUIC Q050, T050 and T051: they are not really based on any drafts, - // but we must return a sensible value - if (version == 0x51303530 || version == 0x54303530 || version == 0x54303531) - return 27; - - /* - * https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15 - * "Versions that follow the pattern 0x?a?a?a?a are reserved for use in - * forcing version negotiation to be exercised" - * It is tricky to return a correct draft version: such number is primarly - * used to select a proper salt (which depends on the version itself), but - * we don't have a real version here! Let's hope that we need to handle - * only latest drafts... - */ - if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) - return 29; - - return 0; -} - -static inline gboolean is_quic_draft_max(guint32 version, guint8 max_version) { - guint8 draft_version = quic_draft_version(version); - return draft_version && draft_version <= max_version; -} - -/** - * Compute the client and server initial secrets given Connection ID "cid". - * - * On success TRUE is returned and the two initial secrets are set. - * FALSE is returned on error (see "error" parameter for the reason). - */ -static gboolean quic_derive_initial_secrets(const quic_cid_t *cid, guint8 client_initial_secret[HASH_SHA2_256_LENGTH], guint8 server_initial_secret[HASH_SHA2_256_LENGTH], guint32 version, const gchar **error) -{ - /* - * https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2 - * - * initial_salt = 0xafbfec289993d24c9e9786f19c6111e04390a899 - * initial_secret = HKDF-Extract(initial_salt, client_dst_connection_id) - * - * client_initial_secret = HKDF-Expand-Label(initial_secret, - * "client in", "", Hash.length) - * server_initial_secret = HKDF-Expand-Label(initial_secret, - * "server in", "", Hash.length) - * - * Hash for handshake packets is SHA-256 (output size 32). - */ - static const guint8 handshake_salt_draft_22[20] = { - 0x7f, 0xbc, 0xdb, 0x0e, 0x7c, 0x66, 0xbb, 0xe9, 0x19, 0x3a, - 0x96, 0xcd, 0x21, 0x51, 0x9e, 0xbd, 0x7a, 0x02, 0x64, 0x4a - }; - static const guint8 handshake_salt_draft_23[20] = { - 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, - 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02, - }; - static const guint8 handshake_salt_draft_29[20] = { - 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, - 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99 - }; - static const guint8 handshake_salt_v1[20] = { - 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, - 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a - }; - static const guint8 hanshake_salt_draft_q50[20] = { - 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, - 0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45 - }; - static const guint8 hanshake_salt_draft_t50[20] = { - 0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72, 0x91, 0x55, 0x80, - 0x30, 0x4c, 0x43, 0xa2, 0x36, 0x7c, 0x60, 0x48, 0x83, 0x10 - }; - static const guint8 hanshake_salt_draft_t51[20] = { - 0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee, 0x5f, 0xa4, 0x50, - 0x6c, 0x19, 0x12, 0x4f, 0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d - }; - - gcry_error_t err; - guint8 secret[HASH_SHA2_256_LENGTH]; - - if (version == 0x51303530) - { - err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_q50, sizeof(hanshake_salt_draft_q50), cid->cid, cid->len, secret); - } - else if (version == 0x54303530) - { - err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_t50, sizeof(hanshake_salt_draft_t50), cid->cid, cid->len, secret); - } - else if (version == 0x54303531) - { - err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_t51, sizeof(hanshake_salt_draft_t51), cid->cid, cid->len, secret); - } - else if (is_quic_draft_max(version, 22)) - { - err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_22, sizeof(handshake_salt_draft_22), cid->cid, cid->len, secret); - } - else if (is_quic_draft_max(version, 28)) - { - err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_23, sizeof(handshake_salt_draft_23), cid->cid, cid->len, secret); - } - else if (is_quic_draft_max(version, 32)) - { - err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_29, sizeof(handshake_salt_draft_29), cid->cid, cid->len, secret); - } - else - { - err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_v1, sizeof(handshake_salt_v1), cid->cid, cid->len, secret); - } - - if (err) { - //printf("Failed to extract secrets: %s\n", gcry_strerror(err)); - *error = "Failed to extract secrets"; - return FALSE; - } - - if (!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "client in", client_initial_secret, HASH_SHA2_256_LENGTH)) { - *error = "Key expansion (client) failed"; - return FALSE; - } - - if (!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "server in", server_initial_secret, HASH_SHA2_256_LENGTH)) { - *error = "Key expansion (server) failed"; - return FALSE; - } - - *error = NULL; - - return TRUE; -} - -static gboolean quic_create_initial_decoders(const quic_cid_t *cid, const gchar **error, quic_info_data_t *quic_info) -{ - unsigned char client_secret[HASH_SHA2_256_LENGTH]; - unsigned char server_secret[HASH_SHA2_256_LENGTH]; - - if (!quic_derive_initial_secrets(cid, client_secret, server_secret, quic_info->version, error)) - return -1; - - // Packet numbers are protected with AES128-CTR, - // initial packets are protected with AEAD_AES_128_GCM. - if (!quic_ciphers_prepare(&quic_info->client_initial_ciphers, GCRY_MD_SHA256, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, client_secret, error)) - { - return FALSE; - } - - if(!quic_ciphers_prepare(&quic_info->server_initial_ciphers, GCRY_MD_SHA256, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, server_secret, error)) - { - quic_hp_cipher_reset(&quic_info->client_initial_ciphers.hp_cipher); - quic_pp_cipher_reset(&quic_info->client_initial_ciphers.pp_cipher); - - return FALSE; - } - - return TRUE; -} - -static int quic_extract_header(const char *payload, unsigned char *long_packet_type, unsigned int *version, quic_cid_t *dcid, quic_cid_t *scid) -{ - unsigned int offset = 0; - - unsigned char packet_type = payload[offset]; - unsigned char is_long_header = packet_type & 0x80; - if (is_long_header) - // long header form - *long_packet_type = (packet_type & 0x30) >> 4; - else - // short header form, store dummy value that is not a long packet type. - *long_packet_type = QUIC_SHORT_PACKET; - offset++; - - *version = pntoh32((unsigned int *)&payload[offset]); - - if (is_long_header) { - // VN packets don't have any real packet type field, - // even if they have a long header: use a dummy value */ - if (*version == 0x00000000) - *long_packet_type = QUIC_LPT_VER_NEG; - - // skip version - offset += 4; - - // read DCID and SCID (both are prefixed by a length byte). - unsigned char dcil = payload[offset]; - offset++; - - if (dcil && dcil <= QUIC_MAX_CID_LENGTH) { - memcpy(dcid->cid, &payload[offset], dcil); - dcid->len = dcil; - } - offset += dcil; - - unsigned char scil = payload[offset]; - offset++; - - if (scil && scil <= QUIC_MAX_CID_LENGTH) { - memcpy(scid->cid, &payload[offset], scil); - scid->len = scil; - } - offset += scil; - } - else { - // Definitely not draft -10, set version to dummy value. - *version = 0; - // For short headers, the DCID length is unknown and could be 0 or - // anything from 1 to 20 bytes. Copy the maximum possible and let the - // consumer truncate it as necessary. - memcpy(dcid->cid, &payload[offset], QUIC_MAX_CID_LENGTH); - dcid->len = QUIC_MAX_CID_LENGTH; - offset += QUIC_MAX_CID_LENGTH; - } - - return offset; -} - -int dissect_quic(const char *payload, unsigned int length, unsigned char *out, unsigned int *out_length) -{ - guint offset = 0; - quic_packet_info_t quic_packet; - quic_info_data_t conn; - unsigned char long_packet_type; - quic_cid_t dcid = {.len=0}, scid = {.len=0}; - guint64 token_length, payload_length; - const char *error = NULL; - guint8 first_byte = 0; - const gboolean from_server = FALSE; - quic_ciphers *ciphers = NULL; - int ret; - - memset(&quic_packet, 0, sizeof(quic_packet_info_t)); - memset(&conn, 0, sizeof(quic_info_data_t)); - - ret = quic_extract_header(payload, &long_packet_type, &conn.version, &dcid, &scid); - if (ret < 0) - { - return -1; - } - - if (long_packet_type == QUIC_LPT_INITIAL) - { - // Create new decryption context based on the Client Connection ID - // from the *very first* Client Initial packet. - quic_create_initial_decoders(&dcid, &error, &conn); - if (!error) - { - guint32 pkn32 = 0; - // PKN is after type(1) + version(4) + DCIL+DCID + SCIL+SCID - guint pn_offset = 1 + 4 + 1 + dcid.len + 1 + scid.len; - pn_offset += tvb_get_varint(payload, pn_offset, 8, &token_length, ENC_VARINT_QUIC); - pn_offset += (guint)token_length; - // printf("%d\n", token_length); - - pn_offset += tvb_get_varint(payload, pn_offset, 8, &payload_length, ENC_VARINT_QUIC); - // printf("%d\n", payload_length); - - // Assume failure unless proven otherwise. - ciphers = &conn.client_initial_ciphers; - error = "Header deprotection failed"; - if (quic_decrypt_header(payload, pn_offset, &ciphers->hp_cipher, GCRY_CIPHER_AES128, &first_byte, &pkn32)) - error = NULL; - if (!error) { - quic_set_full_packet_number(&conn, &quic_packet, from_server, first_byte, pkn32); - quic_packet.first_byte = first_byte; - } - - // Payload - // skip type(1) + version(4) + DCIL+DCID + SCIL+SCID + len_token_length + token_length + len_payload_length + len_packet_number - offset = pn_offset + quic_packet.pkn_len; - //quic_process_payload(payload, length, offset, &conn, &quic_packet, from_server, &ciphers->pp_cipher, first_byte, quic_packet.pkn_len); - quic_process_payload(payload, payload_length, offset, &conn, &quic_packet, from_server, &ciphers->pp_cipher, first_byte, quic_packet.pkn_len); - - // Out - if (!quic_packet.decryption.error) - { - memcpy(out, quic_packet.decryption.data, quic_packet.decryption.data_len); - *out_length = quic_packet.decryption.data_len; - - g_free((gpointer)quic_packet.decryption.data); - quic_packet.decryption.data = NULL; - - ret=1; - } - else - { - ret=0; - } - - quic_hp_cipher_reset(&conn.client_initial_ciphers.hp_cipher); - quic_pp_cipher_reset(&conn.client_initial_ciphers.pp_cipher); - quic_hp_cipher_reset(&conn.server_initial_ciphers.hp_cipher); - quic_pp_cipher_reset(&conn.server_initial_ciphers.pp_cipher); - - return ret; - } - } - - return 0; -} - diff --git a/src/parser-quic.h b/src/parser-quic.h deleted file mode 100644 index a295bb5..0000000 --- a/src/parser-quic.h +++ /dev/null @@ -1,24 +0,0 @@ -/** - * parser-quic.h - * - * Created on 2020-11-26 - * @author: qyc - * - * - */ -#ifndef PARSER_QUIC_H -#define PARSER_QUIC_H - -#ifdef __cplusplus -extern "C" { -#endif - -/*ret: 1 sucess*/ -int dissect_quic(const char *payload, unsigned int length, unsigned char *out, unsigned int *out_length); - -int gcry_init(); -#ifdef __cplusplus -} -#endif - -#endif //PARSER_QUIC_H diff --git a/src/parser_quic.cpp b/src/parser_quic.cpp new file mode 100644 index 0000000..de3b968 --- /dev/null +++ b/src/parser_quic.cpp @@ -0,0 +1,813 @@ +/** + * parser-quic.c + * + * Created on 2020-11-26 + * @author: qyc + * + * @explain: QUIC解析 + */ +#include +#include +#include + +#include "parser_quic.h" +#include "wsgcrypt.h" +#include "utils.h" +#include "pint.h" +#include "gcrypt.h" + +// #define DEBUG_PARSER_QUIC + +int gcry_init() +{ + gcry_check_version("1.8.7"); + //gcry_control(GCRYCTL_SET_THREAD_CBS,&gcry_threads_pthread); + return 0; +} + + +#define QUIC_LPT_INITIAL 0x0 +#define QUIC_LPT_0RTT 0x1 +#define QUIC_LPT_HANDSHAKE 0x2 +#define QUIC_LPT_RETRY 0x3 +/* Version Negotiation packets don't have any real packet type */ +#define QUIC_LPT_VER_NEG 0xfe +/* dummy value that is definitely not LPT */ +#define QUIC_SHORT_PACKET 0xff + + +/* + * Although the QUIC SCID/DCID length field can store at most 255, v1 limits the + * CID length to 20. + */ +#define QUIC_MAX_CID_LENGTH 20 +typedef struct _quic_cid { + unsigned char len; + unsigned char cid[QUIC_MAX_CID_LENGTH]; +} quic_cid_t; + +/* + * PROTECTED PAYLOAD DECRYPTION (done in first pass) + * + * Long packet types always use a single cipher depending on packet type. + * Short packet types always use 1-RTT secrets for packet protection (pp). + * + * Considerations: + * - QUIC packets might appear out-of-order (short packets before handshake + * message is captured), lost or retransmitted/duplicated. + * - During live capture, keys might not be immediately be available. 1-RTT + * client keys will be ready while client proceses Server Hello (Handshake). + * 1-RTT server keys will be ready while server creates Handshake message in + * response to Initial Handshake. + * - So delay cipher creation until first short packet is received. + * + * Required input from TLS dissector: TLS-Exporter 0-RTT/1-RTT secrets and + * cipher/hash algorithms. + * + * QUIC payload decryption requires proper reconstruction of the packet number + * which requires proper header decryption. The different states are: + * + * Packet type Packet number space Secrets + * Long: Initial Initial Initial secrets + * Long: Handshake Handshake Handshake + * Long: 0-RTT 0/1-RTT (appdata) 0-RTT + * Short header 0/1-RTT (appdata) 1-RTT (KP0 / KP1) + * + * Important to note is that Short Header decryption requires TWO ciphers (one + * for each key phase), but that header protection uses only KP0. Total state + * needed for each peer (client and server): + * - 3 packet number spaces: Initial, Handshake, 0/1-RTT (appdata). + * - 4 header protection ciphers: initial, 0-RTT, HS, 1-RTT. + * - 5 payload protection ciphers: initial, 0-RTT, HS, 1-RTT (KP0), 1-RTT (KP1). + */ + +typedef struct _quic_decrypt_result { + // Error message or NULL for success. + const guchar *error; + // Decrypted result on success (file-scoped). + const guint8 *data; + // Size of decrypted data. + guint data_len; +} quic_decrypt_result_t; + +/** QUIC decryption context. */ + +typedef struct _quic_hp_cipher { + // Header protection cipher. + gcry_cipher_hd_t hp_cipher; +} quic_hp_cipher; + +typedef struct _quic_pp_cipher { + // Packet protection cipher. + gcry_cipher_hd_t pp_cipher; + guint8 pp_iv[TLS13_AEAD_NONCE_LENGTH]; +} quic_pp_cipher; + +typedef struct _quic_ciphers { + quic_hp_cipher hp_cipher; + quic_pp_cipher pp_cipher; +} quic_ciphers; + +/** + * State for a single QUIC connection, identified by one or more Destination + * Connection IDs (DCID). + */ +typedef struct _quic_info_data { + guint32 version; + quic_ciphers client_initial_ciphers; + quic_ciphers server_initial_ciphers; + // Packet number spaces for Initial, Handshake and appdata. + guint64 max_client_pkn[3]; + guint64 max_server_pkn[3]; +} quic_info_data_t; + +/** Per-packet information about QUIC, populated on the first pass. */ +typedef struct _quic_packet_info { + // Reconstructed full packet number. + guint64 packet_number; + quic_decrypt_result_t decryption; + // Length of PKN (1/2/3/4) or unknown (0). + guint8 pkn_len; + // Decrypted flag byte, valid only if pkn_len is non-zero. + guint8 first_byte; +} quic_packet_info_t; + +/** + * Given a QUIC message (header + non-empty payload), the actual packet number, + * try to decrypt it using the PP cipher. + * As the header points to the original buffer with an encrypted packet number, + * the (encrypted) packet number length is also included. + * + * The actual packet number must be constructed according to + * https://tools.ietf.org/html/draft-ietf-quic-transport-22#section-12.3 + */ +static void quic_decrypt_message(quic_pp_cipher *pp_cipher, const char *payload, guint length, guint header_length, + guint8 first_byte, guint pkn_len, guint64 packet_number, quic_decrypt_result_t *result) +{ + gcry_error_t err; + guint8 *header; + guint8 nonce[TLS13_AEAD_NONCE_LENGTH]; + guint8 *buffer; + guint8 atag[16]; + guint buffer_length; + const guchar **error = &result->error; + + g_assert(pp_cipher != NULL); + g_assert(pp_cipher->pp_cipher != NULL); + g_assert(pkn_len < header_length); + g_assert(1 <= pkn_len && pkn_len <= 4); + // copy header, but replace encrypted first byte and PKN by plaintext. + header = (guint8 *)g_malloc(header_length); + memcpy(header, payload, header_length); + header[0] = first_byte; + guint i; + for (i = 0; i < pkn_len; i++) + header[header_length - 1 - i] = (guint8)(packet_number >> (8 * i)); + + // Input is "header || ciphertext (buffer) || auth tag (16 bytes)" + // buffer_length = length - (header_length + 16); + // buffer_length = 297 - (2 + 16); + buffer_length = length - (pkn_len + 16); + if (buffer_length == 0) { + *error = (const guchar *)"Decryption not possible, ciphertext is too short"; + return; + } + buffer = (guint8 *)g_malloc(buffer_length); + memcpy(buffer, payload + header_length, buffer_length); + memcpy(atag, payload + header_length + buffer_length, 16); + + memcpy(nonce, pp_cipher->pp_iv, TLS13_AEAD_NONCE_LENGTH); + // Packet number is left-padded with zeroes and XORed with write_iv + phton64(nonce + sizeof(nonce) - 8, pntoh64(nonce + sizeof(nonce) - 8) ^ packet_number); + + gcry_cipher_reset(pp_cipher->pp_cipher); + err = gcry_cipher_setiv(pp_cipher->pp_cipher, nonce, TLS13_AEAD_NONCE_LENGTH); + if (err) { + //printf("Decryption (setiv) failed: %s\n", gcry_strerror(err)); + *error = (const guchar *)"Decryption (setiv) failed"; + return; + } + + // associated data (A) is the contents of QUIC header + err = gcry_cipher_authenticate(pp_cipher->pp_cipher, header, header_length); + if (err) { + //printf("Decryption (authenticate) failed: %s\n", gcry_strerror(err)); + *error = (const guchar *)"Decryption (authenticate) failed"; + return; + } + + // Output ciphertext (C) + err = gcry_cipher_decrypt(pp_cipher->pp_cipher, buffer, buffer_length, NULL, 0); + if (err) { + //printf("Decryption (decrypt) failed: %s\n", gcry_strerror(err)); + *error = (const guchar *)"Decryption (decrypt) failed"; + return; + } + + err = gcry_cipher_checktag(pp_cipher->pp_cipher, atag, 16); + if (err) { + //printf("Decryption (checktag) failed: %s\n", gcry_strerror(err)); + *error = (const guchar *)"Decryption (checktag) failed"; + return; + } + + g_free(header); + + result->error = NULL; + result->data = buffer; + result->data_len = buffer_length; +} + +static gboolean quic_is_pp_cipher_initialized(quic_pp_cipher *pp_cipher) +{ + return pp_cipher && pp_cipher->pp_cipher; +} + +/** + * Process (protected) payload, adding the encrypted payload to the tree. If + * decryption is possible, frame dissection is also attempted. + * + * The given offset must correspond to the end of the QUIC header and begin of + * the (protected) payload. Dissected frames are appended to "tree" and expert + * info is attached to "ti" (the field with the encrypted payload). + */ +static void quic_process_payload(const char *payload, guint length, guint offset, quic_info_data_t *quic_info, + quic_packet_info_t *quic_packet, gboolean from_server, quic_pp_cipher *pp_cipher, guint8 first_byte, guint pkn_len) +{ + /* + * If no decryption error has occurred yet, try decryption on the first + * pass and store the result for later use. + */ + if (quic_is_pp_cipher_initialized(pp_cipher)) + quic_decrypt_message(pp_cipher, payload, length, offset, first_byte, pkn_len, quic_packet->packet_number, &quic_packet->decryption); +} + +/* Inspired from ngtcp2 */ +static guint64 quic_pkt_adjust_pkt_num(guint64 max_pkt_num, guint64 pkt_num, size_t n) +{ + guint64 k = max_pkt_num == G_MAXUINT64 ? max_pkt_num : max_pkt_num + 1; + guint64 u = k & ~((G_GUINT64_CONSTANT(1) << n) - 1); + guint64 a = u | pkt_num; + guint64 b = (u + (G_GUINT64_CONSTANT(1) << n)) | pkt_num; + guint64 a1 = k < a ? a - k : k - a; + guint64 b1 = k < b ? b - k : k - b; + + if (a1 < b1) + return a; + return b; +} + +/** + * Retrieve the maximum valid packet number space for a peer. + */ +static guint64 *quic_max_packet_number(quic_info_data_t *quic_info, gboolean from_server, guint8 first_byte) +{ + int pkn_space; + if ((first_byte & 0x80) && (first_byte & 0x30) >> 4 == QUIC_LPT_INITIAL) + // Long header, Initial + pkn_space = 0; + else if ((first_byte & 0x80) && (first_byte & 0x30) >> 4 == QUIC_LPT_HANDSHAKE) + // Long header, Handshake + pkn_space = 1; + else + // Long header (0-RTT) or Short Header (1-RTT appdata). + pkn_space = 2; + if (from_server) + return &quic_info->max_server_pkn[pkn_space]; + else + return &quic_info->max_client_pkn[pkn_space]; +} + +/** + * Calculate the full packet number and store it for later use. + */ +static void quic_set_full_packet_number(quic_info_data_t *quic_info, quic_packet_info_t *quic_packet, gboolean from_server, guint8 first_byte, guint32 pkn32) +{ + guint pkn_len = (first_byte & 3) + 1; + guint64 pkn_full; + guint64 max_pn = *quic_max_packet_number(quic_info, from_server, first_byte); + + // Sequential first pass, try to reconstruct full packet number. + pkn_full = quic_pkt_adjust_pkt_num(max_pn, pkn32, 8 * pkn_len); + quic_packet->pkn_len = pkn_len; + quic_packet->packet_number = pkn_full; +} + +/** + * Given a header protection cipher, a buffer and the packet number offset, + * return the unmasked first byte and packet number. + */ +static gboolean quic_decrypt_header(const char *payload, guint pn_offset, quic_hp_cipher *hp_cipher, int hp_cipher_algo, guint8 *first_byte, guint32 *pn) +{ + if (!hp_cipher->hp_cipher) + // need to know the cipher. + return FALSE; + gcry_cipher_hd_t h = hp_cipher->hp_cipher; + + // Sample is always 16 bytes and starts after PKN (assuming length 4). + // https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.2 + guint8 sample[16]; + memcpy(sample, payload + pn_offset + 4, 16); + + guint8 mask[5] = { 0 }; + switch (hp_cipher_algo) { + case GCRY_CIPHER_AES128: + case GCRY_CIPHER_AES256: + // Encrypt in-place with AES-ECB and extract the mask. + if (gcry_cipher_encrypt(h, sample, sizeof(sample), NULL, 0)) + return FALSE; + memcpy(mask, sample, sizeof(mask)); + break; +#ifdef HAVE_LIBGCRYPT_CHACHA20 + case GCRY_CIPHER_CHACHA20: + // If Gcrypt receives a 16 byte IV, it will assume the buffer to be + // counter || nonce (in little endian), as desired. */ + if (gcry_cipher_setiv(h, sample, 16)) + return FALSE; + // Apply ChaCha20, encrypt in-place five zero bytes. + if (gcry_cipher_encrypt(h, mask, sizeof(mask), NULL, 0)) + return FALSE; + break; +#endif // HAVE_LIBGCRYPT_CHACHA20 + default: + return FALSE; + } + + // https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.1 + guint8 packet0 = payload[0]; + if ((packet0 & 0x80) == 0x80) + // Long header: 4 bits masked + packet0 ^= mask[0] & 0x0f; + else + // Short header: 5 bits masked + packet0 ^= mask[0] & 0x1f; + guint pkn_len = (packet0 & 0x03) + 1; + + guint8 pkn_bytes[4]; + memcpy(pkn_bytes, payload + pn_offset, pkn_len); + guint32 pkt_pkn = 0; + guint i; + for (i = 0; i < pkn_len; i++) + pkt_pkn |= (pkn_bytes[i] ^ mask[1 + i]) << (8 * (pkn_len - 1 - i)); + *first_byte = packet0; + *pn = pkt_pkn; + + return TRUE; +} + +static gboolean quic_hkdf_expand_label(int hash_algo, guint8 *secret, guint secret_len, const char *label, guint8 *out, guint out_len) +{ + const StringInfo secret_si = { secret, secret_len }; + guchar *out_mem = NULL; + + if (tls13_hkdf_expand_label(hash_algo, &secret_si, "tls13 ", label, out_len, &out_mem)) { + memcpy(out, out_mem, out_len); + g_free(out_mem); + return TRUE; + } + + return FALSE; +} + +/** + * Expands the secret (length MUST be the same as the "hash_algo" digest size) + * and initialize cipher with the new key. + */ +static gboolean quic_hp_cipher_init(quic_hp_cipher *hp_cipher, int hash_algo, guint8 key_length, guint8 *secret) +{ + guchar hp_key[256/8]; + guint hash_len = gcry_md_get_algo_dlen(hash_algo); + + if (!quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic hp", hp_key, key_length)) + return FALSE; + + return gcry_cipher_setkey(hp_cipher->hp_cipher, hp_key, key_length) == 0; +} + +static gboolean quic_pp_cipher_init(quic_pp_cipher *pp_cipher, int hash_algo, guint8 key_length, guint8 *secret) +{ + // Maximum key size is for AES256 cipher. + guchar write_key[256/8]; + guint hash_len = gcry_md_get_algo_dlen(hash_algo); + + if (key_length > sizeof(write_key)) + return FALSE; + + 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))) + return FALSE; + + return gcry_cipher_setkey(pp_cipher->pp_cipher, write_key, key_length) == 0; +} + +static void quic_hp_cipher_reset(quic_hp_cipher *hp_cipher) +{ + gcry_cipher_close(hp_cipher->hp_cipher); + memset(hp_cipher, 0, sizeof(*hp_cipher)); +} + +static void quic_pp_cipher_reset(quic_pp_cipher *pp_cipher) +{ + gcry_cipher_close(pp_cipher->pp_cipher); + memset(pp_cipher, 0, sizeof(*pp_cipher)); +} + +/** + * Maps a Packet Protection cipher to the Packet Number protection cipher. + * See https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.3 + */ +static gboolean quic_get_pn_cipher_algo(int cipher_algo, int *hp_cipher_mode) +{ + switch (cipher_algo) { + case GCRY_CIPHER_AES128: + case GCRY_CIPHER_AES256: + *hp_cipher_mode = GCRY_CIPHER_MODE_ECB; + return TRUE; +#ifdef HAVE_LIBGCRYPT_CHACHA20 + case GCRY_CIPHER_CHACHA20: + *hp_cipher_mode = GCRY_CIPHER_MODE_STREAM; + return TRUE; +#endif // HAVE_LIBGCRYPT_CHACHA20 + default: + return FALSE; + } +} + +/* + * (Re)initialize the PNE/PP ciphers using the given cipher algorithm. + * If the optional base secret is given, then its length MUST match the hash + * algorithm output. + */ +static gboolean quic_hp_cipher_prepare(quic_hp_cipher *hp_cipher, int hash_algo, int cipher_algo, guint8 *secret, const char **error) +{ + // Clear previous state (if any). + quic_hp_cipher_reset(hp_cipher); + + int hp_cipher_mode; + if (!quic_get_pn_cipher_algo(cipher_algo, &hp_cipher_mode)) { + *error = "Unsupported cipher algorithm"; + return FALSE; + } + + if (gcry_cipher_open(&hp_cipher->hp_cipher, cipher_algo, hp_cipher_mode, 0)) { + quic_hp_cipher_reset(hp_cipher); + *error = "Failed to create HP cipher"; + return FALSE; + } + + if (secret) { + guint cipher_keylen = (guint8)gcry_cipher_get_algo_keylen(cipher_algo); + if (!quic_hp_cipher_init(hp_cipher, hash_algo, cipher_keylen, secret)) { + quic_hp_cipher_reset(hp_cipher); + *error = "Failed to derive key material for HP cipher"; + return FALSE; + } + } + + return TRUE; +} + +static gboolean quic_pp_cipher_prepare(quic_pp_cipher *pp_cipher, int hash_algo, int cipher_algo, int cipher_mode, guint8 *secret, const char **error) +{ + // Clear previous state (if any). + quic_pp_cipher_reset(pp_cipher); + + int hp_cipher_mode; + if (!quic_get_pn_cipher_algo(cipher_algo, &hp_cipher_mode)) { + *error = "Unsupported cipher algorithm"; + return FALSE; + } + + if (gcry_cipher_open(&pp_cipher->pp_cipher, cipher_algo,cipher_mode, 0)) { + quic_pp_cipher_reset(pp_cipher); + *error = "Failed to create PP cipher"; + return FALSE; + } + + if (secret) { + guint cipher_keylen = (guint8) gcry_cipher_get_algo_keylen(cipher_algo); + if (!quic_pp_cipher_init(pp_cipher, hash_algo, cipher_keylen, secret)) { + quic_pp_cipher_reset(pp_cipher); + *error = "Failed to derive key material for PP cipher"; + return FALSE; + } + } + + return TRUE; +} + +static gboolean quic_ciphers_prepare(quic_ciphers *ciphers, int hash_algo, int cipher_algo, int cipher_mode, guint8 *secret, const char **error) +{ + return quic_hp_cipher_prepare(&ciphers->hp_cipher, hash_algo, cipher_algo, secret, error) && + quic_pp_cipher_prepare(&ciphers->pp_cipher, hash_algo, cipher_algo, cipher_mode, secret, error); +} + +/* Returns the QUIC draft version or 0 if not applicable. */ +static inline guint8 quic_draft_version(guint32 version) { + if ((version >> 8) == 0xff0000) + return (guint8) version; + + // Facebook mvfst, based on draft -22. + if (version == 0xfaceb001) + return 22; + + // Facebook mvfst, based on draft -27. + if (version == 0xfaceb002 || version == 0xfaceb00e) + return 27; + + // GQUIC Q050, T050 and T051: they are not really based on any drafts, + // but we must return a sensible value + if (version == 0x51303530 || version == 0x54303530 || version == 0x54303531) + return 27; + + /* + * https://tools.ietf.org/html/draft-ietf-quic-transport-32#section-15 + * "Versions that follow the pattern 0x?a?a?a?a are reserved for use in + * forcing version negotiation to be exercised" + * It is tricky to return a correct draft version: such number is primarly + * used to select a proper salt (which depends on the version itself), but + * we don't have a real version here! Let's hope that we need to handle + * only latest drafts... + */ + if ((version & 0x0F0F0F0F) == 0x0a0a0a0a) + return 29; + + return 0; +} + +static inline gboolean is_quic_draft_max(guint32 version, guint8 max_version) { + guint8 draft_version = quic_draft_version(version); + return draft_version && draft_version <= max_version; +} + +/** + * Compute the client and server initial secrets given Connection ID "cid". + * + * On success TRUE is returned and the two initial secrets are set. + * FALSE is returned on error (see "error" parameter for the reason). + */ +static gboolean quic_derive_initial_secrets(const quic_cid_t *cid, guint8 client_initial_secret[HASH_SHA2_256_LENGTH], guint8 server_initial_secret[HASH_SHA2_256_LENGTH], guint32 version, const gchar **error) +{ + /* + * https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2 + * + * initial_salt = 0xafbfec289993d24c9e9786f19c6111e04390a899 + * initial_secret = HKDF-Extract(initial_salt, client_dst_connection_id) + * + * client_initial_secret = HKDF-Expand-Label(initial_secret, + * "client in", "", Hash.length) + * server_initial_secret = HKDF-Expand-Label(initial_secret, + * "server in", "", Hash.length) + * + * Hash for handshake packets is SHA-256 (output size 32). + */ + static const guint8 handshake_salt_draft_22[20] = { + 0x7f, 0xbc, 0xdb, 0x0e, 0x7c, 0x66, 0xbb, 0xe9, 0x19, 0x3a, + 0x96, 0xcd, 0x21, 0x51, 0x9e, 0xbd, 0x7a, 0x02, 0x64, 0x4a + }; + static const guint8 handshake_salt_draft_23[20] = { + 0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, + 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02, + }; + static const guint8 handshake_salt_draft_29[20] = { + 0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, + 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99 + }; + static const guint8 handshake_salt_v1[20] = { + 0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, + 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a + }; + static const guint8 hanshake_salt_draft_q50[20] = { + 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, + 0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45 + }; + static const guint8 hanshake_salt_draft_t50[20] = { + 0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72, 0x91, 0x55, 0x80, + 0x30, 0x4c, 0x43, 0xa2, 0x36, 0x7c, 0x60, 0x48, 0x83, 0x10 + }; + static const guint8 hanshake_salt_draft_t51[20] = { + 0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee, 0x5f, 0xa4, 0x50, + 0x6c, 0x19, 0x12, 0x4f, 0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d + }; + + gcry_error_t err; + guint8 secret[HASH_SHA2_256_LENGTH]; + + if (version == 0x51303530) + { + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_q50, sizeof(hanshake_salt_draft_q50), cid->cid, cid->len, secret); + } + else if (version == 0x54303530) + { + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_t50, sizeof(hanshake_salt_draft_t50), cid->cid, cid->len, secret); + } + else if (version == 0x54303531) + { + err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_t51, sizeof(hanshake_salt_draft_t51), cid->cid, cid->len, secret); + } + else if (is_quic_draft_max(version, 22)) + { + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_22, sizeof(handshake_salt_draft_22), cid->cid, cid->len, secret); + } + else if (is_quic_draft_max(version, 28)) + { + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_23, sizeof(handshake_salt_draft_23), cid->cid, cid->len, secret); + } + else if (is_quic_draft_max(version, 32)) + { + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_29, sizeof(handshake_salt_draft_29), cid->cid, cid->len, secret); + } + else + { + err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_v1, sizeof(handshake_salt_v1), cid->cid, cid->len, secret); + } + + if (err) { + //printf("Failed to extract secrets: %s\n", gcry_strerror(err)); + *error = "Failed to extract secrets"; + return FALSE; + } + + if (!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "client in", client_initial_secret, HASH_SHA2_256_LENGTH)) { + *error = "Key expansion (client) failed"; + return FALSE; + } + + if (!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "server in", server_initial_secret, HASH_SHA2_256_LENGTH)) { + *error = "Key expansion (server) failed"; + return FALSE; + } + + *error = NULL; + + return TRUE; +} + +static gboolean quic_create_initial_decoders(const quic_cid_t *cid, const gchar **error, quic_info_data_t *quic_info) +{ + unsigned char client_secret[HASH_SHA2_256_LENGTH]; + unsigned char server_secret[HASH_SHA2_256_LENGTH]; + + if (!quic_derive_initial_secrets(cid, client_secret, server_secret, quic_info->version, error)) + return -1; + + // Packet numbers are protected with AES128-CTR, + // initial packets are protected with AEAD_AES_128_GCM. + if (!quic_ciphers_prepare(&quic_info->client_initial_ciphers, GCRY_MD_SHA256, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, client_secret, error)) + { + return FALSE; + } + + if(!quic_ciphers_prepare(&quic_info->server_initial_ciphers, GCRY_MD_SHA256, GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM, server_secret, error)) + { + quic_hp_cipher_reset(&quic_info->client_initial_ciphers.hp_cipher); + quic_pp_cipher_reset(&quic_info->client_initial_ciphers.pp_cipher); + + return FALSE; + } + + return TRUE; +} + +static int quic_extract_header(const char *payload, unsigned char *long_packet_type, unsigned int *version, quic_cid_t *dcid, quic_cid_t *scid) +{ + unsigned int offset = 0; + + unsigned char packet_type = payload[offset]; + unsigned char is_long_header = packet_type & 0x80; + if (is_long_header) + // long header form + *long_packet_type = (packet_type & 0x30) >> 4; + else + // short header form, store dummy value that is not a long packet type. + *long_packet_type = QUIC_SHORT_PACKET; + offset++; + + *version = pntoh32((unsigned int *)&payload[offset]); + + if (is_long_header) { + // VN packets don't have any real packet type field, + // even if they have a long header: use a dummy value */ + if (*version == 0x00000000) + *long_packet_type = QUIC_LPT_VER_NEG; + + // skip version + offset += 4; + + // read DCID and SCID (both are prefixed by a length byte). + unsigned char dcil = payload[offset]; + offset++; + + if (dcil && dcil <= QUIC_MAX_CID_LENGTH) { + memcpy(dcid->cid, &payload[offset], dcil); + dcid->len = dcil; + } + offset += dcil; + + unsigned char scil = payload[offset]; + offset++; + + if (scil && scil <= QUIC_MAX_CID_LENGTH) { + memcpy(scid->cid, &payload[offset], scil); + scid->len = scil; + } + offset += scil; + } + else { + // Definitely not draft -10, set version to dummy value. + *version = 0; + // For short headers, the DCID length is unknown and could be 0 or + // anything from 1 to 20 bytes. Copy the maximum possible and let the + // consumer truncate it as necessary. + memcpy(dcid->cid, &payload[offset], QUIC_MAX_CID_LENGTH); + dcid->len = QUIC_MAX_CID_LENGTH; + offset += QUIC_MAX_CID_LENGTH; + } + + return offset; +} + +int dissect_quic(const char *payload, unsigned int length, unsigned char *out, unsigned int *out_length) +{ + guint offset = 0; + quic_packet_info_t quic_packet; + quic_info_data_t conn; + unsigned char long_packet_type; + quic_cid_t dcid = {.len=0}, scid = {.len=0}; + guint64 token_length, payload_length; + const char *error = NULL; + guint8 first_byte = 0; + const gboolean from_server = FALSE; + quic_ciphers *ciphers = NULL; + int ret; + + memset(&quic_packet, 0, sizeof(quic_packet_info_t)); + memset(&conn, 0, sizeof(quic_info_data_t)); + + ret = quic_extract_header(payload, &long_packet_type, &conn.version, &dcid, &scid); + if (ret < 0) + { + return -1; + } + + if (long_packet_type == QUIC_LPT_INITIAL) + { + // Create new decryption context based on the Client Connection ID + // from the *very first* Client Initial packet. + quic_create_initial_decoders(&dcid, &error, &conn); + if (!error) + { + guint32 pkn32 = 0; + // PKN is after type(1) + version(4) + DCIL+DCID + SCIL+SCID + guint pn_offset = 1 + 4 + 1 + dcid.len + 1 + scid.len; + pn_offset += tvb_get_varint(payload, pn_offset, 8, &token_length, ENC_VARINT_QUIC); + pn_offset += (guint)token_length; + // printf("%d\n", token_length); + + pn_offset += tvb_get_varint(payload, pn_offset, 8, &payload_length, ENC_VARINT_QUIC); + // printf("%d\n", payload_length); + + // Assume failure unless proven otherwise. + ciphers = &conn.client_initial_ciphers; + error = "Header deprotection failed"; + if (quic_decrypt_header(payload, pn_offset, &ciphers->hp_cipher, GCRY_CIPHER_AES128, &first_byte, &pkn32)) + error = NULL; + if (!error) { + quic_set_full_packet_number(&conn, &quic_packet, from_server, first_byte, pkn32); + quic_packet.first_byte = first_byte; + } + + // Payload + // skip type(1) + version(4) + DCIL+DCID + SCIL+SCID + len_token_length + token_length + len_payload_length + len_packet_number + offset = pn_offset + quic_packet.pkn_len; + //quic_process_payload(payload, length, offset, &conn, &quic_packet, from_server, &ciphers->pp_cipher, first_byte, quic_packet.pkn_len); + quic_process_payload(payload, payload_length, offset, &conn, &quic_packet, from_server, &ciphers->pp_cipher, first_byte, quic_packet.pkn_len); + + // Out + if (!quic_packet.decryption.error) + { + memcpy(out, quic_packet.decryption.data, quic_packet.decryption.data_len); + *out_length = quic_packet.decryption.data_len; + + g_free((gpointer)quic_packet.decryption.data); + quic_packet.decryption.data = NULL; + + ret=1; + } + else + { + ret=0; + } + + quic_hp_cipher_reset(&conn.client_initial_ciphers.hp_cipher); + quic_pp_cipher_reset(&conn.client_initial_ciphers.pp_cipher); + quic_hp_cipher_reset(&conn.server_initial_ciphers.hp_cipher); + quic_pp_cipher_reset(&conn.server_initial_ciphers.pp_cipher); + + return ret; + } + } + + return 0; +} + diff --git a/src/parser_quic.h b/src/parser_quic.h new file mode 100644 index 0000000..a295bb5 --- /dev/null +++ b/src/parser_quic.h @@ -0,0 +1,24 @@ +/** + * parser-quic.h + * + * Created on 2020-11-26 + * @author: qyc + * + * + */ +#ifndef PARSER_QUIC_H +#define PARSER_QUIC_H + +#ifdef __cplusplus +extern "C" { +#endif + +/*ret: 1 sucess*/ +int dissect_quic(const char *payload, unsigned int length, unsigned char *out, unsigned int *out_length); + +int gcry_init(); +#ifdef __cplusplus +} +#endif + +#endif //PARSER_QUIC_H diff --git a/src/quic_analysis.cpp b/src/quic_analysis.cpp index 9c71ac4..23dff35 100644 --- a/src/quic_analysis.cpp +++ b/src/quic_analysis.cpp @@ -7,6 +7,8 @@ #include "gquic.h" #include "quic_analysis.h" #include "gquic_process.h" +#include "parser_quic.h" + #include #include #include @@ -156,6 +158,8 @@ extern "C" int QUIC_INIT(void) return -1; } + gcry_init(); + return 0; }/*QUICINIT*/ @@ -242,6 +246,11 @@ extern "C" char QUIC_ENTRY(struct streaminfo *pstream, void**pme, int thread_seq return APP_STATE_DROPME; } + if(!is_quic_port(pstream)) + { + return APP_STATE_DROPME; + } + if(*pme==NULL) { quic_init_stream(pme, thread_seq); diff --git a/src/quic_analysis.h b/src/quic_analysis.h index 71c8c67..e8866b7 100644 --- a/src/quic_analysis.h +++ b/src/quic_analysis.h @@ -41,6 +41,7 @@ enum quic_mes_type{ extern struct _quic_param_t g_quic_param; +int is_quic_port(struct streaminfo *pstream); void quic_release_exts(int thread_seq, quic_tlv_t *ext_tags, int ext_tag_num); #endif /* SRC_QUIC_ANALYSIS_H_ */ -- cgit v1.2.3