[
https://issues.apache.org/jira/browse/THRIFT-6233?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
]
Jens Geyer resolved THRIFT-6233.
--------------------------------
Fix Version/s: 0.26.0
Assignee: Sylwester Lachiewicz
Resolution: Fixed
> TSSLServerSocket peer-address check depends on the Python version
> -----------------------------------------------------------------
>
> Key: THRIFT-6233
> URL: https://issues.apache.org/jira/browse/THRIFT-6233
> Project: Thrift
> Issue Type: Improvement
> Components: Python - Library
> Reporter: Sylwester Lachiewicz
> Assignee: Sylwester Lachiewicz
> Priority: Major
> Fix For: 0.26.0
>
> Time Spent: 0.5h
> Remaining Estimate: 0h
>
> {{TSSLServerSocket}} calls its default {{validate_callback}} with the client
> certificate and the address the connection arrived from, whenever
> {{cert_reqs}} asks for a certificate. Which function that default is depends
> on the interpreter:
> * Python 3.12 and later: {{sslcompat.match_peer_ipaddress}}, which matches IP
> subjectAltName records and, since
> [THRIFT-6201|https://issues.apache.org/jira/browse/THRIFT-6201], treats
> {{::ffff:127.0.0.1}} and {{127.0.0.1}} as the same address.
> * Python 3.7 to 3.11: {{ssl.match_hostname}}, deprecated since 3.7, which
> compares addresses exactly and falls back to the commonName when the
> certificate has no subjectAltName.
> So the same client certificate is accepted or refused depending on the
> server's Python. A dual-stack listener ({{host=None}} binds {{::}} with
> {{IPV6_V6ONLY=0}}) reports an IPv4 client as {{::ffff:127.0.0.1}}; a
> certificate carrying {{IP Address:127.0.0.1}} is accepted on 3.12 and refused
> on 3.10:
> {noformat}
> $ python3.10 -c "import ssl; ssl.match_hostname(cert, '::ffff:127.0.0.1')"
> ssl.SSLCertVerificationError: hostname '::ffff:127.0.0.1' doesn't match
> either of '127.0.0.1', '0:0:0:0:0:0:0:1', 'localhost'
> {noformat}
> [THRIFT-3660|https://issues.apache.org/jira/browse/THRIFT-3660] worked around
> exactly this in 2016 by adding the mapped address to
> {{test/keys/client_v3.crt}};
> [THRIFT-6275|https://issues.apache.org/jira/browse/THRIFT-6275] has to remove
> that entry again because Go 1.27 rejects certificates that carry one, which
> leaves servers on Python 3.11 or earlier exposed to the mismatch.
> h3. Change
> * {{TSSLServerSocket}} defaults {{validate_callback}} to
> {{match_peer_ipaddress}} on every Python version. The server only ever
> matches {{addr[0]}}, an IP address, which is that function's contract. The
> check stays on by default; a certificate that does not list the peer address
> is still refused.
> * On 3.7 to 3.11 this means IPv4-mapped peers match as they do on 3.12, and
> the commonName fallback of {{ssl.match_hostname}} no longer applies: a client
> certificate has to carry the peer address as an IP subjectAltName. That is
> the documented behaviour on 3.12 already, and the fallback only ever matched
> a certificate whose commonName is literally the peer address string.
> * The default matches IP subjectAltName records only. A DNS subjectAltName is
> not matched: the server has no name for the client, and a reverse lookup of
> the peer address is controlled by the client's network. Which subjects may
> connect is the application's policy and goes through a caller-supplied
> {{validate_callback}}, which receives the full {{getpeercert()}} dictionary;
> the docstring gets an example that refuses on the subject.
> * {{TSSLSocket}} on the client side keeps {{_match_hostname}}; OpenSSL
> matches the server name during the handshake through {{check_hostname}} on
> every supported version.
> * The {{backports.ssl_match_hostname}} branch in
> {{sslcompat._optional_dependencies}}, the {{ValueError}} in
> {{TSSLServerSocket.__init__}} that names it, and the {{setup.py}} dependency
> go, which is [THRIFT-6265|https://issues.apache.org/jira/browse/THRIFT-6265]:
> the library requires Python 3 and CI builds from 3.10, so that branch cannot
> run.
> * Tests drive {{TSSLServerSocket.accept()}} under the running interpreter: a
> certificate with the peer address is accepted, one without is refused, a
> client that arrives as an IPv4-mapped address is accepted against a
> certificate carrying the plain IPv4 address, so the 3.10 and 3.11 rows of the
> matrix exercise the reduction, and a custom callback that refuses on the
> subject shows the policy path.
> *
> [lib/py/README.md|https://github.com/apache/thrift/blob/master/lib/py/README.md]
> notes for 0.25.0 that the server-side check is the same on every Python
> version and covers IP subjectAltName records only, with the commonName note
> for servers that relied on {{ssl.match_hostname}} before.
> _This issue was created with AI assistance._
--
This message was sent by Atlassian Jira
(v8.20.10#820010)