2.1. Подготовка к проверке
Замечу, что программа 32-битная, но полностью поддерживает проверку образов в 64-разрядных папках, так что далее по коду будут манипуляции с
файловым переадресатором.
Прототип функции проверки в модуле 'modDigiSign' выглядит таким образом:
Public Function SignVerify(
sFilePath As String, _
ByVal Flags As FLAGS_SignVerify, _
SignResult As SignResult_TYPE) As Boolean
Сперва обнуляется пользовательская структура результатов проверки (SignResult), включается файловый редиректор, комбинируются флаги (Flags), подготавливается локальный кеш, проверяется возможность загрузки библиотеки Wintrust.dll, наличие проверяемого файла на диске, готовятся CLSID провайдеров проверки:
DRIVER_ACTION_VERIFY – для проверки WHQL подписи драйвера.
WINTRUST_ACTION_GENERIC_VERIFY_V2 – для проверки подписи остальных файлов.
Примечание: сами провайдеры состоят из файлов библиотек и записей реестра:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Defaults\Provider
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Cryptography\Providers\Trust
Далее получаем дескриптор контекста административного каталога, указав оптимальный алгоритм хеширования подписи:
CryptCATAdminAcquireContext2 hCatAdmin, VarPtr(DRIVER_ACTION_VERIFY), StrPtr(BCRYPT_SHA256_ALGORITHM), 0&, 0&
Здесь и далее для Windows 8 и выше используются функции с постфиксом «2».
Получая хендл к файлу, временно отключаем редиректор:
RedirResult = ToggleWow64FSRedirection(False, sFilePath)
hFile = CreateFile(StrPtr(sFilePath), GENERIC_READ, FILE_SHARE_READ Or FILE_SHARE_WRITE, ByVal 0&, OPEN_EXISTING, ByVal 0&, ByVal 0&)
If RedirResult Then ToggleWow64FSRedirection True
Примечание: редиректор отключается только для конкретных путей.
Получаем размер файла. Если он превышает
MAX_FILE_SIZE (100 MB.) и не указан флаг игнорирования (
SV_NoFileSizeLimit), выходим из программы. Лимит взят навскидку для защиты от подвисаний.
Далее рассчитываем хеш файла:
CryptCATAdminCalcHashFromFileHandle2(hCatAdmin, hFile, HashSize, aFileHash(0), 0&)
Проводится поиск хеша в каталогах безопасности:
CatalogContext = CryptCATAdminEnumCatalogFromHash(hCatAdmin, aFileHash(0), HashSize, 0&, ByVal 0&)
Модуль
modDigiSign ищет только первое совпадение. Есть ничтожно маленький шанс, что хеш этого файла попадётся в ещё одном каталоге. Если требуется перечислить все каталоги с заданным для поиска хешем, укажите последним параметром хендл результата предыдущего вызова функции.
Если файл был подписан через каталог, мы получим его контекст и затем можем запросить полный путь к файлу каталога безопасности (
.cat) в структуру
CATALOG_INFO:
If CryptCATCatalogInfoFromContext(CatalogContext, CatalogInfo, 0&) Then
Если контекст не был получен, предполагаем, что проверяемая подпись – внутренняя (embedded).
Дальше мы заполняем структуры в зависимости от физического расположения подписи – внутренняя или внешняя (подписанная через каталог).
Файл может быть подписан одновременно обеими. По-умолчанию, в этом случае проверяется подпись через каталог. Чтобы поменять приоритет, установите флаг
SV_DisableCatalogVerify. Такой флаг устанавливается автоматически при проверке вторичной подписи через флаг
SV_CheckSecondarySignature, т.к. вторичная подпись может быть только внутренней.
Также, при проверке через каталог, не заполняется поле результатов проверки
.isEmbedded. Чтобы принудительно заполнять это поле, укажите флаг
SV_CheckEmbeddedPresence.
Для проверки внутренней подписи нам нужно заполнить структуры:
где: WINTRUST_DATA ->
dwUnionChoice устанавливаем как WTD_CHOICE_FILE (для проверки внутренней подписи), или WTD_CHOICE_CATALOG (для внешней).
Возводим флаг:
WINTRUST_DATA -> dwStateAction (WTD_STATEACTION_VERIFY или WTD_STATEACTION_IGNORE)
в зависимости от того, нужно или нет получать
hWVTStateData, откуда мы сможем извлечь инфу о цепочке сертификатов в подписи.
WINTRUST_DATA -> dwProvFlags
Здесь устанавливаются специфические флаги проверки, такие как:
WTD_SAFER_FLAG – тихий режим
WTD_REVOCATION_CHECK_CHAIN – необходимость проверки на отзыв (управляется флагом SV_CheckRevocation)
WTD_LIFETIME_SIGNING_FLAG – разрешение проходить валидацию с просроченным сертификатам даже без подписи сервера времени (управляется флагом SV_AllowExpired) (подробнее см. раздел 1.3. Теория, п.3).
Для ОС Win8 и выше также запрашивается кол-во подписей. Для этого нужно заполнить структуру
WINTRUST_SIGNATURE_SETTINGS и передать указатель на неё в поле WINTRUST_DATA -> pSignatureSettings.
Для проверки внешней подписи заполняем структуры:
P.S. Не все поля в WINTRUST_CATALOG_INFO обязательны к заполнению. Например, можно не указать хендл файла, либо его хеш или тег в каталоге безопасности, тогда функция проверки сама дорасчитывает необходимое. Тем не менее, для надёжности и оптимизации скорости в моей программе заполняются все поля.
2.2. Запуск процедуры проверки и обработка результатов
Проверка выполняется функцией
WinVerifyTrust, экспортируемой WinTrust.dll, с передачей GUID провайдера политики проверки и указателя на WINTRUST_DATA.
В этот момент файловый переадресатор должен быть отключён!
ReturnVal = WinVerifyTrust(INVALID_HANDLE_VALUE, ActionGuid, VarPtr(WintrustData))
INVALID_HANDLE_VALUE – обозначает тихий режим, без UI.
Возвращаемое значение – это результат проверки. Например, 0 – успешная проверка по всем политикам.
Описание части других значений можно посмотреть, например, по ссылкам:
MSDN. CERT_CHAIN_POLICY_STATUS structure
MSDN. TRUST Error Codes
На этом этапе программа преобразует код ошибки в описание (поля .ShortMessage и .FullMessage), заполняются поля .ReturnCode, .isLegit, .isSelfSigned, корректируется поле .isSigned, а также .isEmbedded (если было затребовано флагом SV_CheckEmbeddedPresence). В таком случае вызывается функция IsInternalSignPresent(), где в структуре PE проверяется наличие указателя на SecurityDir.
Дополнительно в прокси-обработчик ошибок WriteError заложен случай проверки признака повреждения бинарных данных подписи.
В результате проверки также заполняется структура WINTRUST_SIGNATURE_SETTINGS и поле WINTRUST_DATA -> hWVTStateData.
hWVTStateData – содержит указатель на данные состояния проверки (см. далее в разделе 2.4.), по которым можно извлечь информацию о сертификатах и крос-подписях.
Поле
dwVerifiedSigIndex содержит индекс проверенной подписи.
Поле
dwIndex нужно заполнить другим индексом, если мы хотим проверить следующую подпись у файла. Такую проверку нельзя выполнить под этим же контекстом административного каталога (когда уже вызван WinVerifyTrust), поэтому для проверки вторичной подписи контекст нужно «перезагрузить», очистив ресурсы и получив новый контекст. Даже если указан флаг SV_CheckSecondarySignature, модуль modDigiSign всегда выполняет первый вызов WinVerifyTrust для получения кол-ва подписей и индекса первой из проверенных, чтобы узнать какой индекс у вторичной подписи для её последующей проверки.
2.3. Очистка ресурсов.
Для освобождения ресурсов функцию WinVerifyTrust нужно вызвать повторно, заменив в поле dwStateAction флаг на WTD_STATEACTION_CLOSE.
Контекст административного каталога освобождается функцией CryptCATAdminReleaseContext.
Контекст каталога безопасности освобождается функцией CryptCATAdminReleaseCatalogContext.
После чего закрывается хендл файла, а результат проверки сохраняется в локальный кеш.
Если ваша программа больше не нуждается в проверке ЭЦП, разумно будет выполнить принудительную очистку памяти, занятой кешем. Для этого возведите флаг
SV_CacheFree, передав пустое имя файла:
SignVerify "", SV_CacheFree, SignResult