Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Undo "Install Certificates.command"

In Python 3.6+ on Mac, various SSL-related operations will fail (typically with a cryptic SSL: CERTIFICATE_VERIFY_FAILED error) until you run /Applications/Python\ 3.6/Install\ Certificates.command to install root certificates. After encountering such an error, I Googled, eventually discovered this solution (which is noted at, e.g. https://bugs.python.org/issue29065#msg283984), did it, and it worked.

But now I'd like to tweak my code to catch the error I was seeing before and show a helpful error message explaining to the user that they need to run /Applications/Python\ 3.6/Install\ Certificates.command. I can't easily test this error-handling code, though, since, having run the command, I no longer get the SSL error that I want to catch.

How can I uninstall the certificates that were installed by Install Certificates.command in order to perform such testing?

like image 675
Mark Amery Avatar asked Apr 18 '18 13:04

Mark Amery


People also ask

Where is install certificates command Mac?

Navigate to Finder > Applications > Utilities > Keychain Access. Select "System" in the left-hand column. Open 'File > Import Items' and import the certificate files into the "System" keychain.

What is certifi Python?

Certifi: Python SSL Certificates. Certifi provides Mozilla's carefully curated collection of Root Certificates for validating the trustworthiness of SSL certificates while verifying the identity of TLS hosts. It has been extracted from the Requests project.


1 Answers

A .command file is (generally) just a shell script that gets special handling if double-clicked in the Finder because of its extension. So, if you want to know what one does, just read it. In this case, it's actually a bare wrapper around a Python script, which makes it even easier (since you presumably know Python even better than sh).

The key part is this:

openssl_dir, openssl_cafile = os.path.split(
    ssl.get_default_verify_paths().openssl_cafile)
# ...
os.symlink(relpath_to_certifi_cafile, openssl_cafile)

Now that you know it's using get_default_verify_paths, it's obvious why this is relevant, and how to check the same path. The default path is /Library/Frameworks/Python.framework/Versions/3.6/etc/openssl/cert.pem, but that doesn't matter; the ssl module and the setup tool both just use the function to get the path, and you can too.

And to uninstall, just delete that cert:

os.remove(openssl_cafile)

… or, maybe, just rename it, so you can go back and forth between "installed" and "uninstalled" state as you're testing.


Meanwhile, it's not quite as simple as it seems to test for this situation usefully, because there are four possibilities, not just two:

  1. No cert.pem.
  2. cert.pem linked to a file from the certifi package directory because the user used this command correctly.
  3. cert.pem not linked to that file, but still correct, because the user fixed it manually (or because the user is running your program in 2021, and things have changed about how Python installs SSL CAs).
  4. cert.pem not linked to that file, and incorrect, because the user tried to fix it manually but got it wrong.

For a LBYL validation of the user's setup, I'd just check for existence to avoid choking on case 3:

cafile = ssl.get_default_verify_paths().openssl_cafile
assert os.path.exists(cafile)

On the other hand, after you get SSL failures, and you want to EAFP-diagnose the setup to help the user, you can do something like this:

cafile = ssl.get_default_verify_paths().openssl_cafile
catarget = os.readlink(cafile)
cadir = os.path.basename(os.path.dirname(catarget))
assert cadir == 'certifi'

(Either way, you obviously want better error handling than this.)

like image 113
abarnert Avatar answered Nov 20 '22 04:11

abarnert