[ 
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)

Reply via email to