Вы используете устаревший браузер. Этот и другие сайты могут отображаться в нём некорректно. Вам необходимо обновить браузер или попробовать использовать другой.
Проверка электронной цифровой подписи Authenticode. Часть 2. Описание реализации
2.1. Подготовка к проверке
2.2. Запуск процедуры проверки и обработка результатов
2.3. Очистка ресурсов.
2.4. Извлечение сертификатов и содержащейся в них информации
2.5. Извлечение атрибутов и крос-подписей
Замечу, что программа 32-битная, но полностью поддерживает проверку образов в 64-разрядных папках, так что далее по коду будут манипуляции с файловым переадресатором.
Прототип функции проверки в модуле 'modDigiSign' выглядит таким образом:
VB.NET / VBA:
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 – для проверки подписи остальных файлов.
Примечание: сами провайдеры состоят из файлов библиотек и записей реестра:
Здесь и далее для Windows 8 и выше используются функции с постфиксом «2».
Получая хендл к файлу, временно отключаем редиректор:
VB.NET / VBA:
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), выходим из программы. Лимит взят навскидку для защиты от подвисаний.
Модуль modDigiSign ищет только первое совпадение. Есть ничтожно маленький шанс, что хеш этого файла попадётся в ещё одном каталоге. Если требуется перечислить все каталоги с заданным для поиска хешем, укажите последним параметром хендл результата предыдущего вызова функции.
Если файл был подписан через каталог, мы получим его контекст и затем можем запросить полный путь к файлу каталога безопасности (.cat) в структуру CATALOG_INFO:
VB.NET / VBA:
If CryptCATCatalogInfoFromContext(CatalogContext, CatalogInfo, 0&) Then
Если контекст не был получен, предполагаем, что проверяемая подпись – внутренняя (embedded).
Дальше мы заполняем структуры в зависимости от физического расположения подписи – внутренняя или внешняя (подписанная через каталог).
Файл может быть подписан одновременно обеими. По-умолчанию, в этом случае проверяется подпись через каталог. Чтобы поменять приоритет, установите флаг SV_DisableCatalogVerify. Такой флаг устанавливается автоматически при проверке вторичной подписи через флаг SV_CheckSecondarySignature, т.к. вторичная подпись может быть только внутренней.
Также, при проверке через каталог, не заполняется поле результатов проверки .isEmbedded. Чтобы принудительно заполнять это поле, укажите флаг SV_CheckEmbeddedPresence.
Для проверки внутренней подписи нам нужно заполнить структуры:
Здесь устанавливаются специфические флаги проверки, такие как:
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. В этот момент файловый переадресатор должен быть отключён!
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, передав пустое имя файла:
Указатель на данные состояния проверки (WINTRUST_DATA -> hWVTStateData) передаётся в функцию WTHelperProvDataFromStateData.
В результате получаем данные о провайдере проверки (структура CRYPT_PROVIDER_DATA).
Получаем данные о подписантах и кросс-подписантах (структура CRYPT_PROVIDER_SGNR).
Кросс-подпись, например, может накладываться сервером отметки времени (см. раздел 2.5.).
Получаем содержание подписанного сообщения PKCS #7 (CMSG_SIGNER_INFO), а из него извлекаем параметр HashAlgorithm (структура CRYPT_ALGORITHM_IDENTIFIER), в которой хранится алгоритм хеша выборки (digest).
Прим.: чтобы получить алгоритма хеша подписи сертификата, с которым был подписан файл, см. пункт 5.2.1.
Получаю массив сертификатов провайдера (CRYPT_PROVIDER_CERT) подписанта (в VB я это делаю через подмену указателя на данные массива).
Если Вам нужны сертификаты кросс-подписанта сделайте тоже самое над массивом CRYPT_PROVIDER_SGNR -> pasCounterSigners (см. раздел 2.5.). Он содержит указатели на другие CRYPT_PROVIDER_SGNR.
4. Provider cert -> Cert. context
VB.NET / VBA:
For i = 0 To CPSigner(idxSign).csCertChain - 1
Signature(idxSign).Certificate(i) = CertDuplicateCertificateContext(CPCERT(i).pCert)
Next
Получаем и дублируем контекст сертификата.
Далее выполняю операции над двумя сертификатами:
1) корневым (сертификат центра сертификации):
Извлекать параметры сертификата, имея его контекст, можно по-разному. Через:
1) CertGetCertificateContextProperty
2) CertNameToStr -> CertGetNameString
В данном случае я передаю CERT_HASH_PROP_ID, чтобы получить SHA256 хеш корневого сертификата.
Также, этим способом я получаю e-mail адрес подписанта, т.к. функция имеет полезный флаг CERT_NAME_STR_ENABLE_PUNYCODE_FLAG для декодирования строк формата IA5_STRING, в котором может быть закодирована часть с хостом e-mail адреса.
Теперь из нее мы можем получить алгоритм хеша подписи сертификата:
VB.NET / VBA:
CERT_INFO -> SignatureAlgorithm -> pszObjId
Ещё в этой структуре хранится такая полезная информация как: серийный номер сертификата, данные об издателе и подписанте, дата начала и дата истечения срока действия сертификата, публичный ключ и прочее.
5.2.2. Cert info subject (CRYPTOAPI_BLOB) -> Subject X.500 string
VB.NET / VBA:
CertNameToStr X509_ASN_ENCODING Or PKCS_7_ASN_ENCODING, VarPtr(Blob), CERT_X500_NAME_STR, StrPtr(sName), BufferSize
Происходит декодирование строки формата X.500, либо PKCS_7.
5.2.3. Subject X.500 string -> CN
Извлекаем CN или по желанию другие параметры, с помощью ручного парсинга строки Distinguished Names (DN).
Строка записана по стандарту rfc2253.
Пример строки:
Этим способом я получаю имя подписанта и имя организации, которая выдала сертификат.
В завершение, контексты всех сертификатов освобождаются функцией CertFreeCertificateContext.
Это нужно делать только с продублированным контекстом.
2.5. Извлечение атрибутов и крос-подписей
Атрибуты являются частью цифровой подписи. Присутствие некоторых из них – опционально.
Существует 2 вида атрибутов:
– authenticated (удостоверенные, ещё называются – проверенные)
– unauthenticated (неудостоверенные)
Проверенные атрибуты – это часть данных, которые входят в выборку хеша и легитимность которых подтверждается в процессе проверки подписи.
Неудостоверенные атрибуты не входят в выборку и их легитимность не подтверждается на первом этапе проверки основной подписи.
Однако, существует такое понятие, как крос-подпись. Она накладывается поверх основной подписи. И храниться она как раз в виде неудостоверенного атрибута. Так что её легитимность проверяется отдельно, точно в таком же порядке, как и для основной подписи.
Атрибут состоит из:
– идентификатора атрибута (OID)
– значения атрибута
Неполный перечень атрибутов можно посмотреть в приложенном к статье файле OIDs.txt
Так, в примере выше: Удостоверенные:
1.3.6.1.4.1.311.2.1.12 – это spcSpOpusInfo
1.2.840.113549.1.9.3 – это contentType (тип содержимого)
1.2.840.113549.1.9.4 – это messageDigest (выборка сообщения) Неудостоверенные:
1.3.6.1.4.1.311.3* – крос-подпись сервера штампа времени (подробнее о назначении см. раздел 1.3, п.3.)
Рассмотрим пример с этой крос-подписью. Она также имеет собственные атрибуты:
Посмотрим на OID 1.2.840.113549.1.9.5 (на рисунке уже расшифрован) – это время подписания файла.
Значение любого атрибута кодируется в формате ASN.1, как:
C++:
{
byte TypeData – тип данных
byte cbData – размер данных в байтах
byte[] RawData – сырые данные
}
Список типов данных можно посмотреть здесь.
Рассматриваемый OID представлен в стандарте RFC5652 (см. п.11.3).
Его значение может быть закодировано в форматах:
– UTCTime, как «YYMMDDHHMMSSZ» (для дат от 01.01.1950 до 31.12.2049)
– GeneralizedTime, как «YYYYMMDDHHMMSSZ» (для всех остальных дат).
При работе через CryptoAPI, это значение автоматически попадает в поле sftVerifyAsOf структуры CRYPT_PROVIDER_SGNR крос-подписи (в формате FILETIME с UTC+0).
К этой крос-подписи может быть «прицеплена» ещё одна и так рекурсивно сколько угодно.
Доступ к атрибутам из-под CryptoAPI можно получить через поля AuthAttrs и UnauthAttrs структуры CMSG_SIGNER_INFO, содержащие масив атрибутов CRYPT_ATTRIBUTE.