【问题标题】:Can a certificate handshake by completed if SSL Labs shows " This server's certificate chain is incomplete. Grade capped to B."?如果 SSL Labs 显示“此服务器的证书链不完整。等级上限为 B。”,是否可以完成证书握手?
【发布时间】:2017-07-27 07:54:45
【问题描述】:

我正在尝试在以“https”开头的网站上使用 urllib.request.urlopen。错误输出为: ssl.SSLError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed

有很多很棒的线程可以解决这个错误。包括提到 SSL Labs 评级的this one。我可以在我测试过的所有其他“https”网站上使用 urllib.request.urlopen。

SSL Labs 显示以下输出:

Key                        RSA 2048 bits (e 65537)
Issuer                     Let's Encrypt Authority X3
AIA:                       http://cert.int-x3.letsencrypt.org/
Signature algorithm        SHA256withRSA
Extended Validation        No
Certificate Transparency   No
OCSP Must Staple           No
Revocation information     OCSP 
Revocation status          Good (not revoked)
DNS CAA                    No (more info)
Trusted                    Yes

为了澄清,我的问题是:是否有完成握手的解决方案,不包括绕过证书验证?而如果有解决方案,能不能完全在linux、macOS和Windows上的一个python脚本里面解决?

【问题讨论】:

  • 请提供您正在使用的服务器名称或 URL。为什么要省略关键信息?这可能是您的副本:Add SSL CA File Using urllib2。这是解决问题的方法:CERTIFICATE_VERIFY_FAILED when using urllib to connect to almerys.com.
  • 如果浏览器(客户端库等)已经有缺失的部分,它可以完成握手,但这意味着您正在减少可以与您的服务器通信的客户端数量。无论如何-您是在询问从客户端解决此问题(例如通过将缺少的中间 CA 证书添加到客户端的商店),还是在修复服务器(如果它在您/您的组织的控制之下,您确实应该这样做)?
  • @jww 我省略了那个关键信息,因为我不认为它很关键。我认为这是其他人可能遇到的普遍问题。不过,我不确定我的推理和思考过程与解决问题有何关联。没有第二句话,您评论中的第一句话独立存在。不过,感谢您提供的第二个链接。我能够从 Let's Encrypt 下载 .pem 文件,从而解决了这个问题。我已经在this solution中详细说明了步骤
  • (我认为 URL 的首要重要性,顺便说一句,允许人们实际测试解决方案以在您的实际用例中工作;没有测试用例,尝试的解决方案是猜测-- 无论是编写它的人还是试图确定是否投票赞成/反对它的其他网站用户都无法 100% 确定地确定它对于遇到的现实世界场景是否实际上正确)。跨度>
  • (...对问题的“缺少中间证书”的解释绝对是最明显的符合给定信息的事情,但我不会断言这是唯一可能的原因有问题的错误)。

标签: python ssl urllib handshake pyopenssl


【解决方案1】:

您可以通过将缺少的中间证书添加到活动的X509Store 来解决此问题:

cert_text = '''
-----BEGIN CERTIFICATE-----
...put your actual certificate text here...
-----END CERTIFICATE-----
'''

# fill this out depending on which specific intermediate cert you're missing
missing_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, cert_text)

context = ssl.create_default_context() # load default trusted certificates
store = context.get_cert_store()       # get the X509Store for that context
store.add_cert(missing_cert)           # add your missing cert to it

urllib.request.urlopen(site, context=context)

请注意,如果您需要与您正在为其执行此操作的一台服务器通信,您只需将适当的cafilecapath 参数传递给create_default_context()

【讨论】:

  • @Charles Duffy 我刚刚阅读了您对我发布的准解决方案的回复。我对此很陌生,所以我不太明白您发布的内容。我对您的解决方案非常感兴趣。什么应该替换missing_cert = OpenSSL.crypto.load_publickey(...) 中的...
  • 这取决于您下载并捆绑到源中的密钥副本的编码方式。如果是 PEM(这很常见),您的第一个参数将是 OpenSSL.crypto.FILETYPE_PEM;无论如何,第二个参数将是您拥有(在这种情况下是 PEM 编码的)证书的缓冲区。
  • @Charles Duffy 我想我明白了。我会从https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem 下载.pem。然后将此文件包含在 github repo 根目录中。然后,在 python 脚本中,我将包含 missing_cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM, PATH_TO_PEM_FILE_HERE) 后跟您编写的额外 4 行?
  • 不是 PEM 文件的路径,而是 PEM 文件的文本——您可以将 PEM 文件的内容作为字符串直接放入 Python 源代码中,如上例所示 (BEGIN CERTIFICATEEND CERTIFICATE 都是您可以在 PEM 文件本身中找到的内容)。
  • @Charles Duffy 我添加了import ssl, OpenSSL 行。但现在我在store = context.get_cert_store() 行收到错误AttributeError: 'SSLContext' object has no attribute 'get_cert_store'。我用谷歌搜索了错误,但什么也没有出现。有什么想法吗?
【解决方案2】:

我无法为 urllib 回答这个问题,但我能够使用 python 请求来解决这个问题。请注意,这仅在相关网站存在受信任的证书链但服务器可能缺少根证书或中间证书时才有效。

使用 SSL 实验室服务器测试 (linked here),运行测试并向下滚动到认证路径。如果存在受信任的证书路径,但服务器由于某种原因未提供完整的链,您可以在此处以文本形式下载完整的受信任路径。复制完整的证书链并将其保存为 .pem 文件并将该文件的路径传递给 requests 函数:

r = requests.get(url, verify = "path/to/chain.pem")

requests 模块可以抛出各种 SSL 相关的认证失败,其中很多是服务器端的问题,你真的想避免禁用 SSL 验证。此解决方案仅适用于存在完整证书但相关颁发者或服务器草率地省略了根证书或中间证书的极少数情况。

【讨论】:

    【解决方案3】:

    我能够解决这个问题(对于基于 debian 的系统,我正在运行 Debian 9)。我仍然需要在 macOS 和 Windows 上测试解决方案。

    SSL Labs 报告的“认证路径”标题下,它显示:

    1   Sent by server  www.exampleSITE.com
    
    BLAH BLAH BLAH
    
    2   Extra download  Let's Encrypt Authority X3
    
    BLAH BLAH BLAH
    
    3   In trust store  DST Root CA X3   Self-signed
    
    BLAH BLAH BLAH
    

    我导航到/etc/ssl/certs/,发现没有Let's Encrypt 证书存在。然后我下载了 .pem 并重新散列。

    cd /etc/ssl/certs
    sudo wget https://letsencrypt.org/certs/lets-encrypt-x3-cross-signed.pem
    sudo c_rehash
    

    然后我测试了之前给我错误的 python 行

    page = urllib.request.urlopen('https://www.exampleSITE.com').read()

    它成功地检索了页面。

    【讨论】:

    • @Charles Duffy 澄清一下:我正在编写一个 python 脚本,该脚本可以打开并从网站(不是我的)读取,并解析一些信息以供稍后在脚本中使用。我可以从 Firefox 访问该网站,但在我的 python 脚本中使用 urllib 时不能。我可以要求运行我的脚本的用户在运行之前下载 Let's Encrypt .pem 文件。
    • 明白了。如果您使用我的答案,则无需要求您的用户修改 /etc 中的任何内容,因为您会将丢失的证书直接嵌入到需要它的脚本中。
    猜你喜欢
    • 2017-03-10
    • 2018-04-27
    • 2016-11-05
    • 2016-06-17
    • 1970-01-01
    • 2018-01-12
    • 2013-06-08
    • 2021-03-17
    • 2012-09-28
    相关资源
    最近更新 更多