From 0b6f4ef443a25e67ad7e8aba60715895bf3a803f Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Thu, 4 Jan 2024 17:16:36 +0100 Subject: [PATCH 1/5] quic: decrypt_crypto: Support QUIC v2 Attempt to refactor in order to re-use common code between the two versions. --- src/analyzer/protocol/quic/QUIC.spicy | 3 + src/analyzer/protocol/quic/decrypt_crypto.cc | 354 +++++++++++-------- 2 files changed, 217 insertions(+), 140 deletions(-) diff --git a/src/analyzer/protocol/quic/QUIC.spicy b/src/analyzer/protocol/quic/QUIC.spicy index 56dc0c9cad..70781da9f0 100644 --- a/src/analyzer/protocol/quic/QUIC.spicy +++ b/src/analyzer/protocol/quic/QUIC.spicy @@ -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, @@ -430,6 +431,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 +451,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, diff --git a/src/analyzer/protocol/quic/decrypt_crypto.cc b/src/analyzer/protocol/quic/decrypt_crypto.cc index 2bdb0636bd..4f2fc38b50 100644 --- a/src/analyzer/protocol/quic/decrypt_crypto.cc +++ b/src/analyzer/protocol/quic/decrypt_crypto.cc @@ -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 #include #include +#include #include #include @@ -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(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 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 CLIENT_INITIAL_INFO = {0x00, 0x20, 0x0f, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, 0x63, - 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x00}; - -std::vector SERVER_INITIAL_INFO = {0x00, 0x20, 0x0f, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, 0x73, - 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x00}; - -std::vector KEY_INFO = {0x00, 0x10, 0x0e, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, - 0x71, 0x75, 0x69, 0x63, 0x20, 0x6b, 0x65, 0x79, 0x00}; - -std::vector IV_INFO = {0x00, 0x0c, 0x0d, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, - 0x71, 0x75, 0x69, 0x63, 0x20, 0x69, 0x76, 0x00}; - -std::vector 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 hkdf_extract(const hilti::rt::Bytes& connection_id) { - std::vector 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 hkdf_expand(EVP_PKEY_CTX* ctx, size_t out_len, const std::vector& key) { - std::vector 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 hkdf_expand_client_initial_info(size_t out_len, const std::vector& 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 hkdf_expand_server_initial_info(size_t out_len, const std::vector& 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 hkdf_expand_key_info(size_t out_len, const std::vector& 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 hkdf_expand_iv_info(size_t out_len, const std::vector& 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 hkdf_expand_hp_info(size_t out_len, const std::vector& 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 calculate_nonce(std::vector 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& 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& 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 info; +}; + +/* +HKDF-Extract as described in https://www.rfc-editor.org/rfc/rfc8446.html#section-7.1 +*/ +std::vector hkdf_extract(const std::vector& salt, const hilti::rt::Bytes& connection_id) { + std::vector 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 hkdf_expand(EVP_PKEY_CTX* ctx, size_t out_len, const std::vector& key) { + std::vector 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 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 GetKey(const std::vector& secret) { + const auto& ctxs = GetHkdfCtxs(); + return hkdf_expand(ctxs.key_info_ctx, AEAD_KEY_LEN, secret); + } + + std::vector GetIv(const std::vector& secret) { + const auto& ctxs = GetHkdfCtxs(); + return hkdf_expand(ctxs.iv_info_ctx, AEAD_IV_LEN, secret); + } + + std::vector GetHp(const std::vector& secret) { + const auto& ctxs = GetHkdfCtxs(); + return hkdf_expand(ctxs.hp_info_ctx, AEAD_HP_LEN, secret); + } + + virtual const std::vector& GetInitialSalt() = 0; + virtual HkdfCtx& GetHkdfCtxs() = 0; + + virtual ~QuicPacketProtection() = default; + + // Helper to initialize HKDF expand only contexts. + static void Initialize(std::vector& 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 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 CLIENT_INITIAL_INFO = {0x00, 0x20, 0x0f, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, 0x63, + 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x00}; + +std::vector SERVER_INITIAL_INFO = {0x00, 0x20, 0x0f, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, 0x73, + 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x69, 0x6e, 0x00}; + +std::vector KEY_INFO = {0x00, 0x10, 0x0e, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, + 0x71, 0x75, 0x69, 0x63, 0x20, 0x6b, 0x65, 0x79, 0x00}; + +std::vector IV_INFO = {0x00, 0x0c, 0x0d, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, + 0x71, 0x75, 0x69, 0x63, 0x20, 0x69, 0x76, 0x00}; + +std::vector 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& 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 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(); + hkdf_ctxs.initialized = true; + } + + static HkdfCtx hkdf_ctxs; + static std::unique_ptr instance; +}; + +HkdfCtx QuicPacketProtectionV1::hkdf_ctxs = {0}; +std::unique_ptr QuicPacketProtectionV1::instance = nullptr; + + +// QUIC v2 +// +// https://datatracker.ietf.org/doc/rfc9369/ +std::vector 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 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 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 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 IV_INFO_V2 = {0x00, 0x0c, 0x0f, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, 0x71, + 0x75, 0x69, 0x63, 0x76, 0x32, 0x20, 0x69, 0x76, 0x00}; + +std::vector 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& GetInitialSalt() override { return INITIAL_SALT_V2; } + virtual HkdfCtx& GetHkdfCtxs() override { return hkdf_ctxs; } + + static void Initialize() { + if ( hkdf_ctxs.initialized ) + return; + std::vector 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(); + hkdf_ctxs.initialized = true; + } + + static HkdfCtx hkdf_ctxs; + static std::unique_ptr instance; +}; + +HkdfCtx QuicPacketProtectionV2::hkdf_ctxs = {0}; +std::unique_ptr 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& version, + const hilti::rt::Bytes& all_data, const hilti::rt::Bytes& connection_id, const hilti::rt::integer::safe& encrypted_offset, const hilti::rt::integer::safe& 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 initial_secret = hkdf_extract(connection_id); + QuicPacketProtection* qpp = nullptr; - std::vector 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 key = hkdf_expand_key_info(AEAD_KEY_LEN, server_client_secret); - std::vector iv = hkdf_expand_iv_info(AEAD_IV_LEN, server_client_secret); - std::vector hp = hkdf_expand_hp_info(AEAD_HP_LEN, server_client_secret); + const auto& secret = qpp->GetSecret(from_client, connection_id); + std::vector key = qpp->GetKey(secret); + std::vector iv = qpp->GetIv(secret); + std::vector hp = qpp->GetHp(secret); DecryptionInformation decryptInfo = remove_header_protection(hp, encrypted_offset, all_data); From dabe85ebbf4fcdcb6ea0b0967410ce496b28740a Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Thu, 4 Jan 2024 13:59:37 +0100 Subject: [PATCH 2/5] quic: analyzer: Support QUIC v2 QUIC v2 changed the version *and* the packet type enumeration to prevent protocol ossification. Use an intermediary unit to handle the difference. --- scripts/base/protocols/quic/consts.zeek | 1 + src/analyzer/protocol/quic/QUIC.evt | 3 - src/analyzer/protocol/quic/QUIC.spicy | 79 +++++++++++++++++++------ 3 files changed, 61 insertions(+), 22 deletions(-) diff --git a/scripts/base/protocols/quic/consts.zeek b/scripts/base/protocols/quic/consts.zeek index b502b04be1..477c8abdaf 100644 --- a/scripts/base/protocols/quic/consts.zeek +++ b/scripts/base/protocols/quic/consts.zeek @@ -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); }; } diff --git a/src/analyzer/protocol/quic/QUIC.evt b/src/analyzer/protocol/quic/QUIC.evt index 984e8a8d6b..c70717851f 100644 --- a/src/analyzer/protocol/quic/QUIC.evt +++ b/src/analyzer/protocol/quic/QUIC.evt @@ -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); diff --git a/src/analyzer/protocol/quic/QUIC.spicy b/src/analyzer/protocol/quic/QUIC.spicy index 70781da9f0..284e152e40 100644 --- a/src/analyzer/protocol/quic/QUIC.spicy +++ b/src/analyzer/protocol/quic/QUIC.spicy @@ -24,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 ) @@ -81,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, @@ -155,17 +166,56 @@ 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; + } + }; +}; 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(cast($$)); fixed_bit: 6; - packet_type: 4..5 &convert=cast(cast($$)); + packet_type: 4..5; type_specific_bits: 0..3 &convert=cast($$); }; @@ -175,18 +225,9 @@ 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; - } - - LongPacketType::ZERO_RTT -> zerortt_hdr : ZeroRTTPacket(self); - LongPacketType::HANDSHAKE -> handshake_hdr : HandshakePacket(self); - LongPacketType::RETRY -> retry_hdr : RetryPacket(self); + switch ( self.version ) { + Version1 -> v1: LongHeaderPacketV1(self); + Version2 -> v2: LongHeaderPacketV2(self); }; }; @@ -402,7 +443,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""; @@ -470,7 +511,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; } From 727091ed67de2d4564a6b0535779c3fb954f7ba1 Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Thu, 4 Jan 2024 16:00:41 +0100 Subject: [PATCH 3/5] quic: tests: Add QUIC v2 test cases Produced using examples from the go-quic project, patching the clients to force QUIC v2. --- .../conn.log.cut | 3 +++ .../quic.log | 11 +++++++++++ .../ssl.log | 11 +++++++++++ .../conn.log.cut | 3 +++ .../quic.log | 11 +++++++++++ .../ssl.log | 11 +++++++++++ testing/btest/Traces/quic/quicv2-echo-443.pcap | Bin 0 -> 7415 bytes testing/btest/Traces/quic/quicv2-http3-443.pcap | Bin 0 -> 5627 bytes .../base/protocols/quic/quicv2-echo-443.zeek | 7 +++++++ .../base/protocols/quic/quicv2-http3-443.zeek | 7 +++++++ 10 files changed, 64 insertions(+) create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/conn.log.cut create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/quic.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/ssl.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/conn.log.cut create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/quic.log create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/ssl.log create mode 100644 testing/btest/Traces/quic/quicv2-echo-443.pcap create mode 100644 testing/btest/Traces/quic/quicv2-http3-443.pcap create mode 100644 testing/btest/scripts/base/protocols/quic/quicv2-echo-443.zeek create mode 100644 testing/btest/scripts/base/protocols/quic/quicv2-http3-443.zeek diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/conn.log.cut b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/conn.log.cut new file mode 100644 index 0000000000..46d72b1541 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/conn.log.cut @@ -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 diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/quic.log b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/quic.log new file mode 100644 index 0000000000..2680a6b719 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/quic.log @@ -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 diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/ssl.log b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/ssl.log new file mode 100644 index 0000000000..0167629cb9 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-echo-443/ssl.log @@ -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 diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/conn.log.cut b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/conn.log.cut new file mode 100644 index 0000000000..46d72b1541 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/conn.log.cut @@ -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 diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/quic.log b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/quic.log new file mode 100644 index 0000000000..c02fd62fa8 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/quic.log @@ -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 diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/ssl.log b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/ssl.log new file mode 100644 index 0000000000..1f7c43083f --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.quicv2-http3-443/ssl.log @@ -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 diff --git a/testing/btest/Traces/quic/quicv2-echo-443.pcap b/testing/btest/Traces/quic/quicv2-echo-443.pcap new file mode 100644 index 0000000000000000000000000000000000000000..7b26d0de12cc5bcc3236998552121f81fc4b0ddf GIT binary patch literal 7415 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 literal 0 HcmV?d00001 diff --git a/testing/btest/scripts/base/protocols/quic/quicv2-echo-443.zeek b/testing/btest/scripts/base/protocols/quic/quicv2-echo-443.zeek new file mode 100644 index 0000000000..891ac2039b --- /dev/null +++ b/testing/btest/scripts/base/protocols/quic/quicv2-echo-443.zeek @@ -0,0 +1,7 @@ +# @TEST-DOC: Pcap with quicv2 echo traffic produced with https://raw.githubusercontent.com/quic-go/quic-go/master/example/echo/echo.go +# +# @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 diff --git a/testing/btest/scripts/base/protocols/quic/quicv2-http3-443.zeek b/testing/btest/scripts/base/protocols/quic/quicv2-http3-443.zeek new file mode 100644 index 0000000000..af7951eff5 --- /dev/null +++ b/testing/btest/scripts/base/protocols/quic/quicv2-http3-443.zeek @@ -0,0 +1,7 @@ +# @TEST-DOC: Pcap with quicv2 http3 traffic produced with https://raw.githubusercontent.com/quic-go/quic-go/master/example/main.go +# +# @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 From 50cdac922fef807ecf99c9c7d5994cd32dea745b Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Thu, 4 Jan 2024 20:07:18 +0100 Subject: [PATCH 4/5] quic: analyzer: Recognize and report unknown versions better 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. --- src/analyzer/protocol/quic/QUIC.evt | 2 ++ src/analyzer/protocol/quic/QUIC.spicy | 13 +++++++++++++ .../analyzer.log.cut | 2 +- .../out | 2 ++ .../base/protocols/quic/vector-max-size-crash.zeek | 12 ++++++++---- 5 files changed, 26 insertions(+), 5 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/out diff --git a/src/analyzer/protocol/quic/QUIC.evt b/src/analyzer/protocol/quic/QUIC.evt index c70717851f..b81b0084db 100644 --- a/src/analyzer/protocol/quic/QUIC.evt +++ b/src/analyzer/protocol/quic/QUIC.evt @@ -20,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); diff --git a/src/analyzer/protocol/quic/QUIC.spicy b/src/analyzer/protocol/quic/QUIC.spicy index 284e152e40..df5e89d7ca 100644 --- a/src/analyzer/protocol/quic/QUIC.spicy +++ b/src/analyzer/protocol/quic/QUIC.spicy @@ -204,6 +204,16 @@ public type LongHeaderPacketV2 = unit(inout outer: LongHeaderPacket) { }; }; +# 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; @@ -228,6 +238,9 @@ public type LongHeaderPacket = unit { switch ( self.version ) { Version1 -> v1: LongHeaderPacketV1(self); Version2 -> v2: LongHeaderPacketV2(self); + * -> unknown: UnhandledVersion(self) { + throw "unhandled QUIC version 0x%x" % self.version; + } }; }; diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/analyzer.log.cut b/testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/analyzer.log.cut index 2b3de832a7..a8d2f384e3 100644 --- a/testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/analyzer.log.cut +++ b/testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/analyzer.log.cut @@ -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::) +1693925959.000001 CHhAvVGS1DHFjwGM9 violation protocol QUIC unhandled QUIC version 0x10010000 (<...>/QUIC.spicy::) diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/out b/testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/out new file mode 100644 index 0000000000..3f16ba040c --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/out @@ -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, diff --git a/testing/btest/scripts/base/protocols/quic/vector-max-size-crash.zeek b/testing/btest/scripts/base/protocols/quic/vector-max-size-crash.zeek index 78e9adeb9b..77636fc5f2 100644 --- a/testing/btest/scripts/base/protocols/quic/vector-max-size-crash.zeek +++ b/testing/btest/scripts/base/protocols/quic/vector-max-size-crash.zeek @@ -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::)/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::)/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; + } From 0796a191c6ad30b365ff19817c74f57bfe0eb2c7 Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Fri, 5 Jan 2024 09:26:02 +0100 Subject: [PATCH 5/5] quic: tests: Require have-spicy --- testing/btest/scripts/base/protocols/quic/quicv2-echo-443.zeek | 1 + testing/btest/scripts/base/protocols/quic/quicv2-http3-443.zeek | 1 + 2 files changed, 2 insertions(+) diff --git a/testing/btest/scripts/base/protocols/quic/quicv2-echo-443.zeek b/testing/btest/scripts/base/protocols/quic/quicv2-echo-443.zeek index 891ac2039b..51b80ccaa7 100644 --- a/testing/btest/scripts/base/protocols/quic/quicv2-echo-443.zeek +++ b/testing/btest/scripts/base/protocols/quic/quicv2-echo-443.zeek @@ -1,5 +1,6 @@ # @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 diff --git a/testing/btest/scripts/base/protocols/quic/quicv2-http3-443.zeek b/testing/btest/scripts/base/protocols/quic/quicv2-http3-443.zeek index af7951eff5..9b10252b40 100644 --- a/testing/btest/scripts/base/protocols/quic/quicv2-http3-443.zeek +++ b/testing/btest/scripts/base/protocols/quic/quicv2-http3-443.zeek @@ -1,5 +1,6 @@ # @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