crypto-pro from get-net — GithubHelp

crypto-pro from get-net - GithubHelp ЭЦП

Так, а что надо на выходе?

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

  1. С помощью службы проверки подлинности электронных документов ЭП подтверждается на протале Gosuslugi.

  2. КриптоАРМ проверяет подписи пользователей

    1. . При вводе поля «Время создания ЭП» появится окно, позволяющее выбрать и кратко описать характеристики этой записи.

      Стобец "Время создация ЭП"
      Стобец «Время создация ЭП»
    2. Вкладки «Time Stamps» имеют следующие значения в информации подписи и сертификата (дважды нажмите на примечание в таблице):

      1. Подпись:
      2. Доказательство подлинности:
    3. Доказательство подлинности», «штамп времени подписи» и блоки времени подписания включаются в отчет о проверке подписи. Для сравнения, отчет о проверке будет сокращен, если документ подписан УЦЭК.

  3. Контур. Crypto выдаст сообщение о том, что улучшенная подпись была подтверждена и сертификат на момент подписания был действителен:

    Усовершенствованная подпись подтверждена
    Усовершенствованная подпись подтверждена

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

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

Дешифрование данных
/**<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);
    }
}

Файлы, из которых система будет извлекать ключ, указаны в репозитории при установке параметров. Расшифрованные данные и используемый сертификат будут выводиться системой.

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

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

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

Как читается подпись?

Извлечение информации из подписи
/**<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), затем туда же помещаются реальные подписи. Остается проверить, что все это реально, и получить список подписантов. Список сертификатов извлекается последовательно:

Он подсчитывает количество сертификатов с помощью параметра CMSG_CERT-COUNT перед получением данных о каждом сертификате по очереди. Процесс создания контекста сертификата и, в конечном итоге, самого сертификата завершен.

Извлечение данных из подписавшего может быть более сложным. Сертификат подписи указан (например, дата подписания). Ниже приведена процедура извлечения данных:

Извлечение информации о подписанте
/**<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 в настоящее время есть только дата.

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

Криптопро | подпись pdf-документов с использованием криптопро .net

Созданный компанией Adobe Systems Inc. (АНО «КриптоПро»), КриптоПро PDF предназначен для генерации и проверки электронной подписи документов формата pdf в соответствии с требованиями ГОСТ Р 34.10-2001.

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

Поработав с компанией iText Software Corp. над этим сценарием в Калифорнии, США, мы подготовили два примера программных кодов для формирования и проверки электронной подписи с использованием их библиотеки iTextSharp. Как программно установить PIN-код на контейнер при подписании чего-либо.

Тексты этих примеров представлены как часть CryptoPro. Net (в файле Simple35, который по умолчанию находится на вкладке %Programfiles (x86) %Crypto Pro).

Если возникнут вопросы, задавайте их!

Alexey Goldbergs

Максимальная пластина

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

Система должна была продолжать работать в среде Windows, несмотря на перенос на Linux, чтобы внешняя криптография могла обрабатываться с использованием хорошо известных методов, таких как «byte[] SignData (Bute [], _ar Dida), X509Certificate2 — pcter», который должен был функционировать одинаково и для lynx, и для pcter.

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

И создать статические классы «WCryptoAPI» и «L».

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

Вы можете самостоятельно создать синтаксис соединения для каждого из методов или воспользоваться сайтом pinvoke (класс 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 позволяет реализовать практически идентичный код для обеих систем.

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

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

Поиск сертификата
/**<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 не будет делать ничего, чтобы очистить ее);

Поиск сертификатов на практике выявляет ряд тонких проблем.

КриптоПро функционирует в Windows без UTF8 и с ANSI-строками.

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

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

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

Процедура создания сертификата из IntPtr отличается в Windows и Linux. Windows легко создаст сертификат.

 new X509Certificate2(hCert);

Два шага требуются для создания сертификата на Linux:

X509Certificate2(new X509Certificate(hCert));

Позже нам нужен доступ к HCERT, и нам нужно хранить его в объекте сертификата. В Windows он может быть выведен из свойства ручки, но Linux будет передаваться на ссылку на структуру x509_ST (openssl).

Помните, что это относится к неуправляемой области памяти и что она должна быть освобождена после завершения работы. из-за. Поскольку X501Certificate2 в Net 4.5 не является Disposable, контекстный метод CertFreeCertificates должен использовать деструктор для очистки после себя.

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

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

Проверка подписи
/**<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 формирует построение цепочки, это более сжатая версия концепции. OID, представляющие права на действия в проверяемом сертификате, могут быть добавлены в систему hCertPolicy. Однако в примере, если мы не проверяем его или их обоснования.

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

MapRevocationFlags могут быть взяты непосредственно из источника. Net, он просто формирует флаги для набора передаваемых символов

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

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

Формирование подписи
/**<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);
    }
}

Метод вызывает метод подписи и создает структуру с параметрами. Структура с параметрами по умолчанию позволяет хранить сертификаты в подписи для создания целой цепочки.

Одновременно могут подписываться несколько документов. Пользователю придется несколько раз подождать, прежде чем нажать кнопку «подписать», но теоретически это не противоречит статье 63 Федерального закона и даже может быть выгодно.

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

Найти OID алгоритма хеширования дайки — тот, который используется при подписании — является единственной проблемой. Кроме того, он будет автоматически подключен, если его можно указать с помощью пустой строки.

Однако есть и другая хитрость: OIL-коды хранятся в данных об алгоритме подписи (структура CRBOID) и Algid. В OID, AGID — это уже технический вопрос.

Получение 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);
    }
}

Если вы внимательно прочитаете код, вы можете удивиться, узнав, что Oid на нем получен с помощью прямого метода (CertOIDToAlgId). Имеет смысл использовать как прямые, так и непрямые методы.

Шифрование

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

Зашифровать данные
/**<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;
    }
}

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

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

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

Получение алгоритма шифрования
/**<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);
    }
}

В примере выбирается контекст закрытого ключа, и его ищут алгоритмы. Но этот список не включает алгоритмы для обмена ключами, хэширования и подписания. Мы проверяем каждый алгоритм, используя группу протоколов шифрования (UCKONSTS. CRPT_ENDING-OD). Если информация найдена — это наш алгоритм.

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

Заключение

Нагрузочное тестирование с использованием плана полного рабочего цикла.

  1. Подождите 10 мс;
  2. Получите сертификат;
  3. Подпишите байт[] {1, 2, 3, 4, 5};
  4. Проверьте полученную подпись;
  5. Получите параметры подписи;
  6. Зашифруйте байт[] {1, 2, 3, 4, 5};
  7. Расшифруйте полученные данные;

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

Необходимо удалить критическую секцию, чтобы гарантировать согласованность работы всех методов класса UCRYPTOAPI. Для этого добавьте поле FPCPSECTION типа OBJECT, а затем добавьте следующую конструкцию к каждому вызову:

private static object fpCPSection = new object();
/**<summary>Закрывает сообщение</summary>
* <param name="_hCryptMsg">Указатель на сообщение</param>
* **/
internal static bool CryptMsgClose(IntPtr _hCryptMsg) {
    lock (pCPSection) {
        if (fIsLinux)
            return LCryptoAPI.CryptMsgClose(_hCryptMsg);
        else
            return WCryptoAPI.CryptMsgClose(_hCryptMsg);
    }
}
/**<summary>Критическая секция для работы с КриптоПро</summary>**/
public static object pCPSection {
    get { return fpCPSection;}
}

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

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

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