Ключевое слово в защите информации
КЛЮЧЕВОЕ СЛОВО
в защите информации
Получить ГОСТ TLS-сертификат для домена (SSL-сертификат)
Добро пожаловать, Гость! Чтобы использовать все возможности Вход или Регистрация.

Уведомление

Icon
Error

Опции
К последнему сообщению К первому непрочитанному
Offline urbrato  
#1 Оставлено : 6 июля 2017 г. 21:51:53(UTC)
urbrato

Статус: Участник

Группы: Участники
Зарегистрирован: 04.11.2016(UTC)
Сообщений: 15
Российская Федерация
Откуда: Москва

Сказал(а) «Спасибо»: 7 раз
Добрый день! Вопрос по поводу передачи сессионного ключа.

1. На машинах создаётся ключ обмена.
2. На одной машине создаётся сессионный ключ. Алгоритм следующий (проверки кодов ошибок, логирование и пр. не пишу, но они есть).
2.1. Создаём ключ через CryptGenKey с флагом CRYPT_EXPORTABLE.
2.2. Экспортируем его через CryptExportKey, используя ключ обмена, с флагом SIMPLEBLOB (вызываем дважды - узнать размер и получить блоб).
2.3. Подписываем его своим сертификатом через CryptSignMessage.
2.4. Старый ключ, если он был, уничтожаем через CryptDestroyKey.
3. Полученный пакет пересылаем на другую машину.
4. На второй машине принимаем сессионный ключ.
4.1. Проверяем подпись через CryptVerifyMessageSignature.
4.2. Если на принимавшей стороне существовал сессионный ключ, уничтожаем его через CryptDestroyKey.
4.3. Импортируем новый ключ через CryptImportKey, используя ключ обмена.

Наблюдается странность. Допустим, у нас машины А и Б. Если машина А создала сессионный ключ и передала его машине Б, всё проходит нормально. Но если потом машина Б создала сессионный ключ и передала его машине А, на машине А при вызове CryptImportKey возвращается NTE_BAD_DATA.
Сертификаты действующие на обеих машинах.
К сожалению, процесс обмена ключами невозможно сделать односторонним (протокол обмена, который мы не можем изменить, подразумевает, что передача данных на нашем уровне стека односторонняя, но в определённый момент меняющая направление). Если машины А и Б поменять местами, остаётся ситуация, что та машина, которая первой принимала ключ, принимает его нормально, а когда при смене направления передачи данных передаётся новый ключ, он при попытке импортировать даёт bad data. Отказаться от перегенерации ключей мы также не можем ввиду ограничения нагрузки на сессионный ключ.

Где может быть проблема?
Спасибо!
Offline Русев Андрей  
#2 Оставлено : 10 июля 2017 г. 11:21:17(UTC)
Русев Андрей

Статус: Сотрудник

Группы: Администраторы, Участники
Зарегистрирован: 16.04.2008(UTC)
Сообщений: 1,261

Сказал(а) «Спасибо»: 21 раз
Поблагодарили: 444 раз в 323 постах
Здравствуйте.
Передача ключей не имеет направленности. Вероятно, в вашем коде ошибка, возможно, логическая.
Процесс передачи ключей начинается с выработки общего секрета по алгоритму Диффи-Хеллмана.
На общем ключе шифруется симметричый ключ. Правильный пример смотрите в нашем SDK:
CSP/ExportingSessionKey/ExportingSessionKey.c
Подписывать ключи не нужно: вместо этого открытый ключ другой стороны надо брать из её сертификата, предварительно проверив его на отзыв.
Официальная техподдержка. Официальная база знаний.
thanks 1 пользователь поблагодарил Русев Андрей за этот пост.
urbrato оставлено 10.07.2017(UTC)
Offline urbrato  
#3 Оставлено : 10 июля 2017 г. 15:15:41(UTC)
urbrato

Статус: Участник

Группы: Участники
Зарегистрирован: 04.11.2016(UTC)
Сообщений: 15
Российская Федерация
Откуда: Москва

Сказал(а) «Спасибо»: 7 раз
К сожалению, я не первый разработчик проекта, поэтому полный SDK с примерами утерян. Какой мне следует взять? КриптоПро OCSP SDK подойдёт?
Offline urbrato  
#4 Оставлено : 10 июля 2017 г. 15:26:41(UTC)
urbrato

Статус: Участник

Группы: Участники
Зарегистрирован: 04.11.2016(UTC)
Сообщений: 15
Российская Федерация
Откуда: Москва

Сказал(а) «Спасибо»: 7 раз
Ввиду того, что тема "горячая", должен задать ещё вопрос. Возможна ли поддержка в виде совместного поиска логической ошибки в коде? Если да, какова цена вопроса?
Offline Максим Коллегин  
#5 Оставлено : 10 июля 2017 г. 15:28:20(UTC)
Максим Коллегин

Статус: Сотрудник

Группы: Администраторы
Зарегистрирован: 12.12.2007(UTC)
Сообщений: 6,375
Мужчина
Откуда: КРИПТО-ПРО

Сказал «Спасибо»: 32 раз
Поблагодарили: 704 раз в 613 постах
Это же публичный форум - выложите самодостаточный отрывок кода, пользователи постараются помочь.
Знания в базе знаний, поддержка в техподдержке
thanks 1 пользователь поблагодарил Максим Коллегин за этот пост.
urbrato оставлено 10.07.2017(UTC)
Offline urbrato  
#6 Оставлено : 10 июля 2017 г. 17:21:20(UTC)
urbrato

Статус: Участник

Группы: Участники
Зарегистрирован: 04.11.2016(UTC)
Сообщений: 15
Российская Федерация
Откуда: Москва

Сказал(а) «Спасибо»: 7 раз
Автор: maxdm Перейти к цитате
Это же публичный форум - выложите самодостаточный отрывок кода, пользователи постараются помочь.


Спасибо!
Итак.
Имеются две машины. Один и тот же код крутится на обеих машинах, их роли код узнаёт при вызове соответствующих функций (машина А вызывает функцию с одним параметром, машина Б с другим). Протокол связи определён не нами, мы только добавляем туда аутентификацию и шифрование. Для тех, кто в курсе - связь осуществляет Windows Message Queue, пара настроена как Server / Requester. Для тех, кто не в курсе - машина Б инициирует сеанс связи, в ходе которого машины убеждаются в аутентичности друг друга, после чего связь становится на нашем уровне односторонней - машина А отправляет машине Б шифрованные сообщения, машина Б их расшифровывает.

При использовании КриптоПро 3.6 алгоритм был следующий.
1. При старте подхватываются сертификаты, далее на обеих машинах генерируется ключ обмена.
Код:

HCRYPTKEY SessionKey::CreateKeyExchange() throw(MQCPException *){
	/*Получаем публичный ключ из сертификата удаленного пользователя.
	*Предполагаем, что алгоритм, которому соответствует открытый ключ, - ГОСТ 34.2001 (CALG_GR3410EL)
	*/
	HCRYPTKEY RemotePublicKey;
	bool isImportPublicKey = CryptImportPublicKeyInfoEx(
		cryptoProvider, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, 
		&remoteCertificate->pCertInfo->SubjectPublicKeyInfo, 
		Config::CryptoParametrs::GetAlgIDKeyExchange(), 0, NULL, &RemotePublicKey);
	if(!isImportPublicKey){
		// обработка ошибки и логирование пропущены
	}

	/*
	*Импортируем полученный открытый ключ в PUBLICKEYBLOB
	*/
	Data ExpRemotePublicKey;
	bool isExportPublicKey = CryptExportKey(
		RemotePublicKey, 0, PUBLICKEYBLOB, 0, ExpRemotePublicKey.data, &(ExpRemotePublicKey.sizeData));
	if(!isExportPublicKey){
		// обработка ошибки и логирование пропущены
	}
	ExpRemotePublicKey.data = new BYTE[ExpRemotePublicKey.sizeData];
	isExportPublicKey = CryptExportKey(RemotePublicKey, 0, PUBLICKEYBLOB, 0, ExpRemotePublicKey.data, &(ExpRemotePublicKey.sizeData));
	if(!isExportPublicKey){
		// обработка ошибки и логирование пропущены
	}

	/*
	*Получаем приватный ключ пользователя, от имени которого мы работаем	
	*/
	HCRYPTKEY PrivateKey;
	bool isGetPrivateKey = CryptGetUserKey(cryptoProvider, AT_KEYEXCHANGE, &PrivateKey);
	if(!isGetPrivateKey){
		// обработка ошибки и логирование пропущены
	}

	/*
	* Создаем ключ обмена 
	*/
	HCRYPTKEY KeyExchange;
	bool isGetKeyExchange = CryptImportKey(
		cryptoProvider, ExpRemotePublicKey.data, ExpRemotePublicKey.sizeData, PrivateKey, 0, &KeyExchange);
	if(!isGetKeyExchange){
		// обработка ошибки и логирование пропущены
	}

	// вызов CryptDestroyKey для всего, что уже не нужно
	ClearCreateKeyExchange(RemotePublicKey, ExpRemotePublicKey, PrivateKey);

	// возврат ключа обмена
	return KeyExchange;
}


2. При начале аутентификации машина Б генерирует сессионный ключ, подписывает его и отправляет машине А. В примере кода отправка, а также проверки на ошибки, их обработка и логирование пропущены.
Код:

if (!sessionKey)
	CryptGenKey(cryptoProvider, Config::CryptoParametrs::GetAlgIDEncryptDecrypt(), CRYPT_EXPORTABLE, &SessionKey);
Data ExpSessionKey;
CryptExportKey(sessionKey, keyExchange, SIMPLEBLOB, 0, ExpSessionKey.data, &(ExpSessionKey.sizeData));
ExpSessionKey.data = new BYTE[ExpSessionKey.sizeData];
CryptExportKey(sessionKey, keyExchange, SIMPLEBLOB, 0, ExpSessionKey.data, &(ExpSessionKey.sizeData));
SignSessionKey = myCertificate->SignAttachedData(ExpSessionKey);


3. Машина А принимает сессионный ключ. Здесь VerifyAttachedDataSignature - обёртка над CryptVerifyMessageSignature, заполняющая параметры верификации, проверяющая коды ошибок, логирующая и т.д.
Код:

Data dataSessionKey = remoteCertificate->VerifyAttachedDataSignature(SignedSessionKey);
if (!sessionKey)
	CryptDestroyKey(sessionKey);
CryptImportKey(cryptoProvider, dataSessionKey.data, dataSessionKey.sizeData, keyExchange, 0, &sessionKey);


4. Далее процедура аутентификации идёт своим чередом (машины обмениваются случайными пакетами, зашифрованными и подписанными), после чего код аутентификации даёт знать протоколу, что из режима аутентификации нужно переключиться в режим передачи данных.

5. При необходимости отправить пакет данных машина А подписывает данные при помощи CryptSignMessage, шифрует их сессионным ключом и отправляет. Машина Б, получив пакет, дешифрует его и проверяет подпись. Эта связка проверена, входной и выходной массивы получаются одинаковыми.


Однако, с переходом на КриптоПро 3.9 возникла необходимость менять сессионный ключ ввиду реализованного в этой версии требования ограничения нагрузки на сессионный ключ.
Поскольку в этот момент связь становится однонаправленной от машины А к машине Б, теперь уже сессионный ключ генерируется машиной А, машина Б должна его импортировать.

Поэтому процедура передачи сообщения модифицирована следующим образом.

1. Генерируется сессионный ключ для следующего пакета, если таковой будет. В оригинале выходные значения функции Crypt... анализируются, при необходимости проверяется GetLastError, идёт логирование и т.д. Здесь для краткости опущены.
Код:

auxKey = CreateSessionKey();
auxBlob.Clear();
CryptExportKey(auxKey, keyExchange, SIMPLEBLOB, 0, auxBlob.data, &(auxBlob.sizeData));
auxBlob.data = new BYTE[auxBlob.sizeData];
CryptExportKey(auxKey, keyExchange, SIMPLEBLOB, 0, auxBlob.data, &(auxBlob.sizeData));


2. Полученный блоб подписывается при помощи CryptSignMessage.

3. Исходное сообщение подписывается и шифруется прежним сессионным ключом, как было раньше.

4. Одним пакетом передаётся структура, скомпонованная из подписанного блоба будущего ключа и зашифрованного подписанного исходного сообщения.

5. На машине А происходит замена сессионного ключа на новый.
Код:

CryptDestroyKey(sessionKey);
sessionKey = auxKey;


5. Машина Б, получив пакет, разбирает его обратно на подписанный блоб будущего ключа и зашифрованное подписанное сообщение. Сборка-разборка структуры проверены и производятся корректно.

6. Зашифрованное сообщение расшифровывается (ещё прежним сессионным ключом), проверяется подпись - как было раньше.

7. Новый сессионный ключ импортируется машиной Б взамен старого точно тем же кодом, каким машина А импортировала сессионный ключ в процессе аутентификации (проверяется подпись, старый ключ, если был, уничтожается, импортируется новый ключ). И вот здесь, в последнем пункте, возникает проблема - CryptImportKey кидает NTE_BAD_DATA.
RSS Лента  Atom Лента
Пользователи, просматривающие эту тему
Быстрый переход  
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.