summaryrefslogtreecommitdiff
path: root/src/parser-quic.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/parser-quic.cpp')
-rw-r--r--src/parser-quic.cpp792
1 files changed, 792 insertions, 0 deletions
diff --git a/src/parser-quic.cpp b/src/parser-quic.cpp
new file mode 100644
index 0000000..cc90787
--- /dev/null
+++ b/src/parser-quic.cpp
@@ -0,0 +1,792 @@
+/**
+ * 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()
+{
+ //const char * tmp = 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 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
+ err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_29, sizeof(handshake_salt_draft_29), 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;
+}
+