mirror of
https://github.com/mitmproxy/mitmproxy.git
synced 2024-11-23 05:09:57 +00:00
Set @SECLEVEL=0
if old TLS versions are requested (#7241)
* set `@SECLEVEL=0` if old TLS versions are requested * [autofix.ci] apply automated fixes --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
99aa0a70af
commit
93649e8baf
@ -11,6 +11,9 @@
|
||||
([#7242](https://github.com/mitmproxy/mitmproxy/pull/7242), @mhils)
|
||||
- Tighten HTTP detection heuristic to better support custom TCP-based protocols.
|
||||
([#7228](https://github.com/mitmproxy/mitmproxy/pull/7228), @fatanugraha)
|
||||
- Fix a bug where mitmproxy would incorrectly report that TLS 1.0 and 1.1 are not supported
|
||||
with the current OpenSSL build.
|
||||
([#7241](https://github.com/mitmproxy/mitmproxy/pull/7241), @mhils)
|
||||
|
||||
## 02 October 2024: mitmproxy 11.0.0
|
||||
|
||||
|
@ -4,6 +4,7 @@ import os
|
||||
import ssl
|
||||
from pathlib import Path
|
||||
from typing import Any
|
||||
from typing import Literal
|
||||
from typing import TypedDict
|
||||
|
||||
from aioquic.h3.connection import H3_ALPN
|
||||
@ -29,7 +30,7 @@ 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
|
||||
|
||||
DEFAULT_CIPHERS = (
|
||||
_DEFAULT_CIPHERS = (
|
||||
"ECDHE-ECDSA-AES128-GCM-SHA256",
|
||||
"ECDHE-RSA-AES128-GCM-SHA256",
|
||||
"ECDHE-ECDSA-AES256-GCM-SHA384",
|
||||
@ -58,6 +59,22 @@ DEFAULT_CIPHERS = (
|
||||
"DES-CBC3-SHA",
|
||||
)
|
||||
|
||||
_DEFAULT_CIPHERS_WITH_SECLEVEL_0 = ("@SECLEVEL=0", *_DEFAULT_CIPHERS)
|
||||
|
||||
|
||||
def _default_ciphers(
|
||||
min_tls_version: net_tls.Version,
|
||||
) -> tuple[str, ...]:
|
||||
"""
|
||||
@SECLEVEL=0 is necessary for TLS 1.1 and below to work,
|
||||
see https://github.com/pyca/cryptography/issues/9523
|
||||
"""
|
||||
if min_tls_version in net_tls.INSECURE_TLS_MIN_VERSIONS:
|
||||
return _DEFAULT_CIPHERS_WITH_SECLEVEL_0
|
||||
else:
|
||||
return _DEFAULT_CIPHERS
|
||||
|
||||
|
||||
# 2022/05: X509_CHECK_FLAG_NEVER_CHECK_SUBJECT is not available in LibreSSL, ignore gracefully as it's not critical.
|
||||
DEFAULT_HOSTFLAGS = (
|
||||
SSL._lib.X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS # type: ignore
|
||||
@ -109,8 +126,6 @@ class TlsConfig:
|
||||
# TODO: This addon should manage the following options itself, which are current defined in mitmproxy/options.py:
|
||||
# - upstream_cert
|
||||
# - add_upstream_certs_to_client_chain
|
||||
# - ciphers_client
|
||||
# - ciphers_server
|
||||
# - key_size
|
||||
# - certs
|
||||
# - cert_passphrase
|
||||
@ -118,12 +133,17 @@ class TlsConfig:
|
||||
# - ssl_verify_upstream_trusted_confdir
|
||||
|
||||
def load(self, loader):
|
||||
insecure_tls_min_versions = (
|
||||
", ".join(x.name for x in net_tls.INSECURE_TLS_MIN_VERSIONS[:-1])
|
||||
+ f" and {net_tls.INSECURE_TLS_MIN_VERSIONS[-1].name}"
|
||||
)
|
||||
loader.add_option(
|
||||
name="tls_version_client_min",
|
||||
typespec=str,
|
||||
default=net_tls.DEFAULT_MIN_VERSION.name,
|
||||
choices=[x.name for x in net_tls.Version],
|
||||
help=f"Set the minimum TLS version for client connections.",
|
||||
help=f"Set the minimum TLS version for client connections. "
|
||||
f"{insecure_tls_min_versions} are insecure.",
|
||||
)
|
||||
loader.add_option(
|
||||
name="tls_version_client_max",
|
||||
@ -137,7 +157,8 @@ class TlsConfig:
|
||||
typespec=str,
|
||||
default=net_tls.DEFAULT_MIN_VERSION.name,
|
||||
choices=[x.name for x in net_tls.Version],
|
||||
help=f"Set the minimum TLS version for server connections.",
|
||||
help=f"Set the minimum TLS version for server connections. "
|
||||
f"{insecure_tls_min_versions} are insecure.",
|
||||
)
|
||||
loader.add_option(
|
||||
name="tls_version_server_max",
|
||||
@ -166,6 +187,18 @@ class TlsConfig:
|
||||
default=False,
|
||||
help=f"Requests a client certificate (TLS message 'CertificateRequest') to establish a mutual TLS connection between client and mitmproxy (combined with 'client_certs' option for mitmproxy and upstream).",
|
||||
)
|
||||
loader.add_option(
|
||||
"ciphers_client",
|
||||
str | None,
|
||||
None,
|
||||
"Set supported ciphers for client <-> mitmproxy connections using OpenSSL syntax.",
|
||||
)
|
||||
loader.add_option(
|
||||
"ciphers_server",
|
||||
str | None,
|
||||
None,
|
||||
"Set supported ciphers for mitmproxy <-> server connections using OpenSSL syntax.",
|
||||
)
|
||||
|
||||
def tls_clienthello(self, tls_clienthello: tls.ClientHelloData):
|
||||
conn_context = tls_clienthello.context
|
||||
@ -188,7 +221,9 @@ class TlsConfig:
|
||||
if not client.cipher_list and ctx.options.ciphers_client:
|
||||
client.cipher_list = ctx.options.ciphers_client.split(":")
|
||||
# don't assign to client.cipher_list, doesn't need to be stored.
|
||||
cipher_list = client.cipher_list or DEFAULT_CIPHERS
|
||||
cipher_list = client.cipher_list or _default_ciphers(
|
||||
net_tls.Version[ctx.options.tls_version_client_min]
|
||||
)
|
||||
|
||||
if ctx.options.add_upstream_certs_to_client_chain: # pragma: no cover
|
||||
# exempted from coverage until https://bugs.python.org/issue18233 is fixed.
|
||||
@ -278,7 +313,9 @@ class TlsConfig:
|
||||
if not server.cipher_list and ctx.options.ciphers_server:
|
||||
server.cipher_list = ctx.options.ciphers_server.split(":")
|
||||
# don't assign to client.cipher_list, doesn't need to be stored.
|
||||
cipher_list = server.cipher_list or DEFAULT_CIPHERS
|
||||
cipher_list = server.cipher_list or _default_ciphers(
|
||||
net_tls.Version[ctx.options.tls_version_server_min]
|
||||
)
|
||||
|
||||
client_cert: str | None = None
|
||||
if ctx.options.client_certs:
|
||||
@ -500,6 +537,10 @@ class TlsConfig:
|
||||
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)
|
||||
if "tls_version_client_min" in updated or "ciphers_client" in updated:
|
||||
self._warn_seclevel_missing("client")
|
||||
if "tls_version_server_min" in updated or "ciphers_server" in updated:
|
||||
self._warn_seclevel_missing("server")
|
||||
|
||||
def _warn_unsupported_version(self, attribute: str, warn_unbound: bool):
|
||||
val = net_tls.Version[getattr(ctx.options, attribute)]
|
||||
@ -520,6 +561,28 @@ class TlsConfig:
|
||||
f"The current build only supports the following versions: {supported_versions_str}"
|
||||
)
|
||||
|
||||
def _warn_seclevel_missing(self, side: Literal["client", "server"]) -> None:
|
||||
"""
|
||||
OpenSSL cipher spec need to specify @SECLEVEL for old TLS versions to work,
|
||||
see https://github.com/pyca/cryptography/issues/9523.
|
||||
"""
|
||||
if side == "client":
|
||||
custom_ciphers = ctx.options.ciphers_client
|
||||
min_tls_version = ctx.options.tls_version_client_min
|
||||
else:
|
||||
custom_ciphers = ctx.options.ciphers_server
|
||||
min_tls_version = ctx.options.tls_version_server_min
|
||||
|
||||
if (
|
||||
custom_ciphers
|
||||
and net_tls.Version[min_tls_version] in net_tls.INSECURE_TLS_MIN_VERSIONS
|
||||
and "@SECLEVEL=0" not in custom_ciphers
|
||||
):
|
||||
logger.warning(
|
||||
f'With tls_version_{side}_min set to {min_tls_version}, ciphers_{side} must include "@SECLEVEL=0" '
|
||||
f"for insecure TLS versions to work."
|
||||
)
|
||||
|
||||
def get_cert(self, conn_context: context.Context) -> certs.CertStoreEntry:
|
||||
"""
|
||||
This function determines the Common Name (CN), Subject Alternative Names (SANs) and Organization Name
|
||||
|
@ -49,6 +49,14 @@ class Version(Enum):
|
||||
TLS1_3 = SSL.TLS1_3_VERSION
|
||||
|
||||
|
||||
INSECURE_TLS_MIN_VERSIONS: tuple[Version, ...] = (
|
||||
Version.UNBOUNDED,
|
||||
Version.SSL3,
|
||||
Version.TLS1,
|
||||
Version.TLS1_1,
|
||||
)
|
||||
|
||||
|
||||
class Verify(Enum):
|
||||
VERIFY_NONE = SSL.VERIFY_NONE
|
||||
VERIFY_PEER = SSL.VERIFY_PEER
|
||||
@ -62,6 +70,9 @@ 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)
|
||||
# Without SECLEVEL, recent OpenSSL versions forbid old TLS versions.
|
||||
# https://github.com/pyca/cryptography/issues/9523
|
||||
client_ctx.set_cipher_list(b"@SECLEVEL=0:ALL")
|
||||
client_ctx.set_min_proto_version(version.value)
|
||||
client_ctx.set_max_proto_version(version.value)
|
||||
client_conn = SSL.Connection(client_ctx)
|
||||
|
@ -72,18 +72,6 @@ class Options(optmanager.OptManager):
|
||||
process list. Specify it in config.yaml to avoid this.
|
||||
""",
|
||||
)
|
||||
self.add_option(
|
||||
"ciphers_client",
|
||||
Optional[str],
|
||||
None,
|
||||
"Set supported ciphers for client <-> mitmproxy connections using OpenSSL syntax.",
|
||||
)
|
||||
self.add_option(
|
||||
"ciphers_server",
|
||||
Optional[str],
|
||||
None,
|
||||
"Set supported ciphers for mitmproxy <-> server connections using OpenSSL syntax.",
|
||||
)
|
||||
self.add_option(
|
||||
"client_certs", Optional[str], None, "Client certificate file or directory."
|
||||
)
|
||||
|
@ -13,6 +13,7 @@ from mitmproxy import connection
|
||||
from mitmproxy import options
|
||||
from mitmproxy import tls
|
||||
from mitmproxy.addons import tlsconfig
|
||||
from mitmproxy.net import tls as net_tls
|
||||
from mitmproxy.proxy import context
|
||||
from mitmproxy.proxy.layers import modes
|
||||
from mitmproxy.proxy.layers import quic
|
||||
@ -131,6 +132,35 @@ class TestTlsConfig:
|
||||
"Note that your OpenSSL build only supports the following TLS versions"
|
||||
) in caplog.text
|
||||
|
||||
def test_configure_ciphers(self, caplog):
|
||||
caplog.set_level(logging.INFO)
|
||||
ta = tlsconfig.TlsConfig()
|
||||
with taddons.context(ta) as tctx:
|
||||
tctx.configure(
|
||||
ta,
|
||||
tls_version_client_min="TLS1",
|
||||
ciphers_client="ALL",
|
||||
)
|
||||
assert (
|
||||
"With tls_version_client_min set to TLS1, "
|
||||
'ciphers_client must include "@SECLEVEL=0" for insecure TLS versions to work.'
|
||||
) in caplog.text
|
||||
caplog.clear()
|
||||
|
||||
tctx.configure(
|
||||
ta,
|
||||
ciphers_server="ALL",
|
||||
)
|
||||
assert not caplog.text
|
||||
tctx.configure(
|
||||
ta,
|
||||
tls_version_server_min="SSL3",
|
||||
)
|
||||
assert (
|
||||
"With tls_version_server_min set to SSL3, "
|
||||
'ciphers_server must include "@SECLEVEL=0" for insecure TLS versions to work.'
|
||||
) in caplog.text
|
||||
|
||||
def test_get_cert(self, tdata):
|
||||
"""Test that we generate a certificate matching the connection's context."""
|
||||
ta = tlsconfig.TlsConfig()
|
||||
@ -481,3 +511,17 @@ class TestTlsConfig:
|
||||
with taddons.context(ta):
|
||||
ta.configure(["confdir"])
|
||||
assert "The mitmproxy certificate authority has expired" in caplog.text
|
||||
|
||||
|
||||
def test_default_ciphers():
|
||||
assert (
|
||||
tlsconfig._default_ciphers(net_tls.Version.TLS1_3) == tlsconfig._DEFAULT_CIPHERS
|
||||
)
|
||||
assert (
|
||||
tlsconfig._default_ciphers(net_tls.Version.SSL3)
|
||||
== tlsconfig._DEFAULT_CIPHERS_WITH_SECLEVEL_0
|
||||
)
|
||||
assert (
|
||||
tlsconfig._default_ciphers(net_tls.Version.UNBOUNDED)
|
||||
== tlsconfig._DEFAULT_CIPHERS_WITH_SECLEVEL_0
|
||||
)
|
||||
|
Loading…
Reference in New Issue
Block a user