mirror of
https://github.com/zeek/zeek.git
synced 2025-10-02 14:48:21 +00:00
Merge remote-tracking branch 'origin/topic/awelzel/3503-quic-v2'
* origin/topic/awelzel/3503-quic-v2: quic: tests: Require have-spicy quic: analyzer: Recognize and report unknown versions better quic: tests: Add QUIC v2 test cases quic: analyzer: Support QUIC v2 quic: decrypt_crypto: Support QUIC v2
This commit is contained in:
commit
fe0f981f87
20 changed files with 398 additions and 167 deletions
25
CHANGES
25
CHANGES
|
@ -1,3 +1,28 @@
|
|||
6.2.0-dev.320 | 2024-01-05 14:43:59 +0100
|
||||
|
||||
* quic: tests: Require have-spicy (Arne Welzel, Corelight)
|
||||
|
||||
* quic: analyzer: Recognize and report unknown versions better (Arne Welzel, Corelight)
|
||||
|
||||
This makes the analyzer.log entry more informative by including the
|
||||
actual version and also allows to handle this scenario in script land
|
||||
if needed.
|
||||
|
||||
* quic: tests: Add QUIC v2 test cases (Arne Welzel, Corelight)
|
||||
|
||||
Produced using examples from the go-quic project, patching the clients
|
||||
to force QUIC v2.
|
||||
|
||||
* quic: analyzer: Support QUIC v2 (Arne Welzel, Corelight)
|
||||
|
||||
QUIC v2 changed the version *and* the packet type enumeration to prevent
|
||||
protocol ossification. Use an intermediary unit to handle the difference.
|
||||
|
||||
* quic: decrypt_crypto: Support QUIC v2 (Arne Welzel, Corelight)
|
||||
|
||||
Attempt to refactor in order to re-use common code between the two
|
||||
versions.
|
||||
|
||||
6.2.0-dev.314 | 2024-01-04 16:40:00 +0100
|
||||
|
||||
* Bump auxil/spicy to latest development snapshot (Benjamin Bannier, Corelight)
|
||||
|
|
3
NEWS
3
NEWS
|
@ -132,6 +132,9 @@ New Functionality
|
|||
``signatures.log``. This log is based on the generation of ``signature_match()``
|
||||
events.
|
||||
|
||||
- The QUIC analyzer has been extended to support analyzing QUIC Version 2
|
||||
INITIAL packets (RFC 9369).
|
||||
|
||||
|
||||
Changed Functionality
|
||||
---------------------
|
||||
|
|
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
6.2.0-dev.314
|
||||
6.2.0-dev.320
|
||||
|
|
|
@ -3,5 +3,6 @@ module QUIC;
|
|||
export {
|
||||
const version_strings: table[count] of string = {
|
||||
[0x00000001] = "1",
|
||||
[0x6b3343cf] = "quicv2",
|
||||
} &default=function(version: count): string { return fmt("unknown-%x", version); };
|
||||
}
|
||||
|
|
|
@ -10,9 +10,6 @@ protocol analyzer QUIC over UDP:
|
|||
|
||||
import QUIC;
|
||||
|
||||
# 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);
|
||||
|
@ -23,3 +20,5 @@ on QUIC::ZeroRTTPacket -> event QUIC::zero_rtt_packet($conn, $is_orig, self.head
|
|||
|
||||
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);
|
||||
|
||||
on QUIC::UnhandledVersion -> event QUIC::unhandled_version($conn, $is_orig, self.header.version, self.header.dest_conn_id, self.header.src_conn_id);
|
||||
|
|
|
@ -8,6 +8,7 @@ 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(
|
||||
version: uint32,
|
||||
all_data: bytes,
|
||||
connection_id: bytes,
|
||||
encrypted_offset: uint64,
|
||||
|
@ -23,11 +24,10 @@ public function decrypt_crypto_payload(
|
|||
# Can we decrypt?
|
||||
function can_decrypt(long_header: LongHeaderPacket, context: ConnectionIDInfo, is_client: bool): bool {
|
||||
|
||||
if ( long_header.first_byte.packet_type != LongPacketType::INITIAL )
|
||||
if ( ! long_header.is_initial )
|
||||
return False;
|
||||
|
||||
# decrypt_crypto_payload() has known secrets for version 1, nothing else.
|
||||
if ( long_header.version != 0x00000001 )
|
||||
if ( long_header.version != Version1 && long_header.version != Version2 )
|
||||
return False;
|
||||
|
||||
if ( is_client )
|
||||
|
@ -80,14 +80,26 @@ type ConnectionIDInfo = struct {
|
|||
##############
|
||||
# Definitions
|
||||
##############
|
||||
const Version1: uint32 = 0x00000001;
|
||||
const Version2: uint32 = 0x6b3343cf;
|
||||
|
||||
type LongPacketType = enum {
|
||||
type LongPacketTypeV1 = enum {
|
||||
INITIAL = 0,
|
||||
ZERO_RTT = 1,
|
||||
HANDSHAKE = 2,
|
||||
RETRY = 3,
|
||||
};
|
||||
|
||||
# V2 changed packet types to avoid ossification.
|
||||
#
|
||||
# https://www.rfc-editor.org/rfc/rfc9369.html#name-long-header-packet-types
|
||||
type LongPacketTypeV2 = enum {
|
||||
INITIAL = 1,
|
||||
ZERO_RTT = 2,
|
||||
HANDSHAKE = 3,
|
||||
RETRY = 0,
|
||||
};
|
||||
|
||||
type HeaderForm = enum {
|
||||
SHORT = 0,
|
||||
LONG = 1,
|
||||
|
@ -154,17 +166,66 @@ type VariableLengthInteger = unit {
|
|||
# Long packets
|
||||
# Generic units
|
||||
##############
|
||||
public type LongHeaderPacketV1 = unit(inout outer: LongHeaderPacket) {
|
||||
switch ( LongPacketTypeV1(outer.first_byte.packet_type) ) {
|
||||
LongPacketTypeV1::INITIAL -> initial_hdr : InitialPacket(outer) {
|
||||
outer.is_initial = True;
|
||||
outer.encrypted_offset = outer.offset() +
|
||||
self.initial_hdr.length.bytes_to_parse +
|
||||
self.initial_hdr.token_length.bytes_to_parse +
|
||||
self.initial_hdr.token_length.result;
|
||||
outer.payload_length = self.initial_hdr.length.result;
|
||||
}
|
||||
|
||||
LongPacketTypeV1::ZERO_RTT -> zerortt_hdr : ZeroRTTPacket(outer);
|
||||
LongPacketTypeV1::HANDSHAKE -> handshake_hdr : HandshakePacket(outer);
|
||||
LongPacketTypeV1::RETRY -> retry_hdr : RetryPacket(outer) {
|
||||
outer.is_retry = True;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
public type LongHeaderPacketV2 = unit(inout outer: LongHeaderPacket) {
|
||||
switch ( LongPacketTypeV2(outer.first_byte.packet_type) ) {
|
||||
LongPacketTypeV2::INITIAL -> initial_hdr : InitialPacket(outer) {
|
||||
outer.is_initial = True;
|
||||
outer.encrypted_offset = outer.offset() +
|
||||
self.initial_hdr.length.bytes_to_parse +
|
||||
self.initial_hdr.token_length.bytes_to_parse +
|
||||
self.initial_hdr.token_length.result;
|
||||
outer.payload_length = self.initial_hdr.length.result;
|
||||
}
|
||||
|
||||
LongPacketTypeV2::ZERO_RTT -> zerortt_hdr : ZeroRTTPacket(outer);
|
||||
LongPacketTypeV2::HANDSHAKE -> handshake_hdr : HandshakePacket(outer);
|
||||
LongPacketTypeV2::RETRY -> retry_hdr : RetryPacket(outer) {
|
||||
outer.is_retry = True;
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
# Just eat the data for event raising.
|
||||
public type UnhandledVersion = unit(header: LongHeaderPacket) {
|
||||
var header: LongHeaderPacket = header;
|
||||
@if SPICY_VERSION >= 10800
|
||||
payload: skip bytes &eod;
|
||||
@else
|
||||
payload: bytes &eod;
|
||||
@endif
|
||||
};
|
||||
|
||||
public type LongHeaderPacket = unit {
|
||||
var encrypted_offset: uint64;
|
||||
var payload_length: uint64;
|
||||
var client_conn_id_length: uint8;
|
||||
var server_conn_id_length: uint8;
|
||||
var is_initial: bool;
|
||||
var is_retry: bool;
|
||||
|
||||
first_byte: bitfield(8) {
|
||||
header_form: 7 &convert=cast<HeaderForm>(cast<uint8>($$));
|
||||
fixed_bit: 6;
|
||||
packet_type: 4..5 &convert=cast<LongPacketType>(cast<uint8>($$));
|
||||
packet_type: 4..5;
|
||||
type_specific_bits: 0..3 &convert=cast<uint8>($$);
|
||||
};
|
||||
|
||||
|
@ -174,18 +235,12 @@ public type LongHeaderPacket = unit {
|
|||
src_conn_id_len: uint8 { self.client_conn_id_length = $$; }
|
||||
src_conn_id: bytes &size=self.client_conn_id_length;
|
||||
|
||||
switch ( self.first_byte.packet_type ) {
|
||||
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.length.result;
|
||||
switch ( self.version ) {
|
||||
Version1 -> v1: LongHeaderPacketV1(self);
|
||||
Version2 -> v2: LongHeaderPacketV2(self);
|
||||
* -> unknown: UnhandledVersion(self) {
|
||||
throw "unhandled QUIC version 0x%x" % self.version;
|
||||
}
|
||||
|
||||
LongPacketType::ZERO_RTT -> zerortt_hdr : ZeroRTTPacket(self);
|
||||
LongPacketType::HANDSHAKE -> handshake_hdr : HandshakePacket(self);
|
||||
LongPacketType::RETRY -> retry_hdr : RetryPacket(self);
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -401,7 +456,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
|||
|
||||
# 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 ) {
|
||||
if ( self.long_header.is_retry ) {
|
||||
context.client_initial_processed = False;
|
||||
context.server_initial_processed = False;
|
||||
context.initial_destination_conn_id = b"";
|
||||
|
@ -430,6 +485,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
|||
# 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.long_header.version,
|
||||
self.all_data,
|
||||
self.long_header.dest_conn_id,
|
||||
self.long_header.encrypted_offset,
|
||||
|
@ -449,6 +505,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
|||
# 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.long_header.version,
|
||||
self.all_data,
|
||||
context.initial_destination_conn_id,
|
||||
self.long_header.encrypted_offset,
|
||||
|
@ -467,7 +524,7 @@ type Packet = unit(from_client: bool, context: ConnectionIDInfo&) {
|
|||
# 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 ) {
|
||||
if ( ! self.long_header.is_retry && ! from_client ) {
|
||||
context.server_initial_processed = True;
|
||||
context.client_initial_processed = True;
|
||||
}
|
||||
|
|
|
@ -2,15 +2,15 @@
|
|||
// Copyright (c) 2023 by the Zeek Project. See COPYING for details.
|
||||
|
||||
/*
|
||||
WARNING: THIS CODE IS NOT SAFE IN MULTI-THREADED
|
||||
WARNING: THIS CODE IS NOT SAFE IN MULTI-THREADED ENVIRONMENTS:
|
||||
|
||||
* Initializations of static OpenSSL contexts without locking
|
||||
* Use of contexts is not protected by locks.
|
||||
* Use of SSL contexts is not protected by locks
|
||||
|
||||
The involved contexts are EVP_CIPHER_CTX and EVP_PKEY_CTX and are allocated
|
||||
lazily just once and re-used for performance reasons. Previously, every
|
||||
decrypt operation allocated, initialized and freed each of the used context
|
||||
resulting in a significant performance hit.
|
||||
The involved contexts are EVP_CIPHER_CTX and EVP_PKEY_CTX. These are allocated
|
||||
lazily and re-used for performance reasons. Previously, every decrypt operation
|
||||
allocated, initialized and freed these individually, resulting in a significant
|
||||
performance hit. Given Zeek's single threaded nature, this is fine.
|
||||
*/
|
||||
|
||||
/*
|
||||
|
@ -25,6 +25,7 @@ refactors as C++ development is not our main profession.
|
|||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
|
@ -51,31 +52,6 @@ struct DecryptionInformation {
|
|||
// 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
|
||||
such as 'quic key' and 'quic hp'. These labels can obviously be
|
||||
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> 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> 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> 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
|
||||
*/
|
||||
|
@ -107,100 +83,6 @@ EVP_CIPHER_CTX* get_aes_128_gcm() {
|
|||
|
||||
return ctx;
|
||||
}
|
||||
/*
|
||||
HKDF-Extract as described in https://www.rfc-editor.org/rfc/rfc8446.html#section-7.1
|
||||
*/
|
||||
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();
|
||||
static EVP_PKEY_CTX* ctx = nullptr;
|
||||
if ( ! ctx ) {
|
||||
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
|
||||
EVP_PKEY_derive_init(ctx);
|
||||
EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256());
|
||||
EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY);
|
||||
}
|
||||
|
||||
EVP_PKEY_CTX_set1_hkdf_key(ctx, data_as_uint8(connection_id), connection_id.size());
|
||||
EVP_PKEY_CTX_set1_hkdf_salt(ctx, INITIAL_SALT_V1.data(), INITIAL_SALT_V1.size());
|
||||
EVP_PKEY_derive(ctx, out_temp.data(), &initial_secret_len);
|
||||
return out_temp;
|
||||
}
|
||||
|
||||
/*
|
||||
HKDF-Expand-Label as described in https://www.rfc-editor.org/rfc/rfc8446.html#section-7.1
|
||||
*/
|
||||
std::vector<uint8_t> hkdf_expand(EVP_PKEY_CTX* ctx, size_t out_len, const std::vector<uint8_t>& key) {
|
||||
std::vector<uint8_t> out_temp(out_len);
|
||||
EVP_PKEY_CTX_set1_hkdf_key(ctx, key.data(), key.size());
|
||||
EVP_PKEY_derive(ctx, out_temp.data(), &out_len);
|
||||
return out_temp;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> hkdf_expand_client_initial_info(size_t out_len, const std::vector<uint8_t>& key) {
|
||||
static EVP_PKEY_CTX* ctx = nullptr;
|
||||
if ( ! ctx ) {
|
||||
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
|
||||
EVP_PKEY_derive_init(ctx);
|
||||
EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256());
|
||||
EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
|
||||
EVP_PKEY_CTX_add1_hkdf_info(ctx, CLIENT_INITIAL_INFO.data(), CLIENT_INITIAL_INFO.size());
|
||||
}
|
||||
|
||||
return hkdf_expand(ctx, out_len, key);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> hkdf_expand_server_initial_info(size_t out_len, const std::vector<uint8_t>& key) {
|
||||
static EVP_PKEY_CTX* ctx = nullptr;
|
||||
if ( ! ctx ) {
|
||||
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
|
||||
EVP_PKEY_derive_init(ctx);
|
||||
EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256());
|
||||
EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
|
||||
EVP_PKEY_CTX_add1_hkdf_info(ctx, SERVER_INITIAL_INFO.data(), SERVER_INITIAL_INFO.size());
|
||||
}
|
||||
|
||||
return hkdf_expand(ctx, out_len, key);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> hkdf_expand_key_info(size_t out_len, const std::vector<uint8_t>& key) {
|
||||
static EVP_PKEY_CTX* ctx = nullptr;
|
||||
if ( ! ctx ) {
|
||||
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
|
||||
EVP_PKEY_derive_init(ctx);
|
||||
EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256());
|
||||
EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
|
||||
EVP_PKEY_CTX_add1_hkdf_info(ctx, KEY_INFO.data(), KEY_INFO.size());
|
||||
}
|
||||
|
||||
return hkdf_expand(ctx, out_len, key);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> hkdf_expand_iv_info(size_t out_len, const std::vector<uint8_t>& key) {
|
||||
static EVP_PKEY_CTX* ctx = nullptr;
|
||||
if ( ! ctx ) {
|
||||
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
|
||||
EVP_PKEY_derive_init(ctx);
|
||||
EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256());
|
||||
EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
|
||||
EVP_PKEY_CTX_add1_hkdf_info(ctx, IV_INFO.data(), IV_INFO.size());
|
||||
}
|
||||
|
||||
return hkdf_expand(ctx, out_len, key);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> hkdf_expand_hp_info(size_t out_len, const std::vector<uint8_t>& key) {
|
||||
static EVP_PKEY_CTX* ctx = nullptr;
|
||||
if ( ! ctx ) {
|
||||
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
|
||||
EVP_PKEY_derive_init(ctx);
|
||||
EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256());
|
||||
EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
|
||||
EVP_PKEY_CTX_add1_hkdf_info(ctx, HP_INFO.data(), HP_INFO.size());
|
||||
}
|
||||
|
||||
return hkdf_expand(ctx, out_len, key);
|
||||
}
|
||||
|
||||
/*
|
||||
Removes the header protection from the INITIAL packet and returns a DecryptionInformation struct
|
||||
|
@ -268,10 +150,8 @@ std::vector<uint8_t> calculate_nonce(std::vector<uint8_t> client_iv, uint64_t pa
|
|||
}
|
||||
|
||||
/*
|
||||
Function that calls the AEAD decryption routine, and returns the
|
||||
decrypted data
|
||||
Function that calls the AEAD decryption routine, and returns the decrypted data.
|
||||
*/
|
||||
|
||||
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;
|
||||
|
@ -324,14 +204,203 @@ hilti::rt::Bytes decrypt(const std::vector<uint8_t>& client_key, const hilti::rt
|
|||
return hilti::rt::Bytes(decrypt_buffer.data(), decrypt_buffer.data() + out);
|
||||
}
|
||||
|
||||
|
||||
// Pre-initialized SSL contexts for re-use. Not thread-safe. These are only used in expand-only mode
|
||||
// and have a fixed HKDF info set.
|
||||
struct HkdfCtx {
|
||||
bool initialized = false;
|
||||
EVP_PKEY_CTX* client_in_ctx = nullptr;
|
||||
EVP_PKEY_CTX* server_in_ctx = nullptr;
|
||||
EVP_PKEY_CTX* key_info_ctx = nullptr;
|
||||
EVP_PKEY_CTX* iv_info_ctx = nullptr;
|
||||
EVP_PKEY_CTX* hp_info_ctx = nullptr;
|
||||
};
|
||||
|
||||
|
||||
struct HkdfCtxParam {
|
||||
EVP_PKEY_CTX** ctx;
|
||||
std::vector<uint8_t> info;
|
||||
};
|
||||
|
||||
/*
|
||||
HKDF-Extract as described in https://www.rfc-editor.org/rfc/rfc8446.html#section-7.1
|
||||
*/
|
||||
std::vector<uint8_t> hkdf_extract(const std::vector<uint8_t>& salt, const hilti::rt::Bytes& connection_id) {
|
||||
std::vector<uint8_t> out_temp(INITIAL_SECRET_LEN);
|
||||
size_t initial_secret_len = out_temp.size();
|
||||
static EVP_PKEY_CTX* ctx = nullptr;
|
||||
if ( ! ctx ) {
|
||||
ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
|
||||
EVP_PKEY_derive_init(ctx);
|
||||
EVP_PKEY_CTX_set_hkdf_md(ctx, EVP_sha256());
|
||||
EVP_PKEY_CTX_hkdf_mode(ctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY);
|
||||
}
|
||||
|
||||
EVP_PKEY_CTX_set1_hkdf_key(ctx, data_as_uint8(connection_id), connection_id.size());
|
||||
EVP_PKEY_CTX_set1_hkdf_salt(ctx, salt.data(), salt.size());
|
||||
EVP_PKEY_derive(ctx, out_temp.data(), &initial_secret_len);
|
||||
return out_temp;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> hkdf_expand(EVP_PKEY_CTX* ctx, size_t out_len, const std::vector<uint8_t>& key) {
|
||||
std::vector<uint8_t> out_temp(out_len);
|
||||
EVP_PKEY_CTX_set1_hkdf_key(ctx, key.data(), key.size());
|
||||
EVP_PKEY_derive(ctx, out_temp.data(), &out_len);
|
||||
return out_temp;
|
||||
}
|
||||
|
||||
class QuicPacketProtection {
|
||||
public:
|
||||
std::vector<uint8_t> GetSecret(bool is_orig, const hilti::rt::Bytes& connection_id) {
|
||||
const auto& ctxs = GetHkdfCtxs();
|
||||
const auto initial_secret = hkdf_extract(GetInitialSalt(), connection_id);
|
||||
EVP_PKEY_CTX* ctx = is_orig ? ctxs.client_in_ctx : ctxs.server_in_ctx;
|
||||
return hkdf_expand(ctx, INITIAL_SECRET_LEN, initial_secret);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GetKey(const std::vector<uint8_t>& secret) {
|
||||
const auto& ctxs = GetHkdfCtxs();
|
||||
return hkdf_expand(ctxs.key_info_ctx, AEAD_KEY_LEN, secret);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GetIv(const std::vector<uint8_t>& secret) {
|
||||
const auto& ctxs = GetHkdfCtxs();
|
||||
return hkdf_expand(ctxs.iv_info_ctx, AEAD_IV_LEN, secret);
|
||||
}
|
||||
|
||||
std::vector<uint8_t> GetHp(const std::vector<uint8_t>& secret) {
|
||||
const auto& ctxs = GetHkdfCtxs();
|
||||
return hkdf_expand(ctxs.hp_info_ctx, AEAD_HP_LEN, secret);
|
||||
}
|
||||
|
||||
virtual const std::vector<uint8_t>& GetInitialSalt() = 0;
|
||||
virtual HkdfCtx& GetHkdfCtxs() = 0;
|
||||
|
||||
virtual ~QuicPacketProtection() = default;
|
||||
|
||||
// Helper to initialize HKDF expand only contexts.
|
||||
static void Initialize(std::vector<HkdfCtxParam>& params) {
|
||||
for ( const auto& p : params ) {
|
||||
*p.ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL);
|
||||
EVP_PKEY_derive_init(*p.ctx);
|
||||
EVP_PKEY_CTX_set_hkdf_md(*p.ctx, EVP_sha256());
|
||||
EVP_PKEY_CTX_hkdf_mode(*p.ctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY);
|
||||
EVP_PKEY_CTX_add1_hkdf_info(*p.ctx, p.info.data(), p.info.size());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// QUIC v1
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/html/rfc9001
|
||||
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> 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> 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};
|
||||
|
||||
|
||||
class QuicPacketProtectionV1 : public QuicPacketProtection {
|
||||
public:
|
||||
virtual std::vector<uint8_t>& GetInitialSalt() override { return INITIAL_SALT_V1; }
|
||||
virtual HkdfCtx& GetHkdfCtxs() override { return hkdf_ctxs; }
|
||||
|
||||
// Pre-initialize SSL context for reuse with HKDF info set to version specific values.
|
||||
static void Initialize() {
|
||||
if ( hkdf_ctxs.initialized )
|
||||
return;
|
||||
|
||||
std::vector<HkdfCtxParam> hkdf_ctx_params = {
|
||||
{&hkdf_ctxs.client_in_ctx, CLIENT_INITIAL_INFO},
|
||||
{&hkdf_ctxs.server_in_ctx, SERVER_INITIAL_INFO},
|
||||
{&hkdf_ctxs.key_info_ctx, KEY_INFO},
|
||||
{&hkdf_ctxs.iv_info_ctx, IV_INFO},
|
||||
{&hkdf_ctxs.hp_info_ctx, HP_INFO},
|
||||
};
|
||||
|
||||
QuicPacketProtection::Initialize(hkdf_ctx_params);
|
||||
|
||||
instance = std::make_unique<QuicPacketProtectionV1>();
|
||||
hkdf_ctxs.initialized = true;
|
||||
}
|
||||
|
||||
static HkdfCtx hkdf_ctxs;
|
||||
static std::unique_ptr<QuicPacketProtectionV1> instance;
|
||||
};
|
||||
|
||||
HkdfCtx QuicPacketProtectionV1::hkdf_ctxs = {0};
|
||||
std::unique_ptr<QuicPacketProtectionV1> QuicPacketProtectionV1::instance = nullptr;
|
||||
|
||||
|
||||
// QUIC v2
|
||||
//
|
||||
// https://datatracker.ietf.org/doc/rfc9369/
|
||||
std::vector<uint8_t> INITIAL_SALT_V2 = {0x0d, 0xed, 0xe3, 0xde, 0xf7, 0x00, 0xa6, 0xdb, 0x81, 0x93,
|
||||
0x81, 0xbe, 0x6e, 0x26, 0x9d, 0xcb, 0xf9, 0xbd, 0x2e, 0xd9};
|
||||
|
||||
std::vector<uint8_t> CLIENT_INITIAL_INFO_V2 = {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_V2 = {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_V2 = {0x00, 0x10, 0x10, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, 0x71,
|
||||
0x75, 0x69, 0x63, 0x76, 0x32, 0x20, 0x6b, 0x65, 0x79, 0x00};
|
||||
|
||||
std::vector<uint8_t> IV_INFO_V2 = {0x00, 0x0c, 0x0f, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, 0x71,
|
||||
0x75, 0x69, 0x63, 0x76, 0x32, 0x20, 0x69, 0x76, 0x00};
|
||||
|
||||
std::vector<uint8_t> HP_INFO_V2 = {0x00, 0x10, 0x0f, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, 0x71,
|
||||
0x75, 0x69, 0x63, 0x76, 0x32, 0x20, 0x68, 0x70, 0x00};
|
||||
|
||||
class QuicPacketProtectionV2 : public QuicPacketProtection {
|
||||
public:
|
||||
virtual std::vector<uint8_t>& GetInitialSalt() override { return INITIAL_SALT_V2; }
|
||||
virtual HkdfCtx& GetHkdfCtxs() override { return hkdf_ctxs; }
|
||||
|
||||
static void Initialize() {
|
||||
if ( hkdf_ctxs.initialized )
|
||||
return;
|
||||
std::vector<HkdfCtxParam> hkdf_ctx_params = {
|
||||
{&hkdf_ctxs.client_in_ctx, CLIENT_INITIAL_INFO_V2},
|
||||
{&hkdf_ctxs.server_in_ctx, SERVER_INITIAL_INFO_V2},
|
||||
{&hkdf_ctxs.key_info_ctx, KEY_INFO_V2},
|
||||
{&hkdf_ctxs.iv_info_ctx, IV_INFO_V2},
|
||||
{&hkdf_ctxs.hp_info_ctx, HP_INFO_V2},
|
||||
};
|
||||
|
||||
QuicPacketProtection::Initialize(hkdf_ctx_params);
|
||||
instance = std::make_unique<QuicPacketProtectionV2>();
|
||||
hkdf_ctxs.initialized = true;
|
||||
}
|
||||
|
||||
static HkdfCtx hkdf_ctxs;
|
||||
static std::unique_ptr<QuicPacketProtectionV2> instance;
|
||||
};
|
||||
|
||||
HkdfCtx QuicPacketProtectionV2::hkdf_ctxs = {0};
|
||||
std::unique_ptr<QuicPacketProtectionV2> QuicPacketProtectionV2::instance = nullptr;
|
||||
|
||||
} // namespace
|
||||
|
||||
/*
|
||||
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.
|
||||
Function that is called from Spicy, decrypting an INITIAL packet and returning
|
||||
the decrypted payload back to the analyzer.
|
||||
*/
|
||||
hilti::rt::Bytes QUIC_decrypt_crypto_payload(const hilti::rt::Bytes& all_data, const hilti::rt::Bytes& connection_id,
|
||||
hilti::rt::Bytes QUIC_decrypt_crypto_payload(const hilti::rt::integer::safe<uint32_t>& version,
|
||||
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) {
|
||||
|
@ -342,19 +411,24 @@ hilti::rt::Bytes QUIC_decrypt_crypto_payload(const hilti::rt::Bytes& all_data, c
|
|||
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(connection_id);
|
||||
QuicPacketProtection* qpp = nullptr;
|
||||
|
||||
std::vector<uint8_t> server_client_secret;
|
||||
if ( from_client ) {
|
||||
server_client_secret = hkdf_expand_client_initial_info(INITIAL_SECRET_LEN, initial_secret);
|
||||
if ( version == 0x00000001 ) { // quicv1
|
||||
QuicPacketProtectionV1::Initialize();
|
||||
qpp = QuicPacketProtectionV1::instance.get();
|
||||
}
|
||||
else if ( version == 0x6b3343cf ) { // quicv2
|
||||
QuicPacketProtectionV2::Initialize();
|
||||
qpp = QuicPacketProtectionV2::instance.get();
|
||||
}
|
||||
else {
|
||||
server_client_secret = hkdf_expand_server_initial_info(INITIAL_SECRET_LEN, initial_secret);
|
||||
throw hilti::rt::RuntimeError(hilti::rt::fmt("unable to handle version %lx", version));
|
||||
}
|
||||
|
||||
std::vector<uint8_t> key = hkdf_expand_key_info(AEAD_KEY_LEN, server_client_secret);
|
||||
std::vector<uint8_t> iv = hkdf_expand_iv_info(AEAD_IV_LEN, server_client_secret);
|
||||
std::vector<uint8_t> hp = hkdf_expand_hp_info(AEAD_HP_LEN, server_client_secret);
|
||||
const auto& secret = qpp->GetSecret(from_client, connection_id);
|
||||
std::vector<uint8_t> key = qpp->GetKey(secret);
|
||||
std::vector<uint8_t> iv = qpp->GetIv(secret);
|
||||
std::vector<uint8_t> hp = qpp->GetHp(secret);
|
||||
|
||||
DecryptionInformation decryptInfo = remove_header_protection(hp, encrypted_offset, all_data);
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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 49320 127.0.0.1 443 quicv2 fa603212c8688817af3d3238735bc7 b168b5cc localhost quic-echo-example ISIIisIH
|
||||
#close XXXX-XX-XX-XX-XX-XX
|
|
@ -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 49320 127.0.0.1 443 TLSv13 TLS_AES_128_GCM_SHA256 x25519 localhost F - - F Cs - - -
|
||||
#close XXXX-XX-XX-XX-XX-XX
|
|
@ -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
|
|
@ -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 50841 127.0.0.1 443 quicv2 bdf0c5b27927cc667e58d95b cdc8b6e6 - h3 ISishIHH
|
||||
#close XXXX-XX-XX-XX-XX-XX
|
|
@ -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 50841 127.0.0.1 443 TLSv13 TLS_AES_128_GCM_SHA256 x25519 - F - - F Cs - - -
|
||||
#close XXXX-XX-XX-XX-XX-XX
|
|
@ -1,3 +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>)
|
||||
1693925959.000001 CHhAvVGS1DHFjwGM9 violation protocol QUIC unhandled QUIC version 0x10010000 (<...>/QUIC.spicy:<line>:<column>)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
|
||||
QUIC::unhandled_version, CHhAvVGS1DHFjwGM9, T, 268500992, \x00,
|
BIN
testing/btest/Traces/quic/quicv2-echo-443.pcap
Normal file
BIN
testing/btest/Traces/quic/quicv2-echo-443.pcap
Normal file
Binary file not shown.
BIN
testing/btest/Traces/quic/quicv2-http3-443.pcap
Normal file
BIN
testing/btest/Traces/quic/quicv2-http3-443.pcap
Normal file
Binary file not shown.
|
@ -0,0 +1,8 @@
|
|||
# @TEST-DOC: Pcap with quicv2 echo traffic produced with https://raw.githubusercontent.com/quic-go/quic-go/master/example/echo/echo.go
|
||||
#
|
||||
# @TEST-REQUIRES: ${SCRIPTS}/have-spicy
|
||||
# @TEST-EXEC: zeek -Cr $TRACES/quic/quicv2-echo-443.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
|
|
@ -0,0 +1,8 @@
|
|||
# @TEST-DOC: Pcap with quicv2 http3 traffic produced with https://raw.githubusercontent.com/quic-go/quic-go/master/example/main.go
|
||||
#
|
||||
# @TEST-REQUIRES: ${SCRIPTS}/have-spicy
|
||||
# @TEST-EXEC: zeek -Cr $TRACES/quic/quicv2-http3-443.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
|
|
@ -1,11 +1,15 @@
|
|||
# @TEST-DOC: Test that runs the pcap
|
||||
|
||||
# @TEST-REQUIRES: ${SCRIPTS}/have-spicy
|
||||
# @TEST-EXEC: zeek -Cr $TRACES/quic/vector-max-size-crash.pcap base/protocols/quic
|
||||
# @TEST-EXEC: zeek -Cr $TRACES/quic/vector-max-size-crash.pcap base/protocols/quic %INPUT > out
|
||||
# @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
|
||||
# @TEST-EXEC: btest-diff out
|
||||
|
||||
# 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
|
||||
# @TEST-EXEC: 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
|
||||
|
||||
event QUIC::unhandled_version(c: connection, is_orig: bool, version: count, dcid: string, scid: string)
|
||||
{
|
||||
print "QUIC::unhandled_version", c$uid, is_orig, version, dcid, scid;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue