How to make Python use CA certificates from Mac OS TrustStore?
Asked Answered
V

10

94

I need to use custom root certificates on the company intranet and loading them in the Mac OS TrustStore (KeyChain) does solve the problem for all browsers and GUI apps.

It seems that it works even with the version of curl that ships with Mac OS X but it doesn't work with python, even the version that ships with Mac OS 10.12 Sierra (Python 2.7.10)

Still, it seems that I would be hit by:

urllib2.URLError: <urlopen error [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed (_ssl.c:590)>

How can I solve this?

Because I encounter this issue in lots and lots of Python tools. I would really appreciate if I find a way to avoid it without having to patch them.

Providing the custom CA certificate myself is not an option because I cannot patch tens of Python tools that I use.

Most of the tools are using the requests library but, there are a few that are using the native ssl support in Python directly.

Vermis answered 18/11, 2016 at 19:20 Comment(2)
Relevant to: https://mcmap.net/q/48852/-urllib-and-quot-ssl-certificate_verify_failed-quot-error/1959808Chewy
Why do we need these certificates in the first place? Might that not open you up to security vulnerabilities?Cordon
V
16

If you put the additional certificates in a PEM bundle file you can use these two environment variables to overwrite the default cert stores used by Python openssl and requests.

SSL_CERT_FILE=/System/Library/OpenSSL/cert.pem
REQUESTS_CA_BUNDLE=/System/Library/OpenSSL/cert.pem

Please note that this file does not exist, you need to build it yourself.

Vermis answered 20/11, 2016 at 22:22 Comment(2)
Mine was located at /Library/Frameworks/Python.framework/Versions/2.7/etc/openssl/cert.pemStocks
// , Setting REQUESTS_CA_BUNDLE to the stacked .pem I generated fixed my issue with Requests for an internal Root CA. Thanks, @Sorin.Rubetta
F
95

This is also a problem in Python 3.6 with MacOS Sierrra. I know your use case is different. But I stumbled upon this thread while investigating this problem. So if anyone is also having this article is worth checking out:

http://www.cdotson.com/2017/01/sslerror-with-python-3-6-x-on-macos-sierra/

In a nutshell: Python 3.6 does not rely on MacOS' openSSL anymore. It comes with its own openSSL bundled and doesn't have access on MacOS' root certificates.

You have two options:

Run an install command shipped with Python 3.6

cd /Applications/Python\ 3.6/
./Install\ Certificates.command

or

Install the certifi package with

pip install certifi

I chose the first option and it worked.

Farce answered 8/2, 2017 at 8:16 Comment(8)
There is also the option to get python from homebrew, which does it for you automatically.Assiduity
The second option is not enough; to create a symlink in the OpenSSL directory is also needed, and that is what the command in the first option does. See this issue for details.Zoa
Awesome! This also helped for an ansible issue I was having: github.com/ansible/ansible/issues/50777Personable
Neither works for me because @Zoa says why the second one can't work and the first one won't work because there is no sort of Python direction within Applications for me, regardless of if you try 3.6 in folder name or any other version numberOvertire
This solution STOPPED working in OSX Catalina with Python 3.7. There is no /Applications/Python 3.7 directory and the Install Certificates command is not present. Installing certification also does NOT solve this issue anymore. #STUCK!! gggrrrrr.... Has anyone solved this on Catalina?Nineveh
This works. If you have custom CA certs, add them to /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/certifi/cacert.pem then re-run /Applications/Python\ 3.7/Install\ Certificates.commandIntrench
I think this solution good but not enough for the people using pyenv or direct build of python. After installing certifi, we need to follow https://mcmap.net/q/223270/-how-to-make-python-use-ca-certificates-from-mac-os-truststoreOzoniferous
(Mostly so I don't forget) I managed to get the second option to work by first copying my self-signed certificates to the end of the certifi certificate store that is located at certifi.where() (MacOS Catalina, Python 3.9 Homebrew install).Sepaloid
T
94

Run this to set the appropriate variables. This is a combination of the answers that have already been given here. Put it in your ~/.bash_profile to make it permanent.

CERT_PATH=$(python -m certifi)
export SSL_CERT_FILE=${CERT_PATH}
export REQUESTS_CA_BUNDLE=${CERT_PATH}
Tabathatabb answered 4/9, 2019 at 21:25 Comment(7)
Wonderful! This answer was most relevant for fixing my issue within a Python 3.8 virtualenv setup, within which Jupyter notebook ran into the certificate error. I sourced the ".bash_profile" within the env. And that was it.Arnelle
@Arnelle it seems you had solved the problem I am struggeling with … Please take a quick look at my question here: #64591035Deutsch
Didn't work for me. Python 3.9.2 installed via brew on Mac OS Big Sur 11.2.3Dozer
@MarcelloRomani I use Mac OS Big Sur 11.2.3 and It was successful.Monocle
Thanks, worked for me! I am running Python 3.8.14 on my Mac installed from source.Insuperable
Thanks @hitesh-patel this worked for me (macOS 12.6 Monterey + pyenv virtualenv Python 3.9.14), but how to make this work for all installed Python versions + imported self-signed certificates?Hematuria
Worked for me with Python 3.10 from MacPorts. Thanks!Caston
E
25

This was intended as an edit to an existing question, but since the queue was already full, posting as a separate answer.

Tested on MacOS 12.3.1 with python 3.10 installed with MacPorts.

If you prefer to trust root CA according to your OS, export them from System Roots keychain into a single file:

security export -t certs -f pemseq -k /System/Library/Keychains/SystemRootCertificates.keychain -o bundleCA.pem

If, additionally, you want to trust some internal self-signed CAs, export them, too. They are probably stored under the System keychain:

security export -t certs -f pemseq -k /Library/Keychains/System.keychain -o selfSignedCAbundle.pem

Merge the two files:

cat bundleCA.pem selfSignedCAbundle.pem >> allCAbundle.pem

Export as bash variable

export REQUESTS_CA_BUNDLE=/path/to/allCAbundle.pem

Consider adding the last code snippet to your .bash_profile

Note that REQUESTS_CA_BUNDLE works only for a single file, and does not for a directory.

Encrust answered 29/4, 2022 at 6:22 Comment(4)
The two security export commands are the same, which I think must be a typo. Is there a way to find location of internal CAs on mac?Nathalia
@sudocode: Indeed. Corrected. Thanks for pointing out!Encrust
I've been scouring the internet for a solution that worked and this was what I neededPozsony
Necessary e.g. for corporate networks with custom certs.Budge
F
22

Mac brew install python env.

$ python3
Python 3.7.3 (v3.7.3:ef4ec6ed12, Mar 25 2019, 16:52:21) 
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> import certifi
>>> certifi.where()
'/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/certifi/cacert.pem'
>>> 

Or from the command line:

$ python -m certifi

then need link cacert.pem as cert.pem

$ ln -s /Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/site-packages/certifi/cacert.pem cert.pem
$ pwd
/Library/Frameworks/Python.framework/Versions/3.7/etc/openssl

rehash

then work fine.

Fervidor answered 15/6, 2019 at 5:34 Comment(5)
Tried so many things but this is the only one that worked. KudosGrath
Worked for me, thanks!Krimmer
this worked for me as well, thank you very much, I have spent a week searching for a solutionThunell
In case you wonder how to find out which file to link to: use command python -c "import ssl; print(ssl.get_default_verify_paths())" and from the output, it's the value listed for openssl_cafileExcursionist
If rehash does not work (command not found), use c_rehash instead.Excursionist
V
16

If you put the additional certificates in a PEM bundle file you can use these two environment variables to overwrite the default cert stores used by Python openssl and requests.

SSL_CERT_FILE=/System/Library/OpenSSL/cert.pem
REQUESTS_CA_BUNDLE=/System/Library/OpenSSL/cert.pem

Please note that this file does not exist, you need to build it yourself.

Vermis answered 20/11, 2016 at 22:22 Comment(2)
Mine was located at /Library/Frameworks/Python.framework/Versions/2.7/etc/openssl/cert.pemStocks
// , Setting REQUESTS_CA_BUNDLE to the stacked .pem I generated fixed my issue with Requests for an internal Root CA. Thanks, @Sorin.Rubetta
B
8

I see a lot of answers out there recommend to turn off certificate validation or to use certifi.where. While turning off SSL is obvious risk. certifi.where is also a risk, mainly if you intend to make this code a production code that will run in a customer env.

PEP describing why it is wrong. ssl.create_default_context is well integrate with linux and windows trustStore. the problem is, as in your case with mac. I solve this by loading the certificates using the integrated security commandline tool

def create_macos_ssl_context():
    import subprocess
    import ssl
    import tempfile
    ctx = ssl.create_default_context()
    macos_ca_certs = subprocess.run(["security", "find-certificate", "-a", "-p",
                                     "/System/Library/Keychains/SystemRootCertificates.keychain"],
                                    stdout=subprocess.PIPE).stdout

    with tempfile.NamedTemporaryFile('w+b') as tmp_file:
        tmp_file.write(macos_ca_certs)
        ctx.load_verify_locations(tmp_file.name)
    print(ctx.get_ca_certs()) # here you can see the certs were loaded

Note that this gives you the systemRoot certificates. if you need the user than simply change the value in the security command.

You can also update your certifi file (not recommended for production unless you know what you are doing)

import os
from pathlib import Path
import certifi


KEYCHAIN_TYPES = {
    'root': '/System/Library/Keychains/SystemRootCertificates.keychain',
    'system': '/Library/Keychains/System.keychain',
    'user': f'{str(Path.home())}/Library/Keychains/login.keychain-db'
}


def update_certifi_with_system_roots_for_mac(key_chain_type: str, overwrite_certifi = False) -> None:
    ca_bundle_path = Path(certifi.where())
    print(f'Start update certifi ca_bundle from keychain "{key_chain_type}". Path: {ca_bundle_path}')
    cmd_args = ('security', 'find-certificate', '-a', '-p', KEYCHAIN_TYPES[key_chain_type])
    system_certs = os.system(' '.join(cmd_args))
    ca_bundle_data = ca_bundle_path.read_text(encoding='utf-8')
    if system_certs in ca_bundle_data:
        print('Certificates already in certifi ca bundle. finish process')
        return
    print('update certifi ca bundle')
    if overwrite_certifi:
        ca_bundle_path.write_text(f'{system_certs}', encoding='utf-8')
    else:
        ca_bundle_path.write_text(f'{system_certs}{os.linesep}{ca_bundle_data}', encoding='utf-8')
    print('update certifi ca bundle completed')

update_certifi_with_system_roots_for_mac('root',True)

You can read more In this blog post and get ideas how to refine your native ssl usage using this repo of requests implementation

Bypass answered 21/11, 2021 at 11:43 Comment(2)
This is by far and away the best answer I have seen to a hundred questions on the subject. I would recommend you remove the 'print' line though. It can be used as a silent initialisation method.Pragmatics
Thanks very much for the compliment. The print is just to show that the certs where loaded even after the file is destroyed.Bypass
V
5

As an update and datapoint, I ran into this issue running Python 3.7.0 on macOS 10.13.4:

$ ipython
Python 3.7.0 (v3.7.0:1bf9cc5093, Jun 26 2018, 23:26:24)
Type 'copyright', 'credits' or 'license' for more information
IPython 7.0.1 -- An enhanced Interactive Python. Type '?' for help.

In [1]: import bokeh.sampledata

In [2]: bokeh.sampledata.download()
Using data directory: /Users/me/.bokeh/data

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

Instructions for solving the problem are in /Applications/Python\ 3.7/ReadMe.rtf

Following the suggestion there and running /Applications/Python\ 3.7/Install\ Certificates.command solved the problem:

From the terminal:

$ /Applications/Python\ 3.7/Install\ Certificates.command

Re-starting IPython...

$ ipython
>>> import bokeh.sampledata

>>> bokeh.sampledata.download()
Using data directory: /Users/me/.bokeh/data
Downloading: CGM.csv (1589982 bytes)
   1589982 [100.00%]
...
Victual answered 30/9, 2018 at 18:7 Comment(0)
M
4

In my case only installing "Install Certificates.command" solved this issue using MAC OS

Update SSL certificate with certifi (MacOS only)

All we would have to do is to run command with the following piece of code:

- Press "command + space" button or open Spotlight
- type "Install Certificates.command" 

What this command does is update our system’s SSL certificate directory for MacOS.

Mucus answered 9/3, 2023 at 6:53 Comment(0)
W
1

For me /Applications/Python\ 3.6/./Install\ Certificates command fails on pip certifi install. I am on mac High Sierra and use python3 so pip somewhat fails and I have to use pip3 instead.

So here what I did:

  1. Manually ran pip3 install --update certify in a shell
  2. Remove the install certifi line from the command script
  3. Reran the script and everything was fine.

Note that you will end up with a cert.pem symbolic link in: /Library/Frameworks/Python.framework/Versions/3.6/etc/openssl/

Willettawillette answered 22/9, 2018 at 16:55 Comment(0)
D
1

Solution for MacOS or Linux with latest Python versions installed either as standalone or via port or brew

Download certificates from Certifi project at https://github.com/certifi/python-certifi/blob/master/certifi/cacert.pem. FYI Certifi is a 3rd party library that provides Mozilla’s curated collection of Root Certificates for validating the trustworthiness of SSL certificates while verifying the identity of TLS hosts.

Then add to your ~/.zshrc on the latest MacOS or ~/.bash_profile or similar:

export SSL_CERT_FILE=/pathtodownloadedfile/cacert.pem
export REQUESTS_CA_BUNDLE=/pathtodownloadedfile/cacert.pem
Doyenne answered 7/1, 2022 at 17:6 Comment(2)
Python requests library depends on certifi lib. And use it certificates by default. No need to add it to the env variable path.Bypass
Thanks igo, on Mac, this addresses the issue where openssl fails to build because mac certs are not loaded in python venv.Deserted

© 2022 - 2024 — McMap. All rights reserved.