diff --git a/Doc/library/ssl.rst b/Doc/library/ssl.rst index 139dc2c24eab764..50409c89929f01b 100644 --- a/Doc/library/ssl.rst +++ b/Doc/library/ssl.rst @@ -432,7 +432,8 @@ Certificate handling same format as used for the *cafile* parameter in :meth:`SSLContext.load_verify_locations`. The call will attempt to validate the server certificate against that set of root certificates, and will fail - if the validation attempt fails. A timeout can be specified with the + if the validation attempt fails or the certificate hostname does not match + the server hostname. A timeout can be specified with the ``timeout`` parameter. .. versionchanged:: 3.3 @@ -445,6 +446,11 @@ Certificate handling .. versionchanged:: 3.10 The *timeout* parameter was added. + .. versionchanged:: next + The function now raises :exc:`SSLCertVerificationError` if *ca_certs* is + specified but the certificate hostname does not match the server + hostname. + .. function:: DER_cert_to_PEM_cert(der_cert_bytes) Given a certificate as a DER-encoded blob of bytes, returns a PEM-encoded diff --git a/Lib/ssl.py b/Lib/ssl.py index df2eece13f2b138..49eec9cde0b6317 100644 --- a/Lib/ssl.py +++ b/Lib/ssl.py @@ -1638,7 +1638,8 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, ca_certs=None, timeout=_GLOBAL_DEFAULT_TIMEOUT): """Retrieve the certificate from the server at the specified address, and return it as a PEM-encoded string. - If 'ca_certs' is specified, validate the server cert against it. + If 'ca_certs' is specified, validate the server cert against it and + check that it matches the hostname. If 'ssl_version' is specified, use it in the connection attempt. If 'timeout' is specified, use it in the connection attempt. """ @@ -1650,6 +1651,7 @@ def get_server_certificate(addr, ssl_version=PROTOCOL_TLS_CLIENT, cert_reqs = CERT_NONE context = _create_stdlib_context(ssl_version, cert_reqs=cert_reqs, + check_hostname=ca_certs is not None, cafile=ca_certs) with create_connection(addr, timeout=timeout) as sock: with context.wrap_socket(sock, server_hostname=host) as sslsock: diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 5b4e4f080607726..b0e3cbaa548e8d5 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -3112,6 +3112,10 @@ def test_get_server_certificate_fail(self): # independent test method _test_get_server_certificate_fail(self, *self.server_addr) + def test_get_server_certificate_fail_by_hostname(self): + _, port = self.server_addr + _test_get_server_certificate_fail(self, socket_helper.HOSTv4, port, SIGNING_CA) + def test_get_server_certificate_timeout(self): def servername_cb(ssl_sock, server_name, initial_context): time.sleep(0.2) @@ -3364,10 +3368,10 @@ def _test_get_server_certificate(test, host, port, cert=None): if support.verbose: sys.stdout.write("\nVerified certificate for %s:%s is\n%s\n" % (host, port ,pem)) -def _test_get_server_certificate_fail(test, host, port): +def _test_get_server_certificate_fail(test, host, port, ca_certs=CERTFILE): with warnings_helper.check_no_resource_warning(test): try: - pem = ssl.get_server_certificate((host, port), ca_certs=CERTFILE) + pem = ssl.get_server_certificate((host, port), ca_certs=ca_certs) except ssl.SSLError as x: #should fail if support.verbose: diff --git a/Misc/NEWS.d/next/Security/2026-11-06-16-22-47.gh-issue-161503.FkIl87.rst b/Misc/NEWS.d/next/Security/2026-11-06-16-22-47.gh-issue-161503.FkIl87.rst new file mode 100644 index 000000000000000..308ba0c83f3efe4 --- /dev/null +++ b/Misc/NEWS.d/next/Security/2026-11-06-16-22-47.gh-issue-161503.FkIl87.rst @@ -0,0 +1,4 @@ +.. cve: CVE-2026-19552 + +:func:`ssl.get_server_certificate` now verifies that the server hostname matches +its certificate when the caller specifies *ca_certs*.