Skip to content

fix(tls): drop the root label from FQDNs in the certificate path - #8340

Open
ArockiaRajamanickam wants to merge 3 commits into
mitmproxy:mainfrom
ArockiaRajamanickam:fix-2989-trailing-dot
Open

fix(tls): drop the root label from FQDNs in the certificate path#8340
ArockiaRajamanickam wants to merge 3 commits into
mitmproxy:mainfrom
ArockiaRajamanickam:fix-2989-trailing-dot

Conversation

@ArockiaRajamanickam

Copy link
Copy Markdown

Fixes #2989.

Python's idna codec keeps a trailing dot ('example.com.'.encode('idna') is b'example.com.'), so nothing along the TLS path ever normalised it away. Two consequences, both reproduced on current main:

The generated certificate carries the dot. _ip_or_dns_name() feeds the SANs, and the CN is then derived from the first SAN, so a request for example.com. produced:

CN   : example.com.
SANs : [<DNSName(value='example.com.')>]

which is not what a client verifying example.com looks for — the error in the issue.

The SNI carries it too. server.sni.encode("idna") is used both as the tlsext_host_name sent upstream and as the X509_VERIFY_PARAM_set1_host verification parameter. RFC 6066 section 3 is explicit that the HostName "MUST NOT" include a trailing dot, so this was also sending a non-conformant SNI upstream.

The change adds _remove_trailing_dot() and applies it at those two points, which is the "use the dot-less version consistently where applicable" you asked for. After it:

SAN for 'example.com.'  -> <DNSName(value='example.com')>
SNI bytes               -> b'example.com'
IPs                     -> <IPAddress(value=10.0.0.1)>          (unchanged)
IDN                     -> <DNSName(value='xn--bcher-kva.example')>  (unchanged)

Scope. I left the idna call in certs._fix_legacy_sans() alone. It is the deprecated string-list path for mitmproxy 10.1 and below and is marked # pragma: no cover, so normalising there seemed like widening the diff into dead code rather than fixing the reported bug. Happy to include it if you would rather it were consistent everywhere.

Behaviour change worth naming. A certificate for a trailing-dot request is now issued for the dot-less name. That is the point of the fix, but it does mean generated certs differ for those hosts, and a cached cert from a previous run keyed on the old name will not be reused.

Tests. Three added: a unit test for the helper, one for _ip_or_dns_name covering the trailing-dot, IDN, plain and IP cases, and test_get_cert_trailing_dot, which goes through TlsConfig.get_cert() with a trailing-dot SNI and server address and asserts both the CN and the SANs come out dot-less. All three fail on main — the integration one with assert 'example.com.' == 'example.com'.

test_tlsconfig.py, test_certs.py and proxy/layers/test_tls.py together are 90 passed. Two failures in test_tlsconfig.py (test_configure_tls_version, test_configure_ciphers) also fail on unmodified main in my environment — they are about SSL3/TLS1 support in the local OpenSSL, unrelated to this change. ruff check and ruff format --check are clean, and mypy reports no errors in tlsconfig.py (the errors it does report are pre-existing and in other files).

I used an AI assistant while working on this. The reproduction, the before/after output above and the check that those two failures pre-date the change are mine.

ArockiaRajamanickam and others added 3 commits July 30, 2026 20:41
Visiting https://example.com./ produced a certificate for 'example.com.'
and sent a trailing dot in the SNI HostName, so hostname verification
failed with "hostname 'example.com.' doesn't match 'example.com'".

Python's idna codec keeps the trailing dot, so nothing normalised it
away. Remove it where a hostname enters the TLS and certificate path:
in _ip_or_dns_name(), which feeds the generated certificate's SANs and
the CN derived from them, and on the SNI sent upstream, which RFC 6066
section 3 says must not carry a trailing dot.

Fixes mitmproxy#2989
…x-2989-trailing-dot

# Conflicts:
#	CHANGELOG.md
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Improper treatment of trailing-dot domains

1 participant