ssl --- SSL/TLS 模块¶
本模块为网络套接字提供传输层安全(Transport Layer Security,旧称且广为人知的“安全套接字层”)加密以及对端身份验证功能,客户端和服务器端均可使用。
小技巧
初次在摄像头上使用 TLS? 请从 使用 TLS 证书 教程开始。它会逐步讲解如何选择密钥类型、创建证书并将其转换为摄像头所需的 DER 格式、将证书部署到设备上,以及验证服务器和客户端——并附有完整的可运行示例。
备注
MicroPython 未实现 ssl.SSLError。SSL/TLS 失败会以 OSError 的形式抛出。
示例¶
TLS 客户端,使用存储在文件系统上的 CA 证书(DER 格式)来验证服务器的证书:
import socket
import ssl
import ntptime
# CERT_REQUIRED checks the certificate's validity dates, so the clock
# must be set (see the certificates tutorial linked above).
ntptime.settime()
# Open a plain TCP connection.
addr = socket.getaddrinfo("example.com", 443)[0][-1]
sock = socket.socket()
sock.connect(addr)
# Wrap it for TLS and require a valid certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="ca.der")
ssock = ctx.wrap_socket(sock, server_hostname="example.com")
ssock.write(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
print(ssock.read())
ssock.close()
若只需要快速建立一个 不安全的 连接(不进行证书验证),可以改用 ssl.wrap_socket() 便捷函数:
ssock = ssl.wrap_socket(sock, server_hostname="example.com")
TLS 服务器,提供其自身的证书和私钥(DER 格式):
import socket
import ssl
sock = socket.socket()
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(socket.getaddrinfo("0.0.0.0", 8443)[0][-1])
sock.listen(1)
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key")
while True:
client, addr = sock.accept()
sclient = ctx.wrap_socket(client, server_side=True)
sclient.write(b"hello\n")
sclient.close()
函数¶
- ssl.wrap_socket(sock: Any, server_side: bool = False, key: bytes | None = None, cert: bytes | None = None, cert_reqs: int = CERT_NONE, cadata: bytes | None = None, server_hostname: str | None = None, do_handshake: bool = True) Any¶
包装给定的 sock 并返回一个新的已包装套接字对象。该函数的实现方式是先创建一个
SSLContext,然后在该上下文对象上调用SSLContext.wrap_socket()方法。参数 sock、server_side 和 server_hostname 会原样传递给该方法调用。参数 do_handshake 会作为 do_handshake_on_connect 传递。其余参数的行为如下:cert_reqs 决定对端(服务器或客户端)是否必须提供有效的证书。请注意
ssl.CERT_NONE和ssl.CERT_OPTIONAL都不会验证任何证书;只有ssl.CERT_REQUIRED才会进行验证。cadata 是一个 bytes 对象,包含将用于验证对端证书的 CA 证书链(DER 格式)。目前仅支持单个 DER 编码的证书。
类¶
- class ssl.SSLContext(protocol: int, /)¶
创建一个新的 SSLContext 实例。protocol 参数必须是
PROTOCOL_*常量之一。- load_cert_chain(certfile: str | bytes, keyfile: str | bytes) None¶
加载一个私钥及其对应的证书。certfile 是一个字符串,表示证书的文件路径。keyfile 是一个字符串,表示私钥的文件路径。
与 CPython 的区别
MicroPython 扩展:certfile 和 keyfile 可以是 bytes 对象而非字符串,在这种情况下它们会被解释为实际的证书/密钥数据。
- load_verify_locations(cafile: str | None = None, cadata: bytes | None = None) None¶
加载将用于验证对端证书的 CA 证书链。cafile 是 CA 证书的文件路径。cadata 是一个包含 CA 证书的 bytes 对象。这两个参数只应提供其中之一。
- set_ciphers(ciphers: List[str]) None¶
为使用该上下文创建的套接字设置可用的密码套件。ciphers 应为采用 IANA 密码套件格式 的字符串列表。
- wrap_socket(sock: Any, *, server_side: bool = False, do_handshake_on_connect: bool = True, server_hostname: str | None = None, client_id: bytes | None = None) Any¶
接收一个 stream sock(通常是
SOCK_STREAM类型的 socket.socket 实例),并返回一个 ssl.SSLSocket 实例,对底层流进行包装。返回的对象具有常见的 stream 接口方法,如read()、write()等。server_side 选择被包装的套接字位于服务器端还是客户端。服务器端的 SSL 套接字应当从非 SSL 监听服务器套接字上调用
accept()所返回的普通套接字来创建。do_handshake_on_connect 决定握手是作为
wrap_socket的一部分立即完成,还是推迟到初次读写时再进行。对于阻塞套接字,立即握手是标准做法。对于非阻塞套接字(即传入wrap_socket的 sock 处于非阻塞模式时),通常应推迟握手,否则wrap_socket会一直阻塞直到握手完成。server_hostname 供客户端使用,用于设置要对照所接收到的服务器证书进行核对的主机名。它还会设置服务器名称指示(SNI)的名称,从而让服务器能够提供正确的证书。
client_id 是 MicroPython 特有的扩展参数,仅在实现 DTLS 服务器时使用。详见 DTLS 支持。
警告
默认情况下不进行任何证书验证(
ssl.CERT_NONE)。为建立安全连接,你必须通过将 cert_reqs /SSLContext.verify_mode设置为ssl.CERT_REQUIRED来验证对端的证书;否则连接将容易受到中间人攻击。CPython 的
wrap_socket返回一个SSLSocket对象,它具有套接字常见的方法,如send、recv等。MicroPython 的wrap_socket返回的对象更类似于 CPython 的SSLObject,后者不具有这些套接字方法。
- verify_mode¶
设置或获取对端证书的验证行为。必须是
CERT_*常量之一。备注
ssl.CERT_REQUIRED要求设备的日期/时间设置正确,例如使用 mpremote rtc --set 或ntptime,并且在客户端时必须指定server_hostname。
DTLS 支持¶
与 CPython 的区别
这是一项 MicroPython 扩展。
本模块通过 PROTOCOL_DTLS_CLIENT 和 PROTOCOL_DTLS_SERVER 常量支持客户端和服务器模式的 DTLS,这些常量可用作 SSLContext 的 protocol 参数。
在这种情况下,底层套接字应当表现得像数据报套接字(即类似于以 socket.AF_INET 作为 af、以 socket.SOCK_DGRAM 作为 type 通过 socket.socket 打开的套接字)。
DTLS 服务器支持¶
MicroPython 的 DTLS 服务器支持按照 DTLS 1.2 的要求配置了“Hello Verify”。这对 DTLS 客户端是透明的,但在 MicroPython 中实现 DTLS 服务器时有一些需要注意的事项:
服务器在调用
SSLContext.wrap_socket()时应额外传入一个参数 client_id。该 ID 必须是一个bytes对象(或类似对象),其中包含表示该客户端的、与传输相关的标识符。最简单的做法是将
socket.recv_from()返回的元组(client_ip, client_port)转换为字节串,即:_, client_addr = sock.recvfrom(1, socket.MSG_PEEK) sock.connect(client_addr) # Connect back to the client sock = ssl_ctx.wrap_socket(sock, server_side=True, client_id=repr(client_addr).encode())
客户端首次连接时,服务器对
wrap_socket的调用会失败并抛出OSError错误“Hello Verify Required”。这是因为客户端尚未获知 DTLS 的“Hello Verify”cookie。如果同一客户端再次连接,则wrap_socket将会成功。用于“Hello Verify”的 DTLS cookie 与
SSLContext对象相关联,因此对于来自同一客户端的后续连接,应使用同一个SSLContext对象来进行包装。该 cookie 实现包含超时机制,且无论有多少客户端连接,内存占用都是恒定的,因此在服务器的整个生命周期内重复使用同一个SSLContext对象是没有问题的。
常量¶
- ssl.CERT_NONE: int¶
cert_reqs 参数以及
SSLContext.verify_mode属性支持的取值。不对对端进行证书验证。
- ssl.CERT_OPTIONAL: int¶
cert_reqs 参数以及
SSLContext.verify_mode属性支持的取值。证书验证是可选的。请注意,在 OpenMV Cam 上其行为与ssl.CERT_NONE相同。
- ssl.CERT_REQUIRED: int¶
cert_reqs 参数以及
SSLContext.verify_mode属性支持的取值。要求对端提供有效的证书。