Эксперт WindowsАвтор FAQ
17829 / 7564 / 889
Регистрация: 25.12.2011
Сообщений: 11,314
Записей в блоге: 17
1

Проверка электронной цифровой подписи Authenticode. Часть 2. Описание реализации

11.05.2017, 02:05. Показов 3680. Ответов 3

Это продолжение. См. также другие части этой статьи:

Часть 1. Кусочек теории.
Часть 3. Программа проверки Authenticode ЭЦП

Часть 2. Описание реализации программы проверки подписей

Содержание:


2.1. Подготовка к проверке
2.2. Запуск процедуры проверки и обработка результатов
2.3. Очистка ресурсов.
2.4. Извлечение сертификатов и содержащейся в них информации
2.5. Извлечение атрибутов и крос-подписей
0
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
11.05.2017, 02:05
Ответы с готовыми решениями:

Проверка электронной цифровой подписи Authenticode. Часть 1. Теория
Привет! Я как-то довольно давно заинтересовался темой цифровых подписей, какова их защита, как они...

Проверка электронной цифровой подписи Authenticode. Часть 3. Набор программ
Это продолжение. См. также другие части этой статьи: Часть 1. Кусочек теории. Часть 2. Описание...

Проверка электронной цифровой подписи
Подскажите как прописать в скрипте проверку подписи, есть скрипт который отправляет подписанный и...

Постановка и проверка электронной цифровой подписи в Word
нужно реализовать процедуру постановки и проверки электронной цыфровой подписи в Word,желательно на...

3
Эксперт WindowsАвтор FAQ
17829 / 7564 / 889
Регистрация: 25.12.2011
Сообщений: 11,314
Записей в блоге: 17
11.05.2017, 02:08  [ТС] 2
2.1. Подготовка к проверке

Замечу, что программа 32-битная, но полностью поддерживает проверку образов в 64-разрядных папках, так что далее по коду будут манипуляции с файловым переадресатором.

Прототип функции проверки в модуле 'modDigiSign' выглядит таким образом:
Visual Basic
1
2
3
4
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\Mi crosoft\Cryptography\Defaults\ Provider
HKEY_LOCAL_MACHINE\SOFTWARE\Mi crosoft\Cryptography\Providers \Trust
Далее получаем дескриптор контекста административного каталога, указав оптимальный алгоритм хеширования подписи:
Visual Basic
1
CryptCATAdminAcquireContext2 hCatAdmin, VarPtr(DRIVER_ACTION_VERIFY), StrPtr(BCRYPT_SHA256_ALGORITHM), 0&, 0&
Здесь и далее для Windows 8 и выше используются функции с постфиксом «2».

Получая хендл к файлу, временно отключаем редиректор:
Visual Basic
1
2
3
4
5
    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), выходим из программы. Лимит взят навскидку для защиты от подвисаний.

Далее рассчитываем хеш файла:
Visual Basic
1
CryptCATAdminCalcHashFromFileHandle2(hCatAdmin, hFile, HashSize, aFileHash(0), 0&)
Проводится поиск хеша в каталогах безопасности:
Visual Basic
1
CatalogContext = CryptCATAdminEnumCatalogFromHash(hCatAdmin, aFileHash(0), HashSize, 0&, ByVal 0&)
Модуль modDigiSign ищет только первое совпадение. Есть ничтожно маленький шанс, что хеш этого файла попадётся в ещё одном каталоге. Если требуется перечислить все каталоги с заданным для поиска хешем, укажите последним параметром хендл результата предыдущего вызова функции.

Если файл был подписан через каталог, мы получим его контекст и затем можем запросить полный путь к файлу каталога безопасности (.cat) в структуру CATALOG_INFO:
Visual Basic
1
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.
В этот момент файловый переадресатор должен быть отключён!
Visual Basic
1
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.

Контекст каталога безопасности освобождается функцией CryptCATAdminReleaseCatalogCon text.

После чего закрывается хендл файла, а результат проверки сохраняется в локальный кеш.

Если ваша программа больше не нуждается в проверке ЭЦП, разумно будет выполнить принудительную очистку памяти, занятой кешем. Для этого возведите флаг SV_CacheFree, передав пустое имя файла:
Visual Basic
1
SignVerify "", SV_CacheFree, SignResult
1
Эксперт WindowsАвтор FAQ
17829 / 7564 / 889
Регистрация: 25.12.2011
Сообщений: 11,314
Записей в блоге: 17
11.05.2017, 02:13  [ТС] 3
2.4. Извлечение сертификатов и содержащейся в них информации

Проверка электронной цифровой подписи Authenticode. Часть 2. Описание реализации


1. State data -> provider data.
Visual Basic
1
pProvData = WTHelperProvDataFromStateData(StateData)
Указатель на данные состояния проверки (WINTRUST_DATA -> hWVTStateData) передаётся в функцию WTHelperProvDataFromStateData.
В результате получаем данные о провайдере проверки (структура CRYPT_PROVIDER_DATA).

2. Provider data -> Provider signer
Visual Basic
1
pCPSigner = WTHelperGetProvSignerFromChain(pProvData, idxSign, 0&, 0&)
Получаем данные о подписантах и кросс-подписантах (структура CRYPT_PROVIDER_SGNR).
Кросс-подпись, например, может накладываться сервером отметки времени (см. раздел 2.5.).

3.1. Provider signer -> psSigner -> HashAlgorithm -> pszObjId
Visual Basic
1
2
3
memcpy MsgSigner, ByVal CPSigner(0).psSigner, LenB(MsgSigner)
 
.Algorithm = StringFromPtrA(MsgSigner.HashAlgorithm.pszObjId)
Получаем содержание подписанного сообщения PKCS #7 (CMSG_SIGNER_INFO), а из него извлекаем параметр HashAlgorithm (структура CRYPT_ALGORITHM_IDENTIFIER), в которой хранится алгоритм хеша выборки (digest).
Проверка электронной цифровой подписи Authenticode. Часть 2. Описание реализации

Проверка электронной цифровой подписи Authenticode. Часть 2. Описание реализации

Прим.: чтобы получить алгоритма хеша подписи сертификата, с которым был подписан файл, см. пункт 5.2.1.

Алгоритм помечен в виде идентификатора объекта (OID), список и расшифровку которых можно увидеть по ссылке: MSDN. CRYPT_ALGORITHM_IDENTIFIER structure.

3.2. Provider signer -> Provider cert.[]
Visual Basic
1
2
3
4
5
ReDim CPCERT(CPSigner(idxSign).csCertChain - 1)
 
GetMem4 ByVal ArrPtr(CPCERT()), lpSA
GetMem4 ByVal lpSA + 12, lpOldPt
GetMem4 CPSigner(idxSign).pasCertChain, ByVal lpSA + 12
Получаю массив сертификатов провайдера (CRYPT_PROVIDER_CERT) подписанта (в VB я это делаю через подмену указателя на данные массива).
Если Вам нужны сертификаты кросс-подписанта сделайте тоже самое над массивом CRYPT_PROVIDER_SGNR -> pasCounterSigners (см. раздел 2.5.). Он содержит указатели на другие CRYPT_PROVIDER_SGNR.

4. Provider cert -> Cert. context
Visual Basic
1
2
3
For i = 0 To CPSigner(idxSign).csCertChain - 1
    Signature(idxSign).Certificate(i) = CertDuplicateCertificateContext(CPCERT(i).pCert)
Next
Получаем и дублируем контекст сертификата.

Далее выполняю операции над двумя сертификатами:
1) корневым (сертификат центра сертификации):
Visual Basic
1
2
idxRoot = UBound(Signature(0).Certificate)
pCertificate = Signature(0).Certificate(idxRoot)
2) финальным (сертификат подписанта)
Visual Basic
1
2
idxSigner = 0
pCertificate = Signature(0).Certificate(idxSigner)
Извлекать параметры сертификата, имея его контекст, можно по-разному. Через:
1) CertGetCertificateContextPrope rty
2) CertNameToStr -> CertGetNameString

Рассмотрим их:

5.1. Cert. context -> Property by ID
Visual Basic
1
CertGetCertificateContextProperty (pCertContext, ID, buf(0), bufSize)
Функция может принимать такие идентификаторы.

В данном случае я передаю CERT_HASH_PROP_ID, чтобы получить SHA256 хеш корневого сертификата.
Также, этим способом я получаю e-mail адрес подписанта, т.к. функция имеет полезный флаг CERT_NAME_STR_ENABLE_PUNYCODE_ FLAG для декодирования строк формата IA5_STRING, в котором может быть закодирована часть с хостом e-mail адреса.

5.2.1. Cert. context -> Cert. info
Visual Basic
1
2
3
memcpy Certificate, ByVal pCertificate, LenB(Certificate)
pCertInfo = Certificate.pCertInfo
memcpy out_CertInfo, ByVal pCertInfo, LenB(out_CertInfo)
Получаем CERT_INFO.

Теперь из нее мы можем получить алгоритм хеша подписи сертификата:
Visual Basic
1
CERT_INFO -> SignatureAlgorithm -> pszObjId
Проверка электронной цифровой подписи Authenticode. Часть 2. Описание реализации


Ещё в этой структуре хранится такая полезная информация как: серийный номер сертификата, данные об издателе и подписанте, дата начала и дата истечения срока действия сертификата, публичный ключ и прочее.

5.2.2. Cert info subject (CRYPTOAPI_BLOB) -> Subject X.500 string
Visual Basic
1
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.
Пример строки:
C=RU, PostalCode=115093, S=Moscow, L=Moscow, STREET="Street Serpukhovsko B, 44", O=RIVER SOLUTIONS, CN=RIVER SOLUTIONS
Детальнее см. раздел 1.4.5.

Этим способом я получаю имя подписанта и имя организации, которая выдала сертификат.

В завершение, контексты всех сертификатов освобождаются функцией CertFreeCertificateContext.
Это нужно делать только с продублированным контекстом.


2.5. Извлечение атрибутов и крос-подписей

Атрибуты являются частью цифровой подписи. Присутствие некоторых из них – опционально.
Существует 2 вида атрибутов:
– authenticated (удостоверенные, ещё называются – проверенные)
– unauthenticated (неудостоверенные)

Проверка электронной цифровой подписи Authenticode. Часть 2. Описание реализации


Проверенные атрибуты – это часть данных, которые входят в выборку хеша и легитимность которых подтверждается в процессе проверки подписи.

Неудостоверенные атрибуты не входят в выборку и их легитимность не подтверждается на первом этапе проверки основной подписи.

Однако, существует такое понятие, как крос-подпись. Она накладывается поверх основной подписи. И храниться она как раз в виде неудостоверенного атрибута. Так что её легитимность проверяется отдельно, точно в таком же порядке, как и для основной подписи.

Атрибут состоит из:
– идентификатора атрибута (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.)

Рассмотрим пример с этой крос-подписью. Она также имеет собственные атрибуты:

Проверка электронной цифровой подписи Authenticode. Часть 2. Описание реализации


Посмотрим на OID 1.2.840.113549.1.9.5 (на рисунке уже расшифрован) – это время подписания файла.

Значение любого атрибута кодируется в формате ASN.1, как:
C++
1
2
3
4
5
{
    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.
1
Эксперт WindowsАвтор FAQ
17829 / 7564 / 889
Регистрация: 25.12.2011
Сообщений: 11,314
Записей в блоге: 17
11.05.2017, 02:21  [ТС] 4
Продолжение статьи:

Часть 3. Программа проверки Authenticode ЭЦП.
0
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
11.05.2017, 02:21
Помогаю со студенческими работами здесь

Создание электронной цифровой подписи
Здравствуйте. Подскажите, пожалуйста, как реализовать создание и проверку электронной цифровой...

Считывание электронной цифровой подписи
Доброго времени суток. Ребята, пишу диплом, одна из составных - проверка электронно цифровой...

Проверка электронной подписи
Ребят,кто может подсказать код для проверки электронной подписи, оооочень нужно)дипломная горит))

Проверка электронной подписи
Приветствую, ребята. Кто может подсказать, как настроить ЭЦП в 1С ? В реестре ключ прописан, но...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
4
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2022, CyberForum.ru