由于 AKI DirName 扩展而在 openSSL 中发现根本原因:Python [SSL: CERTIFICATE_VERIFY_FAILED] ...: 无法获取本地颁发者证书
简而言之,
我得到:[SSL: CERTIFICATE_VERIFY_FAILED] 证书验证失败:无法获取本地颁发者证书 (_ssl.c:1129)
比 [SSL: CERTIFICATE_VERIFY_FAILED] 证书更糟糕的错误验证失败:证书链中的自签名证书 (_ssl.c:1129)
我怀疑 python 或我自己的代码,但相同纯 openSSL 会出现错误:
openssl s_client -connect my-domain.com:443 -CAfile root.pem -verify 2
......
Verify return code: 21 (unable to verify the first certificate)
Extended master secret: no
Max Early Data: 0
---
......
如果 openSSL 不起作用,python 也将不起作用。可以肯定这是针对 TLS 握手时出现的任何错误。 ;-)
比较新旧证书,最终在两个新的附加Authority Key Identifier
条目DirName
和serial
中找到了根本原因。它们以前没有出现过:
openssl x509 -in my-domain.pem -text
The only diff and root cause was Authority Key Identifier with two new entries:
X509v3 Authority Key Identifier:
keyid:1D:E8:38:95:85:65:A2:D9:44:99:96:30:D1:81:D5:5B:F7:38:CC:8C
DirName:/C=DE/O=My Company/OU=My OU/CN=My Sub-CA
serial:02
新的也是最后一个问题:为什么 openSSL 不能与 AKI
中的 DirName
和 serial
一起使用?
我保留了所有最初的东西,因为这可能会帮助其他人构建灵活的 python 代码......
现在,我下面的所有方法都工作正常,正如预期的那样。
如果 CA 不在信任库中,我会收到预期的 python 错误:
[SSL: CERTIFICATE_VERIFY_FAILED] 证书验证失败:证书链中的自签名证书 (_ssl.c:1129)
在上下文、会话或请求级别设置信任库,采取你想要的,它对我有用。
过去 2 年的情况
过去(2 年前)我将我的私有 CA(根和子 ca)导入到 Win 10 本地计算机信任库
并使用此 代码片段 创建我自己的 urllib3 上下文来加载使用 context.load_default_certs()
的 Windows 商店
一直有效,直到本周,我必须更新我的 CA 和服务器证书。 :-(
我删除了旧的私有 CA 捆绑包,并将新的 CA 像往常一样导入到 Windows 本地计算机信任库和 Firefox 信任库中。
所有浏览器都可以在新的信任库中正常工作!
更新我的私有 CA 后出现新问题
使用 python 请求
,我现在再次遇到这些恼人的错误:步骤代码中的 SSLError - 消息:HTTPSConnectionPool(host='my-domain.com', port=443): Max retries gone url: / (由 SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] 证书验证失败:无法获取本地颁发者证书(_ssl.c:1129)')))
我的代码方法
我当前的代码尝试了不同的方法来解决该问题,但没有任何效果。
我确定(或希望)故障在我这边,但我不再看到它了;-)
import certifi
import json
import os
import requests
class MyHttpAdapter(HTTPAdapter):
def init_poolmanager(self, *args, **kwargs):
# Google: "SSL failure on Windows using python requests" -- usage of Windows Truststore
# https://stackoverflow.com/questions/42981429/ssl-failure-on-windows-using-python-requests
context = create_urllib3_context()
我尝试了这 3 个设置来加载不同的 ca 捆绑包(每次尝试只有一个代码行处于活动状态)
## --------------------
# variant 1: usage of Windows Truststore where I imported my root-ca and sub-ca into local computer storage
# load_default_certs() --> this loads the OS defaults of Windows Truststore!!!!
context.load_default_certs()
# variant 2: my special ca bundle contains only my private CA only with 3 entries: root-ca, sub-ca and server cert
# load_verify_locations() --> this loads just a specific CA bundle !!!!
context.load_verify_locations(cafile="C:/Users/...../my_ca_bundle.pem")
# variant 3: I added my private CA with root-ca, sub-ca and server cert to certifi cacert.pem file as a hack
# certifi.where(): C:\Users\........\Python39\Lib\site-packages\certifi\cacert.pem
# load_verify_locations() --> this loads just a specific CA bundle !!!!
context.load_verify_locations(cafile=certifi.where())
## --------------------
以验证我的私有 CA 位于所选存储内,我在 CA 的 CommonName 上执行 assert
,因此如果找不到,它会快速失败。如果没问题,稍后会将上下文添加到 session.mount 中:
# get and print all CAs from loaded list to verify that my CA is within the list
# notice the server certificate of pem file is not loaded into the list, don't no why, maybe because it's no CA
json_ca_certs = context.get_ca_certs()
print("All current context CA certs: {}".format(json.dumps(json_ca_certs, indent=1, sort_keys=True)))
length=len(json_ca_certs)
print("Number of certificates in bundle: {}".format(length))
# fail fast:
assert "MY CommonName" in str(json_ca_certs), "private CA not found in used bundle"
kwargs['ssl_context'] = context
return super().init_poolmanager(*args, **kwargs)
我没有设置这些环境变量,它们是 None
def __init__(self, **kwargs):
print("REQUESTS_CA_BUNDLE: '{}'".format(os.environ.get('REQUESTS_CA_BUNDLE')))
print("CURL_CA_BUNDLE: '{}'".format(os.environ.get('CURL_CA_BUNDLE')))
我创建了一个会话,并且使用我的信任库创建上下文不再有效,我尝试了 3 种不同的方法来使用 session.verify(每次尝试仅激活一个代码行):
self.session = requests.Session()
# variant 1: default not working anymore:
self.session.verify = True
# variant 2: usage of my special ca bundle does not work
self.session.verify = "C:/Users/...../my_ca_bundle.pem"
# variant 3: certifi cacert.pem file contains my private CA and does not work
# certifi.where(): C:\Users\........\Python39\Lib\site-packages\certifi\cacert.pem
self.session.verify = certifi.where()
在这里,我最终将适配器安装到会话中:
# mount with Schema only as prefix to use them for all my calls (this worked in past and should not be a new problem)
adapter = self.MyHttpAdapter()
self.session.mount("https://", adapter)
现在我执行 self.session.get()
调用。我再次尝试了这些设置,因为 context cafile
和 session.verify
都可以工作,直接在会话中的requests
:
_api_url = 'https://my-domain.com/api/function'
###
# variant 1: does not longer work, no setting above for session.verify nor context.load...():
response = self.session.get(_api_url)
# variant 2: verify = my special ca bundle work around does not work:
response = self.session.get(_api_url, verify="C:/Users/...../my_ca_bundle.pem")
# variant 3: verify = certifi cacert.pem work around does not work:
response = self.session.get(_api_url, verify=certifi.where())
我添加到请求/会话中。 py 一些打印调试输出,看看使用了什么
会话 request() 使用的最终设置:
settings = self.merge_environment_settings(
prep.url, proxies, stream, verify, cert
)
print(f"DEBUG: request settings: {settings}")
sessions merge_environment_settings() 就在 return () 之前
print(f"DEBUG: merge_environment_settings() before return: verify={verify}")
return {'verify': verify, 'proxies': proxies, 'stream': stream,
'cert': cert}
只是为了看看我的设置是否被使用:
DEBUG: merge_environment_settings() before return: verify=path_to_ca_bundle.pem
DEBUG: request settings: {'verify': 'path_to_ca_bundle.pem', 'proxies': OrderedDict(), 'stream': False, 'cert': None}
Python 和安装的模块是最新的:
Python 3.9.6(标签/v3.9.6:db3ff76,2021 年 6 月 28 日,15:26:21)
证书==2021.10.8
请求==2.27.1
请求工具带==0.9.1
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
带有简短问题和解决方案的初始条目已经太长了。由于它现在可以使用新的 CA 和服务器证书,因此我将对此答案提出最新问题。
在 rfc3280 4.2.1.1 授权密钥标识符
注意事项:全部三个AuthorityKeyIdentifier 属性是可选的,并且不链接在一起。
但在前面的段落中,人们可以读到:
该标识可以基于1.密钥标识符(颁发者证书中的主题密钥标识符)或2 。发行人名称和序列号。
相反,我们在rfc4158 3.5.12 “匹配关键标识符(KID)”
这只是一个排序标准,而不是验证标准。
最后在openSSL FAQ 15“为什么OpenSSL设置权限密钥标识符( AKID)扩展名错误?”
所以,他们的意思是这样???
这可以通过包含
来完成
--> 或 <--
在后一种情况下,因为它正在识别证书 B
它必须包含 B 的发行人名称和序列号。
这周我终于迷茫了……
The initial entry with short question and solution is already much too long. As it works now with new CA and server certificate, I will put latest question to this answer.
In rfc3280 4.2.1.1 Authority Key Identifier
Notice: All three AuthorityKeyIdentifier attributes are OPTIONAL and not linked together.
But in the paragraph before one could read:
The identification MAY be based on either the 1. key identifier (the subject key identifier in the issuer's certificate) or on the 2. issuer name and serial number.
In contrast we see the NOTE in rfc4158 3.5.12 "Matching Key Identifiers (KIDs)"
It's a sorting criteria only and not a validation criteria.
And finally in openSSL FAQ 15 "Why does OpenSSL set the authority key identifier (AKID) extension incorrectly?"
So, they mean this???
This can be done either by including
--> or <--
In this latter case because it is identifying certificate B
it must contain the issuer name and serial number of B.
I'm finally confused for this week...