From 164d580a1ae602e2dcd0ab5758dc0837db77e693 Mon Sep 17 00:00:00 2001 From: Arne Welzel Date: Mon, 8 Jan 2024 20:45:00 +0100 Subject: [PATCH] Support decryption of a few more versions --- analyzer/QUIC.spicy | 41 +++++++++++++++-- analyzer/decrypt_crypto.cc | 92 ++++++++++++++++++++++++++++++-------- scripts/consts.zeek | 19 ++++++++ 3 files changed, 130 insertions(+), 22 deletions(-) diff --git a/analyzer/QUIC.spicy b/analyzer/QUIC.spicy index df5e89d..2f9c441 100644 --- a/analyzer/QUIC.spicy +++ b/analyzer/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 -> d: LongHeaderPacketV1(self); + VersionFace001, + VersionFace002, + VersionFace00e, + VersionFace011, + VersionFace012, + VersionFace013 -> d: LongHeaderPacketV1(self); Version1 -> v1: LongHeaderPacketV1(self); Version2 -> v2: LongHeaderPacketV2(self); * -> unknown: UnhandledVersion(self) { diff --git a/analyzer/decrypt_crypto.cc b/analyzer/decrypt_crypto.cc index 4f2fc38..f6eb297 100644 --- a/analyzer/decrypt_crypto.cc +++ b/analyzer/decrypt_crypto.cc @@ -208,7 +208,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; @@ -251,9 +250,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 +272,8 @@ class QuicPacketProtection { return hkdf_expand(ctxs.hp_info_ctx, AEAD_HP_LEN, secret); } - virtual const std::vector& GetInitialSalt() = 0; + virtual bool Supports(uint32_t version) = 0; + virtual const std::vector& GetInitialSalt(uint32_t version) = 0; virtual HkdfCtx& GetHkdfCtxs() = 0; virtual ~QuicPacketProtection() = default; @@ -296,6 +296,16 @@ class QuicPacketProtection { 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 +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}; + +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}; + +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}; + std::vector CLIENT_INITIAL_INFO = {0x00, 0x20, 0x0f, 0x74, 0x6c, 0x73, 0x31, 0x33, 0x20, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x20, 0x69, 0x6e, 0x00}; @@ -314,14 +324,50 @@ std::vector HP_INFO = {0x00, 0x10, 0x0d, 0x74, 0x6c, 0x73, 0x31, 0x33, class QuicPacketProtectionV1 : public QuicPacketProtection { public: - virtual std::vector& GetInitialSalt() override { return INITIAL_SALT_V1; } + virtual bool Supports(uint32_t version) 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 == 0xfaceb00e || version == 0xfaceb011 || + version == 0xfaceb012 || version == 0xfaceb013 ) + return true; + + return false; + }; + + virtual std::vector& GetInitialSalt(uint32_t version) override { + if ( version == 0x00000001 || version == 0xff000021 || version == 0xff000022 ) + return INITIAL_SALT_V1; + + 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; + + assert(false); + 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}, @@ -333,7 +379,6 @@ class QuicPacketProtectionV1 : public QuicPacketProtection { QuicPacketProtection::Initialize(hkdf_ctx_params); instance = std::make_unique(); - hkdf_ctxs.initialized = true; } static HkdfCtx hkdf_ctxs; @@ -367,12 +412,16 @@ std::vector HP_INFO_V2 = {0x00, 0x10, 0x0f, 0x74, 0x6c, 0x73, 0x31, 0x3 class QuicPacketProtectionV2 : public QuicPacketProtection { public: - virtual std::vector& GetInitialSalt() override { return INITIAL_SALT_V2; } + virtual bool Supports(uint32_t version) override { return version == 0x6b3343cf; } + + virtual std::vector& GetInitialSalt(uint32_t version) override { + assert(version == 0x6b3343cf); + 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}, @@ -383,7 +432,6 @@ class QuicPacketProtectionV2 : public QuicPacketProtection { QuicPacketProtection::Initialize(hkdf_ctx_params); instance = std::make_unique(); - hkdf_ctxs.initialized = true; } static HkdfCtx hkdf_ctxs; @@ -404,6 +452,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 +466,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); diff --git a/scripts/consts.zeek b/scripts/consts.zeek index 477c8ab..0fa5fb5 100644 --- a/scripts/consts.zeek +++ b/scripts/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); }; }