tls_parser: allow optional extensions

fixes #1816
This commit is contained in:
Thomas Kriechbaumer 2016-12-05 10:09:32 +01:00
parent e44493bda5
commit 47196e8676
5 changed files with 90 additions and 21 deletions

View File

@ -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.

View File

@ -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(

View File

@ -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):

View File

@ -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

View File

@ -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']