237 lines
21 KiB
Markdown
237 lines
21 KiB
Markdown
# Рукопожатие
|
||
|
||
На *рукопожатие* возложена задача создать между *клиентом* и *сервером* новый *поток* на взаимно согласованных условиях. Это включает в себя определение готовности коммуникации по *протоколу*, согласование версии *протокола* в пределах *сессии* (если создаётся новая), а также согласование различных параметров *потока*. *Рукопожатие* может быть "открытое", то есть в котором тело пакета представлено в нешифрованном виде и которое ведёт к созданию нешифрованного *потока*, и "шифрованное", в котором тело пакета зашифровано, подписано, и которое ведёт к созданию шифрованного *потока*. При выполнении этой процедуры используются уникальные форматы пакетов, несоответствующие формальному определению *события*. Формат пакетов использующийся в *рукопожатии* представлен ниже.
|
||
|
||
Существуют следующие типы *рукопожатия*:
|
||
|
||
- Первый тип: то, что создаёт новый *поток* и одновременно новую *сессию*
|
||
- Второй тип: то, что создаёт новый *поток* в рамках существующей *сессии*
|
||
- Третий тип: то, что восстанавливает существующий *поток* из десинхронизированного состояния в рабочее
|
||
|
||
*Рукопожатие* любого типа выполняется ровно в два действия:
|
||
|
||
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 случайных байт, полученные *сервером* с помощью криптостойкого генератора случайных чисел. <!--TODO: вероятно, что стоит перенести это и пункт ниже в соответствующие разделы про поток и сессию-->
|
||
|
||
### 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 |
|
||
----------------------------------------------------------------------------------------------------
|
||
```
|
||
|
||
<!--TODO: параметры SID'ов можно свести к четырём пунктам, но это повышает вероятность ошибки при формализации и реализации-->
|
||
|
||
|
||
|
||
## 3.3. Запрос
|
||
|
||
*Клиент* может отправлять запрос *рукопожатия* второго типа только при условии существования *сессии* между ним и *сервером*, а третьего типа - лишь при условии, что в теле запроса указан существующий *поток* в десинхронизированном состоянии.
|
||
|
||
В теле любого пакета-запроса *рукопожатия* должны быть указаны NoiseParameters и может быть указано ExtensionsEnum, содержащее перечень поддерживаемых *клиентом* расширений *протокола*. В теле пакета-запроса *рукопожатия* первого типа должна быть ProtocolVersion, в формате, указанном в разделе 2.2.. В теле пакета-запроса *рукопожатия* второго типа должен содержаться SessionSecret, к которой относится создаваемый *поток*. В теле пакета-запроса *рукопожатия* третьего типа должен быть указан StreamSecret, состояние которого предполагается восстанавливать.
|
||
|
||
В шифрованном *рукопожатии*, тело запроса шифруется с помощью заведомо-известного *клиенту* публичного ключа *сервера*. <!--TODO: тут и вообще везде применить ту новую терминологию на тему криптографии--> Задаваемые криптографические алгоритмы и ключи, передаваемые *клиентом* в шифрованном запросе *рукопожатия* - распространяются исключительно на создаваемый или восстанавливаемый *поток*.
|
||
|
||
В запросе шифрованного *рукопожатия* любого типа должны быть следующие параметры:
|
||
|
||
- 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
|
||
|
||
<!--TODO: вот тут тоже переделать с новой терминологией-->
|