Question :
I am trying to get data from the web using python. I imported urllib.request package for it but while executing, I get error:
certificate verify failed: unable to get local issuer certificate (_ssl.c:1045)
- I am using Python 3.7 on Mac OS High Sierra.
- I am trying to get CSV file from:
https://s3.amazonaws.com/assets.datacamp.com/production/course_1606/datasets/winequality-red.csv
When I changed the URL to ‘http’ – I am able to get data. But, I believe, this avoids checking SSL certificate.
So I checked on the internet and found one solution:
Run /Applications/Python 3.7/Install Certificates.command
This solved my problem. But I have no knowledge on SSL and the likes. Can you help me understand what it actually did to solve my issue.
If possible, please recommend me any good resource to learn about the security and certificates. I am new to this.
Thanks!
Note: I did go through the link – openssl, python requests error: “certificate verify failed”
My question differs from the one in link because, I want to know what actually happens when I install certifi
package or run Install Certificates.command
to fix the error. I have a poor understanding of securities.
Answer #1:
For anyone who still wonders on how to fix this, i got mine by installing the “Install Certificates.command
“
Here is how I did,
Just double click on that file wait for it to install and in my case, you will be ready to go
Answer #2:
I hit the same issue on OSX, while my code was totally fine on Linux, and you gave the answer in your question!
After inspecting the file you pointed to /Applications/Python 3.7/Install Certificates.command
, it turned out that what this command replaces the root certificates of the default Python installation with the ones shipped through the certifi
package.
certifi
is a set of root certificates. Each SSL certificate relies a chain of trust: you trust one specific certificate because you trust the parent of that certificate, for which you trust the parent, etc. At some point, there is no “parent” and those are “root” certificates. For those, there is no other solution than bundling commonly trusted root certificates (usually big trust companies like eg. “DigiCert”).
You can for instance see the root certificates in your browser security settings (for instance for Firefox->Preference->Privacy and security->view certificates->Authorities).
Coming back to the initial problem, and prior to running the .command
file, executing this returns for me an empty list on a clean installation:
import os
import ssl
openssl_dir, openssl_cafile = os.path.split(
ssl.get_default_verify_paths().openssl_cafile)
# no content in this folder
os.listdir(openssl_dir)
# non existent file
print(os.path.exists(openssl_cafile))
This means that there is no default certificate authority for the Python installation on OSX. A possible default is exactly the one provided by the certifi
package.
After that, you just can create an SSL context that has the proper default as the following (certifi.where()
gives the location of a certificate authority):
import platform
# ...
ssl_context = ssl.SSLContext(ssl.PROTOCOL_TLS)
ssl_context.verify_mode = ssl.CERT_REQUIRED
ssl_context.check_hostname = True
ssl_context.load_default_certs()
if platform.system().lower() == 'darwin':
import certifi
ssl_context.load_verify_locations(
cafile=os.path.relpath(certifi.where()),
capath=None,
cadata=None)
and make request to an url
from python like this:
import urllib
# previous context
https_handler = urllib.request.HTTPSHandler(context=ssl_context)
opener = urllib.request.build_opener(https_handler)
ret = opener.open(url, timeout=2)
Answer #3:
I would like to provide a reference. I use cmd + space, then type Install Certificates.command
, and then press Enter. After a short while, the command line interface pops up to start the installation.
-- removing any existing file or link
-- creating symlink to certifi certificate bundle
-- setting permissions
-- update complete
Finally, it fixes the errors.
Answer #4:
This worked in all OS:
import ssl
import certifi
urlopen(request, context=ssl.create_default_context(cafile=certifi.where()))
Answer #5:
Paste the following code at the start:
# paste this at the start of code
import ssl
try:
_create_unverified_https_context = ssl._create_unverified_context
except AttributeError:
pass
else:
ssl._create_default_https_context = _create_unverified_https_context
Answer #6:
This page is the top google hit for “certificate verify failed: unable to get local issuer certificate”, so while this doesn’t directly answer the original question, below is a fix for a problem with the same symptom. I ran into this while trying to add TLS to an xmlrpc service. This requires use of the fairly low-level ssl.SSLContext
class. The error indicates that a certificate is missing. The fix was to do several things when constructing SSLContext
objects:
First, in the client:
def get_client():
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
# Load the default certs:
context.load_default_certs()
# Optionally, install the intermediate certs.
# This _should_ be handled by the server, but
# maybe helpful in some cases.
# context.load_verify_locations('path/to/ca_bundle.crt')
return xmlrpc.client.ServerProxy('https://server.yourdomain.com/', context=context)
In the server, you need to install the intermediate certs in the context:
class SecureXMLRPCServer(socketserver.TCPServer,
xmlrpc.server.SimpleXMLRPCDispatcher):
# https://gist.github.com/monstermunchkin/1100226
allow_reuse_address = True
def __init__(self, addr, certfile, keyfile=None,
ca_bundle=None,
requestHandler=xmlrpc.server.SimpleXMLRPCRequestHandler,
logRequests=True, allow_none=False, encoding=None,
bind_and_activate=True, ssl_version=ssl.PROTOCOL_TLSv1_2):
self.logRequests = logRequests
# create an SSL context
self.context = ssl.SSLContext(ssl_version)
self.context.load_default_certs()
# The server is the correct place to load the intermediate CA certificates:
self.context.load_verify_locations(ca_bundle)
self.context.load_cert_chain(certfile=certfile, keyfile=keyfile)
xmlrpc.server.SimpleXMLRPCDispatcher.__init__(self, allow_none,
encoding)
# call TCPServer constructor
socketserver.TCPServer.__init__(self, addr, requestHandler,
bind_and_activate)
if fcntl is not None and hasattr(fcntl, 'FD_CLOEXEC'):
flags = fcntl.fcntl(self.fileno(), fcntl.F_GETFD)
flags |= fcntl.FD_CLOEXEC
fcntl.fcntl(self.fileno(), fcntl.F_SETFD, flags)
def get_request(self):
newsocket, fromaddr = self.socket.accept()
# create an server-side SSL socket
sslsocket = self.context.wrap_socket(newsocket, server_side=True)
return sslsocket, fromaddr
Answer #7:
For those who this problem persists: –
Python 3.6 (some other versions too?) on MacOS comes with its own private copy of OpenSSL. That means the trust certificates in the system are no longer used as defaults by the Python ssl module. To fix that, you need to install a certifi package in your system.
You may try to do it in two ways:
1) Via PIP:
pip install --upgrade certifi
2) If it doesn’t work, try to run a Cerificates.command that comes bundled with Python 3.6 for Mac:
open /Applications/Python 3.6/Install Certificates.command
One way or another, you should now have certificates installed, and Python should be able to connect via HTTPS without any issues.
Hope this helped.
Answer #8:
I had the error with conda on linux. My solution was simple.
conda install -c conda-forge certifi
I had to use the conda forge since the default certifi appears to have problems.