Skip to content

Commit 0bfd7d7

Browse files
Fix client for HTTPS endpoints with Python 3.12 (#1454)
* Fix client for HTTPS endpoints for python 3.12 * Use only `DEFAULT_SSL_CONTEXT_OPTIONS` to prevent `CRIME` attacks * lint fix * install certifi types * pre commit * Avoid using dep options for >=3.10 * More tests * Fix lint issues * Use `3.12` by default everywhere * Skip `grout -h` test on windows * Add http only test * Rollback to python versions, 3.12 causes doc/lint failures * [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Keep proxy.py benchmarking on so that users dont run into surprises * Install certifi * No need of certifi --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 74c42f6 commit 0bfd7d7

File tree

7 files changed

+129
-24
lines changed

7 files changed

+129
-24
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -2563,7 +2563,7 @@ usage: -m [-h] [--tunnel-hostname TUNNEL_HOSTNAME] [--tunnel-port TUNNEL_PORT]
25632563
[--filtered-client-ips FILTERED_CLIENT_IPS]
25642564
[--filtered-url-regex-config FILTERED_URL_REGEX_CONFIG]
25652565

2566-
proxy.py v2.4.6.dev27+g975b6b68.d20240811
2566+
proxy.py v2.4.6.dev25+g2754b928.d20240812
25672567

25682568
options:
25692569
-h, --help show this help message and exit
@@ -2692,8 +2692,8 @@ options:
26922692
Default: None. Signing certificate to use for signing
26932693
dynamically generated HTTPS certificates. If used,
26942694
must also pass --ca-key-file and --ca-signing-key-file
2695-
--ca-file CA_FILE Default: /Users/abhinavsingh/Dev/proxy.py/.venv3118/li
2696-
b/python3.11/site-packages/certifi/cacert.pem. Provide
2695+
--ca-file CA_FILE Default: /Users/abhinavsingh/Dev/proxy.py/.venv3122/li
2696+
b/python3.12/site-packages/certifi/cacert.pem. Provide
26972697
path to custom CA bundle for peer certificate
26982698
verification
26992699
--ca-signing-key-file CA_SIGNING_KEY_FILE

benchmark/compare.sh

+4-4
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,10 @@ benchmark_asgi() {
9393
fi
9494
}
9595

96-
# echo "============================="
97-
# echo "Benchmarking Proxy.Py"
98-
# PYTHONPATH=. benchmark_lib proxy $PROXYPY_PORT
99-
# echo "============================="
96+
echo "============================="
97+
echo "Benchmarking Proxy.Py"
98+
PYTHONPATH=. benchmark_lib proxy $PROXYPY_PORT
99+
echo "============================="
100100

101101
# echo "============================="
102102
# echo "Benchmarking Blacksheep"

benchmark/requirements.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
aiohttp==3.8.2
1+
aiohttp==3.10.3
22
# Blacksheep depends upon essentials_openapi which is pinned to pyyaml==5.4.1
33
# and pyyaml>5.3.1 is broken for cython 3
44
# See https://github.com/yaml/pyyaml/issues/724#issuecomment-1638587228

proxy/common/constants.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ def _env_threadless_compliant() -> bool:
161161
DEFAULT_WAIT_FOR_TASKS_TIMEOUT = 1 / 1000
162162
DEFAULT_INACTIVE_CONN_CLEANUP_TIMEOUT = 1 # in seconds
163163
DEFAULT_SSL_CONTEXT_OPTIONS = (
164-
ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
164+
ssl.OP_NO_COMPRESSION
165+
if sys.version_info >= (3, 10)
166+
else (ssl.OP_NO_SSLv2 | ssl.OP_NO_SSLv3 | ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1)
165167
)
166168

167169
DEFAULT_DEVTOOLS_DOC_URL = 'http://proxy'

proxy/http/client.py

+25-15
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,18 @@
99
:license: BSD, see LICENSE for more details.
1010
"""
1111
import ssl
12+
import logging
1213
from typing import Optional
1314

1415
from .parser import HttpParser, httpParserTypes
16+
from ..common.types import TcpOrTlsSocket
1517
from ..common.utils import build_http_request, new_socket_connection
16-
from ..common.constants import HTTPS_PROTO, DEFAULT_TIMEOUT
18+
from ..common.constants import (
19+
HTTPS_PROTO, DEFAULT_TIMEOUT, DEFAULT_SSL_CONTEXT_OPTIONS,
20+
)
21+
22+
23+
logger = logging.getLogger(__name__)
1724

1825

1926
def client(
@@ -25,34 +32,37 @@ def client(
2532
conn_close: bool = True,
2633
scheme: bytes = HTTPS_PROTO,
2734
timeout: float = DEFAULT_TIMEOUT,
35+
content_type: bytes = b'application/x-www-form-urlencoded',
2836
) -> Optional[HttpParser]:
2937
"""Makes a request to remote registry endpoint"""
3038
request = build_http_request(
3139
method=method,
3240
url=path,
3341
headers={
3442
b'Host': host,
35-
b'Content-Type': b'application/x-www-form-urlencoded',
43+
b'Content-Type': content_type,
3644
},
3745
body=body,
3846
conn_close=conn_close,
3947
)
4048
try:
4149
conn = new_socket_connection((host.decode(), port))
42-
except ConnectionRefusedError:
50+
except Exception as exc:
51+
logger.exception('Cannot establish connection', exc_info=exc)
4352
return None
44-
try:
45-
sock = (
46-
ssl.wrap_socket(sock=conn, ssl_version=ssl.PROTOCOL_TLSv1_2)
47-
if scheme == HTTPS_PROTO
48-
else conn
49-
)
50-
except Exception:
51-
conn.close()
52-
return None
53-
parser = HttpParser(
54-
httpParserTypes.RESPONSE_PARSER,
55-
)
53+
sock: TcpOrTlsSocket = conn
54+
if scheme == HTTPS_PROTO:
55+
try:
56+
ctx = ssl.SSLContext(protocol=(ssl.PROTOCOL_TLS_CLIENT))
57+
ctx.options |= DEFAULT_SSL_CONTEXT_OPTIONS
58+
ctx.verify_mode = ssl.CERT_REQUIRED
59+
ctx.load_default_certs()
60+
sock = ctx.wrap_socket(conn, server_hostname=host.decode())
61+
except Exception as exc:
62+
logger.exception('Unable to wrap', exc_info=exc)
63+
conn.close()
64+
return None
65+
parser = HttpParser(httpParserTypes.RESPONSE_PARSER)
5666
sock.settimeout(timeout)
5767
try:
5868
sock.sendall(request)

tests/http/test_client.py

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
proxy.py
4+
~~~~~~~~
5+
⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
6+
Network monitoring, controls & Application development, testing, debugging.
7+
8+
:copyright: (c) 2013-present by Abhinav Singh and contributors.
9+
:license: BSD, see LICENSE for more details.
10+
"""
11+
import unittest
12+
13+
from proxy.http.client import client
14+
15+
16+
class TestClient(unittest.TestCase):
17+
18+
def test_http(self) -> None:
19+
response = client(
20+
host=b'google.com',
21+
port=80,
22+
scheme=b'http',
23+
path=b'/',
24+
method=b'GET',
25+
content_type=b'text/html',
26+
)
27+
assert response is not None
28+
self.assertEqual(response.code, b'301')
29+
30+
def test_client(self) -> None:
31+
response = client(
32+
host=b'google.com',
33+
port=443,
34+
scheme=b'https',
35+
path=b'/',
36+
method=b'GET',
37+
content_type=b'text/html',
38+
)
39+
assert response is not None
40+
self.assertEqual(response.code, b'301')
41+
42+
def test_client_connection_refused(self) -> None:
43+
response = client(
44+
host=b'cannot-establish-connection.com',
45+
port=443,
46+
scheme=b'https',
47+
path=b'/',
48+
method=b'GET',
49+
content_type=b'text/html',
50+
)
51+
assert response is None
52+
53+
def test_cannot_ssl_wrap(self) -> None:
54+
response = client(
55+
host=b'example.com',
56+
port=80,
57+
scheme=b'https',
58+
path=b'/',
59+
method=b'GET',
60+
content_type=b'text/html',
61+
)
62+
assert response is None

tests/test_grout.py

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# -*- coding: utf-8 -*-
2+
"""
3+
proxy.py
4+
~~~~~~~~
5+
⚡⚡⚡ Fast, Lightweight, Pluggable, TLS interception capable proxy server focused on
6+
Network monitoring, controls & Application development, testing, debugging.
7+
8+
:copyright: (c) 2013-present by Abhinav Singh and contributors.
9+
:license: BSD, see LICENSE for more details.
10+
"""
11+
import sys
12+
13+
import pytest
14+
import unittest
15+
16+
from proxy import grout
17+
from proxy.common.constants import IS_WINDOWS
18+
19+
20+
@pytest.mark.skipif(
21+
IS_WINDOWS,
22+
reason="sys.argv replacement don't really work on windows",
23+
)
24+
class TestGrout(unittest.TestCase):
25+
26+
def test_grout(self) -> None:
27+
with self.assertRaises(SystemExit):
28+
original = sys.argv
29+
sys.argv = ['grout', '-h']
30+
grout()
31+
sys.argv = original

0 commit comments

Comments
 (0)