ssl — модуль SSL/TLS

Цей модуль надає доступ до шифрування 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 і повертає новий об’єкт обгорнутого сокета. Реалізація цієї функції полягає в тому, що спочатку створюється 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 — це об’єкт байтів, що містить ланцюжок сертифікатів 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 можуть бути об’єктами байтів замість рядків, у такому випадку вони інтерпретуються як безпосередньо дані сертифіката/ключа.

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

Завантажує ланцюжок сертифікатів CA для перевірки сертифіката однорангового вузла. cafile — шлях до файлу з сертифікатами CA. cadata — об’єкт байтів, що містить сертифікати CA. Слід вказати лише один із цих аргументів.

get_ciphers() List[str]

Отримує список увімкнених шифрів у вигляді списку рядків.

set_ciphers(ciphers: List[str]) None

Встановлює доступні шифри для сокетів, створених з цим контекстом. ciphers має бути списком рядків у форматі IANA cipher suite.

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 (зазвичай екземпляр socket.socket типу SOCK_STREAM) і повертає екземпляр ssl.SSLSocket, що обгортає базовий потік. Повернений об’єкт має звичайні методи інтерфейсу stream, такі як read(), write() тощо.

  • server_side визначає, чи знаходиться обгорнутий сокет на стороні сервера або клієнта. SSL-сокет на стороні сервера слід створювати із звичайного сокета, поверненого accept() з неSSL-слухаючого сокета сервера.

  • do_handshake_on_connect визначає, чи виконується рукостискання як частина wrap_socket, або воно відкладається до перших читань чи записів. Для блокувальних сокетів негайне виконання рукостискання є стандартним. Для неблокувальних сокетів (тобто коли sock, переданий у wrap_socket, перебуває в неблокувальному режимі) рукостискання зазвичай слід відкласти, інакше wrap_socket блокуватиметься до його завершення.

  • server_hostname використовується на стороні клієнта і встановлює ім’я хоста для перевірки отриманого сертифіката сервера. Також задає ім’я для Server Name Indication (SNI), що дозволяє серверу надати відповідний сертифікат.

  • client_id — це специфічний для MicroPython аргумент-розширення, що використовується лише при реалізації DTLS-сервера. Детальніше дивіться Підтримка DTLS.

Попередження

За замовчуванням перевірка сертифіката не виконується (ssl.CERT_NONE). Для безпечного з’єднання необхідно перевіряти сертифікат однорангового вузла, встановивши cert_reqs / SSLContext.verify_mode в ssl.CERT_REQUIRED; інакше з’єднання вразливе до атак типу «людина посередині».

wrap_socket у CPython повертає об’єкт SSLSocket з методами, характерними для сокетів, такими як send, recv тощо. wrap_socket у MicroPython повертає об’єкт, більш схожий на SSLObject у CPython, який не має цих методів сокетів.

verify_mode

Встановлює або отримує поведінку для перевірки сертифікатів однорангових вузлів. Має бути однією з констант CERT_*.

Примітка

ssl.CERT_REQUIRED вимагає, щоб дата/час пристрою були правильно встановлені, наприклад, за допомогою mpremote rtc --set або ntptime, а також необхідно вказати server_hostname на стороні клієнта.

Підтримка DTLS

Відмінність від CPython

Це розширення MicroPython.

Цей модуль підтримує DTLS у режимах клієнта та сервера через константи PROTOCOL_DTLS_CLIENT та PROTOCOL_DTLS_SERVER, які можна використовувати як аргумент protocol для SSLContext.

У цьому випадку базовий сокет повинен поводитися як датаграмний сокет (тобто як сокет, відкритий за допомогою socket.socket з socket.AF_INET як af та socket.SOCK_DGRAM як type).

Підтримка DTLS-сервера

Підтримка DTLS-сервера у MicroPython налаштована з «Hello Verify», як того вимагає DTLS 1.2. Для DTLS-клієнтів це прозоро, однак при реалізації DTLS-сервера в MicroPython є важливі аспекти:

  • Сервер повинен передавати додатковий аргумент client_id при виклику SSLContext.wrap_socket(). Цей ідентифікатор повинен бути об’єктом bytes (або подібним) з транспортно-специфічним ідентифікатором, що представляє клієнта.

    Найпростіший підхід — перетворити кортеж (client_ip, client_port), повернений від socket.recv_from(), у рядок байтів, тобто:

    _, 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» ще не відома клієнту. Якщо той самий клієнт підключиться вдруге, wrap_socket завершиться успішно.

  • Куки DTLS для «Hello Verify» пов’язані з об’єктом SSLContext, тому для обгортання наступного з’єднання від того самого клієнта слід використовувати той самий об’єкт SSLContext. Реалізація куків включає таймаут і використовує постійну пам’ять незалежно від кількості клієнтів, що підключаються, тому можна повторно використовувати той самий об’єкт SSLContext протягом всього часу роботи сервера.

Константи

ssl.PROTOCOL_TLS_CLIENT: int

Підтримуване значення параметра protocol, що вибирає режим TLS-клієнта.

ssl.PROTOCOL_TLS_SERVER: int

Підтримуване значення параметра protocol, що вибирає режим TLS-сервера.

ssl.PROTOCOL_DTLS_CLIENT: int

Підтримуване значення параметра protocol, що вибирає режим DTLS-клієнта.

ssl.PROTOCOL_DTLS_SERVER: int

Підтримуване значення параметра protocol, що вибирає режим DTLS-сервера.

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. Від однорангового вузла вимагається дійсний сертифікат.