Connect OpenSSL to Mono

    In the previous article, the process of integrating CryptoPro standards with mono was described. In the same detail we will focus on connecting RSA certificates.


    We continued to migrate one of our server systems written in C # to Linux, and the turn came to the RSA part. If last time the difficulties in connecting were easily explained by the interaction of two, initially unrelated to each other systems, then when you connected "normal" RSA certificates from mono, obviously no one expected a trick.



    Installing the certificate and key did not cause any problems, and the system even saw it in the regular storage. However, neither sign, nor encrypt, or pull the data from the previously formed signature was no longer possible - mono steadily fell with an error. It was necessary, as in the case of CryptoPro, to connect directly to the encryption library. For RSA certificates in Linux, the main candidate for such a connection is OpenSSL.


    Certificate installation


    Fortunately Centos 7 has a built-in version of OpenSSL - 1.0.2k. In order not to introduce additional difficulties in the work of the system, we decided to connect to this version. OpenSSL allows you to create special file certificate stores, however:


    1. such a store contains certificates and CRLs, not private keys, so they will then have to be stored separately;
    2. storing certificates and private keys on a disk in an unprotected form on Windows is “extremely insecure” (those responsible for digital security usually describe it more succinctly and less censorship), frankly, it is not very safe in Linux, but, in fact, is common practice;
    3. it is quite problematic to coordinate the location of such storage in Windows and Linux;
    4. in the case of manual implementation of the storage, you need a utility to manage a set of certificates;
    5. mono itself uses disk storage with the OpenSSL structure, and also stores private keys in the open form nearby;

    For these reasons, we will use the standard .Net and mono certificate stores for connecting OpenSSL. To do this in Linux, the certificate and private key must first be placed in the mono repository.

    Certificate installation
    Воспользуемся для этого штатной утилитой certmgr. В начале инсталлируем закрытый ключ из pfx:

    certmgr -importKey -c -p {password} My {pfx file}

    Затем ставим сертификат из этого pfx, закрытый ключ автоматически подключится к нему:

    certmgr -add -c My {cer file}

    Если требуется установить ключ в хранилище для машины, то необходимо добавить опцию -m.

    После чего сертификат можно увидеть в хранилище:

    certmgr -list -c -v My

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

    Connection in code


    In the same way as last time, the system, despite the transfer to Linux, should continue to function in the Windows environment. Therefore, externally, work with cryptography should be carried out through general methods of the form “byte [] SignData (byte [] _arData, X509Certificate2 _pCert)”, which should have worked equally in Linux and in Windows.


    Ideally, there should be methods that would work as in Windows - regardless of the type of certificate (on Linux via OpenSSL or CryptoPro, depending on the certificate, and on Windows - via crypt32).


    Analysis of the OpenSSL libraries showed that the main library in Windows is “libeay32.dll”, and in Linux “libcrypto.so.10”. In the same way as last time we form two classes WOpenSSLAPI and LOpenSSLAPI, which contain the list of plug-in library methods:

    [DllImport(CTRYPTLIB, CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.Cdecl)]
    internalstaticexternvoidOPENSSL_init();
    

    Pay attention to the calling convention, unlike CryptoPro - here you must explicitly specify it. The syntax for connecting each of the methods this time will have to be formed independently based on the * .h source files of OpenSSL.


    The basic rules for forming the syntax of a call in C # based on the data from the .h files are as follows:


    1. Any references to structures, strings, etc. - IntPtr, including links within the structures themselves;
    2. links to links - ref IntPtr, if this option does not work, then just IntPtr. In this case, the link itself will have to be put and removed manually;
    3. arrays - byte [];
    4. long in C (OpenSSL) is an int in C # (a small, at first glance, error can turn into hours of searching for the source of unpredictable errors);

    In the declaration, you can, by habit, specify SetLastError = true, but the library will ignore this - errors will not be available through Marshal.GetLastWin32Error (). For access to errors at OpenSSL the methods.


    And then we form the already familiar static class “UOpenSSLAPI” which, depending on the system, will call the method of one of two classes:


    privatestaticobject fpOSSection = newobject();
    /**<summary>Иницииализация библиотеки OpenSSL</summary>**/publicstaticvoidOPENSSL_init() {
        lock (pOSSection) {
            if (fIsLinux)
                LOpenSSLAPI.OPENSSL_init();
            else
                WOpenSSLAPI.OPENSSL_init();
        }
    }
    /**<summary>Критическая секция доступа в OpenSSL</summary>**/publicstaticobject pOSSection
    {
        get { return fpOSSection; }
    }
    /**<summary>Находимся в линуксе</summary>**/publicstaticbool fIsLinux {
        get {
            int iPlatform = (int) Environment.OSVersion.Platform;
            return (iPlatform == 4) || (iPlatform == 6) || (iPlatform == 128);
        }
    }
    

    Just note the presence of a critical section. OpenSSL theoretically works in a multi-threaded environment. But, firstly, the description immediately states that this is not guaranteed:


    But you still can't concurrently use most objects in multiple threads.

    And secondly, the connection method is not the most trivial. Plain two nuclear VM (server with an Intel Xeon E5649 processor in Hyper-Threading mode) using such a critical section gives about 100 complete cycles (see testing algorithm from the previous article ) or 600 signatures per second, which is basically enough for most tasks ( under heavy loads, the microservice or nodal system architecture will still be used rather.


    Initialization and unloading of OpenSSL


    Unlike CryptoPro, OpenSSL requires certain actions before starting to use it even after finishing work with the library:

    /**<summary>Инициализация OpenSSL</summary>**/publicstaticvoidInitOpenSSL() {
        UOpenSSLAPI.OPENSSL_init();
        UOpenSSLAPI.ERR_load_crypto_strings();
        UOpenSSLAPI.ERR_load_RSA_strings();
        UOpenSSLAPI.OPENSSL_add_all_algorithms_conf();
        UOpenSSLAPI.OpenSSL_add_all_ciphers();
        UOpenSSLAPI.OpenSSL_add_all_digests();
    }
    /**<summary>Очистка OpenSSL</summary>**/publicstaticvoidCleanupOpenSSL() {
        UOpenSSLAPI.EVP_cleanup();
        UOpenSSLAPI.CRYPTO_cleanup_all_ex_data();
        UOpenSSLAPI.ERR_free_strings();
    }
    


    Error Information


    OpenSSL stores information about errors in internal structures for access to which, in the library there are special methods. Unfortunately, some simple methods, such as ERR_error_string, are unstable, so you have to use more complex methods:


    Getting Error Information
    /**<summary>Сформировать строку с ошибкой OpenSSL</summary>
    * <param name="_iErr">Код ошибки</param>
    * <param name="_iPart">Раздел</param>
    * <returns>Строка ошибки</returns>
    * **/publicstaticstringGetErrStrPart(ulong _iErr, int _iPart) {
        // 0) Определяем тип строки
        IntPtr hErrStr = IntPtr.Zero;
        switch (_iPart) {
            case0: hErrStr = UOpenSSLAPI.ERR_lib_error_string(_iErr);
                    break;
            case1: hErrStr = UOpenSSLAPI.ERR_func_error_string(_iErr);
                    break;
            case2: hErrStr = UOpenSSLAPI.ERR_reason_error_string(_iErr);
                    break;
        }
        // 1) Формируем срокуreturn PtrToFirstStr(hErrStr);
    }
    /**<summary>Сформировать строку с ошибкой OpenSSL</summary>
    * <param name="_iErr">Код ошибки</param>
    * <returns>Строка ошибки</returns>
    * **/publicstaticstringGetErrStr(ulong _iErr ) {
        return UCConsts.S_GEN_LIB_ERR_MAKRO.Frm(_iErr, GetErrStrPart(_iErr, 0),
                                                GetErrStrPart(_iErr, 1), 
                                                GetErrStrPart(_iErr, 2));
    }
    /**<summary>Сформировать строку с ошибкой OpenSSL</summary>
    * <returns>Строка ошибки</returns>
    * **/publicstaticstringGetErrStrOS() {
        return GetErrStr(UOpenSSLAPI.ERR_get_error());
    }
    

    An error in OpenSSL contains information about the library in which it occurred, the method and the reason. Therefore, after receiving the error code itself, it is necessary to extract all these three parts separately and put them together in a text string. The lengths of each line, according to the OpenSSL documentation, do not exceed 120 characters, and since we use managed code, the line must be carefully extracted by reference:


    Getting a string by IntPtr
    /**<summary>Извлекает из указателя первый PChar из области памяти  длинной _iLen</summary>
    * <param name="_hPtr">Указетель на область неуправляемой памяти</param>
    * <param name="_iLen">Длина области памяти</param>
    * <returns>Итоговая строка</returns>
    * **/publicstaticstringPtrToFirstStr(IntPtr _hPtr, int _iLen = 256) {
        if(_hPtr == IntPtr.Zero) return"";
        try {
            byte[] arStr = newbyte[_iLen];
            Marshal.Copy(_hPtr, arStr, 0, arStr.Length);
            string[] arRes = Encoding.ASCII.GetString(arStr).Split(newchar[] { (char)0 }, 
                                                                   StringSplitOptions.RemoveEmptyEntries);
            if (arRes.Length > 0) return arRes[0];
              return"";
        }catch {
              return"";
        }
    }
    

    Errors when checking certificates are not in the general list, and they must be extracted by a separate method, according to the verification context:


    Getting certificate verification error
    /**<summary>Извлечение ошибки верификации сертификата</summary>
    * <param name="_hStoreCtx">Контекст хранилища</param>
    * <returns>Строка ошибки</returns>
    * **/publicstaticstringGetCertVerifyErr(IntPtr _hStoreCtx) {
        int iErr = UOpenSSLAPI.X509_STORE_CTX_get_error(_hStoreCtx);
        return PtrToFirstStr(UOpenSSLAPI.X509_verify_cert_error_string(iErr));
    }
    

    Certificate search


    As always, cryptography begins with a certificate search. We use a staff repository, so we will look for regular methods:


    Certificate search
    /**<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>
    * **/internalstaticintFindCertificateOS(string _pFindValue, out X509Certificate2 _pCert, refstring _sError, 
                                          StoreLocation _pLocation = StoreLocation.CurrentUser, 
                                          StoreName _pName = StoreName.My, 
                                          X509FindType _pFindType = X509FindType.FindByThumbprint,                                             
                                          bool _fVerify = false) {
        lock (UOpenSSLAPI.pOSSection) {
            // 0) Сздаем нативное хранилище
            _pCert = null;
            X509Store pStore = new X509Store(_pName, _pLocation);
            X509Certificate2Collection pCerts = null;
            try {
                // 1) Открытие хранилища
                pStore.Open(OpenFlags.ReadOnly);
                // 2) Поиск в хранилище (не проверяя, т.к. Verify в Linux всегда false)
                pCerts = pStore.Certificates.Find(_pFindType, _pFindValue, false);
                if (pCerts.Count == 0) return UConsts.E_NO_CERTIFICATE;
                // 3) Нет проверки возвращаем первыйif (!_fVerify) {
                    _pCert = ISDP_X509Cert.Create(pCerts[0], TCryptoPath.cpOpenSSL);
                    return UConsts.S_OK;
                }
                // 4) Проходим по сертфикатам и выбираем валидныйforeach (X509Certificate2 pCert in pCerts) {
                    ISDP_X509Cert pISDPCert = ISDP_X509Cert.Create(pCert, TCryptoPath.cpOpenSSL);
                    if (pISDPCert.ISDPVerify()) {
                        _pCert = pISDPCert;
                         return UConsts.S_OK;
                    }
                }
                return UConsts.E_NO_CERTIFICATE;
            } finally {
                if(pCerts != null) pCerts.Clear();
                pStore.Close();
            }
        }
    }
    

    Pay attention to the critical section. Mono with certificates also works through OpenSSL, but not via UOpenSSLAPI. If it is not done here, you can get memory leaks and floating incomprehensible errors under load.


    The main feature is the creation of a certificate. Unlike the version for CryptoPro, in this case from the repository we get the certificate itself (X509Certificate2), and the link in the Handle in it already points to the OpenSSL structure X509_st. It would seem that this is what is needed, but there is no pointer to EVP_PKEY (link to the private key structure in OpenSSL).

    The private key itself, as it turned out, is stored in clear text in the internal field of the certificate - impl / fallback / _cert / _rsa / rsa. This is a RSAManaged class, and a quick glance at its code (for example, the DecryptValue method ) shows how bad mono is with cryptography. Instead of honestly using OpenSSL cryptography methods, they seem to have implemented several algorithms manually. This assumption is supported by an empty search result for their project using OpenSSL methods such as CMS_final, CMS_sign or CMS_ContentInfo_new. And without them, it is difficult to imagine the formation of a standard CMS signature structure. At the same time, the work with certificates is partially conducted through OpenSSL.


    This suggests that the private key will have to be unloaded from mono and loaded into EVP_PKEY via pem. Because of this, we again need a class derived from X509Certificate, which will store all additional links.


    However, as in the case of CryptoPro, attempts to create a new certificate from Handle do not lead to success either (mono crashes with an error), and creating a certificate based on the resulting certificate leads to memory leaks. Therefore, the only option is to create a certificate based on the byte array containing pem. PEM certificate can be obtained as follows:


    PEM certification
    /**<summary>Получить файл сертификата</summary>
    * <param name="_pCert">Сертификат</param>
    * <param name="_arData">Выходные бинарные данные</param>
    * <param name="_fBase64">Формат Base64</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/publicstaticintToCerFile(this X509Certificate2 _pCert, outbyte[] _arData, 
                                refstring _sError, bool _fBase64 = true) {
        _arData = newbyte[0];
        try {
            byte[] arData = _pCert.Export(X509ContentType.Cert);
            // 0) DERif (!_fBase64) {
                _arData = arData;
                return UConsts.S_OK;
            }
            // 1) Base64using (TextWriter pWriter = new StringWriter()) {                    
                pWriter.WriteLine(UCConsts.S_PEM_BEGIN_CERT);
                pWriter.WriteLine(Convert.ToBase64String(arData, Base64FormattingOptions.InsertLineBreaks));
                pWriter.WriteLine(UCConsts.S_PEM_END_CERT);
                // 1.2) Возвращаем итог
                _arData = Encoding.UTF8.GetBytes(pWriter.ToString());
            }                
            return UConsts.S_OK;
        } catch (Exception E) {
             _sError = UCConsts.S_TO_PEM_ERR.Frm(E.Message);
             return UConsts.E_GEN_EXCEPTION;
        }            
    }
    

    The certificate is obtained without the private key and we connect it ourselves, forming a separate field for the link to ENV_PKEY:


    Generating an ENV_PKEY based on the PEM private key
    /**<summary>Формируем OpenSSL контекст закрытого ключа (EVP_PKEY) по данным закрытого ключа</summary>
    * <remarks>Преобразование через выгрузку закрытого ключа в PEM</remarks>
    * <param name="_arData">Данные закрытого ключа сертификата</param>
    * <returns>Контекст закрытого ключа (EVP_PKEY)</returns>
    * **/internalstatic IntPtr GetENV_PKEYOS(byte[] _arData) {
        IntPtr hBIOPem = IntPtr.Zero;
        try {
            // 0)  Выгружаем в BIO
            hBIOPem = UOpenSSLAPI.BIO_new_mem_buf( _arData, _arData.Length);
            if (hBIOPem == IntPtr.Zero) return IntPtr.Zero;
            IntPtr hKey = IntPtr.Zero;
            // 1)  Формируем структуру закрытого ключа
            UOpenSSLAPI.PEM_read_bio_PrivateKey(hBIOPem, ref hKey, IntPtr.Zero, 0);
            return hKey;
        } finally {
            if(hBIOPem != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOPem);
        }
    }
    

    Uploading a private key to a PEM is a much more complicated task than a PEM certificate, but it is already described here . Note that unloading the private key is an “extremely unsafe” case, and this should be avoided in every way. And since this work becomes mandatory for working with OpenSSL, on Windows it is better to use crypt32.dll methods or regular .Net classes to use this library. On Linux, you’ll have to work like that.


    It is also worth remembering that the generated links point to an unmanaged memory area and they must be freed. Since in .Net 4.5 X509Certificate2 is not Disposable, then this should be done in the destructor


    Signing


    To sign OpenSSL, you can use the simplified CMS_sign method, but it is based on the configuration file, which will be the same for all certificates, in the choice of algorithm. Therefore, relying on the code of this method is better to implement a similar signature generation:


    Data signature
    /**<summary> Подписывает информацию</summary>
    * <param name="_arData">Данные для подписания</param>
    * <param name="_pCert">Сертификат</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <param name="_arRes">Подпись сертфиикат</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintSignDataOS(byte[] _arData, X509Certificate2 _pCert, outbyte[] _arRes, refstring _sError) {
        _arRes         = newbyte[0];
        uint iFlags    = UCConsts.CMS_DETACHED;
        IntPtr hData   = IntPtr.Zero;
        IntPtr hBIORes = IntPtr.Zero;
        IntPtr hCMS    = IntPtr.Zero;
        try {
            // 0) Формируем сертфиикат
            ISDP_X509Cert pCert = ISDP_X509Cert.Convert(_pCert, TCryptoPath.cpOpenSSL);
            // 1) Формируем BIO с даннымиint iRes = GetBIOByBytesOS(_arData, out hData, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            // 2) Создаем итоговый BIO
            hBIORes = UOpenSSLAPI.BIO_new(UOpenSSLAPI.BIO_s_mem());
            // 3) Формируем объект подписи
            hCMS = UOpenSSLAPI.CMS_ContentInfo_new();
            if (hCMS == IntPtr.Zero) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_CR_ERR);
            if (!UOpenSSLAPI.CMS_SignedData_init(hCMS)) 
                return RetErrOS(ref _sError, UCConsts.S_OS_CMS_INIT_ERR);
            // 4) Добавляем подписантаif(UOpenSSLAPI.CMS_add1_signer(hCMS, pCert.hRealHandle, pCert.hOSKey,
                                           pCert.hOSDigestAlg, iFlags) == IntPtr.Zero)
                return RetErrOS(ref _sError, UCConsts.S_OS_CMS_SET_SIGNER_ERR);
            // 5) Установка флага - отцепленная подписьif (!UOpenSSLAPI.CMS_set_detached(hCMS, 1))
                return RetErrOS(ref _sError, UCConsts.S_OS_CMS_SET_DET_ERR);
            // 6) Завершение формирования подписиif (!UOpenSSLAPI.CMS_final(hCMS, hData, IntPtr.Zero, iFlags))
                return RetErrOS(ref _sError, UCConsts.S_OS_CMS_FINAL_ERR);
            // 7) Переписываем в заготовленный BIOif (!UOpenSSLAPI.i2d_CMS_bio_stream(hBIORes, hCMS, IntPtr.Zero, iFlags)) 
                 return RetErrOS(ref _sError, UCConsts.S_OS_CMS_EXP_TO_BIO_ERR);
            // 8) Считываем полученные данные из BIOreturn ReadFromBIO_OS(hBIORes, out _arRes, ref _sError);
        } catch (Exception E) {
            _sError = UCConsts.S_SIGN_OS_GEN_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if(hBIORes != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIORes);
            if(hData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hData);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
        }
    }
    

    The flow of the algorithm is as follows. First we convert the incoming certificate (if it is X509Certificate2) into our type. Since we work with links to the unmanaged memory area, then they must be closely monitored. .Net after some time after the reference to the certificate from the scope is released, it will start the destructor itself. And in it, we have precisely prescribed the methods necessary for cleaning all the unmanaged memory associated with it. Such an approach will allow us not to waste time on tracking these links directly inside the method.


    Having dealt with the certificate we form BIO with the data and the structure of the signature. Then we add the signer's data, set the signature detachment flag and start the final signature generation. The result is transferable to BIO. It remains only to extract the byte array from BIO. Converting a BIO into a set of bytes and back is often used, so it is better to put them into separate methods:


    BIO in byte [] and back
    /**<summary>Прочитать из BIO массив байт из OpenSSL</summary>
    * <param name="_hBIO">Контекст BIO</param>
    * <param name="_sError">Возвращаемая стркоа с ошибкой</param>
    * <param name="_arRes">Результат</param>
    * <param name="_iLen">Длина данных, если 0 - то все что есть</param>
    * <returns>Стандартный код с ошибкой, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintReadFromBIO_OS(IntPtr _hBIO, outbyte[] _arRes, refstring _sError, uint _iLen = 0) {
        _arRes = newbyte[0];
        IntPtr hRes = IntPtr.Zero;
        uint iLen = _iLen;
        if(iLen == 0) iLen = int.MaxValue;
        try {
            // 0) Определяем длину
            iLen = UOpenSSLAPI.BIO_read(_hBIO, IntPtr.Zero, int.MaxValue);
            // 1) Формируем буфер и читаем  него 
            hRes  = Marshal.AllocHGlobal((int)iLen);
            if (UOpenSSLAPI.BIO_read(_hBIO, hRes, iLen) != iLen) {
                _sError = UCConsts.S_OS_BIO_READ_LEN_ERR;
                return UConsts.E_CRYPTO_ERR;
            } 
            // 2) Итоговый массив
            _arRes = newbyte[iLen];
            Marshal.Copy(hRes, _arRes, 0, _arRes.Length);
            return UConsts.S_OK;;
        } catch (Exception E) {
            _sError = UCConsts.S_OS_BIO_READ_GEN_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally { 
            if(hRes != IntPtr.Zero) Marshal.FreeHGlobal(hRes);
        }
    }
    /**<summary>Получить BIO по набору байт</summary>
    * <param name="_arData">Данные</param>
    * <param name="_hBIO">Возвращаемый указатель на BIO</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintGetBIOByBytesOS(byte[] _arData, out IntPtr _hBIO, refstring _sError) {
        _hBIO = UOpenSSLAPI.BIO_new_mem_buf( _arData, _arData.Length);
        if (_hBIO == IntPtr.Zero) 
            return  RetErrOS(ref _sError, UCConsts.S_OS_CM_BIO_CR_ERR);
        return UConsts.S_OK;
    }
    

    As in the case of CryptoPro, it is necessary to extract information about the signature hashing algorithm from the certificate. But in the case of OpenSSL, it is stored directly in the certificate:


    Extract Hash Algorithm
    /**<summary>Извлечение алгоритма хэширования из контекста сертфииката OpenSSL</summary>
    * <param name="_hCert">Контекст сертификта (X509)</param>
    * <returns>Контекст аглоритма</returns>
    * **/publicstatic IntPtr GetDigestAlgOS(IntPtr _hCert) {
        x509_st pCert = (x509_st)Marshal.PtrToStructure(_hCert, typeof(x509_st));
        X509_algor_st pAlgInfo = (X509_algor_st)Marshal.PtrToStructure(pCert.sig_alg, typeof(X509_algor_st));
        IntPtr hAlgSn = UOpenSSLAPI.OBJ_nid2sn(UOpenSSLAPI.OBJ_obj2nid(pAlgInfo.algorithm));
        return UOpenSSLAPI.EVP_get_digestbyname(hAlgSn);
    }
    

    The method turned out pretty tricky, but it works. In the 1.0.2 documentation, you can find the EVP_get_digestbynid method , but the libraries of the version we use do not export it. Therefore, we first form a nid, and based on it a short name. And already by the short name, you can extract the algorithm using the standard method of searching by name.


    Signature verification


    The received signature needs verification. OpenSSL verifies the signature as follows:


    Signature verification
    /**<summary>Проверяет подпись</summary>
    * <param name="_arData">данные, которые было подписаны</param>
    * <param name="_arSign">подпись</param>
    * <param name="_pCert">сертификат</param>
    * <param name="_sError">возвращаемая строка с ошибкой</param>
    * <param name="_pLocation">Местопложение</param>
    * <param name="_fVerifyOnlySign">Проверять только подпись</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * <remarks>Проверяется только первый подписант</remarks>
    * **/internalstaticintCheckSignOS(byte[] _arData, byte[] _arSign, out X509Certificate2 _pCert, refstring _sError,
                                    bool _fVerifyOnlySign = true, 
                                    StoreLocation _pLocation = StoreLocation.CurrentUser){
        _pCert = null;
        IntPtr hBIOData = IntPtr.Zero;
        IntPtr hCMS     = IntPtr.Zero;
        IntPtr hTrStore = IntPtr.Zero;
        try {
            // 0) Формирование BIO с данными для подписиint iRes = GetBIOByBytesOS(_arData, out hBIOData, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            // 1) Чтение структуры CMS
            iRes = GetCMSFromBytesOS(_arSign, out hCMS, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            uint iFlag =  UCConsts.CMS_DETACHED;                
            // 2) Формирование доверенного хранилищаif (!_fVerifyOnlySign) {
                iRes = GetTrustStoreOS(_pLocation, out hTrStore, ref _sError);
                if (iRes != UConsts.S_OK) return iRes;
            } else 
                iFlag |= UCConsts.CMS_NO_SIGNER_CERT_VERIFY;
            // 3) Проверка подписиif (!UOpenSSLAPI.CMS_verify(hCMS, IntPtr.Zero, hTrStore, hBIOData, IntPtr.Zero, iFlag)) 
                return RetErrOS(ref _sError, UCConsts.S_OS_CM_CHECK_ERR);
            return UConsts.S_OK;                 
        } finally {
            if(hBIOData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOData);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
            if(hTrStore != IntPtr.Zero) UOpenSSLAPI.X509_STORE_free(hTrStore);
        }
    }
    

    First, the signature data is converted from the byte array into the CMS structure:


    Formation of the CMS structure
    /**<summary>Получить CMS из набора байт</summary>
    * <param name="_arData">Данные CMS</param>
    * <param name="_hCMS">Возвращаемый указатель  на структуру CMS</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintGetCMSFromBytesOS(byte[] _arData, out IntPtr _hCMS, refstring _sError) {
        _hCMS          = IntPtr.Zero; 
        IntPtr hBIOCMS = IntPtr.Zero;
        IntPtr hCMS    = IntPtr.Zero;
        try {
            // 0) Инициалиацзия структуры CMS
            hCMS = UOpenSSLAPI.CMS_ContentInfo_new();
            if (hCMS == IntPtr.Zero) return RetErrOS(ref _sError);
            if (!UOpenSSLAPI.CMS_SignedData_init(hCMS))
                return RetErrOS(ref _sError);
            // 1) Чтение данных в BIO
            hBIOCMS = UOpenSSLAPI.BIO_new_mem_buf(_arData, _arData.Length);
            if (hBIOCMS == IntPtr.Zero) return RetErrOS(ref _sError);
            // 2) Преобразование в CMSif (UOpenSSLAPI.d2i_CMS_bio(hBIOCMS, ref hCMS) == IntPtr.Zero)
                return RetErrOS(ref _sError);
            // 3) Все ок - перекрываем, чтобы не занулило
            _hCMS = hCMS;
            hCMS = IntPtr.Zero;
            return UConsts.S_OK;
        } finally {
            if(hBIOCMS != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOCMS);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
        }
    }
    

    It also loads the data of the document that was signed in BIO. If you want to check the certificate with which the signature was generated, then we form in memory a store of trusted certificates (root and intermediate) on the basis of which a chain will be formed:


    Creation of certificate storage
    /**<summary>Получить доверенное хранилище в памяти</summary>
    * <param name="_hStore">Возвращаемая ссылка на хранилище</param>
    * <param name="_pLocation">Местоположение хранилища</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
    */internalstaticintGetTrustStoreOS(StoreLocation _pLocation, out IntPtr _hStore, refstring _sError) {
        _hStore = IntPtr.Zero;
        IntPtr hStore   = IntPtr.Zero;
        try {
            List<X509Certificate2> pCerts = GetCertList(_pLocation, StoreName.Root, TCryptoPath.cpOpenSSL);
            pCerts.AddRange(GetCertList(_pLocation, StoreName.AuthRoot, TCryptoPath.cpOpenSSL));
            // 1) Формируем хранилище
            hStore = UOpenSSLAPI.X509_STORE_new();
            foreach (X509Certificate2 pCert in pCerts) {
                // Даже если ошибка идем дальше (чаще всего ошибка дубля сертификатов)
                UOpenSSLAPI.X509_STORE_add_cert(hStore, pCert.getRealHandle());
            }
            // 2) Очистка ошибок
            UOpenSSLAPI.ERR_clear_error();
            _hStore = hStore;
            hStore = IntPtr.Zero;
            return UConsts.S_OK;
        } catch (Exception E) {
            _sError = UCConsts.S_FORM_TRUST_STORE_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if (hStore != IntPtr.Zero) UOpenSSLAPI.X509_STORE_free(hStore);
    }
    

    If part of the certificates from the chain is directly in the signature, then they also need to be added to the repository (the next chapter describes how to extract them from there). Finally, the CMS_Verify method is called, which performs the check.


    If additional verification settings are necessary (for example, if certificates need to be checked without using a CRL), the necessary flags should be added to the iFlag verification parameters variable.


    Signature Information


    In systems working with cryptography, it is necessary to display information about the signature in a convenient form for customers. This has to be done in different places in different ways, so a class is needed in which this information will be stored in an easy-to-use form. In .Net there is such a class - SignedCms, however, as it was already written in the previous article about CryptoPro, it will not work and you will have to write your own analog.


    The signature contains information about the subscriber (there may be several signatories, but it happens very rarely) and a list of certificates for forming the signatory's certificate chain. Therefore, the extraction of signature data takes place in two stages: first, the general list of certificates is extracted, and then the list of signatories.


    Parsing signature data
    /**<summary>Разбор данных подписи</summary>
    * <param name="_arSign">Подпись</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <param name="_arContent">Данные для подписи</param>
    * <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalintDecodeOS(byte[] _arSign, byte[] _arContent, refstring _sError) {
        IntPtr hBIOData = IntPtr.Zero;
        IntPtr hCMS     = IntPtr.Zero;
        IntPtr hCerts   = IntPtr.Zero;
        try {
            // 0) Формируем данные и СMSint iRes = UCUtils.GetCMSFromBytesOS(_arSign, out hCMS, ref _sError);
            if(iRes != UConsts.S_OK) return iRes;
            iRes = UCUtils.GetBIOByBytesOS(_arContent, out hBIOData, ref _sError);
            if(iRes != UConsts.S_OK) return iRes;
            // 1) Устанавливаем флагиuint iFlags = UCConsts.CMS_NO_SIGNER_CERT_VERIFY;
            if(_arContent.Length == 0) iFlags |= UCConsts.CMS_NO_CONTENT_VERIFY;
            // 2) Считываем CMSif (!UOpenSSLAPI.CMS_verify(hCMS, IntPtr.Zero, IntPtr.Zero, hBIOData, IntPtr.Zero,  iFlags))
                 return UCUtils.RetErrOS(ref _sError, UCConsts.S_OS_CMS_VERIFY_ERR);
            // 3) Извлекаем сертификаты
            hCerts = UOpenSSLAPI.CMS_get0_signers(hCMS);
            int iCnt = UOpenSSLAPI.sk_num(hCerts);
            for (int i = 0; i < iCnt; i++) {
                IntPtr hCert = UOpenSSLAPI.sk_value(hCerts, i);
                byte[] arData;
                iRes = UCUtils.GetCertBytesOS(hCert, out arData, ref _sError);
                if(iRes != UConsts.S_OK) return iRes;
                fpCertificates.Add(ISDP_X509Cert.Create(arData, TCryptoPath.cpOpenSSL));
            }
            // 4) Извлекаем подписантов
            IntPtr hSigners = UOpenSSLAPI.CMS_get0_SignerInfos(hCMS);                
            iCnt = UOpenSSLAPI.sk_num(hSigners);                
            for (int i = 0; i < iCnt; i++) {
                IntPtr hSignerInfo = UOpenSSLAPI.sk_value(hSigners, i);
                // 4.1) Информация о подписанте
                ISDPSignerInfo pInfo = new ISDPSignerInfo(this);
                iRes = pInfo.DecodeOS(hSignerInfo, ref _sError);
                if(iRes != UConsts.S_OK) return iRes;
                fpSignerInfos.Add(pInfo);
            }
            return UConsts.S_OK;
        } catch (Exception E) {
            _sError = UCConsts.S_OS_CMS_DECODE.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if(hCerts != IntPtr.Zero) UOpenSSLAPI.sk_free(hCerts);
            if(hBIOData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOData);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
        }
    }
    

    As in the last chapter, we first form BIO arrays of bytes with the data for the signature (if necessary) and the CMS structure, and then start the check. This may seem strange, but information about the signature is formed in the verification method - therefore, you need to call this method before extracting the data.


    The list of certificates and signatories is issued in the form of stacks (STACK_OF (X509)), but if you extract elements using the sk_pop method, then freeing the extracted structures will be the task of the developer. If there is no desire to do this, then it is worth going in a loop and retrieving the value using the sk_value method.


    It is worth noting that when obtaining a list of certificates, you must use the CMS_get0_signers method instead of CMS_get1_certs. The first produces a list of certificates of the signatories, the second all certificates. Logically, it is more correct to use the second option, but it contains a call to the stream locking method, which as a result under load leads to memory leaks:


    CRYPTO_add(&cch->d.certificate->references, 1, CRYPTO_LOCK_X509);

    In version 1.1.0, the call is replaced with X509_up_ref, so the problem may already be fixed.
    For information about each signer, we will create a separate method:


    Parse signer information
    /**<summary>Распарсить информацию о подписанте</summary>
    * <param name="_hSignerInfo">Handler информации о подписанте (OpenSSL)</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
    * **/publicintDecodeOS(IntPtr _hSignerInfo, refstring _sError) {
        try {
            // 0) Определение сертфиката подписантаint iRes = UCUtils.GetSignerInfoCertOS(_hSignerInfo, fpSignedCMS.pCertificates, 
                                                   out fpCertificate, ref _sError);
            if(iRes != UConsts.S_OK) return iRes;
            // 1) Извлечение даты подписиuint iPos =  UOpenSSLAPI.CMS_signed_get_attr_by_NID(_hSignerInfo, UCConsts.NID_pkcs9_signingTime, 0);
            IntPtr hAttr = UOpenSSLAPI.CMS_signed_get_attr(_hSignerInfo, iPos);
            IntPtr hDateTime = UOpenSSLAPI.X509_ATTRIBUTE_get0_data(hAttr, 0, UCConsts.V_ASN1_UTCTIME, IntPtr.Zero);
            asn1_string_st pDate = (asn1_string_st)Marshal.PtrToStructure(hDateTime, typeof(asn1_string_st));
            // 2) Преобрзование в Pkcs9SigningTimebyte[] arDateAttr = newbyte[pDate.iLength];
            Marshal.Copy(pDate.hData, arDateAttr, 0, (int)pDate.iLength);
            arDateAttr = newbyte[] { (byte)UCConsts.V_ASN1_UTCTIME, (byte)pDate.iLength}.Concat(arDateAttr).ToArray();
            fpSignedAttributes.Add(new Pkcs9SigningTime(arDateAttr));
            return UConsts.S_OK;
        } catch (Exception E) {
            _sError = UCConsts.S_CMS_SIGNER_DEC_OS_ER.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        }
    }
    

    In it, we first extract the signer’s certificate, and then we get the date of signing. The signing date is one of the signature attributes in the ASN.1 format. To extract it, consider the asn1_string_st structure and, based on the data in it, form the Pkcs9SigningTime attribute.


    Obtaining a certificate is implemented separately:


    Obtaining a Signer Certificate
    /**<summary>Получить сертификата подписанта</summary>
    * <param name="_hSignerInfo">Информация об подписанте</param>
    * <param name="_pCert">Возврат сертификата</param>
    * <param name="_pCerts">Список сертификатов где искать</param>
    * <param name="_sError">Возвращаемая строкка с ошибкой</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintGetSignerInfoCertOS(IntPtr _hSignerInfo, X509Certificate2Collection _pCerts,
                                            out X509Certificate2 _pCert, refstring _sError) {
        _pCert = null;
        try {
            // 0) Получаем информацию об адресате
            IntPtr hKey    = IntPtr.Zero;
            IntPtr hIssuer = IntPtr.Zero;
            IntPtr hSNO    = IntPtr.Zero;
            if (!UOpenSSLAPI.CMS_SignerInfo_get0_signer_id(_hSignerInfo, ref hKey, ref hIssuer, ref hSNO))
                 return RetErrOS(ref _sError, UCConsts.S_GET_RECEIP_INFO_ERR);
            // 1) Извлекается серийный номерstring sSerial;
            int iRes = GetBinaryHexFromASNOS(hSNO, out sSerial, ref _sError);
            if(iRes != UConsts.S_OK) return iRes;
            X509Certificate2Collection pResCerts = _pCerts.Find(X509FindType.FindBySerialNumber, sSerial, false);
            if(pResCerts.Count == 0) return RetErrOS(ref _sError, UCConsts.S_NO_CERTIFICATE, UConsts.E_NO_CERTIFICATE);
            _pCert = pResCerts[0];
            return UConsts.S_OK;
        } catch (Exception E) {
            _sError = UCConsts.S_GET_SIGN_INFO_GEN_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        }
    }
    

    First, information about the serial number and publisher is extracted, and then the certificate is extracted from the previously received list. The serial number is an asn1_string_st structure from which binary data is extracted and converted into hex format:


    Get hex binary data from their ANS.1
    /**<summary>Получить Hex отображение бинарных данных из ASN.1</summary>
    * <param name="_hASN">Ссылка на элемент ASN.1</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <param name="_sHexData">Данные в формате Hex</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintGetBinaryHexFromASNOS(IntPtr _hASN, outstring _sHexData, refstring _sError) {
        _sHexData = "";
        try {
            asn1_string_st pSerial = (asn1_string_st)Marshal.PtrToStructure(_hASN, typeof(asn1_string_st));
            byte[] arStr = newbyte[pSerial.iLength];
            Marshal.Copy(pSerial.hData, arStr, 0, (int)pSerial.iLength);
            _sHexData = arStr.ToHex().ToUpper();
            return UConsts.S_OK;
        } catch (Exception E) {
            _sError = UCConsts.S_HEX_ASN_BINARY_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        }
    }
    

    As in the case of a CryptoPro signature, the search by serial number is usually sufficient to uniquely identify the certificate.


    Encryption


    Encryption in OpenSSL is quite logical way:


    Data encryption
    /**<summary>Зашифрованные данные</summary>
    * <param name="_arInput">Данные для расшифровки</param>
    * <param name="_pReceipients">Список сертфиикатов адресатов</param>
    * <param name="_arRes">Результат</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартный код с ошибкой, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintEncryptDataOS(byte[] _arInput, List<X509Certificate2> _pReceipients, outbyte[] _arRes, 
                                      refstring _sError) {
        _arRes           = newbyte[0];
        uint iFlags      = UCConsts.CMS_BINARY;
        IntPtr hData     = IntPtr.Zero;
        IntPtr hReceipts = IntPtr.Zero;
        IntPtr hBIORes   = IntPtr.Zero;
        IntPtr hCMS      = IntPtr.Zero;
        try {
            // 0) Сформировать BIO с данными для кодированияint iRes = GetBIOByBytesOS(_arInput, out hData, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            // 1) Формирование стека сертификатов адресатов
            iRes = GetCertsStackOS(_pReceipients, out hReceipts, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            // 2) Формирование CMS
            hCMS = UOpenSSLAPI.CMS_encrypt(hReceipts, hData, UOpenSSLAPI.EVP_des_ede3_cbc(), iFlags);
            if (hCMS == IntPtr.Zero) return RetErrOS(ref _sError, UCConsts.S_ENC_CMS_ERR);
            // 3) Запись CMS в BIO
            hBIORes = UOpenSSLAPI.BIO_new(UOpenSSLAPI.BIO_s_mem());
            if (!UOpenSSLAPI.i2d_CMS_bio_stream(hBIORes, hCMS, IntPtr.Zero, iFlags))
                 return RetErrOS(ref _sError, UCConsts.S_OS_CMS_EXP_TO_BIO_ERR);
            // 4) Преобразование из BIO в набор байтreturn ReadFromBIO_OS(hBIORes, out _arRes, ref _sError);
        } catch (Exception E) {
            _sError = UCConsts.S_ENC_OS_GEN_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if(hBIORes != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIORes);
            if(hData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hData);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
            if(hReceipts != IntPtr.Zero) UOpenSSLAPI.sk_free(hReceipts);
        }
    }
    

    At the beginning of the BIO is formed with the data and a list of certificates - recipients. It is to the address of these certificates will be encrypted. Then encryption takes place directly, and after that the data from the BIO is exported to the byte array. OpenSSL has a fairly large set of encryption algorithms, so it is unlikely to be able to uniquely select it as it was for CryptoPro. For these reasons, either the frequently used EVP_des_ede3_cbc should be used, or the choice should be shifted to the caller.


    To form a stack of certificates of recipients, it is worthwhile to single out a separate method, since the parameter with the stack of certificates is often found in other OpenSSL methods:


    Formation of a stack of certificates
    /**<summary>Получить стек сертификатов</summary>* <param name="_hStack">Возвращаемый стек</param>
    * <param name="_pCerts">Список сертификатов</param>
    * <param name="_sError">Возвращаемая строка с ошибки</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/publicstaticintGetCertsStackOS(List<X509Certificate2> _pCerts, out IntPtr _hStack, refstring _sError) {
        _hStack       = IntPtr.Zero;
        IntPtr hStack = IntPtr.Zero;
        try {
            hStack = UOpenSSLAPI.sk_new_null();
            foreach (X509Certificate2 pCert in _pCerts) {
                // 0) Формируем класс, чтобы не было утечек
                ISDP_X509Cert pLocCert = ISDP_X509Cert.Convert(pCert, TCryptoPath.cpOpenSSL);
                // 1) Добавляем
                UOpenSSLAPI.sk_push(hStack, pLocCert.hRealHandle);
            }
            _hStack = hStack;
            hStack = IntPtr.Zero;
            return UConsts.S_OK;
        } catch (Exception E) { 
            _sError = UCConsts.S_GEN_CERT_STACK_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if(hStack != IntPtr.Zero) UOpenSSLAPI.sk_free(hStack);
        }
    }
    

    Decryption


    Data decryption occurs on the machine on which the private key of one of the recipients is installed. Therefore, the decryption process occurs as follows:


    1. an encryption data structure is formed based on a data array;
    2. retrieves the list of recipients;
    3. for each addressee we are trying to find his certificate on the machine and decrypt it;
    4. if we succeed, we run a general conversion;
    5. then unload the data from the generated BIO into the byte array;

    Data decryption
    /**<summary>Дешифровывает данные</summary>
    * <param name="_arInput">Данные для расшифровки</param>
    * <param name="_arRes">Результат</param>
    * <param name="_pLocation">Местоположение хранилища, где искать</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <param name="_pCert">Сертификат</param>
    * <returns>Стандартный код ошибки, если UCOnsts.S_OK то все ок</returns>
    * **/internalstaticintDecryptDataOS(byte[] _arInput, out X509Certificate2 _pCert, outbyte[] _arRes, refstring _sError, 
                                      StoreLocation _pLocation = StoreLocation.CurrentUser ) {             
        _arRes = newbyte[0];
        _pCert = null;
        uint   iFlag    = UCConsts.CMS_BINARY;
        IntPtr hBIORes  = IntPtr.Zero;
        IntPtr hCMS     = IntPtr.Zero;
        X509Certificate2 pCert;
        try {
            // 0) Чтение структуры CMSint iRes = GetCMSFromBytesOS(_arInput, out hCMS, ref _sError);
            if (iRes != UConsts.S_OK) return iRes;
            // 1) Прохоим по списку подписантов
            IntPtr hReceipts =  UOpenSSLAPI.CMS_get0_RecipientInfos(hCMS);
            int iCnt = UOpenSSLAPI.sk_num(hReceipts);                
            for(int i = 0; i < iCnt; i++) {                 
                IntPtr hRecep    = UOpenSSLAPI.sk_value(hReceipts, i);                    
                iRes = GetRecepInfoCertOS(hRecep, _pLocation, out pCert, ref _sError);
                if (iRes != UConsts.S_OK && iRes != UConsts.E_NO_CERTIFICATE) return iRes;
                // 1.1) Нет сертфиката if (iRes == UConsts.E_NO_CERTIFICATE)  continue;
                ISDP_X509Cert pLocCert = ISDP_X509Cert.Convert(pCert);
                // 1.2) Нет зарытого ключаif (pLocCert.hOSKey == IntPtr.Zero)  continue;
                // 1.3) Установка ключаif (!UOpenSSLAPI.CMS_RecipientInfo_set0_pkey(hRecep, pLocCert.hOSKey))
                     return RetErrOS(ref _sError, UCConsts.S_OS_CMS_SET_DEC_KEY_ERR);
                try {
                    // 1.4) Декодированиеif (!UOpenSSLAPI.CMS_RecipientInfo_decrypt(hCMS, hRecep))
                        return RetErrOS(ref _sError, UCConsts.S_OS_CMS_REC_DEC_ERR);
                } finally {
                    // !! Иначе два освобождения и ошибка
                    UOpenSSLAPI.CMS_RecipientInfo_set0_pkey(hRecep, IntPtr.Zero);
                }
                // 1.5) Общее декодирование
                hBIORes = UOpenSSLAPI.BIO_new(UOpenSSLAPI.BIO_s_mem());
                if (!UOpenSSLAPI.CMS_decrypt(hCMS, IntPtr.Zero, pLocCert.hRealHandle, IntPtr.Zero, hBIORes, iFlag))
                    return RetErrOS(ref _sError, UCConsts.S_OS_CMS_FULL_DEC_ERR);
                _pCert = pLocCert;
                // 2) Считываем полученные данные из BIO                    return ReadFromBIO_OS(hBIORes, out _arRes, ref _sError);
            }
            _sError = UCConsts.S_DEC_NO_CERT_ERR;
            return UConsts.E_NO_CERTIFICATE;
        } catch (Exception E) {
            _sError = UCConsts.S_DEC_GEN_ERR.Frm(E.Message);
            return UConsts.E_GEN_EXCEPTION;
        } finally {
            if(hBIORes != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIORes);
            if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS);
        }
    }
    

    An important feature is the need to keep track of the private key. The fact is that if the link to it is fixed in the addressee by the CMS_RecipientInfo_set0_pkey method, then it will be released along with the CMS structure, and if you try to release it along with the certificate, the system will fall with an error.


    In order to decrypt the data, the recipient information must be converted into a certificate. As with the signer, the serial number and the issuer of the certificate are stored in the data. For simplicity, you can limit the serial number to search:


    Obtaining the certificate of the addressee
    /**<summary>Получение сертификата адресата</summary>
    * <param name="_hRecep">Информация об адресате</param>
    * <param name="_pCert">Возврат сертификата</param>
    * <param name="_pLocation">Расположения хранилища для поиска</param>
    * <param name="_sError">Возвращаемая строкка с ошибкой</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintGetRecepInfoCertOS(IntPtr _hRecep, StoreLocation _pLocation,
                                           out X509Certificate2 _pCert, refstring _sError) {
        _pCert = null;
        try {
             // 0) Получаем информацию об адресате
             IntPtr hKey    = IntPtr.Zero;
             IntPtr hIssuer = IntPtr.Zero;
             IntPtr hSNO    = IntPtr.Zero;
             if (!UOpenSSLAPI.CMS_RecipientInfo_ktri_get0_signer_id(_hRecep, ref hKey,
                                                                    ref hIssuer, ref hSNO))
                 return RetErrOS(ref _sError, UCConsts.S_GET_RECEIP_INFO_ERR);
             // 1) Извлекается серийный номерstring sSerial;
             int iRes = GetBinaryHexFromASNOS(hSNO, out sSerial, ref _sError);
             if(iRes != UConsts.S_OK) return iRes;
             // 2) Ищем сертификат
             iRes = FindCertificateOS(sSerial, out _pCert, ref _sError, _pLocation, 
                                      StoreName.My, X509FindType.FindBySerialNumber);
             if(iRes != UConsts.S_OK) return iRes;
             return UConsts.S_OK;
        } catch (Exception E) {
             _sError = UCConsts.S_GET_RECEIP_INFO_GEN_ERR.Frm(E.Message);
             return UConsts.E_GEN_EXCEPTION;
        }
    }
    

    The CMS_RecipientInfo_ktri_get0_signer_id method retrieves information about the addressee’s certificate, and the serial number itself is extracted from the structure that the hSNO variable refers to by the method already described earlier. It remains to find the certificate by serial number.


    The structure of the data of the addressee, generally speaking, may be different . But when encrypting documents, the most common type is ktri - with the storage of information about public keys. OpenSSL also implemented several methods for working with other types: CMS_RecipientInfo_kari_ *, CMS_RecipientInfo_kekri_ * and CMS_RecipientInfo_set0_password for pwri.


    Certificate Verification


    A certificate is not only a public key, but also information about a signer certified by the publisher of this certificate. This information is relevant only for a limited time and the publisher may withdraw its signature from it. All this makes us attentive to the validity of the certificate, since it directly determines the validity of the actions performed with its help. In OpenSSL, certificate verification is pretty well automated. In essence, we only need to form a certificate storage (in memory or on disk), through which the chaining will take place, specify the date of verification and a set of flags.

    The certificate, for good, needs to be checked completely, so the check flags will most often be installed the same:


    Certificate Verification
    /**<summary>Проверить сертификат в рамках OpenSSL</summary>
    * <param name="_iRevFlag">Флаг отзыва</param>
    * <param name="_iRevMode">Режим отзыва</param>
    * <param name="_hCert">контекст сертфиката</param>
    * <param name="_rOnDate">Дата верификацмм</param>
    * <param name="_pLocation">Местоположение проверки</param>
    * <param name="_sError">Возвращаемая строка с ошибкой</param>
    * <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
    * **/internalstaticintVerifyCertificateOS(IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag,
                                            StoreLocation _pLocation, DateTime _rOnDate, refstring _sError) {
        IntPtr hStore   = IntPtr.Zero;
        IntPtr hStoreCtx = IntPtr.Zero;
        try {                                            
            // 0) Формируем хранилищеint iRes = GetTrustStoreOS(_pLocation, out hStore, ref _sError);
            if(iRes != UConsts.S_OK) return iRes;
            // 1) Формируем контекст проверки
            hStoreCtx = UOpenSSLAPI.X509_STORE_CTX_new();
            if (!UOpenSSLAPI.X509_STORE_CTX_init(hStoreCtx, hStore, _hCert, IntPtr.Zero)) {
                _sError = UCConsts.S_CRYPTO_CONTEXT_CER_ERR;
                return UConsts.E_CRYPTO_ERR;
            }
            // 2) Устанавливаем дату проверки и доп флаги
            SetStoreCtxCheckDate(hStoreCtx, _rOnDate);
            // 3) Проверкаif (!UOpenSSLAPI.X509_verify_cert(hStoreCtx)) {
                _sError = UCConsts.S_CRYPTO_CHAIN_CHECK_ERR.Frm(GetCertVerifyErr(hStoreCtx));
                return UConsts.E_CRYPTO_ERR;
            }
            return UConsts.S_OK;
        } finally {
            if (hStore != IntPtr.Zero) UOpenSSLAPI.X509_STORE_free(hStore);
            if (hStoreCtx != IntPtr.Zero) UOpenSSLAPI.X509_STORE_CTX_free(hStoreCtx);
        }
    }
    

    The generated verification context (X509_STORE_CTX) must contain the date and flags. It is more convenient to do this in a separate method:


    Set date and check flags
    /**<summary>Установить дату проверки сертификата</summary>
    * <param name="_hStoreCtx">Контекст хранилища</param>
    * <param name="_rDate">Дата</param>
    * **/publicstaticvoidSetStoreCtxCheckDate(IntPtr _hStoreCtx, DateTime _rDate) {
        uint iFlags = UCConsts.X509_V_FLAG_USE_CHECK_TIME | UCConsts.X509_V_FLAG_X509_STRICT | 
                      UCConsts.X509_V_FLAG_CRL_CHECK_ALL;
        // Установка флагов
        UOpenSSLAPI.X509_STORE_CTX_set_flags(_hStoreCtx, iFlags);
        // Установка времени
        UOpenSSLAPI.X509_STORE_CTX_set_time(_hStoreCtx, iFlags, (uint)_rDate.ToUnix());
        // Установка обязательств - все верифифированны
        UOpenSSLAPI.X509_STORE_CTX_set_trust(_hStoreCtx, UCConsts.X509_TRUST_TRUSTED);
    }
    

    As already described earlier, certificate verification errors fall into a separate list and are extracted by a separate method.


    Conclusion


    This embodiment has been tested, according to the algorithm described in the previous article. After eliminating the previously described leak in X509Certificate2 (mono), it works stably without memory leaks. The speed is comparable with the work of the CryptoPro library.


    In general, for Windows, due to the peculiarities of storing and working with private keys, this library is not very applicable. Therefore, it is more likely to use regular cryptography procedures. In Linux, if you close your eyes to the need to unload private keys, for non-GOST cryptography it is quite a working option.


    At the same time, CryptoPro has already released CSP 5.0 , which promised support for RSA certificates. Judging from the information on their website, while it is not yet certified, but when it does, the system will use both the GOST keys and RSA at the same time, it is probably more logical to build on it.


    Links


    1. OpenSSL 1.0.2 ManPages ;
    2. Multithreading in OpenSSL 1 and 2 ;
    3. OpenSSL sources :
      1. cms_smime.c;
    4. OpenSSL Wiki ;
    5. Mono sources:
      1. class RSAManaged ;


    Also popular now: