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