From 2d950ffde9ca4b3d97e72cc347ec37ff8a265cf6 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Mon, 19 Apr 2021 11:18:59 +0200 Subject: [PATCH 01/24] ssl: rudimentary decryption for TLS 1.2 Several limitations still apply: - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 is the only supported cipher suite - Some tests are broken due to a failing assertion regarding bytestring - No newly written tests for decryption (the patch was tested extensively for our paper) - Several small open technical questions marked with FIXME - Architecture in the ssl module might not be optimal --- scripts/policy/protocols/ssl/decryption.zeek | 106 ++++++++ scripts/policy/protocols/ssl/heartbleed.zeek | 2 +- scripts/test-all-policy.zeek | 1 + scripts/zeekygen/__load__.zeek | 1 + src/analyzer/protocol/ssl/CMakeLists.txt | 4 +- src/analyzer/protocol/ssl/DTLS.cc | 5 + src/analyzer/protocol/ssl/DTLS.h | 2 + src/analyzer/protocol/ssl/SSL.cc | 238 ++++++++++++++++++ src/analyzer/protocol/ssl/SSL.h | 14 ++ src/analyzer/protocol/ssl/events.bif | 4 +- src/analyzer/protocol/ssl/functions.bif | 22 ++ .../protocol/ssl/proc-client-hello-tls.pac | 56 +++++ .../protocol/ssl/proc-server-hello-tls.pac | 41 +++ .../protocol/ssl/ssl-dtls-analyzer.pac | 13 +- .../protocol/ssl/ssl-dtls-protocol.pac | 2 +- .../protocol/ssl/tls-handshake-analyzer.pac | 4 +- .../protocol/ssl/tls-handshake-protocol.pac | 31 +++ .../really-all-events.log | 2 + .../base/protocols/ssl/handshake-events.test | 2 +- .../scripts/base/protocols/ssl/tls13.test | 2 +- .../ssl/tls13_encrypted_handshake_events.test | 2 +- 21 files changed, 541 insertions(+), 13 deletions(-) create mode 100644 scripts/policy/protocols/ssl/decryption.zeek create mode 100644 src/analyzer/protocol/ssl/proc-client-hello-tls.pac create mode 100644 src/analyzer/protocol/ssl/proc-server-hello-tls.pac diff --git a/scripts/policy/protocols/ssl/decryption.zeek b/scripts/policy/protocols/ssl/decryption.zeek new file mode 100644 index 0000000000..a3608d81e7 --- /dev/null +++ b/scripts/policy/protocols/ssl/decryption.zeek @@ -0,0 +1,106 @@ +#! Decrypt SSL/TLS payloads + +@load base/frameworks/input +@load base/frameworks/notice +@load base/protocols/conn +@load base/protocols/ssl + +module SSL; + +# Local +const input_stream_name = "input-tls-keylog-file"; + +type Idx: record { + client_random: string; +}; + +type Val: record { + secret: string; +}; + +global randoms: table[string] of string = {}; + +export { + redef record Info += { + # decryption uses client_random as identifier + client_random: string &log &optional; + }; + + const keylog_file = getenv("ZEEK_TLS_KEYLOG_FILE") &redef; + + global secrets: table[string] of string = {} &redef; + global keys: table[string] of string = {} &redef; + + event SSL::add_keys(client_random: string, val: string) + { + SSL::keys[client_random] = val; + } + + event SSL::add_secret(client_random: string, val: string) + { + SSL::secrets[client_random] = val; + } +} + +event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec) + { + c$ssl$client_random = client_random; + + if ( client_random in keys ) + { + set_keys(c, keys[client_random]); + } + else if ( client_random in secrets ) + { + set_secret(c, secrets[client_random]); + } + } + +event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string) + { + if ( c$ssl?$client_random ) + { + if ( c$ssl$client_random in keys ) + { + set_keys(c, keys[c$ssl$client_random]); + } + else if ( c$ssl$client_random in secrets ) + { + set_secret(c, secrets[c$ssl$client_random]); + } + else + { + # FIXME: should this be moved to reporter.log or removed completely? + #print "No suitable key or secret found for random:", randoms[c$uid]; + } + } + } + +event SSL::tls_input_done() + { + continue_processing(); + } + +event Input::end_of_data(name: string, source: string) + { + if ( name == input_stream_name ) + { + event SSL::tls_input_done(); + } +} + +event zeek_init() + { + # listen for secrets + Broker::subscribe("/zeek/tls/decryption"); + + # FIXME: is such a functionality helpful? + # ingest keylog file if the environment is set + if ( keylog_file != "" ) + { + suspend_processing(); + + Input::add_table([$name=input_stream_name, $source=keylog_file, $destination=secrets, $idx=Idx, $val=Val, $want_record=F]); + Input::remove(input_stream_name); + } +} diff --git a/scripts/policy/protocols/ssl/heartbleed.zeek b/scripts/policy/protocols/ssl/heartbleed.zeek index aabafbff14..40bc800b8c 100644 --- a/scripts/policy/protocols/ssl/heartbleed.zeek +++ b/scripts/policy/protocols/ssl/heartbleed.zeek @@ -223,7 +223,7 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) } } -event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) +event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string) { if ( !c?$ssl ) return; diff --git a/scripts/test-all-policy.zeek b/scripts/test-all-policy.zeek index 879d1fbd9b..0b421e7c21 100644 --- a/scripts/test-all-policy.zeek +++ b/scripts/test-all-policy.zeek @@ -101,6 +101,7 @@ @load protocols/ssh/geo-data.zeek @load protocols/ssh/interesting-hostnames.zeek @load protocols/ssh/software.zeek +#@load protocols/ssl/decryption.zeek @load protocols/ssl/expiring-certs.zeek @load protocols/ssl/extract-certs-pem.zeek @load protocols/ssl/heartbleed.zeek diff --git a/scripts/zeekygen/__load__.zeek b/scripts/zeekygen/__load__.zeek index 64494037b8..524c12a609 100644 --- a/scripts/zeekygen/__load__.zeek +++ b/scripts/zeekygen/__load__.zeek @@ -1,6 +1,7 @@ @load test-all-policy.zeek # Scripts which are commented out in test-all-policy.zeek. +@load protocols/ssl/decryption.zeek @load protocols/ssl/notary.zeek @load frameworks/control/controllee.zeek @load frameworks/control/controller.zeek diff --git a/src/analyzer/protocol/ssl/CMakeLists.txt b/src/analyzer/protocol/ssl/CMakeLists.txt index 47093a978e..2a9179ce64 100644 --- a/src/analyzer/protocol/ssl/CMakeLists.txt +++ b/src/analyzer/protocol/ssl/CMakeLists.txt @@ -10,8 +10,8 @@ zeek_plugin_bif(events.bif) zeek_plugin_bif(functions.bif) zeek_plugin_bif(consts.bif) zeek_plugin_pac(tls-handshake.pac tls-handshake-protocol.pac tls-handshake-analyzer.pac ssl-defs.pac - proc-client-hello.pac - proc-server-hello.pac + proc-client-hello-tls.pac + proc-server-hello-tls.pac proc-certificate.pac tls-handshake-signed_certificate_timestamp.pac ) diff --git a/src/analyzer/protocol/ssl/DTLS.cc b/src/analyzer/protocol/ssl/DTLS.cc index 09f9e7c305..ee823148bd 100644 --- a/src/analyzer/protocol/ssl/DTLS.cc +++ b/src/analyzer/protocol/ssl/DTLS.cc @@ -72,4 +72,9 @@ void DTLS_Analyzer::SendHandshake(uint16_t raw_tls_version, uint8_t msg_type, ui } } +bool DTLS_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version) + { + // noop for now as DTLS decryption is currently not supported + } + } // namespace zeek::analyzer::dtls diff --git a/src/analyzer/protocol/ssl/DTLS.h b/src/analyzer/protocol/ssl/DTLS.h index 1554c1bb6e..7c956f0860 100644 --- a/src/analyzer/protocol/ssl/DTLS.h +++ b/src/analyzer/protocol/ssl/DTLS.h @@ -27,6 +27,8 @@ public: static analyzer::Analyzer* Instantiate(Connection* conn) { return new DTLS_Analyzer(conn); } + bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version); + protected: binpac::DTLS::SSL_Conn* interp; binpac::TLSHandshake::Handshake_Conn* handshake_interp; diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index d5088a6746..6fd2349fa1 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -1,5 +1,6 @@ #include "zeek/analyzer/protocol/ssl/SSL.h" +#include "zeek/analyzer/Manager.h" #include "zeek/analyzer/protocol/tcp/TCP_Reassembler.h" #include "zeek/Reporter.h" #include "zeek/util.h" @@ -8,6 +9,73 @@ #include "zeek/analyzer/protocol/ssl/ssl_pac.h" #include "zeek/analyzer/protocol/ssl/tls-handshake_pac.h" +#include +#include +#include + +#define MSB(a) ((a>>8)&0xff) +#define LSB(a) (a&0xff) + +static void fmt_seq(uint32_t num, u_char* buf) + { + memset(buf, 0, 8); + uint32_t netnum = htonl(num); + memcpy(buf+4, &netnum, 4); + } + +static void print_hex(std::string name, u_char* data, int len) + { + int i = 0; + printf("%s (%d): ", name.c_str(), len); + if (len > 0) + printf("0x%02x", data[0]); + + for (i = 1; i < len; i++) + { + printf(" 0x%02x", data[i]); + } + printf("\n"); + } + +static bool tls1_prf(const u_char *secret, int secret_len, const char* label, + const u_char* rnd1, int rnd1_len, const u_char* rnd2, int rnd2_len, u_char* out, size_t out_len) + { + // alloc buffers + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); + size_t seed_len = strlen(label) + rnd1_len + rnd2_len; + u_char *seed = (u_char*) malloc(seed_len); + u_char *ptr = seed; + + // seed = label + rnd1 + rnd2 + memcpy(ptr, label, strlen(label)); + ptr += strlen(label); + memcpy(ptr, rnd1, rnd1_len); + ptr += rnd1_len; + memcpy(ptr, rnd2, rnd2_len); + ptr += rnd2_len; + + if (EVP_PKEY_derive_init(pctx) <= 0) + goto abort; /* Error */ + // FIXME: sha384 should not be hardcoded + if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0) + goto abort; /* Error */ + if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret, secret_len) <= 0) + goto abort; /* Error */ + if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed, seed_len) <= 0) + goto abort; /* Error */ + if (EVP_PKEY_derive(pctx, out, &out_len) <= 0) + goto abort; /* Error */ + + EVP_PKEY_CTX_free(pctx); + free(seed); + return true; + +abort: + EVP_PKEY_CTX_free(pctx); + free(seed); + return false; + } + namespace zeek::analyzer::ssl { SSL_Analyzer::SSL_Analyzer(Connection* c) @@ -16,12 +84,20 @@ SSL_Analyzer::SSL_Analyzer(Connection* c) interp = new binpac::SSL::SSL_Conn(this); handshake_interp = new binpac::TLSHandshake::Handshake_Conn(this); had_gap = false; + c_seq = 0; + s_seq = 0; + // FIXME: should this be initialized to nullptr? + secret = new zeek::StringVal(0, ""); + keys = new zeek::StringVal(0, ""); + pia = nullptr; } SSL_Analyzer::~SSL_Analyzer() { delete interp; delete handshake_interp; + delete secret; + delete keys; } void SSL_Analyzer::Done() @@ -98,4 +174,166 @@ void SSL_Analyzer::Undelivered(uint64_t seq, int len, bool orig) interp->NewGap(orig, len); } +void SSL_Analyzer::SetSecret(const u_char* data, int len) + { + // FIXME: Is this the proper way to initialize a zeek::StringVal? + // the alternative requires a cast: new zeek::StringVal(len, data) + secret = new zeek::StringVal(new zeek::String(data, len, true)); + } + +void SSL_Analyzer::SetKeys(const u_char* data, int len) + { + keys = new zeek::StringVal(new zeek::String(data, len, true)); + } + +bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version) + { + // Unsupported cipher suite. Currently supported: + // - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 == 0xC030 + auto cipher = handshake_interp->chosen_cipher(); + if ( cipher != 0xC030 ) + { + //printf("Unsupported cipher suite: %d\n", cipher); + return false; + } + + // Neither secret or key present: abort + if ( secret->Len() == 0 && keys->Len() == 0 ) + { + // FIXME: this is just for debugging + printf("Could not decrypt packet (missing key):\n"); + print_hex("->client_random:", handshake_interp->client_random().data(), handshake_interp->client_random().length()); + return false; + } + + // Secret present, but no keys derived yet: derive keys + if ( secret->Len() != 0 && keys->Len() == 0 ) + { + uint32_t ts = htonl((uint32_t) handshake_interp->gmt_unix_time()); + + u_char crand[32] = {0x00}; + u_char keybuf[72]; + + auto c_rnd = handshake_interp->client_random(); + auto s_rnd = handshake_interp->server_random(); + memcpy(crand, &(ts), 4); + memcpy(crand + 4, c_rnd.data(), c_rnd.length()); + + tls1_prf(secret->Bytes(), secret->Len(), "key expansion", s_rnd.data(), s_rnd.length(), + crand, sizeof(crand), keybuf, sizeof(keybuf)); + + // save derived keys + SetKeys(keybuf, sizeof(keybuf)); + } + + // Keys present: decrypt TLS application data + if ( keys->Len() != 0 ) + { + // session keys & AEAD data + u_char c_wk[32]; + u_char s_wk[32]; + u_char c_iv[4]; + u_char s_iv[4]; + u_char s_aead_nonce[12]; + u_char s_aead_tag[13]; + + memcpy(c_wk, keys->Bytes(), 32); + memcpy(s_wk, &(keys->Bytes()[32]), 32); + memcpy(c_iv, &(keys->Bytes()[64]), 4); + memcpy(s_iv, &(keys->Bytes()[68]), 4); + + // FIXME: should we change types here? + u_char* encrypted = (u_char*)data; + size_t encrypted_len = len; + + // FIXME: should this be moved to SSL_Conn? + if ( is_orig ) + c_seq++; + else + s_seq++; + + if ( is_orig ) + memcpy(s_aead_nonce, c_iv, 4); + else + memcpy(s_aead_nonce, s_iv, 4); + memcpy(&(s_aead_nonce[4]), encrypted, 8); + + EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + EVP_CIPHER_CTX_init(ctx); + EVP_CipherInit(ctx, EVP_aes_256_gcm(), NULL, NULL, 0); + + encrypted += 8; + // FIXME: is this because of nonce and aead tag? + encrypted_len -= 8; + encrypted_len -= 16; + + if (is_orig) + EVP_DecryptInit(ctx, EVP_aes_256_gcm(), c_wk, s_aead_nonce); + else + EVP_DecryptInit(ctx, EVP_aes_256_gcm(), s_wk, s_aead_nonce); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, encrypted + encrypted_len); + + if (is_orig) + fmt_seq(c_seq, s_aead_tag); + else + fmt_seq(s_seq, s_aead_tag); + + s_aead_tag[8] = content_type; + s_aead_tag[9] = MSB(raw_tls_version); + s_aead_tag[10] = LSB(raw_tls_version); + s_aead_tag[11] = MSB(encrypted_len); + s_aead_tag[12] = LSB(encrypted_len); + + u_char *decrypted = (u_char*)malloc(encrypted_len); + int decrypted_len = 0; + + EVP_DecryptUpdate(ctx, NULL, &decrypted_len, s_aead_tag, 13); + EVP_DecryptUpdate(ctx, decrypted, &decrypted_len, (const u_char*) encrypted, encrypted_len); + + int res = 0; + if ( ! (res = EVP_DecryptFinal(ctx, NULL, &res)) ) + { + printf("Decryption failed with return code %d. Invalid key?\n", res); + EVP_CIPHER_CTX_free(ctx); + free(decrypted); + return false; + } + + EVP_CIPHER_CTX_free(ctx); + ForwardDecryptedData(decrypted_len, reinterpret_cast(decrypted), is_orig); + + free(decrypted); + return true; + } + + // This is only reached if key derivation somehow failed + return false; + } + +void SSL_Analyzer::ForwardDecryptedData(int len, const u_char* data, bool is_orig) + { + if ( ! pia ) + { + pia = new analyzer::pia::PIA_TCP(Conn()); + if ( AddChildAnalyzer(pia) ) + { + pia->FirstPacket(true, nullptr); + pia->FirstPacket(false, nullptr); + } + else + reporter->FatalError("Could not initialize PIA"); + + // FIXME: Also statically add HTTP/H2 at the moment. + // We should move this bit to scriptland + auto http = analyzer_mgr->InstantiateAnalyzer("HTTP", Conn()); + if ( http ) + AddChildAnalyzer(http); + auto http2 = analyzer_mgr->InstantiateAnalyzer("HTTP2", Conn()); + if ( http2 ) + AddChildAnalyzer(http2); + } + + ForwardStream(len, data, is_orig); + } + } // namespace zeek::analyzer::ssl diff --git a/src/analyzer/protocol/ssl/SSL.h b/src/analyzer/protocol/ssl/SSL.h index 12dd2ce57c..4eac7e77a6 100644 --- a/src/analyzer/protocol/ssl/SSL.h +++ b/src/analyzer/protocol/ssl/SSL.h @@ -1,5 +1,6 @@ #pragma once +#include "zeek/analyzer/protocol/pia/PIA.h" #include "zeek/analyzer/protocol/tcp/TCP.h" #include "zeek/analyzer/protocol/ssl/events.bif.h" @@ -33,11 +34,24 @@ public: static analyzer::Analyzer* Instantiate(Connection* conn) { return new SSL_Analyzer(conn); } + // Key material for decryption + void SetSecret(const u_char* data, int len); + void SetKeys(const u_char* data, int len); + + bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version); + void ForwardDecryptedData(int len, const u_char* data, bool is_orig); + protected: binpac::SSL::SSL_Conn* interp; binpac::TLSHandshake::Handshake_Conn* handshake_interp; bool had_gap; + // FIXME: should this be moved into the connection? + int c_seq; + int s_seq; + zeek::StringVal *secret; + zeek::StringVal *keys; + zeek::analyzer::pia::PIA_TCP *pia; }; } // namespace zeek::analyzer::ssl diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index 25bc34398b..9192babfdf 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -558,9 +558,11 @@ event ssl_plaintext_data%(c: connection, is_orig: bool, record_version: count, c ## ## length: length of the entire message. ## +## payload: encrypted payload of the SSL/TLS message +## ## .. zeek:see:: ssl_client_hello ssl_established ssl_extension ssl_server_hello ## ssl_alert ssl_heartbeat ssl_probable_encrypted_handshake_message -event ssl_encrypted_data%(c: connection, is_orig: bool, record_version: count, content_type: count, length: count%); +event ssl_encrypted_data%(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string%); ## This event is generated for application data records of TLS 1.3 connections of which ## we suspect that they contain handshake messages. diff --git a/src/analyzer/protocol/ssl/functions.bif b/src/analyzer/protocol/ssl/functions.bif index 2d72a4a741..b04bf19b3f 100644 --- a/src/analyzer/protocol/ssl/functions.bif +++ b/src/analyzer/protocol/ssl/functions.bif @@ -16,3 +16,25 @@ function set_ssl_established%(c: connection%): any static_cast(sa)->StartEncryption(); return nullptr; %} + +function set_secret%(c: connection, secret: string%): bool + %{ + analyzer::Analyzer* sa = c->FindAnalyzer("SSL"); + if ( sa ) + { + static_cast(sa)->SetSecret(secret->Bytes(), secret->Len()); + return zeek::val_mgr->True(); + } + return zeek::val_mgr->False(); + %} + +function set_keys%(c: connection, keys: string%): bool + %{ + analyzer::Analyzer* sa = c->FindAnalyzer("SSL"); + if ( sa ) + { + static_cast(sa)->SetKeys(keys->Bytes(), keys->Len()); + return zeek::val_mgr->True(); + } + return zeek::val_mgr->False(); + %} diff --git a/src/analyzer/protocol/ssl/proc-client-hello-tls.pac b/src/analyzer/protocol/ssl/proc-client-hello-tls.pac new file mode 100644 index 0000000000..a06be74e36 --- /dev/null +++ b/src/analyzer/protocol/ssl/proc-client-hello-tls.pac @@ -0,0 +1,56 @@ + function proc_client_hello( + version : uint16, ts : double, + client_random : bytestring, + session_id : uint8[], + cipher_suites16 : uint16[], + cipher_suites24 : uint24[], + compression_methods: uint8[]) : bool + %{ + if ( ! version_ok(version) ) + { + zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported client SSL version 0x%04x", version)); + zeek_analyzer()->SetSkip(true); + } + else + zeek_analyzer()->ProtocolConfirmation(); + + if ( ssl_client_hello ) + { + vector cipher_suites; + + if ( cipher_suites16 ) + std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(cipher_suites)); + else + std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(cipher_suites), to_int()); + + auto cipher_vec = zeek::make_intrusive(zeek::id::index_vec); + + for ( unsigned int i = 0; i < cipher_suites.size(); ++i ) + { + auto ciph = zeek::val_mgr->Count(cipher_suites[i]); + cipher_vec->Assign(i, ciph); + } + + auto comp_vec = zeek::make_intrusive(zeek::id::index_vec); + + if ( compression_methods ) + { + for ( unsigned int i = 0; i < compression_methods->size(); ++i ) + { + auto comp = zeek::val_mgr->Count((*compression_methods)[i]); + comp_vec->Assign(i, comp); + } + } + + set_client_random(client_random); + set_gmt_unix_time(ts); + zeek::BifEvent::enqueue_ssl_client_hello(zeek_analyzer(), zeek_analyzer()->Conn(), + version, record_version(), ts, + zeek::make_intrusive(client_random.length(), + (const char*) client_random.data()), + {zeek::AdoptRef{}, to_string_val(session_id)}, + std::move(cipher_vec), std::move(comp_vec)); + } + + return true; + %} diff --git a/src/analyzer/protocol/ssl/proc-server-hello-tls.pac b/src/analyzer/protocol/ssl/proc-server-hello-tls.pac new file mode 100644 index 0000000000..456cc9bcd0 --- /dev/null +++ b/src/analyzer/protocol/ssl/proc-server-hello-tls.pac @@ -0,0 +1,41 @@ + function proc_server_hello( + version : uint16, v2 : bool, + server_random : bytestring, + session_id : uint8[], + cipher_suites16 : uint16[], + cipher_suites24 : uint24[], + comp_method : uint8) : bool + %{ + if ( ! version_ok(version) ) + { + zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported server SSL version 0x%04x", version)); + zeek_analyzer()->SetSkip(true); + } + + if ( ssl_server_hello ) + { + vector* ciphers = new vector(); + + if ( cipher_suites16 ) + std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(*ciphers)); + else + std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(*ciphers), to_int()); + + uint32 ts = 0; + if ( v2 == 0 && server_random.length() >= 4 ) + ts = ntohl(*((uint32*)server_random.data())); + + set_server_random(server_random); + zeek::BifEvent::enqueue_ssl_server_hello(zeek_analyzer(), + zeek_analyzer()->Conn(), + version, record_version(), ts, + zeek::make_intrusive(server_random.length(), + (const char*) server_random.data()), + {zeek::AdoptRef{}, to_string_val(session_id)}, + ciphers->size()==0 ? 0 : ciphers->at(0), comp_method); + + delete ciphers; + } + + return true; + %} diff --git a/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac b/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac index f086fae609..8cb076ad6b 100644 --- a/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac @@ -43,7 +43,7 @@ refine connection SSL_Conn += { return true; %} - function proc_ciphertext_record(rec : SSLRecord) : bool + function proc_ciphertext_record(rec : SSLRecord, cont: bytestring) : bool %{ if ( established_ == false && determine_tls13() == 1 ) { @@ -62,8 +62,15 @@ refine connection SSL_Conn += { } if ( ssl_encrypted_data ) + { zeek::BifEvent::enqueue_ssl_encrypted_data(zeek_analyzer(), - zeek_analyzer()->Conn(), ${rec.is_orig}, ${rec.raw_tls_version}, ${rec.content_type}, ${rec.length}); + zeek_analyzer()->Conn(), ${rec.is_orig}, ${rec.raw_tls_version}, ${rec.content_type}, ${rec.length}, + zeek::make_intrusive(cont.length(), (const char*) cont.data())); + if (rec->content_type() == APPLICATION_DATA) + { + zeek_analyzer()->TryDecryptApplicationData(cont.length(), cont.data(), rec->is_orig(), rec->content_type(), rec->raw_tls_version()); + } + } return true; %} @@ -123,7 +130,7 @@ refine typeattr UnknownRecord += &let { }; refine typeattr CiphertextRecord += &let { - proc : bool = $context.connection.proc_ciphertext_record(rec); + proc : bool = $context.connection.proc_ciphertext_record(rec, cont); } refine typeattr PlaintextRecord += &let { diff --git a/src/analyzer/protocol/ssl/ssl-dtls-protocol.pac b/src/analyzer/protocol/ssl/ssl-dtls-protocol.pac index ad2b869ae8..156c1bf6f4 100644 --- a/src/analyzer/protocol/ssl/ssl-dtls-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-dtls-protocol.pac @@ -96,7 +96,7 @@ type UnknownRecord(rec: SSLRecord) = record { }; type CiphertextRecord(rec: SSLRecord) = record { - cont : bytestring &restofdata &transient; + cont : bytestring &restofdata; }; ###################################################################### diff --git a/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac b/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac index 961eb8eaf3..9559ba0151 100644 --- a/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac +++ b/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac @@ -25,8 +25,8 @@ refine connection Handshake_Conn += { - %include proc-client-hello.pac - %include proc-server-hello.pac + %include proc-client-hello-tls.pac + %include proc-server-hello-tls.pac %include proc-certificate.pac function proc_session_ticket_handshake(rec: SessionTicketHandshake, is_orig: bool): bool diff --git a/src/analyzer/protocol/ssl/tls-handshake-protocol.pac b/src/analyzer/protocol/ssl/tls-handshake-protocol.pac index e37a0c512a..628dd3da48 100644 --- a/src/analyzer/protocol/ssl/tls-handshake-protocol.pac +++ b/src/analyzer/protocol/ssl/tls-handshake-protocol.pac @@ -943,6 +943,9 @@ refine connection Handshake_Conn += { uint32 chosen_cipher_; uint16 chosen_version_; uint16 record_version_; + bytestring client_random_; + bytestring server_random_; + uint32 gmt_unix_time_; %} %init{ @@ -950,6 +953,10 @@ refine connection Handshake_Conn += { chosen_version_ = UNKNOWN_VERSION; record_version_ = 0; + // FIXME: How should bytestrings be initialized? + // client_random_ = ?? + // server_random_ = ?? + gmt_unix_time_ = 0; %} function chosen_cipher() : int %{ return chosen_cipher_; %} @@ -983,5 +990,29 @@ refine connection Handshake_Conn += { record_version_ = version; return true; %} + + function client_random() : bytestring %{ return client_random_; %} + + function set_client_random(client_random: bytestring) : bool + %{ + client_random_.init(client_random.data(), client_random.length()); + return true; + %} + + function server_random() : bytestring %{ return server_random_; %} + + function set_server_random(server_random: bytestring) : bool + %{ + server_random_.init(server_random.data(), server_random.length()); + return true; + %} + + function gmt_unix_time() : uint32 %{ return gmt_unix_time_; %} + + function set_gmt_unix_time(ts: uint32) : bool + %{ + gmt_unix_time_ = ts; + return true; + %} }; diff --git a/testing/btest/Baseline/scripts.policy.misc.dump-events/really-all-events.log b/testing/btest/Baseline/scripts.policy.misc.dump-events/really-all-events.log index 0d937690c2..e20dbfb687 100644 --- a/testing/btest/Baseline/scripts.policy.misc.dump-events/really-all-events.log +++ b/testing/btest/Baseline/scripts.policy.misc.dump-events/really-all-events.log @@ -9079,6 +9079,7 @@ XXXXXXXXXX.XXXXXX ssl_encrypted_data [2] record_version: count = 771 [3] content_type: count = 22 [4] length: count = 32 + [5] payload: string = \x1c\x1c\x84S/9\x14e\xb6'\xe5,\x03\x0fY\xdf\x1b\xcfu\xc84\xae\x1a"\xea]9j'\xbeZ\xa7 XXXXXXXXXX.XXXXXX raw_packet [0] p: raw_pkt_hdr = [l2=[encap=LINK_ETHERNET, len=91, cap_len=91, src=58:b0:35:86:54:8d, dst=cc:b2:55:f4:62:92, vlan=, inner_vlan=, eth_type=2048, proto=L3_IPV4], ip=[hl=20, tos=0, len=77, id=51331, ttl=64, p=6, src=192.168.133.100, dst=17.167.150.73], ip6=, tcp=[sport=49655/tcp, dport=443/tcp, seq=3289393854, ack=2319612745, hl=20, dl=37, reserved=0, flags=24, win=8192], udp=, icmp=] @@ -9176,6 +9177,7 @@ XXXXXXXXXX.XXXXXX ssl_encrypted_data [2] record_version: count = 771 [3] content_type: count = 22 [4] length: count = 32 + [5] payload: string = Z\x99\x17~d\x06\xbd;\xb4\xdf\xe2\xb3~9,|\xac\xdb\xb4\xeb\xcc\x95.\x17\xd2Q\x8a\x96\xdb\x13\x09! XXXXXXXXXX.XXXXXX raw_packet [0] p: raw_pkt_hdr = [l2=[encap=LINK_ETHERNET, len=97, cap_len=97, src=cc:b2:55:f4:62:92, dst=58:b0:35:86:54:8d, vlan=, inner_vlan=, eth_type=2048, proto=L3_IPV4], ip=[hl=20, tos=0, len=83, id=50807, ttl=243, p=6, src=17.167.150.73, dst=192.168.133.100], ip6=, tcp=[sport=443/tcp, dport=49655/tcp, seq=2319612745, ack=3289393891, hl=20, dl=43, reserved=0, flags=24, win=3626], udp=, icmp=] diff --git a/testing/btest/scripts/base/protocols/ssl/handshake-events.test b/testing/btest/scripts/base/protocols/ssl/handshake-events.test index 0b45bebc02..0c694bfaa1 100644 --- a/testing/btest/scripts/base/protocols/ssl/handshake-events.test +++ b/testing/btest/scripts/base/protocols/ssl/handshake-events.test @@ -27,7 +27,7 @@ event ssl_plaintext_data(c: connection, is_orig: bool, record_version: count, co print "Plaintext data", c$id$orig_h, c$id$resp_h, is_orig, SSL::version_strings[record_version], content_type, length; } -event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) +event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string) { print "Encrypted data", c$id$orig_h, c$id$resp_h, is_orig, SSL::version_strings[record_version], content_type, length; } diff --git a/testing/btest/scripts/base/protocols/ssl/tls13.test b/testing/btest/scripts/base/protocols/ssl/tls13.test index 875149ce80..f1f03cd5df 100644 --- a/testing/btest/scripts/base/protocols/ssl/tls13.test +++ b/testing/btest/scripts/base/protocols/ssl/tls13.test @@ -37,7 +37,7 @@ event ssl_established(c: connection) print "established", c$id; } -event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) +event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string) { print "encrypted", c$id, is_orig, SSL::version_strings[record_version], content_type; } diff --git a/testing/btest/scripts/base/protocols/ssl/tls13_encrypted_handshake_events.test b/testing/btest/scripts/base/protocols/ssl/tls13_encrypted_handshake_events.test index 3293315723..08936cee56 100644 --- a/testing/btest/scripts/base/protocols/ssl/tls13_encrypted_handshake_events.test +++ b/testing/btest/scripts/base/protocols/ssl/tls13_encrypted_handshake_events.test @@ -6,7 +6,7 @@ redef SSL::disable_analyzer_after_detection=F; -event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) +event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string) { print "encrypted", c$id, is_orig, SSL::version_strings[record_version], content_type; } From f73935aa45a87c92ce1901285584668595657420 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Wed, 5 May 2021 15:41:36 +0200 Subject: [PATCH 02/24] ssl/decryption.zeek: cleanup --- scripts/policy/protocols/ssl/decryption.zeek | 87 +++++++++++--------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/scripts/policy/protocols/ssl/decryption.zeek b/scripts/policy/protocols/ssl/decryption.zeek index a3608d81e7..982dc12154 100644 --- a/scripts/policy/protocols/ssl/decryption.zeek +++ b/scripts/policy/protocols/ssl/decryption.zeek @@ -7,8 +7,25 @@ module SSL; -# Local -const input_stream_name = "input-tls-keylog-file"; +export { + const keylog_file = getenv("ZEEK_TLS_KEYLOG_FILE") &redef; + + global secrets: table[string] of string = {} &redef; + global keys: table[string] of string = {} &redef; + + global add_keys: event(client_random: string, keys: string); + + global add_secret: event(client_random: string, secret: string); +} + +# Do not disable analyzers after detection - otherwise we will not receive +# encrypted packets. +redef SSL::disable_analyzer_after_detection=F; + +redef record SSL::Info += { + # Decryption uses client_random as identifier + client_random: string &log &optional; +}; type Idx: record { client_random: string; @@ -18,30 +35,34 @@ type Val: record { secret: string; }; -global randoms: table[string] of string = {}; +const input_stream_name = "input-tls-keylog-file"; -export { - redef record Info += { - # decryption uses client_random as identifier - client_random: string &log &optional; - }; - - const keylog_file = getenv("ZEEK_TLS_KEYLOG_FILE") &redef; - - global secrets: table[string] of string = {} &redef; - global keys: table[string] of string = {} &redef; - - event SSL::add_keys(client_random: string, val: string) +event zeek_init() { - SSL::keys[client_random] = val; - } + # listen for secrets + Broker::subscribe("/zeek/tls/decryption"); - event SSL::add_secret(client_random: string, val: string) - { - SSL::secrets[client_random] = val; - } + # FIXME: is such a functionality helpful? + # ingest keylog file if the environment is set + if ( keylog_file != "" ) + { + suspend_processing(); + + Input::add_table([$name=input_stream_name, $source=keylog_file, $destination=secrets, $idx=Idx, $val=Val, $want_record=F]); + Input::remove(input_stream_name); + } } +event SSL::add_keys(client_random: string, keys: string) + { + SSL::keys[client_random] = keys; + } + +event SSL::add_secret(client_random: string, secret: string) + { + SSL::secrets[client_random] = secret; + } + event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec) { c$ssl$client_random = client_random; @@ -70,8 +91,8 @@ event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, co } else { - # FIXME: should this be moved to reporter.log or removed completely? - #print "No suitable key or secret found for random:", randoms[c$uid]; + # FIXME: replace with @if gated reporter + #print "No suitable key or secret found for random:", c$ssl$client_random; } } } @@ -84,23 +105,7 @@ event SSL::tls_input_done() event Input::end_of_data(name: string, source: string) { if ( name == input_stream_name ) - { - event SSL::tls_input_done(); - } -} - -event zeek_init() - { - # listen for secrets - Broker::subscribe("/zeek/tls/decryption"); - - # FIXME: is such a functionality helpful? - # ingest keylog file if the environment is set - if ( keylog_file != "" ) { - suspend_processing(); - - Input::add_table([$name=input_stream_name, $source=keylog_file, $destination=secrets, $idx=Idx, $val=Val, $want_record=F]); - Input::remove(input_stream_name); + event SSL::tls_input_done(); } -} + } From c1c0cb6f3c4c4963f9a47c44008b60f6a3907cb8 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Wed, 5 May 2021 15:46:20 +0200 Subject: [PATCH 03/24] analyzer/ssl: Formatting, printf -> DBG_LOG, namespacing --- scripts/policy/protocols/ssl/decryption.zeek | 8 +++--- src/analyzer/protocol/ssl/DTLS.cc | 8 +++--- src/analyzer/protocol/ssl/SSL.cc | 27 ++++++++++---------- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/scripts/policy/protocols/ssl/decryption.zeek b/scripts/policy/protocols/ssl/decryption.zeek index 982dc12154..1bd5eed622 100644 --- a/scripts/policy/protocols/ssl/decryption.zeek +++ b/scripts/policy/protocols/ssl/decryption.zeek @@ -53,14 +53,14 @@ event zeek_init() } } -event SSL::add_keys(client_random: string, keys: string) +event SSL::add_keys(client_random: string, val: string) { - SSL::keys[client_random] = keys; + SSL::keys[client_random] = val; } -event SSL::add_secret(client_random: string, secret: string) +event SSL::add_secret(client_random: string, val: string) { - SSL::secrets[client_random] = secret; + SSL::secrets[client_random] = val; } event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec) diff --git a/src/analyzer/protocol/ssl/DTLS.cc b/src/analyzer/protocol/ssl/DTLS.cc index ee823148bd..faded0d56d 100644 --- a/src/analyzer/protocol/ssl/DTLS.cc +++ b/src/analyzer/protocol/ssl/DTLS.cc @@ -49,9 +49,9 @@ void DTLS_Analyzer::EndOfData(bool is_orig) } uint16_t DTLS_Analyzer::GetNegotiatedVersion() const - { - return handshake_interp->chosen_version(); - } + { + return handshake_interp->chosen_version(); + } void DTLS_Analyzer::SendHandshake(uint16_t raw_tls_version, uint8_t msg_type, uint32_t length, const u_char* begin, const u_char* end, bool orig) { @@ -74,7 +74,7 @@ void DTLS_Analyzer::SendHandshake(uint16_t raw_tls_version, uint8_t msg_type, ui bool DTLS_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version) { - // noop for now as DTLS decryption is currently not supported + // noop for now as DTLS decryption is currently not supported } } // namespace zeek::analyzer::dtls diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index 6fd2349fa1..6f92bdbfcf 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -13,16 +13,6 @@ #include #include -#define MSB(a) ((a>>8)&0xff) -#define LSB(a) (a&0xff) - -static void fmt_seq(uint32_t num, u_char* buf) - { - memset(buf, 0, 8); - uint32_t netnum = htonl(num); - memcpy(buf+4, &netnum, 4); - } - static void print_hex(std::string name, u_char* data, int len) { int i = 0; @@ -78,6 +68,16 @@ abort: namespace zeek::analyzer::ssl { +#define MSB(a) ((a>>8)&0xff) +#define LSB(a) (a&0xff) + +static void fmt_seq(uint32_t num, u_char* buf) + { + memset(buf, 0, 8); + uint32_t netnum = htonl(num); + memcpy(buf+4, &netnum, 4); + } + SSL_Analyzer::SSL_Analyzer(Connection* c) : analyzer::tcp::TCP_ApplicationAnalyzer("SSL", c) { @@ -193,15 +193,14 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i auto cipher = handshake_interp->chosen_cipher(); if ( cipher != 0xC030 ) { - //printf("Unsupported cipher suite: %d\n", cipher); + DBG_LOG(DBG_ANALYZER, "Unsupported cipher suite: %d\n", cipher); return false; } // Neither secret or key present: abort if ( secret->Len() == 0 && keys->Len() == 0 ) { - // FIXME: this is just for debugging - printf("Could not decrypt packet (missing key):\n"); + DBG_LOG(DBG_ANALYZER, "Could not decrypt packet due to missing key\n"); print_hex("->client_random:", handshake_interp->client_random().data(), handshake_interp->client_random().length()); return false; } @@ -293,7 +292,7 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i int res = 0; if ( ! (res = EVP_DecryptFinal(ctx, NULL, &res)) ) { - printf("Decryption failed with return code %d. Invalid key?\n", res); + DBG_LOG(DBG_ANALYZER, "Decryption failed with return code: %d. Invalid key?\n", res); EVP_CIPHER_CTX_free(ctx); free(decrypted); return false; From febc69d320e1ca967d46a1f40fcf2e755f0309a7 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Wed, 5 May 2021 18:22:56 +0200 Subject: [PATCH 04/24] analyzer/ssl: cleanup SSL_Analyzer - make TLS12 PRF a member function of the analyzer - use std::string in PRF - use StringValPtr instead of zeek::StringVal - replace malloc/free with C++ style allocations --- scripts/policy/protocols/ssl/decryption.zeek | 2 +- src/analyzer/protocol/ssl/SSL.cc | 115 +++++++++---------- src/analyzer/protocol/ssl/SSL.h | 5 +- 3 files changed, 61 insertions(+), 61 deletions(-) diff --git a/scripts/policy/protocols/ssl/decryption.zeek b/scripts/policy/protocols/ssl/decryption.zeek index 1bd5eed622..766bb4d940 100644 --- a/scripts/policy/protocols/ssl/decryption.zeek +++ b/scripts/policy/protocols/ssl/decryption.zeek @@ -51,7 +51,7 @@ event zeek_init() Input::add_table([$name=input_stream_name, $source=keylog_file, $destination=secrets, $idx=Idx, $val=Val, $want_record=F]); Input::remove(input_stream_name); } -} + } event SSL::add_keys(client_random: string, val: string) { diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index 6f92bdbfcf..ec1ebffea2 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -27,45 +27,6 @@ static void print_hex(std::string name, u_char* data, int len) printf("\n"); } -static bool tls1_prf(const u_char *secret, int secret_len, const char* label, - const u_char* rnd1, int rnd1_len, const u_char* rnd2, int rnd2_len, u_char* out, size_t out_len) - { - // alloc buffers - EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); - size_t seed_len = strlen(label) + rnd1_len + rnd2_len; - u_char *seed = (u_char*) malloc(seed_len); - u_char *ptr = seed; - - // seed = label + rnd1 + rnd2 - memcpy(ptr, label, strlen(label)); - ptr += strlen(label); - memcpy(ptr, rnd1, rnd1_len); - ptr += rnd1_len; - memcpy(ptr, rnd2, rnd2_len); - ptr += rnd2_len; - - if (EVP_PKEY_derive_init(pctx) <= 0) - goto abort; /* Error */ - // FIXME: sha384 should not be hardcoded - if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0) - goto abort; /* Error */ - if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret, secret_len) <= 0) - goto abort; /* Error */ - if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed, seed_len) <= 0) - goto abort; /* Error */ - if (EVP_PKEY_derive(pctx, out, &out_len) <= 0) - goto abort; /* Error */ - - EVP_PKEY_CTX_free(pctx); - free(seed); - return true; - -abort: - EVP_PKEY_CTX_free(pctx); - free(seed); - return false; - } - namespace zeek::analyzer::ssl { #define MSB(a) ((a>>8)&0xff) @@ -87,8 +48,8 @@ SSL_Analyzer::SSL_Analyzer(Connection* c) c_seq = 0; s_seq = 0; // FIXME: should this be initialized to nullptr? - secret = new zeek::StringVal(0, ""); - keys = new zeek::StringVal(0, ""); + secret = nullptr; + keys = nullptr; pia = nullptr; } @@ -96,8 +57,6 @@ SSL_Analyzer::~SSL_Analyzer() { delete interp; delete handshake_interp; - delete secret; - delete keys; } void SSL_Analyzer::Done() @@ -176,16 +135,49 @@ void SSL_Analyzer::Undelivered(uint64_t seq, int len, bool orig) void SSL_Analyzer::SetSecret(const u_char* data, int len) { - // FIXME: Is this the proper way to initialize a zeek::StringVal? - // the alternative requires a cast: new zeek::StringVal(len, data) - secret = new zeek::StringVal(new zeek::String(data, len, true)); + secret = make_intrusive(len, (const char*)data); } void SSL_Analyzer::SetKeys(const u_char* data, int len) { - keys = new zeek::StringVal(new zeek::String(data, len, true)); + keys = make_intrusive(len, (const char*)data); } +bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label, + const char* rnd1, size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, size_t out_len) + { + // alloc buffers + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); + size_t seed_len = label.length() + rnd1_len + rnd2_len; + std::string seed{}; + seed.reserve(seed_len); + + // seed = label + rnd1 + rnd2 + seed.append(label); + seed.append(rnd1, rnd1_len); + seed.append(rnd2, rnd2_len); + + if (EVP_PKEY_derive_init(pctx) <= 0) + goto abort; /* Error */ + // FIXME: sha384 should not be hardcoded + if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0) + goto abort; /* Error */ + if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret.data(), secret.length()) <= 0) + goto abort; /* Error */ + if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed.data(), seed.length()) <= 0) + goto abort; /* Error */ + if (EVP_PKEY_derive(pctx, out, &out_len) <= 0) + goto abort; /* Error */ + + EVP_PKEY_CTX_free(pctx); + return true; + +abort: + EVP_PKEY_CTX_free(pctx); + return false; + } + + bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version) { // Unsupported cipher suite. Currently supported: @@ -198,19 +190,20 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i } // Neither secret or key present: abort - if ( secret->Len() == 0 && keys->Len() == 0 ) + if ( ( secret == nullptr || secret->Len() == 0 ) && ( keys == nullptr || keys->Len() == 0 ) ) { - DBG_LOG(DBG_ANALYZER, "Could not decrypt packet due to missing key\n"); - print_hex("->client_random:", handshake_interp->client_random().data(), handshake_interp->client_random().length()); + DBG_LOG(DBG_ANALYZER, "Could not decrypt packet due to missing keys/secret.\n"); + // FIXME: change util function to return a printably std::string for DBG_LOG + //print_hex("->client_random:", handshake_interp->client_random().data(), handshake_interp->client_random().length()); return false; } // Secret present, but no keys derived yet: derive keys - if ( secret->Len() != 0 && keys->Len() == 0 ) + if ( secret != nullptr && secret->Len() != 0 && ( keys == nullptr || keys->Len() == 0 ) ) { uint32_t ts = htonl((uint32_t) handshake_interp->gmt_unix_time()); - u_char crand[32] = {0x00}; + char crand[32] = {0x00}; u_char keybuf[72]; auto c_rnd = handshake_interp->client_random(); @@ -218,15 +211,20 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i memcpy(crand, &(ts), 4); memcpy(crand + 4, c_rnd.data(), c_rnd.length()); - tls1_prf(secret->Bytes(), secret->Len(), "key expansion", s_rnd.data(), s_rnd.length(), - crand, sizeof(crand), keybuf, sizeof(keybuf)); + auto res = TLS12_PRF(secret->ToStdString(), "key expansion", + (char*)s_rnd.data(), s_rnd.length(), crand, sizeof(crand), keybuf, sizeof(keybuf)); + if ( !res ) + { + DBG_LOG(DBG_ANALYZER, "TLS PRF failed. Aborting.\n"); + return false; + } // save derived keys SetKeys(keybuf, sizeof(keybuf)); } // Keys present: decrypt TLS application data - if ( keys->Len() != 0 ) + if ( keys != nullptr && keys->Len() != 0 ) { // session keys & AEAD data u_char c_wk[32]; @@ -266,6 +264,7 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i encrypted_len -= 8; encrypted_len -= 16; + // FIXME: aes_256_gcm should not be hardcoded here ;) if (is_orig) EVP_DecryptInit(ctx, EVP_aes_256_gcm(), c_wk, s_aead_nonce); else @@ -283,7 +282,7 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i s_aead_tag[11] = MSB(encrypted_len); s_aead_tag[12] = LSB(encrypted_len); - u_char *decrypted = (u_char*)malloc(encrypted_len); + u_char *decrypted = new u_char[ encrypted_len ]; int decrypted_len = 0; EVP_DecryptUpdate(ctx, NULL, &decrypted_len, s_aead_tag, 13); @@ -294,14 +293,14 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i { DBG_LOG(DBG_ANALYZER, "Decryption failed with return code: %d. Invalid key?\n", res); EVP_CIPHER_CTX_free(ctx); - free(decrypted); + delete [] decrypted; return false; } EVP_CIPHER_CTX_free(ctx); ForwardDecryptedData(decrypted_len, reinterpret_cast(decrypted), is_orig); - free(decrypted); + delete [] decrypted; return true; } diff --git a/src/analyzer/protocol/ssl/SSL.h b/src/analyzer/protocol/ssl/SSL.h index 4eac7e77a6..e4df7a5b55 100644 --- a/src/analyzer/protocol/ssl/SSL.h +++ b/src/analyzer/protocol/ssl/SSL.h @@ -39,6 +39,7 @@ public: void SetKeys(const u_char* data, int len); bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version); + bool TLS12_PRF(const std::string& secret, const std::string& label, const char* rnd1, size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, size_t out_len); void ForwardDecryptedData(int len, const u_char* data, bool is_orig); protected: @@ -49,8 +50,8 @@ protected: // FIXME: should this be moved into the connection? int c_seq; int s_seq; - zeek::StringVal *secret; - zeek::StringVal *keys; + StringValPtr secret; + StringValPtr keys; zeek::analyzer::pia::PIA_TCP *pia; }; From 68f5ae9538b919050e42723c3351cae3937ed759 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Wed, 5 May 2021 19:12:25 +0200 Subject: [PATCH 05/24] analyzer/ssl: move proc-{client,server}-hello into the respective analyzers --- src/analyzer/protocol/ssl/CMakeLists.txt | 4 - .../protocol/ssl/proc-client-hello-tls.pac | 56 ---------- .../protocol/ssl/proc-client-hello.pac | 54 ---------- .../protocol/ssl/proc-server-hello-tls.pac | 41 ------- .../protocol/ssl/proc-server-hello.pac | 40 ------- src/analyzer/protocol/ssl/ssl-analyzer.pac | 98 ++++++++++++++++- .../protocol/ssl/tls-handshake-analyzer.pac | 101 +++++++++++++++++- 7 files changed, 195 insertions(+), 199 deletions(-) delete mode 100644 src/analyzer/protocol/ssl/proc-client-hello-tls.pac delete mode 100644 src/analyzer/protocol/ssl/proc-client-hello.pac delete mode 100644 src/analyzer/protocol/ssl/proc-server-hello-tls.pac delete mode 100644 src/analyzer/protocol/ssl/proc-server-hello.pac diff --git a/src/analyzer/protocol/ssl/CMakeLists.txt b/src/analyzer/protocol/ssl/CMakeLists.txt index 2a9179ce64..9a82ecff8a 100644 --- a/src/analyzer/protocol/ssl/CMakeLists.txt +++ b/src/analyzer/protocol/ssl/CMakeLists.txt @@ -10,14 +10,10 @@ zeek_plugin_bif(events.bif) zeek_plugin_bif(functions.bif) zeek_plugin_bif(consts.bif) zeek_plugin_pac(tls-handshake.pac tls-handshake-protocol.pac tls-handshake-analyzer.pac ssl-defs.pac - proc-client-hello-tls.pac - proc-server-hello-tls.pac proc-certificate.pac tls-handshake-signed_certificate_timestamp.pac ) zeek_plugin_pac(ssl.pac ssl-dtls-analyzer.pac ssl-analyzer.pac ssl-dtls-protocol.pac ssl-protocol.pac ssl-defs.pac - proc-client-hello.pac - proc-server-hello.pac proc-certificate.pac ) zeek_plugin_pac(dtls.pac ssl-dtls-analyzer.pac dtls-analyzer.pac ssl-dtls-protocol.pac dtls-protocol.pac ssl-defs.pac) diff --git a/src/analyzer/protocol/ssl/proc-client-hello-tls.pac b/src/analyzer/protocol/ssl/proc-client-hello-tls.pac deleted file mode 100644 index a06be74e36..0000000000 --- a/src/analyzer/protocol/ssl/proc-client-hello-tls.pac +++ /dev/null @@ -1,56 +0,0 @@ - function proc_client_hello( - version : uint16, ts : double, - client_random : bytestring, - session_id : uint8[], - cipher_suites16 : uint16[], - cipher_suites24 : uint24[], - compression_methods: uint8[]) : bool - %{ - if ( ! version_ok(version) ) - { - zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported client SSL version 0x%04x", version)); - zeek_analyzer()->SetSkip(true); - } - else - zeek_analyzer()->ProtocolConfirmation(); - - if ( ssl_client_hello ) - { - vector cipher_suites; - - if ( cipher_suites16 ) - std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(cipher_suites)); - else - std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(cipher_suites), to_int()); - - auto cipher_vec = zeek::make_intrusive(zeek::id::index_vec); - - for ( unsigned int i = 0; i < cipher_suites.size(); ++i ) - { - auto ciph = zeek::val_mgr->Count(cipher_suites[i]); - cipher_vec->Assign(i, ciph); - } - - auto comp_vec = zeek::make_intrusive(zeek::id::index_vec); - - if ( compression_methods ) - { - for ( unsigned int i = 0; i < compression_methods->size(); ++i ) - { - auto comp = zeek::val_mgr->Count((*compression_methods)[i]); - comp_vec->Assign(i, comp); - } - } - - set_client_random(client_random); - set_gmt_unix_time(ts); - zeek::BifEvent::enqueue_ssl_client_hello(zeek_analyzer(), zeek_analyzer()->Conn(), - version, record_version(), ts, - zeek::make_intrusive(client_random.length(), - (const char*) client_random.data()), - {zeek::AdoptRef{}, to_string_val(session_id)}, - std::move(cipher_vec), std::move(comp_vec)); - } - - return true; - %} diff --git a/src/analyzer/protocol/ssl/proc-client-hello.pac b/src/analyzer/protocol/ssl/proc-client-hello.pac deleted file mode 100644 index ae3774ed9a..0000000000 --- a/src/analyzer/protocol/ssl/proc-client-hello.pac +++ /dev/null @@ -1,54 +0,0 @@ - function proc_client_hello( - version : uint16, ts : double, - client_random : bytestring, - session_id : uint8[], - cipher_suites16 : uint16[], - cipher_suites24 : uint24[], - compression_methods: uint8[]) : bool - %{ - if ( ! version_ok(version) ) - { - zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported client SSL version 0x%04x", version)); - zeek_analyzer()->SetSkip(true); - } - else - zeek_analyzer()->ProtocolConfirmation(); - - if ( ssl_client_hello ) - { - vector cipher_suites; - - if ( cipher_suites16 ) - std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(cipher_suites)); - else - std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(cipher_suites), to_int()); - - auto cipher_vec = zeek::make_intrusive(zeek::id::index_vec); - - for ( unsigned int i = 0; i < cipher_suites.size(); ++i ) - { - auto ciph = zeek::val_mgr->Count(cipher_suites[i]); - cipher_vec->Assign(i, ciph); - } - - auto comp_vec = zeek::make_intrusive(zeek::id::index_vec); - - if ( compression_methods ) - { - for ( unsigned int i = 0; i < compression_methods->size(); ++i ) - { - auto comp = zeek::val_mgr->Count((*compression_methods)[i]); - comp_vec->Assign(i, comp); - } - } - - zeek::BifEvent::enqueue_ssl_client_hello(zeek_analyzer(), zeek_analyzer()->Conn(), - version, record_version(), ts, - zeek::make_intrusive(client_random.length(), - (const char*) client_random.data()), - {zeek::AdoptRef{}, to_string_val(session_id)}, - std::move(cipher_vec), std::move(comp_vec)); - } - - return true; - %} diff --git a/src/analyzer/protocol/ssl/proc-server-hello-tls.pac b/src/analyzer/protocol/ssl/proc-server-hello-tls.pac deleted file mode 100644 index 456cc9bcd0..0000000000 --- a/src/analyzer/protocol/ssl/proc-server-hello-tls.pac +++ /dev/null @@ -1,41 +0,0 @@ - function proc_server_hello( - version : uint16, v2 : bool, - server_random : bytestring, - session_id : uint8[], - cipher_suites16 : uint16[], - cipher_suites24 : uint24[], - comp_method : uint8) : bool - %{ - if ( ! version_ok(version) ) - { - zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported server SSL version 0x%04x", version)); - zeek_analyzer()->SetSkip(true); - } - - if ( ssl_server_hello ) - { - vector* ciphers = new vector(); - - if ( cipher_suites16 ) - std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(*ciphers)); - else - std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(*ciphers), to_int()); - - uint32 ts = 0; - if ( v2 == 0 && server_random.length() >= 4 ) - ts = ntohl(*((uint32*)server_random.data())); - - set_server_random(server_random); - zeek::BifEvent::enqueue_ssl_server_hello(zeek_analyzer(), - zeek_analyzer()->Conn(), - version, record_version(), ts, - zeek::make_intrusive(server_random.length(), - (const char*) server_random.data()), - {zeek::AdoptRef{}, to_string_val(session_id)}, - ciphers->size()==0 ? 0 : ciphers->at(0), comp_method); - - delete ciphers; - } - - return true; - %} diff --git a/src/analyzer/protocol/ssl/proc-server-hello.pac b/src/analyzer/protocol/ssl/proc-server-hello.pac deleted file mode 100644 index 30356508c6..0000000000 --- a/src/analyzer/protocol/ssl/proc-server-hello.pac +++ /dev/null @@ -1,40 +0,0 @@ - function proc_server_hello( - version : uint16, v2 : bool, - server_random : bytestring, - session_id : uint8[], - cipher_suites16 : uint16[], - cipher_suites24 : uint24[], - comp_method : uint8) : bool - %{ - if ( ! version_ok(version) ) - { - zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported server SSL version 0x%04x", version)); - zeek_analyzer()->SetSkip(true); - } - - if ( ssl_server_hello ) - { - vector* ciphers = new vector(); - - if ( cipher_suites16 ) - std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(*ciphers)); - else - std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(*ciphers), to_int()); - - uint32 ts = 0; - if ( v2 == 0 && server_random.length() >= 4 ) - ts = ntohl(*((uint32*)server_random.data())); - - zeek::BifEvent::enqueue_ssl_server_hello(zeek_analyzer(), - zeek_analyzer()->Conn(), - version, record_version(), ts, - zeek::make_intrusive(server_random.length(), - (const char*) server_random.data()), - {zeek::AdoptRef{}, to_string_val(session_id)}, - ciphers->size()==0 ? 0 : ciphers->at(0), comp_method); - - delete ciphers; - } - - return true; - %} diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index c1a8876058..d676630ae8 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -2,10 +2,104 @@ refine connection SSL_Conn += { - %include proc-client-hello.pac - %include proc-server-hello.pac %include proc-certificate.pac + function proc_client_hello( + version : uint16, ts : double, + client_random : bytestring, + session_id : uint8[], + cipher_suites16 : uint16[], + cipher_suites24 : uint24[], + compression_methods: uint8[]) : bool + %{ + if ( ! version_ok(version) ) + { + zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported client SSL version 0x%04x", version)); + zeek_analyzer()->SetSkip(true); + } + else + zeek_analyzer()->ProtocolConfirmation(); + + if ( ssl_client_hello ) + { + vector cipher_suites; + + if ( cipher_suites16 ) + std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(cipher_suites)); + else + std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(cipher_suites), to_int()); + + auto cipher_vec = zeek::make_intrusive(zeek::id::index_vec); + + for ( unsigned int i = 0; i < cipher_suites.size(); ++i ) + { + auto ciph = zeek::val_mgr->Count(cipher_suites[i]); + cipher_vec->Assign(i, ciph); + } + + auto comp_vec = zeek::make_intrusive(zeek::id::index_vec); + + if ( compression_methods ) + { + for ( unsigned int i = 0; i < compression_methods->size(); ++i ) + { + auto comp = zeek::val_mgr->Count((*compression_methods)[i]); + comp_vec->Assign(i, comp); + } + } + + zeek::BifEvent::enqueue_ssl_client_hello(zeek_analyzer(), zeek_analyzer()->Conn(), + version, record_version(), ts, + zeek::make_intrusive(client_random.length(), + (const char*) client_random.data()), + {zeek::AdoptRef{}, to_string_val(session_id)}, + std::move(cipher_vec), std::move(comp_vec)); + } + + return true; + %} + + function proc_server_hello( + version : uint16, v2 : bool, + server_random : bytestring, + session_id : uint8[], + cipher_suites16 : uint16[], + cipher_suites24 : uint24[], + comp_method : uint8) : bool + %{ + if ( ! version_ok(version) ) + { + zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported server SSL version 0x%04x", version)); + zeek_analyzer()->SetSkip(true); + } + + if ( ssl_server_hello ) + { + vector* ciphers = new vector(); + + if ( cipher_suites16 ) + std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(*ciphers)); + else + std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(*ciphers), to_int()); + + uint32 ts = 0; + if ( v2 == 0 && server_random.length() >= 4 ) + ts = ntohl(*((uint32*)server_random.data())); + + zeek::BifEvent::enqueue_ssl_server_hello(zeek_analyzer(), + zeek_analyzer()->Conn(), + version, record_version(), ts, + zeek::make_intrusive(server_random.length(), + (const char*) server_random.data()), + {zeek::AdoptRef{}, to_string_val(session_id)}, + ciphers->size()==0 ? 0 : ciphers->at(0), comp_method); + + delete ciphers; + } + + return true; + %} + function proc_v2_certificate(is_orig: bool, cert : bytestring) : bool %{ vector* cert_list = new vector(1,cert); diff --git a/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac b/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac index 9559ba0151..ccc90bc7e7 100644 --- a/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac +++ b/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac @@ -25,10 +25,107 @@ refine connection Handshake_Conn += { - %include proc-client-hello-tls.pac - %include proc-server-hello-tls.pac %include proc-certificate.pac + function proc_client_hello( + version : uint16, ts : double, + client_random : bytestring, + session_id : uint8[], + cipher_suites16 : uint16[], + cipher_suites24 : uint24[], + compression_methods: uint8[]) : bool + %{ + if ( ! version_ok(version) ) + { + zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported client SSL version 0x%04x", version)); + zeek_analyzer()->SetSkip(true); + } + else + zeek_analyzer()->ProtocolConfirmation(); + + if ( ssl_client_hello ) + { + vector cipher_suites; + + if ( cipher_suites16 ) + std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(cipher_suites)); + else + std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(cipher_suites), to_int()); + + auto cipher_vec = zeek::make_intrusive(zeek::id::index_vec); + + for ( unsigned int i = 0; i < cipher_suites.size(); ++i ) + { + auto ciph = zeek::val_mgr->Count(cipher_suites[i]); + cipher_vec->Assign(i, ciph); + } + + auto comp_vec = zeek::make_intrusive(zeek::id::index_vec); + + if ( compression_methods ) + { + for ( unsigned int i = 0; i < compression_methods->size(); ++i ) + { + auto comp = zeek::val_mgr->Count((*compression_methods)[i]); + comp_vec->Assign(i, comp); + } + } + + set_client_random(client_random); + set_gmt_unix_time(ts); + zeek::BifEvent::enqueue_ssl_client_hello(zeek_analyzer(), zeek_analyzer()->Conn(), + version, record_version(), ts, + zeek::make_intrusive(client_random.length(), + (const char*) client_random.data()), + {zeek::AdoptRef{}, to_string_val(session_id)}, + std::move(cipher_vec), std::move(comp_vec)); + } + + return true; + %} + + function proc_server_hello( + version : uint16, v2 : bool, + server_random : bytestring, + session_id : uint8[], + cipher_suites16 : uint16[], + cipher_suites24 : uint24[], + comp_method : uint8) : bool + %{ + if ( ! version_ok(version) ) + { + zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported server SSL version 0x%04x", version)); + zeek_analyzer()->SetSkip(true); + } + + if ( ssl_server_hello ) + { + vector* ciphers = new vector(); + + if ( cipher_suites16 ) + std::copy(cipher_suites16->begin(), cipher_suites16->end(), std::back_inserter(*ciphers)); + else + std::transform(cipher_suites24->begin(), cipher_suites24->end(), std::back_inserter(*ciphers), to_int()); + + uint32 ts = 0; + if ( v2 == 0 && server_random.length() >= 4 ) + ts = ntohl(*((uint32*)server_random.data())); + + set_server_random(server_random); + zeek::BifEvent::enqueue_ssl_server_hello(zeek_analyzer(), + zeek_analyzer()->Conn(), + version, record_version(), ts, + zeek::make_intrusive(server_random.length(), + (const char*) server_random.data()), + {zeek::AdoptRef{}, to_string_val(session_id)}, + ciphers->size()==0 ? 0 : ciphers->at(0), comp_method); + + delete ciphers; + } + + return true; + %} + function proc_session_ticket_handshake(rec: SessionTicketHandshake, is_orig: bool): bool %{ if ( ssl_session_ticket_handshake ) From 95a6ee27b13fac95676072ea8d5765a822e4a90c Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Fri, 7 May 2021 17:03:24 +0200 Subject: [PATCH 06/24] analyzer/ssl: silence warning in DTLS analyzer --- src/analyzer/protocol/ssl/DTLS.cc | 1 + 1 file changed, 1 insertion(+) diff --git a/src/analyzer/protocol/ssl/DTLS.cc b/src/analyzer/protocol/ssl/DTLS.cc index faded0d56d..95f6d5471b 100644 --- a/src/analyzer/protocol/ssl/DTLS.cc +++ b/src/analyzer/protocol/ssl/DTLS.cc @@ -75,6 +75,7 @@ void DTLS_Analyzer::SendHandshake(uint16_t raw_tls_version, uint8_t msg_type, ui bool DTLS_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version) { // noop for now as DTLS decryption is currently not supported + return false; } } // namespace zeek::analyzer::dtls From 979bf2076985b83612ea01c8e50e5f4bf8979513 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Fri, 7 May 2021 17:04:16 +0200 Subject: [PATCH 07/24] analyzer/ssl: handle missing --- cmake | 2 +- src/analyzer/protocol/ssl/SSL.cc | 16 ++++++++++++---- zeek-config.h.in | 3 +++ 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/cmake b/cmake index 74259745de..cce53d1500 160000 --- a/cmake +++ b/cmake @@ -1 +1 @@ -Subproject commit 74259745dea5ee4889d1ac1f4ebde4e2c59c329a +Subproject commit cce53d15008a26dcb1b7eb534a78f52f9355c676 diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index ec1ebffea2..072518b8ea 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -11,7 +11,10 @@ #include #include -#include + +#ifdef OPENSSL_HAVE_KDF_H + #include +#endif static void print_hex(std::string name, u_char* data, int len) { @@ -146,6 +149,7 @@ void SSL_Analyzer::SetKeys(const u_char* data, int len) bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label, const char* rnd1, size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, size_t out_len) { +#ifdef OPENSSL_HAVE_KDF_H // alloc buffers EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); size_t seed_len = label.length() + rnd1_len + rnd2_len; @@ -174,6 +178,7 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label abort: EVP_PKEY_CTX_free(pctx); +#endif return false; } @@ -201,6 +206,8 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i // Secret present, but no keys derived yet: derive keys if ( secret != nullptr && secret->Len() != 0 && ( keys == nullptr || keys->Len() == 0 ) ) { +#ifdef OPENSSL_HAVE_KDF_H + DBG_LOG(DBG_ANALYZER, "Deriving TLS keys for connection foo"); uint32_t ts = htonl((uint32_t) handshake_interp->gmt_unix_time()); char crand[32] = {0x00}; @@ -221,7 +228,8 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i // save derived keys SetKeys(keybuf, sizeof(keybuf)); - } +#endif + } // Keys present: decrypt TLS application data if ( keys != nullptr && keys->Len() != 0 ) @@ -302,9 +310,9 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i delete [] decrypted; return true; - } + } - // This is only reached if key derivation somehow failed + // This is only reached if key derivation fails or is unsupported return false; } diff --git a/zeek-config.h.in b/zeek-config.h.in index 8cc0c013d5..cf5d690913 100644 --- a/zeek-config.h.in +++ b/zeek-config.h.in @@ -77,6 +77,9 @@ /* Compatibility for Darwin */ #cmakedefine NEED_NAMESER_COMPAT_H +/* openssl/kdf.h for TLS PRF (key derivation) */ +#cmakedefine OPENSSL_HAVE_KDF_H + /* d2i_x509 uses const char** */ #cmakedefine OPENSSL_D2I_X509_USES_CONST_CHAR From 8c67b9c8fccddcd6f834c95f0bdf94502e11ad22 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Wed, 12 May 2021 11:46:17 +0200 Subject: [PATCH 08/24] testing: add ssl/decryption test --- .../http.log | 11 +++++++++++ testing/btest/Traces/tls/tls12-decryption.pcap | Bin 0 -> 6106 bytes .../scripts/policy/protocols/ssl/decryption.zeek | 10 ++++++++++ 3 files changed, 21 insertions(+) create mode 100644 testing/btest/Baseline/scripts.policy.protocols.ssl.decryption/http.log create mode 100644 testing/btest/Traces/tls/tls12-decryption.pcap create mode 100644 testing/btest/scripts/policy/protocols/ssl/decryption.zeek diff --git a/testing/btest/Baseline/scripts.policy.protocols.ssl.decryption/http.log b/testing/btest/Baseline/scripts.policy.protocols.ssl.decryption/http.log new file mode 100644 index 0000000000..a01d49a497 --- /dev/null +++ b/testing/btest/Baseline/scripts.policy.protocols.ssl.decryption/http.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 http +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p trans_depth method host uri referrer version user_agent origin request_body_len response_body_len status_code status_msg info_code info_msg tags username password proxied orig_fuids orig_filenames orig_mime_types resp_fuids resp_filenames resp_mime_types +#types time string addr port addr port count string string string string string string string count count count string count string set[enum] string string set[string] vector[string] vector[string] vector[string] vector[string] vector[string] vector[string] +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 192.168.100.70 48216 193.99.144.80 443 1 GET heise.de / - 1.1 Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Firefox/78.0 - 0 229 301 Moved Permanently - - (empty) - - - - - - FaOfPL638bbaQ1KMh - text/html +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Traces/tls/tls12-decryption.pcap b/testing/btest/Traces/tls/tls12-decryption.pcap new file mode 100644 index 0000000000000000000000000000000000000000..73ffaa14bd5279e5735181330c88b0edb5f855b0 GIT binary patch literal 6106 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 literal 0 HcmV?d00001 diff --git a/testing/btest/scripts/policy/protocols/ssl/decryption.zeek b/testing/btest/scripts/policy/protocols/ssl/decryption.zeek new file mode 100644 index 0000000000..9e73ef418f --- /dev/null +++ b/testing/btest/scripts/policy/protocols/ssl/decryption.zeek @@ -0,0 +1,10 @@ +# @TEST-EXEC: zeek -b -C -r $TRACES/tls/tls12-decryption.pcap %INPUT + +# @TEST-EXEC: btest-diff http.log + +@load protocols/ssl/decryption +@load base/protocols/http + +redef SSL::secrets += { +["\xb4\x0a\x24\x4b\x48\xe4\x2e\xac\x28\x71\x44\xb1\xb7\x39\x30\x57\xca\xa1\x31\xf9\x61\xa7\x8e\x38\xb0\xe7\x7c\x1e"] = "\xbd\x01\xe5\x89\xd1\x05\x19\x9e\x9a\xb5\xfc\x9b\xd7\x58\xb5\xf2\x88\xdb\x28\xfd\x80\xaa\x02\x26\x1e\x47\x65\xac\x13\x57\xd0\x07\xfd\x08\xc7\xbd\xab\x45\x45\x0e\x01\x5a\x01\xd0\x8e\x5e\x7c\xa6", +}; From a7fe2bd9c58717d674325a007686f247a87a89cf Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Fri, 28 May 2021 12:56:01 +0200 Subject: [PATCH 09/24] testing: feature gate ssl/decryption test --- testing/btest/scripts/policy/protocols/ssl/decryption.zeek | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/testing/btest/scripts/policy/protocols/ssl/decryption.zeek b/testing/btest/scripts/policy/protocols/ssl/decryption.zeek index 9e73ef418f..741284a540 100644 --- a/testing/btest/scripts/policy/protocols/ssl/decryption.zeek +++ b/testing/btest/scripts/policy/protocols/ssl/decryption.zeek @@ -1,5 +1,6 @@ -# @TEST-EXEC: zeek -b -C -r $TRACES/tls/tls12-decryption.pcap %INPUT +# @TEST-REQUIRES: grep -q "#define OPENSSL_HAVE_KDF_H" $BUILD/zeek-config.h +# @TEST-EXEC: zeek -b -C -r $TRACES/tls/tls12-decryption.pcap %INPUT # @TEST-EXEC: btest-diff http.log @load protocols/ssl/decryption From aaaff39e127d23ee0c76fcd4af50869407595be6 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Wed, 23 Jun 2021 17:34:41 +0200 Subject: [PATCH 10/24] analyzer/ssl: defensive key length check + more debug logging --- src/analyzer/protocol/ssl/SSL.cc | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index 072518b8ea..24897c0693 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -228,12 +228,16 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i // save derived keys SetKeys(keybuf, sizeof(keybuf)); +#else + DBG_LOG(DBG_ANALYZER, "Cannot derive TLS keys as Zeek was compiled without "); #endif } // Keys present: decrypt TLS application data - if ( keys != nullptr && keys->Len() != 0 ) + if ( keys != nullptr && keys->Len() == 72 ) { + // FIXME: could also print keys or conn id here + DBG_LOG(DBG_ANALYZER, "Decrypting application data"); // session keys & AEAD data u_char c_wk[32]; u_char s_wk[32]; @@ -305,6 +309,7 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i return false; } + DBG_LOG(DBG_ANALYZER, "Successfully decrypted %d bytes.", decrypted_len); EVP_CIPHER_CTX_free(ctx); ForwardDecryptedData(decrypted_len, reinterpret_cast(decrypted), is_orig); From ebea26a0654d764ea9daeb38a81def96efd7bfa0 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Thu, 24 Jun 2021 17:05:34 +0200 Subject: [PATCH 11/24] analyzer/ssl: several improvements - use better data structures for secret and key material storage - add documentation to the new methods in the analyzer --- src/analyzer/protocol/ssl/SSL.cc | 61 +++++++++++------ src/analyzer/protocol/ssl/SSL.h | 90 +++++++++++++++++++++++-- src/analyzer/protocol/ssl/functions.bif | 4 +- 3 files changed, 126 insertions(+), 29 deletions(-) diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index 24897c0693..55c0f74324 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -50,9 +50,6 @@ SSL_Analyzer::SSL_Analyzer(Connection* c) had_gap = false; c_seq = 0; s_seq = 0; - // FIXME: should this be initialized to nullptr? - secret = nullptr; - keys = nullptr; pia = nullptr; } @@ -136,14 +133,27 @@ void SSL_Analyzer::Undelivered(uint64_t seq, int len, bool orig) interp->NewGap(orig, len); } -void SSL_Analyzer::SetSecret(const u_char* data, int len) +void SSL_Analyzer::SetSecret(zeek::StringVal* secret) { - secret = make_intrusive(len, (const char*)data); + SetSecret(secret->Len(), secret->Bytes()); } -void SSL_Analyzer::SetKeys(const u_char* data, int len) +void SSL_Analyzer::SetSecret(size_t len, const u_char* data) { - keys = make_intrusive(len, (const char*)data); + secret.clear(); + secret.append((const char*)data, len); + } + +void SSL_Analyzer::SetKeys(zeek::StringVal* keys) + { + SetKeys(keys->Len(), keys->Bytes()); + } + +void SSL_Analyzer::SetKeys(size_t len, const u_char* data) + { + keys.clear(); + keys.reserve(len); + std::copy(data, data + len, std::back_inserter(keys)); } bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label, @@ -152,7 +162,7 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label #ifdef OPENSSL_HAVE_KDF_H // alloc buffers EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); - size_t seed_len = label.length() + rnd1_len + rnd2_len; + size_t seed_len = label.size() + rnd1_len + rnd2_len; std::string seed{}; seed.reserve(seed_len); @@ -166,9 +176,9 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label // FIXME: sha384 should not be hardcoded if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0) goto abort; /* Error */ - if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret.data(), secret.length()) <= 0) + if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret.data(), secret.size()) <= 0) goto abort; /* Error */ - if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed.data(), seed.length()) <= 0) + if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed.data(), seed.size()) <= 0) goto abort; /* Error */ if (EVP_PKEY_derive(pctx, out, &out_len) <= 0) goto abort; /* Error */ @@ -195,16 +205,16 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i } // Neither secret or key present: abort - if ( ( secret == nullptr || secret->Len() == 0 ) && ( keys == nullptr || keys->Len() == 0 ) ) + if ( secret.size() == 0 && keys.size() == 0 ) { DBG_LOG(DBG_ANALYZER, "Could not decrypt packet due to missing keys/secret.\n"); // FIXME: change util function to return a printably std::string for DBG_LOG - //print_hex("->client_random:", handshake_interp->client_random().data(), handshake_interp->client_random().length()); + //print_hex("->client_random:", handshake_interp->client_random().data(), handshake_interp->client_random().size()); return false; } // Secret present, but no keys derived yet: derive keys - if ( secret != nullptr && secret->Len() != 0 && ( keys == nullptr || keys->Len() == 0 ) ) + if ( secret.size() != 0 && keys.size() == 0 ) { #ifdef OPENSSL_HAVE_KDF_H DBG_LOG(DBG_ANALYZER, "Deriving TLS keys for connection foo"); @@ -218,7 +228,7 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i memcpy(crand, &(ts), 4); memcpy(crand + 4, c_rnd.data(), c_rnd.length()); - auto res = TLS12_PRF(secret->ToStdString(), "key expansion", + auto res = TLS12_PRF(secret, "key expansion", (char*)s_rnd.data(), s_rnd.length(), crand, sizeof(crand), keybuf, sizeof(keybuf)); if ( !res ) { @@ -227,29 +237,36 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i } // save derived keys - SetKeys(keybuf, sizeof(keybuf)); + SetKeys(sizeof(keybuf), keybuf); #else DBG_LOG(DBG_ANALYZER, "Cannot derive TLS keys as Zeek was compiled without "); #endif } // Keys present: decrypt TLS application data - if ( keys != nullptr && keys->Len() == 72 ) + if ( keys.size() == 72 ) { // FIXME: could also print keys or conn id here DBG_LOG(DBG_ANALYZER, "Decrypting application data"); - // session keys & AEAD data + + // client write_key u_char c_wk[32]; + // server write_key u_char s_wk[32]; + // client IV u_char c_iv[4]; + // server IV u_char s_iv[4]; + + // AEAD nonce & tag u_char s_aead_nonce[12]; u_char s_aead_tag[13]; - memcpy(c_wk, keys->Bytes(), 32); - memcpy(s_wk, &(keys->Bytes()[32]), 32); - memcpy(c_iv, &(keys->Bytes()[64]), 4); - memcpy(s_iv, &(keys->Bytes()[68]), 4); + // FIXME: there should be a better way to do these copies + memcpy(c_wk, keys.data(), 32); + memcpy(s_wk, keys.data() + 32, 32); + memcpy(c_iv, keys.data() + 64, 4); + memcpy(s_iv, keys.data() + 68, 4); // FIXME: should we change types here? u_char* encrypted = (u_char*)data; @@ -301,7 +318,7 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i EVP_DecryptUpdate(ctx, decrypted, &decrypted_len, (const u_char*) encrypted, encrypted_len); int res = 0; - if ( ! (res = EVP_DecryptFinal(ctx, NULL, &res)) ) + if ( ! ( res = EVP_DecryptFinal(ctx, NULL, &res) ) ) { DBG_LOG(DBG_ANALYZER, "Decryption failed with return code: %d. Invalid key?\n", res); EVP_CIPHER_CTX_free(ctx); diff --git a/src/analyzer/protocol/ssl/SSL.h b/src/analyzer/protocol/ssl/SSL.h index e4df7a5b55..6a5abe2ce7 100644 --- a/src/analyzer/protocol/ssl/SSL.h +++ b/src/analyzer/protocol/ssl/SSL.h @@ -34,12 +34,92 @@ public: static analyzer::Analyzer* Instantiate(Connection* conn) { return new SSL_Analyzer(conn); } - // Key material for decryption - void SetSecret(const u_char* data, int len); - void SetKeys(const u_char* data, int len); + /** + * Set the secret that should be used to derive keys for the + * connection. (For TLS 1.2 this is the pre-master secret) + * + * @param secret The secret to set + */ + void SetSecret(StringVal* secret); + /** + * Set the secret that should be used to derive keys for the + * connection. (For TLS 1.2 this is the pre-master secret) + * + * @param len Length of the secret bytes + * + * @param data Pointer to the secret bytes + */ + void SetSecret(size_t len, const u_char* data); + + /** + * Set the decryption keys that should be used to decrypt + * TLS application data in the connection. + * + * @param keys The key buffer as derived via TLS PRF (for + * AES_GCM this should be 72 bytes in length) + */ + void SetKeys(StringVal* keys); + + /** + * Set the decryption keys that should be used to decrypt + * TLS application data in the connection. + * + * @param len Length of the key buffer (for AES_GCM this should + * be 72) + * + * @param data Pointer to the key buffer as derived via TLS PRF + */ + void SetKeys(size_t len, const u_char* data); + + /** + * Try to decrypt TLS application data from a packet. Requires secret or keys to be set prior + * + * @param len Length of the encrypted bytes to decrypt + * + * @param data Pointer to the encrypted bytes to decrypt + * + * @param is_orig Direction of the connection + * + * @param content_type Content type as given in the TLS packet + * + * @param raw_tls_version Raw TLS version as given in the TLS packets + */ bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version); + + /** + * TLS 1.2 pseudo random function (PRF) used to expand the pre-master secret and derive keys. + * The seed is obtained by concatinating rnd1 and rnd2 + * + * @param secret Secret as defined in the TLS RFC + * + * @param label Label as defined in the TLS RFC + * + * @param rnd1 Pointer to the first part of the seed + * + * @param rnd1_len Length of the first part of the seed + * + * @param rnd2 Pointer to the second part of the seed + * + * @param rnd2_len Length of the second part of the seed + * + * @param out Pointer to the derived bytes + * + * @param out_len Length indicating how many bytes should be derived + * + * @return True, if the operation completed successfully, false otherwise + */ bool TLS12_PRF(const std::string& secret, const std::string& label, const char* rnd1, size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, size_t out_len); + + /** + * Forward decrypted TLS application data to child analyzers + * + * @param len Length of the data to forward + * + * @param data Pointer to the data to forward + * + * @param is_orig Direction of the connection + */ void ForwardDecryptedData(int len, const u_char* data, bool is_orig); protected: @@ -50,8 +130,8 @@ protected: // FIXME: should this be moved into the connection? int c_seq; int s_seq; - StringValPtr secret; - StringValPtr keys; + std::string secret; + std::vector keys; zeek::analyzer::pia::PIA_TCP *pia; }; diff --git a/src/analyzer/protocol/ssl/functions.bif b/src/analyzer/protocol/ssl/functions.bif index b04bf19b3f..19661ad4f3 100644 --- a/src/analyzer/protocol/ssl/functions.bif +++ b/src/analyzer/protocol/ssl/functions.bif @@ -22,7 +22,7 @@ function set_secret%(c: connection, secret: string%): bool analyzer::Analyzer* sa = c->FindAnalyzer("SSL"); if ( sa ) { - static_cast(sa)->SetSecret(secret->Bytes(), secret->Len()); + static_cast(sa)->SetSecret(secret); return zeek::val_mgr->True(); } return zeek::val_mgr->False(); @@ -33,7 +33,7 @@ function set_keys%(c: connection, keys: string%): bool analyzer::Analyzer* sa = c->FindAnalyzer("SSL"); if ( sa ) { - static_cast(sa)->SetKeys(keys->Bytes(), keys->Len()); + static_cast(sa)->SetKeys(keys); return zeek::val_mgr->True(); } return zeek::val_mgr->False(); From 946d74674a3b7862ea53279159c47b69f17ef671 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Fri, 25 Jun 2021 16:04:41 +0200 Subject: [PATCH 12/24] ssl/analyzer: potentially fix memory leaks caused by bytestrings --- src/analyzer/protocol/ssl/tls-handshake-protocol.pac | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/analyzer/protocol/ssl/tls-handshake-protocol.pac b/src/analyzer/protocol/ssl/tls-handshake-protocol.pac index 628dd3da48..b42f292720 100644 --- a/src/analyzer/protocol/ssl/tls-handshake-protocol.pac +++ b/src/analyzer/protocol/ssl/tls-handshake-protocol.pac @@ -959,6 +959,11 @@ refine connection Handshake_Conn += { gmt_unix_time_ = 0; %} + %cleanup{ + client_random_.free(); + server_random_.free(); + %} + function chosen_cipher() : int %{ return chosen_cipher_; %} function set_cipher(cipher: uint32) : bool @@ -995,6 +1000,7 @@ refine connection Handshake_Conn += { function set_client_random(client_random: bytestring) : bool %{ + client_random_.free(); client_random_.init(client_random.data(), client_random.length()); return true; %} @@ -1003,6 +1009,7 @@ refine connection Handshake_Conn += { function set_server_random(server_random: bytestring) : bool %{ + server_random_.free(); server_random_.init(server_random.data(), server_random.length()); return true; %} From 83938682073057dcca0b70fb291eb54edde6c24a Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Thu, 14 Oct 2021 17:45:41 +0200 Subject: [PATCH 13/24] ssl: adapt TLS-PRF to openSSL 3.0 --- src/analyzer/protocol/ssl/SSL.cc | 37 ++++++++++++++++---------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index 5c72751cb8..fd7fc25eec 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -12,7 +12,8 @@ #include #ifdef OPENSSL_HAVE_KDF_H - #include + #include + #include #endif static void print_hex(std::string name, u_char* data, int len) @@ -160,34 +161,32 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label const char* rnd1, size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, size_t out_len) { #ifdef OPENSSL_HAVE_KDF_H - // alloc buffers - EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); + // alloc context + params + EVP_KDF *kdf = EVP_KDF_fetch(NULL, "TLS1-PRF", NULL); + EVP_KDF_CTX *pctx = EVP_KDF_CTX_new(kdf); + OSSL_PARAM params[4], *p = params; + EVP_KDF_free(kdf); + + // prepare seed: seed = label + rnd1 + rnd2 size_t seed_len = label.size() + rnd1_len + rnd2_len; std::string seed{}; seed.reserve(seed_len); - // seed = label + rnd1 + rnd2 seed.append(label); seed.append(rnd1, rnd1_len); seed.append(rnd2, rnd2_len); - if (EVP_PKEY_derive_init(pctx) <= 0) - goto abort; /* Error */ + // setup OSSL_PARAM array: digest, secret, seed // FIXME: sha384 should not be hardcoded - if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0) - goto abort; /* Error */ - if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret.data(), secret.size()) <= 0) - goto abort; /* Error */ - if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed.data(), seed.size()) <= 0) - goto abort; /* Error */ - if (EVP_PKEY_derive(pctx, out, &out_len) <= 0) - goto abort; /* Error */ + *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha384, strlen(SN_sha384)); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SECRET, (void*)secret.data(), secret.size()); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SEED, (void*)seed.data(), seed.size()); + *p = OSSL_PARAM_construct_end(); - EVP_PKEY_CTX_free(pctx); - return true; - -abort: - EVP_PKEY_CTX_free(pctx); + // derive key material + bool result = EVP_KDF_derive(pctx, out, out_len, params) <= 0; + EVP_KDF_CTX_free(pctx); + return result; #endif return false; } From eabb6eb743745b44105d6757b046add2ce766383 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Fri, 15 Oct 2021 09:13:31 +0100 Subject: [PATCH 14/24] TLS 1.2 decryption: adapt OpenSSL 3.0 changes for 1.1 Now this should compile and work with both OpenSSL 3 and OpenSSL 1.1. --- src/analyzer/protocol/ssl/SSL.cc | 35 ++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index fd7fc25eec..3ab7d63416 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -10,10 +10,14 @@ #include #include +#include #ifdef OPENSSL_HAVE_KDF_H #include - #include +#endif + +#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) +#include #endif static void print_hex(std::string name, u_char* data, int len) @@ -161,11 +165,16 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label const char* rnd1, size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, size_t out_len) { #ifdef OPENSSL_HAVE_KDF_H +#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) // alloc context + params EVP_KDF *kdf = EVP_KDF_fetch(NULL, "TLS1-PRF", NULL); EVP_KDF_CTX *pctx = EVP_KDF_CTX_new(kdf); OSSL_PARAM params[4], *p = params; EVP_KDF_free(kdf); +#else /* OSSL 3 */ + // alloc buffers + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); +#endif /* OSSL 3 */ // prepare seed: seed = label + rnd1 + rnd2 size_t seed_len = label.size() + rnd1_len + rnd2_len; @@ -176,6 +185,7 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label seed.append(rnd1, rnd1_len); seed.append(rnd2, rnd2_len); +#if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) // setup OSSL_PARAM array: digest, secret, seed // FIXME: sha384 should not be hardcoded *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha384, strlen(SN_sha384)); @@ -187,7 +197,28 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label bool result = EVP_KDF_derive(pctx, out, out_len, params) <= 0; EVP_KDF_CTX_free(pctx); return result; -#endif +#else /* OSSL 3 */ + if (EVP_PKEY_derive_init(pctx) <= 0) + goto abort; /* Error */ + // setup OSSL_PARAM array: digest, secret, seed + // FIXME: sha384 should not be hardcoded + if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0) + goto abort; /* Error */ + if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret.data(), secret.size()) <= 0) + goto abort; /* Error */ + if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed.data(), seed.size()) <= 0) + goto abort; /* Error */ + if (EVP_PKEY_derive(pctx, out, &out_len) <= 0) + goto abort; /* Error */ + + EVP_PKEY_CTX_free(pctx); + return true; + +abort: + EVP_PKEY_CTX_free(pctx); +#endif /* OSSL 3 */ + +#endif /* HAVE_KDF */ return false; } From fe4e06e8ca80e3bf98cc7f2dce9fb57495b0db49 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Tue, 19 Oct 2021 17:28:59 +0200 Subject: [PATCH 15/24] TLS decryption: remove payload from ssl_encrypted_data again. There is no reason to make the payload available in the event - it is still encrypted. --- scripts/policy/protocols/ssl/decryption.zeek | 2 +- scripts/policy/protocols/ssl/heartbleed.zeek | 2 +- src/analyzer/protocol/ssl/events.bif | 4 +--- src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac | 3 +-- .../scripts.policy.misc.dump-events/really-all-events.log | 2 -- .../btest/scripts/base/protocols/ssl/handshake-events.test | 2 +- testing/btest/scripts/base/protocols/ssl/tls13.test | 2 +- .../base/protocols/ssl/tls13_encrypted_handshake_events.test | 2 +- 8 files changed, 7 insertions(+), 12 deletions(-) diff --git a/scripts/policy/protocols/ssl/decryption.zeek b/scripts/policy/protocols/ssl/decryption.zeek index 766bb4d940..a8bb7e52e9 100644 --- a/scripts/policy/protocols/ssl/decryption.zeek +++ b/scripts/policy/protocols/ssl/decryption.zeek @@ -77,7 +77,7 @@ event ssl_client_hello(c: connection, version: count, record_version: count, pos } } -event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string) +event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) { if ( c$ssl?$client_random ) { diff --git a/scripts/policy/protocols/ssl/heartbleed.zeek b/scripts/policy/protocols/ssl/heartbleed.zeek index 40bc800b8c..aabafbff14 100644 --- a/scripts/policy/protocols/ssl/heartbleed.zeek +++ b/scripts/policy/protocols/ssl/heartbleed.zeek @@ -223,7 +223,7 @@ event ssl_encrypted_heartbeat(c: connection, is_orig: bool, length: count) } } -event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string) +event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) { if ( !c?$ssl ) return; diff --git a/src/analyzer/protocol/ssl/events.bif b/src/analyzer/protocol/ssl/events.bif index 9192babfdf..25bc34398b 100644 --- a/src/analyzer/protocol/ssl/events.bif +++ b/src/analyzer/protocol/ssl/events.bif @@ -558,11 +558,9 @@ event ssl_plaintext_data%(c: connection, is_orig: bool, record_version: count, c ## ## length: length of the entire message. ## -## payload: encrypted payload of the SSL/TLS message -## ## .. zeek:see:: ssl_client_hello ssl_established ssl_extension ssl_server_hello ## ssl_alert ssl_heartbeat ssl_probable_encrypted_handshake_message -event ssl_encrypted_data%(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string%); +event ssl_encrypted_data%(c: connection, is_orig: bool, record_version: count, content_type: count, length: count%); ## This event is generated for application data records of TLS 1.3 connections of which ## we suspect that they contain handshake messages. diff --git a/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac b/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac index 8cb076ad6b..c3cc45e6ad 100644 --- a/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac @@ -64,8 +64,7 @@ refine connection SSL_Conn += { if ( ssl_encrypted_data ) { zeek::BifEvent::enqueue_ssl_encrypted_data(zeek_analyzer(), - zeek_analyzer()->Conn(), ${rec.is_orig}, ${rec.raw_tls_version}, ${rec.content_type}, ${rec.length}, - zeek::make_intrusive(cont.length(), (const char*) cont.data())); + zeek_analyzer()->Conn(), ${rec.is_orig}, ${rec.raw_tls_version}, ${rec.content_type}, ${rec.length}); if (rec->content_type() == APPLICATION_DATA) { zeek_analyzer()->TryDecryptApplicationData(cont.length(), cont.data(), rec->is_orig(), rec->content_type(), rec->raw_tls_version()); diff --git a/testing/btest/Baseline/scripts.policy.misc.dump-events/really-all-events.log b/testing/btest/Baseline/scripts.policy.misc.dump-events/really-all-events.log index a2360dbd29..3836b0306c 100644 --- a/testing/btest/Baseline/scripts.policy.misc.dump-events/really-all-events.log +++ b/testing/btest/Baseline/scripts.policy.misc.dump-events/really-all-events.log @@ -9079,7 +9079,6 @@ XXXXXXXXXX.XXXXXX ssl_encrypted_data [2] record_version: count = 771 [3] content_type: count = 22 [4] length: count = 32 - [5] payload: string = \x1c\x1c\x84S/9\x14e\xb6'\xe5,\x03\x0fY\xdf\x1b\xcfu\xc84\xae\x1a"\xea]9j'\xbeZ\xa7 XXXXXXXXXX.XXXXXX raw_packet [0] p: raw_pkt_hdr = [l2=[encap=LINK_ETHERNET, len=91, cap_len=91, src=58:b0:35:86:54:8d, dst=cc:b2:55:f4:62:92, vlan=, inner_vlan=, eth_type=2048, proto=L3_IPV4], ip=[hl=20, tos=0, len=77, id=51331, ttl=64, p=6, src=192.168.133.100, dst=17.167.150.73], ip6=, tcp=[sport=49655/tcp, dport=443/tcp, seq=3289393854, ack=2319612745, hl=20, dl=37, reserved=0, flags=24, win=8192], udp=, icmp=] @@ -9177,7 +9176,6 @@ XXXXXXXXXX.XXXXXX ssl_encrypted_data [2] record_version: count = 771 [3] content_type: count = 22 [4] length: count = 32 - [5] payload: string = Z\x99\x17~d\x06\xbd;\xb4\xdf\xe2\xb3~9,|\xac\xdb\xb4\xeb\xcc\x95.\x17\xd2Q\x8a\x96\xdb\x13\x09! XXXXXXXXXX.XXXXXX raw_packet [0] p: raw_pkt_hdr = [l2=[encap=LINK_ETHERNET, len=97, cap_len=97, src=cc:b2:55:f4:62:92, dst=58:b0:35:86:54:8d, vlan=, inner_vlan=, eth_type=2048, proto=L3_IPV4], ip=[hl=20, tos=0, len=83, id=50807, ttl=243, p=6, src=17.167.150.73, dst=192.168.133.100], ip6=, tcp=[sport=443/tcp, dport=49655/tcp, seq=2319612745, ack=3289393891, hl=20, dl=43, reserved=0, flags=24, win=3626], udp=, icmp=] diff --git a/testing/btest/scripts/base/protocols/ssl/handshake-events.test b/testing/btest/scripts/base/protocols/ssl/handshake-events.test index 0c694bfaa1..0b45bebc02 100644 --- a/testing/btest/scripts/base/protocols/ssl/handshake-events.test +++ b/testing/btest/scripts/base/protocols/ssl/handshake-events.test @@ -27,7 +27,7 @@ event ssl_plaintext_data(c: connection, is_orig: bool, record_version: count, co print "Plaintext data", c$id$orig_h, c$id$resp_h, is_orig, SSL::version_strings[record_version], content_type, length; } -event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string) +event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) { print "Encrypted data", c$id$orig_h, c$id$resp_h, is_orig, SSL::version_strings[record_version], content_type, length; } diff --git a/testing/btest/scripts/base/protocols/ssl/tls13.test b/testing/btest/scripts/base/protocols/ssl/tls13.test index f1f03cd5df..875149ce80 100644 --- a/testing/btest/scripts/base/protocols/ssl/tls13.test +++ b/testing/btest/scripts/base/protocols/ssl/tls13.test @@ -37,7 +37,7 @@ event ssl_established(c: connection) print "established", c$id; } -event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string) +event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) { print "encrypted", c$id, is_orig, SSL::version_strings[record_version], content_type; } diff --git a/testing/btest/scripts/base/protocols/ssl/tls13_encrypted_handshake_events.test b/testing/btest/scripts/base/protocols/ssl/tls13_encrypted_handshake_events.test index 08936cee56..3293315723 100644 --- a/testing/btest/scripts/base/protocols/ssl/tls13_encrypted_handshake_events.test +++ b/testing/btest/scripts/base/protocols/ssl/tls13_encrypted_handshake_events.test @@ -6,7 +6,7 @@ redef SSL::disable_analyzer_after_detection=F; -event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count, payload: string) +event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) { print "encrypted", c$id, is_orig, SSL::version_strings[record_version], content_type; } From cc9e38f58b5909617b3fc41856ff8feda59622d2 Mon Sep 17 00:00:00 2001 From: Florian Wilkens Date: Fri, 5 Nov 2021 15:57:43 +0100 Subject: [PATCH 16/24] add missing call to EVP_KDF_CTX_set_params --- src/analyzer/protocol/ssl/SSL.cc | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index 3ab7d63416..cee214fe8f 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -168,7 +168,7 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) // alloc context + params EVP_KDF *kdf = EVP_KDF_fetch(NULL, "TLS1-PRF", NULL); - EVP_KDF_CTX *pctx = EVP_KDF_CTX_new(kdf); + EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); OSSL_PARAM params[4], *p = params; EVP_KDF_free(kdf); #else /* OSSL 3 */ @@ -193,14 +193,23 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SEED, (void*)seed.data(), seed.size()); *p = OSSL_PARAM_construct_end(); + // set OSSL params + if (EVP_KDF_CTX_set_params(kctx, params) <= 0) + goto abort; // derive key material - bool result = EVP_KDF_derive(pctx, out, out_len, params) <= 0; - EVP_KDF_CTX_free(pctx); - return result; + if (EVP_KDF_derive(kctx, out, out_len, NULL) <= 0) + goto abort; + + EVP_KDF_CTX_free(kctx); + return true; + +abort: + EVP_KDF_CTX_free(kctx); + return false; #else /* OSSL 3 */ if (EVP_PKEY_derive_init(pctx) <= 0) goto abort; /* Error */ - // setup OSSL_PARAM array: digest, secret, seed + // setup PKEY params: digest, secret, seed // FIXME: sha384 should not be hardcoded if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0) goto abort; /* Error */ From 9e5c4ae700ea1da32e1ff836810668931a23e314 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Wed, 5 Jan 2022 10:28:26 +0000 Subject: [PATCH 17/24] Clang-format updates --- src/analyzer/protocol/ssl/DTLS.cc | 3 +- src/analyzer/protocol/ssl/DTLS.h | 3 +- src/analyzer/protocol/ssl/SSL.cc | 82 ++++++++++++++++--------------- src/analyzer/protocol/ssl/SSL.h | 8 +-- 4 files changed, 52 insertions(+), 44 deletions(-) diff --git a/src/analyzer/protocol/ssl/DTLS.cc b/src/analyzer/protocol/ssl/DTLS.cc index 67def99641..7ddd714989 100644 --- a/src/analyzer/protocol/ssl/DTLS.cc +++ b/src/analyzer/protocol/ssl/DTLS.cc @@ -76,7 +76,8 @@ void DTLS_Analyzer::SendHandshake(uint16_t raw_tls_version, uint8_t msg_type, ui } } -bool DTLS_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version) +bool DTLS_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, + uint8_t content_type, uint16_t raw_tls_version) { // noop for now as DTLS decryption is currently not supported return false; diff --git a/src/analyzer/protocol/ssl/DTLS.h b/src/analyzer/protocol/ssl/DTLS.h index d0d798f9df..34f69fba65 100644 --- a/src/analyzer/protocol/ssl/DTLS.h +++ b/src/analyzer/protocol/ssl/DTLS.h @@ -39,7 +39,8 @@ public: static analyzer::Analyzer* Instantiate(Connection* conn) { return new DTLS_Analyzer(conn); } - bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version); + bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, + uint16_t raw_tls_version); protected: binpac::DTLS::SSL_Conn* interp; diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index cd606285bc..2d49906d76 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -1,5 +1,9 @@ #include "zeek/analyzer/protocol/ssl/SSL.h" +#include +#include +#include + #include "zeek/Reporter.h" #include "zeek/analyzer/Manager.h" #include "zeek/analyzer/protocol/ssl/events.bif.h" @@ -8,12 +12,8 @@ #include "zeek/analyzer/protocol/tcp/TCP_Reassembler.h" #include "zeek/util.h" -#include -#include -#include - #ifdef OPENSSL_HAVE_KDF_H - #include +#include #endif #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) @@ -24,10 +24,10 @@ static void print_hex(std::string name, u_char* data, int len) { int i = 0; printf("%s (%d): ", name.c_str(), len); - if (len > 0) + if ( len > 0 ) printf("0x%02x", data[0]); - for (i = 1; i < len; i++) + for ( i = 1; i < len; i++ ) { printf(" 0x%02x", data[i]); } @@ -37,14 +37,14 @@ static void print_hex(std::string name, u_char* data, int len) namespace zeek::analyzer::ssl { -#define MSB(a) ((a>>8)&0xff) -#define LSB(a) (a&0xff) +#define MSB(a) ((a >> 8) & 0xff) +#define LSB(a) (a & 0xff) static void fmt_seq(uint32_t num, u_char* buf) { memset(buf, 0, 8); uint32_t netnum = htonl(num); - memcpy(buf+4, &netnum, 4); + memcpy(buf + 4, &netnum, 4); } SSL_Analyzer::SSL_Analyzer(Connection* c) : analyzer::tcp::TCP_ApplicationAnalyzer("SSL", c) @@ -161,19 +161,20 @@ void SSL_Analyzer::SetKeys(size_t len, const u_char* data) std::copy(data, data + len, std::back_inserter(keys)); } -bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label, - const char* rnd1, size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, size_t out_len) +bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label, const char* rnd1, + size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, + size_t out_len) { #ifdef OPENSSL_HAVE_KDF_H #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) // alloc context + params - EVP_KDF *kdf = EVP_KDF_fetch(NULL, "TLS1-PRF", NULL); - EVP_KDF_CTX *kctx = EVP_KDF_CTX_new(kdf); + EVP_KDF* kdf = EVP_KDF_fetch(NULL, "TLS1-PRF", NULL); + EVP_KDF_CTX* kctx = EVP_KDF_CTX_new(kdf); OSSL_PARAM params[4], *p = params; EVP_KDF_free(kdf); #else /* OSSL 3 */ // alloc buffers - EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); + EVP_PKEY_CTX* pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_TLS1_PRF, NULL); #endif /* OSSL 3 */ // prepare seed: seed = label + rnd1 + rnd2 @@ -189,15 +190,16 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label // setup OSSL_PARAM array: digest, secret, seed // FIXME: sha384 should not be hardcoded *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha384, strlen(SN_sha384)); - *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SECRET, (void*)secret.data(), secret.size()); + *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SECRET, (void*)secret.data(), + secret.size()); *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SEED, (void*)seed.data(), seed.size()); *p = OSSL_PARAM_construct_end(); // set OSSL params - if (EVP_KDF_CTX_set_params(kctx, params) <= 0) + if ( EVP_KDF_CTX_set_params(kctx, params) <= 0 ) goto abort; // derive key material - if (EVP_KDF_derive(kctx, out, out_len, NULL) <= 0) + if ( EVP_KDF_derive(kctx, out, out_len, NULL) <= 0 ) goto abort; EVP_KDF_CTX_free(kctx); @@ -207,17 +209,17 @@ abort: EVP_KDF_CTX_free(kctx); return false; #else /* OSSL 3 */ - if (EVP_PKEY_derive_init(pctx) <= 0) + if ( EVP_PKEY_derive_init(pctx) <= 0 ) goto abort; /* Error */ // setup PKEY params: digest, secret, seed // FIXME: sha384 should not be hardcoded - if (EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0) + if ( EVP_PKEY_CTX_set_tls1_prf_md(pctx, EVP_sha384()) <= 0 ) goto abort; /* Error */ - if (EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret.data(), secret.size()) <= 0) + if ( EVP_PKEY_CTX_set1_tls1_prf_secret(pctx, secret.data(), secret.size()) <= 0 ) goto abort; /* Error */ - if (EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed.data(), seed.size()) <= 0) + if ( EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed.data(), seed.size()) <= 0 ) goto abort; /* Error */ - if (EVP_PKEY_derive(pctx, out, &out_len) <= 0) + if ( EVP_PKEY_derive(pctx, out, &out_len) <= 0 ) goto abort; /* Error */ EVP_PKEY_CTX_free(pctx); @@ -231,8 +233,8 @@ abort: return false; } - -bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version) +bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, + uint8_t content_type, uint16_t raw_tls_version) { // Unsupported cipher suite. Currently supported: // - TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 == 0xC030 @@ -248,7 +250,8 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i { DBG_LOG(DBG_ANALYZER, "Could not decrypt packet due to missing keys/secret.\n"); // FIXME: change util function to return a printably std::string for DBG_LOG - //print_hex("->client_random:", handshake_interp->client_random().data(), handshake_interp->client_random().size()); + // print_hex("->client_random:", handshake_interp->client_random().data(), + // handshake_interp->client_random().size()); return false; } @@ -257,7 +260,7 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i { #ifdef OPENSSL_HAVE_KDF_H DBG_LOG(DBG_ANALYZER, "Deriving TLS keys for connection foo"); - uint32_t ts = htonl((uint32_t) handshake_interp->gmt_unix_time()); + uint32_t ts = htonl((uint32_t)handshake_interp->gmt_unix_time()); char crand[32] = {0x00}; u_char keybuf[72]; @@ -267,9 +270,9 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i memcpy(crand, &(ts), 4); memcpy(crand + 4, c_rnd.data(), c_rnd.length()); - auto res = TLS12_PRF(secret, "key expansion", - (char*)s_rnd.data(), s_rnd.length(), crand, sizeof(crand), keybuf, sizeof(keybuf)); - if ( !res ) + auto res = TLS12_PRF(secret, "key expansion", (char*)s_rnd.data(), s_rnd.length(), crand, + sizeof(crand), keybuf, sizeof(keybuf)); + if ( ! res ) { DBG_LOG(DBG_ANALYZER, "TLS PRF failed. Aborting.\n"); return false; @@ -278,7 +281,8 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i // save derived keys SetKeys(sizeof(keybuf), keybuf); #else - DBG_LOG(DBG_ANALYZER, "Cannot derive TLS keys as Zeek was compiled without "); + DBG_LOG(DBG_ANALYZER, + "Cannot derive TLS keys as Zeek was compiled without "); #endif } @@ -323,7 +327,7 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i memcpy(s_aead_nonce, s_iv, 4); memcpy(&(s_aead_nonce[4]), encrypted, 8); - EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); + EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(ctx); EVP_CipherInit(ctx, EVP_aes_256_gcm(), NULL, NULL, 0); @@ -333,13 +337,13 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i encrypted_len -= 16; // FIXME: aes_256_gcm should not be hardcoded here ;) - if (is_orig) + if ( is_orig ) EVP_DecryptInit(ctx, EVP_aes_256_gcm(), c_wk, s_aead_nonce); else EVP_DecryptInit(ctx, EVP_aes_256_gcm(), s_wk, s_aead_nonce); EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, encrypted + encrypted_len); - if (is_orig) + if ( is_orig ) fmt_seq(c_seq, s_aead_tag); else fmt_seq(s_seq, s_aead_tag); @@ -350,18 +354,18 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i s_aead_tag[11] = MSB(encrypted_len); s_aead_tag[12] = LSB(encrypted_len); - u_char *decrypted = new u_char[ encrypted_len ]; + u_char* decrypted = new u_char[encrypted_len]; int decrypted_len = 0; EVP_DecryptUpdate(ctx, NULL, &decrypted_len, s_aead_tag, 13); - EVP_DecryptUpdate(ctx, decrypted, &decrypted_len, (const u_char*) encrypted, encrypted_len); + EVP_DecryptUpdate(ctx, decrypted, &decrypted_len, (const u_char*)encrypted, encrypted_len); int res = 0; - if ( ! ( res = EVP_DecryptFinal(ctx, NULL, &res) ) ) + if ( ! (res = EVP_DecryptFinal(ctx, NULL, &res)) ) { DBG_LOG(DBG_ANALYZER, "Decryption failed with return code: %d. Invalid key?\n", res); EVP_CIPHER_CTX_free(ctx); - delete [] decrypted; + delete[] decrypted; return false; } @@ -369,7 +373,7 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i EVP_CIPHER_CTX_free(ctx); ForwardDecryptedData(decrypted_len, reinterpret_cast(decrypted), is_orig); - delete [] decrypted; + delete[] decrypted; return true; } diff --git a/src/analyzer/protocol/ssl/SSL.h b/src/analyzer/protocol/ssl/SSL.h index 3d8c13e93c..630531aabb 100644 --- a/src/analyzer/protocol/ssl/SSL.h +++ b/src/analyzer/protocol/ssl/SSL.h @@ -97,7 +97,8 @@ public: * * @param raw_tls_version Raw TLS version as given in the TLS packets */ - bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version); + bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, + uint16_t raw_tls_version); /** * TLS 1.2 pseudo random function (PRF) used to expand the pre-master secret and derive keys. @@ -121,7 +122,8 @@ public: * * @return True, if the operation completed successfully, false otherwise */ - bool TLS12_PRF(const std::string& secret, const std::string& label, const char* rnd1, size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, size_t out_len); + bool TLS12_PRF(const std::string& secret, const std::string& label, const char* rnd1, + size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, size_t out_len); /** * Forward decrypted TLS application data to child analyzers @@ -144,7 +146,7 @@ protected: int s_seq; std::string secret; std::vector keys; - zeek::analyzer::pia::PIA_TCP *pia; + zeek::analyzer::pia::PIA_TCP* pia; }; } // namespace zeek::analyzer::ssl From f77213ba66b0fa15ba4f0d13145a1fe3cb15cda9 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Wed, 5 Jan 2022 10:41:55 +0000 Subject: [PATCH 18/24] Deprecation and warning fixes --- src/analyzer/protocol/ssl/SSL.cc | 3 ++- src/analyzer/protocol/ssl/ssl-analyzer.pac | 6 +++--- src/analyzer/protocol/ssl/tls-handshake-analyzer.pac | 6 +++--- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index 2d49906d76..d46c92b120 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -189,7 +189,8 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) // setup OSSL_PARAM array: digest, secret, seed // FIXME: sha384 should not be hardcoded - *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, SN_sha384, strlen(SN_sha384)); + // The const-cast is a bit ugly - but otherwise we have to copy the static string. + *p++ = OSSL_PARAM_construct_utf8_string(OSSL_KDF_PARAM_DIGEST, const_cast(SN_sha384), 0); *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SECRET, (void*)secret.data(), secret.size()); *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SEED, (void*)seed.data(), seed.size()); diff --git a/src/analyzer/protocol/ssl/ssl-analyzer.pac b/src/analyzer/protocol/ssl/ssl-analyzer.pac index d676630ae8..3f7543c39f 100644 --- a/src/analyzer/protocol/ssl/ssl-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-analyzer.pac @@ -14,11 +14,11 @@ refine connection SSL_Conn += { %{ if ( ! version_ok(version) ) { - zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported client SSL version 0x%04x", version)); + zeek_analyzer()->AnalyzerViolation(zeek::util::fmt("unsupported client SSL version 0x%04x", version)); zeek_analyzer()->SetSkip(true); } else - zeek_analyzer()->ProtocolConfirmation(); + zeek_analyzer()->AnalyzerConfirmation(); if ( ssl_client_hello ) { @@ -69,7 +69,7 @@ refine connection SSL_Conn += { %{ if ( ! version_ok(version) ) { - zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported server SSL version 0x%04x", version)); + zeek_analyzer()->AnalyzerViolation(zeek::util::fmt("unsupported server SSL version 0x%04x", version)); zeek_analyzer()->SetSkip(true); } diff --git a/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac b/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac index 589921e6b3..0f222ae682 100644 --- a/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac +++ b/src/analyzer/protocol/ssl/tls-handshake-analyzer.pac @@ -37,11 +37,11 @@ refine connection Handshake_Conn += { %{ if ( ! version_ok(version) ) { - zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported client SSL version 0x%04x", version)); + zeek_analyzer()->AnalyzerViolation(zeek::util::fmt("unsupported client SSL version 0x%04x", version)); zeek_analyzer()->SetSkip(true); } else - zeek_analyzer()->ProtocolConfirmation(); + zeek_analyzer()->AnalyzerConfirmation(); if ( ssl_client_hello ) { @@ -94,7 +94,7 @@ refine connection Handshake_Conn += { %{ if ( ! version_ok(version) ) { - zeek_analyzer()->ProtocolViolation(zeek::util::fmt("unsupported server SSL version 0x%04x", version)); + zeek_analyzer()->AnalyzerViolation(zeek::util::fmt("unsupported server SSL version 0x%04x", version)); zeek_analyzer()->SetSkip(true); } From 4204615997bab79c65af1d497865cdc78ba4ed1b Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Wed, 5 Jan 2022 10:50:28 +0000 Subject: [PATCH 19/24] SSL decryption: small style changes, a bit of documentation --- scripts/policy/protocols/ssl/decryption.zeek | 23 +++++--------------- src/analyzer/protocol/ssl/DTLS.h | 7 ++++++ src/analyzer/protocol/ssl/SSL.h | 5 ++++- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/scripts/policy/protocols/ssl/decryption.zeek b/scripts/policy/protocols/ssl/decryption.zeek index a8bb7e52e9..75c1d345b6 100644 --- a/scripts/policy/protocols/ssl/decryption.zeek +++ b/scripts/policy/protocols/ssl/decryption.zeek @@ -20,11 +20,11 @@ export { # Do not disable analyzers after detection - otherwise we will not receive # encrypted packets. -redef SSL::disable_analyzer_after_detection=F; +redef SSL::disable_analyzer_after_detection = F; redef record SSL::Info += { - # Decryption uses client_random as identifier - client_random: string &log &optional; + # Decryption uses client_random as identifier + client_random: string &log &optional; }; type Idx: record { @@ -55,12 +55,12 @@ event zeek_init() event SSL::add_keys(client_random: string, val: string) { - SSL::keys[client_random] = val; + SSL::keys[client_random] = val; } event SSL::add_secret(client_random: string, val: string) { - SSL::secrets[client_random] = val; + SSL::secrets[client_random] = val; } event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec) @@ -68,13 +68,9 @@ event ssl_client_hello(c: connection, version: count, record_version: count, pos c$ssl$client_random = client_random; if ( client_random in keys ) - { set_keys(c, keys[client_random]); - } else if ( client_random in secrets ) - { set_secret(c, secrets[client_random]); - } } event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) @@ -82,17 +78,12 @@ event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, co if ( c$ssl?$client_random ) { if ( c$ssl$client_random in keys ) - { set_keys(c, keys[c$ssl$client_random]); - } else if ( c$ssl$client_random in secrets ) - { set_secret(c, secrets[c$ssl$client_random]); - } else { - # FIXME: replace with @if gated reporter - #print "No suitable key or secret found for random:", c$ssl$client_random; + # FIXME: perhaps report that we could not decrypt the session } } } @@ -105,7 +96,5 @@ event SSL::tls_input_done() event Input::end_of_data(name: string, source: string) { if ( name == input_stream_name ) - { event SSL::tls_input_done(); - } } diff --git a/src/analyzer/protocol/ssl/DTLS.h b/src/analyzer/protocol/ssl/DTLS.h index 34f69fba65..1642cb4a6c 100644 --- a/src/analyzer/protocol/ssl/DTLS.h +++ b/src/analyzer/protocol/ssl/DTLS.h @@ -39,6 +39,13 @@ public: static analyzer::Analyzer* Instantiate(Connection* conn) { return new DTLS_Analyzer(conn); } + /** + * Try to decrypt TLS application data from a packet. + * + * For DTLS, this operation is not currently implemented and this function will + * always return false. + * + **/ bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version); diff --git a/src/analyzer/protocol/ssl/SSL.h b/src/analyzer/protocol/ssl/SSL.h index 630531aabb..b96172fc1b 100644 --- a/src/analyzer/protocol/ssl/SSL.h +++ b/src/analyzer/protocol/ssl/SSL.h @@ -141,11 +141,14 @@ protected: binpac::TLSHandshake::Handshake_Conn* handshake_interp; bool had_gap; - // FIXME: should this be moved into the connection? + // client and server sequence number, used for TLS 1.2 decryption int c_seq; int s_seq; + // secret, for decyption std::string secret; + // derived keys, for decryption std::vector keys; + // PIA, for decrypted data zeek::analyzer::pia::PIA_TCP* pia; }; From c04246bd7c6840ce5ae97e8d4f85f5f7bb8b9542 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Wed, 5 Jan 2022 15:42:48 +0000 Subject: [PATCH 20/24] SSL decryption: refactor TLS12_PRF Less use of raw pointers. --- src/analyzer/protocol/ssl/SSL.cc | 77 ++++++++++++++------------------ src/analyzer/protocol/ssl/SSL.h | 7 +-- 2 files changed, 37 insertions(+), 47 deletions(-) diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index d46c92b120..2cc072f0cd 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -20,20 +20,6 @@ #include #endif -static void print_hex(std::string name, u_char* data, int len) - { - int i = 0; - printf("%s (%d): ", name.c_str(), len); - if ( len > 0 ) - printf("0x%02x", data[0]); - - for ( i = 1; i < len; i++ ) - { - printf(" 0x%02x", data[i]); - } - printf("\n"); - } - namespace zeek::analyzer::ssl { @@ -149,21 +135,21 @@ void SSL_Analyzer::SetSecret(size_t len, const u_char* data) secret.append((const char*)data, len); } -void SSL_Analyzer::SetKeys(zeek::StringVal* keys) - { - SetKeys(keys->Len(), keys->Bytes()); - } - -void SSL_Analyzer::SetKeys(size_t len, const u_char* data) +void SSL_Analyzer::SetKeys(zeek::StringVal* nkeys) { keys.clear(); - keys.reserve(len); - std::copy(data, data + len, std::back_inserter(keys)); + keys.reserve(nkeys->Len()); + std::copy(nkeys->Bytes(), nkeys->Bytes() + nkeys->Len(), std::back_inserter(keys)); } -bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label, const char* rnd1, - size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, - size_t out_len) +void SSL_Analyzer::SetKeys(const std::vector newkeys) + { + keys = newkeys; + } + +std::optional> +SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label, + const std::string& rnd1, const std::string& rnd2, size_t requested_len) { #ifdef OPENSSL_HAVE_KDF_H #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) @@ -178,13 +164,12 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label #endif /* OSSL 3 */ // prepare seed: seed = label + rnd1 + rnd2 - size_t seed_len = label.size() + rnd1_len + rnd2_len; std::string seed{}; - seed.reserve(seed_len); + seed.reserve(label.size() + rnd1.size() + rnd2.size()); seed.append(label); - seed.append(rnd1, rnd1_len); - seed.append(rnd2, rnd2_len); + seed.append(rnd1); + seed.append(rnd2); #if defined(OPENSSL_VERSION_MAJOR) && (OPENSSL_VERSION_MAJOR >= 3) // setup OSSL_PARAM array: digest, secret, seed @@ -196,20 +181,23 @@ bool SSL_Analyzer::TLS12_PRF(const std::string& secret, const std::string& label *p++ = OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SEED, (void*)seed.data(), seed.size()); *p = OSSL_PARAM_construct_end(); + auto keybuf = std::vector(requested_len); + // set OSSL params if ( EVP_KDF_CTX_set_params(kctx, params) <= 0 ) goto abort; // derive key material - if ( EVP_KDF_derive(kctx, out, out_len, NULL) <= 0 ) + if ( EVP_KDF_derive(kctx, keybuf.data(), requested_len, nullptr) <= 0 ) goto abort; EVP_KDF_CTX_free(kctx); - return true; + return keybuf; abort: EVP_KDF_CTX_free(kctx); - return false; + return {}; #else /* OSSL 3 */ + auto keybuf = std::vector(requested_len); if ( EVP_PKEY_derive_init(pctx) <= 0 ) goto abort; /* Error */ // setup PKEY params: digest, secret, seed @@ -220,18 +208,18 @@ abort: goto abort; /* Error */ if ( EVP_PKEY_CTX_add1_tls1_prf_seed(pctx, seed.data(), seed.size()) <= 0 ) goto abort; /* Error */ - if ( EVP_PKEY_derive(pctx, out, &out_len) <= 0 ) + if ( EVP_PKEY_derive(pctx, keybuf.data(), &requested_len) <= 0 ) goto abort; /* Error */ EVP_PKEY_CTX_free(pctx); - return true; + return keubuf; abort: EVP_PKEY_CTX_free(pctx); #endif /* OSSL 3 */ #endif /* HAVE_KDF */ - return false; + return {}; } bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool is_orig, @@ -260,19 +248,19 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i if ( secret.size() != 0 && keys.size() == 0 ) { #ifdef OPENSSL_HAVE_KDF_H - DBG_LOG(DBG_ANALYZER, "Deriving TLS keys for connection foo"); + DBG_LOG(DBG_ANALYZER, "Deriving TLS keys for connection"); uint32_t ts = htonl((uint32_t)handshake_interp->gmt_unix_time()); - char crand[32] = {0x00}; - u_char keybuf[72]; - auto c_rnd = handshake_interp->client_random(); auto s_rnd = handshake_interp->server_random(); - memcpy(crand, &(ts), 4); - memcpy(crand + 4, c_rnd.data(), c_rnd.length()); - auto res = TLS12_PRF(secret, "key expansion", (char*)s_rnd.data(), s_rnd.length(), crand, - sizeof(crand), keybuf, sizeof(keybuf)); + std::string crand; + crand.append(reinterpret_cast(&(ts)), 4); + crand.append(reinterpret_cast(c_rnd.data()), c_rnd.length()); + std::string srand(reinterpret_cast(s_rnd.data()), s_rnd.length()); + + // fixme - 72 should not be hardcoded + auto res = TLS12_PRF(secret, "key expansion", srand, crand, 72); if ( ! res ) { DBG_LOG(DBG_ANALYZER, "TLS PRF failed. Aborting.\n"); @@ -280,10 +268,11 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i } // save derived keys - SetKeys(sizeof(keybuf), keybuf); + SetKeys(res.value()); #else DBG_LOG(DBG_ANALYZER, "Cannot derive TLS keys as Zeek was compiled without "); + return false; #endif } diff --git a/src/analyzer/protocol/ssl/SSL.h b/src/analyzer/protocol/ssl/SSL.h index b96172fc1b..b19bd0b6c0 100644 --- a/src/analyzer/protocol/ssl/SSL.h +++ b/src/analyzer/protocol/ssl/SSL.h @@ -82,7 +82,7 @@ public: * * @param data Pointer to the key buffer as derived via TLS PRF */ - void SetKeys(size_t len, const u_char* data); + void SetKeys(const std::vector newkeys); /** * Try to decrypt TLS application data from a packet. Requires secret or keys to be set prior @@ -122,8 +122,9 @@ public: * * @return True, if the operation completed successfully, false otherwise */ - bool TLS12_PRF(const std::string& secret, const std::string& label, const char* rnd1, - size_t rnd1_len, const char* rnd2, size_t rnd2_len, u_char* out, size_t out_len); + std::optional> TLS12_PRF(const std::string& secret, + const std::string& label, const std::string& rnd1, + const std::string& rnd2, size_t requested_len); /** * Forward decrypted TLS application data to child analyzers From 543c992e6629893c93c97e39be3b555e6f4d9b77 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Tue, 11 Jan 2022 11:20:05 +0000 Subject: [PATCH 21/24] Small code fix and test baseline update. After this, tests hopefully should pass consistently. --- src/analyzer/protocol/ssl/SSL.cc | 2 +- testing/btest/Baseline/coverage.bare-mode-errors/errors | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index 2cc072f0cd..8d580c03fc 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -212,7 +212,7 @@ abort: goto abort; /* Error */ EVP_PKEY_CTX_free(pctx); - return keubuf; + return keybuf; abort: EVP_PKEY_CTX_free(pctx); diff --git a/testing/btest/Baseline/coverage.bare-mode-errors/errors b/testing/btest/Baseline/coverage.bare-mode-errors/errors index 31f9346536..880d44d1ad 100644 --- a/testing/btest/Baseline/coverage.bare-mode-errors/errors +++ b/testing/btest/Baseline/coverage.bare-mode-errors/errors @@ -1,9 +1,9 @@ ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. ### NOTE: This file has been sorted with diff-sort. -warning in <...>/extract-certs-pem.zeek, line 1: deprecated script loaded from <...>/__load__.zeek:12 "Remove in v5.1. Use log-certs-base64.zeek instead." +warning in <...>/extract-certs-pem.zeek, line 1: deprecated script loaded from <...>/__load__.zeek:13 "Remove in v5.1. Use log-certs-base64.zeek instead." warning in <...>/extract-certs-pem.zeek, line 1: deprecated script loaded from command line arguments "Remove in v5.1. Use log-certs-base64.zeek instead." warning in <...>/log-ocsp.zeek, line 1: deprecated script loaded from <...>/test-all-policy.zeek:59 ("Remove in v5.1. OCSP logging is now enabled by default") warning in <...>/log-ocsp.zeek, line 1: deprecated script loaded from <...>/test-all-policy.zeek:59 ("Remove in v5.1. OCSP logging is now enabled by default") warning in <...>/log-ocsp.zeek, line 1: deprecated script loaded from command line arguments ("Remove in v5.1. OCSP logging is now enabled by default") -warning in <...>/notary.zeek, line 1: deprecated script loaded from <...>/__load__.zeek:4 ("Remove in v5.1. Please switch to other more modern approaches like SCT validation (validate-sct.zeek).") +warning in <...>/notary.zeek, line 1: deprecated script loaded from <...>/__load__.zeek:5 ("Remove in v5.1. Please switch to other more modern approaches like SCT validation (validate-sct.zeek).") warning in <...>/notary.zeek, line 1: deprecated script loaded from command line arguments ("Remove in v5.1. Please switch to other more modern approaches like SCT validation (validate-sct.zeek).") From b78f30339fc25bf65d4ae20a6ed4dd4240693066 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Mon, 17 Jan 2022 12:15:40 +0000 Subject: [PATCH 22/24] TLS decryption: refactoring, more comments, less bare pointers This commit refactors TLS decryption, adds more comments in scripts and in C++ source-code, and removes use of bare pointers, instead relying more on stl data types. --- scripts/policy/protocols/ssl/decryption.zeek | 88 ++++++++++------- scripts/test-all-policy.zeek | 2 +- src/analyzer/protocol/ssl/SSL.cc | 99 ++++++++++--------- src/analyzer/protocol/ssl/SSL.h | 57 +++++++---- src/analyzer/protocol/ssl/functions.bif | 43 ++++++-- .../protocol/ssl/ssl-dtls-analyzer.pac | 13 ++- .../policy/protocols/ssl/decryption.zeek | 4 +- 7 files changed, 186 insertions(+), 120 deletions(-) diff --git a/scripts/policy/protocols/ssl/decryption.zeek b/scripts/policy/protocols/ssl/decryption.zeek index 75c1d345b6..81ed2ee33e 100644 --- a/scripts/policy/protocols/ssl/decryption.zeek +++ b/scripts/policy/protocols/ssl/decryption.zeek @@ -1,4 +1,10 @@ -#! Decrypt SSL/TLS payloads +##! This script allows for the decryption of certain TLS 1.2 connection, if the user is in possession +##! of the private key material for the session. Key material can either be provided via a file (useful +##! for processing trace files) or via sending events via broker (for live decoding). +##! +##! Please note that this feature is experimental and can change without guarantees to our typical +##! deprecation tieline. Please also note that currently only TLS 1.2 connections that use the +##! TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 cipher suite are supported. @load base/frameworks/input @load base/frameworks/notice @@ -7,49 +13,69 @@ module SSL; -export { - const keylog_file = getenv("ZEEK_TLS_KEYLOG_FILE") &redef; - - global secrets: table[string] of string = {} &redef; - global keys: table[string] of string = {} &redef; - - global add_keys: event(client_random: string, keys: string); - - global add_secret: event(client_random: string, secret: string); -} - # Do not disable analyzers after detection - otherwise we will not receive # encrypted packets. redef SSL::disable_analyzer_after_detection = F; +export { + ## This can be set to a file that contains the session secrets for decryption, when parsing a pcap file. + ## Please note that, when using this feature, you probably want to pause processing of data till this + ## file has been read. + const keylog_file = getenv("ZEEK_TLS_KEYLOG_FILE") &redef; + + ## Secrets expire after this time of not being used. + const secret_expiration = 5 mins &redef; + + ## This event can be triggered, e.g., via broker to add known keys to the TLS key database. + ## + ## client_random: client random for which the key is set + ## + ## keys: key material + global add_keys: event(client_random: string, keys: string); + + ## This event can be triggered, e.g., via broker to add known secrets to the TLS secret datbase. + ## + ## client_random: client random for which the secret is set + ## + ## secrets: derived TLS secrets material + global add_secret: event(client_random: string, secret: string); +} + +@if ( keylog_file == "" ) +# If a keylog file was given via an environment variable, let's disable secret expiration - that does not +# make sense for pcaps. +global secrets: table[string] of string = {} &redef; +global keys: table[string] of string = {} &redef; +@else +#global secrets: table[string] of string = {} &read_expire=secret_expiration &redef; +#global keys: table[string] of string = {} &read_expire=secret_expiration &redef; +@endif + + redef record SSL::Info += { # Decryption uses client_random as identifier - client_random: string &log &optional; + client_random: string &optional; }; -type Idx: record { +type SecretsIdx: record { client_random: string; }; -type Val: record { +type SecretsVal: record { secret: string; }; -const input_stream_name = "input-tls-keylog-file"; +const tls_decrypt_stream_name = "tls-keylog-file"; event zeek_init() { # listen for secrets Broker::subscribe("/zeek/tls/decryption"); - # FIXME: is such a functionality helpful? - # ingest keylog file if the environment is set if ( keylog_file != "" ) { - suspend_processing(); - - Input::add_table([$name=input_stream_name, $source=keylog_file, $destination=secrets, $idx=Idx, $val=Val, $want_record=F]); - Input::remove(input_stream_name); + Input::add_table([$name=tls_decrypt_stream_name, $source=keylog_file, $destination=secrets, $idx=SecretsIdx, $val=SecretsVal, $want_record=F]); + Input::remove(tls_decrypt_stream_name); } } @@ -66,6 +92,7 @@ event SSL::add_secret(client_random: string, val: string) event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec) { c$ssl$client_random = client_random; + print "Client random!"; if ( client_random in keys ) set_keys(c, keys[client_random]); @@ -73,7 +100,7 @@ event ssl_client_hello(c: connection, version: count, record_version: count, pos set_secret(c, secrets[client_random]); } -event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, content_type: count, length: count) +event ssl_change_cipher_spec(c: connection, is_orig: bool) { if ( c$ssl?$client_random ) { @@ -81,20 +108,5 @@ event ssl_encrypted_data(c: connection, is_orig: bool, record_version: count, co set_keys(c, keys[c$ssl$client_random]); else if ( c$ssl$client_random in secrets ) set_secret(c, secrets[c$ssl$client_random]); - else - { - # FIXME: perhaps report that we could not decrypt the session - } } } - -event SSL::tls_input_done() - { - continue_processing(); - } - -event Input::end_of_data(name: string, source: string) - { - if ( name == input_stream_name ) - event SSL::tls_input_done(); - } diff --git a/scripts/test-all-policy.zeek b/scripts/test-all-policy.zeek index 10a7637422..6b1febf51b 100644 --- a/scripts/test-all-policy.zeek +++ b/scripts/test-all-policy.zeek @@ -116,7 +116,7 @@ @load protocols/ssh/geo-data.zeek @load protocols/ssh/interesting-hostnames.zeek @load protocols/ssh/software.zeek -#@load protocols/ssl/decryption.zeek +@load protocols/ssl/decryption.zeek @load protocols/ssl/expiring-certs.zeek # @load protocols/ssl/extract-certs-pem.zeek @load protocols/ssl/heartbleed.zeek diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index 8d580c03fc..e968ccfef9 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -26,11 +26,14 @@ namespace zeek::analyzer::ssl #define MSB(a) ((a >> 8) & 0xff) #define LSB(a) (a & 0xff) -static void fmt_seq(uint32_t num, u_char* buf) +static std::basic_string fmt_seq(uint32_t num) { - memset(buf, 0, 8); + std::basic_string out(4, '\0'); + out.reserve(13); uint32_t netnum = htonl(num); - memcpy(buf + 4, &netnum, 4); + out.append(reinterpret_cast(&netnum), 4); + out.append(5, '\0'); + return out; } SSL_Analyzer::SSL_Analyzer(Connection* c) : analyzer::tcp::TCP_ApplicationAnalyzer("SSL", c) @@ -124,9 +127,9 @@ void SSL_Analyzer::Undelivered(uint64_t seq, int len, bool orig) interp->NewGap(orig, len); } -void SSL_Analyzer::SetSecret(zeek::StringVal* secret) +void SSL_Analyzer::SetSecret(const zeek::StringVal& secret) { - SetSecret(secret->Len(), secret->Bytes()); + SetSecret(secret.Len(), secret.Bytes()); } void SSL_Analyzer::SetSecret(size_t len, const u_char* data) @@ -135,11 +138,11 @@ void SSL_Analyzer::SetSecret(size_t len, const u_char* data) secret.append((const char*)data, len); } -void SSL_Analyzer::SetKeys(zeek::StringVal* nkeys) +void SSL_Analyzer::SetKeys(const zeek::StringVal& nkeys) { keys.clear(); - keys.reserve(nkeys->Len()); - std::copy(nkeys->Bytes(), nkeys->Bytes() + nkeys->Len(), std::back_inserter(keys)); + keys.reserve(nkeys.Len()); + std::copy(nkeys.Bytes(), nkeys.Bytes() + nkeys.Len(), std::back_inserter(keys)); } void SSL_Analyzer::SetKeys(const std::vector newkeys) @@ -282,40 +285,38 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i // FIXME: could also print keys or conn id here DBG_LOG(DBG_ANALYZER, "Decrypting application data"); + // NOTE: you must not call functions that invalidate keys.data() on keys during the + // remainder of this function. (Given that we do not manipulate the key material in this + // function that should not be hard) + // client write_key - u_char c_wk[32]; + const u_char* c_wk = keys.data(); // server write_key - u_char s_wk[32]; + const u_char* s_wk = keys.data() + 32; // client IV - u_char c_iv[4]; + const u_char* c_iv = keys.data() + 64; // server IV - u_char s_iv[4]; - - // AEAD nonce & tag - u_char s_aead_nonce[12]; - u_char s_aead_tag[13]; - - // FIXME: there should be a better way to do these copies - memcpy(c_wk, keys.data(), 32); - memcpy(s_wk, keys.data() + 32, 32); - memcpy(c_iv, keys.data() + 64, 4); - memcpy(s_iv, keys.data() + 68, 4); + const u_char* s_iv = keys.data() + 68; // FIXME: should we change types here? u_char* encrypted = (u_char*)data; size_t encrypted_len = len; - // FIXME: should this be moved to SSL_Conn? if ( is_orig ) c_seq++; else s_seq++; + // AEAD nonce, length 12 + std::basic_string s_aead_nonce; if ( is_orig ) - memcpy(s_aead_nonce, c_iv, 4); + s_aead_nonce.assign(c_iv, 4); else - memcpy(s_aead_nonce, s_iv, 4); - memcpy(&(s_aead_nonce[4]), encrypted, 8); + s_aead_nonce.assign(s_iv, 4); + + // this should be the explicit counter + s_aead_nonce.append(encrypted, 8); + assert(s_aead_nonce.size() == 12); EVP_CIPHER_CTX* ctx = EVP_CIPHER_CTX_new(); EVP_CIPHER_CTX_init(ctx); @@ -323,47 +324,60 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i encrypted += 8; // FIXME: is this because of nonce and aead tag? + if ( encrypted_len <= (16 + 8) ) + { + DBG_LOG(DBG_ANALYZER, "Invalid encrypted length encountered during TLS decryption"); + EVP_CIPHER_CTX_free(ctx); + return false; + } encrypted_len -= 8; encrypted_len -= 16; // FIXME: aes_256_gcm should not be hardcoded here ;) if ( is_orig ) - EVP_DecryptInit(ctx, EVP_aes_256_gcm(), c_wk, s_aead_nonce); + EVP_DecryptInit(ctx, EVP_aes_256_gcm(), c_wk, s_aead_nonce.data()); else - EVP_DecryptInit(ctx, EVP_aes_256_gcm(), s_wk, s_aead_nonce); + EVP_DecryptInit(ctx, EVP_aes_256_gcm(), s_wk, s_aead_nonce.data()); + EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, 16, encrypted + encrypted_len); + // AEAD tag + std::basic_string s_aead_tag; if ( is_orig ) - fmt_seq(c_seq, s_aead_tag); + s_aead_tag = fmt_seq(c_seq); else - fmt_seq(s_seq, s_aead_tag); + s_aead_tag = fmt_seq(s_seq); s_aead_tag[8] = content_type; s_aead_tag[9] = MSB(raw_tls_version); s_aead_tag[10] = LSB(raw_tls_version); s_aead_tag[11] = MSB(encrypted_len); s_aead_tag[12] = LSB(encrypted_len); + assert(s_aead_tag.size() == 13); - u_char* decrypted = new u_char[encrypted_len]; + auto decrypted = std::vector( + encrypted_len + + 16); // see OpenSSL manpage - 16 is the block size for the supported cipher int decrypted_len = 0; - EVP_DecryptUpdate(ctx, NULL, &decrypted_len, s_aead_tag, 13); - EVP_DecryptUpdate(ctx, decrypted, &decrypted_len, (const u_char*)encrypted, encrypted_len); + EVP_DecryptUpdate(ctx, NULL, &decrypted_len, s_aead_tag.data(), s_aead_tag.size()); + EVP_DecryptUpdate(ctx, decrypted.data(), &decrypted_len, (const u_char*)encrypted, + encrypted_len); + assert(decrypted_len <= decrypted.size()); + decrypted.resize(decrypted_len); int res = 0; if ( ! (res = EVP_DecryptFinal(ctx, NULL, &res)) ) { DBG_LOG(DBG_ANALYZER, "Decryption failed with return code: %d. Invalid key?\n", res); EVP_CIPHER_CTX_free(ctx); - delete[] decrypted; return false; } DBG_LOG(DBG_ANALYZER, "Successfully decrypted %d bytes.", decrypted_len); EVP_CIPHER_CTX_free(ctx); - ForwardDecryptedData(decrypted_len, reinterpret_cast(decrypted), is_orig); + ForwardDecryptedData(decrypted, is_orig); - delete[] decrypted; return true; } @@ -371,7 +385,7 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i return false; } -void SSL_Analyzer::ForwardDecryptedData(int len, const u_char* data, bool is_orig) +void SSL_Analyzer::ForwardDecryptedData(const std::vector& data, bool is_orig) { if ( ! pia ) { @@ -383,18 +397,9 @@ void SSL_Analyzer::ForwardDecryptedData(int len, const u_char* data, bool is_ori } else reporter->FatalError("Could not initialize PIA"); - - // FIXME: Also statically add HTTP/H2 at the moment. - // We should move this bit to scriptland - auto http = analyzer_mgr->InstantiateAnalyzer("HTTP", Conn()); - if ( http ) - AddChildAnalyzer(http); - auto http2 = analyzer_mgr->InstantiateAnalyzer("HTTP2", Conn()); - if ( http2 ) - AddChildAnalyzer(http2); } - ForwardStream(len, data, is_orig); + ForwardStream(data.size(), data.data(), is_orig); } } // namespace zeek::analyzer::ssl diff --git a/src/analyzer/protocol/ssl/SSL.h b/src/analyzer/protocol/ssl/SSL.h index b19bd0b6c0..415dcd9129 100644 --- a/src/analyzer/protocol/ssl/SSL.h +++ b/src/analyzer/protocol/ssl/SSL.h @@ -25,6 +25,9 @@ namespace zeek::analyzer::ssl class SSL_Analyzer final : public analyzer::tcp::TCP_ApplicationAnalyzer { + // let binpac forward encryppted TLS application data to us. + friend class binpac::SSL::SSL_Conn; + public: explicit SSL_Analyzer(Connection* conn); ~SSL_Analyzer() override; @@ -50,14 +53,20 @@ public: * Set the secret that should be used to derive keys for the * connection. (For TLS 1.2 this is the pre-master secret) * + * Please note that these functions currently are hardcoded to only work with a single TLS 1.2 + * cuphersuite (TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). + * * @param secret The secret to set */ - void SetSecret(StringVal* secret); + void SetSecret(const StringVal& secret); /** * Set the secret that should be used to derive keys for the * connection. (For TLS 1.2 this is the pre-master secret) * + * Please note that these functions currently are hardcoded to only work with a single TLS 1.2 + * cuphersuite (TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). + * * @param len Length of the secret bytes * * @param data Pointer to the secret bytes @@ -68,24 +77,32 @@ public: * Set the decryption keys that should be used to decrypt * TLS application data in the connection. * + * Please note that these functions currently are hardcoded to only work with a single TLS 1.2 + * cuphersuite (TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). + * * @param keys The key buffer as derived via TLS PRF (for * AES_GCM this should be 72 bytes in length) */ - void SetKeys(StringVal* keys); + void SetKeys(const StringVal& keys); /** * Set the decryption keys that should be used to decrypt * TLS application data in the connection. * - * @param len Length of the key buffer (for AES_GCM this should - * be 72) + * Please note that these functions currently are hardcoded to only work with a single TLS 1.2 + * cuphersuite (TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). * - * @param data Pointer to the key buffer as derived via TLS PRF + * @param keys The key buffer as derived via TLS PRF (for + * AES_GCM this should be 72 bytes in length) */ void SetKeys(const std::vector newkeys); +protected: /** - * Try to decrypt TLS application data from a packet. Requires secret or keys to be set prior + * Try to decrypt TLS application data from a packet. Requires secret or keys to be set prior. + * + * Please note that these functions currently are hardcoded to only work with a single TLS 1.2 + * cuphersuite (TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). * * @param len Length of the encrypted bytes to decrypt * @@ -96,48 +113,46 @@ public: * @param content_type Content type as given in the TLS packet * * @param raw_tls_version Raw TLS version as given in the TLS packets + * + * @return True if decryption succeeded and data was forwarded. */ bool TryDecryptApplicationData(int len, const u_char* data, bool is_orig, uint8_t content_type, uint16_t raw_tls_version); /** * TLS 1.2 pseudo random function (PRF) used to expand the pre-master secret and derive keys. - * The seed is obtained by concatinating rnd1 and rnd2 + * The seed is obtained by concatinating rnd1 and rnd2. + * + * Please note that these functions currently are hardcoded to only work with a single TLS 1.2 + * cuphersuite (TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). * * @param secret Secret as defined in the TLS RFC * * @param label Label as defined in the TLS RFC * - * @param rnd1 Pointer to the first part of the seed + * @param First part of the seed * - * @param rnd1_len Length of the first part of the seed - * - * @param rnd2 Pointer to the second part of the seed + * @param rnd2 Second part of the seed * * @param rnd2_len Length of the second part of the seed * - * @param out Pointer to the derived bytes + * @param requested_len Length indicating how many bytes should be derived * - * @param out_len Length indicating how many bytes should be derived - * - * @return True, if the operation completed successfully, false otherwise + * @return The derived bytes, if the operation succeeds. */ std::optional> TLS12_PRF(const std::string& secret, const std::string& label, const std::string& rnd1, const std::string& rnd2, size_t requested_len); /** - * Forward decrypted TLS application data to child analyzers + * Forward decrypted TLS application data to child analyzers. * - * @param len Length of the data to forward - * - * @param data Pointer to the data to forward + * @param data Data to forward * * @param is_orig Direction of the connection */ - void ForwardDecryptedData(int len, const u_char* data, bool is_orig); + void ForwardDecryptedData(const std::vector& data, bool is_orig); -protected: binpac::SSL::SSL_Conn* interp; binpac::TLSHandshake::Handshake_Conn* handshake_interp; bool had_gap; diff --git a/src/analyzer/protocol/ssl/functions.bif b/src/analyzer/protocol/ssl/functions.bif index 19661ad4f3..75e4d3c26a 100644 --- a/src/analyzer/protocol/ssl/functions.bif +++ b/src/analyzer/protocol/ssl/functions.bif @@ -9,32 +9,59 @@ ## finished succesfully). ## ## c: The SSL connection. -function set_ssl_established%(c: connection%): any +## +## Returns: T on success, F on failure. +function set_ssl_established%(c: connection%): bool %{ zeek::analyzer::Analyzer* sa = c->FindAnalyzer("SSL"); + if ( sa ) + { static_cast(sa)->StartEncryption(); - return nullptr; + return zeek::val_mgr->True(); + } + + return zeek::val_mgr->False(); %} +## Set the secret that should be used to derive keys for the connection. +## (For TLS 1.2 this is the pre-master secret). +## +## c: The affected connection +## +## secret: secret to set +## +## Returns: T on success, F on failure. function set_secret%(c: connection, secret: string%): bool %{ analyzer::Analyzer* sa = c->FindAnalyzer("SSL"); + if ( sa ) - { - static_cast(sa)->SetSecret(secret); + { + static_cast(sa)->SetSecret(*secret); return zeek::val_mgr->True(); - } + } + return zeek::val_mgr->False(); %} +## Set the decryption keys that should be used to decrypt +## TLS application data in the connection. +## +## c: The affected connection +## +## keys: The key buffer as derived via TLS PRF. +## +## Returns: T on success, F on failure. function set_keys%(c: connection, keys: string%): bool %{ analyzer::Analyzer* sa = c->FindAnalyzer("SSL"); + if ( sa ) - { - static_cast(sa)->SetKeys(keys); + { + static_cast(sa)->SetKeys(*keys); return zeek::val_mgr->True(); - } + } + return zeek::val_mgr->False(); %} diff --git a/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac b/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac index 1d97ad7e1a..32849bd910 100644 --- a/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac @@ -13,10 +13,12 @@ refine connection SSL_Conn += { %member{ int established_; + int decryption_failed_; %} %init{ established_ = false; + decryption_failed_ = false; %} %cleanup{ @@ -65,10 +67,13 @@ refine connection SSL_Conn += { { zeek::BifEvent::enqueue_ssl_encrypted_data(zeek_analyzer(), zeek_analyzer()->Conn(), ${rec.is_orig}, ${rec.raw_tls_version}, ${rec.content_type}, ${rec.length}); - if (rec->content_type() == APPLICATION_DATA) - { - zeek_analyzer()->TryDecryptApplicationData(cont.length(), cont.data(), rec->is_orig(), rec->content_type(), rec->raw_tls_version()); - } + } + + if ( rec->content_type() == APPLICATION_DATA && decryption_failed_ == false ) + { + // If decryption of one packet fails, do not try to decrypt future packets. + if ( ! zeek_analyzer()->TryDecryptApplicationData(cont.length(), cont.data(), rec->is_orig(), rec->content_type(), rec->raw_tls_version()) ) + decryption_failed_ = true; } return true; diff --git a/testing/btest/scripts/policy/protocols/ssl/decryption.zeek b/testing/btest/scripts/policy/protocols/ssl/decryption.zeek index 741284a540..71dea5e41d 100644 --- a/testing/btest/scripts/policy/protocols/ssl/decryption.zeek +++ b/testing/btest/scripts/policy/protocols/ssl/decryption.zeek @@ -1,11 +1,13 @@ # @TEST-REQUIRES: grep -q "#define OPENSSL_HAVE_KDF_H" $BUILD/zeek-config.h -# @TEST-EXEC: zeek -b -C -r $TRACES/tls/tls12-decryption.pcap %INPUT +# @TEST-EXEC: zeek -B dpd -C -r $TRACES/tls/tls12-decryption.pcap %INPUT # @TEST-EXEC: btest-diff http.log @load protocols/ssl/decryption @load base/protocols/http +module SSL; + redef SSL::secrets += { ["\xb4\x0a\x24\x4b\x48\xe4\x2e\xac\x28\x71\x44\xb1\xb7\x39\x30\x57\xca\xa1\x31\xf9\x61\xa7\x8e\x38\xb0\xe7\x7c\x1e"] = "\xbd\x01\xe5\x89\xd1\x05\x19\x9e\x9a\xb5\xfc\x9b\xd7\x58\xb5\xf2\x88\xdb\x28\xfd\x80\xaa\x02\x26\x1e\x47\x65\xac\x13\x57\xd0\x07\xfd\x08\xc7\xbd\xab\x45\x45\x0e\x01\x5a\x01\xd0\x8e\x5e\x7c\xa6", }; From 1c9ea09d9f83be1d709c40ecdd7f3dae25dfb33b Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Wed, 23 Feb 2022 11:31:21 +0000 Subject: [PATCH 23/24] Address PR feedback This addresses feedback to GH-1814. The most significant change is the fact that the ChipertextRecord now can remain &transient - which might lead to improved speed. --- scripts/policy/protocols/ssl/decryption.zeek | 16 ++++++++-------- src/analyzer/protocol/ssl/SSL.cc | 15 +++++++++++---- src/analyzer/protocol/ssl/SSL.h | 4 ++-- src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac | 4 ++-- src/analyzer/protocol/ssl/ssl-dtls-protocol.pac | 2 +- 5 files changed, 24 insertions(+), 17 deletions(-) diff --git a/scripts/policy/protocols/ssl/decryption.zeek b/scripts/policy/protocols/ssl/decryption.zeek index 81ed2ee33e..3440b11f36 100644 --- a/scripts/policy/protocols/ssl/decryption.zeek +++ b/scripts/policy/protocols/ssl/decryption.zeek @@ -1,9 +1,9 @@ -##! This script allows for the decryption of certain TLS 1.2 connection, if the user is in possession +##! This script allows for the decryption of certain TLS 1.2 connections, if the user is in possession ##! of the private key material for the session. Key material can either be provided via a file (useful -##! for processing trace files) or via sending events via broker (for live decoding). +##! for processing trace files) or via sending events via Broker (for live decoding). ##! ##! Please note that this feature is experimental and can change without guarantees to our typical -##! deprecation tieline. Please also note that currently only TLS 1.2 connections that use the +##! deprecation timeline. Please also note that currently only TLS 1.2 connections that use the ##! TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 cipher suite are supported. @load base/frameworks/input @@ -26,19 +26,19 @@ export { ## Secrets expire after this time of not being used. const secret_expiration = 5 mins &redef; - ## This event can be triggered, e.g., via broker to add known keys to the TLS key database. + ## This event can be triggered, e.g., via Broker to add known keys to the TLS key database. ## ## client_random: client random for which the key is set ## ## keys: key material global add_keys: event(client_random: string, keys: string); - ## This event can be triggered, e.g., via broker to add known secrets to the TLS secret datbase. + ## This event can be triggered, e.g., via Broker to add known secrets to the TLS secret datbase. ## ## client_random: client random for which the secret is set ## ## secrets: derived TLS secrets material - global add_secret: event(client_random: string, secret: string); + global add_secret: event(client_random: string, secrets: string); } @if ( keylog_file == "" ) @@ -47,8 +47,8 @@ export { global secrets: table[string] of string = {} &redef; global keys: table[string] of string = {} &redef; @else -#global secrets: table[string] of string = {} &read_expire=secret_expiration &redef; -#global keys: table[string] of string = {} &read_expire=secret_expiration &redef; +global secrets: table[string] of string = {} &read_expire=secret_expiration &redef; +global keys: table[string] of string = {} &read_expire=secret_expiration &redef; @endif diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index e968ccfef9..37ef7286cc 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -23,8 +23,15 @@ namespace zeek::analyzer::ssl { -#define MSB(a) ((a >> 8) & 0xff) -#define LSB(a) (a & 0xff) +template static inline T MSB(const T a) + { + return ((a >> 8) & 0xff); + } + +template static inline T LSB(const T a) + { + return (a & 0xff); + } static std::basic_string fmt_seq(uint32_t num) { @@ -147,7 +154,7 @@ void SSL_Analyzer::SetKeys(const zeek::StringVal& nkeys) void SSL_Analyzer::SetKeys(const std::vector newkeys) { - keys = newkeys; + keys = std::move(newkeys); } std::optional> @@ -396,7 +403,7 @@ void SSL_Analyzer::ForwardDecryptedData(const std::vector& data, bool is pia->FirstPacket(false, nullptr); } else - reporter->FatalError("Could not initialize PIA"); + reporter->Error("Could not initialize PIA"); } ForwardStream(data.size(), data.data(), is_orig); diff --git a/src/analyzer/protocol/ssl/SSL.h b/src/analyzer/protocol/ssl/SSL.h index 415dcd9129..5fdbc27dca 100644 --- a/src/analyzer/protocol/ssl/SSL.h +++ b/src/analyzer/protocol/ssl/SSL.h @@ -25,7 +25,7 @@ namespace zeek::analyzer::ssl class SSL_Analyzer final : public analyzer::tcp::TCP_ApplicationAnalyzer { - // let binpac forward encryppted TLS application data to us. + // let binpac forward encrypted TLS application data to us. friend class binpac::SSL::SSL_Conn; public: @@ -54,7 +54,7 @@ public: * connection. (For TLS 1.2 this is the pre-master secret) * * Please note that these functions currently are hardcoded to only work with a single TLS 1.2 - * cuphersuite (TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). + * ciphersuite (TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384). * * @param secret The secret to set */ diff --git a/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac b/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac index 32849bd910..8ddecbe318 100644 --- a/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac +++ b/src/analyzer/protocol/ssl/ssl-dtls-analyzer.pac @@ -45,7 +45,7 @@ refine connection SSL_Conn += { return true; %} - function proc_ciphertext_record(rec : SSLRecord, cont: bytestring) : bool + function proc_ciphertext_record(rec : SSLRecord, cont: const_bytestring) : bool %{ if ( established_ == false && determine_tls13() == 1 ) { @@ -72,7 +72,7 @@ refine connection SSL_Conn += { if ( rec->content_type() == APPLICATION_DATA && decryption_failed_ == false ) { // If decryption of one packet fails, do not try to decrypt future packets. - if ( ! zeek_analyzer()->TryDecryptApplicationData(cont.length(), cont.data(), rec->is_orig(), rec->content_type(), rec->raw_tls_version()) ) + if ( ! zeek_analyzer()->TryDecryptApplicationData(cont.length(), cont.begin(), rec->is_orig(), rec->content_type(), rec->raw_tls_version()) ) decryption_failed_ = true; } diff --git a/src/analyzer/protocol/ssl/ssl-dtls-protocol.pac b/src/analyzer/protocol/ssl/ssl-dtls-protocol.pac index 156c1bf6f4..ad2b869ae8 100644 --- a/src/analyzer/protocol/ssl/ssl-dtls-protocol.pac +++ b/src/analyzer/protocol/ssl/ssl-dtls-protocol.pac @@ -96,7 +96,7 @@ type UnknownRecord(rec: SSLRecord) = record { }; type CiphertextRecord(rec: SSLRecord) = record { - cont : bytestring &restofdata; + cont : bytestring &restofdata &transient; }; ###################################################################### From 590d4aa13e56424f591a2982415d0f2f3851b2ce Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Tue, 1 Mar 2022 17:45:11 +0000 Subject: [PATCH 24/24] TLS decryption: add test, fix small issues Add a test loading keys from an external file. Make some debug messages slightly better and remove unnecessary debug output. --- scripts/policy/protocols/ssl/decryption.zeek | 1 - src/analyzer/protocol/ssl/SSL.cc | 8 +++- .../protocol/ssl/tls-handshake-protocol.pac | 3 -- .../Traces/tls/tls-1.2-stream-keylog.pcap | Bin 0 -> 182026 bytes .../protocols/ssl/decryption-keylog.zeek | 42 ++++++++++++++++++ 5 files changed, 48 insertions(+), 6 deletions(-) create mode 100644 testing/btest/Traces/tls/tls-1.2-stream-keylog.pcap create mode 100644 testing/btest/scripts/policy/protocols/ssl/decryption-keylog.zeek diff --git a/scripts/policy/protocols/ssl/decryption.zeek b/scripts/policy/protocols/ssl/decryption.zeek index 3440b11f36..b9be06ca89 100644 --- a/scripts/policy/protocols/ssl/decryption.zeek +++ b/scripts/policy/protocols/ssl/decryption.zeek @@ -92,7 +92,6 @@ event SSL::add_secret(client_random: string, val: string) event ssl_client_hello(c: connection, version: count, record_version: count, possible_ts: time, client_random: string, session_id: string, ciphers: index_vec, comp_methods: index_vec) { c$ssl$client_random = client_random; - print "Client random!"; if ( client_random in keys ) set_keys(c, keys[client_random]); diff --git a/src/analyzer/protocol/ssl/SSL.cc b/src/analyzer/protocol/ssl/SSL.cc index 37ef7286cc..2d8d194e59 100644 --- a/src/analyzer/protocol/ssl/SSL.cc +++ b/src/analyzer/protocol/ssl/SSL.cc @@ -240,14 +240,18 @@ bool SSL_Analyzer::TryDecryptApplicationData(int len, const u_char* data, bool i auto cipher = handshake_interp->chosen_cipher(); if ( cipher != 0xC030 ) { - DBG_LOG(DBG_ANALYZER, "Unsupported cipher suite: %d\n", cipher); + DBG_LOG(DBG_ANALYZER, "Unsupported cipher suite for decryption: %d\n", cipher); return false; } // Neither secret or key present: abort if ( secret.size() == 0 && keys.size() == 0 ) { - DBG_LOG(DBG_ANALYZER, "Could not decrypt packet due to missing keys/secret.\n"); + DBG_LOG( + DBG_ANALYZER, + "Could not decrypt packet due to missing keys/secret. Client_random: %s\n", + util::fmt_bytes(reinterpret_cast(handshake_interp->client_random().data()), + handshake_interp->client_random().length())); // FIXME: change util function to return a printably std::string for DBG_LOG // print_hex("->client_random:", handshake_interp->client_random().data(), // handshake_interp->client_random().size()); diff --git a/src/analyzer/protocol/ssl/tls-handshake-protocol.pac b/src/analyzer/protocol/ssl/tls-handshake-protocol.pac index b42f292720..357bd77909 100644 --- a/src/analyzer/protocol/ssl/tls-handshake-protocol.pac +++ b/src/analyzer/protocol/ssl/tls-handshake-protocol.pac @@ -953,9 +953,6 @@ refine connection Handshake_Conn += { chosen_version_ = UNKNOWN_VERSION; record_version_ = 0; - // FIXME: How should bytestrings be initialized? - // client_random_ = ?? - // server_random_ = ?? gmt_unix_time_ = 0; %} diff --git a/testing/btest/Traces/tls/tls-1.2-stream-keylog.pcap b/testing/btest/Traces/tls/tls-1.2-stream-keylog.pcap new file mode 100644 index 0000000000000000000000000000000000000000..bbebc0a3e88b156d4689b04c959a1fea9e3df976 GIT binary patch literal 182026 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

MV3gAAHUF*5w^P4r>?I6Cp_UcuM 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 Z6EVZl5V+ywBU_OUPB+@a-FOZZjsW1#xLE)I literal 0 HcmV?d00001 diff --git a/testing/btest/scripts/policy/protocols/ssl/decryption-keylog.zeek b/testing/btest/scripts/policy/protocols/ssl/decryption-keylog.zeek new file mode 100644 index 0000000000..70e2baf5c4 --- /dev/null +++ b/testing/btest/scripts/policy/protocols/ssl/decryption-keylog.zeek @@ -0,0 +1,42 @@ +# @TEST-REQUIRES: grep -q "#define OPENSSL_HAVE_KDF_H" $BUILD/zeek-config.h + +# @TEST-EXEC: ZEEK_TLS_KEYLOG_FILE=keylogfile.log zeek -B dpd -C -r $TRACES/tls/tls-1.2-stream-keylog.pcap %INPUT + +@TEST-START-FILE keylogfile.log +#fields client_random secret +\x0e\x78\x2d\x35\x63\x95\x5d\x8a\x30\xa9\xcf\xb6\x4f\x47\xf3\x96\x34\x8a\x1e\x79\x1a\xa2\x32\x55\xe2\x2f\xc5\x7a \x34\x4f\x12\x65\xbf\x43\x40\xb3\x61\x6b\xa0\x16\x5d\x2b\x4d\xb9\xb1\xe8\x4a\x3d\xa2\x42\x0e\x38\xab\x01\x50\x62\x84\xcc\x34\xcd\xe0\x34\x10\xfe\x1a\x02\x30\x49\x74\x6c\x46\x43\xa7\x0c\x67\x0d +\x24\x8c\x7e\x24\xee\xfb\x13\xcd\xee\xde\xb1\xf4\xb6\xd6\xd5\xee\x67\x8d\xd3\xff\xc7\xe7\x39\x23\x18\x3f\x99\xb4 \xe7\xed\x24\x26\x0d\x25\xd9\xfd\xf5\x0f\xc0\xf4\x56\x51\x0e\x4e\xec\x7f\x58\x9c\xaf\x39\x25\x14\x16\xa6\x71\xdd\xea\xfe\xe9\xc0\x93\xbe\x89\x4c\xab\xcc\xff\xb2\xf0\x9a\xea\x98\xf5\xb2\x53\x1e +\x57\xd7\xc7\x7a\x2d\x5e\x35\x29\x2c\xd7\xe7\x94\xee\xf8\x6f\x31\x45\xf6\xbe\x25\x08\xed\x1d\x92\xd2\x0b\x9b\x04 \xc1\x93\x17\x93\xd9\x7d\xd2\x98\xb3\xe0\xdb\x2c\x5d\xbe\x71\x31\xa7\x9a\xf5\x91\xf9\x87\x90\xee\xb7\x79\x9f\x6b\xb4\x1f\x47\xa7\x69\x62\x4b\xa3\x99\x0c\xa9\x43\xf9\xea\x3b\x4d\x5f\x2f\xfe\xfb +\x30\xd7\xb8\x92\xc1\xec\x17\x90\x5b\x0f\xcb\xda\xe6\x42\xb2\x09\x4c\xdd\x7d\x2e\xa1\x9f\x1a\x3b\x70\x23\x7d\xf2 \xc1\x93\x17\x93\xd9\x7d\xd2\x98\xb3\xe0\xdb\x2c\x5d\xbe\x71\x31\xa7\x9a\xf5\x91\xf9\x87\x90\xee\xb7\x79\x9f\x6b\xb4\x1f\x47\xa7\x69\x62\x4b\xa3\x99\x0c\xa9\x43\xf9\xea\x3b\x4d\x5f\x2f\xfe\xfb +\x49\xc7\x71\x25\xdc\xb0\xa7\xbc\xd6\xb6\x67\x5c\x30\x58\x8d\xad\x47\x5a\x93\x60\xac\xa5\x78\xf5\x62\x7e\xff\x62 \xc1\x93\x17\x93\xd9\x7d\xd2\x98\xb3\xe0\xdb\x2c\x5d\xbe\x71\x31\xa7\x9a\xf5\x91\xf9\x87\x90\xee\xb7\x79\x9f\x6b\xb4\x1f\x47\xa7\x69\x62\x4b\xa3\x99\x0c\xa9\x43\xf9\xea\x3b\x4d\x5f\x2f\xfe\xfb +\x38\x1c\x49\xcc\xf9\x62\xd0\x5c\xf0\xd4\xe2\xd5\xa1\x15\xc1\x5e\x8d\x02\xcc\x50\xed\x6c\x90\x63\x73\x9d\xfb\x96 \xdc\xf5\xfc\x10\xf2\xb3\x8b\xd8\x87\xae\xcf\xb5\xcd\x1a\xe3\xa8\x06\x8e\x85\xfc\xbb\xfc\x22\xec\x0f\x79\x99\x04\x13\x5b\x6b\x03\x52\x02\xee\xe9\x04\x59\x78\x44\xf1\xf3\xc8\xac\x22\x68\x6c\x7e +\x61\x9e\x08\x51\xee\x36\x3c\x2c\xf3\x71\x87\x22\x82\x27\xca\x4e\x68\x0f\x9a\x7c\x0b\xd1\x50\x69\xaa\x7a\x59\x70 \xad\x03\xce\xda\x48\x90\xfa\x58\x1e\x98\x9f\x5e\x38\x62\x02\x3e\x2a\x4e\x3e\x8a\xd8\x13\x25\x23\x8d\x90\x80\x66\xe1\xd3\x5c\xc8\x75\x97\x9e\x34\xc0\x8e\x6f\xdf\xd9\xd8\xc6\xf3\x56\xe3\x85\xc1 +\xcb\x3f\x93\xd2\x55\xcb\xb6\x56\x25\x87\xf0\xdd\x01\x02\x12\xfd\xee\x9d\x23\x3a\xff\x64\xe6\xed\x36\xcd\x5c\x45 \x0d\x36\xfa\xaa\x2e\xad\xbd\xa2\xa8\x09\x5f\x95\x1d\xe1\xcb\xac\x46\xb8\x1b\x00\x8f\xbf\x39\x1d\x91\x95\x1b\x34\x85\x47\x6b\xab\x73\x28\x8a\x1e\x17\xcd\x0c\xe8\x0e\x0f\xc0\x40\x1d\xbe\x9e\x3f +\xf9\x7e\x7d\x38\x56\xe2\xfc\xcb\xbe\x80\x79\x8e\xc2\xe3\xf5\x15\x25\x10\x82\xad\x63\xbb\xc7\xc2\x31\xd8\xbe\xe0 \x9a\x7c\xf9\x46\xa0\x47\x18\xa1\x9f\x4d\x20\xc3\xf8\x0c\x1c\xf8\xc8\x23\xc3\xe2\xb1\xc3\x37\xef\x64\x32\x2d\x75\x1b\x41\x05\x43\x31\x5f\x6e\xcf\x7d\xbf\x45\xec\x9b\xe1\x94\xa3\xcc\x7c\x1a\x0f +\x57\x97\x63\x67\xf2\xea\x9c\x95\x46\x7a\x6c\xc5\x59\xda\x6f\xeb\xbc\x44\x2e\x11\x3a\xc5\xea\xa7\xed\x97\xad\x38 \x0e\x5e\xc0\x6c\xa5\x4e\xe3\x86\x05\x5a\xaa\x97\x1c\x7e\x09\x39\xba\x3e\x1f\xb1\x62\x4d\x0a\x5b\x9c\x0c\xae\x97\x5f\x0e\x25\xbc\x4c\x51\x21\xfa\x34\x5e\xa1\x26\x47\xc4\x7a\x5a\x1c\xe5\xbd\xce +\x70\x18\x17\x27\xd6\xe2\x04\xd1\xd8\xa5\xb8\x2a\x05\x01\xaf\x7b\x13\x6d\x3a\x9c\x56\x6c\x32\x5b\x3f\xef\xb5\x04 \x92\x3d\x8a\x93\xba\xc5\x54\xc1\x04\x9a\x8d\xeb\x63\x28\x8c\xd7\x4d\x60\x51\xb0\x7a\x10\x67\x84\x8d\xac\x15\xc8\x75\xf2\x5c\x2a\x60\xe3\x38\xde\xb3\x27\x37\x44\xb1\x53\xe6\x9d\x42\x06\x0e\x18 +\x4f\x12\x67\xb1\x13\xdc\x1a\x3e\x5d\xee\xbf\xff\xa7\x4d\xaa\xa1\x96\xff\x43\x0a\x30\xbe\x04\x07\x60\x29\x5f\x5e \x1d\x61\x52\xa6\x1e\x86\x75\x53\x04\xb8\x8e\x12\x6f\xdb\xa4\x49\x05\xeb\x5e\x4b\x33\xf6\xaf\xee\x67\x20\x37\xfd\x84\x48\x9a\xaa\x62\xa6\xb2\x64\x0f\x62\x87\x12\xe8\x05\x98\xae\x0c\xbf\xae\x5f +\xfe\x13\x61\x60\x80\x41\x0b\x9d\xc2\xcc\xc2\xc3\x00\xab\x20\x6b\xb8\x43\xc4\xc4\x22\x81\x1f\x15\xd4\xed\x34\xc3 \x39\xfb\x4d\x9c\x1d\xff\x4d\xe4\x1c\x86\xf9\x67\x9b\x32\xca\xa3\x99\x9c\x91\xcd\x7a\xf5\x4d\xc5\x58\x98\x1c\xcf\xf6\xd9\xa7\x4c\x92\x6e\x93\x7f\x98\x02\x96\x22\x20\x52\x5e\x9d\xe0\xec\x4a\xc1 +\x92\xc2\x33\xdd\xf3\xf4\x31\xd6\x0c\x9b\x90\x86\x6a\xde\x5d\x80\x32\x22\xb8\x18\x45\xf5\x11\x72\xa0\x4f\xe9\x65 \xda\x22\x06\x86\xef\x25\x99\xb4\x65\x2c\x45\x94\x73\xcd\xe9\xc6\x64\x55\x84\x21\x42\x35\x86\x57\x9a\x60\xd4\xc7\x88\xd8\x1b\x3a\xbe\xdf\x53\x7b\xd7\x9c\xf9\x29\x47\x05\x07\x0f\x23\x3b\x22\xc4 +\x39\x8e\xeb\xdf\x69\xd9\xe3\xe2\xce\xd8\xe9\xb2\x93\xa6\xb7\x58\x30\x9b\xaf\x14\x98\xbd\x27\xa0\xe1\x12\x54\x3f \xa9\xcc\x51\xa6\x83\xf1\xbb\x6b\x37\xf0\xe2\x8b\xa5\xea\x31\xc8\xdc\x19\x5e\xb1\xaf\xa0\x5c\x51\xa1\x4a\x73\x22\xc0\x24\xf1\x41\x4a\xd9\x15\x16\xa8\x83\x38\x84\xe1\xca\x9d\xf0\xd5\x35\x40\x73 +\xdc\xf5\x87\xb0\x6d\x66\xd6\xab\x66\x34\xd7\x64\xc8\x51\xa1\x22\xe3\x97\x3d\x4b\x16\xee\x8e\x1e\x0b\xfb\xfc\x13 \xd5\xaf\x0d\xed\x74\x58\x8d\xe8\x97\x6d\xa0\xb2\x46\x83\x58\x0f\x52\xbc\xc7\x66\xb1\x19\x74\x70\x0d\xaa\xd1\x10\x9b\x71\x53\xe6\x80\x34\x5d\x81\xd2\x86\x8a\x33\xfc\x62\x88\xa7\x80\xac\x63\xb6 +\x51\xcb\xcc\x61\xae\xd0\xeb\x08\x75\x09\xde\x68\x3c\x36\x03\xf5\xa3\xd5\xa5\x15\xdc\x3e\x87\xdb\xcf\xc7\x7a\x1e \x25\x90\xa9\x7e\x5a\x93\xe9\xdd\x61\x6c\x46\xf2\xf6\x03\x7c\x19\xb1\xf5\x9a\x4a\x6c\x58\x71\x8e\xfe\xa4\xfe\xa6\x30\x70\x5f\xaf\xd4\xf9\xb9\x3a\x16\xa8\x0f\x69\x8d\x29\xfb\x1a\x34\x62\x87\x36 +\x01\x01\x12\xfb\x01\x61\xc6\xcd\xde\xdd\x2a\x9b\x2a\x2f\x02\x65\xa5\x0f\x62\xb1\x1b\x26\xd3\xa2\x69\x78\xe0\x17 \x8a\x67\x2f\xc6\xc1\x75\xed\xb9\x2f\x8c\xb5\x3d\xdc\x56\xb4\x3e\xab\x11\xa7\xb6\xff\x32\x47\x7b\x9c\x9c\x32\xe9\xbe\xa6\xb1\xed\xe1\x29\x7e\x4b\x89\xb7\xb0\xd6\x21\xc1\xda\x5c\x90\x70\x1b\xe4 +\x7a\xf0\xf4\x6e\x91\x8e\x38\x51\xfd\xd6\x42\xfb\x3e\x9b\x78\x29\x49\x3f\x78\x19\xd6\x2b\x61\xd5\x8b\xad\xfd\x70 \x78\xd8\x68\x51\x05\xc5\x3c\xeb\xcd\x22\xe0\x2e\x4b\x6f\xae\x53\x3f\xe8\x23\x73\xeb\xeb\x1b\xb2\x9a\x76\xca\x65\x01\x16\xa2\x97\x93\x60\xd5\x5d\xd4\xac\x52\x22\x16\x40\x15\x03\xb6\x23\xc1\xac +\x31\x15\xDD\x9D\x68\x19\xB3\xBF\x45\x32\x99\x74\x0D\x04\xAE\x37\xAD\x69\xE5\x23\x4C\xD5\x40\xF8\xB5\x89\x4B\xA4 \x7C\x57\xC5\x98\xCD\x00\xE0\x0F\x55\x48\x6A\xF0\x02\x4E\x84\xB7\xAE\x07\xB5\xCD\xB1\x1E\x17\x2D\x24\xF0\xB3\xB3\xB8\x4B\x54\x4A\x82\x84\x15\xAD\x52\x24\x52\xBB\x34\x0D\x95\x30\x45\x3E\x15\x14 +\x07\xDF\x9C\xC1\x59\xB6\x42\x8E\x57\x84\xED\xB1\x60\x37\xF3\x24\x2F\x70\x27\x5D\x07\xC4\xA8\xB9\xF0\xA7\xA6\x7F \x13\x9C\x33\x7E\x5C\x4E\x23\x5F\xCB\xFF\xD0\xD0\x54\x38\x0E\x04\x46\x2E\x6C\x8D\x51\x52\xEE\xAD\x79\x3F\x07\xA8\xCD\x18\x7D\x99\x99\x82\x1F\xA1\x51\xE2\xF6\xD4\x3F\x7B\x5C\x8A\xFE\x83\x6F\x4F +@TEST-END-FILE + +@load protocols/ssl/decryption +@load base/protocols/http + +event zeek_init() + { + suspend_processing(); + } + +event Input::end_of_data(name: string, source: string) + { + if ( name == "tls-keylog-file" ) + continue_processing(); + }