Сообщений 6 Оценка 25 [+0/-1] Оценить |
Эта статья для тех, кто по тем или иным причинам решил написать собственный криптопровайдер для OC семейства Windows. Если вы хотите реализовать в вашем провайдере нестандартные алгоритмы, то вам предстоит столкнуться с определенными трудностями. Трудности могут возникнуть, например, при попытках использования вашего криптопровайдера для проверки сертификатов в MS Internet Explorer.
Под нестандартными алгоритмами здесь понимаются не всемирно распространенные DES, RSA, DSA и т.д., а, например, алгоритмы семейства ГОСТ. Дело в том, что для RSA и подобных алгоритмов всё необходимое уже заложено в систему, а для ГОСТ-ов (или многих других алгоритмов) надо отдельно позаботиться о том, чтобы система их “увидела”.
В примерах кода используется Си. Все примеры кода служат только для иллюстрации принципов, изложенных в статье, и не являются полноценными рабочими программами.
Также подразумевается, что у читателя есть базовые знания в области прикладной криптографии, и термины “открытый ключ”, “ASN.1” и им подобные для него не являются загадкой.
Помимо наличия библиотеки самого провайдера дополнительно требуется:
Только после выполнения этих действий провайдер нормально интегрируется в систему, и вы сможете, например, при помощи вашего провайдера генерировать сертификаты с помощью стандартного компонента ОС Windows – Сertification services.
Рассмотрим подробнее каждый из упомянутых выше шагов, предполагая при этом, что библиотека с CSP уже имеется, и что все функции провайдера работают корректно.
Чтобы новый крипто-провайдер стал доступен различным приложениям, необходимо зарегистрировать в системе библиотеку с реализацией функций CSP.
Процесс регистрации самого CSP подробно описан в MDSN [1], и повторять эту информацию здесь смысла нет. Также здесь мы не будем останавливаться на проблеме подписи CSP и путях ее обхода. Гораздо интереснее рассмотреть регистрацию криптографических алгоритмов. Каждый алгоритм имеет свой уникальный 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. Тогда в реестре необходимо прописать следующие идентификаторы:
Далее приведен пример кода регистрации 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.
Первый сценарий – проверка подписи сертификата. Для проверки подписи система загружает открытый ключ из сертификата, которым подписан проверяемый сертификат. Затем по 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 - // IN – буфер с ключом – указатель на структуру CERT_PUBLIC_KEY_INFO VOID *EncodedKeyInfo, DWORD dwAlg, // IN – AlgId ключа DWORD dwFlags, // IN – обычно 0 // OUT – двойной указатель на структуру. // В заголовке структуры идет сначала PUBLICKEYSTRUC, затем DSSPUBKEY, // а затем сам ключ с параметрами. BYTE** ppStructInfo, DWORD* StructLen // OUT – длина структуры ); BOOL WINAPI A_EncodePublicKeyAndParameters( DWORD dwCertEncodingType, // IN LPCSTR lpszStructType, // IN – OID алгоритма // IN – такая же структура, как на выходе ConvertPublicKeyInfo const void* pvStructInfo, DWORD nStructLen, // IN – длина входной структуры DWORD dwFlags, // IN – обычно 0 DWORD Unk, // неизвестно BYTE** pbPubKey, // OUT – открытый ключ в ASN.1 DER DWORD* pcPubKeyLen, // OUT – длина открытого ключа BYTE** pbParams, // OUT – параметры открытого ключа DWORD* pcParamsLen // OUT – длина параметров ); |
Форматы ключей зависят от крипто-провайдера и используемых алгоритмов.
По непонятной для меня причине Windows часто не пытается искать нужный крипто-провайдер по идентификатору алгоритма, с которым требуется работать. В таких случаях она просто вызывает недокументированную функцию I_CryptGetDefaultCryptProvider, и если тот провайдер, который вернула эта функция, не умеет работать с данным алгоритмом, то процесс завершается с ошибкой. Так происходит, например, при разборе в Internet Explorer ответа PKCS#7 в сценарии с запросом сертификата на тестовом УЦ.
HCRYPTPROV WINAPI I_CryptGetDefaultCryptProv(ALG_ID algid); |
Необходимо заменить эту функцию таким образом, чтобы при получении нулевого параметра algid она возвращала наш провайдер, который, в отличие от штатного провайдера, легко справится с «нестандартными» алгоритмами.
ПРИМЕЧАНИЕ Довольно спорное предложение – заниматься изменением системных библиотек Windows. – прим.ред. |
Обсуждение способов замены функций в системной 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 ); |
Успехов в разработке крипто-провайдера!
Сообщений 6 Оценка 25 [+0/-1] Оценить |