Как подписать документ электронной подписью используя Крипто Про 5? | УЦ ITCOM

Как подписать документ электронной подписью используя Крипто Про 5?  | УЦ ITCOM ЭЦП

Дешифрование

Для того, чтобы дешифровать данные, на локальной машине в личных сертификатах пользователя или компьютера должен быть сертификат одного из адресатов. И к нему должен быть привязан закрытый ключ. Процесс проходит по уже привычному сценарию — список параметров, определение длины и сам процесс дешифрования:

Дешифрование данных
/**<summary>Дешифровывает данные</summary>
* <param name="_arInput">Данные для расшифровки</param>
* <param name="_arRes">Результат</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <param name="_pCert">Сертификат</param>
* <returns>Стандартный код ошибки, если UCOnsts.S_OK то все ок</returns>
* **/
public static int DecryptDataCP(byte[] _arInput, out X509Certificate2 _pCert, out byte[] _arRes, ref string _sError) {             
    _arRes = new byte[0];
    _pCert = null;
    IntPtr hSysStore =  UCryptoAPI.CertOpenSystemStore(IntPtr.Zero, UCConsts.AR_CRYPTO_STORE_NAME[(int)StoreName.My]);
    GCHandle GC = GCHandle.Alloc(hSysStore, GCHandleType.Pinned);
    IntPtr hOutCertL = IntPtr.Zero;
    IntPtr hOutCert  = IntPtr.Zero;
    try {
        // 0) Подготовка параметров
        CRYPT_DECRYPT_MESSAGE_PARA pParams = new CRYPT_DECRYPT_MESSAGE_PARA();
        pParams.dwMsgAndCertEncodingType = UCConsts.PKCS_7_OR_X509_ASN_ENCODING;
        pParams.cCertStore = 1;
        pParams.rghCertStore = GC.AddrOfPinnedObject();
        pParams.cbSize = Marshal.SizeOf(pParams);
        int iLen = 0;
        // 1) Первый вызов определяем длину 
        if (!UCryptoAPI.CryptDecryptMessage(ref pParams, _arInput, _arInput.Length,
                                            null, ref iLen, ref hOutCertL)) { 
            _sError = UCConsts.S_DECRYPT_LEN_ERR.Frm(Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        }
        // 2) Второй  вызов дешифруем
        _arRes = new byte[iLen];
        if (!UCryptoAPI.CryptDecryptMessage(ref pParams, _arInput, _arInput.Length,
                                           _arRes, ref iLen, ref hOutCert)) {
            _sError = UCConsts.S_DECRYPT_ERR.Frm(Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        }
        // 3) Если есть вытаскиваем сертификат
        if (hOutCert != IntPtr.Zero) _pCert = new ISDP_X509Cert(hOutCert);
        if(_pCert != null) hOutCert = IntPtr.Zero;
        // Все ок возвращаем
        return UConsts.S_OK;
    } catch (Exception E) { 
        _sError = UCConsts.S_DECRYPT_ERR.Frm(E.Message);
        return UConsts.E_GEN_EXCEPTION;
    } finally {
        if (hOutCertL != IntPtr.Zero) UCryptoAPI.CertFreeCertificateContext(hOutCertL);
        if (hOutCert != IntPtr.Zero) UCryptoAPI.CertFreeCertificateContext(hOutCert);
        GC.Free();
        UCryptoAPI.CertCloseStore(hSysStore, 0);
    }
}

При установке параметров указывается хранилище, из которых система будет пытаться извлечь подходящий сертификат с ключом. В результате работы система выдаст дешифрованные данные и сертификат, который был использован (в Linux сертификат всегда возвращается пустой).

Извлечение информация из подписи

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

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

Чтение подписи происходит следующим образом:

Извлечение информации из подписи
/**<summary>Расшифровать</summary>
* <param name="_arSign">Подпись</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
* **/
public int Decode(byte[] _arSign, ref string _sError) {
    IntPtr hMsg = IntPtr.Zero;
    // 0) Формируем информацию 
    try {
        hMsg = UCryptoAPI.CryptMsgOpenToDecode(UCConsts.PKCS_7_OR_X509_ASN_ENCODING, UCConsts.CMSG_DETACHED_FLAG,
                                               0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
        if (hMsg == IntPtr.Zero) {
            _sError = UCConsts.S_CRYP_MSG_FORM_ERR.Frm(Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        }
        // 1) Вносим сообщение
        if (!UCryptoAPI.CryptMsgUpdate(hMsg, _arSign, (uint)_arSign.Length, true)) {
            _sError = UCConsts.S_CRYP_MSG_SIGN_COPY_ERR.Frm(Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        }
        // 2) Проверяем тип (PKCS7 SignedData)
        uint iMessType = UCUtils.GetCryptMsgParam<uint>(hMsg, UCConsts.CMSG_TYPE_PARAM);
        if (UCConsts.CMSG_SIGNED != iMessType) {
            _sError = UCConsts.S_CRYP_MSG_SIGN_TYPE_ERR.Frm(iMessType, UCConsts.CMSG_SIGNED);
            return UConsts.E_CRYPTO_ERR;
        }
        // 3) Формируем список сертфикатов
        fpCertificates = UCUtils.GetSignCertificates(hMsg);
        // 4) Список подписантов            
        uint iSignerCount =  UCUtils.GetCryptMsgParam<uint>(hMsg, UCConsts.CMSG_SIGNER_COUNT_PARAM);
        for (int i = 0; i < iSignerCount; i  ) {
             ISDPSignerInfo pInfo = new ISDPSignerInfo();
             fpSignerInfos.Add(pInfo);
             int iRes = pInfo.Decode(hMsg, i, this, ref _sError);
             if (iRes != UConsts.S_OK) return iRes;
        }
        return UConsts.S_OK;
    } catch (Exception E) {
        _sError = UCConsts.S_SIGN_INFO_GEN_ERR.Frm(E.Message);
        return UConsts.E_GEN_EXCEPTION;
    } finally {
        if(hMsg != IntPtr.Zero) UCryptoAPI.CryptMsgClose(hMsg);
    }
}

Разбор подписи происходит в несколько этапов, вначале формируется структура сообщения (CryptMsgOpenToDecode), затем в нее вносятся реальные данные подписи (CryptMsgUpdate). Остается проверить что это реально подпись и получить сначала список сертификатов, а потом список подписантов. Список сертификатов извлекается последовательно :

Сначала определятся количество сертификатов из параметра CMSG_CERT_COUNT_PARAM, а затем последовательно извлекается информация о каждом сертификате. Завершает процесс создания формирование контекста сертификата и на его основе самого сертификата.

Извлечение данных подписанта сложнее. В них содержится указание на сертификат и список параметров подписи (например, дата подписания). Процесс извлечения данных выглядит следующим образом:

Извлечение информации о подписанте
/**<summary>Распарсить информацию из подписи</summary>
* <param name="_hMsg">Handler подписи</param>
* <param name="_iIndex">Индекс подписанта</param>
* <param name="_pSignedCms">Структура подписи</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
* **/
public int Decode(IntPtr _hMsg, int _iIndex, ISDPSignedCms _pSignedCms, ref string _sError) {
    // 1) Определяем длину
    uint iLen = 0;
    // 2) Считываем
    IntPtr hInfo =  IntPtr.Zero;
    try {
        if (!UCryptoAPI.CryptMsgGetParam(_hMsg, UCConsts.CMSG_SIGNER_INFO_PARAM, (uint)_iIndex, IntPtr.Zero, ref iLen)) {
            _sError = UCConsts.S_ERR_SIGNER_INFO_LEN.Frm(_iIndex, Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        }
        hInfo = Marshal.AllocHGlobal((int)iLen);
        if (!UCryptoAPI.CryptMsgGetParam(_hMsg, UCConsts.CMSG_SIGNER_INFO_PARAM, (uint)_iIndex, hInfo, ref iLen)) {
            _sError = UCConsts.S_ERR_SIGNER_INFO.Frm(_iIndex, Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        }
        CMSG_SIGNER_INFO pSignerInfo = (CMSG_SIGNER_INFO) Marshal.PtrToStructure(hInfo, typeof(CMSG_SIGNER_INFO));
        // 2.1) Ищем сертификат
        byte[] arSerial = new byte[pSignerInfo.SerialNumber.cbData];
        Marshal.Copy(pSignerInfo.SerialNumber.pbData, arSerial, 0, arSerial.Length);
        X509Certificate2Collection pLocCerts = _pSignedCms.pCertificates.Find(X509FindType.FindBySerialNumber,
                                                                              arSerial.Reverse().ToArray().ToHex(), false);
        if (pLocCerts.Count != 1) {
            _sError = UCConsts.S_ERR_SIGNER_INFO_CERT.Frm(_iIndex);
            return UConsts.E_NO_CERTIFICATE;
        }
        fpCertificate = pLocCerts[0];
        fpSignedAttributes = UCUtils.ReadCryptoAttrsCollection(pSignerInfo.AuthAttrs);
        return UConsts.S_OK;
    } catch (Exception E) {
        _sError = UCConsts.S_ERR_SIGNER_INFO_READ.Frm(_iIndex, E.Message);
         return UConsts.E_GEN_EXCEPTION;            
    } finally {
         if(hInfo != IntPtr.Zero) Marshal.FreeHGlobal(hInfo);
    }
}

В ходе него сначала определяется размер структуры подписанта, а затем извлекается и сама структура CMSG_SIGNER_INFO. В ней легко найти серийный номер сертификата и по нему найти нужный сертификат в ранее извлеченном списке. Обратите внимание, что серийный номер содержится в обратном порядке.

После извлечения сертификата необходимо определить параметры подписи, самая важная из которых — дата подписания (даже если это не верифицированная сервером штампа даты времени, для отображения она очень важна).

Атрибуты представляют из себя вложенный справочник вида Oid – список значений (по сути это разобранная структура ASN.1). Пройдя по первому уровню формируем вложенный список:

Ключевой особенностью данного процесса является правильный подбор наследника Pkcs9AttributeObject. Проблема в том, что стандартный способ создания в mono не работает и приходится формировать выбор класса прямо в коде. К тому же из основных типов Mono на данный момент позволяет формировать только дату.

Обернув представленные выше методы в два класса — информация о подписи и информация о подписанте — получаем аналог SignedCms, из которой при формировании печатного вида извлекаем данные.

Подключение в коде

Несмотря на процесс переноса в Linux система должна была продолжать функционировать и в среде Windows, поэтому внешне работа с криптографией должна была осуществляться через общие методы вида «byte[] SignData(byte[] _arData, X509Certificate2 _pCert)», которые должны были одинаково работать как в Linux, так и в Windows.

Анализ методов библиотек криптографии оказался удачным, т. к. КриптоПро реализовало библиотеку «libcapi20.so» которая полностью мимикрирует под стандартные библиотеки Windows шифрования — «crypt32.dll» и «advapi32.dll». Возможно, конечно, не целиком, но все необходимые методы для работы с криптографии там в наличии, и почти все работают.

Поэтому формируем два статических класса «WCryptoAPI» и «LCryptoAPI» каждый из которых будет импортировать необходимый набор методов следующим образом:

[DllImport(LIBCAPI20, SetLastError = true)]
internal static extern bool CertCloseStore(IntPtr _hCertStore, uint _iFlags);

Синтаксис подключения каждого из методов можно либо сформировать самостоятельно, либо воспользоваться сайтом pinvoke, либо скопировать из исходников .Net (класс CAPISafe). Из этого же модуля можно почерпнуть константы и структуры связанные с криптографией, наличие которых всегда облегчают жизнь при работе с внешними библиотеками.

А затем формируем статический класс «UCryptoAPI» который в зависимости от системы будет вызывать метод одного из двух классов:

/**<summary>Закрыть хранилище</summary>
* <param name="_iFlags">Флаги (нужно ставить 0)</param>
* <param name="_hCertStore">Ссылка на хранилище сертификатов</param>
* <returns>Флаг успешности закрытия хранилища</returns>
* **/
internal static bool CertCloseStore(IntPtr _hCertStore, uint _iFlags) {
    if (fIsLinux)
        return LCryptoAPI.CertCloseStore(_hCertStore, _iFlags);
    else
        return WCryptoAPI.CertCloseStore(_hCertStore, _iFlags);
}
/**<summary>Находимся в линуксе</summary>**/
public static bool fIsLinux {
    get {
        int iPlatform = (int) Environment.OSVersion.Platform;
        return (iPlatform == 4) || (iPlatform == 6) || (iPlatform == 128);
    }
}

Таким образом используя методы класса UCryptoAPI можно реализовывать почти единый код под обе системы.

Подписать документ с помощью криптопро 5.0 — уц айтиком

В КриптоПРО версии 5.0 добавлена возможность подписывать документы электронной подписью с помощью утилиты Инструменты КриптоПро. Данная утилита входит в состав пакета установки КриптоПро 5.0, устанавливать отдельно ее не нужно.

Скачать КриптоПро 5.0 можно по ссылке.

Для подписания документа:

1. Откройте меню «Пуск», в списке программ найдите папку КРИПТО-ПРО, откройте её. Запустите приложение Инструменты КриптоПро.

Ярлык Инструменты КриптоПро

2. Перейдите во вкладку «Создание подписи», нажмите на кнопку «Выбрать файл для подписи».

Создание подписи Инструменты КриптоПро

3. В открывшемся окне выберите файл, который необходимо подписать. Нажмите кнопку «Открыть».

Выбор файла Инструменты КриптоПро

4. Под кнопками «Выбрать файл для подписи» и «Сохранить подпись как» отобразится путь исходного файла и путь подписанного файла, который в дальнейшем будет создан.

Обратите внимание! По умолчанию КриптоПро 5.0 подписывает файлы в формате .p7s. Если требуется подписать файл в другом формате (к примеру, .sig), необходимо в конце пути будущего подписанного файла заменить .p7s на .sig.
Поддерживаемые форматы:
.p7s
.sig
.sgn
Формат подписи Инструменты КриптоПро

По умолчанию подписанный документ сохраняется в той же папке, где находится исходный.

5. Выберите, какой электронной подписью необходимо подписать документ.

Выбор сертификата Инструменты КриптоПро

6. Нажмите кнопку «Подписать«. После завершения процесса подписания Вы увидите сообщение «Создание подписи завершилось успехом«, а так же в указанной директории будет создан файл с указанным расширением (.p7s / .sig / .sgn) – это и есть подписанный файл.

Подписать документ Инструменты КриптоПро
Подписанный документ КЭП
Обратите внимание! По умолчанию КриптоПро 5.0 создает присоединённую подпись.
Если Вам необходимо создать отсоединённую подпись, перед подписанием (шаг 6) нажмите кнопку «Показать расширенные» и поставьте галочку «Создать отсоединённую подпись».
Расширенные настройки Инструменты КриптоПро

Подписанный документ вы можете проверить с помощью нашей инструкции

Для того, чтобы работала система автоматического одобрения заявок на основании заявления, подписанного электронной подписью, нужно, чтобы в поле заявления был загружен файл, подписанный прикрепленной электронной подписью.

Вам необходимо предоставить подписанное квалифицированной ЭП заявление и подписанную доверенность (в случаях выпуска на сотрудника).

Для прикрепления данных документов, необходимо перейти в заявку на выпуск подписи и выбрать «Анкета и документы».

Внимание! Документы не принимаются в случае:
— Если они находятся в архиве.
— Если они имеют расширение отличающееся от .sig, .p7s, .sgn
.
— Документ должен быть подписан прикрепленной подписью, открепленные подписи не принимаются.

В открывшемся окне необходимо выбрать раздел «Документы».

Как подписать документ электронной подписью используя Крипто Про 5?  | УЦ ITCOM

Далее необходимо загрузить документы в правильные поля.

Подписанное заявление загружается в поле:

Как подписать документ электронной подписью используя Крипто Про 5?  | УЦ ITCOM

Необходимо нажать на данное поле, откроется окно выбора необходимого документа, выберите подписанное заявление и нажмите «ОК», либо перетяните документ прямо на данную иконку.

Подписанная доверенность загружается в поле:

Как подписать документ электронной подписью используя Крипто Про 5?  | УЦ ITCOM

Необходимо нажать на данное поле, откроется окно выбора необходимого документа, выберите подписанное заявление и нажмите «ОК», либо перетяните документ прямо на данную иконку.

Поиск сертификата

Работа с криптографией обычно начинается с поиска сертификата, для этого в crypt32.dll имеется два метода CertOpenStore (открывает указанное хранилище сертификатов) и простой CertOpenSystemStore (открывает личные сертификаты пользователя). В силу того, что работа с сертификатами не ограничивается только личными сертификатами пользователя подключаем первый:

Поиск сертификата
/**<summary>Поиск сертификата (первого удовлетворяющего критериям поиска)</summary>
* <param name="_pFindType">Тип поиска</param>
* <param name="_pFindValue">Значение поиска</param>
* <param name="_pLocation">Место </param>
* <param name="_pName">Имя хранилища</param>
* <param name="_pCert">Возвращаемый сертификат</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <param name="_fVerify">Проверить сертфиикат</param>
* <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
* **/
public static int FindCertificateCP(string _pFindValue, out X509Certificate2 _pCert, ref string _sError, 
                                    StoreLocation _pLocation = StoreLocation.CurrentUser, 
                             StoreName _pName = StoreName.My, 
                                    X509FindType _pFindType = X509FindType.FindByThumbprint,                                             
                                    bool _fVerify = false) {
    _pCert = null;
    IntPtr   hCert = IntPtr.Zero;                        
    GCHandle hInternal    = new GCHandle();
    GCHandle hFull        = new GCHandle();
    IntPtr   hSysStore    = IntPtr.Zero;
    try {
          // 0) Открываем хранилище
          hSysStore = UCryptoAPI.CertOpenStore(UCConsts.AR_CERT_STORE_PROV_SYSTEM[fIsLinux.ToByte()],
                                               UCConsts.PKCS_7_OR_X509_ASN_ENCODING,
                                               IntPtr.Zero,
                                               UCUtils.MapX509StoreFlags(_pLocation, OpenFlags.ReadOnly),
                                               UCConsts.AR_CRYPTO_STORE_NAME[(int)_pName]);
          if (hSysStore == IntPtr.Zero) {
              _sError = UCConsts.S_ERR_STORE_OPEN.Frm(Marshal.GetLastWin32Error());
              return UConsts.E_CRYPTO_ERR;
          } 
          // 1) Формируем данные в пакете
          if ((_pFindType == X509FindType.FindByThumbprint) || (_pFindType == X509FindType.FindBySerialNumber))
          {
              byte[] arData = _pFindValue.FromHex();
              CRYPTOAPI_BLOB cryptBlob;
              cryptBlob.cbData = arData.Length;
              hInternal = GCHandle.Alloc(arData, GCHandleType.Pinned);
              cryptBlob.pbData = hInternal.AddrOfPinnedObject();
              hFull = GCHandle.Alloc(cryptBlob, GCHandleType.Pinned);                    
          } else {
               byte[] arData;
               if(fIsLinux)
                   arData = Encoding.UTF8.GetBytes(_pFindValue);
               else
                   arData = Encoding.Unicode.GetBytes(_pFindValue);
               hFull = GCHandle.Alloc(arData, GCHandleType.Pinned);
          }
          // 2) Получаем 
          IntPtr hPrev = IntPtr.Zero;
          do {
               hCert = UCryptoAPI.CertFindCertificateInStore(hSysStore, 
                                                             UCConsts.PKCS_7_OR_X509_ASN_ENCODING, 0,
                                                             UCConsts.AR_CRYPT_FIND_TYPE[(int)_pFindType, fIsLinux.ToByte()],
                                                             hFull.AddrOfPinnedObject(), hPrev);
               // 2.1) Освобождаем предыдущий
               if(hPrev != IntPtr.Zero) UCryptoAPI.CertFreeCertificateContext(hPrev);
               // 2.2) Кончились в списке
               if(hCert == IntPtr.Zero) return UConsts.E_NO_CERTIFICATE;                    
               // 2.3) Нашли и валиден
               X509Certificate2 pCert = new ISDP_X509Cert(hCert);
               if (!_fVerify || pCert.ISDPVerify()) {
                   hCert =  IntPtr.Zero;
                   _pCert = pCert;
                   return UConsts.S_OK;
               } 
               hPrev = hCert;
               // Чтобы не очистило
               hCert = IntPtr.Zero;
          } while(hCert != IntPtr.Zero);
          return UConsts.E_NO_CERTIFICATE;
    } catch (Exception E) { 
          _sError = UCConsts.S_FIND_CERT_GEN_ERR.Frm(E.Message);
          return UConsts.E_GEN_EXCEPTION;
    } finally {
          // Очищаем ссылки и закрываем хранилище
          if(hInternal.IsAllocated) hInternal.Free();
          if(hFull.IsAllocated) hFull.Free();
          if (hCert != IntPtr.Zero) UCryptoAPI.CertFreeCertificateContext(hCert);
          UCryptoAPI.CertCloseStore(hSysStore, 0);                
    }
}

Поиск происходит в несколько этапов:

  1. открытие хранилища;
  2. формирование структуры данных по которым ищем;
  3. поиск сертификата;
  4. если требуется, то проверка сертификата (описана в отдельном разделе);
  5. закрытие хранилища и освобождение структуры из пункта 2 (т. к. повсюду здесь идет работа с неуправляемой памятью .Net за нас ничего по очистке делать не будет);

В ходе поиска сертификатов есть несколько тонких моментов.

КриптоПро в Linux работает с ANSI строками, а в Windows с UTF8, поэтому:

  1. при подключении метода открытия хранилища в Linux необходимо параметру кода хранилища явно указать тип маршалинга [In, MarshalAs (UnmanagedType.LPStr)];
  2. передавая строку для поиска (например, по имени Subject) ее необходимо преобразовывать в набор байт различными кодировками;
  3. для всех констант криптования, у которых есть вариация по типу строки (например, CERT_FIND_SUBJECT_STR_A и CERT_FIND_SUBJECT_STR_W) в Windows необходимо выбирать *_W, а в Linux *_A;

Метод MapX509StoreFlags можно взять напрямую из исходников Microsoft без изменений, он просто формирует итоговую маску исходя из .Net флагов.

Значение по которому происходит поиск зависит от типа поиска (сверяйтесь с MSDN для CertFindCertificateInStore), в примере приведены два самых часто используемых варианта — для строкового формата (имена Subject, Issuer и проч) и бинарного (отпечаток, серийный номер).

Процесс создания сертификата из IntPtr в Windows и в Linux сильно отличается. Windows создаст сертификат простым способом:

 new X509Certificate2(hCert);

в Linux же приходиться создавать сертификат в два этапа:

X509Certificate2(new X509Certificate(hCert));

В дальнейшем нам для работы потребуется доступ к hCert, и его надо бы сохранить в объекте сертификата. В Windows его позже можно достать из свойства Handle, однако Linux преобразует структуру CERT_CONTEXT, лежащую по ссылке hCert, в ссылку на структуру x509_st (OpenSSL) и именно ее прописывает в Handle.

Не стоит забывать, что это ссылка на область неуправляемой памяти и ее надо освобождать после окончания работы. Т.к. в .Net 4.5 X509Certificate2 не Disposable — очистку методом CertFreeCertificateContext, надо проводить в деструкторе.

Проверка подписи

Проверка подписи происходит в два этапа, в начале проверяется сама подпись, а затем проверяется сертификат, которым она была сформирована (цепочка, дата подписания и проч).Так же как и при подписании необходимо указать набор подписываемых данных, параметры подписи и саму подпись:

Проверка подписи
/**<summary>Формирует стандартную сктруктуру для проверки подписи </summary>
* <returns>Структуру</returns>
* **/
internal static CRYPT_VERIFY_MESSAGE_PARA GetStdSignVerifyPar() {
    CRYPT_VERIFY_MESSAGE_PARA  pVerifyParams  =  new CRYPT_VERIFY_MESSAGE_PARA();
    pVerifyParams.cbSize = (int)Marshal.SizeOf(pVerifyParams);
    pVerifyParams.dwMsgEncodingType = UCConsts.PKCS_7_OR_X509_ASN_ENCODING;
    pVerifyParams.hCryptProv = 0;
    pVerifyParams.pfnGetSignerCertificate   = IntPtr.Zero;
    pVerifyParams.pvGetArg  = IntPtr.Zero;
    return pVerifyParams;
}
/**<summary>Проверяет подпись</summary>
* <param name="_arData">данные, которые было подписаны</param>
* <param name="_pSign">подпись</param>
* <param name="_pCert">сертификат</param>
* <param name="_sError">возвращаемая строка с ошибкой</param>
* <param name="_fVerifyOnlySign">Проверять только подпись</param>
* <param name="_pRevMode">Режим проверки сертификата</param>
* <param name="_pRevFlag">Флаг проверки сертфииката</param>
* <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
* <remarks>Проверяется только первый подписант</remarks>
* **/
public static int CheckSignCP(byte[] _arData, byte[] _pSign, out X509Certificate2 _pCert, ref string _sError,
                              bool _fVerifyOnlySign = true, 
                              X509RevocationMode _pRevMode = X509RevocationMode.Online,
                              X509RevocationFlag _pRevFlag = X509RevocationFlag.ExcludeRoot){
    _pCert = null;
    IntPtr pHData = Marshal.AllocHGlobal(_arData.Length);
    GCHandle pCertContext = GCHandle.Alloc(IntPtr.Zero, GCHandleType.Pinned);
    try {
        Marshal.Copy(_arData, 0, pHData, _arData.Length);
        CRYPT_VERIFY_MESSAGE_PARA pVerParam = UCUtils.GetStdSignVerifyPar();
        // 0) Проверка подписи
        bool fRes = UCryptoAPI.CryptVerifyDetachedMessageSignature(
                                           ref pVerParam,                     // Параметры подтверждения
                                           0,                                 // Индекс подписанта
                                           _pSign,                            // Подпись
                                           _pSign.Length,                     // Длина подписи
                                           1,                                 // кол-во файлов на подпись
                                           new IntPtr[1] { pHData },          // подписанные файлы
                                           new int[1] { _arData.Length },     // Длины подписанных файлов
                                           pCertContext.AddrOfPinnedObject());// Ссылка на сертификат
        if (!fRes) {
            _sError = UCConsts.S_SIGN_CHECK_ERR.Frm(Marshal.GetLastWin32Error().ToString("X"));
            return UConsts.E_CRYPTO_ERR;
        }
        // 1) Извлечение сертфииката
        _pCert = new ISDP_X509Cert((IntPtr)pCertContext.Target);
        if (_pCert == null) {
            _sError = UCConsts.S_SIGN_CHECK_CERT_ERR;
            return UConsts.E_CRYPTO_ERR;
        }
        // 2) Проверка сертификата
        if (!_fVerifyOnlySign) {
            List<DateTime> pDates;
            // 2.1) Получаем список дат
            int iRes = GetSignDateTimeCP(_pSign, out pDates, ref  _sError);
            // 2.2) Верифицируем первый сертификат
            iRes = _pCert.ISDPVerify(ref _sError, pDates[0], _pRevMode, _pRevFlag);
            if (iRes != UConsts.S_OK) return iRes;
        }
        return UConsts.S_OK;
    } catch (Exception E) {
        _sError = UCConsts.S_SIGN_CHECK_ERR.Frm(E.Message);
        return UConsts.E_GEN_EXCEPTION;;
    } finally {
        Marshal.FreeHGlobal(pHData);
        if ((_pCert == null) && pCertContext.IsAllocated && ((IntPtr)pCertContext.Target != IntPtr.Zero))
            UCryptoAPI.CertFreeCertificateContext((IntPtr)pCertContext.Target);
        pCertContext.Free();                
    }
}

Для удобства процесс формирования структуры с параметрами вынесен в отдельный метод (GetStdSignVerifyPar). После чего проверяется сама подпись и извлекается первый подписант (по хорошему надо было бы извлечь всех, но подпись содержащая несколько подписантов это все таки экзотика).

После извлечения сертификата подписанта преобразуем его в наш класс и проверяем (если это указано в параметрах метода). Для проверки используется дата подписания первого подписанта (см. раздел извлечение информации из подписи, и раздел проверка сертификата).

Проверка сертификата

Сертификат это не только открытый ключ, но еще и набор разной информации о его владельце, о том, кто его выдал и о наборе действий, которые с его помощью можно делать. Так же у сертификата есть период действия и возможность отзыва, в случае компрометации. Чаще всего под проверкой сертификата подразумевается следующее:

  1. целостность цепочки (сертификат издателя, сертификат издателя сертификата издателя, и т. п.);
  2. сертификат корневого издателя — должен быть в хранилище доверенных корневых центров;
  3. период действия всех сертификатов — момент использования сертификата должен быть в границах этого периода;
  4. каждый из сертификатов в цепочке, кроме корневого, должен отсутствовать в списке отозванных у своего издателя (CRL);

По хорошему надо еще проверять и права подписи, но в реальной жизни это делается редко.

Как уже понятно из введения, проверка сертификата на валидность, одна из самых сложных задач. Именно поэтому в библиотеке масса методов для реализации каждого из пунктов в отдельности. Поэтому, для упрощения обратимся к исходникам .Net для метода X509Certificate2.Verify() и возьмем их за основу.


Проверка состоит из двух этапов:

  1. сформировать цепочку сертификатов вплоть до корневого;
  2. проверить каждый из сертификатов в ней (на отзыв, время и проч.);

Такая проверка должна осуществляться перед подписанием и шифрованием на текущую дату, и в момент проверки подписи на дату подписания. Сам метод проверки небольшой:

Проверка сертификата
/**<summary>Проверить сертификат</summary>
* <param name="_iRevFlag">Флаг отзыва</param>
* <param name="_iRevMode">Режим отзыва</param>
* <param name="_hPolicy">Ссылка на правила проверки</param>
* <param name="_hCert">контекст сертфиката</param>
* <param name="_iCTLTimeout">таймаут запроса списка отзыва</param>
* <param name="_rOnDate">Дата верификацмм</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
* **/
internal static int VerifyCertificate (IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag, 
                                       DateTime _rOnDate, TimeSpan _iCTLTimeout, IntPtr _hPolicy, ref string _sError) {

    if (_hCert == IntPtr.Zero) {
        _sError = UCConsts.S_CRYPTO_CERT_CHECK_ERR;
        return UConsts.E_NO_CERTIFICATE;
    }
    CERT_CHAIN_POLICY_PARA   pPolicyParam  = new CERT_CHAIN_POLICY_PARA(Marshal.SizeOf(typeof(CERT_CHAIN_POLICY_PARA)));
    CERT_CHAIN_POLICY_STATUS pPolicyStatus = new CERT_CHAIN_POLICY_STATUS(Marshal.SizeOf(typeof(CERT_CHAIN_POLICY_STATUS))); 
    // 1) Формируем цепочку
    IntPtr hChain = IntPtr.Zero;
    try {
        int iRes = BuildChain(new IntPtr(UCConsts.HCCE_CURRENT_USER), _hCert, __iRevMode, _iRevFlag,
                             _rOnDate, _iCTLTimeout, ref hChain, ref _sError);
        if (iRes != UConsts.S_OK) return iRes;
        // 2) Проверяем цепочку
        if (UCryptoAPI.CertVerifyCertificateChainPolicy(_hPolicy, hChain, ref pPolicyParam, ref pPolicyStatus)) {
            if (pPolicyStatus.dwError != 0) {
                _sError = UCConsts.S_CRYPTO_CHAIN_CHECK_ERR.Frm(pPolicyStatus.dwError);
                return UConsts.E_CRYPTO_ERR;
            } 
        } else{
            _sError = UCConsts.S_CRYPTO_CHAIN_CHECK_ERR.Frm(Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        }
        return UConsts.S_OK;
    } catch (Exception E) {
        _sError = UCConsts.S_CRYPTO_CERT_VERIFY_GEN_ERR.Frm(E.Message);
        return UConsts.E_GEN_EXCEPTION;
    } finally {
        if(hChain != IntPtr.Zero) UCryptoAPI.CertFreeCertificateChain(hChain);
    }
}

Сначала формируется цепочка методом BuildChain, а затем она проверяется. В ходе формирования цепочки формируется структура параметров, дата проверки и флаги проверки:

Формирование цепочки сертификата
/**<summary>Формирует цепочку сертфикиата для проверки</summary>
* <param name="_hChain">КОнтекст цепочки сертфиикатов</param>
* <param name="_iRevFlag">Флаг отзыва</param>
* <param name="_iRevMode">Режим отзыва</param>
* <param name="_hChainEngine">Тип хранилища</param>
* <param name="_hCert">контекст сертфиката</param>
* <param name="_rCTLTimeOut">таймаут запроса списка отзыва</param>
* <param name="_rOnDate">Дата верификацмм</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
* **/
internal static int BuildChain (IntPtr _hChainEngine, IntPtr _hCert, X509RevocationMode _iRevMode, 
                                X509RevocationFlag _iRevFlag, DateTime _rOnDate, TimeSpan _rCTLTimeOut, 
                                ref IntPtr _hChain, ref string _sError) {
    // 0) Проверка наличия сертификата
    if (_hCert == IntPtr.Zero) {
        _sError = UCConsts.S_CRYPTO_CERT_CHAIN_ERR;
        return UConsts.E_NO_CERTIFICATE;
    }
    // 1) Параметры
    CERT_CHAIN_PARA pChainParams = new CERT_CHAIN_PARA();
    pChainParams.cbSize = (uint) Marshal.SizeOf(pChainParams); 
    IntPtr hAppPolicy = IntPtr.Zero;
    IntPtr hCertPolicy = IntPtr.Zero;
    try {
        // 2) Формируем правила приложения
        pChainParams.dwUrlRetrievalTimeout = (uint)Math.Floor(_rCTLTimeOut.TotalMilliseconds);
        // 3) Время проверки
        FILETIME pVerifyTime = new FILETIME(_rOnDate.ToFileTime());
        // 4) Формируем флаг
        uint _iFlags = MapRevocationFlags(_iRevMode, _iRevFlag);
        // 5) Формирование цепочки
        if (!UCryptoAPI.CertGetCertificateChain(_hChainEngine, _hCert, ref pVerifyTime,
                                                IntPtr.Zero, ref pChainParams, _iFlags,
                                                IntPtr.Zero, ref _hChain)) {
            _sError = UCConsts.S_CRYPTO_CHAIN_BUILD_ERR.Frm(Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        }
    } catch(Exception E) { 
        _sError = UCConsts.S_CRYPTO_CHAIN_GEN_ERR.Frm(E.Message);
        return UConsts.E_GEN_EXCEPTION;
    } finally {
        Marshal.FreeHGlobal(hAppPolicy);
        Marshal.FreeHGlobal(hCertPolicy);
    }
    return UConsts.S_OK;
}

Это сильно упрощенный вариант формирования цепочки по сравнению с тем, как ее формирует Microsoft. Структуры hCertPolicy и hAppPolicy можно наполнить OID-ами, отображающими права на действия, которые необходимы в проверяемом сертификате. Но в примере, будем считать, что их мы не проверяем.

Так же можно в параметры построения цепочки добавить дополнительное хранилище сертификатов (например, извлеченное из подписи).

Метод MapRevocationFlags — можно взять напрямую из исходников .Net без изменений —он просто формирует uint по набору передаваемых флагов.

Формирование подписи

При работе с ГОСТовыми сертификатами почти всегда используются отцепленные подписи с одним подписантом. Для того чтобы создать такую подпись требуется довольно простой блок кода:

Формирование подписи
/**<summary> Подписывает информацию</summary>
* <param name="_arData">Данные для подписания</param>
* <param name="_pCert">Сертификат</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <param name="_arRes">Подпись сертфииката</param>
* <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
* **/
public static int SignDataCP(byte[] _arData, X509Certificate2 _pCert, out byte[] _arRes, ref string _sError)
{
    _arRes = new byte[0];
    // 0) Формируем параметры
    CRYPT_SIGN_MESSAGE_PARA pParams = new CRYPT_SIGN_MESSAGE_PARA();
    pParams.cbSize = Marshal.SizeOf(typeof(CRYPT_SIGN_MESSAGE_PARA));
    pParams.dwMsgEncodingType      = (int)(UCConsts.PKCS_7_OR_X509_ASN_ENCODING);
    pParams.pSigningCert           = _pCert.getRealHandle();
    pParams.cMsgCert               = 1;            
    pParams.HashAlgorithm.pszObjId = _pCert.getHashAlgirtmOid();
    IntPtr pGlobData = Marshal.AllocHGlobal(_arData.Length);
    GCHandle pGC = GCHandle.Alloc(_pCert.getRealHandle(), GCHandleType.Pinned);
    try {
        pParams.rgpMsgCert = pGC.AddrOfPinnedObject();
        Marshal.Copy(_arData, 0, pGlobData, _arData.Length);
        uint iLen = 50000;
        byte[] arRes = new byte[iLen];
        // 1) Формирование подписи
        if (!UCryptoAPI.CryptSignMessage(ref pParams, true, 1, new IntPtr[1] { pGlobData },
                                         new uint[1] { (uint)_arData.Length }, arRes, ref iLen)) {
            _sError = UCConsts.S_MAKE_SIGN_ERR.Frm(Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        }
        Array.Resize(ref arRes, (int)iLen);
        _arRes = arRes;
        return UConsts.S_OK;;
    } catch (Exception E) { 
        _sError = UCConsts.S_MAKE_SIGN_ERR.Frm(E.Message);
        return UConsts.E_GEN_EXCEPTION;
    } finally {
        pGC.Free();
        Marshal.FreeHGlobal(pGlobData);
    }
}

В ходе работы метода формируется структура с параметрами и вызывается метод подписания. Структура параметров может позволять сохранить в подписи сертификаты для формирования полной цепочки (поля cMsgCert и rgpMsgCert, первый хранит количество сертификатов, второй список ссылок на структуры этих сертификатов).

Метод подписания может получать один или несколько документов для одновременного подписания одной подписью. Это, кстати, не противоречит 63 ФЗ и бывает очень удобно, т. к. пользователь вряд ли обрадуется необходимости несколько раз нажимать на кнопку «подписать».

Основной странностью данного метода является то, что он не работает в режиме двух вызовов, характерного для большинства библиотечных методов, работающих с большими блоками памяти (первый с null — выдает необходимую длину буфера, второй заполняет буфер). Поэтому необходимо создать большой буфер, а затем укоротить его по реальной длине.

Единственной серьезной проблемой является поиск OID алгоритма хэширования (Digest) используемый при подписании — в явном виде его нет в сертификате (там есть только алгоритм самой подписи). И если в Windows его можно указать пустой строкой — он подцепится автоматически, но Linux откажется подписывать если алгоритм не тот.

Но тут есть хитрость — в информации об алгоритме подписи (структура CRYPT_OID_INFO) в pszOID храниться OID подписи, а в Algid — храниться идентификатор алгоритма хэширования. А преобразовать Algid в OID уже дело техники:

Получение OID алгоритма хэширования
/**<summary>Получение OID алгоритма хэширования сертификату</summary>
* <param name="_hCertHandle">Хэндл сертификата</param>
* <param name="_sOID">Возвращаемый параметр OID</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
* **/
internal static int GetHashAlgoritmOID(IntPtr _hCertHandle, out string _sOID, ref string _sError) {
    _sOID = "";
    IntPtr hHashAlgInfo = IntPtr.Zero;
    IntPtr hData        = IntPtr.Zero;
    try {
        CERT_CONTEXT pContext = (CERT_CONTEXT)Marshal.PtrToStructure(_hCertHandle, typeof(CERT_CONTEXT));
        CERT_INFO pCertInfo = (CERT_INFO)Marshal.PtrToStructure(pContext.pCertInfo, typeof(CERT_INFO));
        // Извлекаем AlgID
        // через UCryptoAPI.CertAlgIdToOID  в Windows первый раз работает, второй падает
        byte[] arData = BitConverter.GetBytes(UCryptoAPI.CertOIDToAlgId(pCertInfo.SignatureAlgorithm.pszObjId));
        hData = Marshal.AllocHGlobal(arData.Length);                
        Marshal.Copy(arData, 0, hData, arData.Length);
        // Поиск OID
        hHashAlgInfo = UCryptoAPI.CryptFindOIDInfo(UCConsts.CRYPT_OID_INFO_ALGID_KEY,
                                                   hData,
                                                   UCConsts.CRYPT_HASH_ALG_OID_GROUP_ID);
        if (hHashAlgInfo == IntPtr.Zero) {
            _sError = UCConsts.S_NO_HASH_ALG_ERR.Frm( Marshal.GetLastWin32Error());
            return UConsts.E_GEN_EXCEPTION;
        }
        CRYPT_OID_INFO pHashAlgInfo = (CRYPT_OID_INFO)Marshal.PtrToStructure(hHashAlgInfo, typeof(CRYPT_OID_INFO));
        _sOID = pHashAlgInfo.pszOID;
        return UConsts.S_OK;
    } catch (Exception E) {
        _sError = UCConsts.S_DETERM_HASH_ALG_ERR.Frm(E.Message);
        return UConsts.E_GEN_EXCEPTION;
    } finally {
         Marshal.FreeHGlobal(hData);
    }
}

Внимательно прочитав код можно удивится, что идентификатор алгоритма получается простым способом (CertOIDToAlgId) а Oid по нему — сложным (CryptFindOIDInfo). Логично было бы предположить использование либо оба сложных, либо оба простых способа, и в Linux оба варианта успешно работают.

Шифрование

Процесс шифрования во многом аналогичен процессу подписания, он довольно прост, и основная проблема так же состоит в определении алгоритма. В отличии от подписи шифрование чаще всего используются сцепленное в адрес одного или сразу нескольких адресатов (например, шифруют еще и в адрес себя, чтобы была возможность прочитать сообщение своим ключом).

Зашифровать данные
/**<summary>Зашифрованные данные</summary>
* <param name="_arInput">Данные для расшифровки</param>
* <param name="_pCert">Сертификат</param>
* <param name="_arRes">Результат</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартный код с ошибкой, если UConsts.S_OK то все ок</returns>
* **/
public static int EncryptDataCP(byte[] _arInput, X509Certificate2 _pCert, out byte[] _arRes, ref string _sError) {
    _arRes = new byte[0];
    try {
        // 0) Инициализация параметров
        CRYPT_ENCRYPT_MESSAGE_PARA  pParams         = new CRYPT_ENCRYPT_MESSAGE_PARA();
        pParams.dwMsgEncodingType                   = UCConsts.PKCS_7_OR_X509_ASN_ENCODING;
        pParams.ContentEncryptionAlgorithm.pszObjId = _pCert.getEncodeAlgirtmOid();
        pParams.cbSize = Marshal.SizeOf(pParams);
        // 1) Извлечение длины
        int iLen = 0;
        if (!UCryptoAPI.CryptEncryptMessage(ref pParams, 1, new IntPtr[] { _pCert.getRealHandle() },
                                            _arInput, _arInput.Length, null, ref iLen)) {
            _sError = UCConsts.S_CRYPT_ENCODE_LEN_ERR.Frm(Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        }
        // 2) Второй запрос реальное шифрование
        _arRes = new byte[iLen];
        if (!UCryptoAPI.CryptEncryptMessage(ref pParams, 1, new IntPtr[] {_pCert.getRealHandle() },
                                           _arInput, _arInput.Length, _arRes, ref iLen)) {
              _sError = UCConsts.S_CRYPT_ENCODE_ERR.Frm(Marshal.GetLastWin32Error());
              return UConsts.E_CRYPTO_ERR;
        }
        return UConsts.S_OK;
    } catch (Exception E) {
        _sError = UCConsts.S_CRYPT_ENCODE_ERR.Frm(E.Message);
        return UConsts.E_GEN_EXCEPTION;
    }
}

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

В примере шифруется в адрес одного адресата, но путем добавления дополнительных сертификатов в массив и установке общего количества в параметры метода, можно увеличить число адресатов.

А вот с алгоритмом опять проблемы. В сертификате нет ни его, ни даже косвенных значений по которым его можно было бы определить (как удалось с алгоритмом подписи). Поэтому придется извлекать список поддерживаемых алгоритмов из провайдера:

Получение алгоритма шифрования
/**<summary>Получение OID алгоритма шифрования сертификату</summary>
* <param name="_hCertHandle">Хэндл сертификата</param>
* <param name="_sOID">Возвращаемый параметр OID</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
* **/
internal static int GetEncodeAlgoritmOID(IntPtr _hCertHandle, out string _sOID, ref string _sError) {
    bool fNeedRelease = false;
    _sOID = "";
    uint iKeySpec = 0;
    IntPtr  hCrypto = IntPtr.Zero;
    try {
        // 0) Получаем контекст провайдера
        if (!UCryptoAPI.CryptAcquireCertificatePrivateKey(_hCertHandle, 0, IntPtr.Zero,
                                                          ref hCrypto, ref iKeySpec, ref fNeedRelease)) {
            _sError = UCConsts.S_CRYPTO_PROV_INIT_ERR.Frm(Marshal.GetLastWin32Error());
            return UConsts.E_CRYPTO_ERR;
        } 
        uint iLen = 1000;
        byte[] arData = new byte[1000];
        uint iFlag = 1; // Инициализация
        // 1) Проходим в цикле по алгоритмам
        while (UCryptoAPI.CryptGetProvParam(hCrypto, UCConsts.PP_ENUMALGS, arData, ref iLen, iFlag)){
            iFlag = 2; // Следующий
            PROV_ENUMALGS pInfo = ConvertBytesToStruct<PROV_ENUMALGS>(arData);
            // 2) Пытаемся получить OID  в рамках алгоримтов шифрования
            byte[]  arDataAlg = BitConverter.GetBytes(pInfo.aiAlgid);
            IntPtr hDataAlg = Marshal.AllocHGlobal(arDataAlg.Length);
            try {
                Marshal.Copy(arDataAlg, 0, hDataAlg, arDataAlg.Length);
                IntPtr hHashAlgInfo2 = UCryptoAPI.CryptFindOIDInfo(UCConsts.CRYPT_OID_INFO_ALGID_KEY,
                                                                   hDataAlg,
                                                                   UCConsts.CRYPT_ENCRYPT_ALG_OID_GROUP_ID);
                // 2.1) Нашли - возвращаем
                if (hHashAlgInfo2 != IntPtr.Zero) {
                    CRYPT_OID_INFO pHashAlgInfo2 = (CRYPT_OID_INFO)Marshal.PtrToStructure(hHashAlgInfo2,
                                                                                          typeof(CRYPT_OID_INFO));
                    _sOID = pHashAlgInfo2.pszOID ;
                    return UConsts.S_OK;
                }
            } finally {
                 Marshal.FreeHGlobal(hDataAlg);
            }
        }
        // 3) Не нашли - ошибка
        _sError = UCConsts.S_NO_ENCODE_ALG_ERR;
        return UConsts.E_CRYPTO_ERR;
    } catch (Exception E) { 
        _sError = UCConsts.S_DETERM_ENCODE_ALG_ERR.Frm(E.Message);
        return UConsts.E_GEN_EXCEPTION;
    }finally {
        if((hCrypto != IntPtr.Zero) && fNeedRelease) UCryptoAPI.CryptReleaseContext(hCrypto, 0);
    }
}

В примере извлекается контекст закрытого ключа и по нему происходит поиск по алгоритмам. Но в этом списке находятся все алгоритмы (обмена ключей, хэширования, подписи, шифрования и проч.), поэтому надо отфильтровать только алгоритмы шифрования. Пытаемся по каждому извлечь информацию ограничившись группой алгоритмов шифрования (UCConsts.CRYPT_ENCRYPT_ALG_OID_GROUP_ID). И если информация найдена — значит это наш алгоритм.

В случае если таких алгоритмов больше чем один можно так же фильтровать по размеру (опираясь на размер алгоритма хэширования).

Оцените статью
ЭЦП64
Добавить комментарий