TLS: Fix TLS tunneled within TLS

In order to retrieve the correct set of TLS information, previously
pinfo->curr_layer_num was used. However, this is not a stable
identifier between the first and later passes, as subdissectors that
couldn't dissect data due to fragmentation on the first pass aren't
called on later passes.

To fix issue #16109, the layer number wasn't used at all, which did
break TLS over TLS.

We now have pinfo->curr_proto_layer_num which specifically counts
the number of layers of the current protocol instead of the total
number of layers; using that instead fixes TLS within TLS (in most
situations; some very rare cases, e.g. DVB baseband frames with
multiple TCP PDUs, which might be from the same or from different
TCP connections, might not work, but those don't work currently either)
while not reopening #16109.

Add tests for both cases, the one fixed by the other workaround and
for TLS over TLS.

As noted in the comments to #16109, there are other dissectors that
use curr_layer_num that might break in some cases because it's not stable.

Fix #17977.
This commit is contained in:
John Thacker 2023-05-06 06:53:40 -04:00 committed by AndersBroman
parent 001930e1e5
commit e0f1f8dbf3
6 changed files with 73 additions and 9 deletions

View File

@ -601,16 +601,9 @@ dissect_ssl(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void *data _U_)
* - TLS within a different encrypted TLS tunnel.
*
* To support the second case, 'curr_layer_num_ssl' is used as identifier
* for the current TLS layer. It is however not a stable identifier for the
* second pass (Bug 16109). If the first decrypted record requests
* reassembly for HTTP, then the second pass will skip calling the dissector
* for the first record. That means that 'pinfo->curr_layer_num' will
* actually be lower the second time.
*
* Since this cannot be easily fixed, we will just break the (hopefully less
* common) case of TLS tunneled within TLS.
* for the current TLS layer.
*/
guint8 curr_layer_num_ssl = 0; // pinfo->curr_layer_num;
guint8 curr_layer_num_ssl = pinfo->curr_proto_layer_num;
ti = NULL;
ssl_tree = NULL;

Binary file not shown.

View File

@ -0,0 +1,27 @@
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEAuIpwxT/BdUhlQNwpm11Wrz6oEV5glXDeFBSp3CUd7SX+2O1z
iXxe6cW0p2ljPQjnvVFe/NTkDcGhgaXVH+8/sk1N1s101L98ZxjLaMaMKhj+yf6Q
IOvN1weaMt9qnl8JRrk5f6aNGWTr+dqLU2veE6zX1oT/lFtDTafopyAFGpEJnDRz
NudARQpt1CnakRu2zkKsQ/ApMjRa9Zm4w2Kf+M23IeU+j/gi5wAU9/D39ke67tKO
GDNXYfMfFcgsmDrH+a2xrU3Zf9KtXiPI5lw2dXs3UG19Sr+HRoydCrIPPJBMzUKw
9M3Hc7i0aMO2vn94GJKC6mqpOeeQIWo5Y4GMnwIDAQABAoIBAE6eLAzcbH6aqQhI
wzD9QsDF4LQFkQAZZYMIipTO+0DcvwWLo30fDxBoud3Yd/64nIF6+QydZcq2gyfI
jlNcibZcWJz6SpuYOFdzqLSqYWxN2b4URTLBQqApDPg/VhzCQCFxJ53KRrJa3G1F
PbX8bk/TguBRKND7UGD095i7e3ElPmmTEe4RSpzqYqCsUQ9e1ndhAVRKYSMXHdbY
N4wtM+P2bIJghWQjyo2P4Jb7gXF47ghBett4krduvxv96m3dIuGAM0Y7V9okclQT
7cv/8iDUXBhbWzIsB3bXG0lm9QttQ3T0doSlqg0q2DgLiYBHEhK9SiUAVt+dQftP
kRYa+gECgYEA9c1tNXM2u4SOgMUyiFkU8Slr7MbywMq2jnalbea63Q8JxNWNW9VX
wQOBZvKK/BEGEP9kOeS1aMQ5dUZOxzI6SN9eCfEDFzIA5VknaZcJdBW+X3kyy/wF
7awEWcZ6B9HrtRZ+vA2rQ4LyfJd2M1eGuK6+0WJa0hGjglbFBrHJNLECgYEAwDJg
ls3IWhPEQXWFwXXv1SM63hnbUpGFICLqLz26+qZ64hR52ZcvuZsS3l7slO+5YSim
fQNXXYZiwQm+SdtL5V5anUKvtaMWw9VlZVNOFPYyh1RyaGr5CO0j5aoC76DnD2ee
cRQPv8Uodf50FR5Y9uyxijGOfA3SMj4WfLppak8CgYAnDg3VGUpP/x7ZTPvbeDQA
oXE5fN7jTRI2jpl4XdnA9/u4X6oHNl2sGE9+OPlmVZoeJ0YYgMNmMw9iF9q6gbuL
CpqZf8ba76H+zuyZNVtWK4JFDy/IA3I5skQ6s3N+PJdz/XADlzRoFK1MqJAqVjTc
sT82a6c8i3rsYbcKekMa8QKBgBPjorc4aGlZ7k9P2B2jFMSbtrXROy8aPAqNUmq9
GqJhpAnNUKbBzICKmbNFY3ouLKLvT/tT4zCcfY+4cGa4OOxtjTcE9aX4UJzHcoy/
yC7HI4d5p7VCjK7ty28y3sbpgb/IW08cYlzYDE3ZnS2qTE6RQ/YnFrWjwILOhgk2
ST5lAoGAIYpxE2Qcz9nHHfx2Fn70Ysg8XUoyk61QBSeZEmVBPp1XhVEz7idJc4PZ
tozarw3mo9aqY/sX2f+mwLhL0E5wxy6kcVICkYhE9y45bESAeWk9YIlOMUzTW84C
bVJUJiciDPuTJks6XVrRzc0GayaxpblhKHeR7w+6V22+lS7wgTg=
-----END RSA PRIVATE KEY-----

View File

@ -562,6 +562,22 @@ class TestDecryptTLS:
), encoding='utf-8', env=test_env)
assert 'example.com\t\n\t200\nexample.net\t\n\t200\n' == output
def test_tls_over_tls(self, cmd_tshark, dirs, capture_file, features, test_env):
'''TLS using the server's private key with p < q
(test whether libgcrypt is correctly called)'''
if not features.have_gnutls:
pytest.skip('Requires GnuTLS.')
key_file = os.path.join(dirs.key_dir, 'tls-over-tls.key')
output = subprocess.check_output((cmd_tshark,
'-r', capture_file('tls-over-tls.pcapng.gz'),
'-o', 'tls.keys_list:0.0.0.0,443,http,{}'.format(key_file),
'-z', 'expert,tls.handshake.certificates',
'-Tfields',
'-e', 'tls.handshake.certificate_length',
'-Y', 'tls.handshake.certificates',
), encoding='utf-8', env=test_env)
assert '1152,1115,1352\n1152\n1412,1434,1382\n' == output
class TestDecryptZigbee:
def test_zigbee(self, cmd_tshark, capture_file, test_env):

View File

@ -718,6 +718,18 @@ class TestDissectTls:
expected = ''.join('%04x\n' % i for i in range(1, 1001))
assert stdout == expected
@staticmethod
def check_tls_reassembly_over_tcp_reassembly(cmd_tshark, capture_file, test_env,
extraArgs=[]):
stdout = subprocess.check_output([cmd_tshark,
'-r', capture_file('tls-fragmented-over-tcp-segmented.pcapng.gz'),
'-zexpert,note',
'-Yhttp.host',
'-Tfields', '-ehttp.host'] + extraArgs,
encoding='utf-8', env=test_env)
stdout = stdout.replace(',', '\n')
assert stdout == 'reports.crashlytics.com\n'
def test_tls_handshake_reassembly(self, cmd_tshark, capture_file, test_env):
'''Verify that TCP and TLS handshake reassembly works.'''
self.check_tls_handshake_reassembly(cmd_tshark, capture_file, test_env)
@ -727,6 +739,22 @@ class TestDissectTls:
self.check_tls_handshake_reassembly(
cmd_tshark, capture_file, test_env, extraArgs=['-2'])
def test_tls_reassembly_over_tcp_reassembly(self, cmd_tshark, capture_file, features, test_env):
'''Verify that TLS reassembly over TCP reassembly works.'''
if not features.have_gnutls:
pytest.skip('Requires GnuTLS.')
self.check_tls_reassembly_over_tcp_reassembly(cmd_tshark, capture_file, test_env)
def test_tls_reassembly_over_tcp_reassembly_2(self, cmd_tshark, capture_file, features, test_env):
'''Verify that TLS reassembly over TCP reassembly works (second pass).'''
# pinfo->curr_layer_num can be different on the second pass than the
# first pass, because the HTTP dissector isn't called for the first
# TLS record on the second pass.
if not features.have_gnutls:
pytest.skip('Requires GnuTLS.')
self.check_tls_reassembly_over_tcp_reassembly(cmd_tshark, capture_file,
test_env, extraArgs=['-2'])
class TestDissectQuic:
@staticmethod
def check_quic_tls_handshake_reassembly(cmd_tshark, capture_file, test_env,