ajkj2em писал(а):
за счет того что hash_1 и hash_2 имеют одинаковую структуру,
man in the middle может заставить клиента посчитать hash для
произвольного challenge, что можно использовать для того, чтобы
посчитать клиентский authentication hash в другой сессии ..
Не так просто. Клиент не будет отвечать серверу, если сервер не пройдёт проверку подлинности. Сервер проверяется первым, п.3.
7) Если hash_2_srv == hash_2, то сервер, считает, что это "наш" клиент, и ждёт получения "полезной" команды от клиента , которую и выполняет. Затем сервер разрывает соединение.
.. и man in the middle аккуратно подменяет команду в реквесте на нужную
Пофиксено, в новой версии, см. ниже (вернее вернулся к той версии которая была в самом начале, ещё более упрощённой в сравнении с тем, что ниже)
tcp stream должен быть пакетизирован,
все пакеты должны быть пронумерованы и "подписаны",
Да это сделано. Протокол прехедед. Последовательность пакетов строго учитывается. В тпц пакуется прехедед "транспортный" кастом протокол прикладного уровня. Там команды, длины и офсеты, контрольная сумма передаваемых данных + .... В него пакуется прехедед протокол проверки подлинности, там тоже длины и оффсеты + данные. Может показаться навороченным (протокол лейр в протокол лейр), но на самом деле очень гибко. Я использовал это давно для других целей. Так, что переписывать пришлось почти ничего.
не надо изобретать велосипед
Согласен. Хотя, есть одно маленькое "но". В данном протоколе полностью отсутствуют криптографические методы. Всё открыто. Ничего не шифруется. В некоторых обстоятельствах может оказаться большим плюсом.
======
Вот немного упростил... Как и было ранее, сервер может определить из БД сохранённый hash (userpasswod) hPwd для username. Клиент его вычисляет hPwd.
1) Клиент посылает:
- username (как есть);
- challenge_1 (случайная последовательность байт)
Клиент хочет первым проверить подлинность сервера, чтоб сервер ответил на clallenge_1 значением hash_1 = hash ( clallenge_1 + hPwd ). Если клиент не получит такого результата, то соединение будет им завершено.
2) Сервер получив данные username и challenge_1 выполняет следующее:
- вычисляет "ответ" hash_1 = hash( challenge_1 + hPwd ) на challenge_1 клиента;
- создаёт для клиента свой challenge_2 (случайная последовательность байт);
- посылает клиенту ответ на его challenge_1 в виде hash_1, и при этом посылает свой challenge_2;
Сервер делает две вещи. Он отвечает клиенту на clallenge_1 значением hash_1. Одновременно он просит клиента, в свою очередь, подтвердить подлинность ответом на challenge_2.
Сервер хочет, чтоб клиент ответил ему на clallenge_2 значением hash_2 = hash ( clallenge_2 + hPwd ). Если он не получит такого результата, то соединение будет завершено сервером.
3) Клиент проверяет подлинность сервера. Он сверяет полученный от сервера hash_1 со значением, рассчитываемым по challenge_1 и userpassword. Если ожидания клиента по п.1. не выполнены, т.е. если hash_1 != hash (challenge_1 + hPwd), то это не "наш" сервер. Тогда заканчиваем. Иначе клиент готовит “полезную команду”, п.4.
4) Клиент готовит и отправляет пакет серверу:
- вычисляет hash_2 = hash (challenge_2 + hPwd) (это удостоверение подлинности клиента для сервера);
- включает в пакет несекретные коды команд серверу, который сервер будет выполнять (полезные команды).
- вычисляет контрольную сумму, которую включает в заголовок пакета. Контрольная сумма рассчитывается по значению последовательности байт всего пакета (заголовков и команд) + hash_1 + hPwd, которые потом отбрасываются перед отправкой.
5) Сервер сравнивает полученный hash_2 с вычисляемым значением hash_2_srv = hash( challenge_2 + hPwd ) . Это будет служить проверкой подлинности клиента. Если hash_2_srv == hash_2, то сервер, считает, что это "наш" клиент. Иначе сервер закрывает соединение. Далее от проверяет контрольную сумму, добавляя к полученному пакету сохраненное с начала соединения значение hash_1, и hPwd. Если CRC OK, выполяется команда клиента. CRC должна гарантировать целостность данных.
Спасибо, парни.