hbs2/docs/proto/encryption.texi

350 lines
15 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

\input texinfo
@documentencoding UTF-8
@node Top
@top Дизайн шифрования протокола
@chapter Предпосылки
@section Борьба с DPI
С высокой вероятностью, на текущем нашем уровне понимания
проблемы, DPI будут распознавать протокол всё равно.
Соответственно, нет большой разницы, один - два или ноль
незашифрованных пакетов пройдёт.
Само наличие шифрования --- уже признак для DPI, могут
блокировать просто на основании принадлежности неизвестному
протоколу.
Размеры пакетов и характер обмена --- тоже признаки.
Saltine, возможно, оставляет сигнатуры в зашифрованных пакетах
(проверить).
Таким образом, борьба с DPI совершенно точно должна
осуществляться отдельным Messaging, может быть даже внешним.
Нашего текущего уровня понимания проблемы просто-таки
недостаточно для эффективной борьбы без радикального усложнения
себе жизни.
В дальнейшем, для протокола борьбы с DPI можно выделить даже
отдельные порты, где будут ожидаться другие протоколы.
Скорее всего, нужно будет просто написать транспорт поверх
WS/WSS, а внутренние датаграммы шифровать как обычно.
Таким образом, наверху будет TLS, а внизу --- наше шифрование
пакетов, при этом протокол будет принадлежать к "хорошо
известным протоколам". Можно даже палёные сертификаты
использовать, что бы успокоить службы (на верхнем уровне).
@section Различение шифрованных и нешифрованных пакетов
Возможны следующие способы:
@itemize @bullet
@item Номера портов
@item Сам протокол
@end itemize
@subsection Номера портов
Запускаем Messaging на отдельном порту, там воркер,
который занимается шифрованием и хэндшейком,
по мере расшифровки --- передает данные в указанный Messaging
(Proxy).
Плюсы:
@itemize @bullet
@item Не нужно заглядывать в пакеты: будет быстрее.
@item Не нужны префиксы в протоколе
@item Не будет интерференции с остальными протоколами
@item Обратная совместимость
@end itemize
Минусы:
@itemize @bullet
@item Доработка PEX
@item Доработка бутстрапа
@item Различение шифрованных и нешифрованных протоколов либо
хардкода портов
@item Устойчивые порты => легко блокировать
@end itemize
@subsection Сам протокол
Поскольку Messaing работает @strong{ДО}, мы можем в нём
распаковывать пакет, доставать сообщение и передавать в Peer уже
расшифрованное.
У нас есть два варианта:
Сообщение зашифрованное и незашифрованное.
Допустим, сообщение зашифрованное --- тогда это просто пакет в
формате, который создает libsodium.
Незашифрованное --- содержит префикс, например 0xdeadf1od и
дальше --- уже сам пакет.
При приеме сообщения мы отправляем этот префикс в начале пакета.
Если префикс есть --- то смотрим, что это за пакет.
Допустим, это хэндшейк. Тогда производим согласование ключей, и
дальше шлём зашифрованные пакеты.
Допустим, префикс будет только у пакетов хэндшейка, тогда
мы даже сохраним обратную совместимость --- неразобранные пакеты
будем отправлять по стеку дальше.
Минус устойчивой сигнатуры в определенном месте --- легко
блокировать простейшим фильтром.
Несмотря на то, что наши возможности бороться с DPI ограничены
слабым погружением в вопрос, настолько облегчать работу им мы не
хотим.
Можно в префикс встроить некий байткод, выполнение которого
приведёт к вычислению публичного ключа пира, которым можно,
в частности, идентифицировать самого пира.
Тогда это не будет создавать значительных проблем самому
пиру, но будет создавать проблемы при массовом анализе
протоколов. Можно еще и загадку встроить.
Тогда сам пакет будет выглядеть более или менее как мусор,
для анализа придётся построить интерпретатор, а так же встроить
интерпретацию в DPI, успехов им в борьбе.
Можно так же использовать какой-то вид вывода ключей из хорошо
известной, но динамически меняющейся информации.
А можно использовать всё вместе, расширяя этот байткод по мере
необходимости.
Всё это приведёт к большому усложнению и сторонних реализаций
протокола тоже, так что, для начала --- можно выбрать самый
простой способ распознать пакет хэндшейка.
Но пожалуй, я настою на том, что бы это был какой-то
динамический способ, что бы не зависеть от статических
сигнатур.
Вычисляться должно быстро, желательно
в один проход вперёд.
Плюсы:
@itemize @bullet
@item Меньше кода
@item Не меняется PEX
@item Нет устойчивых номеров портов
@item Можно навесить сверху любого протокола,
например, RPC
@end itemize
Минусы:
@itemize @bullet
@item Потенциально медленнее
@end itemize
@subsection Дизайн
Отдельный Proxy на каждый Messaging, с общим KeyStore.
@itemize @bullet
@item Пир сразу идентифицируется для всех протоколов
@item Можно надстроить над любым Messaing, в частности, над RPC,
который у нас пока без средств шифрования и аутентификации.
@end itemize
@verbatim
keys <- newKeyStore
proxy1 <- newProxyEnc keys (newMessaingUDP ...)
proxy2 <- newProxyEnc keys (newMessaingTCP ...)
proxy3 <- newProxyMessaging proxy1 (Just proxy2)
peer <- newPeer ... (Fabriq proxy3)
@end verbatim
Далее. Предусмотрим два режима: bypass и drop.
@subsection Режим bypass
Пробует провести handshake, если не удаётся ---
то оставляет эти попытки и просто пересылвает сообщения,
как есть на следующий уровень.
Хэндшейк делает сам. <<Чужие>> пакеты при этом пропускает
и наверх, и вниз.
Таким образом, остаётся обратная совместимость --- ведь
пакеты этой прокси просто будут дропнуты.
Так же можно пакеты слать культурно, в виде AnyMessage
с каким-то несуществующим типом протокола, который
отсутствует в каких-либо обработчиках --- тогда их
будет видно в логах. Возможно, это излишнее.
@subsection Режим drop
Пробует провести handshake, если не удаётся ---
то не делает ничего, либо повторяет их заданное число раз,
а потом не делает ничего.
Этот режим выбирают пиры, которые настаивают на защищенном
обмене.
@subsection Сообщения и FSM
Незашифрованное:
1. HEY(PREFIX,PKs,PKe,SIGN(PKs, PKe))
@table @asis
@item PKs:
ключ подписи пира
@item PKe:
публичный ключ шифрования сессии
@end table
Зашифрованное:
2. HEYOURSELF(PKe,BOX(PKe,Sid,SECRET))
@table @asis
@item PKe:
публичный ключ шифрования сессии
@item Sid:
Идентификатор ключа на нашей стороне,
должен быть в открытом виде в зашифрованном пакете
@end table
Пересылаем ключ шифрования, теперь пир может слать нам
зашифрованные сообщения.
Просто пытаемся расшифровать их своим секретом. Неудача ---
просто дропаем или пропускаем, в зависимости от политики.
Теперь, если пир продолжает слать незашифрованные
сообщения --- можем их или дропать, или продолжать
пропускать.
После получения HEYOURSELF мы должны пиру послать
наш секрет ключ тоже, так что должны видимо, в свою
очередь ответить HEY.
@verbatim
Peer1 Peer2
***** ****
| HEY |
|------------------>|
| HEYOURSELF |
|<------------------| Теперь Peer1 может слать Peer2
| | зашифрованные сообщения
| |
| HEY |
|<------------------| Теперь Peer2 может слать Peer1
| HEYOURSELF | зашифрованные сообщения
|------------------>|
| |
| |
@end verbatim
Заметим, что это хорошо ложится на систему обработки
подпротоколов в Peer: пары HEY/YOUSELF являются
независимыми, и, кажется, stateless, в том плане,
что нонсом тут явлется сам PKe.
Но! Если это вынести на уровень Peer, то потеряется
возможность навесить аутентификацию/шифрование на любой
Messaging.
Поэтому делаем на уровне Messaging.
Грубо говоря: получили HEYOURSELF --- обновили ключ.
Исключительная ситуация:
Пир потерял наш SECRET и об этом не знает.
Это может случиться только при рестарте, если мы не сохраняем
ключ.
Но это не может случиться, так как, если у ProxyEnc нет ключа
--- то она посылает HEY.
Замечание: ProxyEnc является пассивной, она ничего не знает про
пиров, PEX и сама не инициирует общение, так как не знает, с кем
общаться.
Поэтому, в режиме Bypass --- просто пропускает сообщения, как
есть, в обе стороны, не препятствуя. Как только удалось
согласовать ключ --- то есть, получить HEYOURSELF --- то
включаем шифрование.
Если пинг прислал нам HEYOURSELF, а сам незашифрованные
сообщения шлёт -- ну дурак, чо. Надо проинформировать об этом
в лог, может забанить его.
В режиме Drop -- ставит сообщения в очередь, пока не появился
ключ.
Как только ключ появился --- шифрует и отправляет.
Пиры авторизуются друг у друга при помощи протокола Ping.
Никаких других ходить не должно до этого, это в принципе ошибка.
Соответственно, пока пиры не авторизовались --- никакого обмена
нет.
Даже если мы этот пинг дропнем --- пир нас пинганёт еще раз.
@strong{Про Sid} Так как один и тот же пир может быть под разными
адресами одновременно, а PeerNonce и прочее --- на этом уровне протокола
недоступно --- будем генерировать уникальный ключ для каждого пира по
адресу, и в HEYOURSELF пересылать Sid ключа. Пир будет сохранять пару
(Sid, SECRET) --- и в ответе будет брать нужный ключ.
Проблема, если мы пишем пиру на один адрес (каким нам ключом шифровать?),
а он отвечает нам с другого адреса.
PeerNonce недоступен, что делать?
Кейс:
Peer1: Peer2:
tcp:1.1.1.1:8957 -> tcp:1.1.10.5:8001
<- tcp:1.1.10.5:62511
В HEYO(SELF) посылаем ему ID ключа, он где-то (где?) запоминает,
что нам надо слать c таким ID ключа.
@bye