In the previous article, the process of integrating CryptoPro standards with mono was described. In the same detail we will focus on connecting RSA certificates.
We continued to migrate one of our server systems written in C # to Linux, and the turn came to the RSA part. If last time the difficulties in connecting were easily explained by the interaction of two, initially unrelated to each other systems, then when you connected "normal" RSA certificates from mono, obviously no one expected a trick.
Installing the certificate and key did not cause any problems, and the system even saw it in the regular storage. However, neither sign, nor encrypt, or pull the data from the previously formed signature was no longer possible - mono steadily fell with an error. It was necessary, as in the case of CryptoPro, to connect directly to the encryption library. For RSA certificates in Linux, the main candidate for such a connection is OpenSSL.
Fortunately Centos 7 has a built-in version of OpenSSL - 1.0.2k. In order not to introduce additional difficulties in the work of the system, we decided to connect to this version. OpenSSL allows you to create special file certificate stores, however:
certmgr -importKey -c -p {password} My {pfx file}
certmgr -add -c My {cer file}
certmgr -list -c -v My
In the same way as last time, the system, despite the transfer to Linux, should continue to function in the Windows environment. Therefore, externally, work with cryptography should be carried out through general methods of the form “byte [] SignData (byte [] _arData, X509Certificate2 _pCert)”, which should have worked equally in Linux and in Windows.
Ideally, there should be methods that would work as in Windows - regardless of the type of certificate (on Linux via OpenSSL or CryptoPro, depending on the certificate, and on Windows - via crypt32).
The analysis of the OpenSSL libraries showed that the main library in Windows is “libeay32.dll”, and in Linux “libcrypto.so.10”. In the same way as last time we form two classes WOpenSSLAPI and LOpenSSLAPI, which contain the list of plug-in library methods:
[DllImport(CTRYPTLIB, CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.Cdecl)] internal static extern void OPENSSL_init();
Pay attention to the calling convention, unlike CryptoPro - here you must explicitly specify it. The syntax for connecting each of the methods this time will have to be formed independently based on the * .h source files of OpenSSL.
The basic rules for forming the syntax of a call in C # are based on the data from the .h files:
In the declaration, you can, by habit, specify SetLastError = true, but the library will ignore this - errors will not be available through Marshal.GetLastWin32Error (). For access to errors at OpenSSL the methods.
And then we form the already familiar static class “UOpenSSLAPI” which, depending on the system, will call the method of one of two classes:
private static object fpOSSection = new object(); /**<summary> OpenSSL</summary>**/ public static void OPENSSL_init() { lock (pOSSection) { if (fIsLinux) LOpenSSLAPI.OPENSSL_init(); else WOpenSSLAPI.OPENSSL_init(); } } /**<summary> OpenSSL</summary>**/ public static object pOSSection { get { return fpOSSection; } } /**<summary> </summary>**/ public static bool fIsLinux { get { int iPlatform = (int) Environment.OSVersion.Platform; return (iPlatform == 4) || (iPlatform == 6) || (iPlatform == 128); } }
Just note the presence of a critical section. OpenSSL theoretically works in a multi-threaded environment. But, firstly, the description immediately states that this is not guaranteed:
But you still can't concurrently use most objects in multiple threads.
And secondly, the connection method is not the most trivial. Plain two nuclear VM (server with an Intel Xeon E5649 processor in Hyper-Threading mode) using such a critical section gives about 100 complete cycles (see testing algorithm from the previous article ) or 600 signatures per second, which is basically enough for most tasks ( under heavy loads, the microservice or nodal system architecture will still be used rather.
Unlike CryptoPro, OpenSSL requires certain actions before starting to use it even after finishing work with the library:
/**<summary> OpenSSL</summary>**/ public static void InitOpenSSL() { UOpenSSLAPI.OPENSSL_init(); UOpenSSLAPI.ERR_load_crypto_strings(); UOpenSSLAPI.ERR_load_RSA_strings(); UOpenSSLAPI.OPENSSL_add_all_algorithms_conf(); UOpenSSLAPI.OpenSSL_add_all_ciphers(); UOpenSSLAPI.OpenSSL_add_all_digests(); } /**<summary> OpenSSL</summary>**/ public static void CleanupOpenSSL() { UOpenSSLAPI.EVP_cleanup(); UOpenSSLAPI.CRYPTO_cleanup_all_ex_data(); UOpenSSLAPI.ERR_free_strings(); }
OpenSSL stores information about errors in internal structures for access to which, in the library there are special methods. Unfortunately, some simple methods, such as ERR_error_string, are unstable, so you have to use more complex methods:
/**<summary> OpenSSL</summary> * <param name="_iErr"> </param> * <param name="_iPart"></param> * <returns> </returns> * **/ public static string GetErrStrPart(ulong _iErr, int _iPart) { // 0) IntPtr hErrStr = IntPtr.Zero; switch (_iPart) { case 0: hErrStr = UOpenSSLAPI.ERR_lib_error_string(_iErr); break; case 1: hErrStr = UOpenSSLAPI.ERR_func_error_string(_iErr); break; case 2: hErrStr = UOpenSSLAPI.ERR_reason_error_string(_iErr); break; } // 1) return PtrToFirstStr(hErrStr); } /**<summary> OpenSSL</summary> * <param name="_iErr"> </param> * <returns> </returns> * **/ public static string GetErrStr(ulong _iErr ) { return UCConsts.S_GEN_LIB_ERR_MAKRO.Frm(_iErr, GetErrStrPart(_iErr, 0), GetErrStrPart(_iErr, 1), GetErrStrPart(_iErr, 2)); } /**<summary> OpenSSL</summary> * <returns> </returns> * **/ public static string GetErrStrOS() { return GetErrStr(UOpenSSLAPI.ERR_get_error()); }
An error in OpenSSL contains information about the library in which it occurred, the method and the reason. Therefore, after receiving the error code itself, it is necessary to extract all these three parts separately and put them together in a text string. The lengths of each line, according to the OpenSSL documentation, do not exceed 120 characters, and since we use managed code, the line must be carefully extracted by reference:
/**<summary> PChar _iLen</summary> * <param name="_hPtr"> </param> * <param name="_iLen"> </param> * <returns> </returns> * **/ public static string PtrToFirstStr(IntPtr _hPtr, int _iLen = 256) { if(_hPtr == IntPtr.Zero) return ""; try { byte[] arStr = new byte[_iLen]; Marshal.Copy(_hPtr, arStr, 0, arStr.Length); string[] arRes = Encoding.ASCII.GetString(arStr).Split(new char[] { (char)0 }, StringSplitOptions.RemoveEmptyEntries); if (arRes.Length > 0) return arRes[0]; return ""; }catch { return ""; } }
Errors when checking certificates are not in the general list, and they must be extracted by a separate method, according to the verification context:
/**<summary> </summary> * <param name="_hStoreCtx"> </param> * <returns> </returns> * **/ public static string GetCertVerifyErr(IntPtr _hStoreCtx) { int iErr = UOpenSSLAPI.X509_STORE_CTX_get_error(_hStoreCtx); return PtrToFirstStr(UOpenSSLAPI.X509_verify_cert_error_string(iErr)); }
As always, cryptography begins with a certificate search. We use a staff repository, so we will look for regular methods:
/**<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> * **/ internal static int FindCertificateOS(string _pFindValue, out X509Certificate2 _pCert, ref string _sError, StoreLocation _pLocation = StoreLocation.CurrentUser, StoreName _pName = StoreName.My, X509FindType _pFindType = X509FindType.FindByThumbprint, bool _fVerify = false) { lock (UOpenSSLAPI.pOSSection) { // 0) _pCert = null; X509Store pStore = new X509Store(_pName, _pLocation); X509Certificate2Collection pCerts = null; try { // 1) pStore.Open(OpenFlags.ReadOnly); // 2) ( , .. Verify Linux false) pCerts = pStore.Certificates.Find(_pFindType, _pFindValue, false); if (pCerts.Count == 0) return UConsts.E_NO_CERTIFICATE; // 3) if (!_fVerify) { _pCert = ISDP_X509Cert.Create(pCerts[0], TCryptoPath.cpOpenSSL); return UConsts.S_OK; } // 4) foreach (X509Certificate2 pCert in pCerts) { ISDP_X509Cert pISDPCert = ISDP_X509Cert.Create(pCert, TCryptoPath.cpOpenSSL); if (pISDPCert.ISDPVerify()) { _pCert = pISDPCert; return UConsts.S_OK; } } return UConsts.E_NO_CERTIFICATE; } finally { if(pCerts != null) pCerts.Clear(); pStore.Close(); } } }
Pay attention to the critical section. Mono with certificates also works through OpenSSL, but not via UOpenSSLAPI. If it is not done here, you can get memory leaks and floating incomprehensible errors under load.
The main feature is the creation of a certificate. Unlike the version for CryptoPro, in this case from the repository we get the certificate itself (X509Certificate2), and the link in the Handle in it already points to the OpenSSL structure X509_st. It would seem that this is what is needed, but there is no pointer to EVP_PKEY (link to the private key structure in OpenSSL).
The private key itself, as it turned out, is stored in clear text in the internal field of the certificate - impl / fallback / _cert / _rsa / rsa. This is a RSAManaged class, and a quick glance at its code (for example, the DecryptValue method) shows how bad mono is with cryptography. Instead of honestly using OpenSSL cryptography methods, they seem to have implemented several algorithms manually. This assumption is supported by an empty search result for their project using OpenSSL methods such as CMS_final, CMS_sign or CMS_ContentInfo_new. And without them, it is difficult to imagine the formation of a standard CMS signature structure. At the same time, the work with certificates is partially conducted through OpenSSL.
This suggests that the private key will have to be unloaded from mono and loaded into EVP_PKEY via pem. Because of this, we again need a class derived from X509Certificate, which will store all additional links.
However, as in the case of CryptoPro, attempts to create a new certificate from Handle do not lead to success either (mono crashes with an error), and creating a certificate based on the resulting certificate leads to memory leaks. Therefore, the only option is to create a certificate based on the byte array containing pem. PEM certificate can be obtained as follows:
/**<summary> </summary> * <param name="_pCert"></param> * <param name="_arData"> </param> * <param name="_fBase64"> Base64</param> * <param name="_sError"> </param> * <returns> , UConsts.S_OK </returns> * **/ public static int ToCerFile(this X509Certificate2 _pCert, out byte[] _arData, ref string _sError, bool _fBase64 = true) { _arData = new byte[0]; try { byte[] arData = _pCert.Export(X509ContentType.Cert); // 0) DER if (!_fBase64) { _arData = arData; return UConsts.S_OK; } // 1) Base64 using (TextWriter pWriter = new StringWriter()) { pWriter.WriteLine(UCConsts.S_PEM_BEGIN_CERT); pWriter.WriteLine(Convert.ToBase64String(arData, Base64FormattingOptions.InsertLineBreaks)); pWriter.WriteLine(UCConsts.S_PEM_END_CERT); // 1.2) _arData = Encoding.UTF8.GetBytes(pWriter.ToString()); } return UConsts.S_OK; } catch (Exception E) { _sError = UCConsts.S_TO_PEM_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } }
The certificate is obtained without the private key and we connect it ourselves, forming a separate field for the link to ENV_PKEY:
/**<summary> OpenSSL (EVP_PKEY) </summary> * <remarks> PEM</remarks> * <param name="_arData"> </param> * <returns> (EVP_PKEY)</returns> * **/ internal static IntPtr GetENV_PKEYOS(byte[] _arData) { IntPtr hBIOPem = IntPtr.Zero; try { // 0) BIO hBIOPem = UOpenSSLAPI.BIO_new_mem_buf( _arData, _arData.Length); if (hBIOPem == IntPtr.Zero) return IntPtr.Zero; IntPtr hKey = IntPtr.Zero; // 1) UOpenSSLAPI.PEM_read_bio_PrivateKey(hBIOPem, ref hKey, IntPtr.Zero, 0); return hKey; } finally { if(hBIOPem != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOPem); } }
Uploading a private key to a PEM is a much more complicated task than a PEM certificate, but it is already described here . Note that unloading the private key is an “extremely unsafe” case, and this should be avoided in every way. And since this work becomes mandatory for working with OpenSSL, on Windows it is better to use crypt32.dll methods or regular .Net classes to use this library. On Linux, you’ll have to work this way for now.
It is also worth remembering that the generated links point to an unmanaged memory area and they must be freed. Since in .Net 4.5 X509Certificate2 is not Disposable, then this should be done in the destructor
To sign OpenSSL, you can use the simplified CMS_sign method, but it is based on the configuration file, which will be the same for all certificates, in the choice of algorithm. Therefore, relying on the code of this method is better to implement a similar signature generation:
/**<summary> </summary> * <param name="_arData"> </param> * <param name="_pCert"></param> * <param name="_sError"> </param> * <param name="_arRes"> </param> * <returns> , UConsts.S_OK </returns> * **/ internal static int SignDataOS(byte[] _arData, X509Certificate2 _pCert, out byte[] _arRes, ref string _sError) { _arRes = new byte[0]; uint iFlags = UCConsts.CMS_DETACHED; IntPtr hData = IntPtr.Zero; IntPtr hBIORes = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; try { // 0) ISDP_X509Cert pCert = ISDP_X509Cert.Convert(_pCert, TCryptoPath.cpOpenSSL); // 1) BIO int iRes = GetBIOByBytesOS(_arData, out hData, ref _sError); if (iRes != UConsts.S_OK) return iRes; // 2) BIO hBIORes = UOpenSSLAPI.BIO_new(UOpenSSLAPI.BIO_s_mem()); // 3) hCMS = UOpenSSLAPI.CMS_ContentInfo_new(); if (hCMS == IntPtr.Zero) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_CR_ERR); if (!UOpenSSLAPI.CMS_SignedData_init(hCMS)) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_INIT_ERR); // 4) if(UOpenSSLAPI.CMS_add1_signer(hCMS, pCert.hRealHandle, pCert.hOSKey, pCert.hOSDigestAlg, iFlags) == IntPtr.Zero) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_SET_SIGNER_ERR); // 5) - if (!UOpenSSLAPI.CMS_set_detached(hCMS, 1)) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_SET_DET_ERR); // 6) if (!UOpenSSLAPI.CMS_final(hCMS, hData, IntPtr.Zero, iFlags)) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_FINAL_ERR); // 7) BIO if (!UOpenSSLAPI.i2d_CMS_bio_stream(hBIORes, hCMS, IntPtr.Zero, iFlags)) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_EXP_TO_BIO_ERR); // 8) BIO return ReadFromBIO_OS(hBIORes, out _arRes, ref _sError); } catch (Exception E) { _sError = UCConsts.S_SIGN_OS_GEN_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } finally { if(hBIORes != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIORes); if(hData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hData); if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS); } }
The flow of the algorithm is as follows. First we convert the incoming certificate (if it is X509Certificate2) into our type. Since we work with links to the unmanaged memory area, then they must be closely monitored. .Net after some time after the reference to the certificate from the scope is released, it will start the destructor itself. And in it, we have precisely prescribed the methods necessary for cleaning all the unmanaged memory associated with it. Such an approach will allow us not to waste time on tracking these links directly inside the method.
Having dealt with the certificate we form BIO with the data and the structure of the signature. Then we add the signer's data, set the signature detachment flag and start the final signature generation. The result is transferable to BIO. It remains only to extract the byte array from BIO. Converting a BIO into a set of bytes and back is often used, so it is better to put them into separate methods:
/**<summary> BIO OpenSSL</summary> * <param name="_hBIO"> BIO</param> * <param name="_sError"> </param> * <param name="_arRes"></param> * <param name="_iLen"> , 0 - </param> * <returns> , UConsts.S_OK </returns> * **/ internal static int ReadFromBIO_OS(IntPtr _hBIO, out byte[] _arRes, ref string _sError, uint _iLen = 0) { _arRes = new byte[0]; IntPtr hRes = IntPtr.Zero; uint iLen = _iLen; if(iLen == 0) iLen = int.MaxValue; try { // 0) iLen = UOpenSSLAPI.BIO_read(_hBIO, IntPtr.Zero, int.MaxValue); // 1) hRes = Marshal.AllocHGlobal((int)iLen); if (UOpenSSLAPI.BIO_read(_hBIO, hRes, iLen) != iLen) { _sError = UCConsts.S_OS_BIO_READ_LEN_ERR; return UConsts.E_CRYPTO_ERR; } // 2) _arRes = new byte[iLen]; Marshal.Copy(hRes, _arRes, 0, _arRes.Length); return UConsts.S_OK;; } catch (Exception E) { _sError = UCConsts.S_OS_BIO_READ_GEN_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } finally { if(hRes != IntPtr.Zero) Marshal.FreeHGlobal(hRes); } } /**<summary> BIO </summary> * <param name="_arData"></param> * <param name="_hBIO"> BIO</param> * <param name="_sError"> </param> * <returns> , UConsts.S_OK </returns> * **/ internal static int GetBIOByBytesOS(byte[] _arData, out IntPtr _hBIO, ref string _sError) { _hBIO = UOpenSSLAPI.BIO_new_mem_buf( _arData, _arData.Length); if (_hBIO == IntPtr.Zero) return RetErrOS(ref _sError, UCConsts.S_OS_CM_BIO_CR_ERR); return UConsts.S_OK; }
As in the case of CryptoPro, it is necessary to extract information about the signature hashing algorithm from the certificate. But in the case of OpenSSL, it is stored directly in the certificate:
/**<summary> OpenSSL</summary> * <param name="_hCert"> (X509)</param> * <returns> </returns> * **/ public static IntPtr GetDigestAlgOS(IntPtr _hCert) { x509_st pCert = (x509_st)Marshal.PtrToStructure(_hCert, typeof(x509_st)); X509_algor_st pAlgInfo = (X509_algor_st)Marshal.PtrToStructure(pCert.sig_alg, typeof(X509_algor_st)); IntPtr hAlgSn = UOpenSSLAPI.OBJ_nid2sn(UOpenSSLAPI.OBJ_obj2nid(pAlgInfo.algorithm)); return UOpenSSLAPI.EVP_get_digestbyname(hAlgSn); }
The method turned out pretty tricky, but it works. In the 1.0.2 documentation, you can find the EVP_get_digestbynid method, but the libraries of the version we use do not export it. Therefore, we first form a nid, and based on it a short name. And already by the short name, you can extract the algorithm using the standard method of searching by name.
The received signature needs verification. OpenSSL verifies the signature as follows:
/**<summary> </summary> * <param name="_arData">, </param> * <param name="_arSign"></param> * <param name="_pCert"></param> * <param name="_sError"> </param> * <param name="_pLocation"></param> * <param name="_fVerifyOnlySign"> </param> * <returns> , UConsts.S_OK </returns> * <remarks> </remarks> * **/ internal static int CheckSignOS(byte[] _arData, byte[] _arSign, out X509Certificate2 _pCert, ref string _sError, bool _fVerifyOnlySign = true, StoreLocation _pLocation = StoreLocation.CurrentUser){ _pCert = null; IntPtr hBIOData = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; IntPtr hTrStore = IntPtr.Zero; try { // 0) BIO int iRes = GetBIOByBytesOS(_arData, out hBIOData, ref _sError); if (iRes != UConsts.S_OK) return iRes; // 1) CMS iRes = GetCMSFromBytesOS(_arSign, out hCMS, ref _sError); if (iRes != UConsts.S_OK) return iRes; uint iFlag = UCConsts.CMS_DETACHED; // 2) if (!_fVerifyOnlySign) { iRes = GetTrustStoreOS(_pLocation, out hTrStore, ref _sError); if (iRes != UConsts.S_OK) return iRes; } else iFlag |= UCConsts.CMS_NO_SIGNER_CERT_VERIFY; // 3) if (!UOpenSSLAPI.CMS_verify(hCMS, IntPtr.Zero, hTrStore, hBIOData, IntPtr.Zero, iFlag)) return RetErrOS(ref _sError, UCConsts.S_OS_CM_CHECK_ERR); return UConsts.S_OK; } finally { if(hBIOData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOData); if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS); if(hTrStore != IntPtr.Zero) UOpenSSLAPI.X509_STORE_free(hTrStore); } }
First, the signature data is converted from the byte array into the CMS structure:
/**<summary> CMS </summary> * <param name="_arData"> CMS</param> * <param name="_hCMS"> CMS</param> * <param name="_sError"> </param> * <returns> , UConsts.S_OK </returns> * **/ internal static int GetCMSFromBytesOS(byte[] _arData, out IntPtr _hCMS, ref string _sError) { _hCMS = IntPtr.Zero; IntPtr hBIOCMS = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; try { // 0) CMS hCMS = UOpenSSLAPI.CMS_ContentInfo_new(); if (hCMS == IntPtr.Zero) return RetErrOS(ref _sError); if (!UOpenSSLAPI.CMS_SignedData_init(hCMS)) return RetErrOS(ref _sError); // 1) BIO hBIOCMS = UOpenSSLAPI.BIO_new_mem_buf(_arData, _arData.Length); if (hBIOCMS == IntPtr.Zero) return RetErrOS(ref _sError); // 2) CMS if (UOpenSSLAPI.d2i_CMS_bio(hBIOCMS, ref hCMS) == IntPtr.Zero) return RetErrOS(ref _sError); // 3) - , _hCMS = hCMS; hCMS = IntPtr.Zero; return UConsts.S_OK; } finally { if(hBIOCMS != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOCMS); if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS); } }
, BIO. , , ( ) :
/**<summary> </summary> * <param name="_hStore"> </param> * <param name="_pLocation"> </param> * <param name="_sError"> </param> * <returns> , UConsts.S_OK </returns> */ internal static int GetTrustStoreOS(StoreLocation _pLocation, out IntPtr _hStore, ref string _sError) { _hStore = IntPtr.Zero; IntPtr hStore = IntPtr.Zero; try { List<X509Certificate2> pCerts = GetCertList(_pLocation, StoreName.Root, TCryptoPath.cpOpenSSL); pCerts.AddRange(GetCertList(_pLocation, StoreName.AuthRoot, TCryptoPath.cpOpenSSL)); // 1) hStore = UOpenSSLAPI.X509_STORE_new(); foreach (X509Certificate2 pCert in pCerts) { // ( ) UOpenSSLAPI.X509_STORE_add_cert(hStore, pCert.getRealHandle()); } // 2) UOpenSSLAPI.ERR_clear_error(); _hStore = hStore; hStore = IntPtr.Zero; return UConsts.S_OK; } catch (Exception E) { _sError = UCConsts.S_FORM_TRUST_STORE_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } finally { if (hStore != IntPtr.Zero) UOpenSSLAPI.X509_STORE_free(hStore); }
, ( , ). CMS_Verify, .
(, CRL), iFlag .
. , , . .Net — SignedCms, .
( , ) . — , .
/**<summary> </summary> * <param name="_arSign"></param> * <param name="_sError"> </param> * <param name="_arContent"> </param> * <returns> , UConsts.S_OK </returns> * **/ internal int DecodeOS(byte[] _arSign, byte[] _arContent, ref string _sError) { IntPtr hBIOData = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; IntPtr hCerts = IntPtr.Zero; try { // 0) MS int iRes = UCUtils.GetCMSFromBytesOS(_arSign, out hCMS, ref _sError); if(iRes != UConsts.S_OK) return iRes; iRes = UCUtils.GetBIOByBytesOS(_arContent, out hBIOData, ref _sError); if(iRes != UConsts.S_OK) return iRes; // 1) uint iFlags = UCConsts.CMS_NO_SIGNER_CERT_VERIFY; if(_arContent.Length == 0) iFlags |= UCConsts.CMS_NO_CONTENT_VERIFY; // 2) CMS if (!UOpenSSLAPI.CMS_verify(hCMS, IntPtr.Zero, IntPtr.Zero, hBIOData, IntPtr.Zero, iFlags)) return UCUtils.RetErrOS(ref _sError, UCConsts.S_OS_CMS_VERIFY_ERR); // 3) hCerts = UOpenSSLAPI.CMS_get0_signers(hCMS); int iCnt = UOpenSSLAPI.sk_num(hCerts); for (int i = 0; i < iCnt; i++) { IntPtr hCert = UOpenSSLAPI.sk_value(hCerts, i); byte[] arData; iRes = UCUtils.GetCertBytesOS(hCert, out arData, ref _sError); if(iRes != UConsts.S_OK) return iRes; fpCertificates.Add(ISDP_X509Cert.Create(arData, TCryptoPath.cpOpenSSL)); } // 4) IntPtr hSigners = UOpenSSLAPI.CMS_get0_SignerInfos(hCMS); iCnt = UOpenSSLAPI.sk_num(hSigners); for (int i = 0; i < iCnt; i++) { IntPtr hSignerInfo = UOpenSSLAPI.sk_value(hSigners, i); // 4.1) ISDPSignerInfo pInfo = new ISDPSignerInfo(this); iRes = pInfo.DecodeOS(hSignerInfo, ref _sError); if(iRes != UConsts.S_OK) return iRes; fpSignerInfos.Add(pInfo); } return UConsts.S_OK; } catch (Exception E) { _sError = UCConsts.S_OS_CMS_DECODE.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } finally { if(hCerts != IntPtr.Zero) UOpenSSLAPI.sk_free(hCerts); if(hBIOData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIOData); if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS); } }
, BIO ( ) CMS, . , — .
(STACK_OF(X509)), sk_pop, . , sk_value.
, CMS_get0_signers CMS_get1_certs. , . , , :
CRYPTO_add(&cch->d.certificate->references, 1, CRYPTO_LOCK_X509);
1.1.0 X509_up_ref, .
:
/**<summary> </summary> * <param name="_hSignerInfo">Handler (OpenSSL)</param> * <param name="_sError"> </param> * <returns> , UConsts.S_OK </returns> * **/ public int DecodeOS(IntPtr _hSignerInfo, ref string _sError) { try { // 0) int iRes = UCUtils.GetSignerInfoCertOS(_hSignerInfo, fpSignedCMS.pCertificates, out fpCertificate, ref _sError); if(iRes != UConsts.S_OK) return iRes; // 1) uint iPos = UOpenSSLAPI.CMS_signed_get_attr_by_NID(_hSignerInfo, UCConsts.NID_pkcs9_signingTime, 0); IntPtr hAttr = UOpenSSLAPI.CMS_signed_get_attr(_hSignerInfo, iPos); IntPtr hDateTime = UOpenSSLAPI.X509_ATTRIBUTE_get0_data(hAttr, 0, UCConsts.V_ASN1_UTCTIME, IntPtr.Zero); asn1_string_st pDate = (asn1_string_st)Marshal.PtrToStructure(hDateTime, typeof(asn1_string_st)); // 2) Pkcs9SigningTime byte[] arDateAttr = new byte[pDate.iLength]; Marshal.Copy(pDate.hData, arDateAttr, 0, (int)pDate.iLength); arDateAttr = new byte[] { (byte)UCConsts.V_ASN1_UTCTIME, (byte)pDate.iLength}.Concat(arDateAttr).ToArray(); fpSignedAttributes.Add(new Pkcs9SigningTime(arDateAttr)); return UConsts.S_OK; } catch (Exception E) { _sError = UCConsts.S_CMS_SIGNER_DEC_OS_ER.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } }
, , . ASN.1. asn1_string_st Pkcs9SigningTime.
:
/**<summary> </summary> * <param name="_hSignerInfo"> </param> * <param name="_pCert"> </param> * <param name="_pCerts"> </param> * <param name="_sError"> </param> * <returns> , UConsts.S_OK </returns> * **/ internal static int GetSignerInfoCertOS(IntPtr _hSignerInfo, X509Certificate2Collection _pCerts, out X509Certificate2 _pCert, ref string _sError) { _pCert = null; try { // 0) IntPtr hKey = IntPtr.Zero; IntPtr hIssuer = IntPtr.Zero; IntPtr hSNO = IntPtr.Zero; if (!UOpenSSLAPI.CMS_SignerInfo_get0_signer_id(_hSignerInfo, ref hKey, ref hIssuer, ref hSNO)) return RetErrOS(ref _sError, UCConsts.S_GET_RECEIP_INFO_ERR); // 1) string sSerial; int iRes = GetBinaryHexFromASNOS(hSNO, out sSerial, ref _sError); if(iRes != UConsts.S_OK) return iRes; X509Certificate2Collection pResCerts = _pCerts.Find(X509FindType.FindBySerialNumber, sSerial, false); if(pResCerts.Count == 0) return RetErrOS(ref _sError, UCConsts.S_NO_CERTIFICATE, UConsts.E_NO_CERTIFICATE); _pCert = pResCerts[0]; return UConsts.S_OK; } catch (Exception E) { _sError = UCConsts.S_GET_SIGN_INFO_GEN_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } }
, . asn1_string_st, hex :
/**<summary> Hex ASN.1</summary> * <param name="_hASN"> ASN.1</param> * <param name="_sError"> </param> * <param name="_sHexData"> Hex</param> * <returns> , UConsts.S_OK </returns> * **/ internal static int GetBinaryHexFromASNOS(IntPtr _hASN, out string _sHexData, ref string _sError) { _sHexData = ""; try { asn1_string_st pSerial = (asn1_string_st)Marshal.PtrToStructure(_hASN, typeof(asn1_string_st)); byte[] arStr = new byte[pSerial.iLength]; Marshal.Copy(pSerial.hData, arStr, 0, (int)pSerial.iLength); _sHexData = arStr.ToHex().ToUpper(); return UConsts.S_OK; } catch (Exception E) { _sError = UCConsts.S_HEX_ASN_BINARY_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } }
, , .
OpenSSL :
/**<summary> </summary> * <param name="_arInput"> </param> * <param name="_pReceipients"> </param> * <param name="_arRes"></param> * <param name="_sError"> </param> * <returns> , UConsts.S_OK </returns> * **/ internal static int EncryptDataOS(byte[] _arInput, List<X509Certificate2> _pReceipients, out byte[] _arRes, ref string _sError) { _arRes = new byte[0]; uint iFlags = UCConsts.CMS_BINARY; IntPtr hData = IntPtr.Zero; IntPtr hReceipts = IntPtr.Zero; IntPtr hBIORes = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; try { // 0) BIO int iRes = GetBIOByBytesOS(_arInput, out hData, ref _sError); if (iRes != UConsts.S_OK) return iRes; // 1) iRes = GetCertsStackOS(_pReceipients, out hReceipts, ref _sError); if (iRes != UConsts.S_OK) return iRes; // 2) CMS hCMS = UOpenSSLAPI.CMS_encrypt(hReceipts, hData, UOpenSSLAPI.EVP_des_ede3_cbc(), iFlags); if (hCMS == IntPtr.Zero) return RetErrOS(ref _sError, UCConsts.S_ENC_CMS_ERR); // 3) CMS BIO hBIORes = UOpenSSLAPI.BIO_new(UOpenSSLAPI.BIO_s_mem()); if (!UOpenSSLAPI.i2d_CMS_bio_stream(hBIORes, hCMS, IntPtr.Zero, iFlags)) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_EXP_TO_BIO_ERR); // 4) BIO return ReadFromBIO_OS(hBIORes, out _arRes, ref _sError); } catch (Exception E) { _sError = UCConsts.S_ENC_OS_GEN_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } finally { if(hBIORes != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIORes); if(hData != IntPtr.Zero) UOpenSSLAPI.BIO_free(hData); if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS); if(hReceipts != IntPtr.Zero) UOpenSSLAPI.sk_free(hReceipts); } }
BIO — . . , BIO . OpenSSL , , , . EVP_des_ede3_cbc, .
, . . OpenSSL:
/**<summary> </summary>* <param name="_hStack"> </param> * <param name="_pCerts"> </param> * <param name="_sError"> </param> * <returns> , UConsts.S_OK </returns> * **/ public static int GetCertsStackOS(List<X509Certificate2> _pCerts, out IntPtr _hStack, ref string _sError) { _hStack = IntPtr.Zero; IntPtr hStack = IntPtr.Zero; try { hStack = UOpenSSLAPI.sk_new_null(); foreach (X509Certificate2 pCert in _pCerts) { // 0) , ISDP_X509Cert pLocCert = ISDP_X509Cert.Convert(pCert, TCryptoPath.cpOpenSSL); // 1) UOpenSSLAPI.sk_push(hStack, pLocCert.hRealHandle); } _hStack = hStack; hStack = IntPtr.Zero; return UConsts.S_OK; } catch (Exception E) { _sError = UCConsts.S_GEN_CERT_STACK_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } finally { if(hStack != IntPtr.Zero) UOpenSSLAPI.sk_free(hStack); } }
, . :
/**<summary> </summary> * <param name="_arInput"> </param> * <param name="_arRes"></param> * <param name="_pLocation"> , </param> * <param name="_sError"> </param> * <param name="_pCert"></param> * <returns> , UCOnsts.S_OK </returns> * **/ internal static int DecryptDataOS(byte[] _arInput, out X509Certificate2 _pCert, out byte[] _arRes, ref string _sError, StoreLocation _pLocation = StoreLocation.CurrentUser ) { _arRes = new byte[0]; _pCert = null; uint iFlag = UCConsts.CMS_BINARY; IntPtr hBIORes = IntPtr.Zero; IntPtr hCMS = IntPtr.Zero; X509Certificate2 pCert; try { // 0) CMS int iRes = GetCMSFromBytesOS(_arInput, out hCMS, ref _sError); if (iRes != UConsts.S_OK) return iRes; // 1) IntPtr hReceipts = UOpenSSLAPI.CMS_get0_RecipientInfos(hCMS); int iCnt = UOpenSSLAPI.sk_num(hReceipts); for(int i = 0; i < iCnt; i++) { IntPtr hRecep = UOpenSSLAPI.sk_value(hReceipts, i); iRes = GetRecepInfoCertOS(hRecep, _pLocation, out pCert, ref _sError); if (iRes != UConsts.S_OK && iRes != UConsts.E_NO_CERTIFICATE) return iRes; // 1.1) if (iRes == UConsts.E_NO_CERTIFICATE) continue; ISDP_X509Cert pLocCert = ISDP_X509Cert.Convert(pCert); // 1.2) if (pLocCert.hOSKey == IntPtr.Zero) continue; // 1.3) if (!UOpenSSLAPI.CMS_RecipientInfo_set0_pkey(hRecep, pLocCert.hOSKey)) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_SET_DEC_KEY_ERR); try { // 1.4) if (!UOpenSSLAPI.CMS_RecipientInfo_decrypt(hCMS, hRecep)) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_REC_DEC_ERR); } finally { // !! UOpenSSLAPI.CMS_RecipientInfo_set0_pkey(hRecep, IntPtr.Zero); } // 1.5) hBIORes = UOpenSSLAPI.BIO_new(UOpenSSLAPI.BIO_s_mem()); if (!UOpenSSLAPI.CMS_decrypt(hCMS, IntPtr.Zero, pLocCert.hRealHandle, IntPtr.Zero, hBIORes, iFlag)) return RetErrOS(ref _sError, UCConsts.S_OS_CMS_FULL_DEC_ERR); _pCert = pLocCert; // 2) BIO return ReadFromBIO_OS(hBIORes, out _arRes, ref _sError); } _sError = UCConsts.S_DEC_NO_CERT_ERR; return UConsts.E_NO_CERTIFICATE; } catch (Exception E) { _sError = UCConsts.S_DEC_GEN_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } finally { if(hBIORes != IntPtr.Zero) UOpenSSLAPI.BIO_free(hBIORes); if(hCMS != IntPtr.Zero) UOpenSSLAPI.CMS_ContentInfo_free(hCMS); } }
. , CMS_RecipientInfo_set0_pkey, CMS, .
, , . , . :
/**<summary> </summary> * <param name="_hRecep"> </param> * <param name="_pCert"> </param> * <param name="_pLocation"> </param> * <param name="_sError"> </param> * <returns> , UConsts.S_OK </returns> * **/ internal static int GetRecepInfoCertOS(IntPtr _hRecep, StoreLocation _pLocation, out X509Certificate2 _pCert, ref string _sError) { _pCert = null; try { // 0) IntPtr hKey = IntPtr.Zero; IntPtr hIssuer = IntPtr.Zero; IntPtr hSNO = IntPtr.Zero; if (!UOpenSSLAPI.CMS_RecipientInfo_ktri_get0_signer_id(_hRecep, ref hKey, ref hIssuer, ref hSNO)) return RetErrOS(ref _sError, UCConsts.S_GET_RECEIP_INFO_ERR); // 1) string sSerial; int iRes = GetBinaryHexFromASNOS(hSNO, out sSerial, ref _sError); if(iRes != UConsts.S_OK) return iRes; // 2) iRes = FindCertificateOS(sSerial, out _pCert, ref _sError, _pLocation, StoreName.My, X509FindType.FindBySerialNumber); if(iRes != UConsts.S_OK) return iRes; return UConsts.S_OK; } catch (Exception E) { _sError = UCConsts.S_GET_RECEIP_INFO_GEN_ERR.Frm(E.Message); return UConsts.E_GEN_EXCEPTION; } }
CMS_RecipientInfo_ktri_get0_signer_id , hSNO . .
C , , . ktri — . OpenSSL : CMS_RecipientInfo_kari_*, CMS_RecipientInfo_kekri_* CMS_RecipientInfo_set0_password pwri.
, . . , . . . OpenSSL . ( ), , .
, , , :
/**<summary> OpenSSL</summary> * <param name="_iRevFlag"> </param> * <param name="_iRevMode"> </param> * <param name="_hCert"> </param> * <param name="_rOnDate"> </param> * <param name="_pLocation"> </param> * <param name="_sError"> </param> * <returns> , UConsts.S_OK </returns> * **/ internal static int VerifyCertificateOS(IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag, StoreLocation _pLocation, DateTime _rOnDate, ref string _sError) { IntPtr hStore = IntPtr.Zero; IntPtr hStoreCtx = IntPtr.Zero; try { // 0) int iRes = GetTrustStoreOS(_pLocation, out hStore, ref _sError); if(iRes != UConsts.S_OK) return iRes; // 1) hStoreCtx = UOpenSSLAPI.X509_STORE_CTX_new(); if (!UOpenSSLAPI.X509_STORE_CTX_init(hStoreCtx, hStore, _hCert, IntPtr.Zero)) { _sError = UCConsts.S_CRYPTO_CONTEXT_CER_ERR; return UConsts.E_CRYPTO_ERR; } // 2) SetStoreCtxCheckDate(hStoreCtx, _rOnDate); // 3) if (!UOpenSSLAPI.X509_verify_cert(hStoreCtx)) { _sError = UCConsts.S_CRYPTO_CHAIN_CHECK_ERR.Frm(GetCertVerifyErr(hStoreCtx)); return UConsts.E_CRYPTO_ERR; } return UConsts.S_OK; } finally { if (hStore != IntPtr.Zero) UOpenSSLAPI.X509_STORE_free(hStore); if (hStoreCtx != IntPtr.Zero) UOpenSSLAPI.X509_STORE_CTX_free(hStoreCtx); } }
(X509_STORE_CTX) . :
/**<summary> </summary> * <param name="_hStoreCtx"> </param> * <param name="_rDate"></param> * **/ public static void SetStoreCtxCheckDate(IntPtr _hStoreCtx, DateTime _rDate) { uint iFlags = UCConsts.X509_V_FLAG_USE_CHECK_TIME | UCConsts.X509_V_FLAG_X509_STRICT | UCConsts.X509_V_FLAG_CRL_CHECK_ALL; // UOpenSSLAPI.X509_STORE_CTX_set_flags(_hStoreCtx, iFlags); // UOpenSSLAPI.X509_STORE_CTX_set_time(_hStoreCtx, iFlags, (uint)_rDate.ToUnix()); // - UOpenSSLAPI.X509_STORE_CTX_set_trust(_hStoreCtx, UCConsts.X509_TRUST_TRUSTED); }
, .
, . X509Certificate2 (mono) . .
, Windows . . Linux , , .
CSP 5.0 , RSA . , , , RSA, , .
Source: https://habr.com/ru/post/423769/