14.4.3. Самоподписанные сертификаты¶
Самоподписанный сертификат – это самый быстрый способ заставить TLS работать между двумя устройствами, которыми вы управляете: оба конца доверяют единому сертификату, который вы генерируете сами. Он покрывает любое развёртывание, где вы настраиваете обе стороны соединения – публичный центр сертификации (Certificate Authority) появляется в картине только тогда, когда сторонние клиенты должны подключаться без указания доверять пользовательскому сертификату.
14.4.3.1. Создание самоподписанного сертификата¶
Запустите OpenSSL на своей машине разработки. subjectAltName (SAN) – это то, что современные TLS-клиенты проверяют во время верификации имени хоста, поэтому задайте в нём имя(имена) хоста и/или IP-адрес(а), по которым клиенты будут обращаться к камере (один лишь CN устарел и игнорируется многими клиентами). Замените DNS:openmv / IP:192.168.1.50 на адрес, к которому ваши клиенты действительно подключаются.
ECDSA P-256 – рекомендуется:
# Generate a P-256 private key.
openssl ecparam -name prime256v1 -genkey -noout -out server.key
# Self-signed certificate valid for one year, with a SAN.
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=openmv" -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
ECDSA P-384 – надёжнее, больше/медленнее:
openssl ecparam -name secp384r1 -genkey -noout -out server.key
openssl req -new -x509 -key server.key -out server.crt -days 365 \
-subj "/CN=openmv" -addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
RSA-2048 – максимальная совместимость:
openssl req -new -x509 -newkey rsa:2048 -nodes -keyout server.key \
-out server.crt -days 365 -subj "/CN=openmv" \
-addext "subjectAltName=DNS:openmv,IP:192.168.1.50"
Примечание
Клиентский сертификат (используемый для взаимной аутентификации, см. ниже) создаётся точно теми же командами – в самом сертификате нет ничего специфичного для клиента. Просто сгенерируйте вторую, независимую пару ключ/сертификат под другими именами (например, client.key / client.crt) и используйте её на клиенте, как показано в примере mTLS. subjectAltName важен только для той стороны, чьё имя хоста проверяет узел (клиент проверяет имя сервера; имя клиента не проверяет ничто), поэтому для сертификата, предназначенного только для клиента, его можно опустить. -subj / CN для клиентского сертификата также является лишь меткой – сервер здесь проверяет только то, что сертификат выстраивается в цепочку до доверенного CA, и никогда не сопоставляет имя – поэтому задайте в нём что угодно, что идентифицирует этого клиента (например, /CN=sensor-01). В любом случае сохраните какое-либо значение -subj, чтобы OpenSSL мог сгенерировать сертификат в неинтерактивном режиме.
Срок действия сертификата задаётся с помощью -days; сертификаты истекают и должны быть перегенерированы и переразвёрнуты до этого момента.
14.4.3.2. Преобразование в DER¶
Преобразуйте и сертификат, и приватный ключ в DER, прежде чем копировать их на камеру:
openssl x509 -in server.crt -outform DER -out server.der
openssl pkey -in server.key -outform DER -out server.key.der
14.4.3.3. Копирование файлов на камеру¶
Скопируйте файлы DER в файловую систему камеры – например, перетащив их на USB-диск OpenMV Cam, или с помощью mpremote cp server.der : и mpremote cp server.key.der :. На проверяющей стороне также скопируйте сертификат CA / узла в форме DER.
Файлы DER не обязательно должны находиться в записываемой файловой системе. MicroPython также может смонтировать образ ROMFS только для чтения по пути /rom, и размещённые там сертификаты загружаются точно так же, как любой другой файл – например, ctx.load_cert_chain("/rom/server.der", "/rom/server.key.der"). Образ ROMFS подготавливается на вашей машине разработки и доступен только для чтения во время выполнения, поэтому сертификат нельзя изменить на устройстве – это полезно для блокировки производственного экземпляра. Обратите внимание, что приватный ключ, хранящийся в ROMFS, по-прежнему доступен для чтения коду, выполняющемуся на камере; ROMFS защищает от изменения, а не от извлечения. Размещённый в ROMFS сертификат можно заменить только пересборкой и перепрошивкой образа.
14.4.3.4. Использование сертификата¶
Полный клиент, который устанавливает часы, открывает сокет, проверяет самоподписанный сервер и обменивается данными:
import socket
import ssl
import ntptime
ntptime.settime() # correct clock for the validity check
# Open a plain TCP connection.
addr = socket.getaddrinfo("openmv", 8443)[0][-1]
sock = socket.socket()
sock.connect(addr)
# Wrap it for TLS, trusting the server's self-signed certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="server.der")
ssock = ctx.wrap_socket(sock, server_hostname="openmv")
ssock.write(b"hello\n")
print(ssock.read())
ssock.close()
Полный сервер, предъявляющий свой сертификат и ключ:
import socket
import ssl
import ntptime
ntptime.settime() # correct clock for the validity check
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key.der")
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)
while True:
client, addr = sock.accept()
sclient = ctx.wrap_socket(client, server_side=True)
sclient.write(b"hello\n")
print(sclient.read())
sclient.close()
Для взаимной аутентификации (mTLS) сервер дополнительно требует и проверяет клиентский сертификат, а клиент предъявляет свой собственный:
# Server side: also demand and verify a client certificate.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
ctx.load_cert_chain("server.der", "server.key.der")
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="client.der")
# Client side: present a certificate of our own.
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
ctx.load_cert_chain("client.der", "client.key.der")
ctx.verify_mode = ssl.CERT_REQUIRED
ctx.load_verify_locations(cafile="server.der")
Полное описание API см. в документации модуля ssl.
Примечание
Всё на этой странице применимо без изменений к DTLS (TLS поверх UDP). Ключи, сертификаты, формат DER, модель доверия, вопросы истечения срока и вызовы load_cert_chain / load_verify_locations идентичны; различается лишь транспорт – вы оборачиваете сокет socket.SOCK_DGRAM и выбираете ssl.PROTOCOL_DTLS_CLIENT / ssl.PROTOCOL_DTLS_SERVER вместо констант протокола TLS. Единственное дополнительное усложнение – серверный анти-спуфинговый cookie: первое соединение от нового клиента, как ожидается, завершится сбоем, и клиент просто повторит попытку; подробности см. в Поддержка DTLS.