Spicy TLS: More TLS 1.3 parsing

This commit is contained in:
Johanna Amann 2023-04-25 14:53:02 +01:00
parent 82bcc2dbb3
commit dc46dbe645
3 changed files with 177 additions and 64 deletions

View file

@ -14,9 +14,9 @@ signature dpd_tls_client {
enable "tls" enable "tls"
} }
# signature dpd_dtls_client { signature dpd_dtls_client {
# ip-proto == udp ip-proto == udp
# # Client hello. # Client hello.
# payload /^\x16\xfe[\xff\xfd]\x00\x00\x00\x00\x00\x00\x00...\x01...........\xfe[\xff\xfd].*/ payload /^\x16\xfe[\xff\xfd]\x00\x00\x00\x00\x00\x00\x00...\x01...........\xfe[\xff\xfd].*/
# enable "dtls" enable "dtls"
# } }

View file

@ -2,9 +2,9 @@ protocol analyzer TLS over TCP:
parse with TLS::Message, parse with TLS::Message,
port 443/tcp; port 443/tcp;
# protocol analyzer DTLS over UDP: protocol analyzer DTLS over UDP:
# parse with TLS::DTLSMessage, parse with TLS::Message,
# port 443/udp; port 443/udp;
import TLS; import TLS;
import zeek; import zeek;
@ -12,7 +12,8 @@ import spicy;
on TLS::ClientHello -> event ssl_client_hello($conn, self.client_version, msg.record_version, cast<time>(self.random.gmt_unix_time), self.random.random_bytes, self.session_id, self.cipher_suites, self.compression_methods); on TLS::ClientHello -> event ssl_client_hello($conn, self.client_version, msg.record_version, cast<time>(self.random.gmt_unix_time), self.random.random_bytes, self.session_id, self.cipher_suites, self.compression_methods);
on TLS::ServerHello -> event ssl_server_hello($conn, self.server_version, msg.record_version, cast<time>(self.gmt_unix_time), self.random_bytes, self.session_id, self.cipher_suite, self.compression_method); on TLS::ServerHello -> event ssl_server_hello($conn, server_version, msg.record_version, cast<time>(self.gmt_unix_time), self.random_bytes, self.session_id, self.cipher_suite, self.compression_method);
on TLS::ServerHelloOneThree -> event ssl_server_hello($conn, server_version, msg.record_version, cast<time>(self.gmt_unix_time), self.random_bytes, "", self.cipher_suite, 0);
on TLS::EllipticCurveList -> event ssl_extension_elliptic_curves($conn, $is_orig, self.elliptic_curve_list); on TLS::EllipticCurveList -> event ssl_extension_elliptic_curves($conn, $is_orig, self.elliptic_curve_list);

View file

@ -163,9 +163,26 @@ type TLSVersion = enum {
SSLv3 = 0x0300, SSLv3 = 0x0300,
TLSv10 = 0x0301, TLSv10 = 0x0301,
TLSv11 = 0x0302, TLSv11 = 0x0302,
TLSv12 = 0x0303 TLSv12 = 0x0303,
DTLSv10 = 0xFEFF,
# DTLSv11 does not exist.
DTLSv12 = 0xFEFD,
DTLSv13 = 0xFEFC
}; };
const UNKNOWN_VERSION : uint16 = 0x0000;
const SSLv2 = 0x0002;
const SSLv3 = 0x0300;
const TLSv10 = 0x0301;
const TLSv11 = 0x0302;
const TLSv12 = 0x0303;
const TLSv13 = 0x0304;
const TLSv13_draft = 0x7F00;
const DTLSv10 = 0xFEFF;
# DTLSv11 does not exist.
const DTLSv12 = 0xFEFD;
const DTLSv13 = 0xFEFC;
type ClientCertificateType = enum { type ClientCertificateType = enum {
rsa_sign = 1, dss_sign = 2, rsa_fixed_dh = 3, dss_fixed_dh = 4, rsa_sign = 1, dss_sign = 2, rsa_fixed_dh = 3, dss_fixed_dh = 4,
rsa_ephemeral_dh_RESERVED = 5, dss_ephemeral_dh_RESERVED = 6, rsa_ephemeral_dh_RESERVED = 5, dss_ephemeral_dh_RESERVED = 6,
@ -536,26 +553,52 @@ type TLSCiphers = enum {
}; };
type Share = unit { type Share = unit {
# version as seen in server_hello (for signature and hash-alg choice)
var chosen_version_sh: uint16; var chosen_version_sh: uint16;
# parsed version, can be used to distinguished tls 1.3
var parsed_version: uint16;
# set to true if chosen version is identified as a tls 1.3 version
var tls_13: bool;
var chosen_cipher: uint16; var chosen_cipher: uint16;
var ccs_seen: uint8; var ccs_seen: uint8;
var invalid_dtls_version_count: uint32; var invalid_dtls_version_count: uint32;
var skipping: bool; # var skipping: bool;
on %init { on %init {
self.ccs_seen = 0; self.ccs_seen = 0;
self.invalid_dtls_version_count = 0; self.invalid_dtls_version_count = 0;
self.skipping = False; self.tls_13 = False;
self.parsed_version = UNKNOWN_VERSION;
} }
}; };
#public type TLSMessage = unit { # This function is called several times in certain circumstances.
# m: Message(False); # If it is called twice, it is first called due to the supported_versions
#}; # field in the server hello - and then again due to the outer version in
# the server hello. So - once we have a version here, let's just stick
# with it.
function set_version(version: uint16, inout msg: Message) : bool {
if ( msg.context().parsed_version != UNKNOWN_VERSION )
return False;
msg.context().parsed_version = version;
if ( version == TLSv13 || version/0xFF == 0x7F )
msg.context().tls_13 = True;
return True;
}
# public type TLSMessage = unit {
# %context = Share;
# #
#public type DTLSMessage = unit { # m: Message(False);
# m: Message(True); # };
#}; #
# public type DTLSMessage = unit {
# %context = Share;
#
# m: Message(True);
# };
public type Message = unit { public type Message = unit {
%context = Share; %context = Share;
@ -564,52 +607,55 @@ public type Message = unit {
sink alertsink; sink alertsink;
var record_version: uint16; var record_version: uint16;
var encrypted : bool = False; var encrypted : bool = False;
var dtls : bool = False; var dtls: bool = False;
on %init { on %init {
self.handshakesink.connect(new Handshake(self)); self.handshakesink.connect(new Handshake(self));
self.alertsink.connect(new Alert); self.alertsink.connect(new Alert);
print "top-level init";
} }
fragment : RecordFragmentChoice(self.handshakesink, self.alertsink, self)[]; fragment : RecordFragmentChoice(self.handshakesink, self.alertsink, self)[];
# : bytes &eod if ( self.context().skipping ); # : bytes &eod if ( self.context().skipping );
}; };
type RecordFragmentChoice = unit(handshakesink: sink, alertsink: sink, inout msg: Message) { function is_dtls_version(version: uint16) : bool {
switch ( msg.dtls ) { if ( version == DTLSv10 || version == DTLSv12 )
True -> dtlsfragment : DTLSRecordFragment(handshakesink, alertsink, msg); return True;
False -> tlsfragment : TLSRecordFragment(handshakesink, alertsink, msg);
};
};
type TLSRecordFragment = unit(handshakesink: sink, alertsink: sink, inout msg: Message) { return False;
}
type RecordFragmentChoice = unit(handshakesink: sink, alertsink: sink, inout msg: Message) {
content_type: uint8; # &convert=ContentType($$); content_type: uint8; # &convert=ContentType($$);
version: uint16; version: uint16;
record: PlaintextRecord(self.content_type, handshakesink, alertsink, msg);
switch ( is_dtls_version(self.version) ) {
True -> dtlsfragment : DTLSRecordFragment(self.content_type, handshakesink, alertsink, msg);
False -> tlsfragment : TLSRecordFragment(self.content_type, handshakesink, alertsink, msg);
};
on content_type { on content_type {
print "Content type", self.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) ) {
msg.dtls = True;
}
} }
}; };
type DTLSRecordFragment = unit(handshakesink: sink, alertsink: sink, inout msg: Message) { type TLSRecordFragment = unit(content_type: uint8, handshakesink: sink, alertsink: sink, inout msg: Message) {
content_type: uint8; # &convert=ContentType($$); record: PlaintextRecord(content_type, handshakesink, alertsink, msg);
version: uint16; };
type DTLSRecordFragment = unit(content_type: uint8, handshakesink: sink, alertsink: sink, inout msg: Message) {
# the epoch signalizes that a changecipherspec message has been received. Hence, everything with # the epoch signalizes that a changecipherspec message has been received. Hence, everything with
# an epoch > 0 should be encrypted # an epoch > 0 should be encrypted
epoch: uint16; epoch: uint16;
sequence_number: bytes &size=6; # uint48 sequence_number: bytes &size=6; # uint48
record: PlaintextRecord(self.content_type, handshakesink, alertsink, msg); record: PlaintextRecord(content_type, handshakesink, alertsink, msg);
on content_type {
print "Content type", self.content_type;
}
on version {
msg.record_version = self.version;
}
}; };
type PlaintextRecord = unit(content_type: uint8, handshakesink: sink, alertsink: sink, inout msg: Message) { type PlaintextRecord = unit(content_type: uint8, handshakesink: sink, alertsink: sink, inout msg: Message) {
@ -665,13 +711,16 @@ type Handshake_message = unit(inout msg: Message) {
# msg_type: uint8 &parse-from=self.msg_type_raw &convert=HandshakeType($$); # msg_type: uint8 &parse-from=self.msg_type_raw &convert=HandshakeType($$);
# msg_type: uint8 &convert=HandshakeType($$); # msg_type: uint8 &convert=HandshakeType($$);
length: bytes &size=3 &convert=$$.to_uint(spicy::ByteOrder::Network); length: bytes &size=3 &convert=$$.to_uint(spicy::ByteOrder::Network);
# TODO: in theory, we need a reassembly step here for DTLS...
fragment_offset: bytes &size=3 &convert=$$.to_uint(spicy::ByteOrder::Network) if (msg.dtls);
fragment_length: bytes &size=3 &convert=$$.to_uint(spicy::ByteOrder::Network) if (msg.dtls);
switch ( HandshakeType(self.msg_type) ) { switch ( HandshakeType(self.msg_type) ) {
HandshakeType::client_hello -> client_hello: ClientHello(self.length, msg); HandshakeType::client_hello -> client_hello: ClientHello(self.length, msg);
HandshakeType::server_hello_done, HandshakeType::server_hello_done,
HandshakeType::hello_request -> : bytes &size=self.length; # Fixme: alert if length != 0 HandshakeType::hello_request -> : bytes &size=self.length; # Fixme: alert if length != 0
HandshakeType::hello_verify_request -> hello_verify_request: HelloVerifyRequest; HandshakeType::hello_verify_request -> hello_verify_request: HelloVerifyRequest;
HandshakeType::server_hello -> server_hello: ServerHello(self.length, msg); HandshakeType::server_hello -> server_hello: ServerHelloChoice(self.length, msg);
HandshakeType::certificate -> certificate: Certificate; HandshakeType::certificate -> certificate: Certificate;
HandshakeType::certificate_request -> certificate_request: CertificateRequest(msg); HandshakeType::certificate_request -> certificate_request: CertificateRequest(msg);
HandshakeType::certificate_verify -> : bytes &size=self.length; # opaque encrypted data HandshakeType::certificate_verify -> : bytes &size=self.length; # opaque encrypted data
@ -726,21 +775,75 @@ type Random = unit {
random_bytes: bytes &size=28; random_bytes: bytes &size=28;
}; };
type ClientHello = unit(len: uint64, msg: Message) { type ClientHelloCookie = unit {
cookie_len: uint8;
cookie: bytes &size=self.cookie_len;
};
type ClientHello = unit(len: uint64, inout msg: Message) {
client_version: uint16; client_version: uint16;
random: Random; random: Random;
session_id_length: uint8; session_id_length: uint8;
session_id: bytes &size=self.session_id_length; session_id: bytes &size=self.session_id_length;
dtls_cookie: ClientHelloCookie if (self.client_version == DTLSv10 || self.client_version == DTLSv12);
cipher_suites_length: uint16; cipher_suites_length: uint16;
cipher_suites: uint16[self.cipher_suites_length/2]; cipher_suites: uint16[self.cipher_suites_length/2];
compression_methods_length: uint8; compression_methods_length: uint8;
compression_methods: uint8[self.compression_methods_length]; compression_methods: uint8[self.compression_methods_length];
extensions_length: uint16 if ( len > self.offset() ); extensions_length: uint16 if ( len > self.offset() );
extensions: Extension(True)[] &size=self.extensions_length if ( len > self.offset() ); extensions: Extension(msg, True)[] &size=self.extensions_length if ( len > self.offset() );
on %error(emsg: string) {
print "Error in client hello", emsg;
print self;
}
}; };
type ServerHello = unit(len: uint64, inout msg: Message) { # Draft versions of TLS 1.3 had a diffent server hello - distinguish here
server_version: uint16; type ServerHelloChoice = unit(len: uint64, inout msg: Message) {
sv : bitfield(16) {
server_version0: 0..7;
server_version1: 8..15;
server_version: 0..15;
};
var parsed_version: uint16;
switch ( self.parsed_version ) {
TLSv13, TLSv13_draft, 0x7F00 -> : ServerHelloOneThree(len, msg, self.sv.server_version);
* -> : ServerHello(len, msg, self.sv.server_version);
};
on sv {
print "Got server version", self.sv.server_version0, self.sv.server_version1, self.sv.server_version;
msg.context().chosen_version_sh = self.sv.server_version;
set_version(self.sv.server_version, msg);
print "set chosen version", self.sv.server_version, msg.context().chosen_version_sh;
if ( self.sv.server_version1 == 0x7F ) {
print "TLS 13 draft";
# map any draft version to draft 00
self.parsed_version = 0x7F00;
} else {
self.parsed_version = self.sv.server_version;
}
}
};
# Draft versions of TLS 1.3 had a diffent server hello.
type ServerHelloOneThree = unit(len: uint64, inout msg: Message, server_version: uint16) {
random_bytes: bytes &size=32;
gmt_unix_time: uint32 &parse-from=self.random_bytes;
cipher_suite: uint16;
extensions_length: uint16 if ( len > self.offset() );
extensions: Extension(msg, False)[] &size=self.extensions_length if ( len > self.offset() );
on cipher_suite {
msg.context().chosen_cipher = self.cipher_suite;
print "set chosen cipher", self.cipher_suite, msg.context().chosen_cipher;
}
};
type ServerHello = unit(len: uint64, inout msg: Message, server_version: uint16) {
random_bytes: bytes &size=32; random_bytes: bytes &size=32;
gmt_unix_time: uint32 &parse-from=self.random_bytes; gmt_unix_time: uint32 &parse-from=self.random_bytes;
session_id_length: uint8; session_id_length: uint8;
@ -748,37 +851,39 @@ type ServerHello = unit(len: uint64, inout msg: Message) {
cipher_suite: uint16; cipher_suite: uint16;
compression_method: uint8; compression_method: uint8;
extensions_length: uint16 if ( len > self.offset() ); extensions_length: uint16 if ( len > self.offset() );
extensions: Extension(False)[] &size=self.extensions_length if ( len > self.offset() ); extensions: Extension(msg, False)[] &size=self.extensions_length if ( len > self.offset() );
on cipher_suite { on cipher_suite {
msg.context().chosen_cipher = self.cipher_suite; msg.context().chosen_cipher = self.cipher_suite;
print "set chosen cipher", self.cipher_suite, msg.context().chosen_cipher;
} }
on server_version { on %error(emsg: string) {
msg.context().chosen_version_sh = self.server_version; print "Error in server hello", emsg;
print self;
} }
}; };
type Extension = unit(client_hello: bool) { type Extension = unit(inout msg: Message, client_hello: bool) {
code: uint16 &convert=Extensions($$); code: uint16 &convert=Extensions($$);
length: uint16; length: uint16;
switch ( self.code ) { switch ( self.code ) {
# Extensions::next_protocol_negotiation -> :bytes &size=self.extension_size; # alert if != 0 # Extensions::next_protocol_negotiation -> :bytes &size=self.extension_size; # alert if != 0
Extensions::ec_point_formats -> ec_point_formats: EcPointsFormat_extension; Extensions::ec_point_formats -> ec_point_formats: EcPointsFormat_extension if ( self.length > 0 );
Extensions::supported_groups -> elliptic_curves: EllipticCurveList; Extensions::supported_groups -> elliptic_curves: EllipticCurveList if ( self.length > 0 );
Extensions::SessionTicket_TLS -> ticket_data: bytes &size=self.length; # ticket data Extensions::SessionTicket_TLS -> ticket_data: bytes &size=self.length; # ticket data
Extensions::heartbeat -> heartbeat: uint8 &convert=HeartbeatMode($$); Extensions::heartbeat -> heartbeat: uint8 &convert=HeartbeatMode($$) if ( self.length > 0 );
Extensions::signature_algorithms -> signature_algorithms: SignatureAlgorithms; Extensions::signature_algorithms -> signature_algorithms: SignatureAlgorithms if ( self.length > 0 );
Extensions::renegotiation_info -> renegotiation_info: RenegotiationInfo; Extensions::renegotiation_info -> renegotiation_info: RenegotiationInfo if ( self.length > 0 );
Extensions::server_name -> server_name: ServerNameList if ( self.length > 0 ); Extensions::server_name -> server_name: ServerNameList if ( self.length > 0 );
Extensions::application_layer_protocol_negotiation -> application_layer_protocol_negotiation: ProtocolNameList; Extensions::application_layer_protocol_negotiation -> application_layer_protocol_negotiation: ProtocolNameList if ( self.length > 0 );
Extensions::status_request -> status_request: StatusRequest(self.length) if ( self.length > 0 ); Extensions::status_request -> status_request: StatusRequest(self.length) if ( self.length > 0 );
Extensions::signed_certificate_timestamp -> signed_certificate_timestamp: SignedCertificateTimestampList if ( self.length > 0 ); Extensions::signed_certificate_timestamp -> signed_certificate_timestamp: SignedCertificateTimestampList if ( self.length > 0 );
Extensions::key_share_old -> key_share_old: KeyShare(client_hello, self.length); Extensions::key_share_old -> key_share_old: KeyShare(client_hello, self.length) if ( self.length > 0 );
Extensions::key_share -> key_share: KeyShare(client_hello, self.length); Extensions::key_share -> key_share: KeyShare(client_hello, self.length) if ( self.length > 0 );
Extensions::supported_versions -> supported_versions: SupportedVersionsSelector(client_hello); Extensions::supported_versions -> supported_versions: SupportedVersionsSelector(msg, client_hello) if ( self.length > 0 );
Extensions::psk_key_exchange_modes -> psk_key_exchange_modes: PSKKeyExchangeModes; Extensions::psk_key_exchange_modes -> psk_key_exchange_modes: PSKKeyExchangeModes if ( self.length > 0 );
Extensions::pre_shared_key -> pre_shared_key: PreSharedKey(client_hello); Extensions::pre_shared_key -> pre_shared_key: PreSharedKey(client_hello) if ( self.length > 0 );
* -> unknown : bytes &size=self.length; * -> unknown : bytes &size=self.length;
}; };
@ -788,6 +893,9 @@ type Extension = unit(client_hello: bool) {
on unknown { on unknown {
print "Unknown extension", self.code; print "Unknown extension", self.code;
} }
on %error(emsg: string) {
print "Error parsing extension with code", self.code, emsg;
}
}; };
type SelectedPreSharedKeyIdentity = unit { type SelectedPreSharedKeyIdentity = unit {
@ -832,10 +940,10 @@ type PSKKeyExchangeModes = unit {
modes: uint8[self.length]; modes: uint8[self.length];
}; };
type SupportedVersionsSelector = unit(client_hello: bool) { type SupportedVersionsSelector = unit(inout msg: Message, client_hello: bool) {
switch (client_hello ) { switch (client_hello ) {
True -> a: SupportedVersions; True -> a: SupportedVersions;
False -> b: OneSupportedVersion; False -> b: OneSupportedVersion(msg);
}; };
}; };
@ -845,8 +953,12 @@ type SupportedVersions = unit {
}; };
# If the server sends it, this is the authorative version. Set it. # If the server sends it, this is the authorative version. Set it.
type OneSupportedVersion = unit { type OneSupportedVersion = unit(inout msg: Message) {
version: uint16; version: uint16;
on version {
set_version(self.version, msg);
}
}; };
type KeyShareEntry = unit { type KeyShareEntry = unit {
@ -1174,7 +1286,7 @@ type EcdheServerKeyExchange = unit(len: uint64, msg: Message) {
public function uses_signature_and_hashalgorithm(msg: Message) : bool public function uses_signature_and_hashalgorithm(msg: Message) : bool
{ {
# larger TLS11 and not DTLSv10 # larger TLS11 and not DTLSv10
if ( msg.context().chosen_version_sh > 0x0302 && msg.context().chosen_version_sh != 0xFEFF ) if ( msg.context().chosen_version_sh > TLSv11 && msg.context().chosen_version_sh != DTLSv10 )
return True; return True;
return False; return False;