Секреты разработки CSP
Секреты разработки CSP
Юрий Зырянов
Введение
Эта статья для тех, кто по тем или иным причинам решил
написать собственный крипто-провайдер для OC семейства Windows. Если Вы хотите
реализовать в вашем провайдере нестандартные алгоритмы, то вам предстоит
столкнуться с определенными трудностями. Трудности могут возникнуть, например,
при попытках использования вашего крипто-провайдера для проверки сертификатов в
MS Internet Explorer.
Под нестандартными алгоритмами здесь понимаются не
всемирные DES, RSA, DSA и т.д, а, например, алгоритмы семейства ГОСТ. Дело в
том, что для RSA и подобных алгоритмов все необходимые идентификаторы уже
зашиты в систему, а для ГОСТ-ов (или многих других алгоритмов) надо отдельно
позаботиться о том, чтобы система их “увидела”.
Для примеров кода используется Си. Все примеры кода
служат только для иллюстрации принципов изложенных в статье и не являются
полноценными рабочими программами.
Также подразумевается, что у читателя есть базовые
знания в области прикладной криптографии и термины “открытый ключ”, “ASN.1” и
подобные для него не являются загадкой.
Интеграция провайдера в Windows
Помимо наличия библиотеки самого провайдера
дополнительно требуется:
зарегистрировать провайдер и крипто-алгоритмы в
системе, прописав определенные параметры в реестре;
создать библиотеку с функциями конвертирования ключей
из форматов криптопровайдера во внешние форматы и зарегистрировать эти функции
в реестре;
заменить функцию I_CryptGetDefaultCryptProvider в
библиотеке crypt32.dll
Только после выполнения этих действий провайдер
нормально интегрируется в систему и вы сможете, например, генерировать
сертификаты при помощи вашего провайдера на основе стандартного компонента ОС
Windows Server - Сertification services или на тестовом УЦ КриптоПро
#"67167.files/image001.jpg">
Рисунок 1 - Идентификаторы алгоритмов
Далее приведен пример кода регистрации OID алгоритма
ГОСТ-34.11-94
// Регистрация GOST-3411-94 HASH OID
//
CRYPT_OID_INFO oidInfo;
int rc = 0;
memset(&oidInfo, 0,
sizeof(CRYPT_OID_INFO));
oidInfo.cbSize = sizeof(CRYPT_OID_INFO);
oidInfo.pszOID="1.2.643.2.2.9";
oidInfo.pwszName= L"GOST-3411-94
HASH";
oidInfo.dwGroupId =
CRYPT_HASH_ALG_OID_GROUP_ID;
oidInfo.Algid = 0x801e;
oidInfo.ExtraInfo.cbData=0;
oidInfo.ExtraInfo.pbData=0;
rc = CryptRegisterOIDInfo(
&oidInfo,
0
);
if(rc)
printf("nHash algorithm
registered”);
else
printf("nError registering hash
algorithm”);
Аналогично регистрируются и остальные алгоритмы.
Подробную информацию о структуре CRYPT_OID_INFO можно найти в MSDN:
#"67167.files/image002.jpg">
Рисунок 2 - Запрос на сертификат в УЦ
Рисунок 3 - Процесс создания сертификата
Оказывается, существуют промежуточные функции для
экспорта/импорта открытых ключей, и без их реализации ничего хорошего с нашим
CSP, в упомянутых выше двух сценариях, не получиться. Беда еще и в том, что
функции эти недокументированные и найти информацию по ним крайне сложно. Их
описанию посвящен следующий раздел.
Функции конвертирования ключей
Архитектура круговорота открытых ключей для
“нестандартных” алгоритмов в Windows представлена на картинке:
Рисунок 4 - Импорт и экспорт открытых ключей в/из CSP
Функция A_ConvertPublicKeyInfo – на входе принимает
ключ в формате ASN.1 DER и должна преобразовать его в формат, который
“понимает” функция CSP CPImportKey
Функция A_EncodePublicKeyInfoAndParameters на входе
принимает ключ в том формате, в котором ключ экспортирует CPExportKey. На
выходе A_EncodePublicKeyInfoAndParameters должна сформировать ASN.1 DER
структуру с ключом – ту же самую которая в случае импорта передается в
A_ConvertPublicKeyInfo – на вход. Надеюсь, вы не запутались во всех этих входах
Jи выходах
Вот сигнатуры и краткие описания параметров этих
функций:
BOOL WINAPI A_ConvertPublicKeyInfo(
DWORD dwCertEncodingType, // IN -
VOID *EncodedKeyInfo, // IN – буфер с ключом
- указатель
// на структуру CERT_PUBLIC_KEY_INFO
DWORD dwAlg, // IN – AlgId ключа
DWORD dwFlags, // IN – обычно
0
BYTE **ppStructInfo, // OUT – двойной указатель на
структуру
// в заголовке структуры идет сначала PUBLICKEYSTRUC,
затем DSSPUBKEY,
// а затем сам ключ с параметрами
DWORD
*StructLen // OUT – длинна структуры
);
BOOL WINAPI A_EncodePublicKeyAndParameters(
DWORD dwCertEncodingType, // IN
LPCSTR lpszStructType, // IN – OID алгоритма
const void* pvStructInfo, // IN – такая же структура как
// на выходе ConvertPublicKeyInfo
DWORD nStructLen, // IN – длинна входной структуры
DWORD dwFlags, // IN – обычно
0
DWORD Unk, // ? - неизвестно
BYTE **pbPubKey, // OUT – открытый ключ в ASN.1 DER
DWORD* pcPubKeyLen, // OUT – длинна открытого ключа
BYTE **pbParams, // OUT – параметры открытого ключа
DWORD* pcParamsLen // OUT – длинна параметров
);
Форматы ключей зависят от крипто-провайдера и
используемых алгоритмов.
Функция
I_CryptGetDefaultCryptProvider из crypt32.dll
По непонятной для меня причине Windows часто не
пытается искать нужный крипто- провайдер по идентификатору алгоритма с которым
требуется работать. В таких случаях она просто вызывает недокументированную
функцию I_CryptGetDefaultCryptProvider, и если тот провайдер, который вернула
эта функция, не умеет работать с данным алгоритмом, то процесс завершается с
ошибкой. Так происходит, например, при разборе в Internet Explorer PKCS#7
ответа в сценарии с запросом сертификата на тестовом УЦ.
HCRYPTPROV WINAPI
I_CryptGetDefaultCryptProv(ALG_ID algid);
Необходимо заменить эту функцию таким образом, чтобы
при нулевом параметре algid на входе она возвращала наш провайдер, который уже
в отличии от штатного провайдера легко справиться с “нестандартными
алгоритмами”.
Обсуждение способов замены функций в системной dll
выходит далеко за рамки данной статьи. Могу лишь, как один из способов решения,
предложить библиотеку Microsoft Detours: #"67167.files/image005.jpg">
Рисунок 5 - Закрытый ключ сертификата
Контекст сертификата это набор дополнительных
атрибутов сертификата, которые находятся не в теле сертификата, а хранятся
отдельно от него. Одним из таких атрибутов и является ссылка на закрытый ключ,
которая состоит из имени провайдера и имени ключевого контейнера.
Пример кода для привязки закрытого ключа к
сертификату:
PCCERT_CONTEXT
pCert;
CRYPT_KEY_PROV_INFO prov_info;
…
prov_info.cProvParam = 0;
prov_info.rgProvParam = 0;
prov_info.dwFlags = 0;
prov_info.dwKeySpec = AT_SIGNATURE;
prov_info.dwProvType = 0;
prov_info.pwszContainerName =
L"key-kont-name";
prov_info.pwszProvName =
L"A-CSP";
CertSetCertificateContextProperty(
pCert,
CERT_KEY_PROV_INFO_PROP_ID,
0,
&prov_info
);
Успехов в разработке крипто-провайдера!
#"#">http://www.realcoding.net/