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", };