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 – это объект 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 – это объект bytes, содержащий сертификаты CA. Следует указывать только один из этих аргументов.

get_ciphers() List[str]

Возвращает список включённых шифров в виде списка строк.

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 (обычно экземпляр socket.socket типа SOCK_STREAM) и возвращает экземпляр ssl.SSLSocket, оборачивающий нижележащий поток. Возвращаемый объект имеет обычные методы интерфейса stream, такие как read(), write() и т. д.

  • server_side выбирает, находится ли обёрнутый сокет на стороне сервера или клиента. SSL-сокет на стороне сервера должен создаваться из обычного сокета, возвращённого методом accept() на не-SSL слушающем серверном сокете.

  • do_handshake_on_connect определяет, выполняется ли рукопожатие как часть wrap_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, имеющий типичные для сокетов методы, такие как send, recv и т. д. Функция MicroPython wrap_socket возвращает объект, более похожий на 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-cookie «Hello Verify» ещё не известен клиенту. Если тот же клиент подключится во второй раз, то wrap_socket завершится успешно.

  • DTLS-cookie для «Hello Verify» связаны с объектом SSLContext, поэтому для оборачивания последующего соединения от того же клиента следует использовать тот же объект SSLContext. Реализация cookie включает тайм-аут и использует постоянный объём памяти независимо от того, сколько клиентов подключается, поэтому допустимо повторно использовать один и тот же объект 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. От участника требуется действительный сертификат.