9.17. Зашифровані сокети та TLS¶
Все розглянуте до цього моменту передає байти у відкритому вигляді. Будь-який пристрій на шляху між камерою та сервером – домашній маршрутизатор, провайдер, зловмисна точка доступу в кав’ярні – може в принципі читати або змінювати те, що проходить через нього. Для більшості інтернет-трафіку це неприйнятно. Стандартне рішення – огорнути з’єднання шаром шифрування: TLS, протокол Transport Layer Security. Значок замка «HTTPS» у браузері – це TLS поверх TCP, і те саме огортання робить будь-який інший інтернет-протокол «безпечним». Модуль ssl камери огортає socket у TLS.
9.17.1. Що додає TLS і що поставляється з камерою¶
TLS розміщується між TCP і застосунком – застосунок записує байти в TLS-загорнутий сокет, TLS шифрує їх і передає результат TCP, а на іншому боці процес відбувається у зворотному порядку. У повному вигляді TLS надає три гарантії поверх звичайного TCP:
Конфіденційність. Підслуховувачі на шляху не можуть прочитати те, що обмінюються дві кінцеві точки.
Цілісність. Будь-яка зміна трафіку в дорозі виявляється; з’єднання переривається, а не доставляє змінені дані.
Автентифікація. Сервер доводить, що є саме названим сервером, а не самозванцем (і, опційно, клієнт теж доводить, хто він такий).
Перші дві забезпечуються самим шифруванням. Третя потребує сертифікатів принаймні з одного боку, плюс щось наперед довірене для їх перевірки. OpenMV Cam поставляється без вбудованого сховища сертифікатів: щойно прошита камера не довіряє жодному центру сертифікації, не має власного серверного сертифіката, а стандартний режим перевірки (ssl.CERT_NONE) не перевіряє сертифікат партнера ні за чим. Отже, з коробки TLS на камері дає перші дві гарантії – шифрування від підслуховування і підробки пасивним спостерігачем – але не третю.
9.17.2. Шифрування вихідного з’єднання¶
Найпростіший варіант використання – огортання вихідного TCP-з’єднання. Послідовність така: відкрити звичайний TCP-сокет, передати його до ssl.wrap_socket(), а потім читати і записувати через загорнутий сокет точно так само, як через звичайний:
import socket
import ssl
addr = socket.getaddrinfo("example.com", 443)[0][-1]
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(addr)
s = ssl.wrap_socket(sock)
s.send(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
print(s.recv(4096))
s.close()
Огортання виконує TLS-рукостискання; після цього кожен байт через s.send шифрується на виході, а кожен байт із s.recv був зашифрований під час передачі. Сертифікати не налаштовувались, якір довіри не надавався – TLS просто погоджує ефемерний сесійний ключ із тим сервером, що відповість, і використовує його.
TLS-рукостискання, яке виконує ssl.wrap_socket(). Воно розміщується поверх вже відкритого TCP-з’єднання з попереднього рисунка; як тільки обидві сторони надіслали Finished, решта розмови шифрується в обох напрямках.¶
Попередження
Це лише шифрування, а не автентифікований TLS. Камера безпечно спілкується з тим, хто відповів на іншому кінці TCP-з’єднання. Якщо атака «людина посередині» перенаправляє з’єднання на підконтрольний сервер і той сервер пред’являє будь-який сертифікат, рукостискання все одно успішне, і камера в підсумку безпечно спілкується зі зловмисником. Використовуйте цей режим лише тоді, коли атака «людина посередині» не входить до моделі загроз – закрита локальна мережа, середовище розробки, камера спілкується з сервісом на тому ж обладнанні – не при виході в публічний інтернет.
Для справжньої автентифікації – коли камера перевіряє публічний сервер, виступає TLS-сервером або здійснюється взаємний TLS – потрібно завантажити сертифікати на пристрій. Повний опис наведено в Робота з TLS-сертифікатами.
Те саме огортання працює для вхідного TCP-трафіку: потрібно вибрати серверний протокол і передати server_side=True до ssl.wrap_socket(). Попередження вище все ще діє: без власного сертифіката камера не може довести клієнту, хто вона є, і допитливий клієнт побачить помилку рукостискання «немає сертифіката» на більшості TLS-стеків. Робочий процес із сертифікатом з боку продакшена – це те, що дозволяє корисно запускати камеру як TLS-сервер.
9.17.3. З asyncio¶
У розділі asyncio було показано asyncio.open_connection() для звичайних TCP-клієнтів. Той самий виклик приймає ключове слово ssl=True, яке огортає з’єднання в TLS – знову ж таки без налаштування сертифікатів:
import asyncio
async def main():
reader, writer = await asyncio.open_connection(
"example.com", 443, ssl=True,
)
writer.write(b"GET / HTTP/1.0\r\nHost: example.com\r\n\r\n")
await writer.drain()
print(await reader.read(4096))
writer.close()
await writer.wait_closed()
asyncio.run(main())
Пара reader/writer за TLS-з’єднанням має ту саму форму, що і для звичайного TCP-з’єднання – різниться лише налаштування. Те саме застереження щодо автентифікації діє: ssl=True окремо дає лише шифрування, а не перевірку.
9.17.4. DTLS – TLS поверх UDP¶
TLS, як розглядалося до цього, працює поверх TCP. Паралельний протокол для UDP – DTLS (Datagram TLS), і модуль ssl камери підтримує його так само. Якщо TLS перетворює одне TCP-з’єднання на один зашифрований потік байтів, то DTLS перетворює один UDP-сокет на потік зашифрованих, окремо доставлених дейтаграм – отже, властивості UDP щодо втрат, позачергової доставки та відсутності управління потоком з UDP – відправ пакет і сподівайся на краще зберігаються, але байти всередині кожної дейтаграми тепер зашифровані.
Огортання виглядає так само, як у випадку TLS, лише з сокетом SOCK_DGRAM і константами протоколу DTLS:
import socket
import ssl
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(socket.getaddrinfo("example.com", 4433)[0][-1])
ctx = ssl.SSLContext(ssl.PROTOCOL_DTLS_CLIENT)
s = ctx.wrap_socket(sock)
s.send(b"ping")
print(s.recv(64))
s.close()
(Виклик connect() на UDP-сокеті не відкриває з’єднання – він просто запам’ятовує стандартний адресат, щоб наступні виклики send() / recv() не повторювали його. DTLS потребує цього фіксованого адресата для виконання свого рукостискання.)
Рукостискання має ту саму форму, що і на TLS-діаграмі вище; різниця в тому, що кожне повідомлення рукостискання є UDP-дейтаграмою, і кожна сторона повторно надішле у разі втрати.
Примітка
Чи порушує втрата пакетів шифрування? Ні. Кожен DTLS-пакет несе порядковий номер, і шифрування використовує цей номер для отримання різного виводу для кожного пакета – тому одні й ті самі вхідні дані ніколи не шифруються в одні й ті самі байти двічі, і будь-який пакет можна розшифрувати самостійно без того, щоб попередній вже надійшов. Втрачені або позачергові пакети не десинхронізують дві сторони. (Саме рукостискання є тією єдиною частиною, яка має надійно дійти, і DTLS вирішує це власним механізмом повторного відправлення.)
Попередження про лише-шифрування-без-сертифікатів вище також діє: DTLS-рукостискання з партнером CERT_NONE шифрує трафік, але не перевіряє, хто є іншою стороною. Повний DTLS-робочий процес – сертифікати, anti-spoofing cookie на стороні сервера, чим він схожий на TLS, крім констант протоколу – розглядається разом із матеріалом TLS в Робота з TLS-сертифікатами.
Версія asyncio використовує той самий неблокувальний UDP-шаблон із Сокети з asyncio. Виконайте рукостискання синхронно спочатку, переключіть сокет у неблокувальний режим, а потім опитуйте всередині корутини:
import asyncio
import socket
import ssl
async def dtls_ping(target_addr, period_ms):
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.connect(target_addr)
# Handshake while still blocking, then switch to async polling.
ctx = ssl.SSLContext(ssl.PROTOCOL_DTLS_CLIENT)
s = ctx.wrap_socket(sock)
s.setblocking(False)
while True:
try:
s.send(b"ping")
except OSError:
pass
await asyncio.sleep_ms(period_ms)
Рукостискання – єдине місце, де ця корутина блокує цикл подій; після цього кожен s.send / s.recv повертається негайно (або генерує OSError), а await asyncio.sleep_ms підтримує роботу решти програми.
9.17.5. Що далі¶
Все більше ніж лише-шифрування TLS – перевірка сертифіката публічного HTTPS-сервера, запуск камери як автентифікованого TLS-сервера, взаємний TLS між камерою і бекендом, вибір ключів і їх типів, робота зі строком дії сертифікатів – описано в Робота з TLS-сертифікатами. Цей розділ охоплює: як генерувати самопідписані сертифікати для локального тестування, як отримати сертифікати, підписані CA, для продакшена, як завантажити їх на камеру у правильному форматі (DER), як перевіряти публічний сервер, коли камера є клієнтом, як думати про захист ключів на пристрої, який зловмисник може розібрати, і як планувати на той день, коли сертифікат закінчиться.
Для повного довідника API ssl – підтримувані версії TLS, набори шифрів і параметри контексту – дивіться ssl — модуль SSL/TLS.