Conectando o CryptoPro ao Mono

Em conexão com a transição para o Linux, tornou-se necessário portar um de nossos sistemas de servidor escritos em C # para Mono. O sistema trabalha com assinaturas digitais aprimoradas; portanto, uma das tarefas que enfrentamos foi verificar o desempenho dos certificados GOST do CryptoPro em mono. O próprio CryptoPro implementou o CSP para Linux há algum tempo, mas a primeira tentativa de usá-lo mostrou que as classes de criptografia Mono nativas (semelhantes às da base .Net - X509Store, X509Certificate2, etc.) não apenas não funcionam com chaves de convidado, mas também não os veja em seus cofres. Devido a isso, o trabalho com criptografia teve que ser conectado diretamente através das bibliotecas CryptoPro.



Instalação de certificado


Antes de implementar o código, você deve instalar o certificado e garantir que ele funcione corretamente.


Instalação de certificado

O componente CryptoPro CSP versão 3.9 foi instalado no Centos 7 na pasta / opt / cprocsp. Para evitar conflitos entre os utilitários mono e CryptoPro com o mesmo nome (por exemplo, certmgr), o caminho para a pasta não foi inserido nas variáveis ​​de ambiente e todos os utilitários foram chamados no caminho completo.


Primeiro, definimos uma lista de leitores:
/opt/cprocsp/bin/amd64/csptest -enum -info -type PP_ENUMREADERS | iconv -f cp1251


Se não houver um leitor da pasta no disco (HDIMAGE) na lista, coloque-o:
/opt/cprocsp/sbin/amd64/cpconfig -hardware reader -add HDIMAGE store


Em seguida, você pode criar contêineres no formato '\\. \ HDIMAGE \ {container name}', criando um novo contêiner com as chaves:
/opt/cprocsp/bin/amd64/csptest -keyset -provtype 75 -newkeyset -cont '\\.\HDIMAGE\test'


ou criando a pasta / var / opt / cprocsp / keys / root / {nome do contêiner} .000, que contém o conjunto padrão de arquivos de contêiner CryptoPro (* .key, * .mask etc.).


Depois disso, o certificado do contêiner pode ser instalado no armazenamento de certificados:
/opt/cprocsp/bin/amd64/certmgr -inst mMy -cont '\\.\HDIMAGE\{ }'


O certificado instalado pode ser visto com o seguinte comando:
/opt/cprocsp/bin/amd64/certmgr -list mMy


A operação do certificado pode ser verificada da seguinte maneira:
/opt/cprocsp/bin/amd64/cryptcp – sign -norev -thumbprint {} {} { }
/opt/cprocsp/bin/amd64/cryptcp – verify -norev { }


Se tudo estiver bem com o certificado, você poderá prosseguir para a conexão no código.



Conexão no código


Apesar do processo de portar para o Linux, o sistema deveria continuar funcionando no ambiente Windows; portanto, externamente, o trabalho com criptografia tinha que ser realizado através de métodos gerais no formato “byte [] SignData (byte [] _arData, X509Certificate2 _pCert)”, que deveria funcionar igualmente como no Linux e no Windows.


Uma análise dos métodos das bibliotecas de criptografia resultou bem-sucedida, porque o CryptoPro implementou a biblioteca “libcapi20.so”, que imita completamente as bibliotecas de criptografia padrão do Windows - “crypt32.dll” e “advapi32.dll”. Talvez, é claro, não inteiramente, mas todos os métodos necessários para trabalhar com criptografia estejam disponíveis lá, e quase todo o trabalho.


Portanto, formamos duas classes estáticas “WCryptoAPI” e “LCryptoAPI”, cada uma das quais importará o conjunto de métodos necessário da seguinte maneira:


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

A sintaxe de conexão de cada um dos métodos pode ser criada independentemente, ou usar o site pinvoke , ou copiar de fontes .Net (classe CAPISafe ). No mesmo módulo, você pode desenhar constantes e estruturas associadas à criptografia, cuja presença sempre facilita a vida ao trabalhar com bibliotecas externas.


E então formamos a classe estática “UCryptoAPI” que, dependendo do sistema, chamará o método de uma das duas classes:


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

Portanto, usando os métodos da classe UCryptoAPI, você pode implementar código quase uniforme para os dois sistemas.


Pesquisa de certificado


O trabalho com criptografia geralmente começa com uma pesquisa de certificado; para isso, no crypt32.dll, existem dois métodos CertOpenStore (abre o armazenamento de certificados especificado) e um CertOpenSystemStore simples (abre os certificados pessoais do usuário). Como o trabalho com certificados não se limita aos certificados de usuário pessoal, conectamos o primeiro:


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


A pesquisa ocorre em várias etapas:
  1. abertura de armazenamento;
  2. a formação da estrutura de dados pela qual estamos procurando
  3. pesquisa de certificado;
  4. se necessário, a verificação do certificado (descrita em uma seção separada);
  5. fechando o repositório e liberando a estrutura do ponto 2 (já que em todos os lugares há trabalho com memória .net não gerenciada, nada será feito para que possamos limpá-lo);

Existem alguns pontos sutis ao procurar certificados.


O CryptoPro no Linux funciona com seqüências de caracteres ANSI e no Windows com UTF8, portanto:


  1. ao conectar o método de abertura de armazenamento no Linux, é necessário indicar explicitamente o tipo de empacotamento [In, MarshalAs (UnmanagedType.LPStr)] para o parâmetro de código de armazenamento;
  2. passando a string de pesquisa (por exemplo, pelo nome de Subject), ela deve ser convertida em um conjunto de bytes com codificações diferentes;
  3. para todas as constantes criptográficas que possuem variação pelo tipo de sequência (por exemplo, CERT_FIND_SUBJECT_STR_A e CERT_FIND_SUBJECT_STR_W) no Windows, você deve selecionar * _W e no Linux * _A;

O método MapX509StoreFlags pode ser obtido diretamente de fontes da Microsoft sem alterações, simplesmente forma uma máscara final com base nos sinalizadores .Net.


O valor pelo qual a pesquisa é realizada depende do tipo de pesquisa (verifique no MSDN o CertFindCertificateInStore ); o exemplo mostra as duas opções mais usadas - para o formato da string (nomes Assunto, Emissor, etc.) e binário (impressão digital, número de série).


O processo de criação de um certificado do IntPtr no Windows e Linux é muito diferente. O Windows criará o certificado de uma maneira simples:

  new X509Certificate2(hCert); 


no Linux, você precisa criar um certificado em duas etapas:

 X509Certificate2(new X509Certificate(hCert)); 


No futuro, precisamos acessar o hCert for work e ele deverá ser armazenado no objeto de certificado. No Windows, ele pode ser recuperado posteriormente a partir da propriedade Handle, mas o Linux converte a estrutura CERT_CONTEXT que segue o link hCert em um link para a estrutura x509_st (OpenSSL) e o registra no Handle. Portanto, vale a pena criar um herdador do X509Certificate2 (ISDP_X509Cert no exemplo), que armazenará o hCert nos dois sistemas em um campo separado.


Não esqueça que este é um link para uma área de memória não gerenciada e deve ser liberada após o término do trabalho. Porque no .Net 4.5 X509Certificate2 não é descartável - a limpeza usando o método CertFreeCertificateContext deve ser executada no destruidor.


Formação de Assinaturas


Ao trabalhar com certificados GOST, quase sempre são usadas assinaturas desconectadas com um signatário. Para criar essa assinatura, é necessário um bloco de código bastante simples:


Formação de Assinaturas
 /**<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 o trabalho do método, uma estrutura com parâmetros é formada e o método de assinatura é chamado. A estrutura de parâmetros pode permitir que você salve certificados na assinatura para formar uma cadeia completa (os campos cMsgCert e rgpMsgCert, o primeiro armazena o número de certificados e a segunda lista de links para as estruturas desses certificados).


O método de assinatura pode receber um ou mais documentos para assinatura simultânea com uma assinatura. A propósito, isso não contradiz a Lei Federal 63 e é muito conveniente, pois é improvável que o usuário fique satisfeito com a necessidade de clicar no botão "assinar" várias vezes.


A principal estranheza desse método é que ele não funciona no modo de duas chamadas, o que é típico da maioria dos métodos de biblioteca que trabalham com grandes blocos de memória (o primeiro com nulo - retorna o comprimento do buffer necessário, o segundo preenche o buffer). Portanto, é necessário criar um buffer grande e reduzi-lo ao seu tamanho real.


O único problema sério é a pesquisa do OID do algoritmo de hash (Digest) usado ao entrar - de forma explícita, não está no certificado (existe apenas o algoritmo da própria assinatura). E se no Windows você pode especificá-lo com uma string vazia - ele será ativado automaticamente, mas o Linux se recusará a assinar se o algoritmo não for o mesmo.


Mas há um truque - nas informações sobre o algoritmo de assinatura (estrutura CRYPT_OID_INFO), o OID da assinatura é armazenado no pszOID e o identificador do algoritmo de hash é armazenado no Algid. E converter Algid em OID já é uma questão técnica:


Obtendo o OID do algoritmo de 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); } } 

Após a leitura cuidadosa do código, você pode se surpreender ao descobrir que o identificador do algoritmo é obtido de maneira simples (CertOIDToAlgId) e o Oid é complicado (CryptFindOIDInfo). Seria lógico supor o uso de métodos complexos ou simples, e no Linux ambas as opções funcionam com êxito. No entanto, no Windows, a opção difícil de obter um identificador e simplesmente obter um OID é instável; portanto, um híbrido tão estranho seria uma solução estável.


Verificação de Assinatura


A verificação da assinatura ocorre em duas etapas, no início, a própria assinatura é verificada e, em seguida, o certificado com o qual foi gerada é verificado (cadeia, data de assinatura, etc.).
Além de assinar, você deve especificar o conjunto de dados a serem assinados, os parâmetros de assinatura e a própria assinatura:


Verificação de Assinatura
 /**<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 conveniência, o processo de formação de uma estrutura com parâmetros foi movido para um método separado (GetStdSignVerifyPar). Depois disso, a própria assinatura é verificada e o primeiro signatário é extraído (para o bem, seria necessário extrair tudo, mas uma assinatura contendo vários signatários ainda é exótica).


Após extrair o certificado do assinante, o converteremos em nossa classe e o verificaremos (se especificado nos parâmetros do método). Para verificação, é usada a data de assinatura do primeiro signatário (consulte a seção sobre extração de informações da assinatura e a seção sobre verificação de certificado).


Extrair informações de assinatura


Geralmente, os sistemas criptográficos exigem uma representação impressa da assinatura. Em cada caso, é diferente, portanto, é melhor criar uma classe de informações sobre a assinatura, que conterá informações de uma forma conveniente para uso e, com sua ajuda, fornecerá uma apresentação impressa. No .Net existe essa classe - o SignedCms, no entanto, seu analógico em mono com as assinaturas do CritiPro pro se recusa a trabalhar no primeiro, em segundo lugar, contém o modificador selado e, em terceiro lugar, quase todas as suas propriedades são protegidas contra gravação, então você terá que criar seu próprio analógico.


A assinatura em si contém dois elementos principais - uma lista de certificados e uma lista de signatários. A lista de certificados pode estar vazia ou pode conter todos os certificados para verificação, incluindo cadeias completas. A lista de signatários indica o número de assinaturas reais. A comunicação entre eles é realizada por número de série e editor (emissor). Teoricamente, em uma assinatura, pode haver dois certificados de diferentes editores com o mesmo número de série, mas, na prática, isso pode ser negligenciado e pesquisado apenas pelo número de série.


A leitura da assinatura é a seguinte:


Extrair informações de assinatura
 /**<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); } } 

A assinatura é analisada em vários estágios, primeiro a estrutura da mensagem (CryptMsgOpenToDecode) é formada e, em seguida, os dados reais da assinatura (CryptMsgUpdate) são inseridos nela. Resta verificar se essa é uma assinatura real e primeiro obter uma lista de certificados e depois uma lista de signatários. A lista de certificados é recuperada sequencialmente:


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

Primeiro, o número de certificados é determinado a partir do parâmetro CMSG_CERT_COUNT_PARAM e, em seguida, as informações sobre cada certificado são recuperadas seqüencialmente. O processo de criação do contexto do certificado e com base no próprio certificado conclui o processo de criação.


Recuperar dados do assinante é mais difícil. Eles contêm uma indicação do certificado e uma lista de parâmetros de assinatura (por exemplo, a data da assinatura). O processo de extração de dados é o seguinte:


Recuperando informações do assinante
 /**<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 já ficou claro na introdução, verificar a validade do certificado é uma das tarefas mais difíceis. É por isso que a biblioteca possui muitos métodos para implementar cada um dos itens individualmente. Portanto, para simplificar, recorremos às fontes .Net do método X509Certificate2.Verify () e as tomamos como base.


A verificação consiste em dois estágios:
  1. formar uma cadeia de certificados até a raiz;
  2. verifique cada um dos certificados nele (revogação, horário etc.);

Essa verificação deve ser realizada antes da assinatura e criptografia na data atual e no momento da verificação da assinatura na data da assinatura. O próprio método de verificação é pequeno:


Verificação 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); } } 

Primeiro, uma cadeia é formada usando o método BuildChain e, em seguida, é verificada. Durante a formação da cadeia, a estrutura dos parâmetros, a data da verificação e os sinalizadores de verificação são formados:


 /**<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 .


Conclusão


:


  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 .


Referências


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


All Articles