stadium-proto/Рукопожатие.md

237 lines
21 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Рукопожатие
На *рукопожатие* возложена задача создать между *клиентом* и *сервером* новый *поток* на взаимно согласованных условиях. Это включает в себя определение готовности коммуникации по *протоколу*, согласование версии *протокола* в пределах *сессии* (если создаётся новая), а также согласование различных параметров *потока*. *Рукопожатие* может быть "открытое", то есть в котором тело пакета представлено в нешифрованном виде и которое ведёт к созданию нешифрованного *потока*, и "шифрованное", в котором тело пакета зашифровано, подписано, и которое ведёт к созданию шифрованного *потока*. При выполнении этой процедуры используются уникальные форматы пакетов, несоответствующие формальному определению *события*. Формат пакетов использующийся в *рукопожатии* представлен ниже.
Существуют следующие типы *рукопожатия*:
- Первый тип: то, что создаёт новый *поток* и одновременно новую *сессию*
- Второй тип: то, что создаёт новый *поток* в рамках существующей *сессии*
- Третий тип: то, что восстанавливает существующий *поток* из десинхронизированного состояния в рабочее
*Рукопожатие* любого типа выполняется ровно в два действия:
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: вот тут тоже переделать с новой терминологией-->