01.08.2006 10:41:14Как узнать имя сертификта в контейнере (дискета) через CryptoAPI ? Ответов: 7
А.Белоусов
Провайдер проинициализирован, имя контейнера вытащил.
Просмотрел MSDN, ничего подходящего не увидел, но винда это как то делает.
Цель проста - узнать имя сертификата на дискете - носителе закрытого ключа.
 
Ответы:
01.08.2006 11:01:01Василий
А что, в Вашем понимании, есть "имя сертификата"?
01.08.2006 11:13:27А.Белоусов
Ну - это та строка, которую я вижу в консоли или в свойствах сертификата и которую я передаю в CertFindCertificateInStore (преобразованную в юникод).
01.08.2006 13:15:45Василий
Понятно. Стало быть, речь о компонентах имени поля Subject сертификата.

Три варианта.
1. Сертификат установлен в ключевой контейнер на дискете.
Тогда, после открытия контейнера с дискеты можно считать ключ - CryptGetUserKey, если известен его тип - AT_KEYEXCHANGE или AT_SIGNATURE (если не известен - попробовать оба). Далее вызов CryptGetKeyParam с параметром KP_CERTIFICATE - получите контекст сертификата PCCERT_CONTEXT, откуда можно будет взять любую информацию.

2. Сертификат НЕ установлен в ключевой контейнер на дискете, но установлен (с привязкой к ключевому контейнеру) в хранилище "Личные" текущего пользователя Win.
Тогда обратная ситуация - перечисляете все сертификаты из хранилища и вызываете функцию открытия ключевого контейнера, пока не попадёте на этот контейнер.

3. Сертификат НЕ установлен ни в ключевой контейнер на дискете, ни в хранилище сертификатов Win с привязкой к ключу, но можно перечислить "подозрительные" (в смысле, потенциально соответствующие ключевому контейнеру) сертификаты из файла(ов) или из хранилищ. Тогда, для каждого перечисляемого сертифката - импорт открытого ключа в блоб и сравнение с блобом, импортированным из контейнера.
01.08.2006 14:15:25А.Белоусов
Спасибо. Мой вариант - первый. Буду пробовать.
01.08.2006 16:21:07А.Белоусов
Не получается.
Достаю через
CryptGetKeyParam(UserKey, KP_CERTIFICATE, PByte(PCert_Context), l, 0);
(заранее определив длинну блока) информацию о сертификате. Однако она не содержит PCCERT_CONTEXT.
И длинна блока - 971 байт (явно не оно).
Посмотрел справку в MSDN - для CryptGetKeyParam с параметром KP_CERTIFICATE там написано :
A buffer containing the DER-encoded X.509 certificate. The public key in the certificate must match the corresponding signature or exchange key.
Попробовал засунуть буфер в строку - в ней явно видна текстовая информация (на английском, русских букв не видно). Как теперь из нее имя сертификата вытащить ?
01.08.2006 17:16:08Василий
Пример:

HCRYPTPROV hCertContainer = 0;
HCRYPTKEY hKey = 0;
LPBYTE pbUserCert;

DWORD dwCertLen;
PCCERT_CONTEXT pUserCert=0;
DWORD dwUserCertLength=0;




if(!CryptAcquireContext(&hCertContainer,
"888", //это имя контейнера
NULL,
75, //это тип CSP - ГОСТ ..2001
0
))
HandleError("CryptAcquireContext");

if(!CryptGetUserKey(
hCertContainer,
AT_KEYEXCHANGE,
&hKey))

HandleError("CryptGetUserKey");

/* Получить сертификат.*/
if (!CryptGetKeyParam (hKey, KP_CERTIFICATE, NULL,
&dwUserCertLength, 0)) {
HandleError ("Error during GetKeyParam.\n");

}
pbUserCert = malloc (dwUserCertLength);
if (pbUserCert == NULL) {
HandleError ("Error during malloc.\n");

}
if (!CryptGetKeyParam (hKey, KP_CERTIFICATE, pbUserCert,
&dwUserCertLength, 0)) {
HandleError ("Error during GetKeyParam.\n");

}
/* Декодировать сертификат */
pUserCert = CertCreateCertificateContext (
X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, pbUserCert,
dwUserCertLength);
if (pUserCert == NULL) {
HandleError ("Error during CertCreateCertificateContext.\n");

}


CryptReleaseContext(hCertContainer,0);
02.08.2006 9:12:32А.Белоусов
Спасибо еще раз. Нашел я сам, как надо было делать.
Получилось примерно так :

function GetCertNameFromCurrentContainer() : String;
var l : Cardinal;
CryptProv : HCRYPTPROV;
UserKey : HCRYPTKEY;
ProvName : String;
ProvType : Cardinal;
Container : String;
Cert_Info : String;
Cert_Context : PCCERT_CONTEXT;
TypePara : Cardinal;
Cert_Name : String;
begin
{
// Достать имя и тип первого провайдера
// Оставляю этот закомментаренный блок на случай, если понадобится узнать имя и тип провайдера
R := CryptEnumProviders(0, nil, 0, ProvType, nil, l);
if not R then begin ShowMessage('Ошибка узнавания длинны имени провайдера'); exit; end;
SetLength(ProvName, l);
R := CryptEnumProviders(0, nil, 0, ProvType, PChar(ProvName), l);
if not R then begin ShowMessage('Ошибка узнавания имени провайдера'); exit; end;
SetLength(ProvName, l - 1);
ShowMessage('Провайдер ' + IntToStr(ProvType) + ' [' + ProvName + ']');
AddToLogText('Тип = ' + IntToStr(ProvType) + ' Имя = [' + ProvName + ']');
AddToLogText('Имя (Hex) = [' + StringToHex(ProvName) + ']');
}

ProvType := 75;
ProvName := 'Crypto-Pro GOST R 34.10-2001 Cryptographic Service Provider';
CryptProv := 0;
try
try
CheckError(CryptAcquireContext(CryptProv, nil, PChar(ProvName),
ProvType, CRYPT_VERIFYCONTEXT));
except
on E : Exception do begin
raise AddExceptionHeaderFooter(E, CryptoPro_Err_Prov_Connect, '', [ProvName]);
end;
end;
// Узнаем имя контейнера
// Размер
l := 0;
try
CheckError(CryptGetProvParam(CryptProv, PP_ENUMCONTAINERS, nil, l, CRYPT_FIRST));
except
on E : Exception do begin
raise AddExceptionHeader(E, CryptoPro_Err_Get_Container_Name_Len);
end;
end;
SetLength(Container, l);
// Собственно имя
try
CheckError(CryptGetProvParam(CryptProv, PP_ENUMCONTAINERS, PByte(PChar(Container)), l, CRYPT_FIRST));
except
on E : Exception do begin
raise AddExceptionHeader(E, CryptoPro_Err_Get_Container_Name_Len);
end;
end;
finally
if (CryptProv <> 0)
then CryptReleaseContext(CryptProv, 0);
end;
// Переподключимся к конкретному контейнеру
CryptProv := 0;
try
try
CheckError(CryptAcquireContext(CryptProv, PChar(Container), PChar(ProvName),
ProvType, 0));
except
on E : Exception do begin
raise AddExceptionHeaderFooter(E, CryptoPro_Err_Prov_Container_Connect, '', [ProvName, Container]);
end;
end;
try
CheckError(CryptGetUserKey(CryptProv, AT_KEYEXCHANGE, UserKey));
except
on E : Exception do begin
raise AddExceptionHeader(E, CryptoPro_Err_Get_Key);
end;
end;
try
CheckError(CryptGetKeyParam(UserKey, KP_CERTIFICATE, nil, l, 0));
except
on E : Exception do begin
raise AddExceptionHeader(E, CryptoPro_Err_Get_Cert_Len);
end;
end;
SetLength(Cert_Info, l);
try
CheckError(CryptGetKeyParam(UserKey, KP_CERTIFICATE, PByte(PChar(Cert_Info)), l, 0));
except
on E : Exception do begin
raise AddExceptionHeader(E, CryptoPro_Err_Get_Cert);
end;
end;
Cert_Context := nil;
try
Cert_Context := CertCreateCertificateContext(X509_ASN_ENCODING, PByte(PChar(Cert_Info)), l);
if (Cert_Context = nil) then begin
raise Exception.Create(CryptoPro_Err_CreateCertificateContext);
end;
l := 0;
l := CertGetNameString(Cert_Context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, @TypePara, nil, l);
if (l <= 0) then begin
raise Exception.Create(CryptoPro_Err_Get_Cert_Name_Len);
end;
SetLength(Cert_Name, l);
l := CertGetNameString(Cert_Context, CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, @TypePara, PChar(Cert_Name), l);
if (l <= 0) then begin
raise Exception.Create(CryptoPro_Err_Get_Cert_Name);
end;
SetLength(Cert_Name, l - 1);
Result := Cert_Name;
finally
CertFreeCertificateContext(Cert_Context);
end;
finally
if (CryptProv <> 0)
then CryptReleaseContext(CryptProv, 0);
end;
end;

Не привожу сдесь константы сообщений об ошибках и процедуру проверки результата CheckError - и без них все понятно.