将CryptoPro连接到Mono

与向Linux的过渡有关,有必要将我们用C#编写的服务器系统之一移植到Mono。 该系统可使用增强的数字签名,因此我们面临的任务之一就是以单声道方式测试CryptoPro的GOST证书的性能。 CryptoPro本身已经在Linux上实现了CSP ,但是使用它的第一次尝试表明,本机Mono密码学类(类似于基本的.Net-X509Store,X509Certificate2等)不仅不能用于来宾密钥,它们甚至不要在他们的保险库中看到他们。 因此,必须通过CryptoPro库直接连接加密技术。



证书安装


在实施代码之前,必须安装证书并确保它可以正常工作。


证书安装

CryptoPro CSP组件版本3.9安装在Centos 7的/ opt / cprocsp文件夹中。 为了避免具有相同名称的mono和CryptoPro实用程序(例如certmgr)之间发生冲突,未将文件夹的路径输入到环境变量中,并且在完整路径中调用了所有实用程序。


首先,我们定义一个读者列表:
/opt/cprocsp/bin/amd64/csptest -enum -info -type PP_ENUMREADERS | iconv -f cp1251


如果列表中磁盘上的文件夹(HDIMAGE)中没有读取器,则将其放置:
/opt/cprocsp/sbin/amd64/cpconfig -hardware reader -add HDIMAGE store


然后,您可以通过使用键创建新的容器来创建格式为'\\。\ HDIMAGE \ {container name}'的容器:
/opt/cprocsp/bin/amd64/csptest -keyset -provtype 75 -newkeyset -cont '\\.\HDIMAGE\test'


或通过创建/ var / opt / cprocsp / keys / root / {容器名称} .000文件夹,其中包含标准的CryptoPro容器文件集(* .key,* .mask等)。


之后,可以将来自容器的证书安装在证书存储中:
/opt/cprocsp/bin/amd64/certmgr -inst mMy -cont '\\.\HDIMAGE\{ }'


可以使用以下命令查看已安装的证书:
/opt/cprocsp/bin/amd64/certmgr -list mMy


证书的操作可以通过以下方式验证:
/opt/cprocsp/bin/amd64/cryptcp – sign -norev -thumbprint {} {} { }
/opt/cprocsp/bin/amd64/cryptcp – verify -norev { }


如果证书一切正常,则可以继续执行代码中的连接。



代码连接


尽管有移植到Linux的过程,该系统仍应在Windows环境中继续运行,因此从表面上看,必须通过“ byte [] SignData(byte [] _arData,X509Certificate2 _pCert)”形式的通用方法来进行加密工作,该方法应与在Linux和Windows上。


事实证明,对密码库方法的分析是成功的,因为CryptoPro实现了“ libcapi20.so”库,该库完全模仿了标准的加密库Windows-“ crypt32.dll”和“ advapi32.dll”。 当然,也许不是全部,但那里提供了所有使用密码的必要方法,几乎​​所有方法都可以使用。


因此,我们形成两个静态类“ WCryptoAPI”和“ LCryptoAPI”,它们各自将导入必要的方法集,如下所示:


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

每种方法的连接语法既可以独立创建,也可以使用pinvoke网站,也可以从.Net源代码( CAPISafe类)进行复制。 在同一模块中,您可以绘制与加密相关的常数和结构,当使用外部库时,它们的存在总是使生活变得更轻松。


然后,我们形成静态类“ UCryptoAPI”,根据系统的不同,它将调用以下两个类之一的方法:


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

因此,使用UCryptoAPI类的方法,您可以为两个系统实现几乎统一的代码。


证书搜寻


加密工作通常从证书搜索开始,为此,在crypt32.dll中有两个CertOpenStore方法(打开指定的证书存储)和一个简单的CertOpenSystemStore(打开用户的个人证书)。 由于使用证书不仅限于个人用户证书,因此我们连接了第一个证书:


证书搜寻
 /**<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); } } 


搜索分几个阶段进行:
  1. 储存开口;
  2. 我们正在寻找的数据结构的形成;
  3. 证书搜索;
  4. 如果需要,则进行证书验证(在单独的部分中进行说明);
  5. 关闭存储库并从第2点开始释放结构(由于到处都有非托管.Net内存的工作,我们将无法对其进行清理);

搜索证书时有一些细微之处。


Linux上的CryptoPro使用ANSI字符串,而Windows上的CryptoPro使用UTF8,因此:


  1. 当连接Linux中打开存储的方法时,有必要为存储代码参数明确指示封送处理的类型[In,MarshalAs(UnmanagedType.LPStr)];
  2. 传递搜索字符串(例如,主题名称)时,必须将其转换为一组具有不同编码的字节;
  3. 对于Windows中所有因字符串类型而异的加密常量(例如CERT_FIND_SUBJECT_STR_A和CERT_FIND_SUBJECT_STR_W),必须选择* _W,在Linux中选择* _A;

MapX509StoreFlags方法可以直接从Microsoft来源获取而无需更改,它只是基于.Net标志形成最终的掩码。


执行搜索的值取决于搜索的类型(与MSDN 一起检查CertFindCertificateInStore ),该示例显示两个最常用的选项-字符串格式(名称Subject,Issuer等)和二进制格式(指纹,序列号)。


在Windows和Linux上从IntPtr创建证书的过程非常不同。 Windows将以一种简单的方式创建证书:

  new X509Certificate2(hCert); 


在Linux上,您必须分两步创建证书:

 X509Certificate2(new X509Certificate(hCert)); 


将来,我们需要访问hCert才能工作,并且必须将其存储在证书对象中。 在Windows上,以后可以从Handle属性中检索它,但是Linux会将hCert链接后面的CERT_CONTEXT结构转换为x509_st(OpenSSL)结构的链接,并将其注册到Handle中。 因此,值得从X509Certificate2(示例中为ISDP_X509Cert)创建继承程序,该继承程序会将hCert存储在两个系统的单独字段中。


不要忘记这是到非托管内存区域的链接,在工作结束后必须将其释放。 因为 .NET 4.5中的X509Certificate2不是一次性的-使用CertFreeCertificateContext方法进行的清洁必须在析构函数中执行。


签名形成


使用GOST证书时,几乎总是使用带有一个签名者的断开签名。 为了创建这样的签名,需要一个相当简单的代码块:


签名形成
 /**<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); } } 

在该方法的工作期间,将形成带有参数的结构,并调用签名方法。 参数结构可以使您将证书保存在签名中以形成完整的链(cMsgCert和rgpMsgCert字段,第一个存储证书的数量,第二个存储这些证书的结构的链接列表)。


签名方法可以接收一个或多个文档,以使用一个签名同时进行签名。 顺便说一下,这并不违反联邦法律63,并且非常方便,因为用户不太可能会对需要多次单击“签名”按钮感到高兴。


这种方法的主要缺点是,它不能在两次调用模式下工作,这对于大多数处理大型内存块的库方法来说是典型的(第一个为null -返回所需的缓冲区长度,第二个填充缓冲区)。 因此,有必要创建一个大的缓冲区,然后将其缩短到其实际长度。


唯一严重的问题是搜索签名时使用的哈希算法(摘要)的OID-以显式形式,它不在证书中(仅签名本身的算法)。 如果在Windows上可以用空字符串指定它-它会自动启动,但是如果算法不同,Linux会拒绝签名。


但是有一个技巧-在有关签名算法(结构CRYPT_OID_INFO)的信息中,签名OID存储在pszOID中,而哈希算法标识符存储在Algid中。 将Algid转换为OID已经是一个技术问题:


获取哈希算法的OID
 /**<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); } } 

仔细阅读代码后,您可能会惊讶地发现以简单的方式获得算法标识符(CertOIDToAlgId),而其中的Oid却很复杂(CryptFindOIDInfo)。 假设同时使用复杂方法或同时使用这两种方法是合乎逻辑的,并且在Linux中,这两个选项都可以成功工作。 但是,在Windows上,获取标识符和仅获取OID的困难选择是不稳定的,因此,这种奇怪的混合方式将是一种稳定的解决方案。


签名验证


签名验证分两个阶段进行,首先对签名本身进行验证,然后对生成签名的证书进行验证(链,签名日期等)。
在签名时,还必须指定要签名的数据集,签名参数和签名本身:


签名验证
 /**<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(); } } 

为了方便起见,使用参数形成结构的过程已移至单独的方法(GetStdSignVerifyPar)。 之后,将检查签名本身并提取第一个签名者(虽然有必要提取所有签名,但是包含多个签名者的签名仍然很奇怪)。


提取签署者的证书后,我们会将其转换为我们的类并进行验证(如果在方法参数中指定)。 为了进行验证,将使用第一个签名者的签名日期(请参阅有关从签名中提取信息的部分以及有关检查证书的部分)。


提取签名信息


密码系统通常需要签名的印刷表示。 在每种情况下,它都是不同的,因此最好创建一类有关签名的信息,其中将以方便使用的形式包含信息,并在其帮助下提供印刷的演示文稿。 在.Net中有一个此类-SignedCms,但是,它的带有CritiPro pro签名的单声道类似物首先无法使用,其次它包含密封的修饰符,其次几乎所有属性都被写保护,因此您必须创建自己的类似物。


签名本身包含两个主要元素-证书列表和签名者列表。 证书列表可能为空,或者可能包含用于验证的所有证书,包括完整的链。 签名者列表指示实际签名的数量。 它们之间的通信由序列号和发行者(发行者)进行。 从理论上讲,在一个签名中,可以有两个来自不同发布者的证书,且具有相同的序列号,但实际上,只能通过序列号来忽略和搜索。


读取签名如下:


提取签名信息
 /**<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); } } 

签名分为几个阶段进行解析,首先是形成消息结构(CryptMsgOpenToDecode),然后将真实的签名数据(CryptMsgUpdate)输入到其中。 仍然需要验证这是真实的签名,并首先获取证书列表,然后再获取签名者列表。 证书列表按顺序检索:


获取证书列表
 /**<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; } 

首先,从CMSG_CERT_COUNT_PARAM参数确定证书的数量,然后顺序检索有关每个证书的信息。 创建证书上下文并基于证书本身的过程完成了创建过程。


检索签名者数据更加困难。 它们包含证书指示和签名参数列表(例如,签名日期)。 数据提取过程如下:


检索签名者信息
 /**<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);

, .


从介绍中已经很清楚,检查证书的有效性是最困难的任务之一。这就是为什么图书馆有很多方法可以单独实现每个项目。因此,为简化起见,让我们转到X509Certificate2.Verify()方法的.Net源代码并以此为基础。


验证包括两个阶段:
  1. 从头到尾形成证书链;
  2. 检查其中的每个证书(用于吊销,时间等);

此类验证必须在当前日期进行签名和加密之前,以及在签名日期进行签名验证时进行。验证方法本身很小:


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

首先,使用BuildChain方法形成一个链,然后对其进行检查。在链形成过程中,将形成参数的结构,验证日期和检查标志:


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


结论


:


  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 .


参考文献


  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/zh-CN423163/


All Articles