From d6c4c510eaa623851071a5f85d6e7a59d479b329 Mon Sep 17 00:00:00 2001 From: Johanna Amann Date: Wed, 3 May 2023 16:15:47 +0100 Subject: [PATCH] Add basic DTLSv1.3 support DTLSv1.3 changes the DTLS record format, introducing a completely new header - which is a first for DTLS. We don't currently completely parse this header, as this requires a bit more statekeeping. This will be added in a future revision. This also also has little practical implications. --- scripts/base/protocols/ssl/consts.zeek | 4 +- src/analyzer/protocol/ssl/dtls-analyzer.pac | 29 +++++++++++++ src/analyzer/protocol/ssl/dtls-protocol.pac | 39 ++++++++++++++++-- src/analyzer/protocol/ssl/ssl-defs.pac | 4 +- .../ssl.log | 11 +++++ testing/btest/Traces/tls/dtls13-wolfssl.pcap | Bin 0 -> 3752 bytes .../scripts/base/protocols/ssl/dtls-13.test | 6 +++ 7 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 testing/btest/Baseline/scripts.base.protocols.ssl.dtls-13/ssl.log create mode 100644 testing/btest/Traces/tls/dtls13-wolfssl.pcap create mode 100644 testing/btest/scripts/base/protocols/ssl/dtls-13.test diff --git a/scripts/base/protocols/ssl/consts.zeek b/scripts/base/protocols/ssl/consts.zeek index fb11ca22b5..7f7188859a 100644 --- a/scripts/base/protocols/ssl/consts.zeek +++ b/scripts/base/protocols/ssl/consts.zeek @@ -11,6 +11,7 @@ export { const DTLSv10 = 0xFEFF; # DTLSv11 does not exist const DTLSv12 = 0xFEFD; + const DTLSv13 = 0xFEFC; ## Mapping between the constants and string values for SSL/TLS versions. const version_strings: table[count] of string = { @@ -21,7 +22,8 @@ export { [TLSv12] = "TLSv12", [TLSv13] = "TLSv13", [DTLSv10] = "DTLSv10", - [DTLSv12] = "DTLSv12" + [DTLSv12] = "DTLSv12", + [DTLSv13] = "DTLSv13" } &default=function(i: count):string { if ( i/0xFF == 0x7F ) # TLS 1.3 draft diff --git a/src/analyzer/protocol/ssl/dtls-analyzer.pac b/src/analyzer/protocol/ssl/dtls-analyzer.pac index 1a68e0d846..61c1e21846 100644 --- a/src/analyzer/protocol/ssl/dtls-analyzer.pac +++ b/src/analyzer/protocol/ssl/dtls-analyzer.pac @@ -138,6 +138,31 @@ refine connection SSL_Conn += { return true; %} + + function proc_unified_record(is_orig: bool, ur: UnifiedRecord) : bool + %{ + // we don't have a CCS packet anymore - so let's just assume the connection is established once we have seen a packet from each direction. + if ( is_orig ) + client_state_ = STATE_ENCRYPTED; + else + server_state_ = STATE_ENCRYPTED; + + if ( client_state_ == STATE_ENCRYPTED && server_state_ == STATE_ENCRYPTED && established_ == false ) + { + established_ = true; + if ( ssl_established ) + zeek::BifEvent::enqueue_ssl_established(zeek_analyzer(), zeek_analyzer()->Conn()); + } + + if ( ssl_encrypted_data ) + { + // FIXME: swallow is not quite the correct length, because we are not parsing the entire header + zeek::BifEvent::enqueue_ssl_encrypted_data(zeek_analyzer(), + zeek_analyzer()->Conn(), is_orig ^ zeek_analyzer()->GetFlipped(), DTLSv13, APPLICATION_DATA, ur->swallow().length()); + } + + return true; + %} }; refine typeattr SSLRecord += &let { @@ -147,3 +172,7 @@ refine typeattr SSLRecord += &let { refine typeattr Handshake += &let { proc: bool = $context.connection.proc_handshake(rec, this); }; + +refine typeattr UnifiedRecord += &let { + proc: bool = $context.connection.proc_unified_record(is_orig, this); +}; diff --git a/src/analyzer/protocol/ssl/dtls-protocol.pac b/src/analyzer/protocol/ssl/dtls-protocol.pac index 760a0bc4e4..84f1542362 100644 --- a/src/analyzer/protocol/ssl/dtls-protocol.pac +++ b/src/analyzer/protocol/ssl/dtls-protocol.pac @@ -4,11 +4,37 @@ ###################################################################### type DTLSPDU(is_orig: bool) = record { - records: SSLRecord(is_orig)[] &transient; + records: SSLRecordSwitch(is_orig)[] &transient; }; -type SSLRecord(is_orig: bool) = record { - content_type: uint8; +# This feels like (another) really dirty hack. DTLS 1.3 introduces a new way in which ciphertext records +# can be encoded, using a new unified header, which is completely different from the earlier DTLS headers. +# It only is used after the client & server hello - which essentially are the same as in DTLS 1.2 (including +# using the same record-layer versions - which is why `dtls_version_ok` underneath does not refer to DTLS 1.3) +# The DTLS 1.3 unified header is signaled by the first 3 bits of the first byte being set to `001`, but only +# after DTLS 1.3 has been negotiated. +type SSLRecordSwitch(is_orig: bool) = record { + firstbyte: uint8; + + cont: case $context.connection.choose_record_type(firstbyte) of { + false -> rec: SSLRecord(firstbyte, is_orig); + true -> unified: UnifiedRecord(firstbyte, is_orig); + }; +}; + +type UnifiedRecord(firstbyte: uint8, is_orig: bool) = record { + # sequence_number: bytestring &length=(sequence_number_length?2:1); + # lets just ignore eveything for now. We have very limited example + # data, and it is hard to parse the CID due to variable length. + swallow: bytestring &restofdata; +} &let { + with_cid: bool = ((firstbyte&0x10)==0x10); + sequence_number_length: bool = ((firstbyte&0x08)==0x08); + lengh_present: bool = ((firstbyte&0x04)==0x04); + epoch_low_bits: uint8 = (firstbyte&0x03); +}; + +type SSLRecord(content_type: uint8, is_orig: bool) = record { version: uint16; # the epoch signalizes that a changecipherspec message has been received. Hence, everything with # an epoch > 0 should be encrypted @@ -83,4 +109,11 @@ refine connection SSL_Conn += { } %} + function choose_record_type(firstbyte: uint8): bool + %{ + uint16_t negotiated_version = zeek_analyzer()->GetNegotiatedVersion(); + if ( negotiated_version == DTLSv13 && ( (firstbyte & 0x20) == 0x20 ) ) + return true; + return false; + %} }; diff --git a/src/analyzer/protocol/ssl/ssl-defs.pac b/src/analyzer/protocol/ssl/ssl-defs.pac index 072c7d5de6..796fc2ba5a 100644 --- a/src/analyzer/protocol/ssl/ssl-defs.pac +++ b/src/analyzer/protocol/ssl/ssl-defs.pac @@ -77,6 +77,7 @@ function version_ok(vers : uint16) : bool case TLSv13: case DTLSv10: case DTLSv12: + case DTLSv13: return true; default: @@ -121,7 +122,8 @@ enum SSLVersions { DTLSv10 = 0xFEFF, # DTLSv11 does not exist. - DTLSv12 = 0xFEFD + DTLSv12 = 0xFEFD, + DTLSv13 = 0xFEFC }; enum SSLExtensions { diff --git a/testing/btest/Baseline/scripts.base.protocols.ssl.dtls-13/ssl.log b/testing/btest/Baseline/scripts.base.protocols.ssl.dtls-13/ssl.log new file mode 100644 index 0000000000..e8253d3f47 --- /dev/null +++ b/testing/btest/Baseline/scripts.base.protocols.ssl.dtls-13/ssl.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 ssl +#open XXXX-XX-XX-XX-XX-XX +#fields ts uid id.orig_h id.orig_p id.resp_h id.resp_p version cipher curve server_name resumed last_alert next_protocol established ssl_history cert_chain_fps client_cert_chain_fps sni_matches_cert +#types time string addr port addr port string string string string bool string string bool string vector[string] vector[string] bool +XXXXXXXXXX.XXXXXX CHhAvVGS1DHFjwGM9 10.168.36.147 33714 10.168.36.144 11111 DTLSv13 TLS_AES_128_GCM_SHA256 secp256r1 - F - - T CjCs - - - +#close XXXX-XX-XX-XX-XX-XX diff --git a/testing/btest/Traces/tls/dtls13-wolfssl.pcap b/testing/btest/Traces/tls/dtls13-wolfssl.pcap new file mode 100644 index 0000000000000000000000000000000000000000..20b1b8ae96faebd999adeefe9ced4fb71af42ef9 GIT binary patch literal 3752 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 literal 0 HcmV?d00001 diff --git a/testing/btest/scripts/base/protocols/ssl/dtls-13.test b/testing/btest/scripts/base/protocols/ssl/dtls-13.test new file mode 100644 index 0000000000..61c358da21 --- /dev/null +++ b/testing/btest/scripts/base/protocols/ssl/dtls-13.test @@ -0,0 +1,6 @@ +# This tests a normal SSL connection and the log it outputs. + +# @TEST-EXEC: zeek -C -r $TRACES/tls/dtls13-wolfssl.pcap %INPUT +# @TEST-EXEC: btest-diff ssl.log +# @TEST-EXEC: test ! -f dpd.log +