Ключевое слово в защите информации
КЛЮЧЕВОЕ СЛОВО
в защите информации
Наши способы организации безопасного удалённого доступа к рабочим местам и корпоративным ресурсам
Добро пожаловать, Гость! Чтобы использовать все возможности Вход или Регистрация.

Уведомление

Icon
Error

Опции
К последнему сообщению К первому непрочитанному
Offline Иван Тимохин  
#1 Оставлено : 29 октября 2020 г. 11:30:47(UTC)
Иван Тимохин

Статус: Новичок

Группы: Участники
Зарегистрирован: 08.04.2020(UTC)
Сообщений: 6
Российская Федерация

Добрый день.

Пытаюсь реализовать импорт сертификата средствами CryptoApi, работу осуществляю с контейнерами,
созданными страницей https://www.cryptopro.ru/certsrv/certrqma.asp в реестре.

1. Столкнулся с ошибкой. Функция CryptQueryObject возвращает 2148081673 (CRYPT_E_NO_MATCH, Not all the attributes were found and matched) для сертификатов, которые были созданы на странице https://www.cryptopro.ru/certsrv/certrqma.asp с указанием параметра "Дополнительные параметры.ПонятноеИмя".
Подскажите, пожалуйста, почему так происходит?

Код:
CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &certBlob, CERT_QUERY_CONTENT_FLAG_CERT,
		CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, NULL, NULL, NULL, NULL, (const void **)&pExtCertCtx)


2. Мне удалось произвести импорт сертификата, у которого поле "Дополнительные параметры.ПонятноеИмя" не заполнено.
Теперь я хочу проверить импорт нового сертификата в контейнер, который уже имеет сертификат.
Для этого на странице https://www.cryptopro.ru/certsrv/certrqma.asp создаю сертификат с параметром "Использовать существующий набор ключей" и указываю идентификатор раннее созданного контейнера, но получаю ошибку

Код:

Произошла ошибка при создании запроса на сертификат. Проверьте, что выбранный поставщик служб шифрования поддерживает заданные вами параметры и введены правильные данные.
Предполагаемая причина:
(нет вариантов)
Ошибка: 0x8007065B - (нет данных)


Можете подсказать почему не удается создать новый сертификат для ранее созданного контейнера?

3. И еще вопрос, касающийся общего понимания процессов.
На странице https://docs.microsoft.c...incrypt-cryptsetkeyparam
указано
Цитата:
The CryptSetKeyParam function customizes various aspects of a session key's operations. The values set by this function are not persisted to memory and can only be used with in a single session.


Меня смущает фраза "The values set by this function are not persisted to memory and can only be used with in a single session"
Я использую CryptSetKeyParam в своей программе и сертификат сохраняется в контейнере даже после завершения программы.
Вопрос: можно использовать эту функцию для записи сертификата в контейнер на постоянной основе или нет?


p.s. Привожу код программы
Код:
#include "stdafx.h"

#pragma comment(lib, "crypt32.lib")

CERT_BLOB readCertificate(const char *name)
{
	std::ifstream fl(name);
	fl.seekg(0, std::ios::end);
	size_t len = fl.tellg();
	char *ret = new char[len];
	fl.seekg(0, std::ios::beg);
	fl.read(ret, len);
	fl.close();

	CERT_BLOB certBlob;
	certBlob.pbData = (BYTE*)ret;
	certBlob.cbData = len;

	return certBlob;
}

wchar_t *convertCharArrayToLPCWSTR(const char* charArray)
{
	wchar_t* wString = new wchar_t[4096];
	MultiByteToWideChar(CP_ACP, 0, charArray, -1, wString, 4096);
	return wString;
}

int main()
{
	DWORD providerType = 80;
	DWORD error = 0;		
	const char* container = "20be05bf1-3ff2-ebaf-fe0e-0ebd851d379";
	CERT_BLOB certBlob = readCertificate("D:\\cert.cer");
	PCCERT_CONTEXT pCertContext = NULL;

	PCCERT_CONTEXT pExtCertCtx = 0;
	if (!CryptQueryObject(CERT_QUERY_OBJECT_BLOB, &certBlob, CERT_QUERY_CONTENT_FLAG_CERT,
		CERT_QUERY_FORMAT_FLAG_ALL, 0, NULL, NULL, NULL, NULL, NULL, (const void **)&pExtCertCtx))
	{
		error = GetLastError();
	}

	pCertContext = CertCreateCertificateContext(PKCS_7_ASN_ENCODING | X509_ASN_ENCODING, // The encoding type
		pExtCertCtx->pbCertEncoded,   // The encoded data from the certificate retrieved
		pExtCertCtx->cbCertEncoded);  // The length of the encoded data
	if (!pCertContext)
	{
		return 1;
	}

	CRYPT_KEY_PROV_INFO providerInfo;
	memset(&providerInfo, 0, sizeof(providerInfo));	
	providerInfo.pwszContainerName = convertCharArrayToLPCWSTR(container);
	providerInfo.dwProvType = providerType;
	providerInfo.dwKeySpec = AT_KEYEXCHANGE;

	// ------------------------------------------------------------------ 
	// 4.) Associate the container with our certificate
	if (!CertSetCertificateContextProperty(pCertContext, CERT_KEY_PROV_INFO_PROP_ID, 0, &providerInfo))
	{
		return 1;
	}

	HCRYPTPROV hCryptProv = NULL;
	if (!CryptAcquireContext(
		&hCryptProv,                 // handle to the CSP
		convertCharArrayToLPCWSTR(container),                  // container name 
		NULL,                        // use the default provider
		providerType,                // provider type
		0)							 // flag values
		)
	{
		return 1;
	}

	// ------------------------------------------------------------------ 
	// 6.) Get key
	HCRYPTKEY hKey = NULL;
	if (!CryptGetUserKey(hCryptProv, AT_KEYEXCHANGE, &hKey))
	{
		return 1;
	}

	// ------------------------------------------------------------------ 
	// 7.) Map key and certificate.
	if (!CryptSetKeyParam(
		hKey,
		KP_CERTIFICATE,
		pCertContext->pbCertEncoded,
		0))
	{
		error = GetLastError();
		return 1;
	}


	return 0;
}

Отредактировано пользователем 29 октября 2020 г. 11:44:21(UTC)  | Причина: Не указана

Offline two_oceans  
#2 Оставлено : 31 октября 2020 г. 9:22:42(UTC)
two_oceans

Статус: Эксперт

Группы: Участники
Зарегистрирован: 05.03.2015(UTC)
Сообщений: 1,129
Российская Федерация
Откуда: Иркутская область

Сказал(а) «Спасибо»: 75 раз
Поблагодарили: 257 раз в 241 постах
Цитата:
1...
CryptQueryObject в данном случае вообще не особо нужен: если заранее известно в каком формате файл сертификата (DER, не BASE64), тогда можно сразу вызвать CertCreateCertificateContext на прочитанном блобе. Поэтому CryptQueryObject там именно для декодирования PEM/BASE64/p7b. Соответственно если ошибка на этом этапе, то прикрепите сертификат на котором возникает ошибка. Указать версию КриптоПро также не помешает.
UPD: возвращаемый этим УЦ сертификат оказался в p7b (связка из сертификата УЦ и выданного сертификата), совершенно не подходяще его хранить как cer.

Вообще меня терзают сомнения насчет правильности применения кодировки - если приложение Unicode и функции Crypt* без литеры связаны с Crypt*W, то как вообще имя контейнера оказалось в CP_ACP? Или Вы в не-Unicode приложение привязали Crypt*W функции без литеры? Тогда еще и операционную систему укажите, на *nix бывает нужен UTF-8 вместо Unicode.

Еще не совсем понятно почему вызываете CryptAcquireContext вместо попытки сразу проверить установленную в 4) ссылку на контейнер функцией CryptAcquireCertificatePrivateKey.

Цитата:
2... Теперь я хочу проверить импорт нового сертификата в контейнер, который уже имеет сертификат.
Можете не трудиться, замена сертификата в контейнере КриптоПро штатно не поддерживается. Как и удаление сертификата из контейнера - штатно не поддерживается, можно удалить вручную, но при ручном удалении нужно указать новую контрольную сумму, а я пока не понял как она считается. Очень актуальная на самом деле проблема для сертификатов длительностью более 1 год 3 месяца.

Проверял на 4.0.9963, создавая через самопальный УЦ на openssl два разных сертификата из одного запроса на сертификат. Чтобы установить новый сертификат, созданный с той же ключевой парой потребуется либо копия контейнера до установки первого сертификата либо пересоздать контейнер экспортом/импортом файла pfx(он же p12). Другими словами, технически возможно у двух сертификатов проставить ссылку на один контейнер (шаг 4), но в таком контейнере не должно быть сертификата (то есть не выполнять шаг 7).

По ошибке.. указал разные формы имени контейнера и у меня все нашлось. Будьте внимательны при вводе имени
Код:
\\.\FAT12_K\FAT12\9898B4C2\01e6xl00.000\97CD
\\.\FAT12_K\FAT12\9898B4C2\01e6xl00.000
FAT12\9898B4C2\01e6xl00.000
(Фамилия имя отчество) 2010211712


Цитата:
3... CryptSetKeyParam
Там же ниже примечание
Цитата:
The Microsoft Base Cryptographic Provider does not permit setting values for key exchange or signature keys; however, custom providers can define values that can be set for its keys.
То есть Майкрософт отвечает только за себя, что изменения не сохраняются для долговременных ключей, но дополнительные криптопровайдеры могут сами решать какие из параметров сохраняются долговременно.

Отредактировано пользователем 31 октября 2020 г. 11:20:04(UTC)  | Причина: Не указана

Offline Иван Тимохин  
#3 Оставлено : 2 ноября 2020 г. 14:33:27(UTC)
Иван Тимохин

Статус: Новичок

Группы: Участники
Зарегистрирован: 08.04.2020(UTC)
Сообщений: 6
Российская Федерация

1.
Цитата:
если заранее известно в каком формате файл сертификата (DER, не BASE64), тогда можно сразу вызвать CertCreateCertificateContext на прочитанном блобе

Значит в CertCreateCertificateContext могу просто передать указатель на массив BYTE и длину массива из CERT_BLOB, так?

2.
Цитата:
Вообще меня терзают сомнения насчет правильности применения кодировки - если приложение Unicode и функции Crypt* без литеры связаны с Crypt*W, то как вообще имя контейнера оказалось в CP_ACP? Или Вы в не-Unicode приложение привязали Crypt*W функции без литеры? Тогда еще и операционную систему укажите, на *nix бывает нужен UTF-8 вместо Unicode.

Подскажие, о какой литере идет речь? О литере L?
Ведь ниже по коду я произвожу конверацию имени контейнера командой convertCharArrayToLPCWSTR(container).

3.
Цитата:
не совсем понятно почему вызываете CryptAcquireContext вместо попытки сразу проверить установленную в 4) ссылку на контейнер функцией CryptAcquireCertificatePrivateKey

Можете подсказать для чего это нужно делать? Ддя проверки корректности результата работы CertSetCertificateContextProperty?

4.
Цитата:
Можете не трудиться, замена сертификата в контейнере КриптоПро штатно не поддерживается.

Получается, я не смогу обновить сертификат в контейнера, который расположен на токене Rutoken?

5.
Цитата:
Другими словами, технически возможно у двух сертификатов проставить ссылку на один контейнер (шаг 4), но в таком контейнере не должно быть сертификата (то есть не выполнять шаг 7).

Можете подсказать что мне даст проставление ссылки на контейнер? Я смогу воспользоваться таким сертификатом/контейнером для подписи/шифрования?
Offline two_oceans  
#4 Оставлено : 3 ноября 2020 г. 17:29:38(UTC)
two_oceans

Статус: Эксперт

Группы: Участники
Зарегистрирован: 05.03.2015(UTC)
Сообщений: 1,129
Российская Федерация
Откуда: Иркутская область

Сказал(а) «Спасибо»: 75 раз
Поблагодарили: 257 раз в 241 постах
Автор: Иван Тимохин Перейти к цитате
1.
Цитата:
если заранее известно в каком формате файл сертификата (DER, не BASE64), тогда можно сразу вызвать CertCreateCertificateContext на прочитанном блобе

Значит в CertCreateCertificateContext могу просто передать указатель на массив BYTE и длину массива из CERT_BLOB, так?
Да, с учетом, что формат должен быть именно такой - одиночный сертификат (DER, не BASE64). В случае фактического формата p7b требуется сначала извлечь сам сертификат из более сложной структуры, об чуть этом ниже.

Проблема с комбайном определения формата CryptQueryObject вероятно связана с тем, что полученный от УЦ формат данных можно разобрать несколькими способами - в том числе, как подпись p7s или как связку сертификатов p7b. При разборе связки первым идет сертификат УЦ, потом конечный сертификат. Если извлекся сертификат УЦ, он конечно не будет соответствовать контейнеру. В случае снятия подписи как p7s извлекается вообще что-то непонятное.

Корректная альтернатива для чтения p7b на мой взгляд - открыть файл как хранилище сертификатов функцией CertOpenStore с параметром CERT_STORE_PROV_FILENAME(_W) или CERT_STORE_PROV_PKCS7 потом перечислить функцией CertEnumCertificatesInStore сертификаты. В результате получится такой же PCERT_CONTEXT, как при CertCreateCertificateContext. Правда, не забываем, что сертификат УЦ тоже там присутствует и может идти впереди.
Автор: Иван Тимохин Перейти к цитате
2.
Цитата:
Вообще меня терзают сомнения насчет правильности применения кодировки - если приложение Unicode и функции Crypt* без литеры связаны с Crypt*W, то как вообще имя контейнера оказалось в CP_ACP? Или Вы в не-Unicode приложение привязали Crypt*W функции без литеры? Тогда еще и операционную систему укажите, на *nix бывает нужен UTF-8 вместо Unicode.
Подскажие, о какой литере идет речь? О литере L?
Ведь ниже по коду я произвожу конверацию имени контейнера командой convertCharArrayToLPCWSTR(container).
Речь о литере A/W. Верно, конвертируете. Зачем? Именно про это и вопрос: например, в библиотеке на самом деле нет функции CryptAcquireContext, есть 2 функции: CryptAcquireContextA для char и CryptAcquireContextW для wchar. Однако заголовочный файл в зависимости от поддержки Unicode в самом приложении вводит синоним CryptAcquireContext (без A/W на конце) - для CryptAcquireContextA если поддержки Unicode нет или для CryptAcquireContextW если поддержка есть. Между прочим, функция CryptAcquireContextA на современных windows вызывает внутри MultiByteToWideChar и CryptAcquireContextW.

В коде использован именно этот синоним CryptAcquireContext без A/W и переданы данные в wchar (Unicode). Вариант первый, заголовочный файл от Microsoft, тогда программа с поддержкой Unicode. Однако если программа Unicode, то объявленная в коде строка констант тоже уже по идее должна быть в wchar. Соответственно вопрос зачем ее переобъявлять как char и потом вручную конвертировать в wchar? Хорошо, объявили char, тогда чем CryptAcquireContextA не угодил?
Вариант второй - в программе нет поддержки Unicode, но зачем-то изменен заголовочный файл, CryptAcquireContext указывает на CryptAcquireContextW вместо CryptAcquireContextA. Тогда конвертация немного лишняя, так как можно положиться на автоматику в CryptAcquireContextA.
Автор: Иван Тимохин Перейти к цитате
3.
Цитата:
не совсем понятно почему вызываете CryptAcquireContext вместо попытки сразу проверить установленную в 4) ссылку на контейнер функцией CryptAcquireCertificatePrivateKey

Можете подсказать для чего это нужно делать? Для проверки корректности результата работы CertSetCertificateContextProperty?
Дело в том, что CertSetCertificateContextProperty не проверяет корректность ссылки на контейнер, просто сохраняет ссылку, не вызывая сам криптопровайдер. Из-за этого не помешает включить шаг проверки, что ссылка на контейнер корректна с точки зрения криптопровайдера. CryptAcquireCertificatePrivateKey производит извлечение ссылки CertGetCertificateContextProperty и CryptAcquireContext внутри. Делать так или иначе конечно Вам решать. Мне привычнее CryptAcquireCertificatePrivateKey.
Автор: Иван Тимохин Перейти к цитате
4.
Цитата:
Можете не трудиться, замена сертификата в контейнере КриптоПро штатно не поддерживается.

Получается, я не смогу обновить сертификат в контейнера, который расположен на токене Rutoken?
Уточню, я проверял на контейнерах, созданных КриптоПро напрямую (в реестре и на флешке), там: один сертификат на один ключ (в одном контейнере могут быть максимум два ключа - один AT_KEYEXCHANGE и один AT_SIGNATURE), после первичной установки обновить сертификат штатно нельзя, ни через API, ни штатными утилитами - возвращается код ошибки.

С токеном, у которого есть самостоятельное API, ситуация может быть иной. При обращении к токену криптопровайдер КриптоПро только промежуточное звено, сам контейнер создается API токена, при активном режиме токена можно вообще без КриптоПро подписывать. Тем не менее предположу, что КриптоПро не даст заменить сертификат и на токене.
Автор: Иван Тимохин Перейти к цитате
5.
Цитата:
Другими словами, технически возможно у двух сертификатов проставить ссылку на один контейнер (шаг 4), но в таком контейнере не должно быть сертификата (то есть не выполнять шаг 7).
Можете подсказать что мне даст проставление ссылки на контейнер? Я смогу воспользоваться таким сертификатом/контейнером для подписи/шифрования?
Теоретически сможете, а практически зависит от программы, где используется ключ.
В целом для подписания шифрования нужно два объекта - сертификат и контейнер с ключевой парой, остальное только упрощает их нахождение и сопоставление. Есть разные подходы подходы к поиску ключей и сертификатов:

Первый - перечислять контейнеры, потом находить сертификаты в хранилище. Для этого достаточно уметь прочитать открытый ключ из контейнера (даже если в контейнере нет сертификата, открытый ключ все равно там есть), по открытому ключу можно найти сертификат в хранилище. Плюс в том, что ссылка в хранилище не очень-то нужна. Минус в том, что сертификат должен быть в хранилище; время обращения к контейнеру само по себе большое, а тут время всего поиска пропорционально произведению количества контейнеров на количество сертификатов. С учетом что обычно количества сравнимые выходит почти квадратичное от количества сертификатов. Токен должен быть подключен чтобы показался сертификат.

Второй подход - перечислять контейнеры, потом находить сертификаты в контейнере. Плюс в том, что хранилище вообще не нужно, как и ссылка в нем; гарантировано что если нашлось, то контейнер вставлен. Минус - что сертификат должен присутствовать в контейнере. Токен должен быть подключен чтобы показался сертификат. Данный подход реализован в плагине госуслуг: если в контейнере нет сертификата, ничего не будет отображено. Плагин госуслуг еще чего-то сверяет по хранилищу, так что время выходит как у первого варианта (квадратичное). А уж если подключить токен и в хранилище 15-16 сертификатов, то можно успеть подремать пока браузер нарисует список сертификатов для госуслуг.

Третий подход - наоборот перечислять сертификаты в хранилище и по ссылке переходить к контейнеру. Плюс - контейнеры не перечисляются, обращение идет к тому, что указан в ссылке в хранилище у конкретного сертификата; общее время поиска линейно зависит от числа сертификатов; в контейнере может не быть сертификата. Если же при неопределенности где искать сертификат перечисление контейнеров все же идет, то пробуются в фоне только 3 контейнера, потом предлагается указать нужный контейнер явно. Минус - ссылка на контейнер должна быть в хранилище; контейнер на токене может оказаться не подключен в данный момент, тогда выйдет предложение подключить токен. Данный подход реализован в плагине КриптоПро. Уже при 4-6 сертификатах третий подход значительно быстрее чем первые два. Например, удобно в программе запомнить отпечаток сертификата, для него есть стандартный поиск по хранилищу сертификатов.

В итоге, можно сказать, что устанавливая сертификат в контейнер оптимизируется поиск для второго подхода. Устанавливая сертификат в хранилище - для первого и третьего. Устанавливая ссылку на контейнер в хранилище - для третьего. Приведенный отрезок кода добавляет в контейнер и прописывает ссылку на контейнер, то есть универсально задуман на все три подхода (но не добавляет сертификат в хранилище!), а при использовании конкретного подхода можно часть шагов пропустить.
RSS Лента  Atom Лента
Пользователи, просматривающие эту тему
Быстрый переход  
Вы не можете создавать новые темы в этом форуме.
Вы не можете отвечать в этом форуме.
Вы не можете удалять Ваши сообщения в этом форуме.
Вы не можете редактировать Ваши сообщения в этом форуме.
Вы не можете создавать опросы в этом форуме.
Вы не можете голосовать в этом форуме.