diff options
Diffstat (limited to 'src/parser_quic.cpp')
| -rw-r--r-- | src/parser_quic.cpp | 813 |
1 files changed, 813 insertions, 0 deletions
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 <stdio.h> +#include <stdlib.h> +#include <string.h> + +#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; +} + |
