TL;DR 在 Python 中执行此操作存在重大警告和安全问题。
一个可行的“解决方案”涉及使用 PKCS#11 库从密钥中读取证书,然后以某种方式将证书保存在磁盘上,最后将生成的文件路径传递给请求对象。
每个安全棒的特性也会有所不同。一些棒不提供将证书连同其私钥(又名 .pfx 或 .p12 文件)一起存储,这基本上会使该解决方案不可行。我无法使用 Safenet 棒,所以使用我自己的,请记住这一点。
解决这个问题需要做很多工作。您使用安全加密狗意味着您的客户端证书位于加密狗本身。因此,为了实现相同级别的功能,您需要编写代码从那里提取证书并将其提供给您的请求对象。
1。 Python 中支持 HTTPS 的库
您对使用 httplib(http.client 用于 python 3.x)或 urllib 的要求引入了一个很大的警告,即请求中使用的证书必须是磁盘上的文件(同样可以说在它们之上构建的所有库,例如requests)。请参阅cnelson's answer to How to open ssl socket using certificate stored in string variables in python 了解原因(简而言之:这是因为python 的ssl 库使用了不提供传递内存对象作为证书的本机C 库)。另请参阅 next answer from Dima Tisnek 详细说明不同程度的黑客技术可能的解决方法。
如果将您的证书(即使是暂时的)写入磁盘对您来说是一个不可行的方法,因为很可能因为您使用了安全棒,那么它一开始看起来并不好。
2。从安全棒获取证书
您面临的最大挑战是获得证书,该证书目前位于安全棒内。与许多其他设备一样,Safenet 存储棒的核心是支持 PKCS#11 的智能卡。我建议您熟悉概念,但本质上,SmartCard 是一种标准化的芯片设计,而 PKCS#11 是与其接口的标准化协议。 “标准化”当然有一些注意事项,因为许多供应商都提出了自己的实现,但它可能已经标准化到足以满足您的目的。这里的技巧是使用棒上可用的 PKCS#11 接口来提取证书的属性。这就是网络浏览器在使用存储证书在网站上进行身份验证时本质上所做的事情,因此您需要让您的 python 程序执行类似的操作。
2.1 选择 PKCS#11 库
不幸的是,在搜索“python pkcs11”时只出现了几个库。我对它们中的任何一个都没有既得利益,并且可能存在其他不太重要的利益。
python-pkcs11(pypi、github、reference)提供“PKCS#11 的高级 Python 实现”。总体上它可能更容易使用,但可能缺乏兼容性和/或功能,具体取决于您想要做什么,但我怀疑简单地检索证书可能没问题。
另一方面,
PyKCS11(pypi、github、reference)是原生 PKCS#11 库的包装器,它将延迟调用。这是较低级别的,但看起来更完整,而且如果相关,使用您的特定供应商的实现可能会有优势。
2.2 示例代码
例如,我将使用 python-pkcs11 的用户友好 API。请记住,此代码未经彻底测试(并且已部分简化),仅用于说明总体思路。
import pkcs11
import asn1crypto.pem
import urllib.request
import tempfile
import ssl
import os
# this is OpenSC's implementation of PKCS#11
# other security sticks may come with another implementation.
# choose the most appropriate one
lib = pkcs11.lib('/usr/lib/pkcs11/opensc-pkcs11.so')
# tokens may be identified with various names, ids...
# it's probably rare that more than one at a time would be plugged in
token = lib.get_token(token_serial='<token_serial_value>')
pem = None
with token.open() as sess:
pkcs11_certificates = sess.get_objects(
{
pkcs11.Attribute.CLASS: pkcs11.ObjectClass.CERTIFICATE,
pkcs11.Attribute.LABEL: "Cardholder certificate"
})
# hopefully the selector above is sufficient
assert len(pkcs11_certificates) == 1
pkcs11_cert = pkcs11_certificates[0]
der_encoded_certificate = pkcs11_cert.__getitem__(pkcs11.Attribute.VALUE)
# the ssl library expects to be given PEM armored certificates
pem_armored_certificate = asn1crypto.pem.armor("CERTIFICATE",
der_encoded_certificate)
# this is the ugly part: persisting the certificate on disk
# i deliberately did not go with a sophisticated solution here since it's
# such a big caveat to have to do this...
certfile = tempfile.mkstemp()
with open(certfile[1], 'w') as certfile_handle:
certfile_handle.write(pem_armored_certificate.decode("utf-8"))
# this will instruct the ssl library to provide the certificate
# if asked by the server.
sslctx = ssl.create_default_context()
sslctx.load_cert_chain(certfile=certfile[1])
# if your certificate does not contain the private key, find it elsewhere
# sslctx.load_cert_chain(certfile=certfile[1],
# keyfile="/path/to/privatekey.pem",
# password="<private_key_password_if_applicable>")
response = urllib.request.urlopen("https://ssl_website", context=sslctx)
# Cleanup and delete the "temporary" certificate from disk
os.remove(certfile[1])
3。结论
我会说 Python 不会成为使用安全棒进行 ssl 客户端身份验证的最佳选择。大多数 ssl 库要求证书存在于磁盘上的事实直接违背了使用安全棒的好处(有时是要求)。我很清楚,这个答案并没有为这个问题提供完整的解决方案,但希望能够足够详细地揭示挑战,以便做出有根据的决定,决定是进一步研究还是寻找其他方法。
无论如何,祝你好运。