aiohttp和客户端SSL证书

12

我最近从使用flask+requests转向aiohttp和其异步HTTP客户端。

在我的场景中,我需要通过HTTPS(带有自定义证书)调用API并发送客户端证书。

对于第一部分(验证自定义证书),支持已经很明确了,在文档中有清晰地记录,并且它工作得很好。

然而,对于第二部分,我似乎找不到一种简单的方法来附加自定义SSL客户端证书以授权客户端。

你们知道如何做吗?非常感谢!

1个回答

36

编辑:我已经提交了一个PR,更新了关于此主题的aiohttp文档,并且已经合并。

对于将来可能遇到此问题的任何人..

TL:DR

import ssl
import aiohttp    

ssl_ctx = ssl.create_default_context(cafile='/path_to_client_root_ca')
ssl_ctx.load_cert_chain('/path_to_client_public_key.pem', '/path_to_client_private_key.pem')

conn = aiohttp.TCPConnector(ssl_context=ssl_ctx)
session = aiohttp.ClientSession(connector=conn)

# session will now send client certificates..

长话短说 - 我已经查看了requests的实现方式(它很好地记录了API),显然是在urllib3内部实现的。

urllib3将cert参数一直传递到其HTTPSConnection对象,最终调用此函数:

...
self.sock = ssl_wrap_socket(
    sock=conn,
    keyfile=self.key_file,
    certfile=self.cert_file,
    ssl_context=self.ssl_context,
)
...

它的作用:

...
if ca_certs or ca_cert_dir:
    try:
        context.load_verify_locations(ca_certs, ca_cert_dir)
    except IOError as e:  # Platform-specific: Python 2.6, 2.7, 3.2
        raise SSLError(e)
    # Py33 raises FileNotFoundError which subclasses OSError
    # These are not equivalent unless we check the errno attribute
    except OSError as e:  # Platform-specific: Python 3.3 and beyond
        if e.errno == errno.ENOENT:
            raise SSLError(e)
        raise
elif getattr(context, 'load_default_certs', None) is not None:
    # try to load OS default certs; works well on Windows (require Python3.4+)
    context.load_default_certs()

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)
...

这里有一个有趣的调用 load_cert_chain - 这意味着,如果我们只是创建一个ssl.SSLContext(它是标准库接口)对象,并像这样使用我们的客户端证书调用load_cert_chain,aiohttp将与requests\urllib3表现相同。

因此,尽管aiohttp的文档缺乏告诉您这一点,但他们确实指定了您可以加载您自己的ssl.SSLContext


3
注意,除非我添加了一个目的(ssl_ctx = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)),否则当服务器尝试验证客户端时,我无法使其工作。留下这里以备将来参考! - Daniel
我们在验证远程服务器证书时遇到了类似于此问题的问题。因此,我们最终加载了服务器的根证书,如下所示: ssl_ctx = ssl.create_default_context(cafile='certs/entrust_ev_ca.cer')在这种情况下,添加客户端证书/密钥是否可以? ssl_ctx.load_cert_chain('client.crt', 'client.key')我之所以问这个问题,是因为在这个例子中,我认为对create_default_context的调用是指客户端的根CA,而不是服务器的... - Jason Capriotti
这个方法在 Python 3.9 上运行良好,但在 3.10 和 3.11 上,客户端证书停止工作,并出现“无法使用 PROTOCOL_TLS_SERVER 上下文创建客户端套接字 (_ssl.c:801)”的错误。有什么办法可以让它再次正常工作吗? - Daniel Sass
注意。由于遇到ssl.SSLError: Cannot create a client socket with a PROTOCOL_TLS_SERVER context错误,我无法使其正常工作。解决方案是使用ssl_ctx = ssl._create_unverified_context()代替,灵感来自于这个问答 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接