我正在编写一个点对点协议,其中节点可以相互连接,并且应该使用 TLS 以及与 Diffie-Hellman 交换一致的会话密钥对连接进行加密。然而,使用任何类型的证书都会很乏味,因为建立 TLS 连接后,每对节点仍然必须使用 ECDSA 质询响应协议(这使用 OpenSSL 不支持的密码套件)进行相互身份验证。
鉴于我可以控制客户端和服务器上的
ssl
,如何使用Python标准库SSLContext
模块来建立这样一个不需要设置蛇油证书的TLS连接?
如何在 TLS 握手完成后使用质询响应协议进行身份验证,从而排除 Mallory 使用凭证转发的可能性?
对于第 1 部分,一种可能性是使用带有
AECDH
的 OpenSSL 密码套件,例如 AECDH-AES256-SHA
。对于服务器,代码是
context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
context.set_ciphers("AECDH-AES256-SHA:@SECLEVEL=0")
with socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0) as sock:
sock.bind(('127.0.0.1', 8443))
sock.listen(5)
with context.wrap_socket(sock, server_side=True) as ssock:
conn, addr = ssock.accept()
对于客户端代码,您可以使用以下内容
context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
context.set_ciphers("AECDH-AES256-SHA:@SECLEVEL=0")
context.check_hostname = False
with socket.create_connection(('localhost', 8443)) as sock:
with context.wrap_socket(sock) as conn:
...
值得注意的是,需要指定
:@SECLEVEL=0
才能正常工作。
对于第2部分和挑战响应协议,可以使用通道绑定函数——
的返回值conn.get_channel_binding(cb_type='tls-unique')
作为挑战-响应协议中签名消息的一部分。这是一个 96 位值。
然而,该解决方案的安全性还远未达到完美。值得注意的是,使用
AECDH
密码似乎限制了与 TLS 1.2 的连接,并且针对 get_channel_binding(cb_type='tls-unique')
存在攻击,基本上使值的长度减半,即有效的安全性仅为 48 位。
在 TLS 1.3 中,可以选择导出更长的值,即类型为
get_channel_binding
的 tls-exporter
,但是 Python 3 SSL 模块目前不支持。另一方面,对于 TLS 1.3,这些匿名 Diffie-Hellman 密码套件似乎不再可能。