diff --git a/README.md b/README.md index 156b71a5..f7477984 100644 --- a/README.md +++ b/README.md @@ -613,10 +613,27 @@ List of fields exported together with basic flow fields on interface by WG plugi ### QUIC List of fields exported together with basic flow fields on interface by quic plugin. - -| Output field | Type | Description | -|:------------------:|:------:|:-------------------------------:| -| QUIC_SNI | string | Decrypted server name | +`-with-quic-ch-full-tls-ext` enables extraction of all TLS extensions in the Client Hello. + +| Output field | Type | Description | +|:-------------------:|:--------:|:---------------------------------------------------------------------------------------------:| +| QUIC_SNI | string | Decrypted server name | +| QUIC_USER_AGENT | string | Decrypted user agent | +| QUIC_VERSION | uint32 | QUIC version from first server long header packets | +| QUIC_CLIENT_VERSION | uint32 | QUIC version from first client long header packet | +| QUIC_TOKEN_LENGTH | uint64 | Token length from Initial and Retry packets | +| QUIC_OCCID | bytes | Source Connection ID from first client packet | +| QUIC_OSCID | bytes | Destination Connection ID from first client packet | +| QUIC_SCID | bytes | Source Connection ID from first server packet | +| QUIC_RETRY_SCID | bytes | Source Connection ID from Retry packet | +| QUIC_MULTIPLEXED | uint8 | > 0 if multiplexed (at least two different QUIC_OSCIDs or SNIs) | +| QUIC_ZERO_RTT | uint8 | Number of 0-RTT packets in flow. | +| QUIC_SERVER_PORT | uint16 | TODO Server Port determined by packet type and TLS message | +| QUIC_PACKETS | uint8\* | QUIC long header packet type (v1 encoded), version negotiation, QUIC bit | +| QUIC_CH_PARSED | uint8 | >0 if TLS Client Hello parsed without errors | +| QUIC_TLS_EXT_TYPE | uint16\* | TLS extensions in the TLS Client Hello | +| QUIC_TLS_EXT_LEN | uint16\* | Length of each TLS extension | +| QUIC_TLS_EXT | string | Payload of all/application_layer_protocol_negotiation and quic_transport params TLS extension | ### ICMP diff --git a/configure.ac b/configure.ac index 83b626c8..0933f6d4 100644 --- a/configure.ac +++ b/configure.ac @@ -159,6 +159,12 @@ if [[ -z "$WITH_QUIC_TRUE" ]]; then AC_DEFINE([WITH_QUIC], [1], [Define to 1 if compile with quic plugin]) fi +AC_ARG_WITH([quic-ch-full-tls-ext], + AC_HELP_STRING([--with-quic-ch-full-tls-ext],[Extract all QUIC TLS payloads from the first client hello.]), + [ + CPPFLAGS="$CPPFLAGS -DQUIC_CH_FULL_TLS_EXT" + ] +) AM_CONDITIONAL(OS_CYGWIN, test x${host_os} = xcygwin) @@ -402,6 +408,8 @@ AC_ARG_WITH([msects], ) + + AM_CONDITIONAL(MAKE_RPMS, test x$RPMBUILD != x) AM_CONDITIONAL(MAKE_DEB, test x$DEBUILD != x) diff --git a/include/ipfixprobe/ipfix-elements.hpp b/include/ipfixprobe/ipfix-elements.hpp index 45ec4005..219233ab 100644 --- a/include/ipfixprobe/ipfix-elements.hpp +++ b/include/ipfixprobe/ipfix-elements.hpp @@ -240,6 +240,21 @@ namespace ipxp { #define QUIC_SNI(F) F(8057, 890, -1, nullptr) #define QUIC_USER_AGENT(F) F(8057, 891, -1, nullptr) #define QUIC_VERSION(F) F(8057, 892, 4, nullptr) +#define QUIC_CLIENT_VERSION(F) F(8057, 893, 4, nullptr) +#define QUIC_TOKEN_LENGTH(F) F(8057, 894, 8, nullptr) +#define QUIC_OCCID(F) F(8057, 895, -1, nullptr) +#define QUIC_OSCID(F) F(8057, 896, -1, nullptr) +#define QUIC_SCID(F) F(8057, 897, -1, nullptr) +#define QUIC_RETRY_SCID(F) F(8057, 898, -1, nullptr) +#define QUIC_MULTIPLEXED(F) F(8057, 899, 1, nullptr) +#define QUIC_ZERO_RTT(F) F(8057, 889, 1, nullptr) +#define QUIC_SERVER_PORT(F) F(8057, 887, 2, nullptr) +#define QUIC_PACKETS(F) F(0, 291, -1, nullptr) // BASIC LIST -- FIELD IS e8057id888 (uint16*) +#define QUIC_CH_PARSED(F) F(8057, 886, 1, nullptr) +#define QUIC_TLS_EXT_TYPE(F) F(0, 291, -1, nullptr) // BASIC LIST -- FIELD IS e8057id885 (uint16*) +#define QUIC_TLS_EXT_LEN(F) F(0, 291, -1, nullptr) // BASIC LIST -- FIELD IS e8057id884 (uint16*) +#define QUIC_TLS_EXT(F) F(8057, 883, -1, nullptr) + #define OSQUERY_PROGRAM_NAME(F) F(8057, 852, -1, nullptr) #define OSQUERY_USERNAME(F) F(8057, 853, -1, nullptr) @@ -496,7 +511,21 @@ namespace ipxp { #define IPFIX_QUIC_TEMPLATE(F) \ F(QUIC_SNI) \ F(QUIC_USER_AGENT) \ - F(QUIC_VERSION) + F(QUIC_VERSION) \ + F(QUIC_CLIENT_VERSION) \ + F(QUIC_TOKEN_LENGTH) \ + F(QUIC_OCCID) \ + F(QUIC_OSCID) \ + F(QUIC_SCID) \ + F(QUIC_RETRY_SCID) \ + F(QUIC_MULTIPLEXED) \ + F(QUIC_ZERO_RTT) \ + F(QUIC_SERVER_PORT) \ + F(QUIC_PACKETS) \ + F(QUIC_CH_PARSED) \ + F(QUIC_TLS_EXT_TYPE) \ + F(QUIC_TLS_EXT_LEN) \ + F(QUIC_TLS_EXT) #define IPFIX_OSQUERY_TEMPLATE(F) \ F(OSQUERY_PROGRAM_NAME) \ diff --git a/process/quic.cpp b/process/quic.cpp index e2fb770b..8f8e9da1 100644 --- a/process/quic.cpp +++ b/process/quic.cpp @@ -7,15 +7,14 @@ * \brief Plugin for enriching flows for quic data. * \author Andrej Lukacovic lukacan1@fit.cvut.cz * \author Karel Hynek - * \date 2022 + * \author Jonas Mücke + * \date 2023 */ - #ifdef WITH_NEMEA -# include +#include #endif - #include "quic.hpp" namespace ipxp { @@ -23,99 +22,517 @@ int RecordExtQUIC::REGISTERED_ID = -1; __attribute__((constructor)) static void register_this_plugin() { - static PluginRecord rec = PluginRecord("quic", [](){ - return new QUICPlugin(); - }); + static PluginRecord rec = PluginRecord("quic", []() { return new QUICPlugin(); }); - register_plugin(&rec); - RecordExtQUIC::REGISTERED_ID = register_extension(); + register_plugin(&rec); + RecordExtQUIC::REGISTERED_ID = register_extension(); } -QUICPlugin::QUICPlugin() +QUICPlugin::QUICPlugin() {} + +QUICPlugin::~QUICPlugin() { - quic_ptr = nullptr; + close(); } -QUICPlugin::~QUICPlugin() +void QUICPlugin::init(const char* params) {} + +void QUICPlugin::close() {} + +ProcessPlugin* QUICPlugin::copy() { - close(); + return new QUICPlugin(*this); } -void QUICPlugin::init(const char *params) -{ } +void QUICPlugin::set_cid_if_unset( + bool& set_flag, + uint8_t& src_id_length, + char* src_id, + uint8_t& dst_id_length, + char* dst_id) +{ + if (!set_flag) { + dst_id_length = src_id_length; + memcpy(dst_id, src_id, src_id_length); + set_flag = true; + } +} -void QUICPlugin::close() +void QUICPlugin::set_stored_cid_fields(RecordExtQUIC* quic_data, bool new_quic_flow) { - if (quic_ptr != nullptr) { - delete quic_ptr; - } - quic_ptr = nullptr; + if (!new_quic_flow & (quic_data->server_port != 0) & (quic_data->dir_dport != 0)) { + if (quic_data->dir_dport == quic_data->server_port) { + // to server + // Check for CH to make sure it is a server chosen CID + if (quic_data->client_hello_seen & quic_data->packet_from_server_seen) { + set_cid_if_unset( + quic_data->scid_set, + quic_data->dir_dcid_length, + quic_data->dir_dcid, + quic_data->scid_length, + quic_data->scid); + } + set_cid_if_unset( + quic_data->occid_set, + quic_data->dir_scid_length, + quic_data->dir_scid, + quic_data->occid_length, + quic_data->occid); + } else { + // from server + set_cid_if_unset( + quic_data->scid_set, + quic_data->dir_scid_length, + quic_data->dir_scid, + quic_data->scid_length, + quic_data->scid); + set_cid_if_unset( + quic_data->occid_set, + quic_data->dir_dcid_length, + quic_data->dir_dcid, + quic_data->occid_length, + quic_data->occid); + } + quic_data->dir_dport = 0; + + if (quic_data->dir_dport2 != 0) { + if (quic_data->dir_dport2 == quic_data->server_port) { + // to server + // Check for CH to make sure it is a server chosen CID + if (quic_data->client_hello_seen & quic_data->packet_from_server_seen) { + set_cid_if_unset( + quic_data->scid_set, + quic_data->dir_dcid_length2, + quic_data->dir_dcid2, + quic_data->scid_length, + quic_data->scid); + } + set_cid_if_unset( + quic_data->occid_set, + quic_data->dir_scid_length2, + quic_data->dir_scid2, + quic_data->occid_length, + quic_data->occid); + } else { + // from server + set_cid_if_unset( + quic_data->scid_set, + quic_data->dir_scid_length2, + quic_data->dir_scid2, + quic_data->scid_length, + quic_data->scid); + set_cid_if_unset( + quic_data->occid_set, + quic_data->dir_dcid_length2, + quic_data->dir_dcid2, + quic_data->occid_length, + quic_data->occid); + } + quic_data->dir_dport2 = 0; + } + } } -ProcessPlugin *QUICPlugin::copy() +void QUICPlugin::set_cid_fields( + RecordExtQUIC* quic_data, + Flow& rec, + QUICParser* process_quic, + int toServer, + bool new_quic_flow, + const Packet& pkt) { - return new QUICPlugin(*this); + uint8_t packets = get_packets_from_server(process_quic->quic_get_server_port(), rec); + + switch (toServer) { + case 1: + set_stored_cid_fields(quic_data, new_quic_flow); + + if ((!quic_data->scid_set) && (quic_data->packet_from_server_seen) + && (packets - 1 != quic_data->cnt_retry_packets)) { + process_quic->quic_get_dcid(quic_data->scid); + process_quic->quic_get_dcid_len(quic_data->scid_length); + quic_data->scid_set = true; + } + + if (!quic_data->occid_set) { + process_quic->quic_get_scid(quic_data->occid); + process_quic->quic_get_scid_len(quic_data->occid_length); + quic_data->occid_set = true; + } + break; + case 0: + set_stored_cid_fields(quic_data, new_quic_flow); + + if (!quic_data->occid_set) { + process_quic->quic_get_dcid(quic_data->occid); + process_quic->quic_get_dcid_len(quic_data->occid_length); + quic_data->occid_set = true; + } + + if ((!quic_data->scid_set) & quic_data->packet_from_server_seen) { + process_quic->quic_get_scid(quic_data->scid); + process_quic->quic_get_scid_len(quic_data->scid_length); + quic_data->scid_set = true; + } + + break; + case -1: + default: + // no direction information, store for future use + // We store only information from the first packet per direction + if ((quic_data->dir_dport != 0) && (quic_data->dir_dport2 == 0) + && (quic_data->dir_dport != pkt.dst_port)) { + // conditions first is set, second is unset, other direction than first. + // Store in secondary dataset + process_quic->quic_get_scid(quic_data->dir_scid2); + process_quic->quic_get_scid_len(quic_data->dir_scid_length2); + process_quic->quic_get_dcid(quic_data->dir_dcid2); + process_quic->quic_get_dcid_len(quic_data->dir_dcid_length2); + quic_data->dir_dport2 = pkt.dst_port; + } + + if (quic_data->dir_dport == 0) { + process_quic->quic_get_scid(quic_data->dir_scid); + process_quic->quic_get_scid_len(quic_data->dir_scid_length); + process_quic->quic_get_dcid(quic_data->dir_dcid); + process_quic->quic_get_dcid_len(quic_data->dir_dcid_length); + quic_data->dir_dport = pkt.dst_port; + } + break; + } } -bool QUICPlugin::process_quic(RecordExtQUIC *quic_data, const Packet &pkt) +int QUICPlugin::get_direction_to_server_and_set_port( + QUICParser* process_quic, + RecordExtQUIC* quic_data, + uint16_t parsed_port, + const Packet& pkt, + bool new_quic_flow) { - QUICParser process_quic; + int toServer = get_direction_to_server(parsed_port, quic_data, pkt, new_quic_flow); + if ((toServer != -1) && (quic_data->server_port == 0)) { + quic_data->server_port = process_quic->quic_get_server_port(); + } + if (toServer == 0) { + quic_data->packet_from_server_seen = true; + } + return toServer; +} - if (!process_quic.quic_start(pkt)) { - return false; - } else { - process_quic.quic_get_sni(quic_data->sni); - process_quic.quic_get_user_agent(quic_data->user_agent); - process_quic.quic_get_version(quic_data->quic_version); - return true; - } -} // QUICPlugin::process_quic +int QUICPlugin::get_direction_to_server( + uint16_t parsed_port, + RecordExtQUIC* quic_data, + const Packet& pkt, + bool new_quic_flow) +{ + if (parsed_port != 0) { + return pkt.dst_port == parsed_port; + } else if ((!new_quic_flow) & (quic_data->server_port != 0)) { + return pkt.dst_port == quic_data->server_port; + } + return -1; +} + +uint8_t QUICPlugin::get_packets_from_server(uint16_t server_port, Flow& rec) +{ + uint8_t packets = 0; + if (server_port == rec.src_port) { + packets = rec.src_packets; + } else { + packets = rec.dst_packets; + } + return packets; +} -int QUICPlugin::pre_create(Packet &pkt) +void QUICPlugin::set_client_hello_fields( + QUICParser* process_quic, + Flow& rec, + RecordExtQUIC* quic_data, + const Packet& pkt, + bool new_quic_flow) { - return 0; + process_quic->quic_get_token_length(quic_data->quic_token_length); + char dcid[MAX_CID_LEN] = {0}; + uint8_t dcid_len = 0; + // since this is a client hello the dcid must be set + process_quic->quic_get_dcid(dcid); + process_quic->quic_get_dcid_len(dcid_len); + + if ((quic_data->quic_token_length + != QUICParser::QUIC_CONSTANTS::QUIC_UNUSED_VARIABLE_LENGTH_INT) + && (quic_data->quic_token_length > 0) + && ((quic_data->retry_scid_length == dcid_len) + || (!new_quic_flow) && (quic_data->retry_scid_length == dcid_len)) + && ((strncmp(quic_data->retry_scid, dcid, std::min(quic_data->retry_scid_length, dcid_len)) + == 0) + || ((!new_quic_flow) + && (strncmp( + quic_data->retry_scid, + dcid, + std::min(quic_data->retry_scid_length, dcid_len))) + == 0))) { + // CH after Retry case: We already have all information from the previous CH. + + } else { + // MULTIPLEXING detection + char oscid[MAX_CID_LEN] = {0}; + uint8_t oscid_len = 0; + process_quic->quic_get_dcid(oscid); + process_quic->quic_get_dcid_len(oscid_len); + + char sni[BUFF_SIZE] = {0}; + process_quic->quic_get_sni(sni); + + if ((new_quic_flow) || (!quic_data->client_hello_seen) + || ((quic_data->client_hello_seen) + && ((strncmp(oscid, quic_data->oscid, oscid_len) == 0) || + // Case that the first response message from server is received and the client + // now uses that SCID as DCID. + (quic_data->packet_from_server_seen && oscid_len == (quic_data->scid_length) + && (strncmp(oscid, quic_data->scid, oscid_len) == 0))) + && (strncmp(quic_data->sni, sni, BUFF_SIZE) == 0))) { + // Repeated Initial or new Initial/QUIC flow + quic_data->server_port = process_quic->quic_get_server_port(); + + process_quic->quic_get_sni(quic_data->sni); + process_quic->quic_get_user_agent(quic_data->user_agent); + + if (!quic_data->oscid_set) { + process_quic->quic_get_dcid(quic_data->oscid); + process_quic->quic_get_dcid_len(quic_data->oscid_length); + quic_data->oscid_set = true; + } + + if (!quic_data->occid_set) { + process_quic->quic_get_scid(quic_data->occid); + process_quic->quic_get_scid_len(quic_data->occid_length); + quic_data->occid_set = true; + } + + // Set client version to extract difference in compatible version negotiation: RFC9368 + if (!quic_data->client_version_set) { + process_quic->quic_get_version(quic_data->quic_client_version); + quic_data->client_version_set = true; + } + } else { + if (quic_data->quic_multiplexed < 0xFF) { + quic_data->quic_multiplexed += 1; + } + } + } } -int QUICPlugin::post_create(Flow &rec, const Packet &pkt) +void QUICPlugin::set_packet_type(RecordExtQUIC* quic_data, Flow& rec, uint8_t packets) { - add_quic(rec, pkt); - return 0; + uint32_t pos = rec.src_packets + rec.dst_packets - 1; + if (pos < QUIC_MAX_ELEMCOUNT) { + quic_data->pkt_types[pos] = packets; + quic_data->last_pkt_type = pos; + } } -int QUICPlugin::pre_update(Flow &rec, Packet &pkt) +int QUICPlugin::process_quic( + RecordExtQUIC* quic_data, + Flow& rec, + const Packet& pkt, + bool new_quic_flow) { - return 0; + QUICParser process_quic; + + // Test for QUIC LH packet in UDP payload + if (process_quic.quic_check_quic_long_header_packet( + pkt, + quic_data->initial_dcid, + quic_data->initial_dcid_length)) { + uint32_t version; + process_quic.quic_get_version(version); + + // Get kinds of packet included in datagram + uint8_t packets = 0; + process_quic.quic_get_packets(packets); + + // Store all QUIC packet types contained in each packet + set_packet_type(quic_data, rec, packets); + + // A 0-RTT carries the same QUIC version as the Client Initial Hello. + // 0-RTT and compatible version negotiation is not defined. + // We ignore those cases because it might be defined in the future + if ((packets & QUICParser::PACKET_TYPE_FLAG::F_ZERO_RTT) == 0) { + quic_data->quic_version = version; + } + + // Simple version, more advanced information is available after Initial parsing + int toServer = get_direction_to_server_and_set_port( + &process_quic, + quic_data, + process_quic.quic_get_server_port(), + pkt, + new_quic_flow); + + if (packets & QUICParser::PACKET_TYPE_FLAG::F_ZERO_RTT) { + uint8_t zero_rtt_pkts = 0; + process_quic.quic_get_zero_rtt(zero_rtt_pkts); + + if ((uint16_t) zero_rtt_pkts + (uint16_t) quic_data->quic_zero_rtt > 0xFF) { + quic_data->quic_zero_rtt = 0xFF; + } else { + quic_data->quic_zero_rtt += zero_rtt_pkts; + } + } + uint8_t parsed_initial = 0; + + if (version == QUICParser::QUIC_VERSION::version_negotiation) { + set_cid_fields(quic_data, rec, &process_quic, toServer, new_quic_flow, pkt); + return FLOW_FLUSH; + } + + // export if parsed CH + quic_data->parsed_ch |= process_quic.quic_get_parsed_ch(); + + switch (process_quic.quic_get_packet_type()) { + case QUICParser::PACKET_TYPE::INITIAL: + process_quic.quic_get_parsed_initial(parsed_initial); + // Store DCID from first observed Initial packet. This is used in the crypto operations. + // Check length works because the first Initial must have a non-zero DCID. + if (quic_data->initial_dcid_length == 0) { + process_quic.quic_get_dcid_len(quic_data->initial_dcid_length); + process_quic.quic_get_dcid(quic_data->initial_dcid); + // Once established it can only be changed by a retry packet. + } + + if (parsed_initial && (process_quic.quic_get_tls_hs_type() == 1)) { + // Successful CH parsing + set_stored_cid_fields(quic_data, new_quic_flow); + set_client_hello_fields(&process_quic, rec, quic_data, pkt, new_quic_flow); + quic_data->client_hello_seen = true; + + if (!quic_data->tls_ext_type_set) { + process_quic.quic_get_tls_ext_type(quic_data->tls_ext_type); + process_quic.quic_get_tls_ext_type_len(quic_data->tls_ext_type_len); + quic_data->tls_ext_type_set = true; + } + + if (!quic_data->tls_ext_len_set) { + process_quic.quic_get_tls_extension_lengths(quic_data->tls_ext_len); + process_quic.quic_get_tls_extension_lengths_len(quic_data->tls_ext_len_len); + quic_data->tls_ext_len_set = true; + } + + if (!quic_data->tls_ext_set) { + process_quic.quic_get_tls_ext(quic_data->tls_ext); + process_quic.quic_get_tls_ext_len(quic_data->tls_ext_length); + quic_data->tls_ext_set = true; + } + break; + } + // Update accounting for information from CH, SH. + toServer = get_direction_to_server_and_set_port( + &process_quic, + quic_data, + process_quic.quic_get_server_port(), + pkt, + new_quic_flow); + // fallthrough to set cids + case QUICParser::PACKET_TYPE::HANDSHAKE: + // -1 sets stores intermediately. + set_cid_fields(quic_data, rec, &process_quic, toServer, new_quic_flow, pkt); + break; + case QUICParser::PACKET_TYPE::RETRY: + quic_data->cnt_retry_packets += 1; + /* + * A client MUST accept and process at most one Retry packet for each connection + * attempt. After the client has received and processed an Initial or Retry packet from + * the server, it MUST discard any subsequent Retry packets that it receives. + */ + if (quic_data->cnt_retry_packets == 1) { + // Additionally set token len + process_quic.quic_get_scid(quic_data->retry_scid); + process_quic.quic_get_scid_len(quic_data->retry_scid_length); + // Update DCID for decryption + process_quic.quic_get_dcid_len(quic_data->initial_dcid_length); + process_quic.quic_get_scid(quic_data->initial_dcid); + + process_quic.quic_get_token_length(quic_data->quic_token_length); + } + + if (!quic_data->occid_set) { + process_quic.quic_get_dcid(quic_data->occid); + process_quic.quic_get_dcid_len(quic_data->occid_length); + quic_data->occid_set = true; + } + + break; + case QUICParser::PACKET_TYPE::ZERO_RTT: + // Connection IDs are identical to Client Initial CH. The DCID might be OSCID at first + // and change to SCID later. We ignore the DCID. + if (!quic_data->occid_set) { + process_quic.quic_get_scid(quic_data->occid); + process_quic.quic_get_scid_len(quic_data->occid_length); + quic_data->occid_set = true; + } + break; + } + + return QUIC_DETECTED; + } else { + // Even if no QUIC detected store packets, which will only include the QUIC bit. + uint8_t packets = 0; + process_quic.quic_get_packets(packets); + set_packet_type(quic_data, rec, packets); + } + return QUIC_NOT_DETECTED; +} // QUICPlugin::process_quic + +int QUICPlugin::pre_create(Packet& pkt) +{ + return 0; } -int QUICPlugin::post_update(Flow &rec, const Packet &pkt) +int QUICPlugin::post_create(Flow& rec, const Packet& pkt) { - RecordExtQUIC *ext = (RecordExtQUIC *) rec.get_extension(RecordExtQUIC::REGISTERED_ID); + return add_quic(rec, pkt); +} - if (ext == nullptr) { - return 0; - } +int QUICPlugin::pre_update(Flow& rec, Packet& pkt) +{ + return 0; +} - add_quic(rec, pkt); - return 0; +int QUICPlugin::post_update(Flow& rec, const Packet& pkt) +{ + return add_quic(rec, pkt); } -void QUICPlugin::add_quic(Flow &rec, const Packet &pkt) +int QUICPlugin::add_quic(Flow& rec, const Packet& pkt) { - if (quic_ptr == nullptr) { - quic_ptr = new RecordExtQUIC(); - } + RecordExtQUIC* q_ptr = (RecordExtQUIC*) rec.get_extension(RecordExtQUIC::REGISTERED_ID); + bool new_qptr = false; + if (q_ptr == nullptr) { + new_qptr = true; + q_ptr = new RecordExtQUIC(); + } - if (process_quic(quic_ptr, pkt)) { - rec.add_extension(quic_ptr); - quic_ptr = nullptr; - } + int ret = process_quic(q_ptr, rec, pkt, new_qptr); + // Test if QUIC extension is not set + if (new_qptr && ((ret == QUIC_DETECTED) || (ret == FLOW_FLUSH))) { + rec.add_extension(q_ptr); + } + if (new_qptr && (ret == QUIC_NOT_DETECTED)) { + // If still no record delete q_ptr + delete q_ptr; + } + // Correct if QUIC has already been detected + if (!new_qptr && (ret == QUIC_NOT_DETECTED)) { + return QUIC_DETECTED; + } + return ret; } void QUICPlugin::finish(bool print_stats) { - if (print_stats) { - std::cout << "QUIC plugin stats:" << std::endl; - std::cout << " Parsed SNI: " << parsed_initial << std::endl; - } -} + if (print_stats) { + std::cout << "QUIC plugin stats:" << std::endl; + std::cout << " Parsed SNI: " << parsed_initial << std::endl; + } } +} // namespace ipxp diff --git a/process/quic.hpp b/process/quic.hpp index e0fb98cf..44db8551 100644 --- a/process/quic.hpp +++ b/process/quic.hpp @@ -7,131 +7,420 @@ * \brief Plugin for enriching flows for quic data. * \author Andrej Lukacovic lukacan1@fit.cvut.cz * \author Karel Hynek - * \date 2022 + * \author Jonas Mücke + * \date 2023 */ - #ifndef IPXP_PROCESS_QUIC_HPP #define IPXP_PROCESS_QUIC_HPP - #ifdef WITH_NEMEA -# include "fields.h" +#include "fields.h" #endif - #include "quic_parser.hpp" -#include +#include +#include #include +#include #include - namespace ipxp { -#define QUIC_UNIREC_TEMPLATE "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION" + +#define QUIC_UNIREC_TEMPLATE \ + "QUIC_SNI,QUIC_USER_AGENT,QUIC_VERSION,QUIC_CLIENT_VERSION,QUIC_TOKEN_LENGTH,QUIC_OCCID,QUIC_" \ + "OSCID,QUIC_SCID,QUIC_RETRY_SCID,QUIC_MULTIPLEXED,QUIC_ZERO_RTT,QUIC_SERVER_PORT,QUIC_" \ + "PACKETS,QUIC_CH_PARSED,QUIC_TLS_EXT_TYPE,QUIC_TLS_EXT_LEN,QUIC_TLS_EXT" + UR_FIELDS( - string QUIC_SNI, - string QUIC_USER_AGENT, - uint32 QUIC_VERSION -) + string QUIC_SNI, + string QUIC_USER_AGENT, + uint32 QUIC_VERSION, + uint32 QUIC_CLIENT_VERSION, + uint64 QUIC_TOKEN_LENGTH, + bytes QUIC_OCCID, + bytes QUIC_OSCID, + bytes QUIC_SCID, + bytes QUIC_RETRY_SCID, + uint8 QUIC_MULTIPLEXED, + uint8 QUIC_ZERO_RTT, + uint16 QUIC_SERVER_PORT, + uint8* QUIC_PACKETS, + uint8 QUIC_CH_PARSED, + uint16* QUIC_TLS_EXT_TYPE, + uint16* QUIC_TLS_EXT_LEN, + bytes QUIC_TLS_EXT) /** * \brief Flow record extension header for storing parsed QUIC packets. */ +#define QUIC_MAX_ELEMCOUNT 30 +#define MAX_CID_LEN 20 +#define QUIC_DETECTED 0 +#define QUIC_NOT_DETECTED 2 +#define QUIC_PKT_FIELD_ID 888 +#define QUIC_TLS_EXT_TYPE_FIELD_ID 885 +#define QUIC_TLS_EXT_LEN_FIELD_ID 884 + struct RecordExtQUIC : public RecordExt { - static int REGISTERED_ID; - char sni[BUFF_SIZE] = { 0 }; - char user_agent[BUFF_SIZE] = { 0 }; - uint32_t quic_version; - - RecordExtQUIC() : RecordExt(REGISTERED_ID) - { - sni[0] = 0; - user_agent[0] = 0; - quic_version = 0; - } - - #ifdef WITH_NEMEA - virtual void fill_unirec(ur_template_t *tmplt, void *record) - { - ur_set_string(tmplt, record, F_QUIC_SNI, sni); - ur_set_string(tmplt, record, F_QUIC_USER_AGENT, user_agent); - ur_set(tmplt, record, F_QUIC_VERSION, quic_version); - } - - const char *get_unirec_tmplt() const - { - return QUIC_UNIREC_TEMPLATE; - } - - #endif // ifdef WITH_NEMEA - - virtual int fill_ipfix(uint8_t *buffer, int size) - { - uint16_t len_sni = strlen(sni); - uint16_t len_user_agent = strlen(user_agent); - uint16_t len_version = sizeof(quic_version); - int pos = 0; - - if ((len_sni + 3) + (len_user_agent + 3) + len_version > size) { - return -1; - } - - pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) sni, len_sni); - pos += variable2ipfix_buffer(buffer + pos, (uint8_t *) user_agent, len_user_agent); - *(uint32_t *) (buffer + pos) = htonl(quic_version); - pos += len_version; - return pos; - } - - const char **get_ipfix_tmplt() const - { - static const char *ipfix_template[] = { - IPFIX_QUIC_TEMPLATE(IPFIX_FIELD_NAMES) - nullptr - }; - - return ipfix_template; - } - - std::string get_text() const - { - std::ostringstream out; - - out << "quicsni=\"" << sni << "\"" << "quicuseragent=\"" << user_agent << "\"" << "quicversion=\"" << - quic_version << "\""; - return out.str(); - } + static int REGISTERED_ID; + char sni[BUFF_SIZE] = {0}; + char user_agent[BUFF_SIZE] = {0}; + uint32_t quic_version; + uint32_t quic_client_version; + uint64_t quic_token_length; + // We use a char as a buffer. + uint8_t occid_length; + uint8_t oscid_length; + uint8_t scid_length; + uint8_t initial_dcid_length; + uint8_t dir_scid_length; + uint8_t dir_dcid_length; + uint8_t dir_scid_length2; + uint8_t dir_dcid_length2; + uint8_t retry_scid_length; + char occid[MAX_CID_LEN] = {0}; + char oscid[MAX_CID_LEN] = {0}; + char scid[MAX_CID_LEN] = {0}; + char initial_dcid[MAX_CID_LEN] = {0}; + char retry_scid[MAX_CID_LEN] = {0}; + // Intermediate storage when direction is not clear + char dir_scid[MAX_CID_LEN] = {0}; + char dir_dcid[MAX_CID_LEN] = {0}; + char dir_scid2[MAX_CID_LEN] = {0}; + char dir_dcid2[MAX_CID_LEN] = {0}; + uint16_t dir_dport; + uint16_t dir_dport2; + uint16_t server_port; + uint8_t cnt_retry_packets; + + uint8_t quic_multiplexed; + uint8_t quic_zero_rtt; + uint8_t pkt_types[QUIC_MAX_ELEMCOUNT]; + + uint16_t tls_ext_type[MAX_QUIC_TLS_EXT_LEN]; + uint16_t tls_ext_type_len; + bool tls_ext_type_set; + + uint16_t tls_ext_len[MAX_QUIC_TLS_EXT_LEN]; + uint8_t tls_ext_len_len; + bool tls_ext_len_set; + + char tls_ext[CURRENT_BUFFER_SIZE]; + uint16_t tls_ext_length; + bool tls_ext_set; + + uint8_t last_pkt_type; + + uint8_t parsed_ch; + + // Flags to ease decisions + bool occid_set; + bool oscid_set; + bool scid_set; + + bool client_version_set; + bool client_hello_seen; + bool packet_from_server_seen; + + RecordExtQUIC() + : RecordExt(REGISTERED_ID) + { + sni[0] = 0; + user_agent[0] = 0; + quic_version = 0; + quic_client_version = 0; + occid_length = 0; + oscid_length = 0; + scid_length = 0; + retry_scid_length = 0; + occid[0] = 0; + oscid[0] = 0; + scid[0] = 0; + retry_scid[0] = 0; + dir_dcid[0] = 0; + dir_scid[0] = 0; + dir_dcid_length = 0; + dir_scid_length = 0; + dir_dcid2[0] = 0; + dir_scid2[0] = 0; + dir_dcid_length2 = 0; + dir_scid_length2 = 0; + server_port = 0; + dir_dport = 0; + dir_dport2 = 0; + quic_token_length = QUICParser::QUIC_CONSTANTS::QUIC_UNUSED_VARIABLE_LENGTH_INT; + quic_multiplexed = 0; + quic_zero_rtt = 0; + memset(pkt_types, 0, sizeof(pkt_types)); + + memset(tls_ext_type, 0, sizeof(tls_ext_type)); + tls_ext_type_len = 0; + tls_ext_type_set = false; + + memset(tls_ext_len, 0, sizeof(tls_ext_len)); + tls_ext_len_len = 0; + tls_ext_len_set = false; + + memset(tls_ext, 0, sizeof(tls_ext)); + tls_ext_length = 0; + tls_ext_set = false; + + last_pkt_type = 0; + initial_dcid[0] = 0; + initial_dcid_length = 0; + parsed_ch = 0; + + occid_set = false; + oscid_set = false; + scid_set = false; + client_version_set = false; + cnt_retry_packets = 0; + + client_hello_seen = false; + packet_from_server_seen = false; + } + +#ifdef WITH_NEMEA + virtual void fill_unirec(ur_template_t* tmplt, void* record) + { + ur_set_string(tmplt, record, F_QUIC_SNI, sni); + ur_set_string(tmplt, record, F_QUIC_USER_AGENT, user_agent); + ur_set(tmplt, record, F_QUIC_VERSION, quic_version); + ur_set(tmplt, record, F_QUIC_CLIENT_VERSION, quic_client_version); + ur_set(tmplt, record, F_QUIC_TOKEN_LENGTH, quic_token_length); + ur_set_var(tmplt, record, F_QUIC_OCCID, occid, occid_length); + ur_set_var(tmplt, record, F_QUIC_OSCID, oscid, oscid_length); + ur_set_var(tmplt, record, F_QUIC_SCID, scid, scid_length); + ur_set_var(tmplt, record, F_QUIC_RETRY_SCID, retry_scid, retry_scid_length); + ur_set(tmplt, record, F_QUIC_MULTIPLEXED, quic_multiplexed); + ur_set(tmplt, record, F_QUIC_ZERO_RTT, quic_zero_rtt); + ur_set(tmplt, record, F_QUIC_SERVER_PORT, server_port); + ur_array_allocate(tmplt, record, F_QUIC_PACKETS, last_pkt_type + 1); + for (int i = 0; i < last_pkt_type + 1; i++) { + ur_array_set(tmplt, record, F_QUIC_PACKETS, i, pkt_types[i]); + } + ur_set(tmplt, record, F_QUIC_CH_PARSED, parsed_ch); + ur_array_allocate(tmplt, record, F_QUIC_TLS_EXT_TYPE, tls_ext_type_len); + for (int i = 0; i < tls_ext_type_len; i++) { + ur_array_set(tmplt, record, F_QUIC_TLS_EXT_TYPE, i, tls_ext_type[i]); + } + ur_array_allocate(tmplt, record, F_QUIC_TLS_EXT_LEN, tls_ext_len_len); + for (int i = 0; i < tls_ext_len_len; i++) { + ur_array_set(tmplt, record, F_QUIC_TLS_EXT_LEN, i, tls_ext_len[i]); + } + ur_set_var(tmplt, record, F_QUIC_TLS_EXT, tls_ext, tls_ext_length); + } + + const char* get_unirec_tmplt() const { return QUIC_UNIREC_TEMPLATE; } + +#endif // ifdef WITH_NEMEA + + virtual int fill_ipfix(uint8_t* buffer, int size) + { + IpfixBasicList basiclist; + basiclist.hdrEnterpriseNum = IpfixBasicList::CesnetPEM; + uint16_t len_sni = strlen(sni); + uint16_t len_user_agent = strlen(user_agent); + uint16_t len_version = sizeof(quic_version); + uint16_t len_client_version = sizeof(quic_client_version); + uint16_t len_token_length = sizeof(quic_token_length); + uint16_t len_multiplexed = sizeof(quic_multiplexed); + uint16_t len_zero_rtt = sizeof(quic_zero_rtt); + uint16_t pkt_types_len + = sizeof(pkt_types[0]) * (last_pkt_type + 1) + basiclist.HeaderSize(); + uint16_t len_server_port = sizeof(server_port); + uint16_t len_parsed_ch = sizeof(parsed_ch); + + uint16_t len_tls_ext_type + = sizeof(tls_ext_type[0]) * (tls_ext_type_len) + basiclist.HeaderSize(); + uint16_t len_tls_len = sizeof(tls_ext_len[0]) * (tls_ext_len_len) + basiclist.HeaderSize(); + uint16_t len_tls_ext = tls_ext_length + 3; + + uint32_t pos = 0; + + if ((len_sni + 3) + (len_user_agent + 3) + len_version + len_client_version + + len_token_length + len_multiplexed + len_zero_rtt + (scid_length + 3) + + (occid_length + 3) + (oscid_length + 3) + (retry_scid_length + 3) + + len_server_port + pkt_types_len + len_parsed_ch + len_tls_ext_type + len_tls_len + + len_tls_ext + 3 * basiclist.HeaderSize() + > size) { + return -1; + } + + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) sni, len_sni); + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) user_agent, len_user_agent); + *(uint32_t*) (buffer + pos) = htonl(quic_version); + pos += len_version; + *(uint32_t*) (buffer + pos) = htonl(quic_client_version); + pos += len_client_version; + *(uint64_t*) (buffer + pos) = htobe64(quic_token_length); + pos += len_token_length; + // original client connection ID + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) occid, occid_length); + // original server connection id + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) oscid, oscid_length); + // server connection id + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) scid, scid_length); + // retry server connection id + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) retry_scid, retry_scid_length); + + *(uint8_t*) (buffer + pos) = quic_multiplexed; + pos += 1; + + *(uint8_t*) (buffer + pos) = quic_zero_rtt; + pos += 1; + *(uint16_t*) (buffer + pos) = htons(server_port); + pos += len_server_port; + // Packet types + pos += basiclist.FillBuffer( + buffer + pos, + pkt_types, + (uint16_t) last_pkt_type + 1, + (uint16_t) QUIC_PKT_FIELD_ID); + + *(uint8_t*) (buffer + pos) = parsed_ch; + pos += 1; + + pos += basiclist.FillBuffer( + buffer + pos, + tls_ext_type, + (uint16_t) tls_ext_type_len, + (uint16_t) QUIC_TLS_EXT_TYPE_FIELD_ID); + pos += basiclist.FillBuffer( + buffer + pos, + tls_ext_len, + (uint16_t) tls_ext_len_len, + (uint16_t) QUIC_TLS_EXT_LEN_FIELD_ID); + pos += variable2ipfix_buffer(buffer + pos, (uint8_t*) tls_ext, tls_ext_length); + + return pos; + } + + const char** get_ipfix_tmplt() const + { + static const char* ipfix_template[] = {IPFIX_QUIC_TEMPLATE(IPFIX_FIELD_NAMES) nullptr}; + + return ipfix_template; + } + + std::string get_text() const + { + std::ostringstream out; + + out << "quicsni=\"" << sni << "\"" + << "quicuseragent=\"" << user_agent << "\"" + << "quicversion=\"" << quic_version << "\"" + << "quicclientversion=\"" << quic_client_version << "\"" + << "quicoccidlength=\"" << (int) occid_length << "\""; + out << "quicoccid=\""; + for (int i = 0; i < occid_length; i++) { + out << std::hex << (occid[i] & 0xff); + } + out << "\"" + << "quicoscidlength=\"" << std::dec << (int) oscid_length << "\""; + out << "quicoscid=\""; + for (int i = 0; i < oscid_length; i++) { + out << std::hex << (oscid[i] & 0xff); + } + out << "\"" + << "quicscidlength=\"" << std::dec << (int) scid_length << "\""; + out << "quicscid=\""; + for (int i = 0; i < scid_length; i++) { + out << std::hex << (scid[i] & 0xff); + } + out << "\"" + << "quicmultiplexed=\"" << std::dec << (int) quic_multiplexed << "\"" + << "quiczerortt=\"" << (int) quic_zero_rtt << "\"" + << "quicparsedch=\"" << (int) parsed_ch << "\""; + out << "quictlsexttype=("; + for (int i = 0; i < tls_ext_type_len; i++) { + out << std::dec << (uint16_t) tls_ext_type[i]; + if (i != tls_ext_type_len - 1) { + out << ","; + } + } + out << ")quictlsextlen=("; + for (int i = 0; i < tls_ext_len_len; i++) { + out << std::dec << (uint16_t) tls_ext_len[i]; + if (i != tls_ext_len_len - 1) { + out << ","; + } + } + out << ")quictlsext=\""; + for (int i = 0; i < tls_ext_length; i++) { + out << std::hex << std::setw(2) << std::setfill('0') << (uint16_t) tls_ext[i]; + } + out << "\""; + + return out.str(); + } }; /** * \brief Flow cache plugin for parsing QUIC packets. */ -class QUICPlugin : public ProcessPlugin -{ +class QUICPlugin : public ProcessPlugin { public: - QUICPlugin(); - ~QUICPlugin(); - void init(const char *params); - void close(); - RecordExt *get_ext() const { return new RecordExtQUIC(); } + QUICPlugin(); + ~QUICPlugin(); + void init(const char* params); + void close(); + RecordExt* get_ext() const { return new RecordExtQUIC(); } - OptionsParser *get_parser() const { return new OptionsParser("quic", "Parse QUIC traffic"); } + OptionsParser* get_parser() const { return new OptionsParser("quic", "Parse QUIC traffic"); } - std::string get_name() const { return "quic"; } + std::string get_name() const { return "quic"; } - ProcessPlugin *copy(); + ProcessPlugin* copy(); - int pre_create(Packet &pkt); - int post_create(Flow &rec, const Packet &pkt); - int pre_update(Flow &rec, Packet &pkt); - int post_update(Flow &rec, const Packet &pkt); - void add_quic(Flow &rec, const Packet &pkt); - void finish(bool print_stats); + int pre_create(Packet& pkt); + int post_create(Flow& rec, const Packet& pkt); + int pre_update(Flow& rec, Packet& pkt); + int post_update(Flow& rec, const Packet& pkt); + int add_quic(Flow& rec, const Packet& pkt); + void finish(bool print_stats); + void set_packet_type(RecordExtQUIC* quic_data, Flow& rec, uint8_t packets); private: - bool process_quic(RecordExtQUIC *, const Packet&); - int parsed_initial; - RecordExtQUIC *quic_ptr; + int process_quic(RecordExtQUIC*, Flow& rec, const Packet&, bool new_quic_flow); + void set_stored_cid_fields(RecordExtQUIC* quic_data, bool new_quic_flow); + void set_cid_fields( + RecordExtQUIC* quic_data, + Flow& rec, + QUICParser* process_quic, + int toServer, + bool new_quic_flow, + const Packet& pkt); + int get_direction_to_server( + uint16_t parsed_port, + RecordExtQUIC* quic_data, + const Packet& pkt, + bool new_quic_flow); + int get_direction_to_server_and_set_port( + QUICParser* process_quic, + RecordExtQUIC* quic_data, + uint16_t parsed_port, + const Packet& pkt, + bool new_quic_flow); + void set_client_hello_fields( + QUICParser* process_quic, + Flow& rec, + RecordExtQUIC* quic_data, + const Packet& pkt, + bool new_quic_flow); + void set_cid_if_unset( + bool& set_flag, + uint8_t& src_id_length, + char* src_id, + uint8_t& dst_id_length, + char* dst_id); + uint8_t get_packets_from_server(uint16_t server_port, Flow& rec); + + int parsed_initial; + RecordExtQUIC* quic_ptr; }; -} + +} // namespace ipxp + #endif /* IPXP_PROCESS_QUIC_HPP */ diff --git a/process/quic_parser.cpp b/process/quic_parser.cpp index 5ec7b4ee..2bc8554c 100644 --- a/process/quic_parser.cpp +++ b/process/quic_parser.cpp @@ -7,13 +7,12 @@ * \brief Class for parsing quic traffic. * \author Andrej Lukacovic lukacan1@fit.cvut.cz * \author Karel Hynek - * \date 2022 + * \author Jonas Mücke + * \date 2023 */ #include "quic_parser.hpp" -// #include "quic_variable_length.cpp" - #ifdef DEBUG_QUIC #define DEBUG_MSG(format, ...) fprintf(stderr, format, ##__VA_ARGS__) #else @@ -36,14 +35,91 @@ QUICParser::QUICParser() header_len = 0; payload_len = 0; + payload_len_offset = 0; dcid = nullptr; + dcid_len = 0; + initial_dcid = nullptr; + initial_dcid_len = 0; + scid = nullptr; + scid_len = 0; pkn = nullptr; sample = nullptr; salt = nullptr; final_payload = nullptr; parsed_initial = 0; is_version2 = false; + packet_type = UNKNOWN; + packets = 0; + token_length = QUIC_UNUSED_VARIABLE_LENGTH_INT; + zero_rtt = 0; + server_port = 0; + pkn_len = 0; + tls_hs_type = 0; + parsed_client_hello = false; + + memset(quic_tls_ext_type, 0, sizeof(quic_tls_ext_type)); + quic_tls_ext_type_pos = 0; + + memset(quic_tls_extension_lengths, 0, sizeof(quic_tls_extension_lengths)); + quic_tls_extension_lengths_pos = 0; + + memset(quic_tls_ext, 0, sizeof(quic_tls_ext)); + quic_tls_ext_pos = 0; +} +void QUICParser::quic_get_tls_ext_type(uint16_t* tls_ext_type_toset) +{ + // *2 since 16 bit instead of 8 + memcpy(tls_ext_type_toset, quic_tls_ext_type, quic_tls_ext_type_pos * 2); +} + +void QUICParser::quic_get_tls_ext_type_len(uint16_t& tls_ext_type_len_toset) +{ + tls_ext_type_len_toset = quic_tls_ext_type_pos; +} + +void QUICParser::quic_get_tls_ext(char* in) +{ + memcpy(in, quic_tls_ext, quic_tls_ext_pos); + return; +} + +void QUICParser::quic_get_tls_ext_len(uint16_t& tls_ext_len_toset) +{ + tls_ext_len_toset = quic_tls_ext_pos; +} + +void QUICParser::quic_get_tls_extension_lengths(uint16_t* tls_extensions_len) +{ + memcpy(tls_extensions_len, quic_tls_extension_lengths, quic_tls_extension_lengths_pos * 2); +} + +void QUICParser::quic_get_tls_extension_lengths_len(uint8_t& tls_extensions_length_len_toset) +{ + tls_extensions_length_len_toset = quic_tls_extension_lengths_pos; +} + +uint8_t QUICParser::quic_get_packet_type() +{ + return packet_type; +} + +uint8_t QUICParser::quic_get_parsed_ch() +{ + if (parsed_client_hello) { + return 1; + } + return 0; +} + +uint8_t QUICParser::quic_get_tls_hs_type() +{ + return tls_hs_type; +} + +void QUICParser::quic_get_zero_rtt(uint8_t& zero_rtt_toset) +{ + zero_rtt_toset = zero_rtt; } void QUICParser::quic_get_version(uint32_t& version_toset) @@ -52,6 +128,59 @@ void QUICParser::quic_get_version(uint32_t& version_toset) return; } +void QUICParser::quic_get_packets(uint8_t& packets_toset) +{ + packets_toset = packets; + return; +} + +void QUICParser::quic_get_token_length(uint64_t& token_len_toset) +{ + token_len_toset = token_length; + return; +} + +uint16_t QUICParser::quic_get_server_port() +{ + return server_port; +} + +void QUICParser::quic_get_parsed_initial(uint8_t& to_set) +{ + to_set = parsed_initial; + return; +} + +void QUICParser::quic_get_dcid_len(uint8_t& scid_length_toset) +{ + scid_length_toset = dcid_len; + return; +} + +void QUICParser::quic_get_scid_len(uint8_t& scid_length_toset) +{ + scid_length_toset = scid_len; + return; +} + +void QUICParser::quic_get_tls_extensions(char* in) +{ + memcpy(in, quic_tls_ext, quic_tls_ext_pos); + return; +} + +void QUICParser::quic_get_dcid(char* in) +{ + memcpy(in, dcid, dcid_len); + return; +} + +void QUICParser::quic_get_scid(char* in) +{ + memcpy(in, scid, scid_len); + return; +} + void QUICParser::quic_get_sni(char* in) { memcpy(in, sni, BUFF_SIZE); @@ -121,17 +250,48 @@ uint64_t QUICParser::quic_get_variable_length(const uint8_t* start, uint64_t& of bool QUICParser::quic_obtain_tls_data(TLSData& payload) { + quic_tls_extension_lengths_pos = 0; + quic_tls_ext_type_pos = 0; + quic_tls_ext_pos = 0; while (payload.start + sizeof(tls_ext) <= payload.end) { tls_ext* ext = (tls_ext*) payload.start; uint16_t type = ntohs(ext->type); uint16_t length = ntohs(ext->length); + // Store extension type + if (quic_tls_ext_type_pos < MAX_QUIC_TLS_EXT_LEN) { + quic_tls_ext_type[quic_tls_ext_type_pos] = type; + quic_tls_ext_type_pos += 1; + } + + // Store extension type length + if (quic_tls_extension_lengths_pos < MAX_QUIC_TLS_EXT_LEN) { + quic_tls_extension_lengths[quic_tls_extension_lengths_pos] = length; + quic_tls_extension_lengths_pos += 1; + } + + // payload.start += sizeof(tls_ext); if (payload.start + length > payload.end) { break; } + // Save value payload except for length + if (quic_tls_ext_pos + length < CURRENT_BUFFER_SIZE) { +#ifndef QUIC_CH_FULL_TLS_EXT + if (type == TLS_EXT_ALPN || type == TLS_EXT_QUIC_TRANSPORT_PARAMETERS_V1 + || type == TLS_EXT_QUIC_TRANSPORT_PARAMETERS + || type == TLS_EXT_QUIC_TRANSPORT_PARAMETERS_V2) { +#endif + memcpy(quic_tls_ext + quic_tls_ext_pos, payload.start, length); + quic_tls_ext_pos += length; +#ifndef QUIC_CH_FULL_TLS_EXT + } +#endif + } + + // Legacy extract specific fields if (type == TLS_EXT_SERVER_NAME && length != 0) { tls_parser.tls_get_server_name(payload, sni, BUFF_SIZE); } else if ( @@ -172,35 +332,89 @@ bool QUICParser::quic_parse_tls() if (!tls_parser.tls_check_ext_len(payload)) { return false; } - if (!quic_obtain_tls_data(payload)) { - return false; - } + // If no parameters were extracted. We also accept the QUIC connection. (no error check here) + quic_obtain_tls_data(payload); + return true; } // QUICPlugin::quic_parse_tls uint8_t QUICParser::quic_draft_version(uint32_t version) { + // Calculate potential draft version + uint8_t draftversion = (uint8_t) version & 0xff; // this is IETF implementation, older version used if ((version >> 8) == older_version) { - return (uint8_t) version; + if (draftversion >= 1 && draftversion <= 34) { + return (uint8_t) version; + } } + // This exists since version 29, but is still present in RFC9000. + if ((version & 0x0F0F0F0F) == force_ver_neg_pattern) { + // Version 1 + return 35; + } + + // Without further knowledge we assume QUIC version 1. + + // Last Nybble zero + switch (version & 0xfffffff0) { + case ms_quic: + return 29; + case ethz: + case telecom_italia: + case tencent_quic: + case quinn_noise: + case quic_over_scion: + return 35; + case moz_quic: + return 14; + } + + // Last Byte zero + switch (version & 0xffffff00) { + case quant: + return draftversion; + case quic_go: + case quicly: + return 35; + } + switch (version) { + case version_negotiation: + // TODO verify: We return a value that has no salt assigned. + return 1; // older mvfst version, but still used, based on draft 22, but salt 21 used + case (facebook_mvfst_old): + return 20; case (faceebook1): return 22; // more used atm, salt 23 used case faceebook2: + // 3 and 4 use default salt 23 according to mvfst: + // https://github.com/facebook/mvfst/blob/e89b990eaec5787a7dca7750362ea530e7703bdf/quic/handshake/HandshakeLayer.cpp#L27 + case facebook3: + case facebook4: case facebook_experimental: + case facebook_experimental2: + case facebook_experimental3: + case facebook_mvfst_alias: + case facebook_mvfst_alias2: return 27; - case (force_ver_neg_pattern & 0x0F0F0F0F): - return 29; - // version 2 draft 00 + case quic_newest: + return 35; + case picoquic1: + case picoquic2: + return 36; case q_version2_draft00: // newest case q_version2_newest: + is_version2 = true; return 100; - + case q_version2: + is_version2 = true; + return 101; + case facebook_v1_alias: default: return 255; } @@ -246,13 +460,19 @@ bool QUICParser::quic_obtain_version() static const uint8_t handshake_salt_v1[SALT_LENGTH] = {0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17, 0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a}; - static const uint8_t handshake_salt_v2[SALT_LENGTH] + static const uint8_t handshake_salt_v2_provisional[SALT_LENGTH] = {0xa7, 0x07, 0xc2, 0x03, 0xa5, 0x9b, 0x47, 0x18, 0x4a, 0x1d, 0x62, 0xca, 0x57, 0x04, 0x06, 0xea, 0x7a, 0xe3, 0xe5, 0xd3}; + static const uint8_t handshake_salt_v2[SALT_LENGTH] + = {0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93, + 0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9}; + // picoquic + static const uint8_t handshake_salt_picoquic_internal[SALT_LENGTH] + = {0x30, 0x67, 0x16, 0xd7, 0x63, 0x75, 0xd5, 0x55, 0x4b, 0x2f, + 0x60, 0x5e, 0xef, 0x78, 0xd8, 0x33, 0x3d, 0xc1, 0xca, 0x36}; if (version == version_negotiation) { DEBUG_MSG("Error, version negotiation\n"); - return false; } else if (!is_version2 && version == quic_newest) { salt = handshake_salt_v1; } else if (!is_version2 && quic_check_version(version, 9)) { @@ -267,7 +487,13 @@ bool QUICParser::quic_obtain_version() salt = handshake_salt_draft_23; } else if (!is_version2 && quic_check_version(version, 32)) { salt = handshake_salt_draft_29; + } else if (!is_version2 && quic_check_version(version, 35)) { + salt = handshake_salt_v1; + } else if (!is_version2 && quic_check_version(version, 36)) { + salt = handshake_salt_picoquic_internal; } else if (is_version2 && quic_check_version(version, 100)) { + salt = handshake_salt_v2_provisional; + } else if (is_version2 && quic_check_version(version, 101)) { salt = handshake_salt_v2; } else { DEBUG_MSG("Error, version not supported\n"); @@ -447,6 +673,12 @@ bool QUICParser::quic_derive_secrets(uint8_t* secret) bool QUICParser::quic_create_initial_secrets() { + // Set DCID if not set by previous packet + if (initial_dcid_len == 0) { + initial_dcid_len = dcid_len; + initial_dcid = (uint8_t*) dcid; + } + uint8_t extracted_secret[HASH_SHA2_256_LENGTH] = {0}; size_t extr_len = HASH_SHA2_256_LENGTH; @@ -480,7 +712,7 @@ bool QUICParser::quic_create_initial_secrets() EVP_PKEY_CTX_free(pctx); return false; } - if (1 != EVP_PKEY_CTX_set1_hkdf_key(pctx, dcid, quic_h1->dcid_len)) { + if (1 != EVP_PKEY_CTX_set1_hkdf_key(pctx, initial_dcid, initial_dcid_len)) { DEBUG_MSG("Error, key initialization failed(Extract)\n"); EVP_PKEY_CTX_free(pctx); return false; @@ -568,14 +800,13 @@ bool QUICParser::quic_encrypt_sample(uint8_t* plaintext) return true; } -bool QUICParser::quic_decrypt_header(const Packet& pkt) +bool QUICParser::quic_decrypt_initial_header(const uint8_t* payload_pointer, uint64_t offset) { uint8_t plaintext[SAMPLE_LENGTH]; uint8_t mask[5] = {0}; uint8_t full_pkn[4] = {0}; uint8_t first_byte = 0; uint32_t packet_number = 0; - uint8_t pkn_len; // https://www.rfc-editor.org/rfc/rfc9001.html#name-header-protection-applicati @@ -605,13 +836,17 @@ bool QUICParser::quic_decrypt_header(const Packet& pkt) // payload payload = payload + pkn_len; payload_len = payload_len - pkn_len; - header_len = payload - pkt.payload; + if (payload_len > CURRENT_BUFFER_SIZE) { + DEBUG_MSG("Payload length underflow\n"); + return false; + } + header_len = payload - payload_pointer; if (header_len > MAX_HEADER_LEN) { DEBUG_MSG("Header length too long\n"); return false; } - memcpy(tmp_header_mem, pkt.payload, header_len); + memcpy(tmp_header_mem, payload_pointer, header_len); header = tmp_header_mem; header[0] = first_byte; @@ -630,7 +865,7 @@ bool QUICParser::quic_decrypt_header(const Packet& pkt) initial_secrets.iv + sizeof(initial_secrets.iv) - 8, pntoh64(initial_secrets.iv + sizeof(initial_secrets.iv) - 8) ^ packet_number); return true; -} // QUICPlugin::quic_decrypt_header +} // QUICPlugin::quic_decrypt_initial_header bool QUICParser::quic_decrypt_payload() { @@ -639,14 +874,16 @@ bool QUICParser::quic_decrypt_payload() /* Input is --> "header || ciphertext (buffer) || auth tag (16 bytes)" */ - if (payload_len <= 16) { - DEBUG_MSG("Payload decryption error, ciphertext too short\n"); + if (payload_len <= 16 || payload_len > CURRENT_BUFFER_SIZE) { + DEBUG_MSG("Payload decryption error, ciphertext too short or long\n"); return false; } // https://datatracker.ietf.org/doc/html/draft-ietf-quic-tls-34#section-5.3 // "These cipher suites have a 16-byte authentication tag and produce an output 16 bytes larger // than their input." adjust length because last 16 bytes are authentication tag payload_len -= 16; + payload_len_offset = 16; + memcpy(&atag, &payload[payload_len], 16); EVP_CIPHER_CTX* ctx; @@ -761,14 +998,23 @@ inline void QUICParser::quic_skip_connection_close2(uint8_t* start, uint64_t& of return; } -inline void QUICParser::quic_copy_crypto(uint8_t* start, uint64_t& offset) +inline void QUICParser::quic_copy_crypto(uint8_t* start, const uint8_t* end, uint64_t& offset) { offset += 1; - uint16_t frame_offset = quic_get_variable_length(start, offset); - uint16_t frame_length = quic_get_variable_length(start, offset); + uint32_t frame_offset = quic_get_variable_length(start, offset); + uint32_t frame_length = quic_get_variable_length(start, offset); - frame_offset = std::min(frame_offset, (uint16_t) (CURRENT_BUFFER_SIZE - 1)); - frame_length = std::min((uint16_t) (CURRENT_BUFFER_SIZE - 1 - frame_offset), frame_length); + if (end < (start + offset)) { + // avoid source buffer overflow + quic_crypto_len += frame_length; + offset += frame_length; + return; + } + + frame_offset = std::min(frame_offset, (uint32_t) (CURRENT_BUFFER_SIZE - 1)); + frame_length = std::min((uint32_t) (CURRENT_BUFFER_SIZE - 1 - frame_offset), frame_length); + // avoid memory overlap in memcpy when not enought space in source buffer + frame_length = std::min(frame_length, (uint32_t) (end - (start + offset))); memcpy(assembled_payload + frame_offset, start + offset, frame_length); if (frame_offset < quic_crypto_start) { @@ -797,7 +1043,7 @@ bool QUICParser::quic_reassemble_frames() // https://www.rfc-editor.org/rfc/rfc9000.html#name-frames-and-frame-types // only those frames can occure in initial packets if (quic_check_frame_type(current, CRYPTO)) { - quic_copy_crypto(decrypted_payload, offset); + quic_copy_crypto(decrypted_payload, payload_end, offset); } else if (quic_check_frame_type(current, ACK1)) { quic_skip_ack1(decrypted_payload, offset); } else if (quic_check_frame_type(current, ACK2)) { @@ -833,43 +1079,179 @@ void QUICParser::quic_initialze_arrays() memset(tmp_header_mem, 0, MAX_HEADER_LEN); } +bool QUICParser::quic_check_long_header(uint8_t packet0) +{ + // We test for 1 in the fist position = long header + // We ignore the QUIC bit, as it might be greased + // https://datatracker.ietf.org/doc/html/rfc9287 + return (packet0 & 0x80) == 0x80; +} + bool QUICParser::quic_check_initial(uint8_t packet0) { - // version 1 (header form:long header(1) | fixed bit:fixed(1) | long packet type:initial(00) --> - // 1100 --> C) - if ((packet0 & 0xF0) == 0xC0) { - is_version2 = false; + // The fixed bit, might be greased. We assume greasing for all packets + // RFC 9287 + // version 1 (header form:long header(1) | fixed bit:fixed(1/0) | long packet type:initial(00) + // --> 1000 --> 8) + if ((packet0 & 0xB0) == 0x80) { return true; } - // version 2 (header form:long header(1) | fixed bit:fixed(1) | long packet type:initial(01) --> - // 1101 --> D) - else if ((packet0 & 0xF0) == 0xD0) { - is_version2 = true; + // version 2 (header form:long header(1) | fixed bit:fixed(1)/0 | long packet type:initial(01) + // --> 1001 --> 9) + else if (is_version2 && ((packet0 & 0xB0) == 0x90)) { return true; } else { return false; } } -bool QUICParser::quic_initial_checks(const Packet& pkt) +bool QUICParser::quic_check_min_initial_size(const Packet& pkt) { - // Port check, Initial packet check and UDP check - if (pkt.ip_proto != 17 || !quic_check_initial(pkt.payload[0]) || pkt.dst_port != 443) { - DEBUG_MSG("Packet is not Initial or does not contains LONG HEADER or is not on port 443\n"); + if (pkt.payload_len < QUIC_MIN_PACKET_LENGTH) { return false; } return true; } -bool QUICParser::quic_parse_header(const Packet& pkt) +uint32_t read_uint32_t(const uint8_t* ptr, uint8_t offset) { - const uint8_t* payload_pointer = pkt.payload; - uint64_t offset = 0; + uint32_t val; + memcpy(&val, ptr + offset, sizeof(uint32_t)); + return val; +} + +bool QUICParser::quic_check_supported_version(const uint32_t version) +{ + uint8_t draft_version = quic_draft_version(version); + return (draft_version > 0) && (draft_version < 255); +} + +bool QUICParser::quic_long_header_packet(const Packet& pkt) +{ + // UDP check, Initial packet check, QUIC min long header size, QUIC version check, + if (pkt.ip_proto != 17 || !quic_check_long_header(pkt.payload[0]) + || !(quic_check_min_initial_size(pkt)) + || !(quic_check_supported_version(ntohl(read_uint32_t(pkt.payload, 1))))) { + DEBUG_MSG( + "Packet is not Initial or does not contains LONG HEADER or is not long enough or is " + "not a supported QUIC version\n"); + return false; + } + return true; +} + +bool QUICParser::quic_parse_initial_header( + const Packet& pkt, + const uint8_t* payload_pointer, + const uint8_t* payload_end, + uint64_t& offset) +{ + token_length = quic_get_variable_length(payload_pointer, offset); + if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { + return false; + } + offset += token_length; + + if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { + return false; + } + + payload_len = quic_get_variable_length(payload_pointer, offset); + if (payload_len > CURRENT_BUFFER_SIZE) { + return false; + } + + if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { + return false; + } + + pkn = (payload_pointer + offset); + + payload = (payload_pointer + offset); + + // This should not cause an offset. + // offset += sizeof(uint8_t) * 4; + sample = (payload_pointer + offset + 4 * sizeof(uint8_t)); + + if (!quic_check_pointer_pos((payload_pointer + offset + 4 * sizeof(uint8_t)), payload_end)) { + return false; + } + return true; +} - const uint8_t* payload_end = payload_pointer + pkt.payload_len; +void QUICParser::quic_parse_quic_bit(uint8_t packet0) +{ + // Contains value of the first included QUIC bit (in the case of coalesced packets) + // Always the second msb. + // Note: no meaning if in Version negotiation. + + packets |= (packet0 & QUIC_BIT) << 1; +} + +void QUICParser::quic_parse_packet_type(uint8_t packet0) +{ + if (version == version_negotiation) { + packets |= F_VERSION_NEGOTIATION; + packet_type = VERSION_NEGOTIATION; + return; + } + + packet_type = (packet0 & 0b00110000) >> 4; + if (!is_version2) { + switch (packet_type) { + case 0b00: + packets |= F_INITIAL; + break; + case 0b01: + packets |= F_ZERO_RTT; + break; + case 0b10: + packets |= F_HANDSHAKE; + break; + case 0b11: + packets |= F_RETRY; + break; + } + } + if (is_version2) { + switch (packet_type) { + case 0b01: + packet_type = INITIAL; + packets |= F_INITIAL; + break; + case 0b10: + packet_type = ZERO_RTT; + packets |= F_ZERO_RTT; + break; + case 0b11: + packet_type = HANDSHAKE; + packets |= F_HANDSHAKE; + break; + case 0b00: + packet_type = RETRY; + packets |= F_RETRY; + break; + } + } +} + +bool QUICParser::quic_parse_header( + const Packet& pkt, + uint64_t& offset, + uint8_t* payload_pointer, + uint8_t* payload_end) +{ + if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { + return false; + } quic_h1 = (quic_first_ver_dcidlen*) (payload_pointer + offset); + if (!quic_check_long_header(quic_h1->first_byte)) { + // If not long header packet -> short header packet. Do not analyze. + return false; + } + if (!quic_obtain_version()) { DEBUG_MSG("Error, version not supported\n"); return false; @@ -882,7 +1264,12 @@ bool QUICParser::quic_parse_header(const Packet& pkt) } if (quic_h1->dcid_len != 0) { + if (quic_h1->dcid_len > MAX_CID_LEN) { + DEBUG_MSG("Recieved DCID longer than supported. dcid_len=%d \n", dcid_len); + return false; + } dcid = (payload_pointer + offset); + dcid_len = quic_h1->dcid_len; offset += quic_h1->dcid_len; } @@ -899,6 +1286,12 @@ bool QUICParser::quic_parse_header(const Packet& pkt) } if (quic_h2->scid_len != 0) { + if (quic_h2->scid_len > MAX_CID_LEN) { + DEBUG_MSG("Recieved SCID longer than supported. scid_len=%d \n", scid_len); + return false; + } + scid = (payload_pointer + offset); + scid_len = quic_h2->scid_len; offset += quic_h2->scid_len; } @@ -906,57 +1299,159 @@ bool QUICParser::quic_parse_header(const Packet& pkt) return false; } - uint64_t token_length = quic_get_variable_length(payload_pointer, offset); + quic_parse_packet_type(quic_h1->first_byte); - if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { - return false; - } + return true; +} - offset += token_length; +bool QUICParser::quic_parse_headers(const Packet& pkt, bool forceInitialParsing) +{ + uint8_t* pkt_payload_pointer = (uint8_t*) pkt.payload; + uint8_t* payload_pointer = pkt_payload_pointer; + uint64_t offset = 0; - if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { - return false; - } + uint8_t* pkt_payload_end = payload_pointer + pkt.payload_len; - payload_len = quic_get_variable_length(payload_pointer, offset); - if (payload_len > CURRENT_BUFFER_SIZE) { - return false; - } + // Handle coalesced packets + // 7 because (1B QUIC LH, 4B Version, 1 B SCID LEN, 1B DCID LEN) + uint64_t stored_payload_len; + while (pkt.payload + offset + QUIC_MIN_PACKET_LENGTH <= pkt.payload + pkt.payload_len) { + payload_pointer = pkt_payload_pointer + offset; - if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { - return false; - } + if (!quic_parse_header(pkt, offset, pkt_payload_pointer, pkt_payload_end)) { + break; + } - pkn = (payload_pointer + offset); + switch (packet_type) { + case ZERO_RTT: + payload_len = quic_get_variable_length(pkt_payload_pointer, offset); + if (zero_rtt < 0xFF) { + zero_rtt += 1; + } + offset += payload_len; + break; + case HANDSHAKE: + payload_len = quic_get_variable_length(pkt_payload_pointer, offset); + if (payload_len > CURRENT_BUFFER_SIZE) { + return false; + } + offset += payload_len; + break; + case INITIAL: + if (!quic_parse_initial_header(pkt, pkt_payload_pointer, pkt_payload_end, offset)) { + return false; + } + stored_payload_len = payload_len; + if (!parsed_initial) { + // Not yet parsed a CH, try to parse as CH with inherited DCID + quic_parse_initial(pkt, pkt_payload_pointer, offset); + // If still not parsed, try with DCID from current packet. + // Session resumption is such a case. + if (!parsed_initial) { + quic_tls_extension_lengths_pos = 0; + // len = 0 forces reading DCID from current packet + initial_dcid_len = 0; + // Increment by tag_len, since subsequent function is not stateless. + payload_len += payload_len_offset; + // Undo side effect from QUICParser::quic_decrypt_initial_header + payload = payload - pkn_len; + payload_len += pkn_len; + quic_parse_initial(pkt, pkt_payload_pointer, offset); + } + } + offset += stored_payload_len; + break; + case RETRY: + // 16 - Integrity tag + token_length = pkt_payload_end - payload_pointer - offset - 16; + if (!quic_check_pointer_pos((pkt_payload_pointer + offset), pkt_payload_end)) { + return false; + } + offset += token_length; + if (!quic_check_pointer_pos((pkt_payload_pointer + offset), pkt_payload_end)) { + return false; + } + break; + } - payload = (payload_pointer + offset); + if (!quic_set_server_port(pkt)) { + DEBUG_MSG("Error, extracting server port"); + return false; + } - offset += sizeof(uint8_t) * 4; - sample = (payload_pointer + offset); + if (packet_type == RETRY) { + break; + } + } - if (!quic_check_pointer_pos((payload_pointer + offset), payload_end)) { - return false; + // Update packet type to most specific, i.e., Initial + if (packets & F_INITIAL) { + packet_type = INITIAL; } - return true; + return packets; } // QUICPlugin::quic_parse_data -bool QUICParser::quic_start(const Packet& pkt) +bool QUICParser::quic_set_server_port(const Packet& pkt) +{ + tls_handshake hs = tls_parser.tls_get_handshake(); + + switch (packet_type) { + case INITIAL: + tls_hs_type = hs.type; + if (hs.type == 1) { + server_port = pkt.dst_port; + } else if (hs.type == 2) { + // Won't be reached, since we don't supply the OCCID to quic_parser + server_port = pkt.src_port; + } + // e.g. ACKs do not reveal direction + break; + case VERSION_NEGOTIATION: + case RETRY: + server_port = pkt.src_port; + break; + case ZERO_RTT: + server_port = pkt.dst_port; + break; + case HANDSHAKE: + // Does not reveal the direction + break; + } + return true; +} + +bool QUICParser::quic_check_quic_long_header_packet( + const Packet& pkt, + char* initial_packet_dcid, + uint8_t& initial_packet_dcid_length) { - if (!quic_initial_checks(pkt)) { + initial_dcid_len = initial_packet_dcid_length; + initial_dcid = (uint8_t*) initial_packet_dcid; + + quic_parse_quic_bit(pkt.payload[0]); + + if (!quic_long_header_packet(pkt)) { return false; } quic_initialze_arrays(); - if (!quic_parse_header(pkt)) { - DEBUG_MSG("Error, parsing header failed\n"); + if (!quic_parse_headers(pkt, false)) { return false; } + return true; +} + +bool QUICParser::quic_parse_initial( + const Packet& pkt, + const uint8_t* payload_pointer, + uint64_t offset) +{ if (!quic_create_initial_secrets()) { DEBUG_MSG("Error, creation of initial secrets failed (client side)\n"); return false; } - if (!quic_decrypt_header(pkt)) { + if (!quic_decrypt_initial_header(payload_pointer, offset)) { DEBUG_MSG("Error, header decryption failed (client side)\n"); return false; } @@ -972,6 +1467,21 @@ bool QUICParser::quic_start(const Packet& pkt) DEBUG_MSG("SNI and User Agent Extraction failed\n"); return false; } + + // 1 if CH or SH parsed + parsed_initial = 1; + + // According to RFC 9000 the server port will not change. + if (!quic_set_server_port(pkt)) { + DEBUG_MSG("Error, extracting server port"); + return false; + } + + if (tls_hs_type == TLS_HANDSHAKE_CLIENT_HELLO) { + parsed_client_hello = true; + } + return true; } + } // namespace ipxp diff --git a/process/quic_parser.hpp b/process/quic_parser.hpp index 23d3e156..629e9d48 100644 --- a/process/quic_parser.hpp +++ b/process/quic_parser.hpp @@ -7,7 +7,8 @@ * \brief Class for parsing quic traffic. * \author Andrej Lukacovic lukacan1@fit.cvut.cz * \author Karel Hynek - * \date 2022 + * \author Jonas Mücke + * \date 2023 */ #include "tls_parser.hpp" @@ -34,14 +35,23 @@ // first byte (1) + version (4) + dcid length (1) + dcid (20) + scid length (1) + scid (20) + // token length (variable so max is 8) + token (idk) + length (max 8) + pkt number (4) -// cant figure out if token length has any boundaries, teoretically 8 byte version of token length +// cant figure out if token length has any boundaries, theoretically 8 byte version of token length // means 2^64 as max length -// 67 (header basic data) + 100 (max token length) -#define MAX_HEADER_LEN 67 + 100 +// 67 (header basic data) + 256 (max token length) +// TODO(jmuecke) I increased the token length, because I observered token_lengths up to 128. Revisit +// if larger tokens are common. +#define MAX_HEADER_LEN 67 + 256 #define BUFF_SIZE 255 #define CURRENT_BUFFER_SIZE 1500 +// 8 because (1B QUIC LH, 4B Version, 1 B SCID LEN, 1B DCID LEN, Payload/Retry Token/Supported +// Version >= 1 B) +#define QUIC_MIN_PACKET_LENGTH 8 +#define MAX_CID_LEN 20 +#define QUIC_BIT 0b01000000 +#define MAX_QUIC_TLS_EXT_LEN 30 namespace ipxp { + typedef struct __attribute__((packed)) quic_first_ver_dcidlen { uint8_t first_byte; uint32_t version; @@ -86,23 +96,12 @@ class QUICParser { = sizeof("tls13 client in") + sizeof(uint16_t) + sizeof(uint8_t) + sizeof(uint8_t) }; - enum QUIC_VERSION { - older_version = 0xff0000, - faceebook1 = 0xfaceb001, - faceebook2 = 0xfaceb002, - facebook_experimental = 0xfaceb00e, - q_version2_draft00 = 0xff020000, - q_version2_newest = 0x709a50c4, - force_ver_neg_pattern = 0x0a0a0a0a, - version_negotiation = 0x00000000, - quic_newest = 0x00000001 - }; bool quic_initial_checks(const Packet&); void quic_initialze_arrays(); bool quic_check_initial(uint8_t); - bool quic_parse_header(const Packet&); + bool quic_check_long_header(uint8_t); bool quic_create_initial_secrets(); - bool quic_decrypt_header(const Packet&); + bool quic_decrypt_initial_header(const uint8_t* payload_pointer, uint64_t offset); bool quic_decrypt_payload(); bool quic_reassemble_frames(); bool quic_parse_tls(); @@ -113,13 +112,24 @@ class QUICParser { void quic_skip_ack2(uint8_t*, uint64_t&); void quic_skip_connection_close1(uint8_t*, uint64_t&); void quic_skip_connection_close2(uint8_t*, uint64_t&); - void quic_copy_crypto(uint8_t*, uint64_t&); + void quic_copy_crypto(uint8_t*, const uint8_t*, uint64_t&); bool quic_encrypt_sample(uint8_t*); + uint8_t quic_draft_version(uint32_t); uint64_t quic_get_variable_length(const uint8_t*, uint64_t&); bool quic_check_version(uint32_t, uint8_t); bool quic_check_pointer_pos(const uint8_t*, const uint8_t*); bool quic_obtain_tls_data(TLSData&); + bool quic_set_server_port(const Packet& pkt); + bool quic_check_min_initial_size(const Packet& pkt); + bool quic_check_supported_version(const uint32_t version); + bool quic_parser_tls_and_set_server_port(const Packet& pkt); + bool quic_parse_initial_header( + const Packet& pkt, + const uint8_t* payload_pointer, + const uint8_t* payload_end, + uint64_t& offset); + void quic_parse_packet_type(uint8_t packet0); Initial_Secrets initial_secrets; @@ -133,19 +143,44 @@ class QUICParser { uint16_t header_len; uint64_t payload_len; + uint8_t payload_len_offset; + uint8_t packet_type; const uint8_t* dcid; + uint8_t dcid_len; + uint8_t* initial_dcid; + uint8_t initial_dcid_len; + const uint8_t* scid; + uint8_t scid_len; const uint8_t* pkn; const uint8_t* sample; uint32_t version; + uint64_t token_length; + + uint8_t pkn_len; uint8_t decrypted_payload[CURRENT_BUFFER_SIZE]; uint8_t assembled_payload[CURRENT_BUFFER_SIZE]; uint8_t tmp_header_mem[MAX_HEADER_LEN]; uint8_t* final_payload; + uint8_t zero_rtt; + + uint16_t quic_tls_ext_type[MAX_QUIC_TLS_EXT_LEN]; + uint8_t quic_tls_ext_type_pos; + + uint16_t quic_tls_extension_lengths[MAX_QUIC_TLS_EXT_LEN]; + uint8_t quic_tls_extension_lengths_pos; + + char quic_tls_ext[CURRENT_BUFFER_SIZE]; + uint16_t quic_tls_ext_pos; + int parsed_initial; + bool parsed_client_hello; + uint16_t server_port; + bool direction_to_server; bool is_version2; + uint8_t tls_hs_type; char sni[BUFF_SIZE] = {0}; char user_agent[BUFF_SIZE] = {0}; @@ -154,13 +189,110 @@ class QUICParser { uint16_t quic_crypto_len; TLSParser tls_parser; + uint8_t packets; + public: + enum PACKET_TYPE { + INITIAL = 0b00, + ZERO_RTT = 0b01, + HANDSHAKE = 0b10, + RETRY = 0b11, + VERSION_NEGOTIATION = 0b111, + UNKNOWN = 0xFF + }; + enum PACKET_TYPE_FLAG { + F_INITIAL = 0b00000001, + F_ZERO_RTT = 0b00000010, + F_HANDSHAKE = 0b00000100, + F_RETRY = 0b00001000, + F_VERSION_NEGOTIATION = 0b00010000, + // We store the QUIC bit in the first bit of QUIC_PACKETS + // The following enum should not be used, unless for extraction + F_QUIC_BIT = 0b10000000 + }; + enum QUIC_CONSTANTS { QUIC_UNUSED_VARIABLE_LENGTH_INT = 0xFFFFFFFFFFFFFFFF }; + enum QUIC_VERSION { + // Full versions + faceebook1 = 0xfaceb001, + faceebook2 = 0xfaceb002, + facebook3 = 0xfaceb00d, + facebook4 = 0xfaceb00f, + facebook_experimental = 0xfaceb00e, + facebook_experimental2 = 0xfaceb011, + facebook_experimental3 = 0xfaceb013, + facebook_mvfst_old = 0xfaceb000, + facebook_mvfst_alias = 0xfaceb010, + facebook_mvfst_alias2 = 0xfaceb012, + facebook_v1_alias = 0xfaceb003, + q_version2_draft00 = 0xff020000, + q_version2_newest = 0x709a50c4, + q_version2 = 0x6b3343cf, + version_negotiation = 0x00000000, + quic_newest = 0x00000001, + picoquic1 = 0x50435130, + picoquic2 = 0x50435131, + // Patterns + force_ver_neg_pattern = 0x0a0a0a0a, + quant = 0x45474700, + older_version = 0xff0000, + quic_go = 0x51474f00, + // unknown handshake salt TODO use version 1 as default? + quicly = 0x91c17000, + // https://github.com/microsoft/msquic/blob/d33bc56d5e11db52e2b34ae152ea598fd6e935c0/src/core/packet.c#L461 + // But version is different + ms_quic = 0xabcd0000, + + ethz = 0xf0f0f0f0, + telecom_italia = 0xf0f0f1f0, + + moz_quic = 0xf123f0c0, + + tencent_quic = 0x07007000, + + quinn_noise = 0xf0f0f2f0, + + quic_over_scion = 0x5c100000 + }; + QUICParser(); - bool quic_start(const Packet&); + void quic_get_zero_rtt(uint8_t& zero_rtt_toset); + bool quic_parse_initial(const Packet&, const uint8_t* payload_end, uint64_t offset); void quic_get_sni(char* in); void quic_get_user_agent(char* in); void quic_get_version(uint32_t&); + void quic_get_token_length(uint64_t&); + void quic_get_dcid(char* in); + void quic_get_scid(char* in); + void quic_get_scid_len(uint8_t&); + void quic_get_dcid_len(uint8_t&); + void quic_get_parsed_initial(uint8_t&); + void quic_get_packets(uint8_t&); + uint8_t quic_get_parsed_ch(); + uint8_t quic_get_packet_type(); + uint16_t quic_get_server_port(); + bool quic_check_quic_long_header_packet( + const Packet& pkt, + char* initial_packet_dcid, + uint8_t& initial_packet_dcid_length); + bool quic_parse_headers(const Packet&, bool forceInitialParsing); + bool quic_parse_header( + const Packet& pkt, + uint64_t& offset, + uint8_t* payload_pointer, + uint8_t* payload_end); + bool quic_long_header_packet(const Packet& pkt); + uint8_t quic_get_tls_hs_type(); + void quic_get_tls_ext_len(uint16_t& tls_ext_len_toset); + void quic_get_tls_ext(char* in); + void quic_get_tls_ext_type_len(uint16_t& tls_ext_type_len_toset); + void quic_get_tls_ext_type(uint16_t* tls_ext_type_toset); + + void quic_parse_quic_bit(uint8_t packet0); + void quic_get_tls_extension_lengths(uint16_t* tls_extensions_len); + void quic_get_tls_extension_lengths_len(uint8_t& tls_extensions_length_len_toset); + void quic_get_tls_extensions(char* in); }; + } // namespace ipxp // known versions @@ -193,18 +325,3 @@ class QUICParser { * 0xfaceb011 -- MVFST_EXPERIMENTAL2 * 0xfaceb013 -- MVFST_EXPERIMENTAL3 */ - -// google salts - -/*static const uint8_t hanshake_salt_draft_q50[SALT_LENGTH] = { - * 0x50, 0x45, 0x74, 0xEF, 0xD0, 0x66, 0xFE, 0x2F, 0x9D, 0x94, - * 0x5C, 0xFC, 0xDB, 0xD3, 0xA7, 0xF0, 0xD3, 0xB5, 0x6B, 0x45 - * }; - * static const uint8_t hanshake_salt_draft_t50[SALT_LENGTH] = { - * 0x7f, 0xf5, 0x79, 0xe5, 0xac, 0xd0, 0x72, 0x91, 0x55, 0x80, - * 0x30, 0x4c, 0x43, 0xa2, 0x36, 0x7c, 0x60, 0x48, 0x83, 0x10 - * }; - * static const uint8_t hanshake_salt_draft_t51[SALT_LENGTH] = { - * 0x7a, 0x4e, 0xde, 0xf4, 0xe7, 0xcc, 0xee, 0x5f, 0xa4, 0x50, - * 0x6c, 0x19, 0x12, 0x4f, 0xc8, 0xcc, 0xda, 0x6e, 0x03, 0x3d - * };*/ diff --git a/tests/functional/reference/quic b/tests/functional/reference/quic index e1d99b34..8e1592b6 100644 --- a/tests/functional/reference/quic +++ b/tests/functional/reference/quic @@ -1,2 +1,2 @@ -2a00:1450:4014:800::2004,2001:718:2:62::2,1378,0,0,2021-08-01T13:25:53.513510,2021-08-01T13:25:53.513510,c4:b2:39:14:28:3f,a8:5e:45:b5:9c:d6,1,0,4278190109,443,58181,0,17,0,0,"www.google.com","Chrome/92.0.4515.107 Windows NT 10.0; Win64; x64" -ipaddr DST_IP,ipaddr SRC_IP,uint64 BYTES,uint64 BYTES_REV,uint64 LINK_BIT_FIELD,time TIME_FIRST,time TIME_LAST,macaddr DST_MAC,macaddr SRC_MAC,uint32 PACKETS,uint32 PACKETS_REV,uint32 QUIC_VERSION,uint16 DST_PORT,uint16 SRC_PORT,uint8 DIR_BIT_FIELD,uint8 PROTOCOL,uint8 TCP_FLAGS,uint8 TCP_FLAGS_REV,string QUIC_SNI,string QUIC_USER_AGENT +2a00:1450:4014:800::2004,2001:718:2:62::2,1378,0,0,55,2021-08-01T13:25:53.513510,2021-08-01T13:25:53.513510,c4:b2:39:14:28:3f,a8:5e:45:b5:9c:d6,1,0,4278190109,4278190109,443,443,58181,0,17,1,0,0,0,0,,d3e8e06049a4f6b2,[129],,,"www.google.com",00060568332d3239010480007530030245c0040480f000000504806000000604806000000704806000000802406409024067200480010000712702646a0f007129304368726f6d652f39322e302e343531352e3130372057696e646f7773204e542031302e303b2057696e36343b207836348000475204ff00001de6116524f56220fb079a3feec6b3af6c,"Chrome/92.0.4515.107 Windows NT 10.0; Win64; x64",[19|8|8|20|38|2|0|3|131|3|8|316],[0|10|16|13|51|45|42|43|65445|27|17513|41] +ipaddr DST_IP,ipaddr SRC_IP,uint64 BYTES,uint64 BYTES_REV,uint64 LINK_BIT_FIELD,uint64 QUIC_TOKEN_LENGTH,time TIME_FIRST,time TIME_LAST,macaddr DST_MAC,macaddr SRC_MAC,uint32 PACKETS,uint32 PACKETS_REV,uint32 QUIC_CLIENT_VERSION,uint32 QUIC_VERSION,uint16 DST_PORT,uint16 QUIC_SERVER_PORT,uint16 SRC_PORT,uint8 DIR_BIT_FIELD,uint8 PROTOCOL,uint8 QUIC_CH_PARSED,uint8 QUIC_MULTIPLEXED,uint8 QUIC_ZERO_RTT,uint8 TCP_FLAGS,uint8 TCP_FLAGS_REV,bytes QUIC_OCCID,bytes QUIC_OSCID,uint8* QUIC_PACKETS,bytes QUIC_RETRY_SCID,bytes QUIC_SCID,string QUIC_SNI,bytes QUIC_TLS_EXT,string QUIC_USER_AGENT,uint16* QUIC_TLS_EXT_LEN,uint16* QUIC_TLS_EXT_TYPE