证书验证失败:无法获取本地颁发者证书
Posted
技术标签:
【中文标题】证书验证失败:无法获取本地颁发者证书【英文标题】:certificate verify failed: unable to get local issuer certificate 【发布时间】:2019-03-19 04:41:34 【问题描述】:我正在尝试使用 python 从网络获取数据。我为其导入了 urllib.request 包,但在执行时出现错误:
certificate verify failed: unable to get local issuer certificate (_ssl.c:1045)
我在 Mac OS High Sierra 上使用 Python 3.7。
我正在尝试从以下位置获取 CSV 文件:
https://s3.amazonaws.com/assets.datacamp.com/production/course_1606/datasets/winequality-red.csv
当我将 URL 更改为“http”时 - 我能够获取数据。但是,我相信,这避免了检查 SSL 证书。
于是我上网查了一下,找到了一个解决方案:
运行/Applications/Python\ 3.7/Install\ Certificates.command
这解决了我的问题。但我对 SSL 之类的东西一无所知。您能帮我了解它实际上是如何解决我的问题的吗?
如果可能,请向我推荐任何好的资源来了解安全性和证书。我是新手。
谢谢!
注意:我确实通过了链接 - openssl, python requests error: "certificate verify failed"
我的问题与链接中的问题不同,因为我想知道当我安装 certifi
包或运行 Install\ Certificates.command
以修复错误时实际发生了什么。我对证券的理解很差。
【问题讨论】:
openssl, python requests error: "certificate verify failed"的可能重复 @stovfl - 我从提供给您的链接中阅读。只是为了澄清(我不知道 SSL 之类的): 1. OpenSSL 库是我正在使用的操作系统原生的还是 Python 使用它自己的? 2. 当我的代码尝试从特定网站获取数据时,它会在 OpenSSL 根目录中检查该网站的证书,并且由于默认情况下它不信任它,因此会向我抛出错误。我对吗? 3. 如果是这样,那么当我运行 install Certificates.command 时会发生什么? 1. 是的,pyopenssl
是对此的封装。 2. 根本不知道requests
是否使用pyopenssl
,但无法验证导致抛出错误的证书。 3. 不知道Certificats.command
,假设更新/安装所需的证书,requests
需要。
检查此***.com/questions/50236117/… 已回答here
【参考方案1】:
对于仍然想知道如何解决此问题的任何人,我通过安装“Install Certificates.command
”得到了我的
这就是我的做法,
只需双击该文件等待它安装,在我的情况下,你就可以开始了
【讨论】:
具体是如何安装的?用酿造?请解释 这是一个.command
文件,这意味着您应该只需双击它,它就会在终端中运行。
这对我很有用。我有同样的问题(macOS high Sierra + Python 3.7)。谢谢!
但是你从哪里得到这个文件?
@JosephAstrahan 它是来自 www.python.org 的标准 python 安装包。这也是官方的python版本(我通常安装这个而不是自制的)【参考方案2】:
我在 OSX 上遇到了同样的问题,而我的代码在 Linux 上完全没问题,您在问题中给出了答案!
在检查您指向/Applications/Python 3.7/Install Certificates.command
的文件后,发现此命令将默认Python 安装的根证书替换为通过certifi
包提供的根证书。
certifi
是一组根证书。每个 SSL 证书都依赖于一个信任链:您信任一个特定的证书,因为您信任该证书的父证书,您信任该证书的父证书,等等。在某些时候,没有“父”证书,而那些是“根”证书。对于那些,除了捆绑通常受信任的根证书(通常是大型信任公司,例如“DigiCert”)之外,没有其他解决方案。
例如,您可以在浏览器安全设置中查看根证书(例如 Firefox->首选项->隐私和安全->查看证书->权限)。
回到最初的问题,在运行 .command
文件之前,执行此操作会为我返回一个全新安装的空列表:
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(os.path.join(openssl_dir, openssl_cafile)))
这意味着在 OSX 上安装 Python 没有默认的证书颁发机构。一个可能的默认值正是certifi
包提供的那个。
之后,您只需创建一个具有正确默认值的 SSL 上下文,如下所示(certifi.where()
提供证书颁发机构的位置):
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)
然后像这样从 python 向url
发出请求:
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)
【讨论】:
您写道:os.path.exists(openssl_cafile)
。这不应该是os.path.exists(os.path.join(openssl_dir, openssl_cafile))
吗?
@bli 我编辑了答案以进行修复【参考方案3】:
这适用于所有操作系统:
import ssl
import certifi
urlopen(request, context=ssl.create_default_context(cafile=certifi.where()))
【讨论】:
这是最好的,因为它很简单!request
定义为什么?【参考方案4】:
我想提供一个参考。我用cmd+空格,然后输入Install Certificates.command
,然后回车。稍等片刻,弹出命令行界面开始安装。
-- removing any existing file or link
-- creating symlink to certifi certificate bundle
-- setting permissions
-- update complete
最后,它修复了错误。
【讨论】:
【参考方案5】:对于那些仍然存在此问题的人:- MacOS 上的 Python 3.6(还有其他版本?)带有自己的 OpenSSL 私有副本。这意味着 Python ssl 模块不再将系统中的信任证书用作默认值。要解决这个问题,您需要在系统中安装 certifi 包。
你可以尝试两种方式:
1) 通过画中画:
pip install --upgrade certifi
2) 如果不起作用,请尝试运行与 Python 3.6 for Mac 捆绑在一起的 Cerificates.command:
open /Applications/Python\ 3.6/Install\ Certificates.command
无论如何,您现在应该已经安装了证书,并且 Python 应该能够通过 HTTPS 连接而没有任何问题。
希望这会有所帮助。
【讨论】:
经过各种来源的多次尝试和建议,#2 对我有用!非常感谢!【参考方案6】:创建从操作系统证书到 Python 的符号链接对我有用:
ln -s /etc/ssl/* /Library/Frameworks/Python.framework/Versions/3.9/etc/openssl
(我在 macOS 上,使用 pyenv
)
【讨论】:
【参考方案7】:在开头粘贴以下代码:
# 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
【讨论】:
我的假设是否正确,这样可以避免检查 SSL 证书的有效性? 这会跳过证书验证。 不要这样做! “我家的钥匙坏了!只要把门一直开着就行了。”【参考方案8】:此页面是“证书验证失败:无法获取本地颁发者证书”的热门谷歌点击,因此虽然这不能直接回答原始问题,但以下是针对具有相同症状的问题的修复。我在尝试将 TLS 添加到 xmlrpc 服务时遇到了这个问题。这需要使用相当低级的ssl.SSLContext
类。该错误表明缺少证书。修复是在构造 SSLContext
对象时做几件事:
首先,在客户端:
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)
在服务器中,需要在上下文中安装中间证书:
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
【讨论】:
【参考方案9】:在我的情况下,此错误的原因是 OPENSSLDIR 设置为不包含实际证书的路径,可能是由于某些升级/重新安装造成的。
要验证这是否适合您,请尝试运行:
openssl s_client -CApath /etc/ssl/certs/ -connect some-domain.com:443
如果您删除-CApath /etc/ssl/certs/
并获得20
错误代码,那么这可能是原因。您还可以通过运行openssl version -a
来检查 OPENSSLDIR 的设置。
由于更改 OPENSSLDIR 需要重新编译,我发现最简单的解决方案是在现有路径中创建符号链接:ln -s /etc/ssl/certs your-openssldir/certs
【讨论】:
【参考方案10】:Certifi
提供 Mozilla 精心策划的根证书集合,用于验证 SSL 证书的可信度,同时验证 TLS 主机的身份。它是从Requests 项目中提取的。
pip install certifi
或者运行下面的程序代码:
# install_certifi.py
#
# sample script to install or update a set of default Root Certificates
# for the ssl module. Uses the certificates provided by the certifi package:
# https://pypi.python.org/pypi/certifi
import os
import os.path
import ssl
import stat
import subprocess
import sys
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 )
def main():
openssl_dir, openssl_cafile = os.path.split(
ssl.get_default_verify_paths().openssl_cafile)
print(" -- pip install --upgrade certifi")
subprocess.check_call([sys.executable,
"-E", "-s", "-m", "pip", "install", "--upgrade", "certifi"])
import certifi
# change working directory to the default SSL directory
os.chdir(openssl_dir)
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")
if __name__ == '__main__':
main()
Brew 尚未运行 Mac 版 Python3 捆绑包中的 Install Certificates.command。
【讨论】:
【参考方案11】:我在 Linux 上遇到了 conda 错误。我的解决方案很简单。
conda install -c conda-forge certifi
我不得不使用 conda forge,因为默认证书似乎有问题。
【讨论】:
【参考方案12】:对我来说,问题是我在我的.bash_profile
中设置了REQUESTS_CA_BUNDLE
/Users/westonagreene/.bash_profile:
...
export REQUESTS_CA_BUNDLE=/usr/local/etc/openssl/cert.pem
...
一旦我将 REQUESTS_CA_BUNDLE
设置为空白(即从 .bash_profile 中删除),requests
就会再次工作。
export REQUESTS_CA_BUNDLE=""
该问题仅在通过 CLI(命令行界面)执行 python requests
时出现。如果我运行requests.get(URL, CERT)
,它就可以解决了。
Mac OS Catalina (10.15.6)。
3.6.11 的 Pyenv。
我收到的错误消息:[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1056)
这个答案在别处:https://***.com/a/64152045/4420657
【讨论】:
【参考方案13】:警告:我对证书不是很了解,但我认为这值得尽早检查。
在花时间重新配置您的代码/包/系统之前,请确保您尝试下载的服务器没有问题。
我认为该错误可能具有误导性,因为“无法获取本地颁发者证书”看起来像是您的本地计算机有问题,但不一定是这种情况。
尝试将您尝试加载的页面更改为可能不错的内容,例如https://www.google.com
,然后查看问题是否仍然存在。此外,通过https://www.digicert.com/help/ 的搜索工具检查给您带来问题的域。
就我而言,DigiCert 的工具告诉我“证书未由受信任的机构签署(检查 Mozilla 的根存储)。”这可以解释为什么我似乎安装了根证书但仍然有错误。当我测试使用 HTTPS 加载不同的网站时,我没有遇到任何问题。
如果这种情况适用于您,那么我认为您可能有 3 个逻辑选项(按优先顺序排列):1)如果服务器在您的控制之下,则修复它,2)在继续使用 HTTPS 的同时禁用证书检查,3)跳过 HTTPS 并转到 HTTP。
【讨论】:
【参考方案14】:我最近在连接到 MongoDB Atlas 时遇到了这个问题。我更新到最新的certifi
python 包,它现在可以工作了。
(python 3.8,升级到 certifi 2020.4.5.1,之前的 certifi 版本是 2019.11.28)
【讨论】:
以上是关于证书验证失败:无法获取本地颁发者证书的主要内容,如果未能解决你的问题,请参考以下文章
Ruby 2.6.6 OpenSSL 1.1.1g - 证书验证失败(无法获取本地颁发者证书)
Python - [SSL: CERTIFICATE_VERIFY_FAILED] 证书验证失败:无法获取本地颁发者证书 (_ssl.c:1091)
urllib.error.URLError: <urlopen 错误 [SSL: CERTIFICATE_VERIFY_FAILED] 证书验证失败:无法获取本地颁发者证书 (_ssl.c:11