-
-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
drop wincertstore and certifi dep by dropping setuptools.ssl_support #2711
Conversation
wincertstore only supports python < 3.4 Cc @tiran |
Urks, this code is bad and should be completely rewritten using SSLContext. |
try: | ||
import wincertstore | ||
except ImportError: | ||
return None | ||
|
||
class CertFile(wincertstore.CertFile): | ||
def __init__(self): | ||
super(CertFile, self).__init__() | ||
atexit.register(self.close) | ||
|
||
def close(self): | ||
try: | ||
super(CertFile, self).close() | ||
except OSError: | ||
pass | ||
|
||
_wincerts = CertFile() | ||
_wincerts.addstore('CA') | ||
_wincerts.addstore('ROOT') | ||
return _wincerts.name |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don't drop this feature. Either replace it with https://github.com/python/cpython/blob/db3ff76da19004f266b62e98a81bdfd322861436/Lib/ssl.py#L555-L575, replace it with SSLContext.load_default_certs
or even better: use ssl.create_default_context()
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
so it turns out the code already does use ssl.create_default_context(
where available. But now it's always available so the whole module can go!
wincertstore; sys_platform=='win32' | ||
|
||
certs = | ||
certifi |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is probably a really bad idea for certain kinds of Windows users, most macOS users, and Debian/Ubuntu users.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
previously setuptools only fell back on certifi if every system cert file was missing:
return (
get_win_certfile()
or next(extant_cert_paths, None)
or _certifi_where()
)
so I think this is correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tiran as far as I can tell I can't install certifi without installing ca-certificates
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Debian-based distros don't come with ca-certificates in their minimal package list because Debian-based distros use plain HTTP + GPG signatures instead of HTTPS to download and verify packages. Starting with Ubuntu >= 21.04 and current Debian Testing, the Python package has a weak dependency on ca-certificates.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The same applies for Windows. ssl.enum_certificates
no longer works in some cases because Windows retrieves trust anchors on demand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems windows urllib.request.urlopen(..., context=ssl.create_default_context(capath=None))
works correctly too:
>>> import ssl
>>> urllib.request.urlopen("https://google.com", context=ssl.create_default_context(capath=None))
<http.client.HTTPResponse object at 0x0000011183D54130>
>>> urllib.request.urlopen("https://wrong.host.badssl.com/", context=ssl.create_default_context(capath=None))
Traceback (most recent call last):
File "C:\Python39\lib\urllib\request.py", line 1346, in do_open
h.request(req.get_method(), req.selector, req.data, headers,
File "C:\Python39\lib\http\client.py", line 1253, in request
self._send_request(method, url, body, headers, encode_chunked)
File "C:\Python39\lib\http\client.py", line 1299, in _send_request
self.endheaders(body, encode_chunked=encode_chunked)
File "C:\Python39\lib\http\client.py", line 1248, in endheaders
self._send_output(message_body, encode_chunked=encode_chunked)
File "C:\Python39\lib\http\client.py", line 1008, in _send_output
self.send(msg)
File "C:\Python39\lib\http\client.py", line 948, in send
self.connect()
File "C:\Python39\lib\http\client.py", line 1422, in connect
self.sock = self._context.wrap_socket(self.sock,
File "C:\Python39\lib\ssl.py", line 500, in wrap_socket
return self.sslsocket_class._create(
File "C:\Python39\lib\ssl.py", line 1040, in _create
self.do_handshake()
File "C:\Python39\lib\ssl.py", line 1309, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'wrong.host.badssl.com'. (_ssl.c:1129)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "C:\Python39\lib\urllib\request.py", line 214, in urlopen
return opener.open(url, data, timeout)
File "C:\Python39\lib\urllib\request.py", line 517, in open
response = self._open(req, data)
File "C:\Python39\lib\urllib\request.py", line 534, in _open
result = self._call_chain(self.handle_open, protocol, protocol +
File "C:\Python39\lib\urllib\request.py", line 494, in _call_chain
result = func(*args)
File "C:\Python39\lib\urllib\request.py", line 1389, in https_open
return self.do_open(http.client.HTTPSConnection, req,
File "C:\Python39\lib\urllib\request.py", line 1349, in do_open
raise URLError(err)
urllib.error.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'wrong.host.badssl.com'. (_ssl.c:1129)>
>>>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My windows installation of setuptools doesn't have certifi or wincertstore installed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tiran I'm confident certifi is never used here, barring intentional user intervention.
The setuptools.package_index module is only used when pip is not available and setup.py is invoked with a deprecated setup_requires
kwarg. On Windows and macos ensurepip works correctly so this isn't an issue.
further certifi is only used when ca-certificates is not installed. because on debian python3-certifi depends on ca-certificates this code is unreachable.
installing certifi manually with pip (eg via get-pip.py sideloaded via USB stick) would also render this code unreachable because installing pip means this code won't run either
installing certifi with easyinstall isn't possible because neither certifi nor ca-certificates is installed and so certificate verification will fail
To reach this code a user would have to manually copy over the certifi files into their site-packages or add the whl to their sys.path
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It should also be noted that setup.py upload
(also deprecated) uses vanilla urllib.request.urlopen without any certificate shenanigans
setuptools/setuptools/_distutils/command/upload.py
Lines 194 to 195 in 582465b
result = urlopen(request) | |
status = result.getcode() |
if verify_ssl or ca_bundle: | ||
import ssl # deferred import because ssl technically isn't mandatory | ||
|
||
self.opener = partial( | ||
urllib.request.urlopen, | ||
context=ssl.create_default_context(cafile=ca_bundle) | ||
) | ||
else: | ||
self.opener = urllib.request.urlopen |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems verify_ssl=False doesn't behave as expected. The default urllib.request.urlopen now verifies TLS correctly, and so this code is equivalent:
if verify_ssl or ca_bundle: | |
import ssl # deferred import because ssl technically isn't mandatory | |
self.opener = partial( | |
urllib.request.urlopen, | |
context=ssl.create_default_context(cafile=ca_bundle) | |
) | |
else: | |
self.opener = urllib.request.urlopen | |
if ca_bundle is not None: | |
import ssl # deferred import because ssl technically isn't mandatory | |
self.opener = partial( | |
urllib.request.urlopen, | |
context=ssl.create_default_context(cafile=ca_bundle) | |
) | |
else: | |
self.opener = urllib.request.urlopen |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here I'm running on a fresh ubuntu docker image after apt install python3-setuptools ca-certificates
>>> import setuptools
>>> import setuptools.package_index
>>> setuptools.package_index.PackageIndex(verify_ssl=False).opener("https://google.com")
<http.client.HTTPResponse object at 0x7f0754c604c0>
>>> setuptools.package_index.PackageIndex(verify_ssl=False).opener("https://wrong.host.badssl.com/")
Traceback (most recent call last):
File "/usr/lib/python3.8/urllib/request.py", line 1354, in do_open
h.request(req.get_method(), req.selector, req.data, headers,
File "/usr/lib/python3.8/http/client.py", line 1252, in request
self._send_request(method, url, body, headers, encode_chunked)
File "/usr/lib/python3.8/http/client.py", line 1298, in _send_request
self.endheaders(body, encode_chunked=encode_chunked)
File "/usr/lib/python3.8/http/client.py", line 1247, in endheaders
self._send_output(message_body, encode_chunked=encode_chunked)
File "/usr/lib/python3.8/http/client.py", line 1007, in _send_output
self.send(msg)
File "/usr/lib/python3.8/http/client.py", line 947, in send
self.connect()
File "/usr/lib/python3.8/http/client.py", line 1421, in connect
self.sock = self._context.wrap_socket(self.sock,
File "/usr/lib/python3.8/ssl.py", line 500, in wrap_socket
return self.sslsocket_class._create(
File "/usr/lib/python3.8/ssl.py", line 1040, in _create
self.do_handshake()
File "/usr/lib/python3.8/ssl.py", line 1309, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: Hostname mismatch, certificate is not valid for 'wrong.host.badssl.com'. (_ssl.c:1131)
See #2716 where I removed ssl support based on findings similar to what graingert reported above. I'll remove those dependencies as well. If custom bundle support is needed, it can be brought back, but it will need to be done using new, non-deprecated functionality. |
Removing the extras by name might break backward compatibility, so I've only removed the dependencies for now. I'll remove the extra names in a future, backward-incompatible release. |
As far as I can tell custom CA bundle support works via the pip based setup_requires codepath already. I'll double check |
Summary of changes
Closes #86
Pull Request Checklist
changelog.d/
.(See documentation for details)