Проверьте файл подписи (PKCS7) с помощью WinCrypt или CNG.
Мне нужно проверить подписанный JAR-файл, используя методы крипто API Windows. У меня есть только базовое понимание вопросов шифрования и подписи. Я также новичок в этих крипто API (WinCrypt, Bcrypt, Ncrypt). Проверка файловых хэшей не была проблемой, но часть подписи блокирует меня.
Благодаря OpenSSL, PKCS7 RFC ( https://tools.ietf.org/html/rfc2315) и различным другим источникам мне удалось выяснить фактическое содержимое файла META-INF/LOCALSIG.DSA, содержащегося в JAR. Но после двух недель копания, проб и ошибок я все еще застрял и не знаю, что еще попробовать.
OpenSSL имеет хорошую команду openssl smime -verify -inform der -in LOCALSIG.DSA -content LOCALSIG.SF -noverify
, который делает именно то, что я хочу сделать. К сожалению, я не смог найти такую высокоуровневую команду в Windows API.
Я пытался использовать VerifySignature
Семейство функций из всех трех API, но мне нужно предоставить открытый ключ для них, и мне не повезло, используя какой-либо из ImportKey
функции. Поэтому я попытался вручную проанализировать формат ASN1, используя CryptDecodeObjectEx
, чтобы передать отдельные части в виде больших двоичных объектов в функции API. Хотя у меня был некоторый успех с этим, я снова застрял, потому что я не могу понять, как анализировать наборы. Я не хочу писать свой собственный парсер ASN1 с нуля...
Итак, как мне использовать файл подписи PKCS7 с API-интерфейсами шифрования Windows?
Я думаю, что было бы проще использовать OpenSSL, но тогда мне пришлось бы убедить моего работодателя добавить OpenSSL в нашу кодовую базу только для этой цели...
ОБНОВИТЬ:
Файл LOCALSIG.DSA содержит сертификат подписавшего и подписанный хеш файла LOCALSIG.SF. Это можно проверить используя openssl pkcs7 -inform der -print_certs -text -in LOCALSIG.DSA
или же openssl cms -cmsout -inform DER -print -in LOCALSIG.DSA
,
Сертификат подписывается нашей компанией и в магазине сертификатов. Возможно, мне придется обеспечить всю цепочку доверия. Вот почему я добавил -noverify
возможность openssl smime -verify
,
Фактически, существует два сценария с разными сертификатами (внутренний и внешний выпуски), один из которых использует DSA (файл sig содержит один сертификат), другой - RSA (файл sig содержит три сертификата). Это означает, что я не могу жестко указать какой сертификат или метод использовать. Мне нужно извлечь эту информацию из предоставленного файла. Как я могу это сделать?
2 ответа
Как я понял из вашего вопроса, вам нужно проверить PKC7 с отдельной подписью. Для этого вы можете использовать функцию CryptVerifyDetachedMessageSignature.
Образец кода:
CRYPT_VERIFY_MESSAGE_PARA vparam = { 0 };
vparam.cbSize = sizeof(CRYPT_VERIFY_MESSAGE_PARA);
vparam.dwMsgAndCertEncodingType = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
vparam.pfnGetSignerCertificate = nullptr;
vparam.pvGetArg = nullptr;
/* read your files somehow */
std::vector<char> sig;
if (!ReadFile("LOCALSIG.DSA", &sig))
{
std::cout << "Failed to read file" << std::endl;
return;
}
std::vector<char> mes;
if (!ReadFile("LOCALSIG.SF", &mes))
{
std::cout << "Failed to read file" << std::endl;
return;
}
/* CryptVerifyDetachedMessageSignature requires array of messages to verify */
const BYTE* marray[] = {
reinterpret_cast<BYTE*>(mes.data())
};
DWORD marray_len[] = {
static_cast<DWORD>(mes.size())
};
if (!CryptVerifyDetachedMessageSignature(vparam,
0,
reinterpret_cast<BYTE*>(sig.data()),
static_cast<DWORD>(sig.size()),
1, /* number of messages in marray */
marray,
marray_len,
nullptr))
{
std::cout << "Failed to verify signature, error: " << GetLastError() << std::endl;
}
else
{
std::cout << "Verify success, signature valid" << std::endl;
}
ОБНОВИТЬ
Для проверки цепочки сертификатов вам необходимо использовать CertGetCertificateChain
Образец кода:
PCCERT_CHAIN_CONTEXT pChain = nullptr;
CERT_CHAIN_PARA chainPara = {0};
HCERTSTORE hStore = nullptr;
/* you sig file */
DATA_BLOB db = {
static_cast<DWORD>(sig.size()), reinterpret_cast<BYTE *>(sig.data())};
/* you can open your sig file as certificate store */
hStore = CertOpenStore(CERT_STORE_PROV_PKCS7, X509_ASN_ENCODING | PKCS_7_ASN_ENCODING, NULL, 0, reinterpret_cast<BYTE *>(&db));
if (!hStore)
{
goto Exit;
}
chainPara.cbSize = sizeof(CERT_CHAIN_PARA);
chainPara.RequestedUsage.dwType = USAGE_MATCH_TYPE_AND;
chainPara.RequestedUsage.Usage.cUsageIdentifier = 0;
if (!CertGetCertificateChain(NULL, /* use default chain engine */
pCert, /* pCert - pointer to signer cert structure (the parameter that was obtained in the previous step) */
NULL,
hStore, /* point to additional store where need to search for certificates to build chain */
&chainPara,
CERT_CHAIN_REVOCATION_CHECK_CHAIN_EXCLUDE_ROOT,
NULL,
&pChain))
{
std::cout << "failed to build chain: " << GetLastError();
goto Exit;
}
if (pChain->TrustStatus.dwErrorStatus == CERT_TRUST_NO_ERROR)
{
std::cout << "certificate valid";
goto Exit;
}
if (pChain->TrustStatus.dwErrorStatus & CERT_TRUST_REVOCATION_STATUS_UNKNOWN)
{
std::cout << "certificate revocation status unknown";
}
/* you need to place root certificate to the Trusted Root Store */
if (pChain->TrustStatus.dwErrorStatus & CERT_TRUST_IS_UNTRUSTED_ROOT)
{
std::cout << "untrusted CA";
}
/* and so on */
Exit :
if (pCert)
{
CertFreeCertificateContext(pCert);
}
if (pChain)
{
CertFreeCertificateChain(pChain);
}
if (hStore)
{
CertCloseStore(hStore, 0);
}
Я не уверен, есть ли у нас вся необходимая информация, поэтому у меня нет однозначных ответов. Тем не менее, давайте посмотрим, сможем ли мы добиться прогресса в направлении решения.
Вы спрашивали:
Как узнать, какой сертификат загрузить и откуда?
Вы, как проверяющий, должны иметь этот доверенный сертификат в той или иной форме. В Windows было бы хорошо, если бы этот сертификат был установлен в вашем хранилище сертификатов. Это то, что я предположил в своем ответе. Если вы не знаете, где находится сертификат, вам придется сначала выяснить это, в противном случае вы не сможете должным образом проверить подпись. Таким образом, подписчик должен предоставить сертификат верификатору (вам), через доверенный канал.
Вы также написали:
OpenSSL имеет хорошую команду
openssl smime -verify -inform der -in LOCALSIG.DSA -content LOCALSIG.SF -noverify
, который делает именно то, что я хочу сделать.
Вы уверены, что это именно то, что вы хотите сделать? Так как вы проходите в -noverify
флаг, все, что делает эта команда, проверяет, что LOCALSIG.DSA
содержит правильную подпись содержимого LOCALSIG.SF
, Однако он не подтверждает, что эта подпись была создана удостоверением, которому вы доверяете. Любой с действительным сертификатом и парой ключей мог создать эту подпись.
Чтобы проверить подпись, в том числе личность подписавшего, в версии stock-openssl, вам нужна вся цепочка сертификатов, доступная вам в формате, понятном openssl (PEM или DER), вплоть до самозаверяющего корня сертификат. Тогда вы можете проверить это с помощью немного другой команды
openssl smime -verify -inform der -in LOCALSIG.DSA -content LOCALSIG.SF -CAfile trusted-cert.pem
Ваш вопрос обновлен, чтобы указать, что сертификат подписи самоподписан и находится в хранилище сертификатов. Есть еще несколько возможных маршрутов, но я предполагаю, что на данный момент вы знаете, каково имя субъекта подписавшего, который, как вы ожидаете, сгенерировал эту подпись. (Если нет, то укажите, что вы ожидаете.) Получив эту информацию, вы можете использовать следующие шаги для проверки вашей подписи:
Первое использование CertEnumCertificatesInStore()
посмотреть сертификат в хранилище сертификатов. Пример использования см. В разделе " Пример программы C": перечисление сертификатов в хранилище.
Когда у вас есть дескриптор этого сертификата, используйте CryptImportPublicKeyInfoEx2()
получить дескриптор необходимого открытого ключа.
Та ручка, которая имеет тип BCRYPT_KEY_HANDLE
Вы можете использовать с BCryptVerifySignature()
сделать проверку.