Back to Home

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 set before us ...

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

Read Next