ssl --- Mô-đun SSL/TLS

Mô-đun này cung cấp khả năng mã hóa Transport Layer Security (trước đây và thường được gọi là "Secure Sockets Layer") cùng tính năng xác thực đối tác cho các socket mạng, cả phía máy khách lẫn phía máy chủ.

Mẹo

Bạn mới làm quen với TLS trên camera? Hãy bắt đầu với hướng dẫn Làm việc với chứng chỉ TLS. Hướng dẫn đó trình bày cách chọn loại khóa, tạo và chuyển đổi chứng chỉ sang định dạng DER mà camera yêu cầu, đưa chúng lên thiết bị và xác minh máy chủ cũng như máy khách -- kèm theo các ví dụ hoàn chỉnh có thể chạy được.

Ghi chú

MicroPython không triển khai ssl.SSLError. Các lỗi SSL/TLS được phát sinh dưới dạng OSError.

Ví dụ

Máy khách TLS, xác minh chứng chỉ máy chủ dựa trên chứng chỉ CA (định dạng DER) được lưu trên hệ thống tệp:

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

Để kết nối nhanh, không an toàn (không xác thực chứng chỉ), có thể sử dụng hàm tiện ích ssl.wrap_socket() thay thế:

ssock = ssl.wrap_socket(sock, server_hostname="example.com")

Máy chủ TLS, trình bày chứng chỉ và khóa riêng tư của nó (định dạng 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()

Hàm

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

Bao bọc sock đã cho và trả về một đối tượng socket đã được bao bọc. Việc triển khai hàm này là trước tiên tạo một SSLContext rồi gọi phương thức SSLContext.wrap_socket() trên đối tượng ngữ cảnh đó. Các đối số sock, server_sideserver_hostname được truyền trực tiếp không thay đổi. Đối số do_handshake được truyền dưới dạng do_handshake_on_connect. Các đối số còn lại có hành vi như sau:

  • cert_reqs xác định xem đối tác (máy chủ hoặc máy khách) có phải trình bày chứng chỉ hợp lệ hay không. Lưu ý rằng ssl.CERT_NONEssl.CERT_OPTIONAL không xác thực bất kỳ chứng chỉ nào; chỉ ssl.CERT_REQUIRED mới làm điều đó.

  • cadata là một đối tượng bytes chứa chuỗi chứng chỉ CA (định dạng DER) sẽ được dùng để xác thực chứng chỉ của đối tác. Hiện tại chỉ hỗ trợ một chứng chỉ được mã hóa DER.

Lớp

class ssl.SSLContext(protocol: int, /)

Tạo một phiên bản SSLContext mới. Đối số protocol phải là một trong các hằng số PROTOCOL_*.

load_cert_chain(certfile: str | bytes, keyfile: str | bytes) None

Nạp khóa riêng tư và chứng chỉ tương ứng. certfile là chuỗi chứa đường dẫn tệp chứng chỉ. keyfile là chuỗi chứa đường dẫn tệp khóa riêng tư.

Khác biệt so với CPython

Mở rộng MicroPython: certfilekeyfile có thể là đối tượng bytes thay vì chuỗi, trong trường hợp đó chúng được hiểu là dữ liệu chứng chỉ/khóa thực tế.

load_verify_locations(cafile: str | None = None, cadata: bytes | None = None) None

Nạp chuỗi chứng chỉ CA dùng để xác thực chứng chỉ của đối tác. cafile là đường dẫn tệp chứng chỉ CA. cadata là đối tượng bytes chứa các chứng chỉ CA. Chỉ nên cung cấp một trong hai đối số này.

get_ciphers() List[str]

Lấy danh sách các cipher đang được bật, trả về dưới dạng danh sách chuỗi.

set_ciphers(ciphers: List[str]) None

Đặt các cipher có sẵn cho các socket được tạo với ngữ cảnh này. ciphers phải là danh sách chuỗi theo định dạng bộ cipher 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

Nhận một stream sock (thường là phiên bản socket.socket kiểu SOCK_STREAM), và trả về một phiên bản ssl.SSLSocket, bao bọc luồng bên dưới. Đối tượng được trả về có các phương thức giao diện stream thông thường như read(), write(), v.v.

  • server_side chọn xem socket được bao bọc ở phía máy chủ hay máy khách. Socket SSL phía máy chủ nên được tạo từ socket thông thường được trả về từ accept() trên socket máy chủ lắng nghe không SSL.

  • do_handshake_on_connect xác định xem quá trình bắt tay có được thực hiện như một phần của wrap_socket hay bị trì hoãn để thực hiện như một phần của các lần đọc hoặc ghi đầu tiên. Với các socket chặn, việc thực hiện bắt tay ngay lập tức là tiêu chuẩn. Với các socket không chặn (tức là khi sock được truyền vào wrap_socket đang ở chế độ không chặn), quá trình bắt tay thường nên được trì hoãn vì nếu không wrap_socket sẽ bị chặn cho đến khi hoàn tất.

  • server_hostname dùng cho phía máy khách, đặt tên máy chủ để kiểm tra với chứng chỉ máy chủ nhận được. Nó cũng đặt tên cho Server Name Indication (SNI), cho phép máy chủ trình bày chứng chỉ phù hợp.

  • client_id là đối số mở rộng đặc thù MicroPython chỉ được dùng khi triển khai máy chủ DTLS. Xem Hỗ trợ DTLS để biết chi tiết.

Cảnh báo

Theo mặc định, không có xác thực chứng chỉ nào được thực hiện (ssl.CERT_NONE). Để có kết nối an toàn, bạn phải xác minh chứng chỉ của đối tác bằng cách đặt cert_reqs / SSLContext.verify_mode thành ssl.CERT_REQUIRED; nếu không, kết nối dễ bị tấn công man-in-the-middle.

wrap_socket của CPython trả về đối tượng SSLSocket có các phương thức điển hình cho socket như send, recv, v.v. wrap_socket của MicroPython trả về đối tượng giống SSLObject của CPython hơn, vốn không có các phương thức socket này.

verify_mode

Đặt hoặc lấy hành vi xác minh chứng chỉ đối tác. Phải là một trong các hằng số CERT_*.

Ghi chú

ssl.CERT_REQUIRED yêu cầu ngày/giờ của thiết bị được đặt đúng, ví dụ: dùng mpremote rtc --set hoặc ntptime, và server_hostname phải được chỉ định khi ở phía máy khách.

Hỗ trợ DTLS

Khác biệt so với CPython

Đây là phần mở rộng của MicroPython.

Mô-đun này hỗ trợ DTLS ở chế độ máy khách và máy chủ thông qua các hằng số PROTOCOL_DTLS_CLIENTPROTOCOL_DTLS_SERVER có thể được dùng làm đối số protocol của SSLContext.

Trong trường hợp này, socket bên dưới được kỳ vọng hoạt động như một socket datagram (tức là giống socket được mở bằng socket.socket với socket.AF_INETafsocket.SOCK_DGRAMtype).

Hỗ trợ máy chủ DTLS

Hỗ trợ máy chủ DTLS của MicroPython được cấu hình với "Hello Verify" theo yêu cầu của DTLS 1.2. Điều này minh bạch với các máy khách DTLS, nhưng có những điểm cần lưu ý khi triển khai máy chủ DTLS trong MicroPython:

  • Máy chủ nên truyền thêm đối số client_id khi gọi SSLContext.wrap_socket(). ID này phải là đối tượng bytes (hoặc tương tự) chứa mã định danh đặc thù cho giao thức vận chuyển đại diện cho máy khách.

    Cách đơn giản nhất là chuyển đổi bộ tuple (client_ip, client_port) được trả về từ socket.recv_from() thành chuỗi byte, tức là:

    _, 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())
    
  • Lần đầu tiên máy khách kết nối, lệnh gọi wrap_socket của máy chủ sẽ thất bại với lỗi OSError "Hello Verify Required". Điều này là vì cookie DTLS "Hello Verify" chưa được máy khách biết đến. Nếu cùng một máy khách kết nối lần thứ hai, wrap_socket sẽ thành công.

  • Cookie DTLS cho "Hello Verify" được liên kết với đối tượng SSLContext, vì vậy cùng một đối tượng SSLContext nên được dùng để bao bọc kết nối tiếp theo từ cùng một máy khách. Việc triển khai cookie bao gồm thời gian chờ và sử dụng bộ nhớ cố định bất kể có bao nhiêu máy khách kết nối, vì vậy việc tái sử dụng cùng một đối tượng SSLContext cho vòng đời của máy chủ là ổn.

Hằng số

ssl.PROTOCOL_TLS_CLIENT: int

Giá trị được hỗ trợ cho tham số protocol, chọn chế độ máy khách TLS.

ssl.PROTOCOL_TLS_SERVER: int

Giá trị được hỗ trợ cho tham số protocol, chọn chế độ máy chủ TLS.

ssl.PROTOCOL_DTLS_CLIENT: int

Giá trị được hỗ trợ cho tham số protocol, chọn chế độ máy khách DTLS.

ssl.PROTOCOL_DTLS_SERVER: int

Giá trị được hỗ trợ cho tham số protocol, chọn chế độ máy chủ DTLS.

ssl.CERT_NONE: int

Giá trị được hỗ trợ cho tham số cert_reqs và thuộc tính SSLContext.verify_mode. Không thực hiện xác minh chứng chỉ nào với đối tác.

ssl.CERT_OPTIONAL: int

Giá trị được hỗ trợ cho tham số cert_reqs và thuộc tính SSLContext.verify_mode. Xác minh chứng chỉ là tùy chọn. Lưu ý rằng trên OpenMV Cam, tính năng này hoạt động giống như ssl.CERT_NONE.

ssl.CERT_REQUIRED: int

Giá trị được hỗ trợ cho tham số cert_reqs và thuộc tính SSLContext.verify_mode. Yêu cầu chứng chỉ hợp lệ từ đối tác.