Conectando OpenSSL a Mono

En un artículo anterior, se describió el proceso de integración de los certificados GOST de CryptoPro con mono. En el mismo, nos detenemos en conectar certificados RSA.


Continuamos portando uno de nuestros sistemas de servidor escritos en C # a Linux, y la cola llegó a la parte relacionada con RSA. Si la última vez que las dificultades para conectarse se explicaron fácilmente por la interacción de dos sistemas que originalmente no estaban conectados entre sí, cuando se conectaban certificados RSA "ordinarios" desde mono, nadie esperaba una trampa.



La instalación del certificado y la clave no causó problemas, e incluso el sistema lo vio en el almacenamiento estándar. Sin embargo, ya no era posible firmar, cifrar o extraer datos de una firma generada previamente; mono cayó de manera estable con un error. Tuve que, como en el caso de CryptoPro, conectarme directamente a la biblioteca de cifrado. Para los certificados RSA en Linux, el principal candidato para dicha conexión es OpenSSL.


Instalación de certificado


Afortunadamente, Centos 7 tiene una versión incorporada de OpenSSL - 1.0.2k. Para no introducir dificultades adicionales en el sistema, decidimos conectarnos a esta versión. OpenSSL le permite crear almacenes de certificados de archivos especiales, sin embargo:


  1. dicha tienda contiene certificados y CRL, no claves privadas, por lo que en este caso deberán almacenarse por separado;
  2. almacenar certificados y claves privadas en Windows en un disco de forma insegura es "extremadamente inseguro" (los responsables de la seguridad digital generalmente lo describen con mayor capacidad y menos censurados), para ser sincero, esto no es muy seguro en Linux, pero, de hecho, es común practica
  3. coordinar la ubicación de dichos repositorios en Windows y Linux es bastante problemático;
  4. en caso de implementación manual del almacenamiento, se requerirá una utilidad para administrar el conjunto de certificados;
  5. el propio mono usa almacenamiento en disco con una estructura OpenSSL, y también almacena claves privadas en un formulario abierto cercano;

Por estos motivos, utilizaremos los almacenes de certificados estándar .Net y mono para conectar OpenSSL. Para hacer esto, en Linux, el certificado y la clave privada primero deben colocarse en el repositorio mono.

Instalación de certificado
Utilizaremos la utilidad certmgr estándar para esto. Primero, instale la clave privada de pfx:

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

Luego colocamos el certificado de este pfx, la clave privada se conectará automáticamente a él:

certmgr -add -c My {cer file}

Si desea instalar la clave en el almacenamiento de la máquina, debe agregar la opción -m.

Después de lo cual el certificado se puede ver en el repositorio:

certmgr -list -c -v My

Presta atención a la emisión. Cabe señalar que el certificado es visible para el sistema y está vinculado a la clave privada descargada anteriormente. Después de eso, puede proceder a la conexión en el código.

Conexión en código


Al igual que la última vez, el sistema, a pesar de estar portado a Linux, debería haber seguido funcionando en el entorno de Windows. Por lo tanto, externamente, el trabajo con criptografía debe realizarse a través de métodos generales de la forma "byte [] SignData (byte [] _arData, X509Certificate2 _pCert)", que debería funcionar igual en Linux y Windows.


Idealmente, debería haber métodos que funcionen igual que en Windows, independientemente del tipo de certificado (en Linux a través de OpenSSL o CryptoPro según el certificado, y en Windows a través de crypt32).


El análisis de las bibliotecas OpenSSL mostró que en Windows la biblioteca principal es "libeay32.dll", y en Linux "libcrypto.so.10". Además de la última vez, formamos dos clases WOpenSSLAPI y LOpenSSLAPI, que contienen una lista de métodos de biblioteca de biblioteca:

 [DllImport(CTRYPTLIB, CharSet = CharSet.Auto, SetLastError = true, CallingConvention = CallingConvention.Cdecl)] internal static extern void OPENSSL_init(); 

Preste atención a la convención de llamada, a diferencia de CryptoPro, aquí debe especificarse explícitamente. Esta vez, la sintaxis para conectar cada uno de los métodos deberá generarse de forma independiente en función de los archivos fuente * .h OpenSSL.


Las reglas básicas para generar sintaxis de llamadas en C # basadas en datos de archivos .h son las siguientes:


  1. cualquier enlace a estructuras, cadenas, etc. - IntPtr, incluidos los enlaces dentro de las estructuras mismas;
  2. enlaces a enlaces - ref IntPtr, si esta opción no funciona, entonces solo IntPtr. En este caso, el enlace en sí deberá colocarse y eliminarse manualmente;
  3. matrices - byte [];
  4. largo en C (OpenSSL) es un int en C # (un pequeño error, a primera vista, puede convertirse en horas de búsqueda de la fuente de errores impredecibles);

En la declaración, por costumbre, puede especificar SetLastError = true, pero la biblioteca lo ignorará; los errores no estarán disponibles a través de Marshal.GetLastWin32Error (). OpenSSL tiene sus propios métodos para acceder a los errores.


Y luego formamos la clase estática ya familiar "UOpenSSLAPI" que, según el sistema, llamará al método de una de dos clases:


 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); } } 

Inmediatamente, notamos la presencia de una sección crítica. OpenSSL teóricamente proporciona trabajo en un entorno multiproceso. Pero, en primer lugar, inmediatamente en la descripción se dice que esto no está garantizado:


Pero aún no puede utilizar simultáneamente la mayoría de los objetos en múltiples hilos.

Y en segundo lugar, el método de conexión no es el más trivial. La VM de dos núcleos habitual (servidor con el procesador Intel Xeon E5649 en modo Hyper-Threading) cuando se utiliza una sección tan crítica proporciona alrededor de 100 ciclos completos (consulte el algoritmo de prueba del artículo anterior) o 600 firmas por segundo, que es básicamente suficiente para la mayoría de las tareas ( bajo cargas pesadas, el microservicio o la arquitectura nodal del sistema se utilizarán de todos modos).


Inicializando y descargando OpenSSL


A diferencia de CryptoPro OpenSSL requiere ciertas acciones antes de comenzar a usarlo y después de que termine de trabajar con la biblioteca:

 /**<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(); } 


Información de error


OpenSSL almacena información de error en estructuras internas para las cuales existen métodos especiales en la biblioteca. Desafortunadamente, algunos de los métodos simples, como ERR_error_string, son inestables, por lo que debe usar métodos más complejos:


Obteniendo información de error
 /**<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()); } 

Un error en OpenSSL contiene información sobre la biblioteca en la que ocurrió, el método y la razón. Por lo tanto, después de recibir el código de error en sí, es necesario extraer estas tres partes por separado y reunirlas en una línea de texto. La longitud de cada línea, de acuerdo con la documentación de OpenSSL , no supera los 120 caracteres, y dado que usamos código administrado, debemos extraer cuidadosamente la línea del enlace:


Obteniendo una cadena por IntPtr
 /**<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 ""; } } 

Los errores al verificar los certificados no se incluyen en la lista general y deben extraerse por un método separado, de acuerdo con el contexto de verificación:


Recibir error de verificación de certificado
 /**<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)); } 

Búsqueda de certificados


Como siempre, la criptografía comienza con una búsqueda de certificado. Usamos almacenamiento a tiempo completo, por lo que buscaremos utilizando métodos regulares:


Búsqueda de certificados
 /**<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(); } } } 

Presta atención a la sección crítica. Mono con certificados también funciona a través de OpenSSL, pero no a través de UOpenSSLAPI. Si no lo hace aquí, puede obtener pérdidas de memoria y errores flotantes incomprensibles bajo carga.


La característica principal es la creación de un certificado. A diferencia de la versión para CryptoPro, en este caso obtenemos el certificado en sí (X509Certificate2) de la tienda, y el enlace en Handle ya está dirigido a la estructura OpenSSL X509_st. Parece que es necesario, pero no hay un puntero a EVP_PKEY (enlace a la estructura de clave privada en OpenSSL) en el certificado.

La clave privada en sí misma, como se vio después, se almacena en el claro en el campo interno del certificado: impl / fallback / _cert / _rsa / rsa. Esta es la clase RSAManaged, y un vistazo rápido a su código (por ejemplo, el método DecryptValue ) muestra cuán malo es mono con la criptografía. En lugar de utilizar honestamente las técnicas de criptografía de OpenSSL, parecen haber implementado varios algoritmos manualmente. Esta suposición está respaldada por un resultado de búsqueda vacío para su proyecto para métodos OpenSSL como CMS_final, CMS_sign o CMS_ContentInfo_new. Y sin ellos, es difícil imaginar la formación de una estructura de firma CMS estándar. Al mismo tiempo, el trabajo con certificados se realiza parcialmente a través de OpenSSL.


Esto significa que la clave privada deberá descargarse de mono y cargarse en EVP_PKEY a través de pem. Debido a esto, nuevamente necesitamos el heredero de clase de X509Certificate, que almacenará todos los enlaces adicionales.


Estos son solo intentos, como en el caso de CryptoPro para crear un nuevo certificado de Handle, que tampoco conducen al éxito (fallas mono con un error), y la creación sobre la base del certificado recibido conduce a pérdidas de memoria. Por lo tanto, la única opción es crear un certificado basado en una matriz de bytes que contenga pem. El certificado PEM se puede obtener de la siguiente manera:


Obtención de un certificado PEM
 /**<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; } } 

El certificado se obtiene sin una clave privada y lo conectamos nosotros mismos, formando un campo separado para un enlace a ENV_PKEY:


Generando ENV_PKEY basado en clave privada PEM
 /**<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); } } 

Al cargar la clave privada en PEM, la tarea es mucho más complicada que el certificado PEM, pero ya se describe aquí . Tenga en cuenta que descargar la clave privada es un negocio "extremadamente inseguro", y esto debe evitarse en todos los sentidos. Y dado que esta descarga es necesaria para trabajar con OpenSSL, en Windows es mejor usar los métodos crypt32.dll o las clases regulares .Net cuando se usa esta biblioteca. En Linux, por ahora, tienes que trabajar así.


También vale la pena recordar que los enlaces generados indican un área de memoria no administrada y deben liberarse. Porque en .Net 4.5 X509Certificate2 no es desechable, entonces debe hacer esto en el destructor


Firma


Para firmar OpenSSL, puede usar el método simplificado CMS_sign, sin embargo, se basa en un archivo de configuración para elegir un algoritmo, que será el mismo para todos los certificados. Por lo tanto, es mejor confiar en el código de este método para implementar una generación de firma similar:


Firma de datos
 /**<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); } } 

El progreso del algoritmo es el siguiente. Primero, convierta el certificado entrante (si es X509Certificate2) a nuestro tipo. Porque Dado que trabajamos con enlaces a un área de memoria no administrada, debemos monitorearlos cuidadosamente. .Net algún tiempo después de que el enlace al certificado esté fuera de alcance, lanzará el destructor. Y en él ya hemos prescrito con precisión los métodos necesarios para borrar toda la memoria no administrada asociada a ella. Este enfoque nos permite no perder el tiempo rastreando estos enlaces directamente dentro del método.


Habiendo tratado con el certificado, formamos un BIO con datos y una estructura de firma. Luego agregamos los datos del firmante, establecemos la bandera para desconectar la firma y comenzamos la formación final de la firma. El resultado se transfiere a BIO. Solo resta extraer una matriz de bytes de la BIO. Con frecuencia se usa la conversión de BIO a un conjunto de bytes y viceversa, por lo que es mejor colocarlos en un método separado:


BIO en byte [] y viceversa
 /**<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; } 

Como en el caso de CryptoPro, es necesario extraer información sobre el algoritmo de hash de firma del certificado. Pero en el caso de OpenSSL, se almacena directamente en el certificado:


Obteniendo un Algoritmo Hash
 /**<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); } 

El método resultó bastante complicado, pero funciona. Puede encontrar el método EVP_get_digestbynid en la documentación 1.0.2, sin embargo, las bibliotecas de la versión que utilizamos no lo exportan. Por lo tanto, primero formamos nid y, sobre esta base, un nombre corto. Y ya con un nombre corto, puede extraer el algoritmo de forma regular buscando por nombre.


Verificación de firma


La firma recibida necesita verificación. OpenSSL verifica la firma de la siguiente manera:


Verificación de firma
 /**<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); } } 

Primero, los datos de la firma se convierten de la matriz de bytes a la estructura CMS:


Formación de la estructura CMS.
 /**<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 :


hex ANS.1
  /**<summary> Hex     ASN.1</summary> * <param name="_hASN">   ASN.1</param> * <param name="_sError">   </param> * <param name="_sHexData">   Hex</param> * <returns>  ,  UConsts.S_OK   </returns> * **/ 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); } } 


, . :


  1. , ;
  2. ;
  3. ;
  4. ;
  5. BIO ;

 /**<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); } 

, .


Conclusión


, . X509Certificate2 (mono) . .


, Windows . . Linux , , .


CSP 5.0 , RSA . , , , RSA, , .


Referencias


  1. OpenSSL 1.0.2 ManPages ;
  2. OpenSSL 1 2 ;
  3. OpenSSL :
    1. cms_smime.c;
  4. Wiki OpenSSL ;
  5. mono:
    1. RSAManaged ;

Source: https://habr.com/ru/post/es423769/


All Articles