Здравствуйте, возникла следующая проблема:
Есть сторонний сервис, вложения в запросах к которому подписываются подписью в формате PKCS #7 Detached.
Реализованы средства для формирования подписи с использованием криптопровайдеров JCP и CSP.
Подпись, сделанная с помощью JCP корректно воспринимается сервисом.
Подпись же, сделанная с помощью CSP, не проходит проверку, и возвращается ошибка "Файл подписи не соответствует файлу данных".
Пробовал сделать подпись с помощью CryptoPro BrowserPlugin, подпись также не прошла проверку (ошибка была что-то вроде "Неизвестная ошибка при проверке подписи").
Также замечено, что подпись, сформированная CSP не проходит проверку через JCP.
Привожу пример исходного тестового кода программы формирования подписи средствами CSP (привожу код так, потому что не могу совладать с кодоформаттером форума):
#include <stdio.h>
#include <windows.h>
#include <wincrypt.h>
#include "crypto.h"
#include <iostream>
#include <fstream>
#pragma comment(lib, "Crypt32.Lib")
#define MY_TYPE (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)
#define CERT_STORE_NAME L"MY"
DWORD crypto() {
// Сообщение, которое мы подписываем
std::ifstream fl("D:\\req.xml");
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();
BYTE* pbMessage = (BYTE*)ret;
DWORD cbMessage = (DWORD)strlen((char*) pbMessage)+1;
// Открываем хранилище сертификатов
HCERTSTORE hStoreHandle;
if (!(hStoreHandle = CertOpenStore(
CERT_STORE_PROV_SYSTEM,
0,
NULL,
CERT_SYSTEM_STORE_CURRENT_USER,
CERT_STORE_NAME)))
{
HandleError("Нельзя открыть хранилище MY.");
}
// Получаем указатель на наш сертификат
PCCERT_CONTEXT pSignerCert = NULL;
// берем для теста первый и единственный сертификат в хранилище
pSignerCert = CertEnumCertificatesInStore(hStoreHandle, pSignerCert);
// Переменные для указателя и длины подписи
BYTE *pbSignedMessageBlob;
DWORD cbSignedMessageBlob;
// Создаем и заполняем структуру для создания цифровой подписи
CRYPT_SIGN_MESSAGE_PARA SigParams;
SigParams.cbSize = sizeof(CRYPT_SIGN_MESSAGE_PARA);
SigParams.dwMsgEncodingType = MY_TYPE;
SigParams.pSigningCert = pSignerCert;
SigParams.HashAlgorithm.pszObjId = szOID_CP_GOST_R3411;
SigParams.HashAlgorithm.Parameters.cbData = NULL;
SigParams.cMsgCert = 1;
SigParams.rgpMsgCert = &pSignerCert;
SigParams.cAuthAttr = 0;
SigParams.dwInnerContentType = 0;
SigParams.cMsgCrl = 0;
SigParams.cUnauthAttr = 0;
SigParams.dwFlags = 0;
SigParams.pvHashAuxInfo = NULL;
SigParams.rgAuthAttr = NULL;
const BYTE* MessageArray[] = {pbMessage};
DWORD MessageSizeArray[1];
MessageSizeArray[0] = cbMessage;
// Получаем длину буфера подписи
if(CryptSignMessage(
&SigParams, // указатель на SigParams
TRUE, // подпись создается отдельно
1, // число сообщений
MessageArray, // сообщение
MessageSizeArray, // длина сообщения
NULL, // буфер для подписи
&cbSignedMessageBlob)) // размер буфера
{
printf("Размер подписи %d.\n",cbSignedMessageBlob);
}
else {
HandleError("Ошибка CryptSignMessage.");
}
// выделяем память под подпись
pbSignedMessageBlob = new BYTE[cbSignedMessageBlob];
if(!pbSignedMessageBlob) {
HandleError("Ошибка.");
}
// формируем подпись
if(!CryptSignMessage(
&SigParams, // указатель на SigParams
TRUE, // подпись создается отдельно
1, // число сообщений
MessageArray, // сообщение
MessageSizeArray, // длина сообщения
pbSignedMessageBlob, // буфер для подписи
&cbSignedMessageBlob)) // размер буфера
{
HandleError("Ошибка");
}
verifyMessage(pbMessage, pbSignedMessageBlob, cbSignedMessageBlob, pSignerCert);
if(pSignerCert)
CertFreeCertificateContext(pSignerCert);
if(CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG)) {
printf("\nХранилище закрыто. \n");
}
else {
printf("Ошибка!");
}
if(pbSignedMessageBlob) {
FILE *outfile_f = fopen("D:\\req.xml.sig", "wb");
if(!outfile_f) {
printf("Output file not found!");
}
fwrite(pbSignedMessageBlob, sizeof(BYTE), cbSignedMessageBlob, outfile_f);
fclose(outfile_f);
}
return cbSignedMessageBlob;
}
void HandleError(char *s) {
printf("Ошибка. \n");
printf("%s\n",s);
printf("Ошибка N %x.\n", GetLastError());
printf("Программа завершена с ошибкой. \n");
exit(1);
}
// функция обратного вызова для структуры
// CRYPT_VERIFY_MESSAGE_PARA VerifyParams;
PCCERT_CONTEXT WINAPI MyGetSignerCertificateCallback(
void *pvGetArg, // in
DWORD dwCertEncodingType, // in
PCERT_INFO pSignerId, // in
HCERTSTORE hMsgCertStore // in
)
{
return PCCERT_CONTEXT(pvGetArg);
};
void verifyMessage(BYTE* pbMessage, BYTE* pbArray, DWORD cbArray, PCCERT_CONTEXT pSignerCert) {
DWORD cbMessage = (DWORD)strlen((char*) pbMessage)+1;
CRYPT_VERIFY_MESSAGE_PARA VerifyParams;
// Заполнение структуры для верификации
VerifyParams.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA);
VerifyParams.dwMsgAndCertEncodingType = MY_TYPE;
VerifyParams.hCryptProv = 0;
VerifyParams.pfnGetSignerCertificate = MyGetSignerCertificateCallback;
VerifyParams.pvGetArg = (void*)pSignerCert;
const BYTE* MessageArray[] = {pbMessage};
DWORD MessageSizeArray[1];
MessageSizeArray[0] = cbMessage;
// верификация подписи
if(CryptVerifyDetachedMessageSignature(
&VerifyParams, // указатель на структуру VerifyParams
0, //
pbArray, // указатель на подпись
cbArray, // длина подписи
1, // число сообщений
MessageArray, // сообщение
MessageSizeArray, // длина сообщения
&pSignerCert)) // указатель на сертификат
{
printf("\nВерификация прошла успешно!.\n");
}
else
{
HandleError("Верификация не прошла.");
}
}
Сертификат был импортирован в хранилище через CSP.
В процессе выполнения программа дважды запрашивала пароль к контейнеру (из которого сертификат экспортировался), верификация подписи прошла успешно.
Хотелось бы узнать мнение более опытных пользователей по данному вопросу.
Может что-то в вышеприведенном коде не верно... И вообще, по вопросу совместимости CSP и JCP.