# Рукопожатие На *рукопожатие* возложена задача создать между *клиентом* и *сервером* новый *поток* на взаимно согласованных условиях. Это включает в себя определение готовности коммуникации по *протоколу*, согласование версии *протокола* в пределах *сессии* (если создаётся новая), а также согласование различных параметров *потока*. *Рукопожатие* может быть "открытое", то есть в котором тело пакета представлено в нешифрованном виде и которое ведёт к созданию нешифрованного *потока*, и "шифрованное", в котором тело пакета зашифровано, подписано, и которое ведёт к созданию шифрованного *потока*. При выполнении этой процедуры используются уникальные форматы пакетов, несоответствующие формальному определению *события*. Формат пакетов использующийся в *рукопожатии* представлен ниже. Существуют следующие типы *рукопожатия*: - Первый тип: то, что создаёт новый *поток* и одновременно новую *сессию* - Второй тип: то, что создаёт новый *поток* в рамках существующей *сессии* - Третий тип: то, что восстанавливает существующий *поток* из десинхронизированного состояния в рабочее *Рукопожатие* любого типа выполняется ровно в два действия: 1. *Клиент* запрашивает *рукопожатие* с избранными параметрами 2. *Сервер* отвечает *клиенту* либо своей частью параметров, либо ошибкой Пакет *рукопожатия* состоит из заголовка и тела. В заголовок входят магическое число протокола и флаг шифрования. Тело представляет из себя данные сериализованные в формат LBM. Соответствие ключей ячеек LBM и их содержимого приведено ниже. Флаг шифрования является восьмибитным целым числом и может принимать одно из двух значений: 1. `0x00` - шифрования нет. 2. `0x80` - шифрование включено. Формат пакета *рукопожатия* следующий: ```text [Magic number: 8 bytes][Encryption flag: 1 byte][Handshake body: non-fixed bytes] ``` ## Базовые параметры Базовые параметры, использующиеся в *рукопожатии* и их соответствие ключам ячеек LBM. Обратите внимание, что представленные здесь ключи ячеек не имеют отношения к зарезервированным, которые указаны в разделе 9. ```text ---------------------------------------------------------------------------------------- | 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:8 - `0b010`: соотношение 1:4 - `0b011`: 1:2 - `0b100`: 1:1 - `0b101`: 2:1 - `0b110`: 4:1 - `0b111`: зарезервировано Следующие два бита выделены под настройку размера шумовых *событий*: - `0b00`: случайный размер, в пределах от размера минимального *события* с нулевой *полезной нагрузкой*, до MaxPayloadSize - `0b01`: случайный размер, на основе среднего размера передаваемого *события* +- 25% (но всегда меньше максимального) - `0b10`: случайный размер, в пределах от минимального, до MaxPayloadSize / 2 - `0b11`: зарезервировано Ещё два бита выделены под настройку размера шумовых данных в обычных *событиях*: - `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 с текстовым описанием ошибки, без завершающего нулевого байта. ## Криптографические параметры Тоже самое, что и базовые параметры, только связанное с криптографией. ```text ---------------------------------------------------------------------------------------------------- | 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). *Клиент* может, но не обязан, обработать это описание наиболее подходящим образом (например - вывести на экран). *Сервер* определяет тип *рукопожатия* по запросу, последовательно выполняя следующие шаги: 1. Проверить, есть-ли в теле запроса секрет *сессии* (SessionSecret). Если да, то это *рукопожатие* второго типа, создание нового *потока*. 2. Проверить, указана-ли в теле версия *протокола* (ProtocolVersion). Если да, то это *рукопожатие* первого типа, создание новой *сессии*. 3. Если проверки на прошлых шагах провалились, то полученный запрос интерпретируется как *рукопожатие* третьего типа, восстановление состояния *потока*. *Сервер* отвечает ошибкой... - ...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