13.12.2007 11:04:09Ошибка подписи хэша Ответов: 15
Денис
При вызове функции CryptSignHash()
Получаю ошибку (0x8009000D) - Key does not exist.
Сертификат ГОСТ 34.10-2001. Аналогичный сертификат по ГОСТ 34.10-94 подписывает на ура.
 
Ответы:
13.12.2007 11:05:47Денис
Забыл написать: csp 2.0
13.12.2007 11:59:23Kirill Sobolev
Странный у Вас код - в вызове CryptSignHash сертификат не участвует. Можете рассказать поподробнее про порядок вызова функций?
13.12.2007 12:11:30Денис
STDMETHODIMP CSignMaker::OpenKeyContainer(BSTR certSubj, CertStoreLocation keyLoc, BSTR certNum)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
DWORD dwFlags = 0;
HCRYPTPROV hCertStore = NULL;
DWORD dwNameLen = 0;
LPCWSTR nm = NULL;
BYTE* name = NULL;
DWORD dwKeySpec;
BOOL fCallerFreeProv;
try{
CloseKeyContainer();
dwFlags = (keyLoc==CSL_LOCAL_MACHINE) ? CERT_SYSTEM_STORE_LOCAL_MACHINE
: CERT_SYSTEM_STORE_CURRENT_USER;
F_hCertStore = CertOpenStore(CERT_STORE_PROV_SYSTEM, MY_CERT_ENCODING, F_hCrProv, dwFlags, L"MY");
if(!F_hCertStore) ThrowLastErrorString("CertOpenStore()");
nm = ((CComBSTR)certSubj).Detach();
CComBSTR _certNum = certNum;
_certNum.ToUpper();
string sn0 = (certNum != NULL) ? (string)(_bstr_t)_certNum : "";
F_phCert = NULL;
while(true){
F_phCert = CertFindCertificateInStore(F_hCertStore, MY_CERT_ENCODING, 0, CERT_FIND_SUBJECT_STR_A, nm, F_phCert);
if(!F_phCert) ThrowLastErrorString("CertFindCertificateInStore()");
string sn1 = CertNoToHex(F_phCert->pCertInfo->SerialNumber.pbData, F_phCert->pCertInfo->SerialNumber.cbData);
if((sn0 == "") || (sn0 == sn1)) break;
}
if(!CryptAcquireCertificatePrivateKey(F_phCert, MY_ACQUIRE_FLAG, NULL, &F_hCrProv, &dwKeySpec, &fCallerFreeProv))
ThrowLastErrorString("CryptAcquireCertificatePrivateKey()");
if(!fCallerFreeProv) ThrowStringException("Certificate Public Key Is Not Usable.");
if(!CryptImportPublicKeyInfo(F_hCrProv, MY_CERT_ENCODING, &(F_phCert->pCertInfo->SubjectPublicKeyInfo), &F_hUserKey))
ThrowLastErrorString("CryptImportPublicKeyInfo()");
}
catch(string &e){
return Error(e.c_str(), IID_ISignMaker);
}
return S_OK;
}

STDMETHODIMP CSignMaker::Sign(BSTR b64str, BSTR* res)
{
AFX_MANAGE_STATE(AfxGetStaticModuleState());
try{
CloseHash();
if(!CryptCreateHash(F_hCrProv, CRYPTO_PRO_ALG_ID, NULL, 0, &F_hHash))
ThrowLastErrorString("CryptCreateHash()");
string b64s = (string)(_bstr_t)b64str;
BYTE* pData = (BYTE*)alloca(DATA_BUF_LEN + 1);
DWORD dwDataLen;
while((dwDataLen = Base64_ToBin(b64s, (char*)pData, DATA_BUF_LEN)) > 0){
if(!CryptHashData(F_hHash, pData, dwDataLen, 0))
ThrowLastErrorString("CryptHashData()");
}
DWORD dwSignLen = 1024; // если без вычисления размера хэша (должно хватить по любому)
if(!CryptSignHash(F_hHash, AT_SIGNATURE, NULL, 0, NULL, &dwSignLen))
ThrowLastErrorString("CryptSignHash(NULL)");
pData = (BYTE*)alloca(dwSignLen + 128); // На всякий случай !
if(!CryptSignHash(F_hHash, AT_SIGNATURE, NULL, 0, pData, &dwSignLen))
ThrowLastErrorString("CryptSignHash(data)");
b64s = Bin_ToBase64((char*)pData, dwSignLen);
*res = CComBSTR(b64s.c_str()).Detach();
}
catch(string &e){
CloseHash();
return Error(e.c_str(), IID_ISignMaker);
}
CloseHash();
return S_OK;
}
13.12.2007 12:13:09Денис
#define CRYPTO_PRO_TYPE 71
#define CRYPTO_PRO_NEW_TYPE 75
#define CRYPTO_PRO_ALG_ID 32798

#define MY_CERT_ENCODING (X509_ASN_ENCODING | PKCS_7_ASN_ENCODING)
#define MY_NAME_ENCODING (CERT_X500_NAME_STR | CERT_NAME_STR_ENABLE_UTF8_UNICODE_FLAG | CERT_NAME_STR_REVERSE_FLAG)
#define MY_ACQUIRE_FLAG (CRYPT_ACQUIRE_COMPARE_KEY_FLAG | CRYPT_ACQUIRE_SILENT_FLAG)
13.12.2007 13:00:23Юрий
Здесь скорее всего в ключевом контейнере отсутствует сгенерированый ключ для AT_SIGNATURE / AT_KEYEXCHANGE.

Сгенерируйте предварительно ключ и все будет впорядке.
13.12.2007 13:52:05Денис
Оба сертификата лежат в хранилище сертификатов, в одном каталоге. Оба при просмотре показывают наличие секретного ключа. Инсталлировались абсолютно одинаково. Один подписывает, другой - нет.
13.12.2007 14:37:25Юрий
Не важно какие сертификаты где лежат, главное что в ключевых контейнерах, с которыми ассоциированы эти сертификаты, должны быть соответствующие ключи.
Смотреть в MSDN, CryptGenKey.
13.12.2007 14:48:53Денис
Все ключи и сертификаты устанавливались через Copy container + Install personal certificate. У обоих сертификатов есть право Client authentication (1.3.6.1.5.5.7.3.2). У того, который не работает, есть еще Secure Email (1.3.6.1.5.5.7.3.4) и Пользователь Центра Регистрации (1.2.643.2.2.34.6).
Более того, оба сертификата подписывают через CAPICOM.
13.12.2007 15:01:53Юрий
Мне все-равно как сертификаты были установлены или сделаны. Я говорю, что Вам нужно сделать для того чтобы Ваш же код у Вас же работал.
13.12.2007 15:27:26Денис
Я как-то предполагал, что функция CryptAcquireCertificatePrivateKey (см.выше) и предназначена для загрузки секретного ключа из контейнера. Не совсем понятно зачем мне генерить новую пару ключей на лету. Такой вариант нужен, скорее, для выработки сеансового ключа.

Вопрос №2: по содержимому файлов name.key, header.key, primary.key, masks.key, primary2.key, masks2.key можно как-то понять разрешено ли ставить подпись на ключе из данного контейнера? Может какой битовый флаг посмотреть?
13.12.2007 15:34:46Юрий
То есть Вы предполагаете, что следующая последовательность успешно сгенерирует пары ключей?
1) CryptAcquireContext(..., CRYPT_NEWKEYSET)
2) CryptReleaseContext
13.12.2007 15:44:24Kirill Sobolev
А почему у Вас в CryptSignHash жестко прописано AT_SIGNATURE? Это неправильно, там должна быть переменная dwKeySpec, которую возвращает CryptAcquireCertificatePrivateKey.
13.12.2007 16:02:03Денис
То ли я тупой, то ли вы о чем-то не о том...
CertOpenStore() - открываем хранилище
CertFindCertificateInStore() - находим сертификат
CryptAcquireCertificatePrivateKey() - грузим секретный ключ сертификата и получаем криптопровайдера
CryptImportPublicKeyInfo() - импортируем открытый ключ в криптопровайдер.
Теоретически, если нет ошибок, криптопровайдер готов к подписи. Что и происходит в случае с первым сертификатом.
Почему не работает второй? ГОСТ 2001 года предусматривает принципиально другой алгоритм подписи? С выработкой дополнительных пар ключей?

Действительно, в переменной dwKeySpec возвращается AT_KEYEXCHANGE.
13.12.2007 16:15:42Денис
Заработало! После подстановки dwKeySpec в CryptSignHash.
Спасибо!
13.12.2007 16:47:55Kirill Sobolev
Для подписи не надо импортировать ОК, это надо для проверки.
Алгоритм подписи там действительно не такой, как в 94 из-за другого алгоритма ОК, но тут дело было не в этом. В контейнере могут быть 2 ключа - подписи и обмена, в Вашем случае на 94 ГОСТе ключ AT_SIGNATURE был, а на 2001- нет, поэтому и не работало. А подписывать можно и ключами подписи, и ключами обмена.