From 4ca6f690d7c08464e5f8469be149a538daec3130 Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Mon, 8 Jan 2024 20:45:00 +0100 Subject: [PATCH 1/2] quic: Support decryption of a few more versions --- scripts/base/protocols/quic/consts.zeek | 19 +++ src/analyzer/protocol/quic/QUIC.spicy | 41 ++++- src/analyzer/protocol/quic/decrypt_crypto.cc | 158 ++++++++++++------- 3 files changed, 158 insertions(+), 60 deletions(-) diff --git a/scripts/base/protocols/quic/consts.zeek b/scripts/base/protocols/quic/consts.zeek index 477c8abdaf..0fa5fb5475 100644 --- a/scripts/base/protocols/quic/consts.zeek +++ b/scripts/base/protocols/quic/consts.zeek @@ -4,5 +4,24 @@ export { const version_strings: table[count] of string = { [0x00000001] = "1", [0x6b3343cf] = "quicv2", + [0xff000016] = "draft-22", + [0xff000017] = "draft-23", + [0xff000018] = "draft-24", + [0xff000019] = "draft-25", + [0xff00001a] = "draft-26", + [0xff00001b] = "draft-27", + [0xff00001c] = "draft-28", + [0xff00001d] = "draft-29", + [0xff00001e] = "draft-30", + [0xff00001f] = "draft-30", + [0xff000020] = "draft-32", + [0xff000021] = "draft-33", + [0xff000022] = "draft-34", + [0xfaceb001] = "mvfst (faceb001)", + [0xfaceb002] = "mvfst (faceb002)", + [0xfaceb00e] = "mvfst (faceb00e)", + [0xfaceb011] = "mvfst (faceb011)", + [0xfaceb012] = "mvfst (faceb012)", + [0xfaceb013] = "mvfst (faceb013)", } &default=function(version: count): string { return fmt("unknown-%x", version); }; } diff --git a/src/analyzer/protocol/quic/QUIC.spicy b/src/analyzer/protocol/quic/QUIC.spicy index df5e89d7ca..d6522c8e93 100644 --- a/src/analyzer/protocol/quic/QUIC.spicy +++ b/src/analyzer/protocol/quic/QUIC.spicy @@ -27,9 +27,6 @@ function can_decrypt(long_header: LongHeaderPacket, context: ConnectionIDInfo, i if ( ! long_header.is_initial ) return False; - if ( long_header.version != Version1 && long_header.version != Version2 ) - return False; - if ( is_client ) return ! context.client_initial_processed; @@ -80,6 +77,25 @@ type ConnectionIDInfo = struct { ############## # Definitions ############## +const VersionDraft22: uint32 = 0xff000016; +const VersionDraft23: uint32 = 0xff000017; +const VersionDraft24: uint32 = 0xff000018; +const VersionDraft25: uint32 = 0xff000019; +const VersionDraft26: uint32 = 0xff00001a; +const VersionDraft27: uint32 = 0xff00001b; +const VersionDraft28: uint32 = 0xff00001c; +const VersionDraft29: uint32 = 0xff00001d; +const VersionDraft30: uint32 = 0xff00001e; +const VersionDraft31: uint32 = 0xff00001f; +const VersionDraft32: uint32 = 0xff000020; +const VersionDraft33: uint32 = 0xff000021; +const VersionDraft34: uint32 = 0xff000022; +const VersionFace001: uint32 = 0xfaceb001; +const VersionFace002: uint32 = 0xfaceb002; +const VersionFace00e: uint32 = 0xfaceb00e; +const VersionFace011: uint32 = 0xfaceb011; +const VersionFace012: uint32 = 0xfaceb012; +const VersionFace013: uint32 = 0xfaceb013; const Version1: uint32 = 0x00000001; const Version2: uint32 = 0x6b3343cf; @@ -236,6 +252,25 @@ public type LongHeaderPacket = unit { src_conn_id: bytes &size=self.client_conn_id_length; switch ( self.version ) { + VersionDraft22, + VersionDraft23, + VersionDraft24, + VersionDraft25, + VersionDraft26, + VersionDraft27, + VersionDraft28, + VersionDraft29, + VersionDraft30, + VersionDraft31, + VersionDraft32, + VersionDraft33, + VersionDraft34, + VersionFace001, + VersionFace002, + VersionFace00e, + VersionFace011, + VersionFace012, + VersionFace013, Version1 -> v1: LongHeaderPacketV1(self); Version2 -> v2: LongHeaderPacketV2(self); * -> unknown: UnhandledVersion(self) { diff --git a/src/analyzer/protocol/quic/decrypt_crypto.cc b/src/analyzer/protocol/quic/decrypt_crypto.cc index 4f2fc38b50..d38bc4154f 100644 --- a/src/analyzer/protocol/quic/decrypt_crypto.cc +++ b/src/analyzer/protocol/quic/decrypt_crypto.cc @@ -24,7 +24,6 @@ refactors as C++ development is not our main profession. #include #include #include -#include #include #include #include @@ -208,7 +207,6 @@ hilti::rt::Bytes decrypt(const std::vector& client_key, const hilti::rt // 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; @@ -216,7 +214,6 @@ struct HkdfCtx { EVP_PKEY_CTX* hp_info_ctx = nullptr; }; - struct HkdfCtxParam { EVP_PKEY_CTX** ctx; std::vector info; @@ -251,9 +248,9 @@ std::vector hkdf_expand(EVP_PKEY_CTX* ctx, size_t out_len, const std::v class QuicPacketProtection { public: - std::vector GetSecret(bool is_orig, const hilti::rt::Bytes& connection_id) { + std::vector GetSecret(bool is_orig, uint32_t version, const hilti::rt::Bytes& connection_id) { const auto& ctxs = GetHkdfCtxs(); - const auto initial_secret = hkdf_extract(GetInitialSalt(), connection_id); + const auto initial_secret = hkdf_extract(GetInitialSalt(version), 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); } @@ -273,7 +270,8 @@ public: return hkdf_expand(ctxs.hp_info_ctx, AEAD_HP_LEN, secret); } - virtual const std::vector& GetInitialSalt() = 0; + virtual bool Supports(uint32_t version) const = 0; + virtual const std::vector& GetInitialSalt(uint32_t version) const = 0; virtual HkdfCtx& GetHkdfCtxs() = 0; virtual ~QuicPacketProtection() = default; @@ -293,34 +291,73 @@ public: // 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 bool Supports(uint32_t version) const override { + // Quic V1 + if ( version == 0x00000001 ) + return true; + + // Draft 22 through 34 + if ( version >= 0xff000016 && version <= 0xff000022 ) + return true; + + // mvfst from facebook + if ( version == 0xfaceb001 || (version >= 0xfaceb002 && version <= 0xfaceb013) ) + return true; + + return false; + }; + + virtual const std::vector& GetInitialSalt(uint32_t version) const override { + static 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}; + + // https://insights.sei.cmu.edu/documents/4499/2023_017_001_890985.pdf + static std::vector INITIAL_SALT_D22 = {0x7f, 0xbc, 0xdb, 0x0e, 0x7c, 0x66, 0xbb, 0xe9, 0x19, 0x3a, + 0x96, 0xcd, 0x21, 0x51, 0x9e, 0xbd, 0x7a, 0x02, 0x64, 0x4a}; + + static std::vector INITIAL_SALT_D23_D28 = {0xc3, 0xee, 0xf7, 0x12, 0xc7, 0x2e, 0xbb, 0x5a, 0x11, 0xa7, + 0xd2, 0x43, 0x2b, 0xb4, 0x63, 0x65, 0xbe, 0xf9, 0xf5, 0x02}; + + static std::vector INITIAL_SALT_D29_D32 = {0xaf, 0xbf, 0xec, 0x28, 0x99, 0x93, 0xd2, 0x4c, 0x9e, 0x97, + 0x86, 0xf1, 0x9c, 0x61, 0x11, 0xe0, 0x43, 0x90, 0xa8, 0x99}; + if ( version == 0xff000016 ) + return INITIAL_SALT_D22; + + if ( version >= 0xff000017 && version <= 0xff00001c ) + return INITIAL_SALT_D23_D28; + + if ( version >= 0xff00001d && version <= 0xff000020 ) + return INITIAL_SALT_D29_D32; + + if ( version == 0xfaceb001 ) + return INITIAL_SALT_D22; + + if ( version >= 0xfaceb002 && version <= 0xfaceb013 ) + return INITIAL_SALT_D23_D28; + + 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 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}; std::vector hkdf_ctx_params = { {&hkdf_ctxs.client_in_ctx, CLIENT_INITIAL_INFO}, @@ -333,7 +370,6 @@ public: QuicPacketProtection::Initialize(hkdf_ctx_params); instance = std::make_unique(); - hkdf_ctxs.initialized = true; } static HkdfCtx hkdf_ctxs; @@ -347,32 +383,35 @@ std::unique_ptr QuicPacketProtectionV1::instance = nullp // 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 bool Supports(uint32_t version) const override { return version == 0x6b3343cf; } + + virtual const std::vector& GetInitialSalt(uint32_t version) const override { + static 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}; + + return INITIAL_SALT_V2; + } + virtual HkdfCtx& GetHkdfCtxs() override { return hkdf_ctxs; } static void Initialize() { - if ( hkdf_ctxs.initialized ) - return; + 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}; + std::vector hkdf_ctx_params = { {&hkdf_ctxs.client_in_ctx, CLIENT_INITIAL_INFO_V2}, {&hkdf_ctxs.server_in_ctx, SERVER_INITIAL_INFO_V2}, @@ -383,7 +422,6 @@ public: QuicPacketProtection::Initialize(hkdf_ctx_params); instance = std::make_unique(); - hkdf_ctxs.initialized = true; } static HkdfCtx hkdf_ctxs; @@ -404,6 +442,13 @@ hilti::rt::Bytes QUIC_decrypt_crypto_payload(const hilti::rt::integer::safe& encrypted_offset, const hilti::rt::integer::safe& payload_length, const hilti::rt::Bool& from_client) { + static bool initialized = false; + if ( ! initialized ) { + QuicPacketProtectionV1::Initialize(); + QuicPacketProtectionV2::Initialize(); + initialized = true; + } + if ( payload_length < 20 ) throw hilti::rt::RuntimeError(hilti::rt::fmt("payload too small %ld < 20", payload_length)); @@ -411,21 +456,20 @@ hilti::rt::Bytes QUIC_decrypt_crypto_payload(const hilti::rt::integer::safeSupports(v) ) { qpp = QuicPacketProtectionV1::instance.get(); } - else if ( version == 0x6b3343cf ) { // quicv2 - QuicPacketProtectionV2::Initialize(); + else if ( QuicPacketProtectionV2::instance->Supports(v) ) { qpp = QuicPacketProtectionV2::instance.get(); } else { - throw hilti::rt::RuntimeError(hilti::rt::fmt("unable to handle version %lx", version)); + throw hilti::rt::RuntimeError(hilti::rt::fmt("unable to decrypt QUIC version 0x%lx", version)); } - const auto& secret = qpp->GetSecret(from_client, connection_id); + const auto& secret = qpp->GetSecret(from_client, v, connection_id); std::vector key = qpp->GetKey(secret); std::vector iv = qpp->GetIv(secret); std::vector hp = qpp->GetHp(secret); From ec9ed812505e2aae2d0e94902be55698d469b079 Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Tue, 9 Jan 2024 09:44:43 +0100 Subject: [PATCH 2/2] quic: Handle and log unhandled_version --- scripts/base/protocols/quic/main.zeek | 13 +++++++++++++ .../quic.log | 11 +++++++++++ .../base/protocols/quic/vector-max-size-crash.zeek | 1 + 3 files changed, 25 insertions(+) create mode 100644 testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/quic.log diff --git a/scripts/base/protocols/quic/main.zeek b/scripts/base/protocols/quic/main.zeek index 4d793cbdc5..9a119c3f6a 100644 --- a/scripts/base/protocols/quic/main.zeek +++ b/scripts/base/protocols/quic/main.zeek @@ -157,6 +157,19 @@ event QUIC::retry_packet(c: connection, is_orig: bool, version: count, dcid: str delete c$quic; } +# If we couldn't handle a version, log it as a single record. +event QUIC::unhandled_version(c: connection, is_orig: bool, version: count, dcid: string, scid: string) + { + if ( ! c?$quic ) + set_conn(c, is_orig, version, dcid, scid); + + add_to_history(c, is_orig, "UNHANDLED_VERSION"); + + 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) diff --git a/testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/quic.log b/testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/quic.log new file mode 100644 index 0000000000..eaccf8f7f2 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.quic.vector-max-size-crash/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 1.2.3.4 45492 7.7.7.7 443 unknown-10010000 00 - - - U +#close XXXX-XX-XX-XX-XX-XX 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 77636fc5f2..dc6b802389 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 @@ -6,6 +6,7 @@ # @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 +# @TEST-EXEC: btest-diff quic.log # @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