Logo Море(!) аналитической информации!
IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware
2006 г.

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

Зырянов Юрий Сергеевич
OOO "ЛИССИ"

Введение

Эта статья для тех, кто по тем или иным причинам решил написать собственный криптопровайдер для OC семейства Windows. Если Вы хотите реализовать в вашем провайдере нестандартные алгоритмы, то вам предстоит столкнуться с определенными трудностями. Трудности могут возникнуть, например, при попытках использования вашего криптопровайдера для проверки сертификатов в MS Internet Explorer.

Под нестандартными алгоритмами здесь понимаются не всемирные DES, RSA, DSA и т.д., а, например, алгоритмы семейства ГОСТ. Дело в том, что для RSA и подобных алгоритмов все необходимые идентификаторы уже зашиты в систему, а для ГОСТ-ов (или многих других алгоритмов) надо отдельно позаботиться о том, чтобы система их "увидела".

Для примеров кода используется Си. Все примеры кода служат только для иллюстрации принципов, изложенных в статье и не являются полноценными рабочими программами.

Также подразумевается, что у читателя есть базовые знания в области прикладной криптографии и термины "открытый ключ", "ASN.1" и подобные для него не являются загадкой.

Интеграция провайдера в Windows

Помимо наличия библиотеки самого провайдера, дополнительно требуется:
  • зарегистрировать провайдер и криптоалгоритмы в системе, прописав определенные параметры в реестре;
  • создать библиотеку с функциями конвертирования ключей из форматов криптопровайдера во внешние форматы и зарегистрировать эти функции в реестре;
  • заменить функцию I_CryptGetDefaultCryptProvider в библиотеке crypt32.dll

Только после выполнения этих действий провайдер нормально интегрируется в систему и вы сможете, например, генерировать сертификаты при помощи вашего провайдера на основе стандартного компонента ОС Windows Server - Сertification services или на тестовом УЦ КриптоПро.

Корректно будет отображаться статус проверки подписи у сертификатов, подписанных вашим "нестандартным" алгоритмом и т.п.

Далее подробно рассмотрим каждый из упомянутых выше шагов, предполагая при этом, что библиотека с Вашим 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 - Алгоритм Хэша + Алгоритм Ключа

Рисунок 1Создание необходимых разделов в реестре Windows

Далее приведен пример кода регистрации 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.

Для того, чтобы провайдер вызывался для проверки сертификата, подписанного нашим "нестандартным" алгоритмом, необходимо еще одно дополнительное действие.

Дело в том, что 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 запрос, который затем будет отправлен на УЦ. И тут все опять не совсем так.

Рисунок 2Создание сертификата при помощи веб-интерфейса

Оказывается, существуют промежуточные функции для экспорта/импорта открытых ключей, и без их реализации ничего хорошего с нашим CSP, в упомянутых выше двух сценариях, не получится. Беда еще и в том, что функции эти недокументированные и найти информацию по ним крайне сложно. Их описанию посвящен следующий раздел.

Функции конвертирования ключей

Архитектура круговорота открытых ключей для "нестандартных" алгоритмов в Windows представлена на рисунке 3.

Рисунок 3Архитектура круговорота открытых ключей для "нестандартных" алгоритмов

Функция 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.

Привязка закрытого ключа к сертификату

В отличие от открытого ключа, закрытые ключи никогда не покидают криптопровайдер и поэтому, когда вы видите, что для данного сертификата есть закрытый ключ (как на рисунке), это значит, что в контексте этого сертификата существует явная ссылка на закрытый ключ.

img-04.png

Контекст сертификата - это набор дополнительных атрибутов сертификата, которые находятся не в теле сертификата, а хранятся отдельно от него. Одним из таких атрибутов и является ссылка на закрытый ключ, которая состоит из имени провайдера и имени ключевого контейнера.

Пример кода для привязки закрытого ключа к сертификату:

	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
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
3.обратноМного интересной информации об архитектуре CSP можно найти тут

Новости мира IT:

Архив новостей

Последние комментарии:

Loading

IT-консалтинг Software Engineering Программирование СУБД Безопасность Internet Сети Операционные системы Hardware

Информация для рекламодателей PR-акции, размещение рекламы — adv@citforum.ru,
тел. +7 985 1945361
Пресс-релизы — pr@citforum.ru
Обратная связь
Информация для авторов
Rambler's Top100 TopList liveinternet.ru: показано число просмотров за 24 часа, посетителей за 24 часа и за сегодня This Web server launched on February 24, 1997
Copyright © 1997-2000 CIT, © 2001-2015 CIT Forum
Внимание! Любой из материалов, опубликованных на этом сервере, не может быть воспроизведен в какой бы то ни было форме и какими бы то ни было средствами без письменного разрешения владельцев авторских прав. Подробнее...