Warn if TLS version is unsupported by OpenSSL (#7139)

* warn if TLS version is unsupported by OpenSSL

fix #7138

* [autofix.ci] apply automated fixes

* coverage++

---------

Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
Maximilian Hils 2024-08-31 09:02:12 +02:00 committed by GitHub
parent 36df8c8fac
commit f2500dd0ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 83 additions and 1 deletions

View File

@ -20,6 +20,9 @@
([#7073](https://github.com/mitmproxy/mitmproxy/pull/7073), @mhils)
- Fix a bug where fragmented QUIC client hellos were not handled properly.
([#7067](https://github.com/mitmproxy/mitmproxy/pull/7067), @errorxyz)
- Emit a warning when users configure a TLS version that is not supported by the
current OpenSSL build.
([#7139](https://github.com/mitmproxy/mitmproxy/pull/7139), @mhils)
- Fix a bug where mitmproxy would crash when receiving `STOP_SENDING` QUIC frames.
([#7119](https://github.com/mitmproxy/mitmproxy/pull/7119), @mhils)
- mitmproxy now officially supports Python 3.13.

View File

@ -24,6 +24,8 @@ from mitmproxy.proxy.layers import modes
from mitmproxy.proxy.layers import quic
from mitmproxy.proxy.layers import tls as proxy_tls
logger = logging.getLogger(__name__)
# We manually need to specify this, otherwise OpenSSL may select a non-HTTP2 cipher by default.
# https://ssl-config.mozilla.org/#config=old
@ -441,7 +443,7 @@ class TlsConfig:
else None,
)
if self.certstore.default_ca.has_expired():
logging.warning(
logger.warning(
"The mitmproxy certificate authority has expired!\n"
"Please delete all CA-related files in your ~/.mitmproxy folder.\n"
"The CA will be regenerated automatically after restarting mitmproxy.\n"
@ -484,6 +486,34 @@ class TlsConfig:
f"Invalid ECDH curve: {ecdh_curve!r}"
) from e
if "tls_version_client_min" in updated:
self._warn_unsupported_version("tls_version_client_min", True)
if "tls_version_client_max" in updated:
self._warn_unsupported_version("tls_version_client_max", False)
if "tls_version_server_min" in updated:
self._warn_unsupported_version("tls_version_server_min", True)
if "tls_version_server_max" in updated:
self._warn_unsupported_version("tls_version_server_max", False)
def _warn_unsupported_version(self, attribute: str, warn_unbound: bool):
val = net_tls.Version[getattr(ctx.options, attribute)]
supported_versions = [
v for v in net_tls.Version if net_tls.is_supported_version(v)
]
supported_versions_str = ", ".join(v.name for v in supported_versions)
if val is net_tls.Version.UNBOUNDED:
if warn_unbound:
logger.info(
f"{attribute} has been set to {val.name}. Note that your "
f"OpenSSL build only supports the following TLS versions: {supported_versions_str}"
)
elif val not in supported_versions:
logger.warning(
f"{attribute} has been set to {val.name}, which is not supported by the current OpenSSL build. "
f"The current build only supports the following versions: {supported_versions_str}"
)
def get_cert(self, conn_context: context.Context) -> certs.CertStoreEntry:
"""
This function determines the Common Name (CN), Subject Alternative Names (SANs) and Organization Name

View File

@ -3,6 +3,7 @@ import threading
from collections.abc import Callable
from collections.abc import Iterable
from enum import Enum
from functools import cache
from functools import lru_cache
from pathlib import Path
from typing import Any
@ -58,6 +59,22 @@ DEFAULT_MAX_VERSION = Version.UNBOUNDED
DEFAULT_OPTIONS = SSL.OP_CIPHER_SERVER_PREFERENCE | SSL.OP_NO_COMPRESSION
@cache
def is_supported_version(version: Version):
client_ctx = SSL.Context(SSL.TLS_CLIENT_METHOD)
client_ctx.set_min_proto_version(version.value)
client_ctx.set_max_proto_version(version.value)
client_conn = SSL.Connection(client_ctx)
client_conn.set_connect_state()
try:
client_conn.recv(4096)
except SSL.WantReadError:
return True
except SSL.Error:
return False
class MasterSecretLogger:
def __init__(self, filename: Path):
self.filename = filename.expanduser()

View File

@ -1,4 +1,5 @@
import ipaddress
import logging
import ssl
import time
from pathlib import Path
@ -107,6 +108,29 @@ class TestTlsConfig:
)
assert ta.certstore.certs
def test_configure_tls_version(self, caplog):
caplog.set_level(logging.INFO)
ta = tlsconfig.TlsConfig()
with taddons.context(ta) as tctx:
for attr in [
"tls_version_client_min",
"tls_version_client_max",
"tls_version_server_min",
"tls_version_server_max",
]:
caplog.clear()
tctx.configure(ta, **{attr: "SSL3"})
assert (
f"{attr} has been set to SSL3, "
"which is not supported by the current OpenSSL build."
) in caplog.text
caplog.clear()
tctx.configure(ta, tls_version_client_min="UNBOUNDED")
assert (
"tls_version_client_min has been set to UNBOUNDED. "
"Note that your OpenSSL build only supports the following TLS versions"
) in caplog.text
def test_get_cert(self, tdata):
"""Test that we generate a certificate matching the connection's context."""
ta = tlsconfig.TlsConfig()

View File

@ -1,5 +1,6 @@
from pathlib import Path
import pytest
from OpenSSL import crypto
from OpenSSL import SSL
@ -7,6 +8,13 @@ from mitmproxy import certs
from mitmproxy.net import tls
@pytest.mark.parametrize("version", [tls.Version.UNBOUNDED, tls.Version.SSL3])
def test_supported(version):
# wild assumption: test environments should not do SSLv3 by default.
expected_support = version is tls.Version.UNBOUNDED
assert tls.is_supported_version(version) == expected_support
def test_make_master_secret_logger():
assert tls.make_master_secret_logger(None) is None
assert isinstance(tls.make_master_secret_logger("filepath"), tls.MasterSecretLogger)