quic: Squashed follow-ups: quic.log, tests, various fixes, performance

This commit is contained in:
Arne Welzel 2023-08-15 16:14:42 +02:00
parent 44d7c45723
commit 359f8d2ae6
65 changed files with 1194 additions and 532 deletions

View file

@ -1 +1,2 @@
@load ./consts
@load ./main

View file

@ -0,0 +1,7 @@
module QUIC;
export {
const version_strings: table[count] of string = {
[0x00000001] = "1",
} &default=function(version: count): string { return fmt("unknown-%x", version); };
}

View file

@ -1,2 +1,215 @@
##! Initial idea for a quic.log.
@load base/frameworks/notice/weird
@load base/protocols/conn/removal-hooks
@load ./consts
module QUIC;
export {
redef enum Log::ID += { LOG };
type Info: record {
## Timestamp of first QUIC packet for this entry.
ts: time &log;
## Unique ID for the connection.
uid: string &log;
## The connection's 4-tuple of endpoint addresses/ports.
id: conn_id &log;
## QUIC version as found in the first INITIAL packet from
## the client.
version: string &log;
## First Destination Connection ID used by client. This is
## random and unpredictable, but used for packet protection
## by client and server.
client_initial_dcid: string &log &optional;
## Server chosen Connection ID usually from server's first
## INITIAL packet. This is to be used by the client in
## subsequent packets.
server_scid: string &log &optional;
## Server name extracted from SNI extension in ClientHello
## packet if available.
server_name: string &log &optional;
## First protocol extracted from ALPN extension in ClientHello
## packet if available.
client_protocol: string &log &optional;
## Experimental QUIC history.
##
## Letters have the following meaning with client-sent
## letters being capitalized:
##
## ====== ====================================================
## Letter Meaning
## ====== ====================================================
## I INIT packet
## H HANDSHAKE packet
## Z 0RTT packet
## R RETRY packet
## C CONNECTION_CLOSE packet
## S SSL Client/Server Hello
## ====== ====================================================
history: string &log &default="";
# Internal state for the history field.
history_state: vector of string;
# Internal state if this record has already been logged.
logged: bool &default=F;
};
global log_quic: event(rec: Info);
global log_policy: Log::PolicyHook;
global finalize_quic: Conn::RemovalHook;
}
redef record connection += {
# XXX: We may have multiple QUIC connections with different
# Connection ID over the same UDP connection.
quic: Info &optional;
};
# Faster to modify here than re-compiling .evt files.
const quic_ports = {
443/udp, # HTTP3-over-QUIC
853/udp, # DNS-over-QUIC
784/udp, # DNS-over-QUIC early
};
function add_to_history(quic: Info, is_orig: bool, what: string)
{
if ( |quic$history_state| == 10 )
return;
quic$history_state += is_orig ? to_upper(what[0]) : to_lower(what[0]);
}
function log_record(quic: Info)
{
quic$history = join_string_vec(quic$history_state, "");
Log::write(LOG, quic);
quic$logged = T;
}
function set_conn(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
if ( ! c?$quic )
{
c$quic = Info(
$ts=network_time(),
$uid=c$uid,
$id=c$id,
$version=version_strings[version],
);
Conn::register_removal_hook(c, finalize_quic);
}
if ( is_orig && |dcid| > 0 && ! c$quic?$client_initial_dcid )
c$quic$client_initial_dcid = bytestring_to_hexstr(dcid);
if ( ! is_orig && |scid| > 0 )
c$quic$server_scid = bytestring_to_hexstr(scid);
}
event QUIC::initial_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
set_conn(c, is_orig, version, dcid, scid);
add_to_history(c$quic, is_orig, "INIT");
}
event QUIC::handshake_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
set_conn(c, is_orig, version, dcid, scid);
add_to_history(c$quic, is_orig, "HANDSHAKE");
}
event QUIC::zero_rtt_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
set_conn(c, is_orig, version, dcid, scid);
add_to_history(c$quic, is_orig, "ZeroRTT");
}
# RETRY packets trigger a log entry and state reset.
event QUIC::retry_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string, retry_token: string, integrity_tag: string)
{
if ( ! c?$quic )
set_conn(c, is_orig, version, dcid, scid);
add_to_history(c$quic, is_orig, "RETRY");
log_record(c$quic);
delete c$quic;
}
# Upon a connection_close_frame(), if any c$quic state is pending to be logged, do so
# now and prepare for a new entry.
event QUIC::connection_close_frame(c: connection, is_orig: bool, version: count, dcid: string, scid: string, error_code: count, reason_phrase: string)
{
if ( ! c?$quic )
return;
add_to_history(c$quic, is_orig, "CONNECTION_CLOSE");
log_record(c$quic);
delete c$quic;
}
event ssl_extension_server_name(c: connection, is_client: bool, names: string_vec) &priority=5
{
if ( is_client && c?$quic && |names| > 0 )
c$quic$server_name = names[0];
}
event ssl_extension_application_layer_protocol_negotiation(c: connection, is_client: bool, protocols: string_vec)
{
if ( c?$quic && is_client )
{
c$quic$client_protocol = protocols[0];
if ( |protocols| > 1 )
# Probably not overly weird, but the quic.log only
# works with the first one in the hope to avoid
# vector or concatenation.
Reporter::conn_weird("QUIC_many_protocols", c, cat(protocols));
}
}
event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec)
{
if ( ! c?$quic )
return;
add_to_history(c$quic, T, "SSL");
}
event ssl_server_hello(c: connection, version: count, record_version: count, possible_ts: time, server_random: string, session_id: string, cipher: count, comp_method: count) &priority=-5
{
if ( ! c?$quic )
return;
add_to_history(c$quic, F, "SSL");
}
hook finalize_quic(c: connection)
{
if ( ! c?$quic || c$quic$logged )
return;
log_record(c$quic);
}
event zeek_init()
{
Log::create_stream(LOG, [$columns=Info, $ev=log_quic, $path="quic", $policy=log_policy]);
Analyzer::register_for_ports(Analyzer::ANALYZER_QUIC, quic_ports);
}

View file

@ -1,5 +1,23 @@
spicy_add_analyzer(
NAME QUIC
PACKAGE_NAME QUIC
SOURCES decrypt_crypto.cc QUIC.spicy QUIC.evt zeek_QUIC.spicy
SCRIPTS __load__.zeek main.zeek)
SOURCES QUIC.spicy QUIC.evt
SCRIPTS __load__.zeek main.zeek
CXX_LINK ${CMAKE_CURRENT_BINARY_DIR}/libdecrypt_crypto.a)
add_dependencies(QUIC decrypt_crypto)
find_program(SPICY_CONFIG name spicy-config REQUIRED)
execute_process(
COMMAND ${SPICY_CONFIG} --include-dirs
OUTPUT_VARIABLE SPICY_INCLUDE_DIRS)
string(REPLACE " " ";" SPICY_INCLUDE_DIRS ${SPICY_INCLUDE_DIRS})
find_package(OpenSSL REQUIRED)
add_library(decrypt_crypto STATIC decrypt_crypto.cc)
set_target_properties(
decrypt_crypto PROPERTIES
CXX_STANDARD 17
POSITION_INDEPENDENT_CODE ON)
target_include_directories(decrypt_crypto PRIVATE "${OPENSSL_INCLUDE_DIR}" "${SPICY_INCLUDE_DIRS}")
target_link_libraries(decrypt_crypto ${OpenSSL_LIBRARIES})

View file

@ -1,10 +1,19 @@
protocol analyzer spicy::QUIC over UDP:
protocol analyzer QUIC over UDP:
parse originator with QUIC::RequestFrame,
parse responder with QUIC::ResponseFrame,
ports { 443/udp };
parse responder with QUIC::ResponseFrame;
import QUIC;
import Zeek_QUIC;
# TODO: Add actual events, instead of this dummy event
on QUIC::ResponseFrame -> event QUIC::example($conn);
# Make the enum available.
export QUIC::LongPacketType;
on QUIC::InitialPacket -> event QUIC::initial_packet($conn, $is_orig, self.header.version, self.header.dest_conn_id, self.header.src_conn_id);
on QUIC::RetryPacket -> event QUIC::retry_packet($conn, $is_orig, self.header.version, self.header.dest_conn_id, self.header.src_conn_id, self.retry_token, self.integrity_tag);
on QUIC::HandshakePacket -> event QUIC::handshake_packet($conn, $is_orig, self.header.version, self.header.dest_conn_id, self.header.src_conn_id);
on QUIC::ZeroRTTPacket -> event QUIC::zero_rtt_packet($conn, $is_orig, self.header.version, self.header.dest_conn_id, self.header.src_conn_id);
on QUIC::ConnectionClosePayload -> event QUIC::connection_close_frame($conn, $is_orig, self.header.version, self.header.dest_conn_id, self.header.src_conn_id,
self.error_code.result, self.reason_phrase);

View file

@ -4,26 +4,74 @@ import spicy;
import zeek;
# The interface to the C++ code that handles the decryption of the INITIAL packet payload using well-known keys
public function decrypt_crypto_payload(entire_packet: bytes, connection_id: bytes, encrypted_offset: uint64, payload_offset: uint64, from_client: bool): bytes &cxxname="decrypt_crypto_payload";
public function decrypt_crypto_payload(
all_data: bytes,
connection_id: bytes,
encrypted_offset: uint64,
payload_offset: uint64,
from_client: bool
): bytes &cxxname="QUIC_decrypt_crypto_payload";
##############
## Context - tracked in one connection
##############
type ConnectionIDInfo = unit {
var client_cid_len: uint8;
var server_cid_len: uint8;
var initial_destination_conn_id: bytes;
var initial_packets_exchanged: bool;
var initialized: bool;
# Can we decrypt?
function can_decrypt(long_header: LongHeaderPacket, context: ConnectionIDInfo, is_client: bool): bool {
on %init {
self.client_cid_len = 0;
self.server_cid_len = 0;
self.initial_packets_exchanged = False;
self.initialized = False;
}
if ( long_header.first_byte.packet_type != LongPacketType::INITIAL )
return False;
# decrypt_crypto_payload() has known secrets for version 1, nothing else.
if ( long_header.version != 0x00000001 )
return False;
if ( is_client )
return ! context.client_initial_processed;
# This is the responder, can only decrypt if we have an initial
# destination_id from the client
return context.client_initial_processed
&& |context.initial_destination_conn_id| > 0
&& ! context.server_initial_processed;
}
type ConnectionIDInfo = struct {
client_cid_len: uint8;
server_cid_len: uint8;
# The DCID used by the client is employed by client and
# server for packet protection. Packet re-ordering
# will make life miserable.
#
# https://quicwg.org/base-drafts/rfc9001.html#appendix-A
initial_destination_conn_id: bytes;
# Currently, this analyzer assumes that ClientHello
# and ServerHello fit into the first INITIAL packet (and
# that there is only one that we're interested in.
#
# But minimally the following section sounds like this might not
# hold in general and the Wireshark has samples showing
# the handshake spanning across more than two INITIAL packets.
# (quic-fragmented-handshakes.pcapng.gz)
#
# https://datatracker.ietf.org/doc/html/rfc9001#section-4.3
#
# Possible fix is to buffer up all CRYPTO frames across multiple
# INITIAL packets until we see a non-INITIAL frame.
#
# We also rely heavily on getting originator and responder right.
#
client_initial_processed: bool;
server_initial_processed: bool;
@if SPICY_VERSION >= 10800
ssl_handle: zeek::ProtocolHandle &optional;
@else
did_ssl_begin: bool;
@endif
};
##############
@ -80,74 +128,26 @@ type FrameType = enum {
# Helper units
##############
# Used to peek into the next byte and determine if it's a long or short packet
public type InitialByte = unit {
initialbyte: bitfield(8) {
header_form: 7 &convert=cast<HeaderForm>(cast<uint8>($$));
};
on %done{
self.backtrack();
}
};
# Used to peek into the next byte and check it's value
type InitialUint8 = unit {
var bt: uint8;
: uint8 {
self.bt = $$;
}
on %done{
self.backtrack();
}
};
# https://datatracker.ietf.org/doc/rfc9000/
# Section 16 and Appendix A
type VariableLengthIntegerLength = unit {
var length: uint8;
a: bitfield(8) {
length: 6..7 &convert=cast<uint8>($$) &byte-order=spicy::ByteOrder::Big;
};
on %done {
self.length = self.a.length;
self.backtrack();
}
};
type VariableLengthInteger = unit {
var bytes_to_parse: uint64;
var result: uint64;
var result_bytes: bytes;
: VariableLengthIntegerLength &try {
switch ( $$.length ) {
case 0:
self.bytes_to_parse = 1;
case 1:
self.bytes_to_parse = 2;
case 2:
self.bytes_to_parse = 4;
case 3:
self.bytes_to_parse = 8;
}
# Value of the two most significant bits indicates number of bytes
# to parse for the variable length integer.
#
# https://datatracker.ietf.org/doc/rfc9000/
# Section 16 and Appendix A
#
first_byte: bytes &size=1 {
local uint8_val = uint8($$.to_uint(spicy::ByteOrder::Big));
self.bytes_to_parse = 2**((0xC0 & uint8_val) >> 6);
# Re-pack without most significant two bits for later use.
self.first_byte = pack(0x3f & uint8_val, spicy::ByteOrder::Big);
}
remaining_bytes: bytes &size=self.bytes_to_parse - 1;
# Parse the required amount of bytes and apply a mask to clear the
# first two bits, leaving the actual length
remainder: bytes &size=self.bytes_to_parse {
switch ( self.bytes_to_parse ) {
case 1:
self.result = $$.to_uint(spicy::ByteOrder::Big) & 0x3f;
case 2:
self.result = $$.to_uint(spicy::ByteOrder::Big) & 0x3fff;
case 4:
self.result = $$.to_uint(spicy::ByteOrder::Big) & 0x3fffffff;
case 8:
self.result = $$.to_uint(spicy::ByteOrder::Big) & 0x3fffffffffffffff;
}
on %done {
self.result = (self.first_byte + self.remaining_bytes).to_uint(spicy::ByteOrder::Big);
}
};
@ -156,22 +156,7 @@ type VariableLengthInteger = unit {
# Generic units
##############
# Used to capture all data form the entire frame. May be inefficient, but works for now.
# This is passed to the decryption function, as this function needs both the header and the payload
# Performs a backtrack() at the end
type AllData = unit {
var data: bytes;
: bytes &eod {
self.data = $$;
}
on %done {
self.backtrack();
}
};
public type LongHeader = unit {
public type LongHeaderPacket = unit {
var encrypted_offset: uint64;
var payload_length: uint64;
var client_conn_id_length: uint8;
@ -179,7 +164,7 @@ public type LongHeader = unit {
first_byte: bitfield(8) {
header_form: 7 &convert=cast<HeaderForm>(cast<uint8>($$));
fixed_bit: 6;
fixed_bit: 6;
packet_type: 4..5 &convert=cast<LongPacketType>(cast<uint8>($$));
type_specific_bits: 0..3 &convert=cast<uint8>($$);
};
@ -190,84 +175,68 @@ public type LongHeader = unit {
src_conn_id_len: uint8 { self.client_conn_id_length = $$; }
src_conn_id: bytes &size=self.client_conn_id_length;
# We pass the type specific 4 bits too and don't parse them again
switch ( self.first_byte.packet_type ) {
LongPacketType::INITIAL -> initial_hdr : InitialLongPacketHeader(self.first_byte.type_specific_bits) {
self.encrypted_offset = self.offset() +
self.initial_hdr.payload_length.bytes_to_parse +
self.initial_hdr.token_length.bytes_to_parse +
LongPacketType::INITIAL -> initial_hdr : InitialPacket(self) {
self.encrypted_offset = self.offset() +
self.initial_hdr.length.bytes_to_parse +
self.initial_hdr.token_length.bytes_to_parse +
self.initial_hdr.token_length.result;
self.payload_length = self.initial_hdr.payload_length.result;
self.payload_length = self.initial_hdr.length.result;
}
LongPacketType::ZERO_RTT -> zerortt_hdr : ZeroRTTLongPacketHeader(self.first_byte.type_specific_bits);
LongPacketType::HANDSHAKE -> handshake_hdr : HandshakeLongPacketHeader(self.first_byte.type_specific_bits);
LongPacketType::RETRY -> retry_hdr : RetryLongPacketHeader(self.first_byte.type_specific_bits);
LongPacketType::ZERO_RTT -> zerortt_hdr : ZeroRTTPacket(self);
LongPacketType::HANDSHAKE -> handshake_hdr : HandshakePacket(self);
LongPacketType::RETRY -> retry_hdr : RetryPacket(self);
};
};
# Decrypted long packet payload that can actually be parsed
public type DecryptedLongPacketPayload = unit(packet_type: LongPacketType, from_client: bool) {
# A QUIC Frame.
public type Frame = unit(header: LongHeaderPacket, from_client: bool, crypto_sink: sink) {
frame_type : uint8 &convert=cast<FrameType>($$);
# TODO: add other FrameTypes as well
switch ( self.frame_type ) {
FrameType::ACK1 -> a: ACKPayload;
FrameType::ACK2 -> b: ACKPayload;
FrameType::CRYPTO -> c: CRYPTOPayload(from_client);
FrameType::PADDING -> d: PADDINGPayload;
FrameType::CRYPTO -> c: CRYPTOPayload(from_client) {
# Have the sink re-assemble potentially out-of-order cryptodata
crypto_sink.write(self.c.cryptodata, self.c.offset.result);
}
FrameType::CONNECTION_CLOSE1 -> : ConnectionClosePayload(header);
@if SPICY_VERSION >= 10800
FrameType::PADDING -> : skip /\x00*/; # eat the padding
@else
FrameType::PADDING -> : /\x00*/; # eat the padding
@endif
FrameType::PING -> : void;
* -> : void {
throw "unhandled frame type %s in %s" % (self.frame_type, header.first_byte.packet_type);
}
};
};
# TODO: investigate whether we can do something useful with this
public type EncryptedLongPacketPayload = unit {
payload: bytes &eod;
};
# Determines how to parse the long packet payload, depending on whether is was decrypted or not
public type LongPacketPayload = unit(packet_type: LongPacketType, from_client: bool, encrypted: bool) {
: DecryptedLongPacketPayload(packet_type, from_client) if (encrypted == False);
: EncryptedLongPacketPayload if (encrypted == True);
};
type CRYPTOPayload = unit(from_client: bool) {
var length_in_byte1: bytes;
var length_in_byte2: bytes;
offset: uint8;
offset: VariableLengthInteger;
length: VariableLengthInteger;
cryptodata: bytes &size=self.length.result;
on %done {
# As of 5 Sept. 2022 there is no function to convert a unsigned integer back to bytes.
# Therefore, the following (quite dirty) method is used. Should be fixed/improved whenever
# a better alternative is available.
# It converts a uint16 to its two-byte representation.
self.length_in_byte1 = ("%c" % cast<uint8>((self.length.result >> 8) & 0xff)).encode();
self.length_in_byte2 = ("%c" % cast<uint8>(self.length.result & 0xff)).encode();
# The data is passed to the SSL analyzer as part of a HANDSHAKE (0x16) message with TLS1.3 (\x03\x03).
# The 2 length bytes are also passed, followed by the actual CRYPTO blob which contains a CLIENT HELLO or SERVER HELLO
zeek::protocol_data_in(from_client, b"\x16\x03\x03" + self.length_in_byte1 + self.length_in_byte2 + self.cryptodata);
}
};
type ACKPayload = unit {
latest_ack: uint8;
ack_delay: uint8;
ack_range_count: uint8;
first_ack_range: uint8;
latest_ack: VariableLengthInteger;
ack_delay: VariableLengthInteger;
ack_range_count: VariableLengthInteger;
first_ack_range: VariableLengthInteger;
};
public type NullBytes = unit {
: (b"\x00")[];
x: InitialUint8 &try;
};
type PADDINGPayload = unit {
var padding_length: uint64 = 0;
# Simply consume all next nullbytes
: NullBytes;
type ConnectionClosePayload = unit(header: LongHeaderPacket) {
var header: LongHeaderPacket = header;
error_code: VariableLengthInteger;
switch {
-> unknown_frame_type: b"\x00";
-> frame_type: VariableLengthInteger;
};
reason_phrase_length: VariableLengthInteger;
reason_phrase: bytes &size=self.reason_phrase_length.result;
};
@ -276,35 +245,78 @@ type PADDINGPayload = unit {
# Specific long packet type units
##############
type InitialLongPacketHeader = unit(type_specific_bits: uint8) {
var packet_number_length_full: uint8;
# Remainder of an Initial packet
type InitialPacket = unit(header: LongHeaderPacket) {
var header: LongHeaderPacket = header;
token_length: VariableLengthInteger;
token: bytes &size=self.token_length.result;
payload_length: VariableLengthInteger;
packet_number: bytes &size=self.packet_number_length_full &convert=$$.to_uint(spicy::ByteOrder::Big);
on %init {
# Calculate the packet number length while the initial byte is still encoded.
# Will result in 0, 1, 2 or 3. So we need to read n+1 bytes to properly parse the header.
self.packet_number_length_full = (type_specific_bits & 0x03) + 1;
}
# 5.4.2. Header Protection Sample
#
# That is, in sampling packet ciphertext for header
# protection, the Packet Number field is assumed to
# be 4 bytes long (its maximum possible encoded length).
#
# Enforce 4 bytes Packet Number length + 16 bytes sample
# ciphertext available.
length: VariableLengthInteger &requires=self.length.result >= 20;
# Consume the remainder of payload. This
# includes the packet number field, but we
# do not know its length yet. We need the
# payload for sampling, however.
@if SPICY_VERSION >= 10800
payload: skip bytes &size=self.length.result;
@else
payload: bytes &size=self.length.result;
@endif
};
# TODO: implement
type ZeroRTTLongPacketHeader = unit(type_specific_bits: uint8) {};
type HandshakeLongPacketHeader = unit(type_specific_bits: uint8) {};
type RetryLongPacketHeader = unit(type_specific_bits: uint8) {};
type ZeroRTTPacket = unit(header: LongHeaderPacket) {
var header: LongHeaderPacket = header;
length: VariableLengthInteger;
@if SPICY_VERSION >= 10800
payload: skip bytes &size=self.length.result;
@else
payload: bytes &size=self.length.result;
@endif
};
type HandshakePacket = unit(header: LongHeaderPacket) {
var header: LongHeaderPacket = header;
length: VariableLengthInteger;
@if SPICY_VERSION >= 10800
payload: skip bytes &size=self.length.result;
@else
payload: bytes &size=self.length.result;
@endif
};
type RetryPacket = unit(header: LongHeaderPacket) {
var header: LongHeaderPacket = header;
var retry_token: bytes;
var integrity_tag: bytes;
# A retry packet ends with a 128bit / 16 byte integrity
# tag, but otherwise we do not know anything about the
# size of the retry_token. Slurp the whole datagram and
# post split it into the distinct parts.
data: bytes &eod {
self.retry_token = self.data.sub(0, |self.data| - 16);
self.integrity_tag = self.data.sub(|self.data| - 16, |self.data|);
}
};
##############
# Short packets
##############
# TODO: implement
public type ShortHeader = unit(dest_conn_id_length: uint8) {
public type ShortHeader = unit(dest_conn_id_length: uint8) {
first_byte: bitfield(8) {
header_form: 7 &convert=cast<HeaderForm>(cast<uint8>($$));
fixed_bit: 6;
fixed_bit: 6;
spin_bit: 5;
todo: 0..4;
};
@ -313,86 +325,193 @@ public type ShortHeader = unit(dest_conn_id_length: uint8) {
# TODO: investigate whether we can parse something useful out of this
public type ShortPacketPayload = unit {
@if SPICY_VERSION >= 10800
payload: skip bytes &eod;
@else
payload: bytes &eod;
@endif
};
# TODO: investigate whether we can do something useful with this
public type EncryptedLongPacketPayload = unit {
@if SPICY_VERSION >= 10800
payload: skip bytes &eod;
@else
payload: bytes &eod;
@endif
};
# Buffer all crypto messages (which might be fragmented and unordered)
# into the following unit.
type CryptoBuffer = unit() {
var buffered: bytes;
: bytes &chunked &eod {
self.buffered += $$;
# print "crypto_buffer got data", |$$|, |self.buffered|;
}
};
##############
# QUIC frame parsing
# QUIC packet parsing
#
# A UDP datagram contains one or more QUIC packets.
##############
type Frame = unit(from_client: bool, context: ConnectionIDInfo&) {
var hdr_form: HeaderForm;
type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
var decrypted_data: bytes;
var full_packet: bytes;
var start: iterator<stream>;
# Peek into the header to check if it's a SHORT or LONG header
: InitialByte &try {
self.hdr_form = $$.initialbyte.header_form;
sink crypto_sink;
var crypto_buffer: CryptoBuffer&;
# Attach an SSL analyzer to this connection once.
on %init {
@if SPICY_VERSION >= 10800
if ( ! context?.ssl_handle ) {
context.ssl_handle = zeek::protocol_handle_get_or_create("SSL");
}
@else
if ( ! context.did_ssl_begin ) {
zeek::protocol_begin("SSL");
context.did_ssl_begin = True;
}
@endif
self.start = self.input();
}
# Peek into the first byte and determine the header type.
first_byte: bitfield(8) {
header_form: 7 &convert=HeaderForm($$);
};
# TODO: Consider bitfield based look-ahead-parsing in the switch below
# to avoid this rewinding here. It's a hack.
: void {
self.set_input(self.start); # rewind
}
# Capture all the packet bytes if we're still have a chance of decrypting the INITIAL PACKETS
fpack: AllData &try if (context.initial_packets_exchanged == False);
# Depending on the header, parse it and update the src/dest ConnectionID's
switch ( self.hdr_form ) {
switch ( self.first_byte.header_form ) {
HeaderForm::SHORT -> short_header: ShortHeader(context.client_cid_len);
HeaderForm::LONG -> long_header: LongHeader {
HeaderForm::LONG -> long_header: LongHeaderPacket {
# For now, only allow a change of src/dest ConnectionID's for INITIAL packets.
# TODO: allow this for Retry packets
if ( self.long_header.first_byte.packet_type == LongPacketType::INITIAL
&& context.initial_packets_exchanged == False ) {
# If we see a retry packet from the responder, reset the decryption
# context such that the next DCID from the client is used for decryption.
if ( self.long_header.first_byte.packet_type == LongPacketType::RETRY ) {
context.client_initial_processed = False;
context.server_initial_processed = False;
context.initial_destination_conn_id = b"";
if ( from_client ) {
context.server_cid_len = self.long_header.dest_conn_id_len;
context.client_cid_len = self.long_header.src_conn_id_len;
# This means that here, we can try to decrypt the initial packet!
# All data is accessible via the `long_header` unit
self.decrypted_data = decrypt_crypto_payload(self.fpack.data,
self.long_header.dest_conn_id,
self.long_header.encrypted_offset,
self.long_header.payload_length,
from_client);
# Set this to be the seed for the decryption
if ( ! context.initial_packets_exchanged ) {
context.initial_destination_conn_id = self.long_header.dest_conn_id;
}
} else {
context.server_cid_len = self.long_header.src_conn_id_len;
context.client_cid_len = self.long_header.dest_conn_id_len;
# Assuming that the client set up the connection, this can be considered the first
# received Initial from the client. So disable change of ConnectionID's afterwards
self.decrypted_data = decrypt_crypto_payload(self.fpack.data,
context.initial_destination_conn_id,
self.long_header.encrypted_offset,
self.long_header.payload_length,
from_client);
}
# Allow re-opening the SSL analyzer the next time around.
@if SPICY_VERSION >= 10800
zeek::protocol_handle_close(context.ssl_handle);
unset context.ssl_handle;
@else
zeek::protocol_end();
context.did_ssl_begin = False;
@endif
}
}
};
# If it's a reply from the server and it's not a REPLY, we assume the keys are restablished and decryption is no longer possible
# TODO: verify if this is actually correct per RFC
if (self.long_header.first_byte.packet_type != LongPacketType::RETRY && ! from_client) {
context.initial_packets_exchanged = True;
}
# Slurp in the whole packet if we determined we have a chance to decrypt.
all_data: bytes &parse-at=self.start &eod if ( self?.long_header && can_decrypt(self.long_header, context, from_client) ) {
self.crypto_buffer = new CryptoBuffer();
self.crypto_sink.connect(self.crypto_buffer);
if ( from_client ) {
context.server_cid_len = self.long_header.dest_conn_id_len;
context.client_cid_len = self.long_header.src_conn_id_len;
# This means that here, we can try to decrypt the initial packet!
# All data is accessible via the `long_header` unit
self.decrypted_data = decrypt_crypto_payload(
self.all_data,
self.long_header.dest_conn_id,
self.long_header.encrypted_offset,
self.long_header.payload_length,
from_client
);
# Set this to be the seed for the decryption
if ( |context.initial_destination_conn_id| == 0 ) {
context.initial_destination_conn_id = self.long_header.dest_conn_id;
}
} else {
context.server_cid_len = self.long_header.src_conn_id_len;
context.client_cid_len = self.long_header.dest_conn_id_len;
# Assuming that the client set up the connection, this can be considered the first
# received Initial from the client. So disable change of ConnectionID's afterwards
self.decrypted_data = decrypt_crypto_payload(
self.all_data,
context.initial_destination_conn_id,
self.long_header.encrypted_offset,
self.long_header.payload_length,
from_client
);
}
};
# Depending on the type of header, we parse the remaining payload.
switch ( self.hdr_form ) {
HeaderForm::SHORT -> remaining_short_payload: ShortPacketPayload;
HeaderForm::LONG -> remaining_long_payload : LongPacketPayload(self.long_header.first_byte.packet_type, from_client, context.initial_packets_exchanged)[] &parse-from=self.decrypted_data;
};
# We attempted decryption, but it failed. Just reject the
# input and assume Zeek will disable the analyzer for this
# connection.
if ( |self.decrypted_data| == 0 )
throw "decryption failed";
on %init {
# Make sure to only attach the SSL analyzer once per QUIC connection
if ( ! context.initialized ) {
context.initialized = True;
zeek::protocol_begin("SSL");
# If this was a reply from the server and it's not a RETRY, we assume the keys
# are restablished and decryption is no longer possible
#
# TODO: verify if this is actually correct per RFC
if ( self.long_header.first_byte.packet_type != LongPacketType::RETRY && ! from_client ) {
context.server_initial_processed = True;
context.client_initial_processed = True;
}
}
# Depending on the type of header and whether we were able to decrypt
# some of it, parse the remaining payload.
: ShortPacketPayload if (self.first_byte.header_form == HeaderForm::SHORT);
: EncryptedLongPacketPayload if (self.first_byte.header_form == HeaderForm::LONG && |self.decrypted_data| == 0);
# If this was packet with a long header and decrypted data exists, attempt
# to parse the plain QUIC frames from it.
frames: Frame(self.long_header, from_client, self.crypto_sink)[] &parse-from=self.decrypted_data if (self.first_byte.header_form == HeaderForm::LONG && |self.decrypted_data| > 0);
# Once the Packet is fully parsed, pass the accumulated CRYPTO frames
# to the SSL analyzer as handshake data.
on %done {
# print "packet done", zeek::is_orig(), self.first_byte.header_form, |self.decrypted_data|;
if ( self.crypto_buffer != Null && |self.crypto_buffer.buffered| > 0 ) {
local handshake_data = self.crypto_buffer.buffered;
# The data is passed to the SSL analyzer as part of a HANDSHAKE (0x16) message with TLS1.3 (\x03\x03).
# The 2 length bytes are also passed, followed by the actual CRYPTO blob which contains a CLIENT HELLO or SERVER HELLO
local length_bytes = pack(cast<uint16>(|handshake_data|), spicy::ByteOrder::Big);
zeek::protocol_data_in(
from_client
, b"\x16\x03\x03" + length_bytes + handshake_data
# With Spicy 1.8.0, can use the SSL handle directly.
@if SPICY_VERSION >= 10800
, context.ssl_handle
@endif
);
# Stop decryption attempts after processing the very first
# INITIAL packet.
if ( from_client )
context.client_initial_processed = True;
else
context.server_initial_processed = True;
# Take buffered crypto data as confirmation signal.
spicy::accept_input();
}
}
};
@ -402,10 +521,10 @@ type Frame = unit(from_client: bool, context: ConnectionIDInfo&) {
##############
public type RequestFrame = unit {
%context = ConnectionIDInfo;
: Frame(True, self.context());
: Packet(True, self.context());
};
public type ResponseFrame = unit {
%context = ConnectionIDInfo;
: Frame(False, self.context());
: Packet(False, self.context());
};

View file

@ -8,29 +8,40 @@ refactors as C++ development is not our main profession.
*/
// Default imports
#include <stdlib.h>
#include <array>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <vector>
#include <iostream>
#include <string>
#include <vector>
// OpenSSL imports
#include <openssl/kdf.h>
#include <openssl/evp.h>
#include <openssl/kdf.h>
#include <openssl/sha.h>
// Import HILTI
#include <hilti/rt/libhilti.h>
namespace
{
// Struct to store decryption info for this specific connection
struct DecryptionInformation
{
std::vector<uint8_t> unprotected_header;
std::vector<uint8_t> protected_header;
uint64_t packet_number;
std::vector<uint8_t> nonce;
uint8_t packet_number_length;
};
{
std::vector<uint8_t> unprotected_header;
uint64_t packet_number;
std::vector<uint8_t> nonce;
uint8_t packet_number_length;
};
// Return rt::hilti::Bytes::data() value as const uint8_t*
//
// This should be alright: https://stackoverflow.com/a/15172304
inline const uint8_t* data_as_uint8(const hilti::rt::Bytes& b)
{
return reinterpret_cast<const uint8_t*>(b.data());
}
/*
Constants used in the HKDF functions. HKDF-Expand-Label uses labels
@ -39,41 +50,25 @@ calculated dynamically, but are incluced statically for now, as the
goal of this analyser is only to analyze the INITIAL packets.
*/
std::vector<uint8_t> INITIAL_SALT_V1 = {
0x38, 0x76, 0x2c, 0xf7, 0xf5,
0x59, 0x34, 0xb3, 0x4d, 0x17,
0x9a, 0xe6, 0xa4, 0xc8, 0x0c,
0xad, 0xcc, 0xbb, 0x7f, 0x0a};
std::vector<uint8_t> INITIAL_SALT_V1 = {0x38, 0x76, 0x2c, 0xf7, 0xf5, 0x59, 0x34, 0xb3, 0x4d, 0x17,
0x9a, 0xe6, 0xa4, 0xc8, 0x0c, 0xad, 0xcc, 0xbb, 0x7f, 0x0a};
std::vector<uint8_t> CLIENT_INITIAL_INFO = {
0x00, 0x20, 0x0f, 0x74, 0x6c,
0x73, 0x31, 0x33, 0x20, 0x63,
0x6c, 0x69, 0x65, 0x6e, 0x74,
0x20, 0x69, 0x6e, 0x00};
std::vector<uint8_t> CLIENT_INITIAL_INFO = {0x00, 0x20, 0x0f, 0x74, 0x6c, 0x73, 0x31,
0x33, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e,
0x74, 0x20, 0x69, 0x6e, 0x00};
std::vector<uint8_t> SERVER_INITIAL_INFO = {
0x00, 0x20, 0x0f, 0x74, 0x6c,
0x73, 0x31, 0x33, 0x20, 0x73,
0x65, 0x72, 0x76, 0x65, 0x72,
0x20, 0x69, 0x6e, 0x00};
std::vector<uint8_t> SERVER_INITIAL_INFO = {0x00, 0x20, 0x0f, 0x74, 0x6c, 0x73, 0x31,
0x33, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65,
0x72, 0x20, 0x69, 0x6e, 0x00};
std::vector<uint8_t> KEY_INFO = {
0x00, 0x10, 0x0e, 0x74, 0x6c,
0x73, 0x31, 0x33, 0x20, 0x71,
0x75, 0x69, 0x63, 0x20, 0x6b,
0x65, 0x79, 0x00};
std::vector<uint8_t> KEY_INFO = {0x00, 0x10, 0x0e, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20,
0x71, 0x75, 0x69, 0x63, 0x20, 0x6b, 0x65, 0x79, 0x00};
std::vector<uint8_t> IV_INFO = {
0x00, 0x0c, 0x0d, 0x74, 0x6c,
0x73, 0x31, 0x33, 0x20, 0x71,
0x75, 0x69, 0x63, 0x20, 0x69,
0x76, 0x00};
std::vector<uint8_t> IV_INFO = {0x00, 0x0c, 0x0d, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20,
0x71, 0x75, 0x69, 0x63, 0x20, 0x69, 0x76, 0x00};
std::vector<uint8_t> HP_INFO = {
0x00, 0x10, 0x0d, 0x74, 0x6c,
0x73, 0x31, 0x33, 0x20, 0x71,
0x75, 0x69, 0x63, 0x20, 0x68,
0x70, 0x00};
std::vector<uint8_t> HP_INFO = {0x00, 0x10, 0x0d, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20,
0x71, 0x75, 0x69, 0x63, 0x20, 0x68, 0x70, 0x00};
/*
Constants used by the different functions
@ -88,293 +83,233 @@ const size_t MAXIMUM_PACKET_LENGTH = 1500;
const size_t MAXIMUM_PACKET_NUMBER_LENGTH = 4;
/*
HKDF-Extract as decribed in https://www.rfc-editor.org/rfc/rfc8446.html#section-7.1
HKDF-Extract as described in https://www.rfc-editor.org/rfc/rfc8446.html#section-7.1
*/
std::vector<uint8_t> hkdf_extract(std::vector<uint8_t> connection_id)
{
std::vector<uint8_t> out_temp(INITIAL_SECRET_LEN);
size_t initial_secret_len = out_temp.size();
const EVP_MD *digest = EVP_sha256();
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
EVP_PKEY_derive_init(pctx);
EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY);
EVP_PKEY_CTX_set_hkdf_md(pctx, digest);
EVP_PKEY_CTX_set1_hkdf_key(pctx,
connection_id.data(),
connection_id.size());
EVP_PKEY_CTX_set1_hkdf_salt(pctx,
INITIAL_SALT_V1.data(),
INITIAL_SALT_V1.size());
EVP_PKEY_derive(pctx,
out_temp.data(),
reinterpret_cast<size_t *>(&initial_secret_len));
EVP_PKEY_CTX_free(pctx);
return out_temp;
}
std::vector<uint8_t> hkdf_extract(const hilti::rt::Bytes& connection_id)
{
std::vector<uint8_t> out_temp(INITIAL_SECRET_LEN);
size_t initial_secret_len = out_temp.size();
const EVP_MD* digest = EVP_sha256();
EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
EVP_PKEY_derive_init(pctx);
EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY);
EVP_PKEY_CTX_set_hkdf_md(pctx, digest);
EVP_PKEY_CTX_set1_hkdf_key(pctx, data_as_uint8(connection_id), connection_id.size());
EVP_PKEY_CTX_set1_hkdf_salt(pctx, INITIAL_SALT_V1.data(), INITIAL_SALT_V1.size());
EVP_PKEY_derive(pctx, out_temp.data(), &initial_secret_len);
EVP_PKEY_CTX_free(pctx);
return out_temp;
}
/*
HKDF-Expand-Label as decribed in https://www.rfc-editor.org/rfc/rfc8446.html#section-7.1
HKDF-Expand-Label as described in https://www.rfc-editor.org/rfc/rfc8446.html#section-7.1
that uses the global constant labels such as 'quic hp'.
*/
std::vector<uint8_t> hkdf_expand(size_t out_len,
std::vector<uint8_t> key,
std::vector<uint8_t> info)
{
std::vector<uint8_t> out_temp(out_len);
const EVP_MD *digest = EVP_sha256();
EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
EVP_PKEY_derive_init(pctx);
EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
EVP_PKEY_CTX_set_hkdf_md(pctx, digest);
EVP_PKEY_CTX_set1_hkdf_key(pctx, key.data(), key.size());
EVP_PKEY_CTX_add1_hkdf_info(pctx, info.data(), info.size());
EVP_PKEY_derive(pctx, out_temp.data(), &out_len);
EVP_PKEY_CTX_free(pctx);
return out_temp;
}
std::vector<uint8_t> hkdf_expand(size_t out_len, const std::vector<uint8_t>& key,
const std::vector<uint8_t>& info)
{
std::vector<uint8_t> out_temp(out_len);
const EVP_MD* digest = EVP_sha256();
EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
EVP_PKEY_derive_init(pctx);
EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
EVP_PKEY_CTX_set_hkdf_md(pctx, digest);
EVP_PKEY_CTX_set1_hkdf_key(pctx, key.data(), key.size());
EVP_PKEY_CTX_add1_hkdf_info(pctx, info.data(), info.size());
EVP_PKEY_derive(pctx, out_temp.data(), &out_len);
EVP_PKEY_CTX_free(pctx);
return out_temp;
}
/*
Removes the header protection from the INITIAL packet and returns a DecryptionInformation struct that is partially filled
Removes the header protection from the INITIAL packet and returns a DecryptionInformation struct
that is partially filled
*/
DecryptionInformation remove_header_protection(std::vector<uint8_t> client_hp, uint8_t encrypted_offset, std::vector<uint8_t> encrypted_packet)
{
DecryptionInformation decryptInfo;
int outlen;
auto cipher = EVP_aes_128_ecb();
auto ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, 1);
EVP_CIPHER_CTX_set_key_length(ctx, client_hp.size());
// Passing an 1 means ENCRYPT
EVP_CipherInit_ex(ctx, NULL, NULL, client_hp.data(), NULL, 1);
DecryptionInformation remove_header_protection(const std::vector<uint8_t>& client_hp,
uint64_t encrypted_offset,
const hilti::rt::Bytes& all_data)
{
DecryptionInformation decryptInfo;
int outlen;
auto cipher = EVP_aes_128_ecb();
auto ctx = EVP_CIPHER_CTX_new();
EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, 1);
EVP_CIPHER_CTX_set_key_length(ctx, client_hp.size());
// Passing an 1 means ENCRYPT
EVP_CipherInit_ex(ctx, NULL, NULL, client_hp.data(), NULL, 1);
std::vector<uint8_t> sample(encrypted_packet.begin() +
encrypted_offset +
MAXIMUM_PACKET_NUMBER_LENGTH,
static_assert(AEAD_SAMPLE_LENGTH > 0);
assert(all_data.size() >= encrypted_offset + MAXIMUM_PACKET_NUMBER_LENGTH + AEAD_SAMPLE_LENGTH);
encrypted_packet.begin() +
encrypted_offset +
MAXIMUM_PACKET_NUMBER_LENGTH +
AEAD_SAMPLE_LENGTH);
std::vector<uint8_t> mask(sample.size());
EVP_CipherUpdate(ctx, mask.data(), &outlen, sample.data(), AEAD_SAMPLE_LENGTH);
const uint8_t* sample = data_as_uint8(all_data) + encrypted_offset +
MAXIMUM_PACKET_NUMBER_LENGTH;
// To determine the actual packet number length,
// we have to remove the mask from the first byte
uint8_t first_byte = encrypted_packet[0];
std::array<uint8_t, AEAD_SAMPLE_LENGTH> mask;
EVP_CipherUpdate(ctx, mask.data(), &outlen, sample, AEAD_SAMPLE_LENGTH);
EVP_CIPHER_CTX_free(ctx);
if (first_byte & 0x80)
{
first_byte ^= mask[0] & 0x0F;
}
else
{
first_byte ^= first_byte & 0x1F;
}
// To determine the actual packet number length,
// we have to remove the mask from the first byte
uint8_t first_byte = data_as_uint8(all_data)[0];
// And now we can fully recover the correct packet number length...
int recovered_packet_number_length = (first_byte & 0x03) + 1;
if ( first_byte & 0x80 )
{
first_byte ^= mask[0] & 0x0F;
}
else
{
first_byte ^= first_byte & 0x1F;
}
// .. and use this to reconstruct the (partially) unprotected header
std::vector<uint8_t> unprotected_header(
encrypted_packet.begin(),
// And now we can fully recover the correct packet number length...
int recovered_packet_number_length = (first_byte & 0x03) + 1;
encrypted_packet.begin() +
encrypted_offset +
recovered_packet_number_length);
// .. and use this to reconstruct the (partially) unprotected header
std::vector<uint8_t> unprotected_header(data_as_uint8(all_data),
data_as_uint8(all_data) + encrypted_offset +
recovered_packet_number_length);
uint32_t decoded_packet_number = 0;
uint32_t decoded_packet_number = 0;
unprotected_header[0] = first_byte;
for (int i = 0; i < recovered_packet_number_length; ++i)
{
unprotected_header[encrypted_offset + i] ^= mask[1 + i];
decoded_packet_number =
unprotected_header[encrypted_offset + i] |
(decoded_packet_number << 8);
}
std::vector<uint8_t> protected_header(encrypted_packet.begin(),
encrypted_packet.begin() +
encrypted_offset +
recovered_packet_number_length);
unprotected_header[0] = first_byte;
for ( int i = 0; i < recovered_packet_number_length; ++i )
{
unprotected_header[encrypted_offset + i] ^= mask[1 + i];
decoded_packet_number = unprotected_header[encrypted_offset + i] |
(decoded_packet_number << 8);
}
// Store the information back in the struct
decryptInfo.packet_number = decoded_packet_number;
decryptInfo.packet_number_length = recovered_packet_number_length;
decryptInfo.protected_header = protected_header;
decryptInfo.unprotected_header = unprotected_header;
return decryptInfo;
}
// Store the information back in the struct
decryptInfo.packet_number = decoded_packet_number;
decryptInfo.packet_number_length = recovered_packet_number_length;
decryptInfo.unprotected_header = std::move(unprotected_header);
return decryptInfo;
}
/*
Calculate the nonce for the AEAD by XOR'ing the CLIENT_IV and the
decoded packet number, and returns the nonce
*/
std::vector<uint8_t> calculate_nonce(std::vector<uint8_t> client_iv, uint64_t packet_number)
{
std::vector<uint8_t> nonce = client_iv;
{
for ( int i = 0; i < 8; ++i )
client_iv[AEAD_IV_LEN - 1 - i] ^= (uint8_t)(packet_number >> 8 * i);
for (int i = 0; i < 8; ++i)
{
nonce[AEAD_IV_LEN - 1 - i] ^=
(uint8_t)(packet_number >> 8 * i);
}
// Return the nonce
return nonce;
}
return client_iv;
}
/*
Function that calls the AEAD decryption routine, and returns the
decrypted data
*/
std::vector<uint8_t> decrypt(std::vector<uint8_t> client_key,
std::vector<uint8_t> encrypted_packet,
uint64_t payload_offset,
DecryptionInformation decryptInfo)
{
int out, out2, res;
std::vector<uint8_t> encrypted_payload(
encrypted_packet.begin() +
decryptInfo.protected_header.size(),
encrypted_packet.begin() +
decryptInfo.protected_header.size() +
payload_offset -
decryptInfo.packet_number_length -
AEAD_TAG_LENGTH);
hilti::rt::Bytes decrypt(const std::vector<uint8_t>& client_key, const hilti::rt::Bytes& all_data,
uint64_t payload_length, const DecryptionInformation& decryptInfo)
{
int out, out2, res;
std::vector<uint8_t> tag_to_check(
encrypted_packet.begin() +
decryptInfo.protected_header.size() +
payload_offset -
decryptInfo.packet_number_length -
AEAD_TAG_LENGTH,
if ( payload_length < decryptInfo.packet_number_length + AEAD_TAG_LENGTH )
throw hilti::rt::RuntimeError(
hilti::rt::fmt("payload too small %ld < %ld", payload_length,
decryptInfo.packet_number_length + AEAD_TAG_LENGTH));
encrypted_packet.begin() +
decryptInfo.protected_header.size() +
payload_offset -
decryptInfo.packet_number_length);
const uint8_t* encrypted_payload = data_as_uint8(all_data) +
decryptInfo.unprotected_header.size();
unsigned char decrypt_buffer[MAXIMUM_PACKET_LENGTH];
int encrypted_payload_size = payload_length - decryptInfo.packet_number_length -
AEAD_TAG_LENGTH;
// Setup context
auto cipher = EVP_aes_128_gcm();
auto ctx = EVP_CIPHER_CTX_new();
if ( encrypted_payload_size < 0 )
throw hilti::rt::RuntimeError(
hilti::rt::fmt("encrypted_payload_size underflow %ld", encrypted_payload_size));
EVP_CipherInit_ex(ctx,
cipher,
NULL,
NULL,
NULL,
0);
if ( all_data.size() <
decryptInfo.unprotected_header.size() + encrypted_payload_size + AEAD_TAG_LENGTH )
throw hilti::rt::RuntimeError(
hilti::rt::fmt("all_data too short %ld < %ld", all_data.size(),
decryptInfo.unprotected_header.size() + encrypted_payload_size));
// Set the sizes for the IV and KEY
EVP_CIPHER_CTX_ctrl(ctx,
EVP_CTRL_CCM_SET_IVLEN,
decryptInfo.nonce.size(),
NULL);
const void* tag_to_check = all_data.data() + decryptInfo.unprotected_header.size() +
encrypted_payload_size;
int tag_to_check_length = AEAD_TAG_LENGTH;
EVP_CIPHER_CTX_set_key_length(ctx,
client_key.size());
std::array<uint8_t, MAXIMUM_PACKET_LENGTH> decrypt_buffer;
// Set the KEY and IV
EVP_CipherInit_ex(ctx,
NULL,
NULL,
client_key.data(),
decryptInfo.nonce.data(),
0);
// Setup context
auto cipher = EVP_aes_128_gcm();
auto ctx = EVP_CIPHER_CTX_new();
// Set the tag to be validated after decryption
EVP_CIPHER_CTX_ctrl(ctx,
EVP_CTRL_CCM_SET_TAG,
tag_to_check.size(),
tag_to_check.data());
EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, 0);
// Setting the second parameter to NULL will pass it as Associated Data
EVP_CipherUpdate(ctx,
NULL,
&out,
decryptInfo.unprotected_header.data(),
decryptInfo.unprotected_header.size());
// Set the sizes for the IV and KEY
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_IVLEN, decryptInfo.nonce.size(), NULL);
// Set the actual data to decrypt data into the decrypt_buffer. The amount of
// byte decrypted is stored into `out`
EVP_CipherUpdate(ctx,
decrypt_buffer,
&out,
encrypted_payload.data(),
encrypted_payload.size());
EVP_CIPHER_CTX_set_key_length(ctx, client_key.size());
// Validate whether the decryption was successful or not
EVP_CipherFinal_ex(ctx, NULL, &out2);
// Set the KEY and IV
EVP_CipherInit_ex(ctx, NULL, NULL, client_key.data(), decryptInfo.nonce.data(), 0);
// Copy the decrypted data from the decrypted buffer into a new vector and return this
// Use the `out` variable to only include relevant bytes
std::vector<uint8_t> decrypted_data(decrypt_buffer, decrypt_buffer + out);
return decrypted_data;
}
// Set the tag to be validated after decryption
EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_CCM_SET_TAG, tag_to_check_length,
const_cast<void*>(tag_to_check));
// Setting the second parameter to NULL will pass it as Associated Data
EVP_CipherUpdate(ctx, NULL, &out, decryptInfo.unprotected_header.data(),
decryptInfo.unprotected_header.size());
// Set the actual data to decrypt data into the decrypt_buffer. The amount of
// byte decrypted is stored into `out`
EVP_CipherUpdate(ctx, decrypt_buffer.data(), &out, encrypted_payload, encrypted_payload_size);
// Validate whether the decryption was successful or not
EVP_CipherFinal_ex(ctx, NULL, &out2);
EVP_CIPHER_CTX_free(ctx);
// Copy the decrypted data from the decrypted buffer into a Bytes instance.
return hilti::rt::Bytes(decrypt_buffer.data(), decrypt_buffer.data() + out);
}
}
/*
Function that is called from Spicy. It's a wrapper around `process_data`;
it stores all the passed data in a global struct and then calls `process_data`,
which will eventually return the decrypted data and pass it back to Spicy.
*/
hilti::rt::Bytes decrypt_crypto_payload(
const hilti::rt::Bytes &entire_packet,
const hilti::rt::Bytes &connection_id,
const hilti::rt::integer::safe<uint64_t> &encrypted_offset,
const hilti::rt::integer::safe<uint64_t> &payload_offset,
const hilti::rt::Bool &from_client)
{
hilti::rt::Bytes
QUIC_decrypt_crypto_payload(const hilti::rt::Bytes& all_data, const hilti::rt::Bytes& connection_id,
const hilti::rt::integer::safe<uint64_t>& encrypted_offset,
const hilti::rt::integer::safe<uint64_t>& payload_length,
const hilti::rt::Bool& from_client)
{
// Fill in the entire packet bytes
std::vector<uint8_t> e_pkt;
for (const auto &singlebyte : entire_packet)
{
e_pkt.push_back(singlebyte);
}
if ( payload_length < 20 )
throw hilti::rt::RuntimeError(hilti::rt::fmt("payload too small %ld < 20", payload_length));
std::vector<uint8_t> cnnid;
for (const auto &singlebyte : connection_id)
{
cnnid.push_back(singlebyte);
}
if ( (all_data.size() < encrypted_offset + payload_length) )
throw hilti::rt::RuntimeError(hilti::rt::fmt("packet too small %ld %ld", all_data.size(),
encrypted_offset + payload_length));
std::vector<uint8_t> initial_secret = hkdf_extract(cnnid);
std::vector<uint8_t> initial_secret = hkdf_extract(connection_id);
std::vector<uint8_t> server_client_secret;
if (from_client)
{
server_client_secret = hkdf_expand(INITIAL_SECRET_LEN,
initial_secret,
CLIENT_INITIAL_INFO);
}
else
{
server_client_secret = hkdf_expand(INITIAL_SECRET_LEN,
initial_secret,
SERVER_INITIAL_INFO);
}
std::vector<uint8_t> server_client_secret;
if ( from_client )
{
server_client_secret = hkdf_expand(INITIAL_SECRET_LEN, initial_secret, CLIENT_INITIAL_INFO);
}
else
{
server_client_secret = hkdf_expand(INITIAL_SECRET_LEN, initial_secret, SERVER_INITIAL_INFO);
}
std::vector<uint8_t> key = hkdf_expand(AEAD_KEY_LEN,
server_client_secret,
KEY_INFO);
std::vector<uint8_t> iv = hkdf_expand(AEAD_IV_LEN,
server_client_secret,
IV_INFO);
std::vector<uint8_t> hp = hkdf_expand(AEAD_HP_LEN,
server_client_secret,
HP_INFO);
std::vector<uint8_t> key = hkdf_expand(AEAD_KEY_LEN, server_client_secret, KEY_INFO);
std::vector<uint8_t> iv = hkdf_expand(AEAD_IV_LEN, server_client_secret, IV_INFO);
std::vector<uint8_t> hp = hkdf_expand(AEAD_HP_LEN, server_client_secret, HP_INFO);
DecryptionInformation decryptInfo = remove_header_protection(hp, (uint8_t)encrypted_offset, e_pkt);
DecryptionInformation decryptInfo = remove_header_protection(hp, encrypted_offset, all_data);
// Calculate the correct nonce for the decryption
decryptInfo.nonce = calculate_nonce(iv, decryptInfo.packet_number);
// Calculate the correct nonce for the decryption
decryptInfo.nonce = calculate_nonce(iv, decryptInfo.packet_number);
std::vector<uint8_t> decrypted_data = decrypt(key, e_pkt, payload_offset, decryptInfo);
// Return it as hilti Bytes again
hilti::rt::Bytes decr(decrypted_data.begin(), decrypted_data.end());
return decr;
}
return decrypt(key, all_data, payload_length, decryptInfo);
}

View file

@ -1,12 +0,0 @@
module Zeek_QUIC;
import zeek;
import QUIC;
on QUIC::ResponseFrame::%done {
zeek::confirm_protocol();
}
on QUIC::ResponseFrame::%error {
zeek::reject_protocol("error while parsing QUIC message");
}

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 Dd quic,ssl

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path quic
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 82.239.54.117 53727 110.213.53.115 443 1 95412c47018cdfe8 d5412c47018cdfe8 api.cirrus-ci.com h3 ISisH
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path ssl
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert
#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 82.239.54.117 53727 110.213.53.115 443 TLSv13 TLS_AES_128_GCM_SHA256 x25519 api.cirrus-ci.com T - - F Cs - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 Dd quic,ssl

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path quic
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.17.0.2 34347 64.233.166.94 443 1 815d62c70884f4b51e8ccadd5beed372 c15d62c70884f4b5 www.google.de h3 ISishIhHhh
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path ssl
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert
#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 172.17.0.2 34347 64.233.166.94 443 TLSv13 TLS_AES_128_GCM_SHA256 x25519 www.google.de F - - F Cs - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,46 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
1.0, initial_packet, C4J4Th3PJpwUYZZ6gc, T, 1, 4a8294bf9201d6cf,
1.0, retry_packet, C4J4Th3PJpwUYZZ6gc, F, 1, , 1b036a11, 98, 9b3ac827ccae092e3f8fdf84ec8ee526
1.0, initial_packet, C4J4Th3PJpwUYZZ6gc, T, 1, 1b036a11,
1.0, initial_packet, C4J4Th3PJpwUYZZ6gc, F, 1, , fc674735
1.0, handshake_packet, F, C4J4Th3PJpwUYZZ6gc, 1, , fc674735
1.0, initial_packet, C4J4Th3PJpwUYZZ6gc, T, 1, fc674735,
1.0, handshake_packet, T, C4J4Th3PJpwUYZZ6gc, 1, ef3a4e06,
zerortt.pcap
1.0, initial_packet, C4J4Th3PJpwUYZZ6gc, T, 1, b7c7841c64883e3261d840,
1.0, initial_packet, C4J4Th3PJpwUYZZ6gc, F, 1, , 8d2041ac
1.0, handshake_packet, F, C4J4Th3PJpwUYZZ6gc, 1, , 8d2041ac
1.0, initial_packet, C4J4Th3PJpwUYZZ6gc, T, 1, 8d2041ac,
1.0, handshake_packet, T, C4J4Th3PJpwUYZZ6gc, 1, 5b7bc400,
1.0, initial_packet, CtPZjS20MLrsMUOJi2, T, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, initial_packet, CtPZjS20MLrsMUOJi2, F, 1, , e483a751
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, zero_rtt_packet, T, CtPZjS20MLrsMUOJi2, 1, 15ae5e5e4962163f410b5529fc125bbc,
1.0, initial_packet, CtPZjS20MLrsMUOJi2, T, 1, 3ec82f67,
1.0, handshake_packet, T, CtPZjS20MLrsMUOJi2, 1, 3ec82f67,

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 Dd quic,ssl

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path quic
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 82.239.54.117 44174 250.58.23.113 443 1 c5a5015ae8f479784a 01275b138ee6aca8a6276b132ae6b3547cf7773f blog.cloudflare.com h3 ISiihIhhhH
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path ssl
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert
#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 82.239.54.117 44174 250.58.23.113 443 - - - blog.cloudflare.com F - - F C - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 Dd quic,ssl

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 TLSv13 TLS_AES_128_GCM_SHA256 x25519 www.google.de F - - F Cs

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,5 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
0.015059 ClEkJM2Vm5giqnMf4h - -
0.001000 CHhAvVGS1DHFjwGM9 - -
0.648580 C4J4Th3PJpwUYZZ6gc Dd quic,ssl

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path quic
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string
1.000000 C4J4Th3PJpwUYZZ6gc 193.167.0.100 40084 193.167.100.100 443 1 a771f6161a4072c0bf10 5911deff server4:443 hq-interop ISishIH
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path ssl
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert
#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool
1.000000 C4J4Th3PJpwUYZZ6gc 193.167.0.100 40084 193.167.100.100 443 TLSv13 TLS_AES_128_GCM_SHA256 x25519 server4:443 F - - F Cs - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,5 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 - -
0.016059 ClEkJM2Vm5giqnMf4h - -
0.669020 C4J4Th3PJpwUYZZ6gc Dd quic,ssl

View file

@ -0,0 +1,12 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path quic
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string
1.000000 C4J4Th3PJpwUYZZ6gc 193.167.0.100 42834 193.167.100.100 443 1 4a8294bf9201d6cf - server4:443 hq-interop ISr
1.000000 C4J4Th3PJpwUYZZ6gc 193.167.0.100 42834 193.167.100.100 443 1 1b036a11 fc674735 server4:443 hq-interop ISishIH
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path ssl
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert
#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool
1.000000 C4J4Th3PJpwUYZZ6gc 193.167.0.100 42834 193.167.100.100 443 TLSv13 TLS_AES_128_GCM_SHA256 x25519 server4:443 F - - F CCs - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,6 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
0.015059 ClEkJM2Vm5giqnMf4h - -
0.001000 CHhAvVGS1DHFjwGM9 - -
0.790739 CtPZjS20MLrsMUOJi2 Dd quic,ssl
0.718160 C4J4Th3PJpwUYZZ6gc Dd quic,ssl

View file

@ -0,0 +1,12 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path quic
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string
1.000000 CtPZjS20MLrsMUOJi2 193.167.0.100 49394 193.167.100.100 443 1 15ae5e5e4962163f410b5529fc125bbc e483a751 server4:443 hq-interop ISZisZZZZZ
1.000000 C4J4Th3PJpwUYZZ6gc 193.167.0.100 60492 193.167.100.100 443 1 b7c7841c64883e3261d840 8d2041ac server4:443 hq-interop ISishIH
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,12 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path ssl
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert
#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool
1.000000 CtPZjS20MLrsMUOJi2 193.167.0.100 49394 193.167.100.100 443 TLSv13 TLS_AES_128_GCM_SHA256 x25519 server4:443 T - - F Cs - - -
1.000000 C4J4Th3PJpwUYZZ6gc 193.167.0.100 60492 193.167.100.100 443 TLSv13 TLS_AES_128_GCM_SHA256 x25519 server4:443 F - - F Cs - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path quic
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 669b:cb7a:de99:6a13:4a9b:46ef:3bed:cb6c 57538 6699:ded3:da8c:be73:5a99:ca73:5a99:cadb 443 1 5a37463b0eb7cc5d da37463b0eb7cc5d www.google.de h3 ISishIhHhh
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 Dd quic,ssl

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path quic
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version client_initial_dcid server_scid server_name client_protocol history
#types time string addr port addr port string string string string string string
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 46907 127.0.0.1 853 1 fda05288ab9ff546 a31f4933d8727231 - doq ISishH
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,11 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path ssl
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert
#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 127.0.0.1 46907 127.0.0.1 853 TLSv13 TLS_AES_128_GCM_SHA256 secp256r1 - F - - F Cs - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -1,11 +0,0 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
#separator \x09
#set_separator ,
#empty_field (empty)
#unset_field -
#path conn
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p proto service duration orig_bytes resp_bytes conn_state local_orig local_resp missed_bytes history orig_pkts orig_ip_bytes resp_pkts resp_ip_bytes tunnel_parents
#types time string addr port addr port enum string interval count count string bool bool count string count count count count set[string]
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 1.2.3.4 49369 4.3.2.1 443 udp spicy_quic,ssl 18.071102 14371 394242 SF - - 0 Dd 96 17059 345 403902 -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 Dd quic,ssl

View file

@ -7,5 +7,5 @@
#open XXXX-XX-XX-XX-XX-XX
#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert
#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 1.2.3.4 49369 4.3.2.1 443 - - - www.google.com F - - F C - - -
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 1.2.3.4 49369 4.3.2.1 443 TLSv13 TLS_AES_128_GCM_SHA256 x25519 www.google.com F - - F Cs - - -
#close XXXX-XX-XX-XX-XX-XX

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid cause analyzer_kind analyzer_name failure_reason
1693925959.000001 CHhAvVGS1DHFjwGM9 violation protocol QUIC &requires failed: self.length.result >= 20 (<...>/QUIC.spicy:<line>:<column>)

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
ts uid history service
XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 D -

View file

@ -0,0 +1,10 @@
PCAP files in this directory were gathered from the QUIC interop project webpage.
Marten Seemann didn't have any concerns or reservations using these as testing
material. They require DLT_PPP support in Zeek, unfortunately.
Some references:
https://interop.seemann.io/
https://github.com/marten-seemann/quic-network-simulator
https://github.com/marten-seemann/quic-interop-runner

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,6 @@
# @TEST-DOC: Test that runs the pcap
# @TEST-EXEC: zeek -Cr $TRACES/quic/chromium-115.0.5790.110-api-cirrus-com.pcap base/protocols/quic
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: btest-diff ssl.log
# @TEST-EXEC: btest-diff quic.log

View file

@ -0,0 +1,6 @@
# @TEST-DOC: Test that runs the pcap
# @TEST-EXEC: zeek -Cr $TRACES/quic/curl-8.1.2-dev-http3-www-google-de.pcap base/protocols/quic
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: btest-diff ssl.log
# @TEST-EXEC: btest-diff quic.log

View file

@ -0,0 +1,29 @@
# @TEST-DOC: Supported events so far.
# @TEST-REQUIRES: zeek -b -e 'print PacketAnalyzer::ANALYZER_PPP == PacketAnalyzer::ANALYZER_PPP'
# @TEST-EXEC: zeek -Cr $TRACES/quic/interop/quic-go_quic-go/retry.pcap base/protocols/quic %INPUT >out
# @TEST-EXEC: echo "zerortt.pcap" >>out
# @TEST-EXEC: zeek -Cr $TRACES/quic/interop/quic-go_quic-go/zerortt.pcap base/protocols/quic %INPUT >>out
# @TEST-EXEC: btest-diff out
# @TEST-EXEC: btest-diff .stderr
#
function b2hex(s: string):string { return bytestring_to_hexstr(s); }
event QUIC::initial_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
print network_time(), "initial_packet", c$uid, is_orig, version, b2hex(dcid), b2hex(scid);
}
event QUIC::retry_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string, retry_token: string, integrity_tag: string)
{
print network_time(), "retry_packet", c$uid, is_orig, version, b2hex(dcid), b2hex(scid), |retry_token|, b2hex(integrity_tag);
}
event QUIC::handshake_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
print network_time(), "handshake_packet", is_orig, c$uid, version, b2hex(dcid), b2hex(scid);
}
event QUIC::zero_rtt_packet(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
{
print network_time(), "zero_rtt_packet", is_orig, c$uid, version, b2hex(dcid), b2hex(scid);
}

View file

@ -0,0 +1,6 @@
# @TEST-DOC: Test that runs the pcap
# @TEST-EXEC: zeek -Cr $TRACES/quic/firefox-102.13.0esr-blog-cloudflare-com.pcap base/protocols/quic
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: btest-diff ssl.log
# @TEST-EXEC: btest-diff quic.log

View file

@ -0,0 +1,7 @@
# @TEST-DOC: Pcap with fragmented and unordered CRYPTO frames.
#
# @TEST-EXEC: zeek -Cr $TRACES/quic/chromium-115.0.5790.110-google-de-fragmented.pcap base/protocols/quic
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: zeek-cut -m ts uid version cipher curve server_name resumed last_alert next_protocol established ssl_history < ssl.log > ssl.log.cut
# @TEST-EXEC: btest-diff ssl.log.cut

View file

@ -0,0 +1,12 @@
# @TEST-DOC: Test interop pcap containing RETRY packet from server side.
#
# interop pcaps have link type DLT_PPP, test for its availability. Available in Zeek 6.1 or later only.
# @TEST-REQUIRES: zeek -b -e 'print PacketAnalyzer::ANALYZER_PPP == PacketAnalyzer::ANALYZER_PPP'
#
# @TEST-EXEC: zeek -Cr $TRACES/quic/interop/quic-go_quic-go/handshake.pcap base/protocols/quic
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: btest-diff ssl.log
# @TEST-EXEC: btest-diff quic.log
# @TEST-EXEC: btest-diff .stderr
# @TEST-EXEC: test ! -f analyzer.log

View file

@ -0,0 +1,12 @@
# @TEST-DOC: Test interop pcap containing RETRY packet from server side.
#
# interop pcaps have link type DLT_PPP, test for its availability. Available in Zeek 6.1 or later only.
# @TEST-REQUIRES: zeek -b -e 'print PacketAnalyzer::ANALYZER_PPP == PacketAnalyzer::ANALYZER_PPP'
#
# @TEST-EXEC: zeek -Cr $TRACES/quic/interop/quic-go_quic-go/retry.pcap base/protocols/quic
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: btest-diff ssl.log
# @TEST-EXEC: btest-diff quic.log
# @TEST-EXEC: btest-diff .stderr
# @TEST-EXEC: test ! -f analyzer.log

View file

@ -0,0 +1,12 @@
# @TEST-DOC: Test that client initiating connection using 0RTT packet doesn't cause analyzer errors trying to decrypt server side.
#
# interop pcaps have link type DLT_PPP, test for its availability. Available in Zeek 6.1 or later only.
# @TEST-REQUIRES: zeek -b -e 'print PacketAnalyzer::ANALYZER_PPP == PacketAnalyzer::ANALYZER_PPP'
#
# @TEST-EXEC: zeek -Cr $TRACES/quic/interop/quic-go_quic-go/zerortt.pcap base/protocols/quic
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: btest-diff ssl.log
# @TEST-EXEC: btest-diff quic.log
# @TEST-EXEC: btest-diff .stderr
# @TEST-EXEC: test ! -f analyzer.log

View file

@ -0,0 +1,5 @@
# @TEST-DOC: Smoke test the quic.log production
#
# @TEST-EXEC: zeek -Cr $TRACES/quic/chromium-115.0.5790.110-google-de-fragmented.pcap base/protocols/quic
# @TEST-EXEC: btest-diff quic.log
# @TEST-EXEC: btest-diff .stderr

View file

@ -0,0 +1,6 @@
# @TEST-DOC: Pcap with dns-over-quic lookup using https://github.com/private-octopus/quicdoq
# @TEST-EXEC: zeek -Cr $TRACES/quic/quicdoq.pcap base/protocols/quic
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: btest-diff ssl.log
# @TEST-EXEC: btest-diff quic.log

View file

@ -1,4 +1,5 @@
# @TEST-DOC: Test that runs the pcap
# @TEST-EXEC: zeek -Cr $TRACES/quic/quic_win11_firefox_google.pcap base/protocols/quic >output
# @TEST-EXEC: btest-diff conn.log
# @TEST-EXEC: btest-diff ssl.log
# @TEST-EXEC: zeek -Cr $TRACES/quic/quic_win11_firefox_google.pcap base/protocols/quic
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# @TEST-EXEC: btest-diff ssl.log

View file

@ -0,0 +1,9 @@
# @TEST-DOC: Test that runs the pcap
# @TEST-EXEC: zeek -Cr $TRACES/quic/vector-max-size-crash.pcap base/protocols/quic
# @TEST-EXEC: zeek-cut -m ts uid history service < conn.log > conn.log.cut
# @TEST-EXEC: zeek-cut -m ts uid cause analyzer_kind analyzer_name failure_reason < analyzer.log > analyzer.log.cut
# @TEST-EXEC: btest-diff conn.log.cut
# Only run btest-ddiff on analyzer.log with 6.1-dev or later. The violation
# reporting has more detail in later versions.
# @TEST-EXEC: zeek -b -e 'exit(Version::info$version_number < 60100 ? 0 : 1)' || TEST_DIFF_CANONIFIER='sed -r "s/\((.+)\.spicy:[0-9]+:[0-9]+\)/(\1.spicy:<line>:<column>)/g" | $SCRIPTS/diff-remove-abspath' btest-diff analyzer.log.cut