据我所知,目前无法为您用于身份验证的客户端证书指定密码。
这有点问题,因为您通常总是希望密码保护包含私钥的 .pem 文件。 openssl
甚至不会让您在没有密码的情况下创建一个。
就像是:
requests.get('https://kennethreitz.com', cert='server.pem', cert_pw='my_password')
很确定你应该使用cert
参数: cert=('server.pem', 'my_password')
@西格玛病毒24
该元组用于(certificate, key)
。 目前不支持加密的密钥文件。
stdlib仅支持 3.3 版中的那些。
嘿,@t-8ch,您不小心链接到本地 FS 上的文件。 ;)正确的链接。
非常正确@t-8ch。 这就是为什么我不应该在公交车上回答问题。 :/
所以目前的共识是我们不支持这一点。 在非 3.3 版本的 Python 中添加支持可能需要做多少工作?
在这种情况下抛出错误有多难? 我刚刚遇到了这个愚蠢的问题,花了两个小时才弄明白,如果它会抛出错误就好了,它目前只是坐在那里循环。 感谢您的精彩图书馆!
等等,它在哪里循环? 我们在哪里执行失败? 你能从我们循环的地方打印回溯吗?
它似乎挂在这里:
r = requests.get(url,
auth=headeroauth,
cert=self.cert_tuple,
标题=标题,
超时=10,
验证=真)
我尝试将超时调高或调低无济于事,但我想它在超时之前就知道它不能使用证书。 谢谢!
啊,抱歉,我没说清楚。 我的意思是让它挂起,然后用 Ctrl + C 杀死它,这样 python 就会抛出一个 KeyboardInterrupt 异常,然后看看我们在回溯中的位置。 我想知道 Requests 中的哪里停止执行。
正在发生的事情(或者至少我在许多情况下看到的)是 OpenSSL 在获得受密码保护的证书后会提示用户输入密码。 它不会出现在任何日志中(因为提示是直接打印的),并且不会超时,因为它正在等待用户按 Enter。
毋庸置疑,当代码在服务器上运行时,这是一种繁琐而危险的行为(因为它会挂起您的工作人员,除了终止进程之外别无选择进行恢复)。
在这种情况下,有没有办法让请求引发异常而不是提示输入密码,或者这完全不受您的控制并且在 OpenSSL 的手中?
@maxnoel我很确定这在 OpenSSL 的手中,但如果你能回答@Lukasa的问题(关于这个问题的最后一条评论),这将非常有助于给出明确的答案,以确定我们是否可以提供任何帮助.
您可以从交互式 python 提示中确认 OpenSSL 在 stdin 上阻塞了密码:
>>> r = requests.get("https://foo.example.com/api/user/bill", cert=("client.crt", "client.key"))
Enter PEM pass phrase:
>>>
如果您从后台进程运行,我认为 OpenSSL 将阻止等待该输入。
没错。 是否有任何请求可以防止这种情况发生? 在没有给出密码时引发异常比在 stdin 上提示输入内容(尤其是在非交互式程序中)有用得多。
恐怕我不知道任何方式。 @收割者绿巨人?
有很多方法可以阻止 OpenSSL 这样做,但我不确定它们是否被 pyOpenSSL 公开。 requests 在哪里调用 pyopenssl 加载客户端证书? 我可以挖一点。
@reaperhulk这是在 urllib3 中完成的,这里。
我们还为 stdlib 做了一些非常相似的事情,这将是一个完全不同的问题。
所以我们可以使用 PyOpenSSL 使用这样的补丁来做到这一点。 在 stdlib 版本中,我们需要使用load_cert_chain
和密码。
这个问题解决了吗? 我目前在尝试连接到 Apache 服务器时遇到了这个问题。
它没有。
可能包含客户端证书/密钥的 PKCS#12 格式(和加密)容器怎么样? 这是否属于相同的功能请求?
@mikelupo 是的。
@telam @mikelupo
我也有同样的问题,google了很多,最后用pycurl解决了。
在我的情况下,我使用 openssl 将我的 .pfx 文件转换为包含证书和密钥(使用密码短语加密)的 .pem 文件,然后调用以下代码。
import pycurl
import StringIO
b = StringIO.StringIO()
c = pycurl.Curl()
url = "https://example.com"
c.setopt(pycurl.URL, url)
c.setopt(pycurl.WRITEFUNCTION, b.write)
c.setopt(pycurl.CAINFO, "/path/cacert.pem")
c.setopt(pycurl.SSLKEY, "/path/key_file.pem")
c.setopt(pycurl.SSLCERT, "/path/cert_file.pem")
c.setopt(pycurl.SSLKEYPASSWD, "your pass phrase")
c.perform()
c.close()
response_body = b.getvalue()
顺便说一句,为了安全起见,最好不要对pass phrase
进行硬编码
当然。 也就是说,问题并不是真的需要密码短语——而是 OpenSSL 使您的程序在等待某人在 stdin 中输入密码短语时挂起,即使是在非交互式、GUI 或远程程序的情况下。
当需要密码但没有提供密码时,应该引发异常。
如果您对密钥使用默认密码短语 '',则 openssl 不会挂起。
它会返回一个错误的密码文本。 你可以立即改变你的 py 流程
然后在没有那个明显的摊位的情况下通知用户
任何添加此功能的计划
我们想添加它,但我们目前没有时间表来添加它。
@botondus我想我找到了一种更简单的方法来使用请求库来实现这一点。 我正在为面临此问题的其他人记录此信息。
我假设您有一个 .p12 证书和密钥的密码。
// Generate the certificate file.
openssl pkcs12 -in /path/to/p12cert -nokeys -out certificate.pem
// Generate private key with passpharse, First enter the password provided with the key and then an arbitrary PEM password //(say: 1234)
openssl pkcs12 -in /path/to/p12cert -nocerts -out privkey.pem
好吧,我们还没有完成,每次需要与服务器通信时,我们都需要生成不需要 PEM 密码的密钥。
// Running this command will prompt for the pem password(1234), on providing which we will obtain the plainkey.pem
openssl rsa -in privkey.pem -out plainkey.pem
现在,您将拥有certificate.pem
和plainkey.pem
,这两个文件都需要使用请求与 API 对话。
这是使用这些证书和密钥的示例请求。
import requests
url = 'https://exampleurl.com'
headers = {
'header1': '1214141414',
'header2': 'adad-1223-122'
}
response = requests.get(url, headers=headers, cert=('~/certificate.pem', '~/plainkey.pem'), verify=True)
print response.json()
希望这可以帮助:
抄送@kennethreitz @Lukasa @sigmavirus24
我通过小道消息听说亚马逊在内部就是这样做的。
我也面临这个问题。 我担心的是我不想将普通私钥存储到文件系统中(可能有被他人盗用的风险)。 所以在我看来,实现这一点的更可扩展的方法是支持使用诸如PEM encoded string of private key
而不是文件路径来指定私钥。 只是将私钥/证书的加密/解密以他们喜欢的方式留给开发人员。
看了request的源码,好像不太容易实现,request依赖python的ssl lib,只支持证书/私钥文件。 我只是想知道我们是否可以使用 pyopenssl 而不是 python stdlib? pyopenssl 具有 openssl 连接的包装器,请参阅: https: //pyopenssl.readthedocs.io/en/latest/api/ssl.html#connection -objects。 因此,我们可以使用 'pkey' 对象作为私钥而不是文件路径。
Requests 已经支持 PyOpenSSL,只要它和其他一些必需的依赖项被安装。 然而,这永远不会成为强制性的:对我们来说,与标准库配合得很好很重要。
在未来的版本中,我们将支持将 SSLContext 对象传递给 urllib3 以处理 TLS:这将启用此功能。
对于那些面临这个问题的人,直到 requests 添加了将 ssl.SSLContext/OpenSSL.SSL.Context 传递给 urllib3 的能力,这里有一个实际支持使用加密证书/密钥文件的解决方法(需要安装和使用 PyOpenSSL 而不是标准库ssl,如果安装了它应该是)
import requests
# Get the password from the user/configfile/whatever
password = ...
# Subclass OpenSSL.SSL.Context to use a password callback that gives your password
class PasswordContext(requests.packages.urllib3.contrib.pyopenssl.OpenSSL.SSL.Context):
def __init__(self, method):
super(PasswordContext, self).__init__(method)
def passwd_cb(maxlen, prompt_twice, userdata):
return password if len(password) < maxlen else ''
self.set_passwd_cb(passwd_cb)
# Monkey-patch the subclass into OpenSSL.SSL so it is used in place of the stock version
requests.packages.urllib3.contrib.pyopenssl.OpenSSL.SSL.Context = PasswordContext
# Use requests as normal, e.g.
endpoint = 'https://example.com/authenticated'
ca_certs = '/path/to/ca/certs/bundle'
certfile = '/path/to/certificate'
keyfile = '/path/to/encrypted/keyfile'
requests.get(endpoint, verify=ca_certs, cert=(certfile, keyfile))
@ahnolds :这是否也适用于 PKCS#12 文件,还是仅适用于 PEM?
@Lukasa :PKCS#12 案件真的应该在这里处理,还是我应该为此单独开一个问题?
PKCS#12 是一个棘手的问题,但基本上你需要做任何需要定制你的 SSLContext 的事情。
@Lukasa :我更多地考虑在请求中提供一个好的高级 API。 例如,只需通过cert=...
关键字参数提供client_cert.p12
文件名和密码。
@vog您认为需要什么代码才能使其工作?
@Lukasa我不确定requests
的内部结构,所以也许我低估了已经存在的东西,但我认为需要做以下事情之一:
OpenSSL
绑定来完成的。 但是,结果是 PEM 证书作为字符串,我还没有找到一种方法将(未加密的)PEM 作为字符串提供给下层(除非使用 OpenSSL / python "ssl" "buffer" 包装器,例如wrap_bio
,但这仅适用于最新的 Python 3 版本,而不适用于 Python 2)。请注意,最后一点是我目前正在做的事情,但我根本不喜欢这一点。 为什么我不能向包含证书的 OpenSSL 提供一个简单的字符串? 此外,为什么我不能简单地将 PKCS#12 文件名和密码传递给较低层?
我将在@reaperhulk 中标记为 OpenSSL 专家,但我的理解是 OpenSSL 没有 API 可以为客户端证书加载 PKCS#12 格式的证书。 这意味着我们需要绝对转换为 PEM。 在内存中做这件事当然是可能的,但在某个时候我想知道我们是否只是想充分考虑这个专家,以至于我们将委托给您传递给我们的任何 SSLContext。
@Lukasa感谢您认真对待这个问题。 对不起,如果这听起来太技术化了,但本质上就是这样:
您希望通过客户端证书访问服务。 几乎在任何地方都可以将其作为文件和密码(其中文件是 PKCS#12 编码的)。 在大多数 API(例如 Java 标准库)中,您只需为其提供文件名和密码,即可完成。
然而,在 Python 中,这非常复杂。
这就是为什么几乎没有人这样做。 相反,他们通过 OpenSSL 手动将文件和密码转换为 PEM 文件,并使用该文件。 这是此类应用程序的每个用户的管理开销。 因为他们不能简单地命名(PKCS#12)文件和密码。
我认为requests
库应该使它至少像在 Java 中一样简单。
requests
在简化愚蠢的复杂 API 方面已经做得很好,而 PKCS#12 用例只是愚蠢的复杂 API 的另一个例子。
PKCS#12 用例只是愚蠢的复杂 API 的另一个例子。
是的,我完全不同意这一点:我很高兴在堆栈中的某个地方有某种支持 PKCS#12 的解决方案。
我想要了解的是需要哪些代码才能使该工作正常工作,因此应该将其放置在何处。 我的推理是这样的:
cert=
的语法(只是扩大了它将支持的内容)并且不会回归行为(也就是说,我们可以可靠地分辨出 PKCS#12 文件和 PEM 文件之间的区别,或者我们可以轻松地通过两个逻辑链进行处理),我认为这算作表面的一个足够小的变化,它可能是值得的.这意味着我想权衡这有多微妙,代码有多复杂,它是否需要额外的依赖项,然后使用这些信息来找出最好的代码放置位置。 例如,我现在有一个 _suspicion_ 标准库无法处理 PKCS#12,这意味着 _at best_ Requests 只能在安装了[security]
使用 PKCS#12。 在更糟糕的情况下,我们可能根本没有任何 OpenSSL 绑定中可用的函数,在这种情况下,我们将不得不做一些真正的疯狂的事情来让它工作。 这就是我希望@reaperhulk参与的原因:他可能会比我做研究更快地为我们澄清这一点。
我希望看到这个支持添加:我只需要让一些知道工作范围的人在这里发表评论,让我知道我们需要移动的山实际上有多大。
PKCS#12 实现的更多细节:如果密码以unicode
对象而不是字节字符串形式给出,则旧版本的 Python OpenSSL 绑定将失败。 所以它应该在将它传递给load_pkcs12()
之前进行转换,如下所示:
if isinstance(password, unicode):
password_bytes = password.encode('utf8')
else:
password_bytes = password
pkcs12 = OpenSSL.crypto.load_pkcs12(pkcs12_data, password_bytes)
一个完整的转换器可能看起来像这样,其中pkcs12_data
应该是一个带有二进制数据的字节字符串,而password
可能是一个字节字符串或一个 unicode 字符串:
def pkcs12_to_pem(pkcs12_data, password):
# Old versions of OpenSSL.crypto.load_pkcs12() fail if the password is a unicode object
if isinstance(password, unicode):
password_bytes = password.encode('utf8')
else:
password_bytes = password
p12 = OpenSSL.crypto.load_pkcs12(pkcs12_data, password_bytes)
p12_cert = p12.get_certificate()
p12_key = p12.get_privatekey()
pem_cert = OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, p12_cert)
pem_key = OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12_key)
pem = pem_cert + pem_key
return pem
在我看来,PKCS#12 的讨论超出了最初问题的范围,因为所讨论的问题是请求是否应该原生支持 PKCS#12。 我会投票它有它自己的问题,但显然这取决于负责人。
也就是说,作为不需要未加密临时文件的解决方法, OpenSSL.crypto.dump_privatekey
方法有一个可选的密码参数,因此您可以通过这种方式获得 PEM 格式的加密私钥的副本。 这将减少到我们开始的加密 PEM 问题。
或者,你也可以写大概一个类似我使用之前提出一个黑客use_privatekey
的方法OpenSSL.SSL.Context
。 在我的头顶(未经测试)类似的东西
# From somewhere
pkcs12_data = ...
password_bytes = ...
class Pkcs12Context(requests.packages.urllib3.contrib.pyopenssl.OpenSSL.SSL.Context):
def __init__(self, method):
super(PasswordContext, self).__init__(method)
p12 = OpenSSL.crypto.load_pkcs12(pkcs12_data, password_bytes)
self.use_certificate(p12.get_certificate())
self.use_privatekey(p12.get_privatekey())
# Monkey-patch the subclass into OpenSSL.SSL so it is used in place of the stock version
requests.packages.urllib3.contrib.pyopenssl.OpenSSL.SSL.Context = Pkcs12Context
然后只需使用requests.get
等,根本不指定证书,因为它已经在构造函数中处理了。
现在审查这个线程。 原文改写:
给定一个加密的 PEM 格式的客户端证书,请求可以处理提供密码吗?
由于这是在当前的标准库中,因此集成此选项会很棒。 这对于企业安全方面的考虑(我们的证书是加密的,并且旨在保持加密)非常有价值。
此时,这可以通过使用传输适配器将自定义 ssl 上下文传递给 urllib3 来完成。 这可以做标准库 ssl 上下文允许的任何事情。 您可以在此处查看传递自定义上下文的示例。
通过使用临时文件将 .pfx 和 .p12 转换为 .pem,我能够在请求中使用 .pfx 和 .p12。 见https://gist.github.com/erikbern/756b1d8df2d1487497d29b90e81f8068
如果有兴趣,我可以提交 PR。 避免使用临时文件和上下文管理器会很棒。 让我知道。
恐怕这不太可能合并,但请注意,您现在可以通过传输适配器将 PyOpenSSL 上下文直接传递给请求,因此您可能会发现可以绕过该问题。
恐怕这不太可能合并,但请注意,您现在可以通过传输适配器将 PyOpenSSL 上下文直接传递给请求,因此您可能会发现可以绕过该问题。
抱歉让您感到困惑,但您是说一般的 pfx/p12 支持可能不会合并吗? (假设通过上下文等以正确的方式完成)。 很高兴尝试一下,但如果它不打算合并,显然不值得我花时间。
我相信“不太可能被合并”是关于临时文件解决方案的。
@erikbern明确地说,我很高兴接近并合并任何在某种程度上一致工作的解决方案。 例如,通过 urllib3 中的 PyOpenSSL contrib 模块使用 PKCS#12 的解决方案是可以接受的。
但是,临时文件解决方案是不可接受的(如@vog 所述)。 这意味着对 PKCS#12 的支持不太可能与标准库一起使用,因为标准库ssl
模块不公开对它的任何支持,因此所有请求配置都不会支持它。
听起来不错。 我也同意临时文件不好,因为在磁盘上存储解密的密钥存在安全风险。 下周可能会去看看。 感谢您对ssl
模块的提醒——如果限制在requests
那么显然它会变得更加棘手
我查看了它, ssl
模块添加了一个cadata
参数,您可以在其中将 pem 数据作为原始字符串传递: https :
我们必须在很多地方修补 urllib3 才能使其工作,所以我可能会从那里开始
@erikbern需要明确的是,几乎任何类似的解决方案都可以通过使用TransportAdapter
将适当配置的SSLContext
对象传递给 urllib3 来更好地工作。
https://github.com/kennethreitz/requests/issues/2519似乎与此问题相同,因此它们可能应该合并
关于此问题的任何更新我正在尝试使用密码加密的客户端证书,但无法使其正常工作。 除了请求,我应该寻找其他选择吗? 能否请您尽快回复。
我们是否记录了这一点? 我觉得这是我们最需要的功能。
我相信这个线程始于 2013 年,直到最后我都没有找到任何解决方案。 你们提供了提供密码的选项吗? 或者这仍在进行中?
我正在尝试在我正在构建的应用安全产品中使用请求。 所以任何指针都会有所帮助
@AnoopPillai你检查过这条评论吗? https://github.com/requests/requests/issues/1573#issuecomment -188125157
是的,我确实阅读了此评论,这是一种解决方法,但在我的情况下,我不想将其转换为 2 个证书文件,因为这必须在我的应用程序之外完成。 此外,我们使用类似保管库的东西来存储加密的 .pem 文件的密码。
此密码由应用程序在运行时动态检索,因此无需硬编码。
@AnoopPillai好的。
@kennethreitz不,我们没有记录它。
@AnoopPillai是的,这很好用。 你只需要使用一些较低级别的钩子。 在这种情况下,我们允许您直接在传输适配器级别
@AnoopPillai使用我发现有用的临时文件的解决方法: https : //gist.github.com/erikbern/756b1d8df2d1487497d29b90e81f8068
感谢 Lukasa 让我知道有一种方法可以做到。
我对 python 很陌生,正在使用 3.6 版本。 您能否指导我在哪里可以找到用于传递客户端证书密码的密码等选项。
@Erikbern我还没有通过临时文件解决方案,但今天会看看同样的。 感谢您的回复。
@AnoopPillai你会想要load_cert_chain
。
@Lukasa你介意记录一下吗? 应该只需要几分钟(我正在考虑高级部分,或者可能在一个新的高级高级部分)
抱歉,我对 python 缺乏经验可能是原因,但我无法修改 Lukasa 上面解释的代码。 我的代码是:
class DESAdapter(HTTPAdapter):
"""
A TransportAdapter that re-enables 3DES support in Requests.
"""
def init_poolmanager(self, *args, **kwargs):
context = create_urllib3_context(load_cert_chain='rtmqa-clientid.pem',password='weblogic')
kwargs['ssl_context'] = context
return super(DESAdapter, self).init_poolmanager(*args, **kwargs)
def proxy_manager_for(self, *args, **kwargs):
context = create_urllib3_context(load_cert_chain='rtmqa-clientid.pem', password='weblogic')
kwargs['ssl_context'] = context
return super(DESAdapter, self).proxy_manager_for(*args, **kwargs)
s = requests.Session()
s.mount(url, DESAdapter())
r = s.get(url, headers=request_header).json()
我得到一个错误
类型错误:create_urllib3_context() 得到了一个意外的关键字参数“load_cert_chain”
这是一个错误,是的。 您想调用create_urllib3_context
并获取其返回值,然后在返回的对象上调用load_cert_chain
。 尝试在交互式解释器中使用这些函数,看看它们是如何工作的。
安装在我的 mac 上的 urllib3..util.ssl_.py 没有最新的密码选项。
这是代码
if certfile:
context.load_cert_chain(certfile, keyfile)
if HAS_SNI: # Platform-specific: OpenSSL with enabled SNI
return context.wrap_socket(sock, server_hostname=server_hostname)
缺少密码选项。 如何更新 ssl_.py 以获取最新版本?
@AnoopPillai你没有。 调用不带参数的函数,然后在返回的对象上调用load_cert_chain
。 您不需要更改 urllib3。
要清楚,像这样:
ctx = create_urllib3_context()
ctx.load_cert_chain(your_arguments_here)
让我们记录一下:)
@ erikbern 我尝试了您的临时文件解决方案,但出现以下错误:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 441, in wrap_socket
cnx.do_handshake()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/SSL.py", line 1716, in do_handshake
self._raise_ssl_error(self._ssl, result)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/SSL.py", line 1456, in _raise_ssl_error
_raise_current_error()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/_util.py", line 54, in exception_from_error_queue
raise exception_type(errors)
OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 595, in urlopen
self._prepare_proxy(conn)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 816, in _prepare_proxy
conn.connect()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connection.py", line 326, in connect
ssl_context=context)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/util/ssl_.py", line 329, in ssl_wrap_socket
return context.wrap_socket(sock, server_hostname=server_hostname)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 448, in wrap_socket
raise ssl.SSLError('bad handshake: %r' % e)
ssl.SSLError: ("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/adapters.py", line 440, in send
timeout=timeout
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 639, in urlopen
_stacktrace=sys.exc_info()[2])
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/util/retry.py", line 388, in increment
raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='credit-cards-accounts-qa.kdc.capitalone.com', port=443): Max retries exceeded with url: /credit-cards-accounts/credit-cards/accounts/XqLuxBTABbIDvpw56ba34p2WV9JoWUSkPJ09hrBlWD8= (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/tsu892/Desktop/Office/Pythone-work/ASR-pythone/ASR-python3.6/test-request.py", line 48, in <module>
r = requests.get(url, headers=request_header, cert=cert).json()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/api.py", line 72, in get
return request('get', url, params=params, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/api.py", line 58, in request
return session.request(method=method, url=url, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/sessions.py", line 508, in request
resp = self.send(prep, **send_kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/sessions.py", line 618, in send
r = adapter.send(request, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/adapters.py", line 506, in send
raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='credit-cards-accounts-qa.kdc.capitalone.com', port=443): Max retries exceeded with url: /credit-cards-accounts/credit-cards/accounts/XqLuxBTABbIDvpw56ba34p2WV9JoWUSkPJ09hrBlWD8= (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))
下面是我的代码:
import requests
import json
import OpenSSL.crypto
import tempfile
import os
import contextlib
import ssl
json_file='apiInput.json'
hdr_key=[]
hdr_value=[]
json_data=open(json_file)
data = json.load(json_data)
request_body={}
#pprint(data)
json_data.close()
request_data = data['request1']
request_header=request_data['header-data']
url=request_header['url']
@contextlib.contextmanager
def pfx_to_pem():
print('inside pfx tp pem')
with tempfile.NamedTemporaryFile(suffix='.pem') as t_pem:
f_pem = open(t_pem.name, 'wb')
fr_pfx = open('rtmqa-clientid.pfx', 'rb').read()
p12 = OpenSSL.crypto.load_pkcs12(fr_pfx,'xxxxxxxxx')
f_pem.write(OpenSSL.crypto.dump_privatekey(OpenSSL.crypto.FILETYPE_PEM, p12.get_privatekey()))
f_pem.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, p12.get_certificate()))
ca = p12.get_ca_certificates()
if ca is not None:
for cert in ca:
f_pem.write(OpenSSL.crypto.dump_certificate(OpenSSL.crypto.FILETYPE_PEM, cert))
f_pem.close()
yield t_pem.name
with pfx_to_pem() as cert:
print(cert)
r = requests.get(url, headers=request_header, cert=cert).json()
print(r.status_code)
print(r.json())
抱歉,很难知道为什么从您的评论中看它会中断。 我已经将它用于一堆应用程序并且没有任何问题
@Lukasa我确实尝试过更改代码(代码粘贴在下面),但最终遇到了与 tempfile 方法相同的错误。
import requests
import json
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.ssl_ import create_urllib3_context
json_file='apiInput.json'
hdr_key=[]
hdr_value=[]
json_data=open(json_file)
data = json.load(json_data)
request_body={}
#pprint(data)
json_data.close()
request_data = data['request1']
request_header=request_data['header-data']
url=request_header['url']
class DESAdapter(HTTPAdapter):
"""
A TransportAdapter that re-enables 3DES support in Requests.
"""
def init_poolmanager(self, *args, **kwargs):
context = create_urllib3_context()
context.load_cert_chain('rtmqa-clientid.pem',password='weblogic')
kwargs['ssl_context'] = context
return super(DESAdapter, self).init_poolmanager(*args, **kwargs)
def proxy_manager_for(self, *args, **kwargs):
context = create_urllib3_context()
context.load_cert_chain('rtmqa-clientid.pem',password='weblogic')
kwargs['ssl_context'] = context
return super(DESAdapter, self).proxy_manager_for(*args, **kwargs)
s = requests.Session()
s.headers=request_header
s.mount(url, DESAdapter())
r = s.get(url)
/Users/tsu892/Python3.6/bin/python /Users/tsu892/Desktop/Office/Pythone-work/ASR-pythone/ASR-python3.6/Test-ASRreq.py
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 441, in wrap_socket
cnx.do_handshake()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/SSL.py", line 1716, in do_handshake
self._raise_ssl_error(self._ssl, result)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/SSL.py", line 1456, in _raise_ssl_error
_raise_current_error()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/OpenSSL/_util.py", line 54, in exception_from_error_queue
raise exception_type(errors)
OpenSSL.SSL.Error: [('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')]
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 595, in urlopen
self._prepare_proxy(conn)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 816, in _prepare_proxy
conn.connect()
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connection.py", line 326, in connect
ssl_context=context)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/util/ssl_.py", line 329, in ssl_wrap_socket
return context.wrap_socket(sock, server_hostname=server_hostname)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/contrib/pyopenssl.py", line 448, in wrap_socket
raise ssl.SSLError('bad handshake: %r' % e)
ssl.SSLError: ("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",)
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/adapters.py", line 440, in send
timeout=timeout
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/connectionpool.py", line 639, in urlopen
_stacktrace=sys.exc_info()[2])
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/urllib3/util/retry.py", line 388, in increment
raise MaxRetryError(_pool, url, error or ResponseError(cause))
urllib3.exceptions.MaxRetryError: HTTPSConnectionPool(host='credit-cards-accounts-qa.kdc.capitalone.com', port=443): Max retries exceeded with url: /credit-cards-accounts/credit-cards/accounts/XqLuxBTABbIDvpw56ba34p2WV9JoWUSkPJ09hrBlWD8= (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/Users/tsu892/Desktop/Office/Pythone-work/ASR-pythone/ASR-python3.6/Test-ASRreq.py", line 37, in <module>
r = s.get(url)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/sessions.py", line 521, in get
return self.request('GET', url, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/sessions.py", line 508, in request
resp = self.send(prep, **send_kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/sessions.py", line 618, in send
r = adapter.send(request, **kwargs)
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/requests/adapters.py", line 506, in send
raise SSLError(e, request=request)
requests.exceptions.SSLError: HTTPSConnectionPool(host='credit-cards-accounts-qa.kdc.capitalone.com', port=443): Max retries exceeded with url: /credit-cards-accounts/credit-cards/accounts/XqLuxBTABbIDvpw56ba34p2WV9JoWUSkPJ09hrBlWD8= (Caused by SSLError(SSLError("bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)",),))
@erikbern可能是我笔记本电脑上的设置问题。 我用的是 mac,Pythone3.6
6c40089ea258:~ tsu892$ pip3 show requests
Name: requests
Version: 2.18.4
Summary: Python HTTP for Humans.
Home-page: http://python-requests.org
Author: Kenneth Reitz
Author-email: [email protected]
License: Apache 2.0
Location: /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages
Requires: idna, certifi, chardet, urllib3
6c40089ea258:~ tsu892$ pip3 show certifi
Name: certifi
Version: 2017.7.27.1
Summary: Python package for providing Mozilla's CA Bundle.
Home-page: http://certifi.io/
Author: Kenneth Reitz
Author-email: [email protected]
License: MPL-2.0
Location: /Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages
Requires:
你觉得证书有什么问题吗?
python -m requests.help
的输出是什么?
@Lukasa输出是:
6c40089ea258:~ tsu892$ python3 -m requests.help?
/Library/Frameworks/Python.framework/Versions/3.6/bin/python3: No module named requests.help?
请从命令行中删除问号。
6c40089ea258:~ tsu892$ python3 -m requests.help
{
"chardet": {
"version": "3.0.4"
},
"cryptography": {
"version": "2.0.3"
},
"idna": {
"version": "2.6"
},
"implementation": {
"name": "CPython",
"version": "3.6.2"
},
"platform": {
"release": "16.7.0",
"system": "Darwin"
},
"pyOpenSSL": {
"openssl_version": "1010006f",
"version": "17.2.0"
},
"requests": {
"version": "2.18.4"
},
"system_ssl": {
"version": "100020bf"
},
"urllib3": {
"version": "1.22"
},
"using_pyopenssl": true
}
因此,您收到的错误是由于我们无法验证服务器 TLS 证书造成的。 certifi 和 OpenSSL 似乎是正确的,所以我认为服务器行为不端。 您要访问什么服务器?
该应用程序部署在云 AWS 上。 但是当我们调用 API 时,它首先转到 OSB,它对证书进行身份验证,然后将请求路由到 AWS。
我使用相同的证书并使用邮递员或我的 ruby 代码 API 工作正常
您需要特定的根证书吗? 你能提供我正在访问的主机名吗?
没有路径的主机 URL 是https://credit-cards-accounts-qa.kdc.capitalone.com
这是一个内部端点
是的,所以我看不到那里发生了什么。 你能运行openssl s_client -showcerts -connect credit-cards-accounts-qa.kdc.capitalone.com:443
并提供完整的输出吗?
已删除
这看起来很像您没有使用全球信任的根证书。 该服务的根证书在哪里?
我没有使用任何其他证书。 不确定在幕后是否使用了任何根证书,有没有办法让我找出来?
是的,Chrome 的开发人员工具会告诉您它正在使用的完整证书链。
您可能不想在网上发布一些内部证书让任何人看到...
@erikbern这是公开信息。 您可以通过运行相同的命令获得相同的结果。
@SethMichaelLarson来自@erikbern的 GitHub 个人资料“首席巨魔官”。 也许他们只是在拖钓?
@erikbern @sigmavirus24啊! 我不知道我在和谁说话。 继续! 🙇
当我从邮递员那里跑时,除了 sha-1 证书之外,我看不到任何东西
可能我需要以某种方式将它添加到 Pycharm
如果您真的在 Chrome 中浏览网站,那应该就足够了。
@SethMichaelLarson运行什么命令? 仅供参考,该评论现在已被删除,但早些时候这里有一个完整的 BEGIN CERTIFICATE blob...afaik 你不想在网上分享
@erikbern那只是证书的公钥......
证书是公共数据; 每次连接尝试时,它们都以纯文本形式通过网络传输。
我去了证书链,只找到了一个 Sha-1 证书和我用来访问 API 的 .Pem 证书
@AnoopPillai我从 9 月 1 日获得了您的示例代码,使用带密码的客户端 pem 文件没有问题。 主机似乎正在使用常规证书。 非常感谢@Lukasa !
不幸的是,即使使用临时文件方法,我仍然遇到问题。 我可以在 Google Postman 中使用 .pfx 并且在身份验证方面没有问题(所以我知道我的凭据有效),但我仍然使用 Python 获得 401。 不幸的是,我与之打交道的公司的支持人员并没有提供太多帮助 - 有没有人对故障排除有任何建议?
在这个阶段,我真的不确定在哪里寻找问题,因为其他人报告使用 Temp File 方法成功,而我仍然没有从他们的证书管理团队收到任何回复。
任何建议将不胜感激 - 如果我可以提供任何其他信息以使这更容易,请告诉我。
谢谢 :)
只是一个建议,您是否尝试将 PFX 转换为 PEM? 此外,如果服务器也使用用户名/密码,您需要使用 auth=() 添加获取/发布请求。 我已经使用上面的class DESAdapter(HTTPAdapter)
方法几个星期了,没有问题,使用密码保护的 PEM 文件。
@ideasean仍然获取无效凭据。 我应该将 load_cert_chain 指向由为 Temp File 方法编写的 pfx_to_pem 函数生成的 .pem 文件,对吗? 它有私钥和证书。
由于 .pfx 可与 Postman 一起使用,但不会在此处进行身份验证,这是否意味着转换过程中出现问题?
我没有使用临时文件方法。 我使用了 DESAdapter 方法,就像上面 9 月 1 日 AnoopPillai 的帖子中所写的那样,从 -
我确实尝试过更改代码(代码粘贴在下面),但最终遇到了与 tempfile 方法相同的错误。
我无法谈论转换过程,但也许一个很好的测试是尝试将转换后的 pem 文件与 Postman 一起使用?
另请注意,我使用上述方法是因为我的 pem 文件已加密/密码保护,而 Python 请求目前不支持该方法。 如果您的 pem 最终不受密码保护,那么您应该能够使用每个链接的本机请求(但是您的文件系统上将拥有一个不受保护的证书)。
@ideasean我按照这个方法分解了 .pfx 并得到了一个带有包属性和证书的 .pem 文件以及一个带有包属性和加密私钥的 .pem 文件。
仍然获得无效凭据,我想我会尝试将证书放在 Postman 上并查看它们是否有效,但我无法弄清楚为什么我显然无法正确解压缩此 .pfx
我还尝试了 openssl 命令openssl pkcs12 -in <my_pfx>.pfx -out certificate.cer -nodes
,当我像这样更改它时,它仍然给我一个 401 错误: context.load_cert_chain('certificate.cer')
我安装了上面提到的 .cer 并且 Postman 在我进行 API 调用时甚至没有要求使用它(与要求使用 .pfx 时的弹出窗口不同),不知道我还能如何使它使用该特定证书因为在设置中没有“证书”面板,就像文档所说的那样。
您可能正在使用 Postman 的浏览器版本,其中不包括证书面板、ssl 验证禁用等。尝试使用完整客户端更改证书设置。 你可能想在不同的线程上继续这个讨论,因为我们有点偏离主题。
@mkane848看到了您的原始评论,其中您收到了ValueError: String expected
。 您可能需要查看https://github.com/pyca/pyopenssl/issues/701和https://github.com/shazow/urllib3/issues/1275。
我使用带有密码的私人 pem:
from requests.adapters import HTTPAdapter
from urllib3.util.ssl_ import create_urllib3_context
class SSLAdapter(HTTPAdapter):
def __init__(self, certfile, keyfile, password=None, *args, **kwargs):
self._certfile = certfile
self._keyfile = keyfile
self._password = password
return super(self.__class__, self).__init__(*args, **kwargs)
def init_poolmanager(self, *args, **kwargs):
self._add_ssl_context(kwargs)
return super(self.__class__, self).init_poolmanager(*args, **kwargs)
def proxy_manager_for(self, *args, **kwargs):
self._add_ssl_context(kwargs)
return super(self.__class__, self).proxy_manager_for(*args, **kwargs)
def _add_ssl_context(self, kwargs):
context = create_urllib3_context()
context.load_cert_chain(certfile=self._certfile,
keyfile=self._keyfile,
password=str(self._password))
kwargs['ssl_context'] = context
为了您的信息,我刚刚实现了对requests
PKCS#12 支持作为一个单独的库:
代码是一个干净的实现:它既不使用猴子补丁也不使用临时文件。 相反,使用自定义 TransportAdapter,它提供自定义 SSLContext。
欢迎任何反馈和改进!
当然,我希望requests
能直接提供这个功能,但在我们到达之前,这个库会减轻痛苦。
如果我们可以简单地这样做,那就太好了:
~~~
cert=("cert.pem", "key.pem", "somepassphrase") # 单独的证书/密钥
cert=("keycert.pem", None, "somepassphrase") # combined cert/key
~~~
...即使它只适用于 python 3.3+。 这只是对 API 表面的一个小补充。
AFAICS,这意味着对 urllib3 进行小幅更改,以便 HTTPSConnection 接受可选的password
参数; 这是通过ssl_wrap_socket
传递的,最后是:
~如果证书文件:如果密码不是无:context.load_cert_chain(证书文件,密钥文件,密码)别的:context.load_cert_chain(certfile, keyfile)~
然后它将向后兼容,仅当您尝试在不支持它的旧平台上使用私钥密码时才会引发异常。
请注意, contrib/pyopenssl.py
适配器已经支持load_cert_chain
这个额外参数, python 2.7也是如此。
旁白:我使用 AWS KMS 来管理“秘密”数据,因此我会在运行时从 KMS 加载密钥密码,而不是将其硬编码到应用程序中。
我个人不会反对这种变化,因为我认为它会极大地改善我们所有用户的用户界面。
@sigmavirus24 有什么想法吗?
@candlerb @kennethreitz将 PKCS#12 案例也包含在该 API 中是否可以接受?
cert=('keycert.p12', None, 'somepassphrase')
区别可以是文件扩展名( *.p12
与*.pem
),也可以是查看该文件的第一个字节。
@candlerb正如我在之前的评论(https://github.com/requests/requests/issues/1573#issuecomment-348968658)中所写的那样,我已经创建了一个与requests
很好地集成的干净实现。
所以你描述的问题已经解决了。
现在我的实现添加了新的pkcs12_*
关键字参数,以尽可能地避开。
但我认为它应该被集成到cert
关键字参数中,我的问题是:
cert=('keycert.p12', None, 'somepassphrase')
可以接受吗?(此外,我更愿意将其放入requests
而不是我单独的requests_pkcs12
库中。但考虑到这个问题的年龄,我几乎不希望这会很快进入上游。然而,如果有关于确切需要哪种实现的具体声明,也许我可以相应地调整我的实现并提出拉取请求。)
所以,有几点:
我认为我们不应该采用 cert 关键字并像这样扩展它。 它是隐式结构化数据,人们已经对files
关键字中的元组感到困惑。 我认为继续一个已知的坏模式是愚蠢的。
我认为如果有的话,应该修改 pkcs12 适配器并将其上传到请求工具带中。 我认为最好修改它以创建ssl_context
一次,而不是将 pkcs12 密码存储在该对象的内存中。
我认为还有其他工作需要做,然后我们才能在更一般的情况下处理这个问题,这包括为 Requests 3.0 确定正确的 API。
@sigmavirus24感谢您的反馈。
pkcs12_*
关键字。如何将 PKCS#12 TransportAdapter
类包含到requests
? 该类是简单地添加到requests
,还是有另一种方法将它包含在“更深”级别,因此可以在没有任何request()/get()/...
包装器的情况下使用它,而无需显式加载它适配器?
我的组织需要使用 PKCS12 证书,并愿意对您的库进行必要的增强,以便这样做。 将 .p12 文件解密为 .pem 文件被认为风险太大,它增加了一个额外的处理步骤。 我们想添加功能来为给定的会话生成和提供适当的ssl_context
。 假设它得到正确实施,这仍然是您的团队愿意接受的功能吗?
提醒一下:我们公司已经提供了一个干净的实现,但是作为一个单独的适配器: https :
随意将其重新格式化为请求本身的拉取请求。
在此过程中,您可能想要解决一个小问题: ssl_context 不应在整个会话期间都保存在内存中,而应尽可能短地保存在内存中,仅用于单个给定连接。 也可以看看:
如果您在此过程中修复它,那么除了请求本身之外,您还可以将它作为一个小的拉取请求提供给https://github.com/m-click/requests_pkcs12会很好。
这样,所有现在使用requests_pkcs12
库的人也将自动受益于该改进,而无需切换到(随后改进的)新的请求 API。
是的, https://github.com/m-click/requests_pkcs12为我工作并且完全按照我的意愿去做。 非常感谢@vog ! 我希望 requests 最终能够支持这一点。
我还要感谢@vog的实现,按预期工作,并解决了将证书/密钥保存在 S3 等非安全存储中的问题。 希望这可以达到requests
。
最有用的评论
@botondus我想我找到了一种更简单的方法来使用请求库来实现这一点。 我正在为面临此问题的其他人记录此信息。
我假设您有一个 .p12 证书和密钥的密码。
生成证书和私钥。
好吧,我们还没有完成,每次需要与服务器通信时,我们都需要生成不需要 PEM 密码的密钥。
生成没有密码的密钥。
现在,您将拥有
certificate.pem
和plainkey.pem
,这两个文件都需要使用请求与 API 对话。这是使用这些证书和密钥的示例请求。
希望这可以帮助:
抄送@kennethreitz @Lukasa @sigmavirus24