CodeNet / Приложения / Алгоритмы / Защита информации и ее взлом
Секреты разработки CSP для Windows - Создание криптографического провайдера для Windows
Создание криптографического провайдера для Windows
Введение
Эта статья для тех, кто по тем или иным причинам решил написать собственный крипто-провайдер для OC семейства Windows. Если Вы хотите реализовать в вашем провайдере нестандартные алгоритмы, то вам предстоит столкнуться с определенными трудностями. Трудности могут возникнуть, например, при попытках использования вашего крипто-провайдера для проверки сертификатов в MS Internet Explorer.
Под нестандартными алгоритмами здесь понимаются не всемирные DES, RSA, DSA и т.д, а, например, алгоритмы семейства ГОСТ. Дело в том, что для RSA и подобных алгоритмов все необходимые идентификаторы уже зашиты в систему, а для ГОСТ-ов (или многих других алгоритмов) надо отдельно позаботиться о том, чтобы система их "увидела".
Для примеров кода используется Си. Все примеры кода служат только для иллюстрации принципов изложенных в статье и не являются полноценными рабочими программами.
Также подразумевается, что у читателя есть базовые знания в области прикладной криптографии и термины "открытый ключ", "ASN.1" и подобные для него не являются загадкой.
Интеграция провайдера в Windows
Помимо наличия библиотеки самого провайдера дополнительно требуется:
- зарегистрировать провайдер и крипто-алгоритмы в системе, прописав определенные параметры в реестре;
- создать библиотеку с функциями конвертирования ключей из форматов криптопровайдера во внешние форматы и зарегистрировать эти функции в реестре;
- заменить функцию I_CryptGetDefaultCryptProvider в библиотеке crypt32.dll
Только после выполнения этих действий провайдер нормально интегрируется в систему и вы сможете, например, генерировать сертификаты при помощи вашего провайдера на основе стандартного компонента ОС Windows Server - Сertification services или на тестовом УЦ КриптоПро http://www.cryptopro.ru/certsrv
Корректно будет отображаться статус проверки подписи у сертификатов, подписанных вашим "нестандартным" алгоритмом и т.п.
Далее подробно рассмотрим каждый из упомянутых выше шагов, предполагая при этом что библиотека с Вашим CSP уже имеется и корректно работают все функции провайдера.
Регистрация крипто-провайдера и алгоритмов в системе
Когда у вас уже есть готовая библиотека с реализацией функций CSP, необходимо зарегистрировать ее в системе, для того чтобы новый крипто-провайдер стал доступен различным приложениям.
Процесс регистрации самого CSP подробно описан в MDSN, и повторять эту информацию здесь смысла нет. Все это подробно описано в [1]. Также здесь мы не будем останавливаться на проблеме подписи нового CSP в Microsoft и путях ее обхода. Эта проблема уже многократно обсуждалась на различных форумах, например смотрите [3]. Гораздо интереснее рассмотреть регистрацию криптографических алгоритмов. Каждый алгоритм имеет свой уникальный ASN.1 идентификтор Оbject Identifier - OID. Например, алгоритм подписи ГОСТ-34.10-2001 имеет такой OID (представленный в виде строки) - "1.2.643.2.2.3". Идентификатор каждого поддерживаемого вашим CSP алгоритма следует занести в реестр. Помимо OID у каждого крипто-алгоритма в Windows существует еще идентификатор в виде четырех-байтового числа - AlgID, по которому алгоритмы идентифицируются в провайдере. Этот идентификатор заноситься в CSP и его можно узнать перечислив алгоритмы посредством вызова CPGetProvParam. В КриптоПро, например, для алгоритма хеширования ГОСТ-34.11-94 AlgID используется значение 0x801e
Пусть нам необходимо зарегистрировать алгоритм подписи ГОСТ-34.10-2001. Тогда в реестре необходимо прописать следующие идентификаторы:
- "1.2.643.2.2. 9!1" - Хэш ГОСТ-34.11-94
- "1.2.643.2.2.19!3" - Ключ подписи ГОСТ-34.10-2001
- "1.2.643.2.2.3!4" - Подпись ГОСТ-34.10-2001 - Алгоритм Хэша + Алгоритм Ключа
Далее приведен пример кода регистрации 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: http://msdn.microsoft.com/library/default.asp?url=/library/en-us/seccrypto/security/crypt_oid_info.asp
Для того чтобы провайдер вызывался для проверки сертификата, подписанного нашим "нестандартным" алгоритмом необходимо еще одно дополнительное действие.
Дело в том, что Windows определяет какой провайдер использовать для проверки по полю ExtraInfo (см. ссылку в предыдущем абзаце для описания этого поля) в ключе реестра соответвующему алгоритму подписи - такой ключ мы создаем, вызывая функцию CryptRegisterOIDInfo. Поэтому надо указать системе наш провайдер в качестве провайдера по умолчанию для типа, который занесен в ExtraInfo алгоритма подписи.
Следующий код устанавливает провайдер по умолчанию для определенного типа.
#define YOUR_PROV_NAME "MY_PROV" #define YOUR_PROV_TYPE 75 rc = CryptSetProvider( YOUR_PROV_NAME, YOUR_PROV_TYPE );
Сценарии применения нашего CSP
Рассмотрим здесь два сценария применения CSP.
Первый сценарий - проверка подписи сертификата. Для проверки подписи система загружает открытый ключ из сертификата, которым подписан проверяемый. Затем по OID алгоритма подписи проверяемого сертификата, так как описано в предыдущем разделе статьи, определяется требуемый провайдер. Чтобы проверить подпись, нужно импортировать открытый ключ в CSP и можно было бы подумать что Windows сразу вызывает функцию нашего провайдера CPImportKey. Но не тут-то было!
Второй сценарий - генерация ключевой пары и отправка запроса на сертификат на Удостоверяющий Центр. Windows загружает наш CSP, генерирует ключевую пару и экспортирует к себе наверх открытый ключ вызывая функцию CPExportKey. Все хорошо. Вроде бы надо взять и поместить полученный буфер с ключом в PKCS#10 запрос, который затем будет отправлен на УЦ. И тут все опять не совсем так.
Оказывается, существуют промежуточные функции для экспорта/импорта открытых ключей, и без их реализации ничего хорошего с нашим CSP, в упомянутых выше двух сценариях, не получиться. Беда еще и в том, что функции эти недокументированные и найти информацию по ним крайне сложно. Их описанию посвящен следующий раздел.
Функции конвертирования ключей
Архитектура круговорота открытых ключей для "нестандартных" алгоритмов в Windows представлена на картинке:
Функция A_ConvertPublicKeyInfo - на входе принимает ключ в формате ASN.1 DER и должна преобразовать его в формат, который "понимает" функция CSP CPImportKey
Функция A_EncodePublicKeyInfoAndParameters на входе принимает ключ в том формате, в котором ключ экспортирует CPExportKey. На выходе A_EncodePublicKeyInfoAndParameters должна сформировать ASN.1 DER структуру с ключом - ту же самую которая в случае импорта передается в A_ConvertPublicKeyInfo - на вход. Надеюсь, вы не запутались во всех этих входах и выходах :)
Вот сигнатуры и краткие описания параметров этих функций:
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: http://research.microsoft.com/sn/detours/
Привязка закрытого ключа к сертификату
В отличие от открытого ключа закрытые ключи никогда не покидают крипто-провайдер и поэтому когда вы видите что для данного сертификата есть закрытый ключ (как на рисунке) это значит что в контексте этого сертификата существует явная ссылка на закрытый ключ.
Контекст сертификата это набор дополнительных атрибутов сертификата, которые находятся не в теле сертификата, а хранятся отдельно от него. Одним из таких атрибутов и является ссылка на закрытый ключ, которая состоит из имени провайдера и имени ключевого контейнера.
Пример кода для привязки закрытого ключа к сертификату:
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 );
Успехов в разработке крипто-провайдера!
Дополнительная информация
- Разработка CSP для Windows http://msdn.microsoft.com/library/default.asp?url=/library/en-us/seccrypto/security/cryptography_portal.asp
- Additional Cryptographic Algorithms for Use with GOST 28147-89, GOST R 34.10-94, GOST R 34.10-2001, and GOST R 34.11-94 Algorithms http://www.ietf.org/rfc/rfc4357.txt
- Много интересной информации об архитектуре CSP можно найти тут: http://www.derkeiler.com/Mailing-Lists/securityfocus/focus-ms/