From 47196e86760122bb21227b8241a3fcd6272d85ac Mon Sep 17 00:00:00 2001 From: Thomas Kriechbaumer Date: Mon, 5 Dec 2016 10:09:32 +0100 Subject: [PATCH] tls_parser: allow optional extensions fixes #1816 --- mitmproxy/contrib/tls/__init__.py | 4 -- .../{tls/_constructs.py => tls_parser.py} | 11 ++++-- mitmproxy/proxy/protocol/tls.py | 32 +++++++++------- test/mitmproxy/contrib/test_tls_parser.py | 38 +++++++++++++++++++ test/mitmproxy/protocol/test_tls.py | 26 +++++++++++++ 5 files changed, 90 insertions(+), 21 deletions(-) delete mode 100644 mitmproxy/contrib/tls/__init__.py rename mitmproxy/contrib/{tls/_constructs.py => tls_parser.py} (94%) create mode 100644 test/mitmproxy/contrib/test_tls_parser.py create mode 100644 test/mitmproxy/protocol/test_tls.py diff --git a/mitmproxy/contrib/tls/__init__.py b/mitmproxy/contrib/tls/__init__.py deleted file mode 100644 index 450986f7a..000000000 --- a/mitmproxy/contrib/tls/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# This file is dual licensed under the terms of the Apache License, Version -# 2.0, and the BSD License. See the LICENSE file in the root of this repository -# for complete details. - diff --git a/mitmproxy/contrib/tls/_constructs.py b/mitmproxy/contrib/tls_parser.py similarity index 94% rename from mitmproxy/contrib/tls/_constructs.py rename to mitmproxy/contrib/tls_parser.py index 8b3f12af9..61fb3e3ee 100644 --- a/mitmproxy/contrib/tls/_constructs.py +++ b/mitmproxy/contrib/tls_parser.py @@ -1,3 +1,6 @@ +# This file originally comes from https://github.com/pyca/tls/blob/master/tls/_constructs.py. +# Modified by the mitmproxy team. + # This file is dual licensed under the terms of the Apache License, Version # 2.0, and the BSD License. See the LICENSE file in the root of this repository # for complete details. @@ -113,9 +116,11 @@ Extension = "Extension" / Struct( ) ) -extensions = "extensions" / Struct( - Int16ub, - "extensions" / GreedyRange(Extension) +extensions = "extensions" / Optional( + Struct( + Int16ub, + "extensions" / GreedyRange(Extension) + ) ) ClientHello = "ClientHello" / Struct( diff --git a/mitmproxy/proxy/protocol/tls.py b/mitmproxy/proxy/protocol/tls.py index 58d9e28d3..08ce53d06 100644 --- a/mitmproxy/proxy/protocol/tls.py +++ b/mitmproxy/proxy/protocol/tls.py @@ -4,7 +4,7 @@ from typing import Union import construct from mitmproxy import exceptions -from mitmproxy.contrib.tls import _constructs +from mitmproxy.contrib import tls_parser from mitmproxy.proxy.protocol import base from mitmproxy.net import check @@ -248,7 +248,7 @@ def get_client_hello(client_conn): class TlsClientHello: def __init__(self, raw_client_hello): - self._client_hello = _constructs.ClientHello.parse(raw_client_hello) + self._client_hello = tls_parser.ClientHello.parse(raw_client_hello) def raw(self): return self._client_hello @@ -259,21 +259,25 @@ class TlsClientHello: @property def sni(self): - for extension in self._client_hello.extensions.extensions: - is_valid_sni_extension = ( - extension.type == 0x00 and - len(extension.server_names) == 1 and - extension.server_names[0].name_type == 0 and - check.is_valid_host(extension.server_names[0].host_name) - ) - if is_valid_sni_extension: - return extension.server_names[0].host_name.decode("idna") + if self._client_hello.extensions: + for extension in self._client_hello.extensions.extensions: + is_valid_sni_extension = ( + extension.type == 0x00 and + len(extension.server_names) == 1 and + extension.server_names[0].name_type == 0 and + check.is_valid_host(extension.server_names[0].host_name) + ) + if is_valid_sni_extension: + return extension.server_names[0].host_name.decode("idna") + return None @property def alpn_protocols(self): - for extension in self._client_hello.extensions.extensions: - if extension.type == 0x10: - return list(extension.alpn_protocols) + if self._client_hello.extensions: + for extension in self._client_hello.extensions.extensions: + if extension.type == 0x10: + return list(extension.alpn_protocols) + return [] @classmethod def from_client_conn(cls, client_conn): diff --git a/test/mitmproxy/contrib/test_tls_parser.py b/test/mitmproxy/contrib/test_tls_parser.py new file mode 100644 index 000000000..66972b623 --- /dev/null +++ b/test/mitmproxy/contrib/test_tls_parser.py @@ -0,0 +1,38 @@ +from mitmproxy.contrib import tls_parser + + +def test_parse_chrome(): + """ + Test if we properly parse a ClientHello sent by Chrome 54. + """ + data = bytes.fromhex( + "03033b70638d2523e1cba15f8364868295305e9c52aceabda4b5147210abc783e6e1000022c02bc02fc02cc030" + "cca9cca8cc14cc13c009c013c00ac014009c009d002f0035000a0100006cff0100010000000010000e00000b65" + "78616d706c652e636f6d0017000000230000000d00120010060106030501050304010403020102030005000501" + "00000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a00080006001d00" + "170018" + ) + c = tls_parser.ClientHello.parse(data) + assert c.version.major == 3 + assert c.version.minor == 3 + + alpn = [a for a in c.extensions.extensions if a.type == 16] + assert len(alpn) == 1 + assert alpn[0].alpn_protocols == [b"h2", b"http/1.1"] + + sni = [a for a in c.extensions.extensions if a.type == 0] + assert len(sni) == 1 + assert sni[0].server_names[0].name_type == 0 + assert sni[0].server_names[0].host_name == b"example.com" + + +def test_parse_no_extensions(): + data = bytes.fromhex( + "03015658a756ab2c2bff55f636814deac086b7ca56b65058c7893ffc6074f5245f70205658a75475103a152637" + "78e1bb6d22e8bbd5b6b0a3a59760ad354e91ba20d353001a0035002f000a000500040009000300060008006000" + "61006200640100" + ) + c = tls_parser.ClientHello.parse(data) + assert c.version.major == 3 + assert c.version.minor == 1 + assert c.extensions is None diff --git a/test/mitmproxy/protocol/test_tls.py b/test/mitmproxy/protocol/test_tls.py new file mode 100644 index 000000000..e17ee46fe --- /dev/null +++ b/test/mitmproxy/protocol/test_tls.py @@ -0,0 +1,26 @@ +from mitmproxy.proxy.protocol.tls import TlsClientHello + + +class TestClientHello: + + def test_no_extensions(self): + data = bytes.fromhex( + "03015658a756ab2c2bff55f636814deac086b7ca56b65058c7893ffc6074f5245f70205658a75475103a152637" + "78e1bb6d22e8bbd5b6b0a3a59760ad354e91ba20d353001a0035002f000a000500040009000300060008006000" + "61006200640100" + ) + c = TlsClientHello(data) + assert c.sni is None + assert c.alpn_protocols == [] + + def test_extensions(self): + data = bytes.fromhex( + "03033b70638d2523e1cba15f8364868295305e9c52aceabda4b5147210abc783e6e1000022c02bc02fc02cc030" + "cca9cca8cc14cc13c009c013c00ac014009c009d002f0035000a0100006cff0100010000000010000e00000b65" + "78616d706c652e636f6d0017000000230000000d00120010060106030501050304010403020102030005000501" + "00000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a00080006001d00" + "170018" + ) + c = TlsClientHello(data) + assert c.sni == 'example.com' + assert c.alpn_protocols == [b'h2', b'http/1.1']