Важное вступление
В гайде описывается формирование отсоединенной подписи вформате PKCS7 (рядом с файлом появится файл в формате .sig). Такую подпись может запросить нотариус, ЦБ и любой кому нужно долгосрочное хранения подписанного документа. Удобство такой подписи в том, что при улучшении ее до УКЭП CAdES-X Long Type 1 (CMS Advanced Electronic Signatures [1]) в нее добавляется штамп времени, который генерирует TSA (Time-Stamp Protocol [2]) и статус сертификата на момент подписания (OCSP [3]) - подлинность такой подписи можно подтвердить по прошествии длительного периода (Усовершенствованная квалифицированная подпись [4]).
Код основан на репозиториях corefx и DotnetCoreSampleProject - в последнем проще протестировать свои изменения перед переносом в основной проект и он будет отправной точкой по сборке corefx. Судя по записям с форума компании [5], решение для .NET Core в стадии бета-тестирования. Далее по тексту я также буду ссылаться на этот форум. Разработка велась в Visual Studio Community 2019.
Для получения штампа времени использован TSP-сервис http://qs.cryptopro.ru/tsp/tsp.srf
Что имеем на входе?
-
КриптоПро CSP версии 5.0 - для поддержки Российских криптографических алгоритмов (подписи, которые выпустили в аккредитованном УЦ в РФ)
-
КриптоПро TSP Client 2.0 - нужен для штампа времени
-
КриптоПро OCSP Client 2.0 - проверит не отозван ли сертификат на момент подписания
-
КриптоПро .NET Client - таков путь
-
Любой сервис по проверке ЭП - я использовал Контур.Крипто как основной сервис для проверки ЭП и КриптоАРМ как локальный. А еще можно проверить ЭП на сайте Госуслуг
-
КЭП по ГОСТ Р 34.11-2012/34.10-2012 256 bit, которую выпустил любой удостоверяющий центр
-
КриптоПро CSP версии 5.0 - у меня установлена версия 5.0.11944 КС1, лицензия встроена в ЭП.
-
КриптоПро TSP Client 2.0 и КриптоПро OCSP Client 2.0 - лицензии покупается отдельно, а для гайда мне хватило демонстрационного срока.
-
КриптоПро .NET Client версии 1.0.7132.2 - в рамках этого гайда я использовал демонстрационную версию клиентской части и все действия выполнялись локально. Лицензию на сервер нужно покупать отдельно.
-
Контур.Крипто бесплатен, но требует регистрации. В нем также можно подписать документы КЭП, УКЭП и проверить созданную подпись загрузив ее файлы.
Так, а что надо на выходе?
А на выходе надо получить готовое решение, которое сделает отсоединенную ЭП в формате .sig со штампом времени на подпись и доказательством подлинности. Для этого зададим следующие критерии:
-
ЭП проходит проверку на протале Госуслуг, через сервис для подтверждения подлинности ЭП формата PKCS#7 в электронных документах;
-
КриптоАРМ после проверки подписи
-
Заполнит поле "Время создания ЭП" - в конце проверки появится окно, где можно выбрать ЭП и кратко посмотреть ее свойства
Стобец "Время создация ЭП" -
В информации о подписи и сертификате (двойной клик по записе в таблице) на вкладке "Штампы времени" в выпадающем списке есть оба значения и по ним заполнена информация:
-
Подпись:
-
Доказательства подлинности:
-
-
В протоколе проверки подписи есть блоки "Доказательства подлинности", "Штамп времени на подпись" и "Время подписания". Для сравнения: если документ подписан просто КЭП, то отчет по проверке будет достаточно коротким в сравнении с УКЭП.
-
-
Контур.Крипто при проверке подписи выдаст сообщение, что совершенствованная подпись подтверждена, сертификат на момент подписания действовал и указано время создания подпись:
Усовершенствованная подпись подтверждена
Соберем проект с поддержкой ГОСТ Р 34.11-2012 256 bit
Гайд разделен на несколько этапов. Основная инструкция по сборке опубликована вместе с репозиторием DotnetCoreSampleProject - периодически я буду на нее ссылаться.
Первым делом создадим новую папку
... и положим туда все необходимое.
Инструкция делится на 2 этапа - мне пришлось выполнить оба, чтобы решение заработало. В папку добавьте подпапки .\runtime и .\packages
I - Сборка проекта без сборки corefx для Windows
-
Установите КриптоПро 5.0 и убедитесь, что у вас есть действующая лицензия. - для меня подошла втроенная в ЭП;
-
Установите core 3.1 sdk и runtime и распространяемый пакет Visual C++ для Visual Studio 2015 обычно ставится вместе со студией; прим.: на II этапе мне пришлось через установщик студии поставить дополнительное ПО для разработки на C++ - сборщик требует предустановленный DIA SDK.
-
Задайте переменной среды DOTNET_MULTILEVEL_LOOKUP значение 0 - не могу сказать для чего это нужно, но в оригинальной инструкции это есть;
-
Скачайте 2 файла из релиза corefx (package_windows_debug.zip и runtime-debug-windows.zip) - они нужны для корректной сборки проекта. В гайде рассматривается версия v3.1.1-cprocsp-preview4.325 от 04.02.2021:
-
package_windows_debug.zip распакуйте в .\packages
-
runtime-debug-windows.zip распакуйте в .\runtime
-
-
Добавьте источник пакетов NuGet в файле %appdata%\NuGet\NuGet.Config - источник должен ссылаться на путь .\packages в созданной вами папке. Пример по добавлению источника есть в основной инструкеии. Для меня это не сработало, поэтому я добавил источник через VS Community;
-
Склонируйте NetStandard.Library в .\ и выполните PowerShell скрипт (взят из основной инструкции), чтобы заменить пакеты в $env:userprofile\.nuget\packages\
git clone https://github.com/CryptoProLLC/NetStandard.LibraryNew-Item -ItemType Directory -Force -Path "$env:userprofile\.nuget\packages\netstandard.library"Copy-Item -Force -Recurse ".\NetStandard.Library\nugetReady\netstandard.library" -Destination "$env:userprofile\.nuget\packages\"
-
Склонируйте репизиторий DotnetCoreSampleProject в .\
-
Измените файл.\DotnetSampleProject\DotnetSampleProject.csproj - для сборок System.Security.Cryptography.Pkcs.dll и System.Security.Cryptography.Xml.dll укажите полные пути к .\runtime;
-
Перейдите в папку проекта и попробуйте собрать решение. Я собирал через Visual Studio после открытия проекта.
II - Сборка проекта со сборкой corefx для Windows
-
Выполните 1-3 и 6-й шаги из I этапа;
-
Склонируйте репозиторий corefx в .\
-
Выполните сборку запустив .\corefx\build.cmd - на этом этапе потребуется предустановленный DIA SDK
-
Выполните шаги 5, 7-9 из I этапа. Вместо условного пути .\packages укажите .\corefx\artifacts\packages\Debug\NonShipping, а вместо .\runtime укажите .\corefx\artifacts\bin\runtime\netcoreapp-Windows_NT-Debug-x64
На этом месте у вас должно получиться решение, которое поддерживает ГОСТ Р 34.11-2012 256 bit.
Немного покодим
Потребуется 2 COM библиотеки: "CAPICOM v2.1 Type Library" и "Crypto-Pro CAdES 1.0 Type Library". Они содержат необходимые объекты для создания УКЭП.
В этом примере будет подписываться BASE64 строка, содержащая в себе PDF-файл. Немного доработав код можно будет подписать hash-значение этого фала.
Основной код для подписания был взят со страниц Подпись PDF с помощью УЭЦП- Page 2 (cryptopro.ru) и Подпись НЕОПРЕДЕЛЕНА при создании УЭЦП для PDF на c# (cryptopro.ru), но он использовался для штампа подписи на PDF документ. Код из этого гайда переделан под сохранение файла подписи в отдельный файл.
Условно процесс можно поделить на 4 этапа:
-
Поиск сертификата в хранилище - я использовал поиск по отпечатку в хранилище пользователя;
-
Чтение байтов подписанного файла;
-
Создание УКЭП;
-
Сохранение файла подписи рядом с файлом.
using CAdESCOM;using CAPICOM;using System;using System.Globalization;using System.IO;using System.Security.Cryptography;using System.Security.Cryptography.X509Certificates;using System.Security.Cryptography.Xml;using System.Text;using System.Threading.Tasks;using System.Xml;public static void Main(){ //Сертификат для подписиX509Certificate2 gostCert = GetX509Certificate2("отпечаток"); //Файл, который предстоит подписать byte[] fileBytes = File.ReadAllBytes("C:\\Тестовое заявление.pdf"); //Файл открепленной подписи byte[] signatureBytes = SignWithAdvancedEDS(fileBytes, gostCert); //Сохранение файла подписи File.WriteAllBytes("C:\\Users\\mikel\\Desktop\\Тестовое заявление.pdf.sig", signatureBytes);}//Поиск сертификата в хранилищеpublic static X509Certificate2 GetX509Certificate2(string thumbprint){ X509Store store = CreateStoreObject("My", StoreLocation.CurrentUser); store.Open(OpenFlags.ReadOnly); X509Certificate2Collection certCollection = store.Certificates.Find(X509FindType.FindByThumbprint, thumbprint, false); X509Certificate2Enumerator enumerator = certCollection.GetEnumerator(); X509Certificate2 gostCert = null; while (enumerator.MoveNext()) gostCert = enumerator.Current; if (gostCert == null) throw new Exception("Certificiate was not found!"); return gostCert;}//Создание УКЭПpublic static byte[] SignWithAdvancedEDS(byte[] fileBytes, X509Certificate2 certificate){ string signature = ""; try { string tspServerAddress = @"http://qs.cryptopro.ru/tsp/tsp.srf"; CPSigner cps = new CPSigner(); cps.Certificate = GetCAPICOMCertificate(certificate.Thumbprint); cps.Options = CAPICOM_CERTIFICATE_INCLUDE_OPTION.CAPICOM_CERTIFICATE_INCLUDE_WHOLE_CHAIN; cps.TSAAddress = tspServerAddress; CadesSignedData csd = new CadesSignedData(); csd.ContentEncoding = CADESCOM_CONTENT_ENCODING_TYPE.CADESCOM_BASE64_TO_BINARY; csd.Content = Convert.ToBase64String(fileBytes); //Создание и проверка подписи CAdES BES signature = csd.SignCades(cps, CADESCOM_CADES_TYPE.CADESCOM_CADES_BES, true, CAdESCOM.CAPICOM_ENCODING_TYPE.CAPICOM_ENCODE_BASE64); csd.VerifyCades(signature, CADESCOM_CADES_TYPE.CADESCOM_CADES_BES, true); //Дополнение и проверка подписи CAdES BES до подписи CAdES X Long Type 1 //(вторая подпись остается без изменения, так как она уже CAdES X Long Type 1) signature = csd.EnhanceCades(CADESCOM_CADES_TYPE.CADESCOM_CADES_X_LONG_TYPE_1, tspServerAddress, CAdESCOM.CAPICOM_ENCODING_TYPE.CAPICOM_ENCODE_BASE64); csd.VerifyCades(signature, CADESCOM_CADES_TYPE.CADESCOM_CADES_X_LONG_TYPE_1, true); } catch (Exception ex) { throw ex; } return Convert.FromBase64String(signature);}
Пробный запуск
Для подписания возьмем PDF-документ, который содержит надпись "Тестовое заявление.":
Больше для теста нам ничего не надоДалее запустим программу и дождемся подписания файла:
Готово. Теперь можно приступать к проверкам.
Проверка в КриптоАРМ
Время создания ЭП заполнено:
Штамп времени на подпись есть:
Доказательства подлинности также заполнены:
В протоколе проверки есть блоки "Доказательства подлинности", "Штамп времени на подпись" и "Время подписания":
Важно отметить, что серийный номер параметров сертификата принадлежит TSP-сервису http://qs.cryptopro.ru/tsp/tsp.srf
Проверка на Госуслугах
Проверка в Контур.Крипто
Done.
Гайд написан с исследовательской целью - проверить возможность подписания документов УКЭП с помощью самописного сервиса на .NET Core 3.1 с формированием штампов подлинности и времени подписания документов.
Безусловно это решение не стоит брать в работу "как есть" и нужны некоторые доработки, но в целом оно работает и подписывает документы подписью УКЭП.
Это вообще законно?
С удовольствием узнаю ваше мнение в комментариях.
Ссылки на публичные источники
[1] CMS Advanced Electronic Signatures (CAdES) - https://tools.ietf.org/html/rfc5126#ref-ISO7498-2
[2] Internet X.509 Public Key Infrastructure Time-Stamp Protocol (TSP) - https://www.ietf.org/rfc/rfc3161.txt
[3] X.509 Internet Public Key Infrastructure Online Certificate Status Protocol - OCSP - https://tools.ietf.org/html/rfc2560
[4] Усовершенствованная квалифицированная подпись Удостоверяющий центр СКБ Контур (kontur.ru)
[5] Поддержка .NET Core (cryptopro.ru)
[6] http://qs.cryptopro.ru/tsp/tsp.srf - TSP-сервис КриптоПро
UPD1: Поменял в коде переменную, куда записываются байты файла подписи.
Также я забыл написать немного про подпись штампа времени - он подписывается сертификатом владельца TSP-сервиса. По гайду это ООО "КРИПТО-ПРО":