Conectando CryptoPro a Mono

En relación con la transición a Linux, se hizo necesario portar uno de nuestros sistemas de servidor escritos en C # a Mono. El sistema funciona con firmas digitales mejoradas, por lo que una de las tareas a las que nos enfrentamos fue probar el rendimiento de los certificados GOST de CryptoPro en mono. CryptoPro ha implementado CSP para Linux durante bastante tiempo, pero el primer intento de usarlo demostró que las clases de criptografía Mono nativas (similares a las de la base .Net - X509Store, X509Certificate2, etc.) no solo no funcionan con claves de invitado, sino que incluso no los veo en sus bóvedas. Debido a esto, el trabajo con criptografía tuvo que conectarse directamente a través de las bibliotecas CryptoPro.



Instalación de certificado


Antes de implementar el código, debe instalar el certificado y asegurarse de que funciona correctamente.


Instalación de certificado

El componente CryptoPro CSP versión 3.9 se instaló en Centos 7 en la carpeta / opt / cprocsp. Para evitar conflictos entre las utilidades mono y CryptoPro con el mismo nombre (por ejemplo, certmgr), la ruta a la carpeta no se agregó a las variables de entorno y todas las utilidades se llamaron en la ruta completa.


Primero, definimos una lista de lectores:
/opt/cprocsp/bin/amd64/csptest -enum -info -type PP_ENUMREADERS | iconv -f cp1251


Si no hay un lector de la carpeta en el disco (HDIMAGE) entre la lista, póngalo:
/opt/cprocsp/sbin/amd64/cpconfig -hardware reader -add HDIMAGE store


Luego puede crear contenedores con la forma '\\. \ HDIMAGE \ {container name}' ya sea creando un nuevo contenedor con claves:
/opt/cprocsp/bin/amd64/csptest -keyset -provtype 75 -newkeyset -cont '\\.\HDIMAGE\test'


o creando la carpeta / var / opt / cprocsp / keys / root / {container name} .000, que contiene el conjunto estándar de archivos de contenedor CryptoPro (* .key, * .mask, etc.).


Después de eso, el certificado del contenedor se puede instalar en el almacén de certificados:
/opt/cprocsp/bin/amd64/certmgr -inst mMy -cont '\\.\HDIMAGE\{ }'


El certificado instalado se puede ver con el siguiente comando:
/opt/cprocsp/bin/amd64/certmgr -list mMy


El funcionamiento del certificado se puede verificar de la siguiente manera:
/opt/cprocsp/bin/amd64/cryptcp – sign -norev -thumbprint {} {} { }
/opt/cprocsp/bin/amd64/cryptcp – verify -norev { }


Si todo está bien con el certificado, puede proceder a la conexión en el código.



Conexión en código


A pesar del proceso de portabilidad a Linux, se suponía que el sistema continuaría funcionando en el entorno de Windows, por lo que, externamente, el trabajo con criptografía debía realizarse a través de métodos generales de la forma "byte [] SignData (byte [] _arData, X509Certificate2 _pCert)", que debería funcionar igual que tanto en Linux como en Windows.


El análisis de los métodos de las bibliotecas de criptografía resultó exitoso, porque CryptoPro implementó la biblioteca "libcapi20.so", que imita completamente las bibliotecas de cifrado estándar de Windows: "crypt32.dll" y "advapi32.dll". Quizás, por supuesto, no del todo, pero todos los métodos necesarios para trabajar con criptografía están disponibles allí, y casi todos funcionan.


Por lo tanto, formamos dos clases estáticas "WCryptoAPI" y "LCryptoAPI", cada una de las cuales importará el conjunto de métodos necesarios de la siguiente manera:


 [DllImport(LIBCAPI20, SetLastError = true)] internal static extern bool CertCloseStore(IntPtr _hCertStore, uint _iFlags); 

La sintaxis de conexión para cada uno de los métodos se puede crear de forma independiente, o usar el sitio web pinvoke , o copiar de fuentes .Net (clase CAPISafe ). Desde el mismo módulo, puede dibujar constantes y estructuras asociadas con la criptografía, cuya presencia siempre facilita la vida al trabajar con bibliotecas externas.


Y luego formamos la clase estática "UCryptoAPI" que, dependiendo del sistema, llamará al método de una de dos clases:


 /**<summary> </summary> * <param name="_iFlags"> (  0)</param> * <param name="_hCertStore">   </param> * <returns>   </returns> * **/ internal static bool CertCloseStore(IntPtr _hCertStore, uint _iFlags) { if (fIsLinux) return LCryptoAPI.CertCloseStore(_hCertStore, _iFlags); else return WCryptoAPI.CertCloseStore(_hCertStore, _iFlags); } /**<summary>  </summary>**/ public static bool fIsLinux { get { int iPlatform = (int) Environment.OSVersion.Platform; return (iPlatform == 4) || (iPlatform == 6) || (iPlatform == 128); } } 

Por lo tanto, utilizando los métodos de la clase UCryptoAPI, puede implementar código casi uniforme para ambos sistemas.


Búsqueda de certificados


El trabajo con criptografía generalmente comienza con una búsqueda de certificados, para esto en crypt32.dll hay dos métodos CertOpenStore (abre el almacén de certificados especificado) y un simple CertOpenSystemStore (abre los certificados personales del usuario). Debido al hecho de que trabajar con certificados no se limita a los certificados de usuario personales, conectamos el primero:


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> * **/ public static int FindCertificateCP(string _pFindValue, out X509Certificate2 _pCert, ref string _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); } } 


La búsqueda se lleva a cabo en varias etapas:
  1. apertura de almacenamiento;
  2. formación de la estructura de datos por la cual estamos buscando;
  3. búsqueda de certificados;
  4. si es necesario, luego la verificación del certificado (descrito en una sección separada)
  5. cerrar el repositorio y liberar la estructura desde el punto 2 (ya que en todas partes hay trabajo con memoria .Net no administrada, no se hará nada para limpiarlo);

Hay algunos puntos sutiles al buscar certificados.


CryptoPro en Linux funciona con cadenas ANSI y en Windows con UTF8, por lo tanto:


  1. al conectar el método de abrir almacenamiento en Linux, es necesario indicar explícitamente el tipo de cálculo de referencias [In, MarshalAs (UnmanagedType.LPStr)] para el parámetro del código de almacenamiento;
  2. pasando la cadena de búsqueda (por ejemplo, por el nombre del Asunto) se debe convertir en un conjunto de bytes con diferentes codificaciones;
  3. para todas las constantes criptográficas que tienen variaciones según el tipo de cadena (por ejemplo, CERT_FIND_SUBJECT_STR_A y CERT_FIND_SUBJECT_STR_W) en Windows, debe seleccionar * _W y en Linux * _A;

El método MapX509StoreFlags se puede tomar directamente de las fuentes de Microsoft sin cambios, simplemente forma una máscara final basada en banderas .Net.


El valor por el cual se realiza la búsqueda depende del tipo de búsqueda (consulte con MSDN para CertFindCertificateInStore ), el ejemplo muestra las dos opciones más utilizadas: para el formato de cadena (nombres Asunto, Emisor, etc.) y binario (huella digital, número de serie).


El proceso de creación de un certificado de IntPtr en Windows y Linux es muy diferente. Windows creará el certificado de una manera simple:

  new X509Certificate2(hCert); 


en Linux, debe crear un certificado en dos pasos:

 X509Certificate2(new X509Certificate(hCert)); 


En el futuro, necesitamos acceso a hCert para el trabajo, y debería almacenarse en el objeto de certificado. En Windows, se puede recuperar más tarde de la propiedad Handle, pero Linux convierte la estructura CERT_CONTEXT que sigue al enlace hCert en un enlace a la estructura x509_st (OpenSSL) y lo registra en Handle. Por lo tanto, vale la pena crear un heredero de X509Certificate2 (ISDP_X509Cert en el ejemplo), que almacenará hCert en ambos sistemas en un campo separado.


No olvide que este es un enlace a un área de memoria no administrada y debe liberarse después del final del trabajo. Porque en .Net 4.5 X509Certificate2 no es desechable: la limpieza con el método CertFreeCertificateContext debe realizarse en el destructor.


Formación de firma


Cuando se trabaja con certificados GOST, las firmas desconectadas con un firmante casi siempre se usan. Para crear dicha firma, se requiere un bloque de código bastante simple:


Formación de firma
 /**<summary>  </summary> * <param name="_arData">  </param> * <param name="_pCert"></param> * <param name="_sError">   </param> * <param name="_arRes"> </param> * <returns>  ,  UConsts.S_OK   </returns> * **/ public static int SignDataCP(byte[] _arData, X509Certificate2 _pCert, out byte[] _arRes, ref string _sError) { _arRes = new byte[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 = new byte[iLen]; // 1)   if (!UCryptoAPI.CryptSignMessage(ref pParams, true, 1, new IntPtr[1] { pGlobData }, new uint[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); } } 

Durante el trabajo del método, se forma una estructura con parámetros y se llama al método de firma. La estructura de parámetros puede permitirle guardar certificados en la firma para formar una cadena completa (los campos cMsgCert y rgpMsgCert, el primero almacena el número de certificados, la segunda lista de enlaces a las estructuras de estos certificados).


El método de firma puede recibir uno o más documentos para la firma simultánea con una firma. Esto, por cierto, no contradice la Ley Federal 63 y es muy conveniente, porque es poco probable que el usuario esté contento de la necesidad de hacer clic en el botón "firmar" varias veces.


La peculiaridad principal de este método es que no funciona en el modo de dos llamadas, lo cual es típico para la mayoría de los métodos de biblioteca que funcionan con grandes bloques de memoria (el primero con nulo, devuelve la longitud de búfer requerida, el segundo llena el búfer). Por lo tanto, es necesario crear un búfer grande y luego acortarlo a su longitud real.


El único problema grave es la búsqueda del OID del algoritmo hash (resumen) utilizado al firmar; en forma explícita, no está en el certificado (solo existe el algoritmo de la firma en sí). Y si en Windows puede especificarlo con una cadena vacía, se recuperará automáticamente, pero Linux se negará a firmar si el algoritmo no es el mismo.


Pero hay un truco: en la información sobre el algoritmo de firma (estructura CRYPT_OID_INFO), el OID de firma se almacena en pszOID y el identificador del algoritmo hash se almacena en Algid. Y convertir Algid a OID ya es una cuestión técnica:


Obteniendo el OID del algoritmo hash
 /**<summary> OID   </summary> * <param name="_hCertHandle"> </param> * <param name="_sOID">  OID</param> * <param name="_sError">   </param> * <returns>  ,  UConsts.S_OK   </returns> * **/ internal static int GetHashAlgoritmOID(IntPtr _hCertHandle, out string _sOID, ref string _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); } } 

Después de leer cuidadosamente el código, puede sorprenderse de que el identificador del algoritmo se obtenga de una manera simple (CertOIDToAlgId) y que el Oid sea complicado (CryptFindOIDInfo). Sería lógico suponer el uso de métodos tanto complejos como simples, y en Linux ambas opciones funcionan con éxito. Sin embargo, en Windows, la difícil opción de obtener un identificador y simplemente obtener un OID es inestable, por lo que un híbrido tan extraño sería una solución estable.


Verificación de firma


La verificación de la firma se lleva a cabo en dos etapas, al principio se verifica la firma y luego se verifica el certificado con el que se generó (cadena, fecha de firma, etc.).
Además de al firmar, debe especificar el conjunto de datos a firmar, los parámetros de firma y la firma en sí:


Verificación de firma
 /**<summary>      </summary> * <returns></returns> * **/ internal static 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> * **/ public static int CheckSignCP(byte[] _arData, byte[] _pSign, out X509Certificate2 _pCert, ref string _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 }, //   new int[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(); } } 

Por conveniencia, el proceso de formar una estructura con parámetros se ha movido a un método separado (GetStdSignVerifyPar). Después de eso, se verifica la firma en sí y se extrae el primer firmante (para bien, sería necesario extraer todo, pero una firma que contenga varios firmantes sigue siendo exótica).


Después de extraer el certificado del firmante, lo convertiremos a nuestra clase y lo verificaremos (si se especifica en los parámetros del método). Para la verificación, se utiliza la fecha de firma del primer firmante (consulte la sección sobre la extracción de información de la firma y la sección sobre la verificación del certificado).


Extraer información de firma


A menudo, los sistemas criptográficos requieren una representación impresa de la firma. En cada caso, es diferente, por lo que es mejor crear una clase de información sobre la firma, que contendrá la información en una forma conveniente para su uso y con su ayuda proporcionará una presentación impresa. En .Net existe tal clase: SignedCms, sin embargo, su análogo en mono con las firmas de CritoPro se niega a funcionar en el primero, en segundo lugar contiene el modificador sellado y en tercer lugar casi todas sus propiedades están protegidas contra escritura, por lo que debe crear su propio análogo.


La firma en sí contiene dos elementos principales: una lista de certificados y una lista de firmantes. La lista de certificados puede estar vacía o puede contener todos los certificados para verificación, incluidas las cadenas completas. La lista de firmantes indica el número de firmas reales. La comunicación entre ellos se lleva a cabo por el número de serie y el editor (Emisor). Teóricamente, en una firma puede haber dos certificados de diferentes editores con el mismo número de serie, pero en la práctica esto puede ser descuidado y buscado solo por el número de serie.


La lectura de la firma es la siguiente:


Extraer información de firma
 /**<summary></summary> * <param name="_arSign"></param> * <param name="_sError">   </param> * <returns>  ,  UConsts.S_OK   </returns> * **/ public int Decode(byte[] _arSign, ref string _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); } } 

La firma se analiza en varias etapas, primero se forma la estructura del mensaje (CryptMsgOpenToDecode), luego se ingresan los datos reales de la firma (CryptMsgUpdate). Queda por verificar que se trata de una firma real y primero obtener una lista de certificados y luego una lista de firmantes. La lista de certificados se recupera secuencialmente:


Obtener una lista de certificados
 /**<summary>     </summary> * <param name="_hMsg">Handle </param> * <returns> </returns> * **/ internal static 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; } 

Primero, el número de certificados se determina a partir del parámetro CMSG_CERT_COUNT_PARAM, y luego la información sobre cada certificado se recupera secuencialmente. El proceso de creación del contexto del certificado y sobre la base del propio certificado completa el proceso de creación.


Recuperar los datos del firmante es más difícil. Contienen una indicación del certificado y una lista de parámetros de firma (por ejemplo, la fecha de la firma). El proceso de extracción de datos es el siguiente:


Recuperando información del firmante
 /**<summary>   </summary> * <param name="_hMsg">Handler </param> * <param name="_iIndex"> </param> * <param name="_pSignedCms"> </param> * <param name="_sError">   </param> * <returns>  ,  UConsts.S_OK   </returns> * **/ public int Decode(IntPtr _hMsg, int _iIndex, ISDPSignedCms _pSignedCms, ref string _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 = new byte[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); } } 

, CMSG_SIGNER_INFO. . , .


, — ( , ).


 /**<summary>   </summary> * <param name="_pAttrs"> </param> * <returns> </returns> * **/ internal static 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; } 

Oid – ( ASN.1). :


 /**<summary>      </summary> * <param name="_sName"></param> * <returns> </returns> * **/ internal static Pkcs9AttributeObject Pkcs9AttributeFromOID(string _sName) { switch (_sName) { case UCConsts.S_SIGN_DATE_OID : return new Pkcs9SigningTime(); // case UConsts.S_CONTENT_TYPE_OID : return new Pkcs9ContentType(); ->>  Mono  // case UConsts.S_MESS_DIGEST_OID : return new Pkcs9MessageDigest(); default: return new Pkcs9AttributeObject(); } } /**<summary>  ASN</summary> * <param name="_pAttr"></param> * <returns></returns> * **/ internal static 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; } 

Pkcs9AttributeObject. , mono . Mono .


— — SignedCms, .



, , . (, , ).


 /**<summary> </summary> * <param name="_arInput">  </param> * <param name="_pCert"></param> * <param name="_arRes"></param> * <param name="_sError">   </param> * <returns>   ,  UConsts.S_OK   </returns> * **/ public static int EncryptDataCP(byte[] _arInput, X509Certificate2 _pCert, out byte[] _arRes, ref string _sError) { _arRes = new byte[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 = new byte[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; } } 

— , . , , .


, , .


. , ( ). :


 /**<summary> OID   </summary> * <param name="_hCertHandle"> </param> * <param name="_sOID">  OID</param> * <param name="_sError">   </param> * <returns>  ,  UConsts.S_OK   </returns> * **/ internal static int GetEncodeAlgoritmOID(IntPtr _hCertHandle, out string _sOID, ref string _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 = new byte[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); } } 

. ( , , , .), . (UCConsts.CRYPT_ENCRYPT_ALG_OID_GROUP_ID). — .


( ).



, , . . — , :


 /**<summary> </summary> * <param name="_arInput">  </param> * <param name="_arRes"></param> * <param name="_sError">   </param> * <param name="_pCert"></param> * <returns>  ,  UCOnsts.S_OK   </returns> * **/ public static int DecryptDataCP(byte[] _arInput, out X509Certificate2 _pCert, out byte[] _arRes, ref string _sError) { _arRes = new byte[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 = new byte[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); } } 

, . , ( Linux ).



, , , , . , . :


  1. ( , , . .);
  2. — ;
  3. — ;
  4. , , (CRL);

, .


Como ya está claro en la introducción, verificar la validez del certificado es una de las tareas más difíciles. Es por eso que la biblioteca tiene muchos métodos para implementar cada uno de los elementos individualmente. Por lo tanto, para simplificar, recurrimos a las fuentes .Net para el método X509Certificate2.Verify () y las tomamos como base.


La verificación consta de dos etapas:
  1. formar una cadena de certificados hasta la raíz;
  2. verifique cada uno de los certificados que contiene (por revocación, tiempo, etc.);

Dicha verificación debe realizarse antes de firmar y cifrar en la fecha actual, y en el momento de la verificación de la firma en la fecha de la firma. El método de verificación en sí es pequeño:


Verificación de certificado
 /**<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> * **/ internal static int VerifyCertificate (IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag, DateTime _rOnDate, TimeSpan _iCTLTimeout, IntPtr _hPolicy, ref string _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); } } 

Primero, se forma una cadena usando el método BuildChain, y luego se verifica. Durante la formación de la cadena, se forman la estructura de parámetros, la fecha de verificación y las banderas de verificación:


 /**<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> * **/ internal static int BuildChain (IntPtr _hChainEngine, IntPtr _hCert, X509RevocationMode _iRevMode, X509RevocationFlag _iRevFlag, DateTime _rOnDate, TimeSpan _rCTLTimeOut, ref IntPtr _hChain, ref string _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; } 

, Microsoft. hCertPolicy hAppPolicy OID-, , . , , .


(, ).


MapRevocationFlags — .Net — uint .


Conclusión


:


  1. 10 ;
  2. ;
  3. byte[] {1, 2, 3, 4, 5};
  4. ;
  5. ;
  6. byte[] {1, 2, 3, 4, 5};
  7. ;

Windows Linux 1-, 10- 50- , Linux . Linux - - ( , ), «» . (deadlock-) ( «Access Violation»).


UCryptoAPI . fpCPSection object :


 private static object fpCPSection = new object(); /**<summary> </summary> * <param name="_hCryptMsg">  </param> * **/ internal static bool CryptMsgClose(IntPtr _hCryptMsg) { lock (pCPSection) { if (fIsLinux) return LCryptoAPI.CryptMsgClose(_hCryptMsg); else return WCryptoAPI.CryptMsgClose(_hCryptMsg); } } /**<summary>     </summary>**/ public static object pCPSection { get { return fpCPSection;} } 

, Linux- .


mono Issuer Subject . , , mono X500DistinguishedName . , mono ( ), (impl.issuerName impl.subjectName). (Reflection) X500DistinguishedName, CERT_CONTEXT .


Referencias


  1. CAPILite
  2. c #
  3. .Net:
    1. CAPIBase
    2. X509Certificate2
    3. SignedCMS
    4. SignerInfo

  4. mono:
    1. X509Certificate2
    2. X509CertificateImplBtls

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


All Articles