11 KiB
Спецификация Marafon Protocol (MFP) v1.0
Marafon Protocol это протокол для одноимённого полнофункционального мессенджера, работающего поверх TCP (в будущем будет добавлена поддержка UDP и QUIC). Основной фокус при работе над оным идёт на:
- Снижение оверхэдов, по сравнению с классическими решениями (Matrix, Discord, WhatsApp, пр.).
- Устойчивость к цензуре.
- Федеративность.
- Поддержка сквозного шифрования.
- Совместимость со всеми мажорными оверлейными сетями (Tor, I2P и yggdrasil).
- Расширяемость.
Поведение сервера и клиента
Сервер обязан:
- Оповещать клиент и сервера в федерации о всех ошибках, кроме:
- Связанных с безопасностью
- Связанных с внутренними неполадками
- Отдавать предпочтение данным других серверов, нежели клиентов.
- Отдавать предпочтение сетевым настройкам входящих соединений, нежели локальным (не считая лимиты).
Клиент обязан:
- Хранить тишину о всех ошибках.
- Явно уведомлять юзера при возникновении проблем с безопасностью, как минимум по умолчанию.
События, пакеты и их структура
Существует три уровня категорий событий:
- Надкатегория: Client2Server, Server2Client, Server2Server.
- Категория: Authentication, User, Message, т.д.
- Подкатегория: Login, Create, Delete, т.д.
Надкатегория никак не указывается в пакете, зависит от контекста и при упоминании - чаще всего опускается. Она также определяет структуру некоторых событий, которая может меняться в конкретных случаях. Остальные две занимают по одному байту соответственно.
Все категории вместе являются типом события. Значение типа - это число, которое указывается в начале пакета.
События всех категорий, кроме явно оговорённых или принадлежащих к надкатегории Server2Client, содержат идентификатор серверной сессии, являющийся четырёхбайтным числом без знака (uint64_t
).
События всех категорий из надкатегории Client2Server, кроме явно оговорённых, содержат хэш полезной нагрузки, зашифрованный с помощью закрытого ключа подписи клиента.
Данные (AKA "полезная нагрузка") являются расположенными последовательно парами "ключ-значение" и могут быть расположены в пакете относительно друг-друга в произвольном порядке. Одна пара имеет следующий формат:
[1b: key][2b: data length][>0 b: data]
Все пакеты размером ниже установленного дополняются псевдослучайной последовательностью байт. (???)
Исходя из всего вышеописанного, итоговая примерная структура пакета выглядит следующим образом:
[1b: category][1b: subcategory][4b: server session][?b: payload hash][>0b: payload][~b: random bytes]
Размер пакета определяется запросом клиента на этапе обмена характеристиками, но не может быть больше, чем 65535 байт или меньше 128 байт. До этого этапа (включительно) размер пакета составляет 512 байт.
Магическим числом протокола является следующая последовательность байт:
HEX: 0x4d 0x61 0x72 0x61 0x66 0x6f 0x6e 0x50
DEC: 77 97 114 97 102 111 110 80
Что соответствует строке "MarafonP" в кодировке ASCII.
Соединение, аутентификация и сессии
Первичное подключение к серверу может выполнятся разными способами, в том числе подразумевающими маскировку траффика под существующие протоколы, но при использовании вне локальных сетей - должно сводиться к установке защищённого соединения. Данная версия протокола подразумевает поддержку пока лишь двух способов подключения: напрямую (без шифрования) и с использованием TLS-over-TCP.
Рукопожатие
После успешной установки защищённого соединения, происходит обмен характеристиками, AKA "рукопожатие". Клиент отправляет событие типа Handshake.Request
, на что сервер отвечает либо ошибкой, либо событием типа Handshake.Accepted
.
Сервер отвечает ошибкой Error.HandshakeFailed
и разрывает соединение, если:
- При обмене данными клиент или сервер обнаруживают несоответствие магических чисел.
- Сервер не поддерживает указанную клиентом версию протокола.
- Невозможно выделить новый порт для подключения.
- Указанный транспортный протокол не поддерживается.
- Размер пакета не попадает в рамки жёсткого лимита сервера.
Если при хэндшейке клиентом был указан новый способ подключения и получен валидный ответ от сервера - то они обязаны разорвать текущее соединение, затем, спустя случайное количество времени, от 50 мс до 500 мс, создать новое.
Регистрация
При успешной регистрации учётной записи, клиент генерирует пару ключей (открытый-закрытый) в качестве подписи, после чего открытый ключ отправляется серверу и тот сохраняет его.
Вход в учётную запись
Вход в учётную запись происходит путём отправки логина и хэша пароля на сервер. При этом клиент может указать ID серверной сессии, если произошло непредвиденное отключение и ему необходимо переподключиться.
Если логин или пароль несовпадают - сервер отвечает ошибкой и разрывает соединение. Если полезная нагрузка подписана ложно - сервер отвечает ошибкой, соединение разрывается, а администратор сервера и владелец учётной записи уведомляются об инциденте.
Если ID серверной сессии нет в списке недавно-разорванных - сервер отвечает ошибкой и разрывает соединение.
При успешном входе в учётную запись, сервер генерирует uint32_t
, являющийся идентификатором серверной сессии. Клиент обязан хранить его и прикреплять к каждому пакету (кроме особо-оговорённых случаев) до момента отключения от сервера.
Пользовательские сессии и подпись
Каждый принятый сервером пакет, содержащий хэш полезной нагрузки, должен проверяться на соответствие подписи. Если сервер обнаруживает, что подпись неверна - сервер отвечает ошибкой, соединение разрывается, а администратор сервера и владелец учётной записи уведомляются об инциденте.
Каждый принятый клиентом пакет, содержащий хэш полезной нагрузки, может быть проверен на соответствие подписи. Если клиент обнаруживает, что подпись неверна - он должен оповестить пользователя.
Полезная нагрузка пакета может быть проверена на целостность сервером или клиентом с использованием подписанного хеша, но данная проверка может быть отключена, по усмотрению владельца сервера или пользователя клиента.
Если при проверке ID серверной сессии обнаруживается несоответствие - сервер отвечает ошибкой, соединение разрывается.