mirror of
https://github.com/Grasscutters/mitmproxy.git
synced 2024-11-25 09:37:37 +00:00
move ClientHello
to mitmproxy.tls
This commit is contained in:
parent
9f39e2f387
commit
bdf4e31c58
@ -1,4 +1,3 @@
|
|||||||
import io
|
|
||||||
import ipaddress
|
import ipaddress
|
||||||
import os
|
import os
|
||||||
import threading
|
import threading
|
||||||
@ -11,12 +10,9 @@ import certifi
|
|||||||
|
|
||||||
from OpenSSL.crypto import X509
|
from OpenSSL.crypto import X509
|
||||||
from cryptography.hazmat.primitives.asymmetric import rsa
|
from cryptography.hazmat.primitives.asymmetric import rsa
|
||||||
from kaitaistruct import KaitaiStream
|
|
||||||
|
|
||||||
from OpenSSL import SSL, crypto
|
from OpenSSL import SSL, crypto
|
||||||
from mitmproxy import certs
|
from mitmproxy import certs
|
||||||
from mitmproxy.contrib.kaitaistruct import tls_client_hello
|
|
||||||
from mitmproxy.net import check
|
|
||||||
|
|
||||||
|
|
||||||
# redeclared here for strict type checking
|
# redeclared here for strict type checking
|
||||||
@ -279,49 +275,3 @@ def is_tls_record_magic(d):
|
|||||||
d[1] == 0x03 and
|
d[1] == 0x03 and
|
||||||
0x0 <= d[2] <= 0x03
|
0x0 <= d[2] <= 0x03
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class ClientHello:
|
|
||||||
|
|
||||||
def __init__(self, raw_client_hello):
|
|
||||||
self._client_hello = tls_client_hello.TlsClientHello(
|
|
||||||
KaitaiStream(io.BytesIO(raw_client_hello))
|
|
||||||
)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def cipher_suites(self) -> List[int]:
|
|
||||||
return self._client_hello.cipher_suites.cipher_suites
|
|
||||||
|
|
||||||
@property
|
|
||||||
def sni(self) -> Optional[str]:
|
|
||||||
if self._client_hello.extensions:
|
|
||||||
for extension in self._client_hello.extensions.extensions:
|
|
||||||
is_valid_sni_extension = (
|
|
||||||
extension.type == 0x00 and
|
|
||||||
len(extension.body.server_names) == 1 and
|
|
||||||
extension.body.server_names[0].name_type == 0 and
|
|
||||||
check.is_valid_host(extension.body.server_names[0].host_name)
|
|
||||||
)
|
|
||||||
if is_valid_sni_extension:
|
|
||||||
return extension.body.server_names[0].host_name.decode("ascii")
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def alpn_protocols(self) -> List[bytes]:
|
|
||||||
if self._client_hello.extensions:
|
|
||||||
for extension in self._client_hello.extensions.extensions:
|
|
||||||
if extension.type == 0x10:
|
|
||||||
return list(x.name for x in extension.body.alpn_protocols)
|
|
||||||
return []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def extensions(self) -> List[Tuple[int, bytes]]:
|
|
||||||
ret = []
|
|
||||||
if self._client_hello.extensions:
|
|
||||||
for extension in self._client_hello.extensions.extensions:
|
|
||||||
body = getattr(extension, "_raw_body", extension.body)
|
|
||||||
ret.append((extension.type, body))
|
|
||||||
return ret
|
|
||||||
|
|
||||||
def __repr__(self):
|
|
||||||
return f"ClientHello(sni: {self.sni}, alpn_protocols: {self.alpn_protocols})"
|
|
||||||
|
@ -4,8 +4,8 @@ from dataclasses import dataclass
|
|||||||
from typing import Iterator, Literal, Optional, Tuple
|
from typing import Iterator, Literal, Optional, Tuple
|
||||||
|
|
||||||
from OpenSSL import SSL
|
from OpenSSL import SSL
|
||||||
|
from mitmproxy.tls import ClientHello
|
||||||
from mitmproxy import certs, connection
|
from mitmproxy import certs, connection
|
||||||
from mitmproxy.net import tls as net_tls
|
|
||||||
from mitmproxy.proxy import commands, events, layer, tunnel
|
from mitmproxy.proxy import commands, events, layer, tunnel
|
||||||
from mitmproxy.proxy import context
|
from mitmproxy.proxy import context
|
||||||
from mitmproxy.proxy.commands import StartHook
|
from mitmproxy.proxy.commands import StartHook
|
||||||
@ -70,7 +70,7 @@ def get_client_hello(data: bytes) -> Optional[bytes]:
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def parse_client_hello(data: bytes) -> Optional[net_tls.ClientHello]:
|
def parse_client_hello(data: bytes) -> Optional[ClientHello]:
|
||||||
"""
|
"""
|
||||||
Check if the supplied bytes contain a full ClientHello message,
|
Check if the supplied bytes contain a full ClientHello message,
|
||||||
and if so, parse it.
|
and if so, parse it.
|
||||||
@ -86,7 +86,7 @@ def parse_client_hello(data: bytes) -> Optional[net_tls.ClientHello]:
|
|||||||
client_hello = get_client_hello(data)
|
client_hello = get_client_hello(data)
|
||||||
if client_hello:
|
if client_hello:
|
||||||
try:
|
try:
|
||||||
return net_tls.ClientHello(client_hello[4:])
|
return ClientHello(client_hello[4:])
|
||||||
except EOFError as e:
|
except EOFError as e:
|
||||||
raise ValueError("Invalid ClientHello") from e
|
raise ValueError("Invalid ClientHello") from e
|
||||||
return None
|
return None
|
||||||
@ -102,7 +102,7 @@ HTTP_ALPNS = (b"h2",) + HTTP1_ALPNS
|
|||||||
class ClientHelloData:
|
class ClientHelloData:
|
||||||
context: context.Context
|
context: context.Context
|
||||||
"""The context object for this connection."""
|
"""The context object for this connection."""
|
||||||
client_hello: net_tls.ClientHello
|
client_hello: ClientHello
|
||||||
"""The entire parsed TLS ClientHello."""
|
"""The entire parsed TLS ClientHello."""
|
||||||
ignore_connection: bool = False
|
ignore_connection: bool = False
|
||||||
"""
|
"""
|
||||||
|
53
mitmproxy/tls.py
Normal file
53
mitmproxy/tls.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import io
|
||||||
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
|
from kaitaistruct import KaitaiStream
|
||||||
|
|
||||||
|
from mitmproxy.contrib.kaitaistruct import tls_client_hello
|
||||||
|
from mitmproxy.net import check
|
||||||
|
|
||||||
|
|
||||||
|
class ClientHello:
|
||||||
|
|
||||||
|
def __init__(self, raw_client_hello):
|
||||||
|
self._client_hello = tls_client_hello.TlsClientHello(
|
||||||
|
KaitaiStream(io.BytesIO(raw_client_hello))
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cipher_suites(self) -> List[int]:
|
||||||
|
return self._client_hello.cipher_suites.cipher_suites
|
||||||
|
|
||||||
|
@property
|
||||||
|
def sni(self) -> Optional[str]:
|
||||||
|
if self._client_hello.extensions:
|
||||||
|
for extension in self._client_hello.extensions.extensions:
|
||||||
|
is_valid_sni_extension = (
|
||||||
|
extension.type == 0x00 and
|
||||||
|
len(extension.body.server_names) == 1 and
|
||||||
|
extension.body.server_names[0].name_type == 0 and
|
||||||
|
check.is_valid_host(extension.body.server_names[0].host_name)
|
||||||
|
)
|
||||||
|
if is_valid_sni_extension:
|
||||||
|
return extension.body.server_names[0].host_name.decode("ascii")
|
||||||
|
return None
|
||||||
|
|
||||||
|
@property
|
||||||
|
def alpn_protocols(self) -> List[bytes]:
|
||||||
|
if self._client_hello.extensions:
|
||||||
|
for extension in self._client_hello.extensions.extensions:
|
||||||
|
if extension.type == 0x10:
|
||||||
|
return list(x.name for x in extension.body.alpn_protocols)
|
||||||
|
return []
|
||||||
|
|
||||||
|
@property
|
||||||
|
def extensions(self) -> List[Tuple[int, bytes]]:
|
||||||
|
ret = []
|
||||||
|
if self._client_hello.extensions:
|
||||||
|
for extension in self._client_hello.extensions.extensions:
|
||||||
|
body = getattr(extension, "_raw_body", extension.body)
|
||||||
|
ret.append((extension.type, body))
|
||||||
|
return ret
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"ClientHello(sni: {self.sni}, alpn_protocols: {self.alpn_protocols})"
|
@ -4,17 +4,6 @@ from OpenSSL import SSL
|
|||||||
from mitmproxy import certs
|
from mitmproxy import certs
|
||||||
from mitmproxy.net import tls
|
from mitmproxy.net import tls
|
||||||
|
|
||||||
CLIENT_HELLO_NO_EXTENSIONS = bytes.fromhex(
|
|
||||||
"03015658a756ab2c2bff55f636814deac086b7ca56b65058c7893ffc6074f5245f70205658a75475103a152637"
|
|
||||||
"78e1bb6d22e8bbd5b6b0a3a59760ad354e91ba20d353001a0035002f000a000500040009000300060008006000"
|
|
||||||
"61006200640100"
|
|
||||||
)
|
|
||||||
FULL_CLIENT_HELLO_NO_EXTENSIONS = (
|
|
||||||
b"\x16\x03\x03\x00\x65" # record layer
|
|
||||||
b"\x01\x00\x00\x61" + # handshake header
|
|
||||||
CLIENT_HELLO_NO_EXTENSIONS
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def test_make_master_secret_logger():
|
def test_make_master_secret_logger():
|
||||||
assert tls.make_master_secret_logger(None) is None
|
assert tls.make_master_secret_logger(None) is None
|
||||||
@ -84,43 +73,3 @@ def test_is_record_magic():
|
|||||||
assert tls.is_tls_record_magic(b"\x16\x03\x01")
|
assert tls.is_tls_record_magic(b"\x16\x03\x01")
|
||||||
assert tls.is_tls_record_magic(b"\x16\x03\x02")
|
assert tls.is_tls_record_magic(b"\x16\x03\x02")
|
||||||
assert tls.is_tls_record_magic(b"\x16\x03\x03")
|
assert tls.is_tls_record_magic(b"\x16\x03\x03")
|
||||||
|
|
||||||
|
|
||||||
class TestClientHello:
|
|
||||||
def test_no_extensions(self):
|
|
||||||
c = tls.ClientHello(CLIENT_HELLO_NO_EXTENSIONS)
|
|
||||||
assert repr(c)
|
|
||||||
assert c.sni is None
|
|
||||||
assert c.cipher_suites == [53, 47, 10, 5, 4, 9, 3, 6, 8, 96, 97, 98, 100]
|
|
||||||
assert c.alpn_protocols == []
|
|
||||||
assert c.extensions == []
|
|
||||||
|
|
||||||
def test_extensions(self):
|
|
||||||
data = bytes.fromhex(
|
|
||||||
"03033b70638d2523e1cba15f8364868295305e9c52aceabda4b5147210abc783e6e1000022c02bc02fc02cc030"
|
|
||||||
"cca9cca8cc14cc13c009c013c00ac014009c009d002f0035000a0100006cff0100010000000010000e00000b65"
|
|
||||||
"78616d706c652e636f6d0017000000230000000d00120010060106030501050304010403020102030005000501"
|
|
||||||
"00000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a00080006001d00"
|
|
||||||
"170018"
|
|
||||||
)
|
|
||||||
c = tls.ClientHello(data)
|
|
||||||
assert repr(c)
|
|
||||||
assert c.sni == 'example.com'
|
|
||||||
assert c.cipher_suites == [
|
|
||||||
49195, 49199, 49196, 49200, 52393, 52392, 52244, 52243, 49161,
|
|
||||||
49171, 49162, 49172, 156, 157, 47, 53, 10
|
|
||||||
]
|
|
||||||
assert c.alpn_protocols == [b'h2', b'http/1.1']
|
|
||||||
assert c.extensions == [
|
|
||||||
(65281, b'\x00'),
|
|
||||||
(0, b'\x00\x0e\x00\x00\x0bexample.com'),
|
|
||||||
(23, b''),
|
|
||||||
(35, b''),
|
|
||||||
(13, b'\x00\x10\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x02\x01\x02\x03'),
|
|
||||||
(5, b'\x01\x00\x00\x00\x00'),
|
|
||||||
(18, b''),
|
|
||||||
(16, b'\x00\x0c\x02h2\x08http/1.1'),
|
|
||||||
(30032, b''),
|
|
||||||
(11, b'\x01\x00'),
|
|
||||||
(10, b'\x00\x06\x00\x1d\x00\x17\x00\x18')
|
|
||||||
]
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from hypothesis import given, example
|
from hypothesis import given, example
|
||||||
from hypothesis.strategies import binary, integers
|
from hypothesis.strategies import binary, integers
|
||||||
|
|
||||||
from mitmproxy.net.tls import ClientHello
|
from mitmproxy.tls import ClientHello
|
||||||
from mitmproxy.proxy.layers.tls import parse_client_hello
|
from mitmproxy.proxy.layers.tls import parse_client_hello
|
||||||
|
|
||||||
client_hello_with_extensions = bytes.fromhex(
|
client_hello_with_extensions = bytes.fromhex(
|
||||||
@ -17,7 +17,7 @@ client_hello_with_extensions = bytes.fromhex(
|
|||||||
|
|
||||||
@given(i=integers(0, len(client_hello_with_extensions)), data=binary())
|
@given(i=integers(0, len(client_hello_with_extensions)), data=binary())
|
||||||
@example(i=183, data=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
@example(i=183, data=b'\x00\x00\x00\x00\x00\x00\x00\x00\x00')
|
||||||
def test_fuzz_h2_request_chunks(i, data):
|
def test_fuzz_parse_client_hello(i, data):
|
||||||
try:
|
try:
|
||||||
ch = parse_client_hello(client_hello_with_extensions[:i] + data)
|
ch = parse_client_hello(client_hello_with_extensions[:i] + data)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
|
53
test/mitmproxy/test_tls.py
Normal file
53
test/mitmproxy/test_tls.py
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
from mitmproxy import tls
|
||||||
|
|
||||||
|
|
||||||
|
CLIENT_HELLO_NO_EXTENSIONS = bytes.fromhex(
|
||||||
|
"03015658a756ab2c2bff55f636814deac086b7ca56b65058c7893ffc6074f5245f70205658a75475103a152637"
|
||||||
|
"78e1bb6d22e8bbd5b6b0a3a59760ad354e91ba20d353001a0035002f000a000500040009000300060008006000"
|
||||||
|
"61006200640100"
|
||||||
|
)
|
||||||
|
FULL_CLIENT_HELLO_NO_EXTENSIONS = (
|
||||||
|
b"\x16\x03\x03\x00\x65" # record layer
|
||||||
|
b"\x01\x00\x00\x61" + # handshake header
|
||||||
|
CLIENT_HELLO_NO_EXTENSIONS
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestClientHello:
|
||||||
|
def test_no_extensions(self):
|
||||||
|
c = tls.ClientHello(CLIENT_HELLO_NO_EXTENSIONS)
|
||||||
|
assert repr(c)
|
||||||
|
assert c.sni is None
|
||||||
|
assert c.cipher_suites == [53, 47, 10, 5, 4, 9, 3, 6, 8, 96, 97, 98, 100]
|
||||||
|
assert c.alpn_protocols == []
|
||||||
|
assert c.extensions == []
|
||||||
|
|
||||||
|
def test_extensions(self):
|
||||||
|
data = bytes.fromhex(
|
||||||
|
"03033b70638d2523e1cba15f8364868295305e9c52aceabda4b5147210abc783e6e1000022c02bc02fc02cc030"
|
||||||
|
"cca9cca8cc14cc13c009c013c00ac014009c009d002f0035000a0100006cff0100010000000010000e00000b65"
|
||||||
|
"78616d706c652e636f6d0017000000230000000d00120010060106030501050304010403020102030005000501"
|
||||||
|
"00000000001200000010000e000c02683208687474702f312e3175500000000b00020100000a00080006001d00"
|
||||||
|
"170018"
|
||||||
|
)
|
||||||
|
c = tls.ClientHello(data)
|
||||||
|
assert repr(c)
|
||||||
|
assert c.sni == 'example.com'
|
||||||
|
assert c.cipher_suites == [
|
||||||
|
49195, 49199, 49196, 49200, 52393, 52392, 52244, 52243, 49161,
|
||||||
|
49171, 49162, 49172, 156, 157, 47, 53, 10
|
||||||
|
]
|
||||||
|
assert c.alpn_protocols == [b'h2', b'http/1.1']
|
||||||
|
assert c.extensions == [
|
||||||
|
(65281, b'\x00'),
|
||||||
|
(0, b'\x00\x0e\x00\x00\x0bexample.com'),
|
||||||
|
(23, b''),
|
||||||
|
(35, b''),
|
||||||
|
(13, b'\x00\x10\x06\x01\x06\x03\x05\x01\x05\x03\x04\x01\x04\x03\x02\x01\x02\x03'),
|
||||||
|
(5, b'\x01\x00\x00\x00\x00'),
|
||||||
|
(18, b''),
|
||||||
|
(16, b'\x00\x0c\x02h2\x08http/1.1'),
|
||||||
|
(30032, b''),
|
||||||
|
(11, b'\x01\x00'),
|
||||||
|
(10, b'\x00\x06\x00\x1d\x00\x17\x00\x18')
|
||||||
|
]
|
Loading…
Reference in New Issue
Block a user