\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