Справочник функций

Ваш аккаунт

Войти через: 
Забыли пароль?
Регистрация
Информацию о новых материалах можно получать и без регистрации:

Почтовая рассылка

Подписчиков: -1
Последний выпуск: 19.06.2015

Секреты разработки CSP для Windows - Создание криптографического провайдера для Windows

Создание криптографического провайдера для Windows

Автор: Зырянов Юрий Сергеевич
OOO "ЛИССИ" , www.lissi.ru

Введение

Эта статья для тех, кто по тем или иным причинам решил написать собственный крипто-провайдер для 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
	);

Успехов в разработке крипто-провайдера!

Дополнительная информация

  1. Разработка CSP для Windows http://msdn.microsoft.com/library/default.asp?url=/library/en-us/seccrypto/security/cryptography_portal.asp
  2. 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
  3. Много интересной информации об архитектуре CSP можно найти тут: http://www.derkeiler.com/Mailing-Lists/securityfocus/focus-ms/

Оставить комментарий

Комментарий:
можно использовать BB-коды
Максимальная длина комментария - 4000 символов.
 
Реклама на сайте | Обмен ссылками | Ссылки | Экспорт (RSS) | Контакты
Добавить статью | Добавить исходник | Добавить хостинг-провайдера | Добавить сайт в каталог