Connecting CryptoPro to Mono

In connection with the transition to Linux, it became necessary to transfer one of our server systems written in C # to Mono. The system works with enhanced EDS, so one of the tasks assigned to us was to check the performance of GOST certificates from CryptoPro in mono. CryptoPro itself has already implemented CSP for Linux for quite some time , but the very first attempt to use showed that Mono's native cryptography classes (similar to those in the base .Net - X509Store, X509Certificate2, etc.) do not only work with GOST keys, they even do not see them in their vaults. Because of this, work with cryptography had to be connected directly through the CryptoPro libraries.



Certificate installation


Before you implement the code, you must install the certificate and make sure that it works normally.


Certificate installation

Компонент КриптоПро CSP версии 3.9 был установлен в Centos 7 в папку /opt/cprocsp. Для того, чтобы не было конфликтов между утилитами mono и КриптоПро, имеющих одинаковые названия (например, certmgr), в переменные окружения не стали вносить путь до папки и все утилиты вызывались по полному пути.


Для начала определяем список считывателей:
/opt/cprocsp/bin/amd64/csptest -enum -info -type PP_ENUMREADERS | iconv -f cp1251


Если среди списка нет считывателя с папки на диске (HDIMAGE) ставим его:
/opt/cprocsp/sbin/amd64/cpconfig -hardware reader -add HDIMAGE store


После чего можно создавать контейнеры вида '\\.\HDIMAGE\{имя контейнера}' путем либо создания нового контейнера с ключами:
/opt/cprocsp/bin/amd64/csptest -keyset -provtype 75 -newkeyset -cont '\\.\HDIMAGE\test'


либо формируя папку /var/opt/cprocsp/keys/root/{имя контейнера}.000, в которой располагается стандартный набор файлов контейнера КриптоПро (*.key, *.mask, и проч.).


После этого сертификат из контейнера можно установить в хранилище сертификатов:
/opt/cprocsp/bin/amd64/certmgr -inst mMy -cont '\\.\HDIMAGE\{имя контейнера}'


Установленный сертификат можно увидеть с помощью следующей команды:
/opt/cprocsp/bin/amd64/certmgr -list mMy


Работу сертификата можно проверить следующим образом:
/opt/cprocsp/bin/amd64/cryptcp – sign -norev -thumbprint {отпечаток} {файл} {файл подписи}
/opt/cprocsp/bin/amd64/cryptcp – verify -norev {файл подписи}


Если с сертификатом все нормально, то можно переходить к подключению в коде.



Connection in code


Despite the process of porting to Linux, the system had to continue to function in the Windows environment, therefore, externally, work with cryptography had to be carried out through general methods of the form “byte [] SignData (byte [] _arData, X509Certificate2 _pCert)”, which should work the same way in Linux and in Windows.


The analysis of cryptography library methods was successful, since CryptoPro implemented the library “libcapi20.so”, which completely mimics the standard Windows encryption libraries - “crypt32.dll” and “advapi32.dll”. Perhaps, of course, not entirely, but all the necessary methods for working with cryptography are available there, and almost everyone works.


Therefore, we form two static classes “WCryptoAPI” and “LCryptoAPI” each of which will import the necessary set of methods as follows:


[DllImport(LIBCAPI20, SetLastError = true)]
internalstaticexternboolCertCloseStore(IntPtr _hCertStore, uint _iFlags);

The connection syntax for each of the methods can either be formed independently, or you can use the pinvoke website , or copy it from the .Net sources (the CAPISafe class ). From the same module, one can draw constants and structures related to cryptography, the presence of which always makes life easier when working with external libraries.


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


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

Thus, using the methods of the UCryptoAPI class, you can implement almost a single code for both systems.


Certificate search


Working with cryptography usually begins with a certificate search, for this crypt32.dll has two methods CertOpenStore (opens the specified certificate store) and simple CertOpenSystemStore (opens the user's personal certificates). Due to the fact that working with certificates is not limited to personal user certificates, we connect the first one:


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>
* **/publicstaticintFindCertificateCP(string _pFindValue, out X509Certificate2 _pCert, refstring _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);                
    }
}


The search takes place in several stages:
  1. opening the repository;
  2. formation of the data structure for which we are looking for;
  3. certificate search;
  4. if required, certificate verification (described in a separate section);
  5. closing the repository and freeing the structure from point 2 (since everywhere there is work with unmanaged memory. Net will not do anything for us to clean up);

There are a few subtle points in the search for certificates.


CryptoPro on Linux works with ANSI strings, and on Windows with UTF8, therefore:


  1. when connecting the storage discovery method in Linux, the storage code parameter must explicitly specify the marshaling type [In, MarshalAs (UnmanagedType.LPStr)];
  2. passing a string to search for (for example, by the name of Subject) must be converted to a set of bytes in various encodings;
  3. for all encryption constants that have a variation on the type of string (for example, CERT_FIND_SUBJECT_STR_A and CERT_FIND_SUBJECT_STR_W) in Windows you must select * _W, and in Linux * _A;

The MapX509StoreFlags method can be taken directly from Microsoft sources without changes, it simply generates a final mask based on the .Net flags.


The value of the search depends on the type of search (refer to MSDN for CertFindCertificateInStore ), in the example there are two most frequently used options - for the string format (names Subject, Issuer, etc.) and binary (fingerprint, serial number).


The process of creating a certificate from IntPtr on Windows and on Linux is very different. Windows will create a certificate in a simple way:

new X509Certificate2(hCert);


in Linux, you have to create a certificate in two steps:

X509Certificate2(new X509Certificate(hCert));


In the future, we will need access to hCert to work, and it would be necessary to save it in the certificate object. In Windows, you can later get it out of the Handle property, but Linux converts the CERT_CONTEXT structure, which is referenced by hCert, into a reference to the x509_st structure (OpenSSL), and it is this that is written into the Handle. Therefore, it is worth creating an inheritor from X509Certificate2 (ISDP_X509Cert in the example), which will save in a separate field hCert in both systems.


Do not forget that this is a link to the area of ​​unmanaged memory and it should be released after the end of the work. Because in .Net 4.5 X509Certificate2 is not Disposable - cleaning with the CertFreeCertificateContext method should be done in the destructor.


Signature generation


When working with GOST certificates, almost always used are detached signatures with one signer. In order to create such a signature, a fairly simple block of code is required:


Signature generation
/**<summary> Подписывает информацию</summary>
* <param name="_arData">Данные для подписания</param>
* <param name="_pCert">Сертификат</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <param name="_arRes">Подпись сертфииката</param>
* <returns>Стандартый код ошибки, если UConsts.S_OK то все ок</returns>
* **/publicstaticintSignDataCP(byte[] _arData, X509Certificate2 _pCert, outbyte[] _arRes, refstring _sError)
{
    _arRes = newbyte[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 = newbyte[iLen];
        // 1) Формирование подписиif (!UCryptoAPI.CryptSignMessage(ref pParams, true, 1, new IntPtr[1] { pGlobData },
                                         newuint[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);
    }
}

During the work of the method, a structure with parameters is formed and the signing method is called. The structure of the parameters can allow to save certificates for the formation of a complete chain (fields cMsgCert and rgpMsgCert, the first one stores the number of certificates, the second list of references to the structures of these certificates).


The method of signing can receive one or several documents for simultaneous signing by one signature. This, by the way, does not contradict 63 FZ and is very convenient, since the user is unlikely to be pleased to have to press the “sign” button several times.


The main oddity of this method is that it does not work in the two-call mode, typical of most library methods working with large memory blocks (the first with null gives the required length of the buffer, the second fills the buffer). Therefore, it is necessary to create a large buffer, and then shorten it along the actual length.


The only serious problem is finding the OID of the hashing algorithm (Digest) used when signing - it is not explicitly in the certificate (there is only the signature algorithm itself). And if in Windows you can specify it with an empty string - it will pick up automatically, but Linux will refuse to sign if the algorithm is wrong.


But there is a trick - in the information about the signature algorithm (structure CRYPT_OID_INFO) the signature OID is stored in pszOID, and the identifier of the hashing algorithm is stored in Algid. And to convert Algid to OID is already a matter of technology:


Getting the OID hashing algorithm
/**<summary>Получение OID алгоритма хэширования сертификату</summary>
* <param name="_hCertHandle">Хэндл сертификата</param>
* <param name="_sOID">Возвращаемый параметр OID</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
* **/internalstaticintGetHashAlgoritmOID(IntPtr _hCertHandle, outstring _sOID, refstring _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);
    }
}

After carefully reading the code, you will be surprised that the algorithm identifier is obtained in a simple way (CertOIDToAlgId) and the Oid by it is complicated (CryptFindOIDInfo). It would be logical to assume the use of either both complex or both simple methods, and in Linux both options work successfully. However, in Windows, the complex option of obtaining an identifier and simply obtaining an OID is unstable, so this strange hybrid will be a stable solution.


Signature verification


The signature verification takes place in two stages, at the beginning the signature itself is verified, and then the certificate by which it was formed (chain, date of signature, etc.) is verified.
As with signing, you must specify a set of signed data, signature parameters and the signature itself:


Signature verification
/**<summary>Формирует стандартную сктруктуру для проверки подписи </summary>
* <returns>Структуру</returns>
* **/internalstatic 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>
* **/publicstaticintCheckSignCP(byte[] _arData, byte[] _pSign, out X509Certificate2 _pCert, refstring _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 },          // подписанные файлыnewint[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();                
    }
}

For convenience, the process of forming a structure with parameters is moved to a separate method (GetStdSignVerifyPar). After that, the signature itself is checked and the first signatory is retrieved (for good it would be necessary to extract everyone, but a signature containing several signatories is still exotic).


After extracting the certificate, the signer will convert it to our class and check it (if specified in the method parameters). For verification, the date of signing of the first signatory is used (see the section on extracting information from the signature, and the section on checking the certificate).


Extracting signature information


Often in systems working with cryptography, a printed signature is required. In each case, it is different, so it is better to form a class of information about the signature, which will contain information in an easy-to-use form and already provide a printed representation with it. In .Net there is such a class - SignedCms, however, its analogue in mono with the signatures of CrytoPro, firstly refuses to work, secondly it contains the sealed modifier and in the third almost all of its properties are closed for writing, so you will have to create your own analogue.


The signature itself contains two main elements - a list of certificates and a list of signatories. The list of certificates can be empty, and it can contain all certificates for verification, including complete chains. The list of signatories indicates the number of real signatures. The connection between them is carried out by serial number and publisher (Issuer). Theoretically, in one signature there can be two certificates from different publishers with the same serial number, but in practice this can be neglected and searched only by serial number.


The signature is read as follows:


Extracting information from the signature
/**<summary>Расшифровать</summary>
* <param name="_arSign">Подпись</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
* **/publicintDecode(byte[] _arSign, refstring _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);
    }
}

The signature analysis takes place in several stages, first the message structure is formed (CryptMsgOpenToDecode), then the actual signature data is entered into it (CryptMsgUpdate). It remains to verify that this is a real signature and first obtain a list of certificates, and then a list of signatories. The list of certificates is retrieved sequentially:


Getting a list of certificates
/**<summary>Получить коллекцию сертификатов по подписи </summary>
* <param name="_hMsg">Handle подписи</param>
* <returns>Коллекция сертификатов</returns>
* **/internalstatic X509Certificate2Collection GetSignCertificates(IntPtr _hMsg) {
    X509Certificate2Collection certificates = new X509Certificate2Collection();
    uint iCnt = GetCryptMsgParam<uint>(_hMsg, UCConsts.CMSG_CERT_COUNT_PARAM);
    for (int i = 0; i < iCnt; i++) {
        IntPtr hInfo = IntPtr.Zero;
        IntPtr hCert = IntPtr.Zero;
        try {
            uint iLen = 0;
            if (!GetCryptMsgParam(_hMsg, UCConsts.CMSG_CERT_PARAM, out hInfo, out iLen)) continue;
            hCert = UCryptoAPI.CertCreateCertificateContext(UCConsts.PKCS_7_OR_X509_ASN_ENCODING, hInfo, iLen);
            if (hCert != IntPtr.Zero) {
                certificates.Add(new ISDP_X509Cert(hCert));
                hCert = IntPtr.Zero;
            }
        } finally {
            if (hInfo != IntPtr.Zero) Marshal.FreeHGlobal(hInfo);
            if (hInfo != IntPtr.Zero) Marshal.FreeHGlobal(hCert);
        }
    }
    return certificates;
}

First, the number of certificates from the CMSG_CERT_COUNT_PARAM parameter is determined, and then information about each certificate is sequentially extracted. Completes the process of creating the formation of the context of the certificate and on the basis of the certificate itself.


Retrieving signatory data is more difficult. They contain an indication of the certificate and a list of signature parameters (for example, the date of signing). The process of extracting data is as follows:


Retrieving Signer Information
/**<summary>Распарсить информацию из подписи</summary>
* <param name="_hMsg">Handler подписи</param>
* <param name="_iIndex">Индекс подписанта</param>
* <param name="_pSignedCms">Структура подписи</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
* **/publicintDecode(IntPtr _hMsg, int _iIndex, ISDPSignedCms _pSignedCms, refstring _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 = newbyte[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);
    }
}

During it, the size of the subscriber's structure is first determined, and then the CMSG_SIGNER_INFO structure is also retrieved. It is easy to find the serial number of the certificate in it and to find the necessary certificate in it in the previously extracted list. Please note that the serial number is in reverse order.


After extracting the certificate, it is necessary to determine the signature parameters, the most important of which is the date of signing (even if it’s not verified by the date stamp server, it is very important to display).


Signature Attribute List
/**<summary>Получить список атрибутов подписи</summary>
* <param name="_pAttrs">Структура атрибутов</param>
* <returns>Коллекция аттрибутов</returns>
* **/internalstatic CryptographicAttributeObjectCollection ReadCryptoAttrsCollection(CRYPT_ATTRIBUTES _pAttrs) {
    CryptographicAttributeObjectCollection pRes = new CryptographicAttributeObjectCollection();            
    for (int i = 0; i < _pAttrs.cAttr; i++) {
        IntPtr hAttr = new IntPtr((long)_pAttrs.rgAttr + (i * Marshal.SizeOf(typeof(CRYPT_ATTRIBUTE))));
        CRYPT_ATTRIBUTE pAttr = (CRYPT_ATTRIBUTE) Marshal.PtrToStructure(hAttr, typeof(CRYPT_ATTRIBUTE));
        CryptographicAttributeObject pAttrInfo =  new CryptographicAttributeObject(new Oid(pAttr.pszObjId), 
                                                                                   GetAsnEncodedDataCollection(pAttr));
        pRes.Add(pAttrInfo);
    }
    return pRes;
}

Attributes are a nested reference of the form Oid - a list of values ​​(in fact, it is a disassembled ASN.1 structure). Having passed on the first level we form the nested list:


Parse signature attribute
/**<summary>Сформировать объект коллекции нужного класса по имени</summary>
* <param name="_sName">Имя</param>
* <returns>Созданный объект</returns>
* **/internalstatic Pkcs9AttributeObject Pkcs9AttributeFromOID(string _sName) {
    switch (_sName) {
        case UCConsts.S_SIGN_DATE_OID    : returnnew Pkcs9SigningTime();           
//        case UConsts.S_CONTENT_TYPE_OID : return new Pkcs9ContentType();      ->> в Mono падает                          //        case UConsts.S_MESS_DIGEST_OID  : return new Pkcs9MessageDigest();default:  returnnew Pkcs9AttributeObject();
    }        
} 
/**<summary>Формирует коллекуцию ASN</summary>
* <param name="_pAttr">Структура</param>
* <returns>Коллекция</returns>
* **/internalstatic AsnEncodedDataCollection GetAsnEncodedDataCollection (CRYPT_ATTRIBUTE _pAttr) {
    AsnEncodedDataCollection pRes = new AsnEncodedDataCollection();
    Oid pOid = new Oid(_pAttr.pszObjId);
    string sOid = pOid.Value;
    for (uint i = 0; i < _pAttr.cValue; i++) {
        checked {
            IntPtr pAttributeBlob = new IntPtr((long)_pAttr.rgValue + (i * Marshal.SizeOf(typeof(CRYPTOAPI_BLOB))));
            Pkcs9AttributeObject attribute = new Pkcs9AttributeObject(pOid, BlobToByteArray(pAttributeBlob));
            Pkcs9AttributeObject customAttribute = Pkcs9AttributeFromOID(sOid);
            if (customAttribute != null) {
                customAttribute.CopyFrom(attribute);
                attribute = customAttribute;
            }
            pRes.Add(attribute);
        }
    }
    return pRes;
}

A key feature of this process is the correct selection of the heir to Pkcs9AttributeObject. The problem is that the standard way of creating in mono does not work and you have to form the choice of a class right in the code. In addition, of the main types of Mono at the moment allows you to generate only the date.


By wrapping the above methods in two classes - information about the signature and information about the signer - we get an analogue of SignedCms, from which we extract data when forming the printed form.


Encryption


The encryption process is in many ways similar to the signing process, it is quite simple, and the main problem is also in the definition of the algorithm. Unlike a signature, encryption is most often used concatenated to the address of one or several recipients (for example, they also encrypt themselves to the address so that they can read the message with their own key).


Encrypt data
/**<summary>Зашифрованные данные</summary>
* <param name="_arInput">Данные для расшифровки</param>
* <param name="_pCert">Сертификат</param>
* <param name="_arRes">Результат</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартный код с ошибкой, если UConsts.S_OK то все ок</returns>
* **/publicstaticintEncryptDataCP(byte[] _arInput, X509Certificate2 _pCert, outbyte[] _arRes, refstring _sError) {
    _arRes = newbyte[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 = newbyte[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;
    }
}

The encryption process takes place in three stages - filling in the parameters, determining the length, and finally encryption. Encrypted data can be large, which is probably why the method supports two-call mode.


In the example, it is encrypted to the address of one addressee, but by adding additional certificates to the array and setting the total number to the method parameters, you can increase the number of recipients.


But the problem again with the algorithm. The certificate has neither it, nor even indirect values ​​by which it could be determined (as it was possible with the signature algorithm). Therefore, it is necessary to extract the list of supported algorithms from the provider:


Getting the encryption algorithm
/**<summary>Получение OID алгоритма шифрования сертификату</summary>
* <param name="_hCertHandle">Хэндл сертификата</param>
* <param name="_sOID">Возвращаемый параметр OID</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <returns>Стандартный код ошибки, если UConsts.S_OK то все ок</returns>
* **/internalstaticintGetEncodeAlgoritmOID(IntPtr _hCertHandle, outstring _sOID, refstring _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 = newbyte[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);
    }
}

In the example, the context of the private key is retrieved and it is searched by algorithms. But this list contains all the algorithms (key exchange, hashing, signature, encryption, etc.), so only the encryption algorithms need to be filtered. We are trying to extract information for everyone by limiting it to a group of encryption algorithms (UCConsts.CRYPT_ENCRYPT_ALG_OID_GROUP_ID). And if the information is found, then this is our algorithm.


If there are more than one such algorithms, you can also filter by size (based on the size of the hashing algorithm).


Decryption


In order to decrypt data, the local machine must have one of the recipients in the personal certificates of the user or computer. And the private key must be tied to it. The process follows the already familiar scenario — the list of parameters, the determination of length, and the decryption process itself:


Data decryption
/**<summary>Дешифровывает данные</summary>
* <param name="_arInput">Данные для расшифровки</param>
* <param name="_arRes">Результат</param>
* <param name="_sError">Возвращаемая строка с ошибкой</param>
* <param name="_pCert">Сертификат</param>
* <returns>Стандартный код ошибки, если UCOnsts.S_OK то все ок</returns>
* **/publicstaticintDecryptDataCP(byte[] _arInput, out X509Certificate2 _pCert, outbyte[] _arRes, refstring _sError) {             
    _arRes = newbyte[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 = newbyte[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);
    }
}

When setting the parameters, the storage is indicated, from which the system will try to extract the appropriate certificate with the key. As a result of the work, the system will issue the decrypted data and the certificate that was used (in Linux, the certificate is always returned empty).


Certificate Verification


A certificate is not only a public key, but also a set of different information about its owner, about who issued it and about the set of actions that can be done with its help. Also, the certificate has a period of validity and the possibility of revocation, in case of compromise. Most often, the certificate verification implies the following:


  1. chain integrity (certificate of publisher, certificate of publisher of certificate of publisher, etc.);
  2. Root Publisher Certificate - Must be in the Trusted Root Centers Store;
  3. the period of validity of all certificates - the moment of use of the certificate must be within the boundaries of this period;
  4. Each of the certificates in the chain, except for the root one, must be absent from the list of withdrawals from its publisher (CRL);

For good, you still have to check the signature rights, but in real life it is rarely done.


As is already clear from the introduction, checking the certificate for validity is one of the most difficult tasks. That is why the library has a lot of methods for implementing each of the items separately. Therefore, for simplicity, let's turn to the .Net sources for the X509Certificate2.Verify () method and take them as a basis.


The check consists of two stages:
  1. form a chain of certificates up to the root;
  2. check each of the certificates in it (for review, time, and so on.);

Such verification should be carried out before signing and encryption on the current date, and at the time of verification of the signature on the date of signing. The verification method itself is small:


Certificate Verification
/**<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>
* **/internalstaticintVerifyCertificate (IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag, 
                                       DateTime _rOnDate, TimeSpan _iCTLTimeout, IntPtr _hPolicy, refstring _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);
    }
}

First, a chain is formed using the BuildChain method, and then it is checked. During the formation of the chain, the structure of the parameters, the date of verification and the check flags are formed:


Certificate chaining
/**<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>
* **/internalstaticintBuildChain (IntPtr _hChainEngine, IntPtr _hCert, X509RevocationMode _iRevMode, 
                                X509RevocationFlag _iRevFlag, DateTime _rOnDate, TimeSpan _rCTLTimeOut, 
                                ref IntPtr _hChain, refstring _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;
}

This is a greatly simplified version of chaining compared to how Microsoft forms it. The hCertPolicy and hAppPolicy structures can be filled with OIDs, which display the rights to actions that are required in the certificate being checked. But in the example, we will assume that we do not check them.


You can also add additional certificate storage (for example, extracted from the signature) to the chain building parameters.


The MapRevocationFlags method can be taken directly from .Net sources without changes — it simply forms a uint based on a set of transmitted flags.


Conclusion


A set of implemented methods for working with cryptography was subjected to stress testing according to the complete work cycle scheme:


  1. waiting 10 ms;
  2. certificate retrieval;
  3. signing byte [] {1, 2, 3, 4, 5};
  4. verification of the received signature;
  5. extract signature parameters;
  6. encryption byte [] {1, 2, 3, 4, 5};
  7. decrypt received data;

This cycle was launched on Windows and Linux in the 1st, 10th, and 50th threads in order to test the operation of Linux in several threads at once. An application in Linux worked stably for some time in a multi-thread mode (and the more threads, the less time), and then it “got up” tightly. That indicates the presence of mutual blocking (deadlock) in the library (in case of violation of work with streams associated with shared access, it usually drops with Access Violation).


For this reason, to ensure stability, all methods of the UCryptoAPI class should be framed with a critical section. To do this, we add an object type fpCPSection field, after which we add the following construction to each call:


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

This slows down the work, so those who are interested can only wrap the Linux section with a critical section.


Load testing also showed memory leaks in mono when accessing the Issuer and Subject fields of the certificate. The leak is likely to occur when mono tries to generate X500DistinguishedName classes for the signer and publisher. Fortunately, mono considered this process quite resource-intensive (or they know about the leak), therefore, provided for caching the result of this formation in the internal fields of the certificate (impl.issuerName and impl.subjectName). Therefore, this leakage is treated by direct recording through reflection (Reflection) into these fields instances of class X500DistinguishedName, formed on the basis of the values ​​from the certificate's CERT_CONTEXT structure.


Links


  1. CryptoPro CAPILite documentation
  2. resource with the declaration of standard exported functions in C #
  3. source .Net:
    1. class CAPIBase
    2. class X509Certificate2
    3. SignedCMS class
    4. SignerInfo class

  4. mono sources:
    1. class X509Certificate2
    2. class X509CertificateImplBtls


Also popular now: