I am making a python-based mac app that uses discord.py
to do stuff with discord. As I knew from previous experience making discord bots, running discord bots requires that you run Install Certificates.command
in your version of python. However, if another users uses this app, I don't want to require them to install python. I took a snippet of code from Install Certificates.command
, thinking it would put the certificate in the right place on a user's computer. However, a tester got this error running the app on their computer:
Traceback (most recent call last):
File "Interface.py", line 136, in <module>
File "installCerts.py", line 25, in installCerts
FileNotFoundError: [Errno 2] No such file or directory: '/Library/Frameworks/Python.framework/Versions/3.8/etc/openssl'
[2514] Failed to execute script 'Interface' due to unhandled exception: [Errno 2] No such file or directory: '/Library/Frameworks/Python.framework/Versions/3.8/etc/openssl'
[2514] Traceback:
Traceback (most recent call last):
File "Interface.py", line 136, in <module>
File "installCerts.py", line 25, in installCerts
FileNotFoundError: [Errno 2] No such file or directory: '/Library/Frameworks/Python.framework/Versions/3.8/etc/openssl'
It's pretty clear what this error is saying: They don't have python (3.8) installed, so it can't put the ssl certificates anywhere (this is because the app is running in a python 3.8 environment).
By the way, the path mentioned in the error is the directory name of the path given by ssl.get_default_verify_paths().openssl_cafile
.
I'm not super well-versed in the finer points of web connections and stuff like that, so I don't know the exact role of these certificates. Here's my question:
Is it possible to get this to work without the user installing python on their computer?
I.e. Can I add the ssl certificates to the app's local python version (as far as I can tell, in my app, python is simply a large bundled exec file)? Is there somewhere deep in the file system where I can put the certificates to let the connection to discord happen? . Pretty much any solution would be appreciated.
Additional Info:
My Code to Install Certificates:
STAT_0o775 = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
| stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
| stat.S_IROTH | stat.S_IXOTH)
openssl_dir, openssl_cafile = os.path.split(
ssl.get_default_verify_paths().openssl_cafile)
os.chdir(openssl_dir) #Error happens here
relpath_to_certifi_cafile = os.path.relpath(certifi.where())
print(" -- removing any existing file or link")
try:
os.remove(openssl_cafile)
except FileNotFoundError:
pass
print(" -- creating symlink to certifi certificate bundle")
os.symlink(relpath_to_certifi_cafile, openssl_cafile)
print(" -- setting permissions")
os.chmod(openssl_cafile, STAT_0o775)
print(" -- update complete")
The error that discord.py throws when the user doesn't have correct certificates installed:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/site-packages/aiohttp/connector.py", line 969, in _wrap_create_connection
return await self._loop.create_connection(*args, **kwargs) # type: ignore # noqa
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py", line 1050, in create_connection
transport, protocol = await self._create_connection_transport(
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/base_events.py", line 1080, in _create_connection_transport
await waiter
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/sslproto.py", line 529, in data_received
ssldata, appdata = self._sslpipe.feed_ssldata(data)
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/asyncio/sslproto.py", line 189, in feed_ssldata
self._sslobj.do_handshake()
File "/Library/Frameworks/Python.framework/Versions/3.8/lib/python3.8/ssl.py", line 944, in do_handshake
self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1125)
If you need more info, let me know.
CodePudding user response:
Ok. This was very tough, but I got to an answer after much research. ssl
in python is basically just a set of bindings for openSSL
. When you do import ssl
, it builds an openSSL
environment (I don't think I'm using the exact right words here). As you could see, it was defaulting to the openSSL
folder in Python
because from python's perspective, that is where openSSL
keeps its certs. Turns out, ssl.DefaultVerifyPaths
objects have other attributes, namely cafile
. This was how I made the path to the cert whatever I wanted. You see, when openSSL
builds, it looks for an environment variable SSL_CERT_FILE
. As long as I set that variable with os.environ
before I imported ssl
, it would work, because ssl
would find the certificate. I simplified installCerts
down to the following:
import os
import stat
import certifi
def installCerts():
os.environ['SSL_CERT_FILE'] = certifi.where()
import ssl
# ssl build needs to happen after enviro var is set
STAT_0o775 = (stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR
| stat.S_IRGRP | stat.S_IWGRP | stat.S_IXGRP
| stat.S_IROTH | stat.S_IXOTH)
cafile = ssl.get_default_verify_paths().cafile
os.chmod(cafile, STAT_0o775)
And it seems to work fine on other people's computers now without them needing to install python.
This question helped me:
How to change the 'cafile' argument in the ssl module in Python3?