21 KiB
Рукопожатие
На рукопожатие возложена задача создать между клиентом и сервером новый поток на взаимно согласованных условиях. Это включает в себя определение готовности коммуникации по протоколу, согласование версии протокола в пределах сессии (если создаётся новая), а также согласование различных параметров потока. Рукопожатие может быть "открытое", то есть в котором тело пакета представлено в нешифрованном виде и которое ведёт к созданию нешифрованного потока, и "шифрованное", в котором тело пакета зашифровано, подписано, и которое ведёт к созданию шифрованного потока. При выполнении этой процедуры используются уникальные форматы пакетов, несоответствующие формальному определению события. Формат пакетов использующийся в рукопожатии представлен ниже.
Существуют следующие типы рукопожатия:
- Первый тип: то, что создаёт новый поток и одновременно новую сессию
- Второй тип: то, что создаёт новый поток в рамках существующей сессии
- Третий тип: то, что восстанавливает существующий поток из десинхронизированного состояния в рабочее
Рукопожатие любого типа выполняется ровно в два действия:
- Клиент запрашивает рукопожатие с избранными параметрами
- Сервер отвечает клиенту либо своей частью параметров, либо ошибкой
Пакет рукопожатия состоит из заголовка и тела. В заголовок входят магическое число протокола и флаг шифрования. Тело представляет из себя данные сериализованные в формат LBM. Соответствие ключей ячеек LBM и их содержимого приведено ниже.
Флаг шифрования является восьмибитным целым числом и может принимать одно из двух значений:
0x00
- шифрования нет.0x80
- шифрование включено.
Формат пакета рукопожатия следующий:
[Magic number: 8 bytes][Encryption flag: 1 byte][Handshake body: non-fixed bytes]
Базовые параметры
Базовые параметры, использующиеся в рукопожатии и их соответствие ключам ячеек LBM. Обратите внимание, что представленные здесь ключи ячеек не имеют отношения к зарезервированным, которые указаны в разделе 9.
----------------------------------------------------------------------------------------
| Name | Key | Description |
|------------------|------|------------------------------------------------------------|
| NoiseParameters | 0x01 | Параметры шума в потоке |
| ProtocolVersion | 0x02 | Версия протокола, использующаяся в сессии |
| StreamSecret | 0x03 | Секрет потока |
| SessionSecret | 0x04 | Секрет сессии |
| MaxPayloadSize | 0x05 | Максимальный размер полезной нагрузки |
| ExtensionsEnum | 0x06 | Перечисление расширений |
| ErrorCode | 0x20 | Код ошибки сервера |
| ErrorDescription | 0x21 | Детальное описание ошибки |
----------------------------------------------------------------------------------------
NoiseParameters
Является однобайтовым битовым полем, описывает параметры шума в потоке. Структура поля выглядит примерно так:
[3 bits][2 bits][2 bits][1 bit (reserved)]
Первые три бита выделены под настройку количества шумовых событий, допустимые значения:
0b000
: отсутствие шумовых событий0b001
: соотношение шумовых событий к реальным 1:80b010
: соотношение 1:40b011
: 1:20b100
: 1:10b101
: 2:10b110
: 4:10b111
: зарезервировано
Следующие два бита выделены под настройку размера шумовых событий:
0b00
: случайный размер, в пределах от размера минимального события с нулевой полезной нагрузкой, до MaxPayloadSize0b01
: случайный размер, на основе среднего размера передаваемого события +- 25% (но всегда меньше максимального)0b10
: случайный размер, в пределах от минимального, до MaxPayloadSize / 20b11
: зарезервировано
Ещё два бита выделены под настройку размера шумовых данных в обычных событиях:
0b00
: без шумовых данных0b01
: заполнение шумовыми данными до лимита полезной нагрузки0b10
: заполнение шумовыми данными в случайном количестве, от нуля, до лимита полезной нагрузки0b11
: зарезервировано
Последний бит зарезервирован.
StreamSecret
Секрет потока представляет из себя 16 случайных байт, полученные сервером с помощью криптостойкого генератора случайных чисел.
SessionSecret
Секрет сессии представляет из себя 32 случайных байта, полученные сервером с помощью криптостойкого генератора случайных чисел.
MaxPayloadSize
Целое число без знака размером 1, 2, 4 или 8 байт, означающее максимальный размер полезной нагрузки.
ExtensionsEnum
Перечисление расширений протокола является массивом нефиксированного общего размера и переменным размером отдельной ячейки. В каждой ячейке содержится корректный дескриптор расширения протокола (SPXD).
ErrorCode
Код ошибки сервера представляет из себя восьмибитное число без знака. Не может являться нулём. Перечень допустимых значений и ассоциированных с ними названий:
0x01
, FailedToParseBody: не удалось корректно разобрать тело пакета0x02
, InsufficientParams: недостаточно параметров для выполнения рукопожатия избранного типа0x11
, WrongCryptoAlgo: криптографический алгоритм не указан или не существует0x12
, UnsupportedCryptoAlgo: указанный криптоалгоритм известен серверу, но отключен или не имплементирован0x13
, WrongCryptoValue: ключ или иные криптографические данные не указаны, имеют ложный формат или не соответствуют приведённому алгоритму0x14
, FailedToDecryptBody: не удалось расшифровать тело пакета0x21
, InvalidStreamSecret: указанный секрет потока не существует0x22
, InvalidSessionSecret: указанный секрет сессии не существует0x23
, UnsupportedProtocolVersion: указанная версия протокола не поддерживается0x24
, WrongNoiseParams: параметры шума не указаны, имеют ложный формат или отвергнуты сервером
ErrorDescription
Строка в кодировке ASCII с текстовым описанием ошибки, без завершающего нулевого байта.
Криптографические параметры
Тоже самое, что и базовые параметры, только связанное с криптографией.
----------------------------------------------------------------------------------------------------
| Name | Key | Type | Description |
|------------------|------|---------------------|--------------------------------------------------|
| PayloadHashAlgo | 0x40 | HashCryptoPack | Способ хэширования события | # Event signing
| EvSymCiphAlgo | 0x41 | SymCiphCryptoPack | Способ шифрования события | # Event encryption
| EvKDAlgo | 0x42 | KDCryptoPack | Способ генерации ключей шифрования для событий |
| EvKDInitValue | 0x43 | KDInitValue | Значение для инициализации генератора ключей |
| EvKDPrivConst | 0x44 | KDPrivConst | Константа для использования генератором ключей |
| ServerSigAlgo | 0x51 | AsymCiphCryptoDesc | Дескриптор алгоритма подписи сервера | # Server-side event signing
| ServerSigPubKey | 0x52 | | Публичный ключ подписи сервера |
| ClientSigAlgo | 0x55 | AsymCiphCryptoDesc | Дескриптор алгоритма подписи клиента | # Client-side event signing
| ClientSigPubKey | 0x56 | | Публичный ключ подписи клиента |
| SsidKDAlgo | 0x61 | KDCryptoPack | Способ генерации SSID | # Server-side SID setup
| SsidKDInitValue | 0x62 | KDInitValue | Значение для инициализации генератора SSID |
| SsidKDPrivConst | 0x63 | KDPrivConst | Константа для использования генератором SSID |
| CsidKDAlgo | 0x65 | KDCryptoPack | Способ генерации CSID | # Client-side SID setup
| CsidKDInitValue | 0x66 | KDInitValue | Значение для инициализации генератора CSID |
| CsidKDPrivConst | 0x67 | KDPrivConst | Константа для использования генератором CSID |
----------------------------------------------------------------------------------------------------
3.3. Запрос
Клиент может отправлять запрос рукопожатия второго типа только при условии существования сессии между ним и сервером, а третьего типа - лишь при условии, что в теле запроса указан существующий поток в десинхронизированном состоянии.
В теле любого пакета-запроса рукопожатия должны быть указаны NoiseParameters и может быть указано ExtensionsEnum, содержащее перечень поддерживаемых клиентом расширений протокола. В теле пакета-запроса рукопожатия первого типа должна быть ProtocolVersion, в формате, указанном в разделе 2.2.. В теле пакета-запроса рукопожатия второго типа должен содержаться SessionSecret, к которой относится создаваемый поток. В теле пакета-запроса рукопожатия третьего типа должен быть указан StreamSecret, состояние которого предполагается восстанавливать.
В шифрованном рукопожатии, тело запроса шифруется с помощью заведомо-известного клиенту публичного ключа сервера. Задаваемые криптографические алгоритмы и ключи, передаваемые клиентом в шифрованном запросе рукопожатия - распространяются исключительно на создаваемый или восстанавливаемый поток.
В запросе шифрованного рукопожатия любого типа должны быть следующие параметры:
- EvKDInitValue
- SsidKDAlgo
- SsidKDPrivConst
- CsidKDInitValue
В запросах шифрованного рукопожатия первого и второго типов должны быть следующие параметры:
- PayloadHashAlgo
- EvSymCiphAlgo
- EvKDAlgo
- EvKDPrivConst
- ClientSigAlgo
- ClientSigPubKey
Также в рукопожатии первого и второго типов клиент может опционально добавить MaxPayloadSize.
3.4. Ответ
Сервер должен отвечать только в случае, если полученные данные имеют корректную структуру, в них присутствует магическое число протокола и корректное значение флага шифрования.
Если сервер принимает запрос клиента и отвечает своей частью параметров, то флаг шифрования должен быть установлен в то-же значение, что и в запросе клиента и, соответственно флагу, ответ должен быть либо "открытым", либо "шифрованным". Если сервер отвечает ошибкой, то, по умолчанию, предполагается "открытый" ответ, за исключением особо-оговорённых случаев.
При ответе клиенту ошибкой, сервер может, но не обязан, добавлять в ответ её описание (ErrorDescription). Клиент может, но не обязан, обработать это описание наиболее подходящим образом (например - вывести на экран).
Сервер определяет тип рукопожатия по запросу, последовательно выполняя следующие шаги:
- Проверить, есть-ли в теле запроса секрет сессии (SessionSecret). Если да, то это рукопожатие второго типа, создание нового потока.
- Проверить, указана-ли в теле версия протокола (ProtocolVersion). Если да, то это рукопожатие первого типа, создание новой сессии.
- Если проверки на прошлых шагах провалились, то полученный запрос интерпретируется как рукопожатие третьего типа, восстановление состояния потока.
Сервер отвечает ошибкой...
- ...FailedToDecryptBody, если проваливает попытку расшифровать тело запроса шифрованного рукопожатия.
- ...FailedToParseBody, если проваливает попытку корректно разобрать тело запроса рукопожатия.
- ...InsufficientParams, если обнаруживает:
- Отсутствие StreamSecret в рукопожатии третьего типа
- Наличие хотя бы одного криптографического параметра, но при этом в целом криптопараметров недостаточно для создания потока
- ...UnsupportedProtocolVersion, если не поддерживает запрошенную версию протокола.
- ...InvalidStreamSecret, если указанный StreamSecret не существует.
- ...InvalidSessionSecret, если указанный SessionSecret не существует.
- ...UnsupportedCryptoAlgo, если знает, но не поддерживает хотя бы один из параметров:
- PayloadHashAlgo
- EvSymCiphAlgo
- EvKDAlgo
- ClientSigAlgo
- SsidKDAlgo
- ...WrongCryptoAlgo, если обнаруживает недопустимые/неизвестные значения в хотя бы одном из параметров:
- PayloadHashAlgo
- EvSymCiphAlgo
- EvKDAlgo
- ClientSigAlgo
- SsidKDAlgo
- ...WrongCryptoValue, если обнаруживает отсутствие или несоответствие избранным криптографическим параметрам хотя бы одного из параметров:
- EvKDInitValue
- EvKDPrivConst
- CsidKDInitValue
- SsidKDPrivConst
- ClientSigPubKey
Если после произведения сервером всех проверок не обнаружилось каких-либо проблем, то он должен ответить своей частью параметров. Если в рукопожатии любого типа клиент предоставил ExtensionsEnum, то сервер обязан в ответе предоставить ExtensionsEnum с перечислением поддерживаемых им расширений протокола. Если в запросе рукопожатия содержался параметр MaxPayloadSize, то ответ сервера должен содержать MaxPayloadSize со значением равным значению из запроса или меньше. Некорректные значения MaxPayloadSize в запросе - игнорируются.
В рукопожатии первого и второго типа в ответе сервера должны содержаться MaxPayloadSize и StreamSecret. В ответе на рукопожатие первого типа должен содержаться параметр SessionSecret.
В ответе шифрованного рукопожатия любого типа должны быть параметры:
- SsidKDInitValue
- CsidKDAlgo
- CsidKDPrivConst
В ответе шифрованного рукопожатия первого типа должны быть параметры:
- ServerSigAlgo
- ServerSigPubKey