aboutsummaryrefslogtreecommitdiff
path: root/src/lib/protocols
diff options
context:
space:
mode:
Diffstat (limited to 'src/lib/protocols')
-rw-r--r--src/lib/protocols/quic.c735
1 files changed, 734 insertions, 1 deletions
diff --git a/src/lib/protocols/quic.c b/src/lib/protocols/quic.c
index dbbcb6c9f..f3d46e89c 100644
--- a/src/lib/protocols/quic.c
+++ b/src/lib/protocols/quic.c
@@ -30,6 +30,12 @@
#define NDPI_CURRENT_PROTO NDPI_PROTOCOL_QUIC
#include "ndpi_api.h"
+#ifdef HAVE_LIBGCRYPT
+#include <gcrypt.h>
+#endif
+
+// #define DEBUG_CRYPT
+
/* This dissector handles GQUIC and IETF-QUIC both.
Main references:
* https://groups.google.com/a/chromium.org/g/proto-quic/c/wVHBir-uRU0?pli=1
@@ -40,6 +46,8 @@
* https://tools.ietf.org/html/draft-ietf-quic-transport-29
*/
+extern int processClientServerHello(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow, int is_quic);
/* Versions */
#define V_Q024 0x51303234
@@ -80,6 +88,13 @@ static uint8_t get_u8_quic_ver(uint32_t version)
return (uint8_t)version;
return 0;
}
+#ifdef HAVE_LIBGCRYPT
+static int is_quic_ver_less_than(uint32_t version, uint8_t max_version)
+{
+ uint8_t u8_ver = get_u8_quic_ver(version);
+ return u8_ver && u8_ver <= max_version;
+}
+#endif
static int is_quic_ver_greater_than(uint32_t version, uint8_t min_version)
{
return get_u8_quic_ver(version) >= min_version;
@@ -114,6 +129,11 @@ static int is_version_supported(uint32_t version)
version == V_MVFST_27 ||
is_quic_ver_greater_than(version, 23));
}
+static int is_version_with_encrypted_header(uint32_t version)
+{
+ return is_version_quic(version) ||
+ ((version & 0xFFFFFF00) == 0x51303500) /* Q05X */;
+}
static int quic_len(const uint8_t *buf, uint64_t *value)
{
@@ -144,6 +164,679 @@ static uint16_t gquic_get_u16(const uint8_t *buf, uint32_t version)
}
+#ifdef HAVE_LIBGCRYPT
+
+#ifdef DEBUG_CRYPT
+char *__gcry_err(gpg_error_t err, char *buf, size_t buflen)
+{
+#ifdef HAVE_LIBGPG_ERROR
+ gpg_strerror_r(err, buf, buflen);
+ /* I am not sure if the string will be always null-terminated...
+ Better safe than sorry */
+ if(buflen > 0)
+ buf[buflen - 1] = '\0';
+#else
+ if(buflen > 0)
+ buf[0] = '\0';
+#endif
+ return buf;
+}
+#endif /* DEBUG_CRYPT */
+
+static uint64_t pntoh64(const void *p)
+{
+ return (uint64_t)*((const uint8_t *)(p)+0)<<56|
+ (uint64_t)*((const uint8_t *)(p)+1)<<48|
+ (uint64_t)*((const uint8_t *)(p)+2)<<40|
+ (uint64_t)*((const uint8_t *)(p)+3)<<32|
+ (uint64_t)*((const uint8_t *)(p)+4)<<24|
+ (uint64_t)*((const uint8_t *)(p)+5)<<16|
+ (uint64_t)*((const uint8_t *)(p)+6)<<8|
+ (uint64_t)*((const uint8_t *)(p)+7)<<0;
+}
+static void phton64(uint8_t *p, uint64_t v)
+{
+ p[0] = (uint8_t)(v >> 56);
+ p[1] = (uint8_t)(v >> 48);
+ p[2] = (uint8_t)(v >> 40);
+ p[3] = (uint8_t)(v >> 32);
+ p[4] = (uint8_t)(v >> 24);
+ p[5] = (uint8_t)(v >> 16);
+ p[6] = (uint8_t)(v >> 8);
+ p[7] = (uint8_t)(v >> 0);
+}
+
+static void *memdup(const uint8_t *orig, size_t len)
+{
+ void *dest = ndpi_malloc(len);
+ if(dest)
+ memcpy(dest, orig, len);
+ return dest;
+}
+
+
+/*
+ * Generic Wireshark definitions
+ */
+
+#define HASH_SHA2_256_LENGTH 32
+#define TLS13_AEAD_NONCE_LENGTH 12
+
+typedef struct _StringInfo {
+ unsigned char *data; /* Backing storage which may be larger than data_len */
+ unsigned int data_len; /* Length of the meaningful part of data */
+} StringInfo;
+
+/* QUIC decryption context. */
+typedef struct quic_cipher {
+ gcry_cipher_hd_t hp_cipher; /* Header protection cipher. */
+ gcry_cipher_hd_t pp_cipher; /* Packet protection cipher. */
+ uint8_t pp_iv[TLS13_AEAD_NONCE_LENGTH];
+} quic_cipher;
+
+typedef struct quic_decrypt_result {
+ uint8_t *data; /* Decrypted result on success (file-scoped). */
+ uint32_t data_len; /* Size of decrypted data. */
+} quic_decrypt_result_t;
+
+
+/*
+ * From wsutil/wsgcrypt.{c,h}
+ */
+
+static gcry_error_t ws_hmac_buffer(int algo, void *digest, const void *buffer,
+ size_t length, const void *key, size_t keylen)
+{
+ gcry_md_hd_t hmac_handle;
+ gcry_error_t result = gcry_md_open(&hmac_handle, algo, GCRY_MD_FLAG_HMAC);
+ if(result) {
+ return result;
+ }
+ result = gcry_md_setkey(hmac_handle, key, keylen);
+ if(result) {
+ gcry_md_close(hmac_handle);
+ return result;
+ }
+ gcry_md_write(hmac_handle, buffer, length);
+ memcpy(digest, gcry_md_read(hmac_handle, 0), gcry_md_get_algo_dlen(algo));
+ gcry_md_close(hmac_handle);
+ return GPG_ERR_NO_ERROR;
+}
+static gcry_error_t hkdf_expand(int hashalgo, const uint8_t *prk, uint32_t prk_len,
+ const uint8_t *info, uint32_t info_len,
+ uint8_t *out, uint32_t out_len)
+{
+ /* Current maximum hash output size: 48 bytes for SHA-384. */
+ uint8_t lastoutput[48];
+ gcry_md_hd_t h;
+ gcry_error_t err;
+ const unsigned int hash_len = gcry_md_get_algo_dlen(hashalgo);
+
+ /* Some sanity checks */
+ if(!(out_len > 0 && out_len <= 255 * hash_len) ||
+ !(hash_len > 0 && hash_len <= sizeof(lastoutput))) {
+ return GPG_ERR_INV_ARG;
+ }
+
+ err = gcry_md_open(&h, hashalgo, GCRY_MD_FLAG_HMAC);
+ if(err) {
+ return err;
+ }
+
+ for(uint32_t offset = 0; offset < out_len; offset += hash_len) {
+ gcry_md_reset(h);
+ gcry_md_setkey(h, prk, prk_len); /* Set PRK */
+ if(offset > 0) {
+ gcry_md_write(h, lastoutput, hash_len); /* T(1..N) */
+ }
+ gcry_md_write(h, info, info_len); /* info */
+ gcry_md_putc(h, (uint8_t) (offset / hash_len + 1)); /* constant 0x01..N */
+
+ memcpy(lastoutput, gcry_md_read(h, hashalgo), hash_len);
+ memcpy(out + offset, lastoutput, MIN(hash_len, out_len - offset));
+ }
+
+ gcry_md_close(h);
+ return 0;
+}
+/*
+ * Calculate HKDF-Extract(salt, IKM) -> PRK according to RFC 5869.
+ * Caller MUST ensure that 'prk' is large enough to store the digest from hash
+ * algorithm 'hashalgo' (e.g. 32 bytes for SHA-256).
+ */
+static gcry_error_t hkdf_extract(int hashalgo, const uint8_t *salt, size_t salt_len,
+ const uint8_t *ikm, size_t ikm_len, uint8_t *prk)
+{
+ /* PRK = HMAC-Hash(salt, IKM) where salt is key, and IKM is input. */
+ return ws_hmac_buffer(hashalgo, prk, ikm, ikm_len, salt, salt_len);
+}
+
+
+/*
+ * From epan/dissectors/packet-tls-utils.c
+ */
+
+/*
+ * Computes HKDF-Expand-Label(Secret, Label, Hash(context_value), Length) with a
+ * custom label prefix. If "context_hash" is NULL, then an empty context is
+ * used. Otherwise it must have the same length as the hash algorithm output.
+ */
+static int tls13_hkdf_expand_label_context(int md, const StringInfo *secret,
+ const char *label_prefix, const char *label,
+ const uint8_t *context_hash, uint8_t context_length,
+ uint16_t out_len, uint8_t **out)
+{
+ /* RFC 8446 Section 7.1:
+ * HKDF-Expand-Label(Secret, Label, Context, Length) =
+ * HKDF-Expand(Secret, HkdfLabel, Length)
+ * struct {
+ * uint16 length = Length;
+ * opaque label<7..255> = "tls13 " + Label; // "tls13 " is label prefix.
+ * opaque context<0..255> = Context;
+ * } HkdfLabel;
+ *
+ * RFC 5869 HMAC-based Extract-and-Expand Key Derivation Function (HKDF):
+ * HKDF-Expand(PRK, info, L) -> OKM
+ */
+ gcry_error_t err;
+ const unsigned int label_prefix_length = (unsigned int)strlen(label_prefix);
+ const unsigned label_length = (unsigned int)strlen(label);
+#ifdef DEBUG_CRYPT
+ char buferr[128];
+#endif
+
+ /* Some sanity checks */
+ if(!(label_length > 0 && label_prefix_length + label_length <= 255)) {
+#ifdef DEBUG_CRYPT
+ printf("Failed sanity checks\n");
+#endif
+ return 0;
+ }
+
+ /* info = HkdfLabel { length, label, context } */
+ /* Keep original Wireshark code as reference */
+#if 0
+ GByteArray *info = g_byte_array_new();
+ const uint16_t length = htons(out_len);
+ g_byte_array_append(info, (const guint8 *)&length, sizeof(length));
+
+ const uint8_t label_vector_length = label_prefix_length + label_length;
+ g_byte_array_append(info, &label_vector_length, 1);
+ g_byte_array_append(info, (const uint8_t *)label_prefix, label_prefix_length);
+ g_byte_array_append(info, (const uint8_t *)label, label_length);
+
+ g_byte_array_append(info, &context_length, 1);
+ if (context_length) {
+ g_byte_array_append(info, context_hash, context_length);
+ }
+#else
+ uint32_t info_len = 0;
+ uint8_t *info_data = (uint8_t *)ndpi_malloc(1024);
+ if(!info_data)
+ return 0;
+ const uint16_t length = htons(out_len);
+ memcpy(&info_data[info_len], &length, sizeof(length));
+ info_len += sizeof(length);
+
+ const uint8_t label_vector_length = label_prefix_length + label_length;
+ memcpy(&info_data[info_len], &label_vector_length, 1);
+ info_len += 1;
+ memcpy(&info_data[info_len], (const uint8_t *)label_prefix, label_prefix_length);
+ info_len += label_prefix_length;
+ memcpy(&info_data[info_len], (const uint8_t *)label, label_length);
+ info_len += label_length;
+
+ memcpy(&info_data[info_len], &context_length, 1);
+ info_len += 1;
+ if(context_length) {
+ memcpy(&info_data[info_len], context_hash, context_length);
+ info_len += context_length;
+ }
+#endif
+
+ *out = (uint8_t *)ndpi_malloc(out_len);
+ if(!*out)
+ return 0;
+ err = hkdf_expand(md, secret->data, secret->data_len, info_data, info_len, *out, out_len);
+ ndpi_free(info_data);
+
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Failed hkdf_expand: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ ndpi_free(*out);
+ *out = NULL;
+ return 0;
+ }
+
+ return 1;
+}
+static int tls13_hkdf_expand_label(int md, const StringInfo *secret,
+ const char *label_prefix, const char *label,
+ uint16_t out_len, unsigned char **out)
+{
+ return tls13_hkdf_expand_label_context(md, secret, label_prefix, label, NULL, 0, out_len, out);
+}
+
+
+/*
+ * From epan/dissectors/packet-quic.c
+ */
+
+static int quic_hkdf_expand_label(int hash_algo, uint8_t *secret, uint32_t secret_len,
+ const char *label, uint8_t *out, uint32_t out_len)
+{
+ const StringInfo secret_si = { secret, secret_len };
+ uint8_t *out_mem = NULL;
+ if(tls13_hkdf_expand_label(hash_algo, &secret_si, "tls13 ", label, out_len, &out_mem)) {
+ memcpy(out, out_mem, out_len);
+ ndpi_free(out_mem);
+ return 1;
+ }
+ return 0;
+}
+static void quic_cipher_reset(quic_cipher *cipher)
+{
+ gcry_cipher_close(cipher->hp_cipher);
+ gcry_cipher_close(cipher->pp_cipher);
+#if 0
+ memset(cipher, 0, sizeof(*cipher));
+#endif
+}
+/**
+ * Expands the secret (length MUST be the same as the "hash_algo" digest size)
+ * and initialize cipher with the new key.
+ */
+static int quic_cipher_init(quic_cipher *cipher, int hash_algo,
+ uint8_t key_length, uint8_t *secret)
+{
+ uint8_t write_key[256/8]; /* Maximum key size is for AES256 cipher. */
+ uint8_t hp_key[256/8];
+ uint32_t hash_len = gcry_md_get_algo_dlen(hash_algo);
+
+ if(key_length > sizeof(write_key)) {
+ return 0;
+ }
+
+ if(!quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic key", write_key, key_length) ||
+ !quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic iv", cipher->pp_iv, sizeof(cipher->pp_iv)) ||
+ !quic_hkdf_expand_label(hash_algo, secret, hash_len, "quic hp", hp_key, key_length)) {
+ return 1;
+ }
+
+ return gcry_cipher_setkey(cipher->hp_cipher, hp_key, key_length) == 0 &&
+ gcry_cipher_setkey(cipher->pp_cipher, write_key, key_length) == 0;
+}
+/**
+ * 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 int 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 1;
+ default:
+ return 0;
+ }
+}
+/*
+ * (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 int quic_cipher_prepare(quic_cipher *cipher, int hash_algo, int cipher_algo,
+ int cipher_mode, uint8_t *secret)
+{
+#if 0
+ /* Clear previous state (if any). */
+ quic_cipher_reset(cipher);
+#endif
+
+ int hp_cipher_mode;
+ if(!quic_get_pn_cipher_algo(cipher_algo, &hp_cipher_mode)) {
+#ifdef DEBUG_CRYPT
+ printf("Unsupported cipher algorithm\n");
+#endif
+ return 0;
+ }
+
+ if(gcry_cipher_open(&cipher->hp_cipher, cipher_algo, hp_cipher_mode, 0) ||
+ gcry_cipher_open(&cipher->pp_cipher, cipher_algo, cipher_mode, 0)) {
+ quic_cipher_reset(cipher);
+#ifdef DEBUG_CRYPT
+ printf("Failed to create ciphers\n");
+#endif
+ return 0;
+ }
+
+ if(secret) {
+ uint32_t cipher_keylen = (uint8_t)gcry_cipher_get_algo_keylen(cipher_algo);
+ if(!quic_cipher_init(cipher, hash_algo, cipher_keylen, secret)) {
+ quic_cipher_reset(cipher);
+#ifdef DEBUG_CRYPT
+ printf("Failed to derive key material for cipher\n");
+#endif
+ return 0;
+ }
+ }
+
+ return 1;
+}
+/**
+ * Given a header protection cipher, a buffer and the packet number offset,
+ * return the unmasked first byte and packet number.
+ */
+static int quic_decrypt_header(const uint8_t *packet_payload,
+ uint32_t pn_offset, gcry_cipher_hd_t hp_cipher,
+ int hp_cipher_algo, uint8_t *first_byte, uint32_t *pn)
+{
+ gcry_cipher_hd_t h = hp_cipher;
+ if(!hp_cipher) {
+ /* Need to know the cipher */
+ return 0;
+ }
+
+ /* 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 */
+ uint8_t sample[16];
+ memcpy(sample, packet_payload + pn_offset + 4, 16);
+
+ uint8_t 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 0;
+ }
+ memcpy(mask, sample, sizeof(mask));
+ break;
+ default:
+ return 0;
+ }
+
+ /* https://tools.ietf.org/html/draft-ietf-quic-tls-22#section-5.4.1 */
+ uint8_t packet0 = packet_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;
+ }
+ uint32_t pkn_len = (packet0 & 0x03) + 1;
+ /* printf("packet0 0x%x pkn_len %d\n", packet0, pkn_len); */
+
+ uint8_t pkn_bytes[4];
+ memcpy(pkn_bytes, packet_payload + pn_offset, pkn_len);
+ uint32_t pkt_pkn = 0;
+ for(uint32_t i = 0; i < pkn_len; i++) {
+ pkt_pkn |= (uint32_t)(pkn_bytes[i] ^ mask[1 + i]) << (8 * (pkn_len - 1 - i));
+ }
+ *first_byte = packet0;
+ *pn = pkt_pkn;
+ return 1;
+}
+/**
+ * Given a QUIC message (header + non-empty payload), the actual packet number,
+ * try to decrypt it using the 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_cipher *cipher, const uint8_t *packet_payload, uint32_t packet_payload_len,
+ uint32_t header_length, uint8_t first_byte, uint32_t pkn_len,
+ uint64_t packet_number, quic_decrypt_result_t *result)
+{
+ gcry_error_t err;
+ uint8_t *header;
+ uint8_t nonce[TLS13_AEAD_NONCE_LENGTH];
+ uint8_t *buffer;
+ uint8_t atag[16];
+ uint32_t buffer_length;
+#ifdef DEBUG_CRYPT
+ char buferr[128];
+#endif
+
+ if(!(cipher != NULL) ||
+ !(cipher->pp_cipher != NULL) ||
+ !(pkn_len < header_length) ||
+ !(1 <= pkn_len && pkn_len <= 4)) {
+#ifdef DEBUG_CRYPT
+ printf("Failed sanity checks\n");
+#endif
+ return;
+ }
+ /* Copy header, but replace encrypted first byte and PKN by plaintext. */
+ header = (uint8_t *)memdup(packet_payload, header_length);
+ if(!header)
+ return;
+ header[0] = first_byte;
+ for(uint32_t i = 0; i < pkn_len; i++) {
+ header[header_length - 1 - i] = (uint8_t)(packet_number >> (8 * i));
+ }
+
+ /* Input is "header || ciphertext (buffer) || auth tag (16 bytes)" */
+ buffer_length = packet_payload_len - (header_length + 16);
+ if(buffer_length == 0) {
+#ifdef DEBUG_CRYPT
+ printf("Decryption not possible, ciphertext is too short\n");
+#endif
+ ndpi_free(header);
+ return;
+ }
+ buffer = (uint8_t *)memdup(packet_payload + header_length, buffer_length);
+ if(!buffer) {
+ ndpi_free(header);
+ return;
+ }
+ memcpy(atag, packet_payload + header_length + buffer_length, 16);
+
+ memcpy(nonce, 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(cipher->pp_cipher);
+ err = gcry_cipher_setiv(cipher->pp_cipher, nonce, TLS13_AEAD_NONCE_LENGTH);
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Decryption (setiv) failed: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ ndpi_free(header);
+ ndpi_free(buffer);
+ return;
+ }
+
+ /* associated data (A) is the contents of QUIC header */
+ err = gcry_cipher_authenticate(cipher->pp_cipher, header, header_length);
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Decryption (authenticate) failed: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ ndpi_free(header);
+ ndpi_free(buffer);
+ return;
+ }
+
+ ndpi_free(header);
+
+ /* Output ciphertext (C) */
+ err = gcry_cipher_decrypt(cipher->pp_cipher, buffer, buffer_length, NULL, 0);
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Decryption (decrypt) failed: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ ndpi_free(buffer);
+ return;
+ }
+
+ err = gcry_cipher_checktag(cipher->pp_cipher, atag, 16);
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Decryption (checktag) failed: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ ndpi_free(buffer);
+ return;
+ }
+
+ result->data = buffer;
+ result->data_len = buffer_length;
+}
+/**
+ * Compute the client and server initial secrets given Connection ID "cid".
+ */
+static int quic_derive_initial_secrets(uint32_t version,
+ const uint8_t *cid, uint8_t cid_len,
+ uint8_t client_initial_secret[HASH_SHA2_256_LENGTH])
+{
+ /*
+ * https://tools.ietf.org/html/draft-ietf-quic-tls-29#section-5.2
+ *
+ * initial_secret = HKDF-Extract(initial_salt, client_dst_connection_id)
+ *
+ * client_initial_secret = HKDF-Expand-Label(initial_secret,
+ * "client in", "", Hash.length)
+ *
+ * Hash for handshake packets is SHA-256 (output size 32).
+ */
+ static const uint8_t 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 uint8_t 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 uint8_t 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 uint8_t 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
+ };
+
+ gcry_error_t err;
+ uint8_t secret[HASH_SHA2_256_LENGTH];
+#ifdef DEBUG_CRYPT
+ char buferr[128];
+#endif
+
+ if(version == V_Q050) {
+ err = hkdf_extract(GCRY_MD_SHA256, hanshake_salt_draft_q50,
+ sizeof(hanshake_salt_draft_q50),
+ cid, cid_len, secret);
+ } else if(is_quic_ver_less_than(version, 22) ||
+ version == V_MVFST_22) {
+ err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_22,
+ sizeof(handshake_salt_draft_22),
+ cid, cid_len, secret);
+ } else if(is_quic_ver_less_than(version, 28) ||
+ version == V_MVFST_27) {
+ err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_23,
+ sizeof(handshake_salt_draft_23),
+ cid, cid_len, secret);
+ } else {
+ err = hkdf_extract(GCRY_MD_SHA256, handshake_salt_draft_29,
+ sizeof(handshake_salt_draft_29),
+ cid, cid_len, secret);
+ }
+ if(err) {
+#ifdef DEBUG_CRYPT
+ printf("Failed to extract secrets: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ return -1;
+ }
+
+ if(!quic_hkdf_expand_label(GCRY_MD_SHA256, secret, sizeof(secret), "client in",
+ client_initial_secret, HASH_SHA2_256_LENGTH)) {
+#ifdef DEBUG_CRYPT
+ printf("Key expansion (client) failed: %s\n", __gcry_err(err, buferr, sizeof(buferr)));
+#endif
+ return -1;
+ }
+
+ return 0;
+}
+
+/*
+ * End Wireshark code
+ */
+
+
+static uint8_t *decrypt_initial_packet(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow,
+ const uint8_t *dest_conn_id, uint8_t dest_conn_id_len,
+ uint8_t source_conn_id_len, uint32_t version,
+ uint32_t *clear_payload_len)
+{
+ uint64_t token_length, payload_length, packet_number;
+ struct ndpi_packet_struct *packet = &flow->packet;
+ uint8_t first_byte;
+ uint32_t pkn32, pn_offset, pkn_len, offset;
+ quic_cipher cipher = {0}; /* Client initial cipher */
+ quic_decrypt_result_t decryption = {0};
+ uint8_t client_secret[HASH_SHA2_256_LENGTH];
+
+ if(quic_derive_initial_secrets(version, dest_conn_id, dest_conn_id_len,
+ client_secret) != 0) {
+ NDPI_LOG_DBG(ndpi_struct, "Error quic_derive_initial_secrets\n");
+ return NULL;
+ }
+
+ /* Packet numbers are protected with AES128-CTR,
+ Initial packets are protected with AEAD_AES_128_GCM. */
+ if(!quic_cipher_prepare(&cipher, GCRY_MD_SHA256,
+ GCRY_CIPHER_AES128, GCRY_CIPHER_MODE_GCM,
+ client_secret)) {
+ NDPI_LOG_DBG(ndpi_struct, "Error quic_cipher_prepare\n");
+ return NULL;
+ }
+
+ /* Type(1) + version(4) + DCIL + DCID + SCIL + SCID */
+ pn_offset = 1 + 4 + 1 + dest_conn_id_len + 1 + source_conn_id_len;
+ pn_offset += quic_len(&packet->payload[pn_offset], &token_length);
+ pn_offset += token_length;
+ /* Checks: quic_len reads 8 bytes, at most; quic_decrypt_header reads other 20 bytes */
+ if(pn_offset + 8 + (4 + 16) >= packet->payload_packet_len)
+ return NULL;
+ pn_offset += quic_len(&packet->payload[pn_offset], &payload_length);
+
+ NDPI_LOG_DBG2(ndpi_struct, "pn_offset %d token_length %d payload_length %d\n",
+ pn_offset, token_length, payload_length);
+
+ if(!quic_decrypt_header(&packet->payload[0], pn_offset, cipher.hp_cipher,
+ GCRY_CIPHER_AES128, &first_byte, &pkn32)) {
+ quic_cipher_reset(&cipher);
+ return NULL;
+ }
+ NDPI_LOG_DBG2(ndpi_struct, "first_byte 0x%x pkn32 0x%x\n", first_byte, pkn32);
+
+ pkn_len = (first_byte & 3) + 1;
+ /* TODO: is it always true in Initial Packets? */
+ packet_number = pkn32;
+
+ offset = pn_offset + pkn_len;
+ quic_decrypt_message(&cipher, &packet->payload[0], packet->payload_packet_len,
+ offset, first_byte, pkn_len, packet_number, &decryption);
+
+ quic_cipher_reset(&cipher);
+
+ if(decryption.data_len) {
+ *clear_payload_len = decryption.data_len;
+ return decryption.data;
+ }
+ return NULL;
+}
+
+#endif /* HAVE_LIBGCRYPT */
+
+
static const uint8_t *get_crypto_data(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow,
uint32_t version,
@@ -281,13 +974,45 @@ static uint8_t *get_clear_payload(struct ndpi_detection_module_struct *ndpi_stru
version, source_conn_id_len);
return NULL;
}
- /* TODO */
+#ifdef HAVE_LIBGCRYPT
+ const u_int8_t *dest_conn_id = &packet->payload[6];
+ clear_payload = decrypt_initial_packet(ndpi_struct, flow,
+ dest_conn_id, dest_conn_id_len,
+ source_conn_id_len, version,
+ clear_payload_len);
+#else
clear_payload = NULL;
+#endif
}
return clear_payload;
}
+static void process_tls(struct ndpi_detection_module_struct *ndpi_struct,
+ struct ndpi_flow_struct *flow,
+ const u_int8_t *crypto_data, uint32_t crypto_data_len)
+{
+ struct ndpi_packet_struct *packet = &flow->packet;
+
+ /* Overwriting packet payload */
+ u_int16_t p_len;
+ const u_int8_t *p;
+ p = packet->payload;
+ p_len = packet->payload_packet_len;
+ packet->payload = crypto_data;
+ packet->payload_packet_len = crypto_data_len;
+ processClientServerHello(ndpi_struct, flow, 1);
+
+ /* Restore */
+ packet->payload = p;
+ packet->payload_packet_len = p_len;
+
+ /* ServerHello is not needed to sub-classified QUIC, so we ignore it:
+ this way we lose JA3S and negotiated ciphers...
+ Negotiated version is only present in the ServerHello message too, but
+ fortunately, QUIC always uses TLS version 1.3 */
+ flow->protos.stun_ssl.ssl.ssl_version = 0x0304;
+}
static void process_chlo(struct ndpi_detection_module_struct *ndpi_struct,
struct ndpi_flow_struct *flow,
const u_int8_t *crypto_data, uint32_t crypto_data_len)
@@ -472,6 +1197,9 @@ void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct,
&crypto_data_len);
if(!crypto_data) {
NDPI_EXCLUDE_PROTO(ndpi_struct, flow);
+ if(is_version_with_encrypted_header(version)) {
+ ndpi_free(clear_payload);
+ }
return;
}
@@ -480,6 +1208,11 @@ void ndpi_search_quic(struct ndpi_detection_module_struct *ndpi_struct,
*/
if(is_version_gquic(version)) {
process_chlo(ndpi_struct, flow, crypto_data, crypto_data_len);
+ } else {
+ process_tls(ndpi_struct, flow, crypto_data, crypto_data_len);
+ }
+ if(is_version_with_encrypted_header(version)) {
+ ndpi_free(clear_payload);
}
}