Conectando o OpenSSL ao Mono

Em um artigo anterior, foi descrito o processo de integração dos certificados GOST do CryptoPro com mono. Do mesmo modo, insistimos em conectar certificados RSA.


Continuamos a portar um de nossos sistemas de servidor gravados em C # para Linux, e a fila atingiu a parte relacionada à RSA. Se a última vez em que as dificuldades de conexão foram explicadas facilmente pela interação de dois sistemas que não estavam originalmente conectados um ao outro, ao conectar certificados RSA “comuns” a partir de mono, ninguém esperava uma captura.



A instalação do certificado e da chave não causou problemas, e o sistema ainda o viu no armazenamento padrão. No entanto, não era mais possível assinar, criptografar ou extrair dados de uma assinatura gerada anteriormente - o mono caiu de maneira estável com um erro. Eu tive que, como no caso do CryptoPro, conectar-me diretamente à biblioteca de criptografia. Para certificados RSA no Linux, o principal candidato a essa conexão é o OpenSSL.


Instalação de certificado


Felizmente, o Centos 7 possui uma versão interna do OpenSSL - 1.0.2k. Para não introduzir dificuldades adicionais no sistema, decidimos nos conectar a esta versão. O OpenSSL permite criar armazenamentos especiais de certificados de arquivos, no entanto:


  1. essa loja contém certificados e CRLs, não chaves privadas; portanto, nesse caso, eles deverão ser armazenados separadamente;
  2. armazenar certificados e chaves privadas no Windows em um disco de forma insegura é "extremamente inseguro" (os responsáveis ​​pela segurança digital geralmente o descrevem com mais capacidade e menos censura); para ser sincero, isso não é muito seguro no Linux, mas, de fato, é comum prática;
  3. coordenar a localização desses repositórios no Windows e Linux é bastante problemático;
  4. no caso de implementação manual do armazenamento, será necessário um utilitário para gerenciar o conjunto de certificados
  5. o próprio mono usa armazenamento em disco com uma estrutura OpenSSL e também armazena chaves privadas de forma aberta nas proximidades;

Por esses motivos, usaremos os repositórios padrão de certificados .Net e mono para conectar o OpenSSL. Para fazer isso, no Linux, o certificado e a chave privada devem primeiro ser colocados no repositório mono.

Instalação de certificado
Usaremos o utilitário certmgr padrão para isso. Primeiro, instale a chave privada do pfx:

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

Em seguida, colocamos o certificado neste pfx, a chave privada se conectará automaticamente a ele:

certmgr -add -c My {cer file}

Se você deseja instalar a chave no armazenamento da máquina, você deve adicionar a opção -m.

Após o qual o certificado pode ser visto no repositório:

certmgr -list -c -v My

Preste atenção à emissão. Deve-se observar que o certificado está visível para o sistema e está vinculado à chave privada baixada anteriormente. Depois disso, você pode prosseguir para a conexão no código.

Conexão no código


Assim como na última vez, o sistema, apesar de ter sido portado para Linux, deveria ter continuado a funcionar no ambiente Windows. Portanto, externamente, o trabalho com criptografia deve ser realizado por meio de métodos gerais no formato “byte [] SignData (byte [] _arData, X509Certificate2 _pCert)”, que deve funcionar da mesma forma no Linux e no Windows.


Idealmente, deve haver métodos que funcionem exatamente como no Windows - independentemente do tipo de certificado (no Linux através do OpenSSL ou CryptoPro, dependendo do certificado, e no Windows através do crypt32).


A análise das bibliotecas OpenSSL mostrou que no Windows a biblioteca principal é "libeay32.dll" e no Linux "libcrypto.so.10". Assim como na última vez, formamos duas classes WOpenSSLAPI e LOpenSSLAPI, contendo uma lista de métodos da biblioteca de bibliotecas:

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

Preste atenção à convenção de chamada, ao contrário do CryptoPro - aqui ela deve ser especificada explicitamente. A sintaxe para conectar cada um dos métodos desta vez terá que ser gerada independentemente, com base nos arquivos de origem * .h OpenSSL.


As regras básicas para gerar a sintaxe de chamada em C # com base nos dados dos arquivos .h são as seguintes:


  1. quaisquer links para estruturas, strings, etc. - IntPtr, incluindo links dentro das próprias estruturas;
  2. links para links - ref IntPtr, se esta opção não funcionar, apenas IntPtr. Nesse caso, o próprio link deverá ser colocado e removido manualmente;
  3. matrizes - byte [];
  4. long in C (OpenSSL) é um int em C # (um pequeno erro, à primeira vista, pode se transformar em horas de pesquisa da fonte de erros imprevisíveis);

Na declaração, por hábito, você pode especificar SetLastError = true, mas a biblioteca ignorará isso - os erros não estarão disponíveis no Marshal.GetLastWin32Error (). O OpenSSL possui seus próprios métodos para acessar erros.


E então formamos a já estática classe estática “UOpenSSLAPI” que, dependendo do sistema, chamará o método de uma das duas 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); } } 

Imediatamente, notamos a presença de uma seção crítica. O OpenSSL teoricamente fornece trabalho em um ambiente multiencadeado. Mas, em primeiro lugar, a descrição diz imediatamente que isso não é garantido:


Mas você ainda não pode usar a maioria dos objetos simultaneamente em vários threads.

E segundo, o método de conexão não é o mais trivial. A VM de dois núcleos usual (servidor com o processador Intel Xeon E5649 no modo Hyper-Threading) ao usar uma seção crítica fornece cerca de 100 ciclos completos (consulte o algoritmo de teste do artigo anterior) ou 600 assinaturas por segundo, o que é basicamente o suficiente para a maioria das tarefas ( sob cargas pesadas, o microsserviço ou a arquitetura nodal do sistema será usada de qualquer maneira).


Inicializando e descarregando o OpenSSL


Ao contrário do CryptoPro, o OpenSSL requer determinadas ações antes de você começar a usá-lo e depois de terminar de trabalhar com a 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(); } 


Informações de erro


O OpenSSL armazena informações de erro em estruturas internas para as quais existem métodos especiais na biblioteca. Infelizmente, alguns dos métodos simples, como ERR_error_string, são instáveis, portanto você deve usar métodos mais complexos:


Obtendo informações de erro
 /**<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()); } 

Um erro no OpenSSL contém informações sobre a biblioteca em que ocorreu, o método e o motivo. Portanto, após receber o código de erro, é necessário extrair todas as três partes separadamente e reuni-las em uma linha de texto. O comprimento de cada linha, de acordo com a documentação do OpenSSL , não excede 120 caracteres e, como usamos o código gerenciado, devemos extrair cuidadosamente a linha do link:


Obtendo uma string pelo 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 ""; } } 

Erros ao verificar certificados não se enquadram na lista geral e devem ser extraídos por um método separado, de acordo com o contexto de verificação:


Receber erro de verificação 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)); } 

Pesquisa de certificado


Como sempre, a criptografia começa com uma pesquisa de certificado. Como usamos armazenamento em tempo integral, pesquisamos usando métodos regulares:


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

Preste atenção à seção crítica. Mono com certificados também funciona através do OpenSSL, mas não através do UOpenSSLAPI. Se você não fizer isso aqui, poderá obter vazamentos de memória e erros incompreensíveis flutuantes sob carga.


A principal característica é a criação de um certificado. Diferentemente da versão do CryptoPro, nesse caso, obtemos o próprio certificado (X509Certificate2) da loja, e o link no Handle já está direcionado para a estrutura do OpenSSL X509_st. Parece que é necessário, mas não há ponteiro para EVP_PKEY (link para a estrutura de chave privada no OpenSSL) no certificado.

A própria chave privada, como se viu, é armazenada de forma clara no campo interno do certificado - impl / fallback / _cert / _rsa / rsa. Esta é a classe RSAManaged, e uma rápida olhada em seu código (por exemplo, o método DecryptValue ) mostra o quão ruim é o mono na criptografia. Em vez de usar honestamente as técnicas de criptografia OpenSSL, elas parecem ter implementado vários algoritmos manualmente. Essa suposição é suportada por um resultado de pesquisa vazio para seu projeto para métodos OpenSSL, como CMS_final, CMS_sign ou CMS_ContentInfo_new. E sem eles, é difícil imaginar a formação de uma estrutura de assinatura padrão do CMS. Ao mesmo tempo, o trabalho com certificados é parcialmente realizado através do OpenSSL.


Isso significa que a chave privada precisará ser descarregada do mono e carregada no EVP_PKEY via pem. Por isso, precisamos novamente do herdeiro da classe do X509Certificate, que armazenará todos os links adicionais.


Aqui estão apenas algumas tentativas, como no caso do CryptoPro de criar um novo certificado a partir do Handle - também não levam ao sucesso (falhas mono com erro), e a criação com base no certificado recebido leva a vazamentos de memória. Portanto, a única opção é criar um certificado com base em uma matriz de bytes contendo pem. O certificado PEM pode ser obtido da seguinte forma:


Obtendo um 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; } } 

O certificado é obtido sem uma chave privada e nós o conectamos, formando um campo separado para um link para ENV_PKEY:


Gerando ENV_PKEY com base na chave 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); } } 

Ao carregar a chave privada no PEM, a tarefa é muito mais complicada do que o certificado PEM, mas já está descrito aqui . Observe que descarregar a chave privada é um negócio "extremamente inseguro", e isso deve ser evitado de todas as maneiras. E como esse descarregamento é necessário para funcionar com o OpenSSL, no Windows é melhor usar os métodos crypt32.dll ou classes .Net regulares ao usar esta biblioteca. No Linux, por enquanto, você precisa trabalhar assim.


Também é importante lembrar que os links gerados indicam uma área de memória não gerenciada e devem ser liberados. Porque no .Net 4.5 X509Certificate2 não é descartável, é necessário fazer isso no destruidor


Assinatura


Para assinar o OpenSSL, você pode usar o método CMS_sign simplificado, no entanto, ele depende de um arquivo de configuração na escolha de um algoritmo, que será o mesmo para todos os certificados. Portanto, é melhor confiar no código desse método para implementar uma geração de assinatura semelhante:


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

O progresso do algoritmo é o seguinte. Primeiro, converta o certificado recebido (se for X509Certificate2) em nosso tipo. Porque Como trabalhamos com links para uma área de memória não gerenciada, devemos monitorá-los cuidadosamente. .Net algum tempo depois que o link para o certificado estiver fora do escopo, ele iniciará o destruidor. E nele já prescrevemos com precisão os métodos necessários para limpar toda a memória não gerenciada associada a ela. Essa abordagem nos permite não perder tempo rastreando esses links diretamente dentro do método.


Depois de lidar com o certificado, formamos uma BIO com dados e uma estrutura de assinatura. Em seguida, adicionamos os dados do assinante, configuramos o sinalizador para desconectar a assinatura e iniciamos a formação final da assinatura. O resultado é transferido para a BIO. Resta apenas extrair uma matriz de bytes da BIO. A conversão de BIO em um conjunto de bytes e vice-versa é frequentemente usada, portanto, é melhor colocá-los em um método separado:


BIO em byte [] e vice-versa
 /**<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 no caso do CryptoPro, é necessário extrair informações sobre o algoritmo de hash de assinatura do certificado. Mas no caso do OpenSSL, ele é armazenado diretamente no certificado:


Buscando um algoritmo de 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); } 

O método acabou sendo bastante complicado, mas funciona. Você pode encontrar o método EVP_get_digestbynid na documentação 1.0.2, no entanto, as bibliotecas da versão que usamos não o exportam. Portanto, primeiro formamos nid e, com base nisso, um nome abreviado. E já com um nome abreviado, você pode extrair o algoritmo da maneira regular de pesquisar por nome.


Verificação de Assinatura


A assinatura recebida precisa de verificação. O OpenSSL verifica a assinatura da seguinte maneira:


Verificação de Assinatura
 /**<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); } } 

Primeiro, os dados da assinatura são convertidos da matriz de bytes na estrutura do CMS:


Formação da estrutura 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); } 

, .


Conclusão


, . X509Certificate2 (mono) . .


, Windows . . Linux , , .


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


Referências


  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/pt423769/


All Articles