Проверьте файл подписи (PKCS7) с помощью WinCrypt или CNG. - PullRequest
0 голосов
/ 16 января 2019

Мне нужно проверить подписанный 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 в нашу кодовую базу только для этой цели ...

UPDATE:

Файл 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 ]

0 голосов
/ 18 января 2019

Как я понял из вашего вопроса, вам нужно проверить 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;
    }

UPDATE
Для проверки цепочки сертификатов необходимо использовать 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);
}
0 голосов
/ 17 января 2019

Я не уверен, есть ли у нас вся необходимая информация, поэтому у меня нет однозначных ответов. Тем не менее, давайте посмотрим, сможем ли мы добиться прогресса в направлении решения.

Вы спросили:

Как узнать, какой сертификат загрузить и откуда?

Вы, как проверяющий, должны иметь этот доверенный сертификат в какой-либо форме. В 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() для проверки.

...