Spicy TLS - refactoring and partial connection fix

The analyzer now detects partial connections at the beginning of a
connection - and will skip them. This makes behavior more similar to the
binpac analyzer.

The decryption test is skipped.

And some minor refacoring.
This commit is contained in:
Johanna Amann 2024-07-23 15:34:10 +01:00
parent f95f5d2adb
commit 7c0c48b290
3 changed files with 97 additions and 44 deletions

View file

@ -6,6 +6,8 @@ public function get_certificate_fuid(is_client: bool, pos: uint32): string &cxxn
public function get_ocsp_fuid(): string &cxxname="ssl_get_ocsp_fuid"; public function get_ocsp_fuid(): string &cxxname="ssl_get_ocsp_fuid";
public function is_partial_tcp(): bool &cxxname="ssl_is_partial_tcp";
type ContentType = enum { type ContentType = enum {
change_cipher_spec = 20, change_cipher_spec = 20,
alert = 21, alert = 21,
@ -549,9 +551,9 @@ type Share = unit {
var flipped: bool; var flipped: bool;
var flip_already_alerted: bool; var flip_already_alerted: bool;
# version as seen in server_hello (for signature and hash-alg choice) # version as seen in server_hello (for signature and hash-alg choice)
var chosen_version_sh: uint16; var chosen_version_sh_outer: uint16;
# parsed version, can be used to distinguished tls 1.3 # final negotiated version - can e.g. be used to distinguished tls 1.3
var parsed_version: uint16; var negotiated_version: uint16;
# set to true if chosen version is identified as a tls 1.3 version # set to true if chosen version is identified as a tls 1.3 version
var tls_13: bool; var tls_13: bool;
var chosen_cipher: uint16; var chosen_cipher: uint16;
@ -569,7 +571,7 @@ type Share = unit {
self.ccs_seen = 0; self.ccs_seen = 0;
self.invalid_dtls_version_count = 0; self.invalid_dtls_version_count = 0;
self.tls_13 = False; self.tls_13 = False;
self.parsed_version = UNKNOWN_VERSION; self.negotiated_version = UNKNOWN_VERSION;
self.flipped = False; self.flipped = False;
self.flip_already_alerted = False; self.flip_already_alerted = False;
self.server_encrypted = False; self.server_encrypted = False;
@ -619,13 +621,23 @@ function startEncryption(handshakesink: sink&, alertsink: sink&, inout sh: Share
# the server hello - and then again due to the supported_versions # the server hello - and then again due to the supported_versions
# field in the server hello. # field in the server hello.
function set_version(version: uint16, inout sh: Share): bool { function set_version(version: uint16, inout sh: Share): bool {
sh.parsed_version = version; sh.negotiated_version = version;
if (version == TLSv13 || version / 0xFF == 0x7F) if (version == TLSv13 || version / 0xFF == 0x7F)
sh.tls_13 = True; sh.tls_13 = True;
return True; return True;
} }
# check for partial connection - and at the moment just disable the analyzer
# in that case, for 1:1 equivalence with binpac.
function check_partial(): bool {
if ( is_partial_tcp() ) {
zeek::skip_input();
return True;
}
return False;
}
# public type TLSMessage = unit { # public type TLSMessage = unit {
# %context = Share; # %context = Share;
# #
@ -645,15 +657,18 @@ public type Message = unit {
sink alertsink; sink alertsink;
var record_version: uint16; var record_version: uint16;
var dtls: bool = False; var dtls: bool = False;
var partial: bool = False;
on %init { on %init {
self.handshakesink.connect(new Handshake(self, self.context())); self.handshakesink.connect(new Handshake(self, self.context()));
self.alertsink.connect(new Alert(self.context())); self.alertsink.connect(new Alert(self.context()));
# print "top-level init";
self.partial = check_partial();
} }
: skip bytes &eod if ( self.partial );
fragment: RecordFragmentChoice(self.handshakesink, self.alertsink, self, self.context())[]; fragment: RecordFragmentChoice(self.handshakesink, self.alertsink, self, self.context())[];
# : bytes &eod if ( self.context().skipping );
}; };
function is_dtls_version(version: uint16): bool { function is_dtls_version(version: uint16): bool {
@ -672,9 +687,6 @@ type RecordFragmentChoice = unit(handshakesink: sink&, alertsink: sink&, inout m
False -> tlsfragment: TLSRecordFragment(self.content_type, handshakesink, alertsink, msg, sh); False -> tlsfragment: TLSRecordFragment(self.content_type, handshakesink, alertsink, msg, sh);
}; };
# on content_type {
# print "Content type", self.content_type;
# }
on version { on version {
msg.record_version = self.version; msg.record_version = self.version;
if (is_dtls_version(self.version)) { if (is_dtls_version(self.version)) {
@ -764,6 +776,10 @@ type PlaintextRecord = unit(content_type: uint8, handshakesink: sink&, alertsink
## a bit of context here - we can't really say when we get the first packet ## a bit of context here - we can't really say when we get the first packet
## that uses the final cryptographic key material - and will contain content ## that uses the final cryptographic key material - and will contain content
## data. We just don't have that information available in TLS 1.3 anymore. ## data. We just don't have that information available in TLS 1.3 anymore.
##
## Currently this returns True if the connection is encrypted, or in an otherwise
## unclear state (e.g. no known version for the connection), and False if the
## packet should be in plaintext
function determine_encryption_on(pr: PlaintextRecord, content_type: uint8, handshakesink: sink&, alertsink: sink&, inout sh: Share): bool { function determine_encryption_on(pr: PlaintextRecord, content_type: uint8, handshakesink: sink&, alertsink: sink&, inout sh: Share): bool {
if (get_encrypted(sh)) if (get_encrypted(sh))
return True; return True;
@ -831,16 +847,6 @@ type Handshake_message = unit(inout msg: Message, inout sh: Share) {
* -> unhandled: skip bytes &size=self.length; * -> unhandled: skip bytes &size=self.length;
}; };
# on msg_type {
# print "Handshake message", self.msg_type;
# }
# on unhandled {
# print "Unhandled handshake message of type ", self.msg_type;
# }
# on %error(emsg: string) {
# print "Error in handshake message of type", self.msg_type, self, emsg;
# print self;
# }
}; };
type HelloRequest = unit(inout sh: Share) { type HelloRequest = unit(inout sh: Share) {
@ -915,10 +921,6 @@ type ClientHello = unit(len: uint64, msg: Message, inout sh: Share) {
compression_methods: uint8[self.compression_methods_length]; compression_methods: uint8[self.compression_methods_length];
extensions_length: uint16 if(len > self.offset() + 2); extensions_length: uint16 if(len > self.offset() + 2);
extensions: Extension(sh, True)[] &size=self.extensions_length if(len > self.offset() + 2); extensions: Extension(sh, True)[] &size=self.extensions_length if(len > self.offset() + 2);
# on %error(emsg: string) {
# print "Error in client hello", emsg;
# print self;
# }
}; };
# Draft versions of TLS 1.3 had a different server hello - distinguish here # Draft versions of TLS 1.3 had a different server hello - distinguish here
@ -929,9 +931,9 @@ type ServerHelloChoice = unit(len: uint64, msg: Message, inout sh: Share) {
server_version1: 8..15; server_version1: 8..15;
server_version: 0..15; server_version: 0..15;
}; };
var parsed_version: uint16; var negotiated_version: uint16;
switch (self.parsed_version) { switch (self.negotiated_version) {
TLSv13, TLSv13,
TLSv13_draft, TLSv13_draft,
0x7F00 -> sh_one_three: ServerHelloOneThree(len, msg, sh, self.sv.server_version); 0x7F00 -> sh_one_three: ServerHelloOneThree(len, msg, sh, self.sv.server_version);
@ -940,16 +942,16 @@ type ServerHelloChoice = unit(len: uint64, msg: Message, inout sh: Share) {
on sv { on sv {
# print "Got server version", self.sv.server_version0, self.sv.server_version1, self.sv.server_version; # print "Got server version", self.sv.server_version0, self.sv.server_version1, self.sv.server_version;
sh.chosen_version_sh = self.sv.server_version; sh.chosen_version_sh_outer = self.sv.server_version;
set_version(self.sv.server_version, sh); set_version(self.sv.server_version, sh);
# print "set chosen version", self.sv.server_version, sh.chosen_version_sh; # print "set chosen version", self.sv.server_version, sh.chosen_version_sh_outer;
if (self.sv.server_version1 == 0x7F) { if (self.sv.server_version1 == 0x7F) {
# print "TLS 13 draft"; # print "TLS 13 draft";
# map any draft version to draft 00 # map any draft version to draft 00
self.parsed_version = 0x7F00; self.negotiated_version = 0x7F00;
} else { } else {
self.parsed_version = self.sv.server_version; self.negotiated_version = self.sv.server_version;
} }
} }
}; };
@ -982,10 +984,6 @@ type ServerHello = unit(len: uint64, msg: Message, inout sh: Share, server_versi
sh.chosen_cipher = self.cipher_suite; sh.chosen_cipher = self.cipher_suite;
# print "set chosen cipher", self.cipher_suite, sh.chosen_cipher; # print "set chosen cipher", self.cipher_suite, sh.chosen_cipher;
} }
# on %error(emsg: string) {
# print "Error in server hello", emsg;
# print self;
# }
}; };
type Extension = unit(inout sh: Share, client_hello: bool) { type Extension = unit(inout sh: Share, client_hello: bool) {
@ -1015,15 +1013,6 @@ type Extension = unit(inout sh: Share, client_hello: bool) {
* -> unknown: bytes &size=self.length; * -> unknown: bytes &size=self.length;
}; };
# on code {
# print "Extension", self.code, client_hello;
# }
# on unknown {
# print "Unknown extension", self.code;
# }
# on %error(emsg: string) {
# print "Error parsing extension with code", self.code, emsg;
# }
on raw { on raw {
self.set_input(self.input() + 4); self.set_input(self.input() + 4);
} }
@ -1422,7 +1411,7 @@ type EcdheServerKeyExchange = unit(len: uint64, sh: Share) {
public function uses_signature_and_hashalgorithm(sh: Share): bool { public function uses_signature_and_hashalgorithm(sh: Share): bool {
# larger TLS11 and not DTLSv10 # larger TLS11 and not DTLSv10
if (sh.chosen_version_sh > TLSv11 && sh.chosen_version_sh != DTLSv10) if (sh.chosen_version_sh_outer > TLSv11 && sh.chosen_version_sh_outer != DTLSv10)
return True; return True;
return False; return False;
@ -1873,3 +1862,48 @@ on SSL::Certificate::%done {
first = False; first = False;
} }
} }
# Debug stuff
# on RecordFragmentChoice::content_type {
# print "RecordFragmentChoice: Content type", self.content_type;
# }
#
# on PlaintextRecord::unhandled {
# print "PlaintextRecord: Unhandled content type", content_type;
# }
#
# on Handshake_message::msg_type {
# print "Handshake message", self.msg_type;
# }
#
# on Handshake_message::unhandled {
# print "Unhandled handshake message of type ", self.msg_type;
# }
#
# on Handshake_message::%error(emsg: string) {
# print "Error in handshake message of type", self.msg_type, self, emsg;
# print self;
# }
#
# on ClientHello::%error(emsg: string) {
# print "Error in client hello", emsg;
# print self;
# }
#
# on ServerHello::%error(emsg: string) {
# print "Error in server hello", emsg;
# print self;
# }
#
# on Extension::code {
# print "Extension", self.code, client_hello;
# }
#
# on Extension::unknown {
# print "Unknown extension", self.code;
# }
#
# on Extension::%error(emsg: string) {
# print "Error parsing extension with code", self.code, emsg;
# }

View file

@ -43,3 +43,21 @@ std::string ssl_get_ocsp_fuid() {
std::string file_id = zeek::file_mgr->HashHandle(file_handle.Description()); std::string file_id = zeek::file_mgr->HashHandle(file_handle.Description());
return file_id; return file_id;
} }
bool ssl_is_partial_tcp() {
auto cookie = static_cast<zeek::spicy::rt::Cookie*>(hilti::rt::context::cookie());
assert(cookie);
auto x = cookie->protocol;
if ( ! x || ! x->analyzer )
return false;
auto* tcp = dynamic_cast<zeek::analyzer::tcp::TCP_ApplicationAnalyzer*>(x->analyzer);
if ( ! tcp )
return false;
if ( tcp->TCP() && tcp->TCP()->IsPartial() )
return true;
return false;
}

View file

@ -1,4 +1,5 @@
# @TEST-REQUIRES: grep -q "#define OPENSSL_HAVE_KDF_H" $BUILD/zeek-config.h # @TEST-REQUIRES: grep -q "#define OPENSSL_HAVE_KDF_H" $BUILD/zeek-config.h
# @TEST-REQUIRES: ! grep -q "#define ENABLE_SPICY_SSL" $BUILD/zeek-config.h
# @TEST-EXEC: zeek -B dpd -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 # @TEST-EXEC: btest-diff http.log