ssl --- SSL/TLS 模組¶
本模組為網路 socket 提供傳輸層安全性(Transport Layer Security,先前及廣為人知的名稱為「安全通訊端層」,Secure Sockets Layer)的加密與對端驗證功能,同時支援用戶端與伺服器端。
小訣竅
初次在相機上使用 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 並回傳一個新的已包裝 socket 物件。此函式的實作方式是先建立一個
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¶
設定使用此情境所建立的 socket 可用的密碼套件。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 選擇被包裝的 socket 是位於伺服器端還是用戶端。伺服器端的 SSL socket 應由非 SSL 監聽伺服器 socket 上
accept()所回傳的一般 socket 建立而成。do_handshake_on_connect 決定握手是作為
wrap_socket的一部分完成,還是延後到作為初始讀取或寫入的一部分時才完成。對於阻塞式 socket,立即進行握手是標準做法。對於非阻塞式 socket(亦即傳入wrap_socket的 sock 處於非阻塞模式時),通常應延後握手,否則wrap_socket會一直阻塞直到握手完成。server_hostname 供用戶端使用,設定用以比對所收到伺服器憑證的主機名稱。它同時也設定伺服器名稱指示(Server Name Indication,SNI)所用的名稱,使伺服器能夠提供正確的憑證。
client_id 是 MicroPython 特有的擴充引數,僅在實作 DTLS 伺服器時使用。詳情請見 DTLS 支援。
警告
預設情況下不會執行任何憑證驗證(
ssl.CERT_NONE)。若要建立安全連線,您必須將 cert_reqs /SSLContext.verify_mode設定為ssl.CERT_REQUIRED來驗證對端的憑證;否則連線將容易遭受中間人攻擊。CPython 的
wrap_socket會回傳一個SSLSocket物件,該物件具有 socket 常見的方法,例如send、recv等。而 MicroPython 的wrap_socket回傳的物件比較類似 CPython 的SSLObject,它並不具備這些 socket 方法。
- 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 應表現為資料包 socket(亦即如同以 socket.socket 開啟、其 af 為 socket.AF_INET 且 type 為 socket.SOCK_DGRAM 的 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屬性的支援值。要求對端提供有效的憑證。