Статус: Участник
Группы: Участники
Зарегистрирован: 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.
|