证书验证失败:无法获取本地颁发者证书

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

Curl 响应失败:mailwizz 上的“无法获取本地颁发者证书”

OpenSSL 验证返回码:20(无法获取本地颁发者证书)

openssl 无法获取本地颁发者证书 debian