Введение
Эта статья для тех, кто по тем или иным причинам решил написать собственный криптопровайдер для 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.
Привязка закрытого ключа к сертификату
В отличие от открытого ключа, закрытые ключи никогда не покидают криптопровайдер и поэтому, когда вы видите, что для данного сертификата есть закрытый ключ (как на рисунке), это значит, что в контексте этого сертификата существует явная ссылка на закрытый ключ.
Контекст сертификата - это набор дополнительных атрибутов сертификата, которые находятся не в теле сертификата, а хранятся отдельно от него. Одним из таких атрибутов и является ссылка на закрытый ключ, которая состоит из имени провайдера и имени ключевого контейнера.
Пример кода для привязки закрытого ключа к сертификату:
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
);
Успехов в разработке криптопровайдера!
Дополнительная информация