3-manifolds / Sage_macOS

SageMath as a macOS application bundle.
152 stars 15 forks source link

SSLCertVerificationError #69

Closed GMS103 closed 4 months ago

GMS103 commented 4 months ago

The command

load('https://moreno.perso.math.cnrs.fr/SageMath/test.sage')

gives an SSLCertVerificationError instead of

Hello world

This command succeeds in SageMath 10.4.beta2 compiled from sources on macOS 12.7.4 with Homebrew. Also, the URL https://moreno.perso.math.cnrs.fr/SageMath/test.sage works as expected in a web browser on the same computer. The full message is:

---------------------------------------------------------------------------
SSLCertVerificationError                  Traceback (most recent call last)
File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py:1348](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py#line=1347), in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
   1347 try:
-> 1348     h.request(req.get_method(), req.selector, req.data, headers,
   1349               encode_chunked=req.has_header('Transfer-encoding'))
   1350 except OSError as err: # timeout error

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py:1282](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py#line=1281), in HTTPConnection.request(self, method, url, body, headers, encode_chunked)
   1281 """Send a complete request to the server."""
-> 1282 self._send_request(method, url, body, headers, encode_chunked)

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py:1328](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py#line=1327), in HTTPConnection._send_request(self, method, url, body, headers, encode_chunked)
   1327     body = _encode(body, 'body')
-> 1328 self.endheaders(body, encode_chunked=encode_chunked)

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py:1277](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py#line=1276), in HTTPConnection.endheaders(self, message_body, encode_chunked)
   1276     raise CannotSendHeader()
-> 1277 self._send_output(message_body, encode_chunked=encode_chunked)

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py:1037](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py#line=1036), in HTTPConnection._send_output(self, message_body, encode_chunked)
   1036 del self._buffer[:]
-> 1037 self.send(msg)
   1039 if message_body is not None:
   1040 
   1041     # create a consistent interface to message_body

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py:975](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py#line=974), in HTTPConnection.send(self, data)
    974 if self.auto_open:
--> 975     self.connect()
    976 else:

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py:1454](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/http/client.py#line=1453), in HTTPSConnection.connect(self)
   1452     server_hostname = self.host
-> 1454 self.sock = self._context.wrap_socket(self.sock,
   1455                                       server_hostname=server_hostname)

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/ssl.py:517](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/ssl.py#line=516), in SSLContext.wrap_socket(self, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, session)
    511 def wrap_socket(self, sock, server_side=False,
    512                 do_handshake_on_connect=True,
    513                 suppress_ragged_eofs=True,
    514                 server_hostname=None, session=None):
    515     # SSLSocket class handles server_hostname encoding before it calls
    516     # ctx._wrap_socket()
--> 517     return self.sslsocket_class._create(
    518         sock=sock,
    519         server_side=server_side,
    520         do_handshake_on_connect=do_handshake_on_connect,
    521         suppress_ragged_eofs=suppress_ragged_eofs,
    522         server_hostname=server_hostname,
    523         context=self,
    524         session=session
    525     )

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/ssl.py:1075](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/ssl.py#line=1074), in SSLSocket._create(cls, sock, server_side, do_handshake_on_connect, suppress_ragged_eofs, server_hostname, context, session)
   1074             raise ValueError("do_handshake_on_connect should not be specified for non-blocking sockets")
-> 1075         self.do_handshake()
   1076 except (OSError, ValueError):

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/ssl.py:1346](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/ssl.py#line=1345), in SSLSocket.do_handshake(self, block)
   1345         self.settimeout(None)
-> 1346     self._sslobj.do_handshake()
   1347 finally:

SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:992)

During handling of the above exception, another exception occurred:

URLError                                  Traceback (most recent call last)
Cell In[5], line 1
----> 1 load('https://moreno.perso.math.cnrs.fr/SageMath/test.sage')

File /private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/site-packages/sage/misc/persist.pyx:175, in sage.misc.persist.load (build[/cythonized/sage/misc/persist.c:4547](http://localhost:8888/cythonized/sage/misc/persist.c#line=4546))()
    173 
    174     if sage.repl.load.is_loadable_filename(filename):
--> 175         sage.repl.load.load(filename, globals())
    176         return
    177 

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/site-packages/sage/repl/load.py:236](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/site-packages/sage/repl/load.py#line=235), in load(filename, globals, attach)
    234         raise NotImplementedError("you cannot attach a URL")
    235     from sage.misc.remote_file import get_remote_file
--> 236     filename = get_remote_file(filename, verbose=False)
    238 from sage.repl.attach import load_attach_path
    239 for path in load_attach_path():

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/site-packages/sage/misc/remote_file.py:46](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/site-packages/sage/misc/remote_file.py#line=45), in get_remote_file(filename, verbose)
     43 if verbose:
     44     print("Loading started")
---> 46 content = urlopen(req, timeout=1, context=default_context())
     47 with open(temp_name, 'wb') as f:
     48     f.write(content.read())

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py:216](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py#line=215), in urlopen(url, data, timeout, cafile, capath, cadefault, context)
    214 else:
    215     opener = _opener
--> 216 return opener.open(url, data, timeout)

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py:519](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py#line=518), in OpenerDirector.open(self, fullurl, data, timeout)
    516     req = meth(req)
    518 sys.audit('urllib.Request', req.full_url, req.data, req.headers, req.get_method())
--> 519 response = self._open(req, data)
    521 # post-process response
    522 meth_name = protocol+"_response"

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py:536](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py#line=535), in OpenerDirector._open(self, req, data)
    533     return result
    535 protocol = req.type
--> 536 result = self._call_chain(self.handle_open, protocol, protocol +
    537                           '_open', req)
    538 if result:
    539     return result

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py:496](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py#line=495), in OpenerDirector._call_chain(self, chain, kind, meth_name, *args)
    494 for handler in handlers:
    495     func = getattr(handler, meth_name)
--> 496     result = func(*args)
    497     if result is not None:
    498         return result

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py:1391](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py#line=1390), in HTTPSHandler.https_open(self, req)
   1390 def https_open(self, req):
-> 1391     return self.do_open(http.client.HTTPSConnection, req,
   1392         context=self._context, check_hostname=self._check_hostname)

File [/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py:1351](http://localhost:8888/private/var/tmp/sage-10.3-current/local/var/lib/sage/venv-python3.11.1/lib/python3.11/urllib/request.py#line=1350), in AbstractHTTPHandler.do_open(self, http_class, req, **http_conn_args)
   1348         h.request(req.get_method(), req.selector, req.data, headers,
   1349                   encode_chunked=req.has_header('Transfer-encoding'))
   1350     except OSError as err: # timeout error
-> 1351         raise URLError(err)
   1352     r = h.getresponse()
   1353 except:

URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:992)>
culler commented 4 months ago

Thank you for reporting this.

I am able to reproduce this problem. Also, I get the same error if I just run urlopen('https://moreno.perso.math.cnrs.fr/SageMath/test.sage'). And I get the same error with Python3.11.4 installed from the python.org package.

I can successfully retrieve that file with the requests package included in the SageMath-10.3 app.

I checked that the app includes the latest version of the certifi package (2024.02.02) while SageMath-10.4b2 is using version 2023.11.17. I tried upgrading certify to version 2023.11.17 in my python.org Python 3.11.4 and I still got the same error. So the Python 3.11.1 in SageMath-10-3.app and Python 3.11.4 from python.org have the same bug, and it appears to be independent of the version of certifi.

Of course the call to urlopen works fine if I add the keyword argument: context=ssl._create_unverified_context()

What are the versions of Python and certifi in your local build of Sage for which the load command works?

culler commented 4 months ago

It looks like your Let's Encrypt certificate was created yesterday. I wonder if that has anything to do with this issue.

GMS103 commented 4 months ago

Below is what I get. About the certificate, I have tried other sites with older certificates with the same result, so I do not think it is an age issue.

% ./sage
┌────────────────────────────────────────────────────────────────────┐
│ SageMath version 10.4.beta2, Release Date: 2024-04-08              │
│ Using Python 3.9.6. Type "help()" for help.                        │
└────────────────────────────────────────────────────────────────────┘
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
┃ Warning: this is a prerelease version, and it may be unstable.     ┃
┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
sage: pip show certifi
Name: certifi
Version: 2023.11.17
Summary: Python package for providing Mozilla's CA Bundle.
Home-page: https://github.com/certifi/python-certifi
Author: Kenneth Reitz
Author-email: me@kennethreitz.com
License: MPL-2.0
Location: /Users/gms/SageMath/Git/sage/local/var/lib/sage/venv-python3.9/lib/python3.9/site-packages
Requires: 
Required-by: httpcore, httpx, requests
Note: you may need to restart the kernel to use updated packages.
sage: 
culler commented 4 months ago

I tested whether I could open your url with urllib.request.urlopen using Python 3.12.0 from python.org. It worked fine, using certifi 2023.07.22. Neither the SageMath Python 3.11.1 nor the python.org Python 3.11.4 can open your url with urlopen. I also tried using Python 3.10.12 on Ubuntu 22.04 with certifi 2020.06.20. That also worked fine.

I conclude that this is a bug in multiple versions of Python 3.11. I am not sure what I can do about it. The Sage project is working on updating its Python spkg to 3.12. Once they do that, this problem should be fixed. But that will probably have to wait at least until SageMath 10.4 is released.

Maybe the urllib.requests package could be patched. I don't know what the nature of the bug is, however.

GMS103 commented 4 months ago

Thank you very much, Marc, for taking the time of looking into this. Do you think it would be worthwhile for me to try to open a Python issue? I could not find anything relevant in https://github.com/python/cpython/issues?q=is%3Aissue+SSLCertVerificationError

culler commented 4 months ago

I tried one more experiment. I installed Python3.11.8 (the newest point release of 3.11) in place of my 3.11.4. I also installed the latest certifi package (2024.02.02). I then tried to open your url with urllib.requests.urlopen. It worked fine. So it looks like the problem has been fixed in the latest release of Python 3.11. I don't know what more the Python developers could do, once they have fixed the problem in a point release. They can't recall older versions. So I would say that it is not worth the trouble to file an issue with them.

I will experiment with using Python 3.11.8 in the SageMath app. If I can do that without too much trouble I will do another release of the app. I'm sorry that this happened.

GMS103 commented 4 months ago

Thank you very much once again, Marc, for all the time and energies you devote to this wonderful SageMath for macOS. If this load thing eventually works it will be most useful for many of my students. But please do not be sorry, it is not your fault.

culler commented 4 months ago

It turned out that updating python to verson 3.11.8 was straightforward. But that did not fix the problem. I also had to patch the sage.misc.remote_file module. I needed to explicitly tell the urlopen command to use the cert chain provided by the certifi package: cafile = certifi.where() content = urlopen(req, timeout=1, context=default_context(cafile=cafile)) I now think this is a python bug, but a subtle one. I am not sure how to construct a reproducible demonstration. It seems that sometimes the default ssl context uses certifi, but sometimes it doesn't.

I have published a pre-release of a new version of SageMath-10-3.app which contains the fix. At least it works for me. @GMS103: would you please test it?

GMS103 commented 4 months ago

I confirm that SageMath 10.3 - v2.3.1-pre Pre-release works, I am most grateful. Strange bug indeed.

culler commented 4 months ago

Thanks!