CMS在.NET中签名,证书链不在本地可信证书库中

我有存储在网络上的X509证书。 我可以从远程Windows证书库中读取链。 我需要签署一些数据并在签名中包含链,以便以后validation它。

问题是我找不到将证书链放到CsmSigner的方法。 我已经读过它从构造函数参数获取证书并尝试使用X509Chain.Build构建一个链。 它忽略了证书列表值并且(显然)失败,因为在本地Windows证书库中找不到证书。

请在下面找到我的测试代码(仅当证书本地保存到Windows证书库时才有效)

protected byte[] SignWithSystem(byte[] data, X509Certificate2 cert, X509Certificate[] chain) { ContentInfo contentInfo = new ContentInfo(data); SignedCms signedCms = new SignedCms(contentInfo, true); CmsSigner cmsSigner = new CmsSigner(cert); cmsSigner.DigestAlgorithm = new Oid("2.16.840.1.101.3.4.2.1"); //sha256 cmsSigner.IncludeOption = X509IncludeOption.WholeChain; if (chain != null) { //adding cert chain to signer cmsSigner.Certificates.AddRange(chain); signedCms.Certificates.AddRange(chain); } signedCms.ComputeSignature(cmsSigner); //fails here with System.Security.Cryptography.CryptographicException : A certificate chain could not be built to a trusted root authority. byte[] signedPkcs = signedCms.Encode(); return signedPkcs; } 

有没有办法让它无需上传证书到本地商店? 我应该使用任何替代签名者吗?

我可以尝试将证书上传到商店,但问题是这样

  • 我必须添加和删除证书(必须授予权限)

  • 有几个进程应用签名,因此必须添加跨进程同步。

  • 这不是我想做的。

示例CMS使用BouncyCastle for .NET进行签名

您可以使用.NET的BouncyCastle加密库,它包含自己的X509证书和CMS签名机制。 Web上的很多示例和文档都是针对Java的,因为BouncyCastle首先是Java库。 我已将此Stackoverflow问题的答案用作证书和密钥加载的起点,并添加了CMS签名。 您可能必须调整参数以生成您的用例所需的结果。

我已经使签名function看起来与您的相似,但请注意私钥现在是一个单独的参数。

 using System; using System.Collections.Generic; using System.IO; using System.Linq; using Org.BouncyCastle.Cms; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.X509; using Org.BouncyCastle.Crypto; using Org.BouncyCastle.X509.Store; class Program { protected static byte[] SignWithSystem(byte[] data, AsymmetricKeyParameter key, X509Certificate cert, X509Certificate[] chain) { var generator = new CmsSignedDataGenerator(); // Add signing key generator.AddSigner( key, cert, "2.16.840.1.101.3.4.2.1"); // SHA256 digest ID var storeCerts = new List(); storeCerts.Add(cert); // NOTE: Adding end certificate too storeCerts.AddRange(chain); // I'm assuming the chain collection doesn't contain the end certificate already // Construct a store from the collection of certificates and add to generator var storeParams = new X509CollectionStoreParameters(storeCerts); var certStore = X509StoreFactory.Create("CERTIFICATE/COLLECTION", storeParams); generator.AddCertificates(certStore); // Generate the signature var signedData = generator.Generate( new CmsProcessableByteArray(data), false); // encapsulate = false for detached signature return signedData.GetEncoded(); } static void Main(string[] args) { try { // Load end certificate and signing key AsymmetricKeyParameter key; var signerCert = ReadCertFromFile(@"C:\Temp\David.p12", "pin", out key); // Read CA cert var caCert = ReadCertFromFile(@"C:\Temp\CA.cer"); var certChain = new X509Certificate[] { caCert }; var result = SignWithSystem( Guid.NewGuid().ToByteArray(), // Any old data for sake of example key, signerCert, certChain); File.WriteAllBytes(@"C:\Temp\Signature.data", result); } catch (Exception ex) { Console.WriteLine("Failed : " + ex.ToString()); Console.ReadKey(); } } public static X509Certificate ReadCertFromFile(string strCertificatePath) { // Create file stream object to read certificate using (var keyStream = new FileStream(strCertificatePath, FileMode.Open, FileAccess.Read)) { var parser = new X509CertificateParser(); return parser.ReadCertificate(keyStream); } } // This reads a certificate from a file. // Thanks to: http://blog.softwarecodehelp.com/2009/06/23/CodeForRetrievePublicKeyFromCertificateAndEncryptUsingCertificatePublicKeyForBothJavaC.aspx public static X509Certificate ReadCertFromFile(string strCertificatePath, string strCertificatePassword, out AsymmetricKeyParameter key) { key = null; // Create file stream object to read certificate using (var keyStream = new FileStream(strCertificatePath, FileMode.Open, FileAccess.Read)) { // Read certificate using BouncyCastle component var inputKeyStore = new Pkcs12Store(); inputKeyStore.Load(keyStream, strCertificatePassword.ToCharArray()); var keyAlias = inputKeyStore.Aliases.Cast().FirstOrDefault(n => inputKeyStore.IsKeyEntry(n)); // Read Key from Aliases if (keyAlias == null) throw new NotImplementedException("Alias"); key = inputKeyStore.GetKey(keyAlias).Key; //Read certificate into 509 format return (X509Certificate)inputKeyStore.GetCertificate(keyAlias).Certificate; } } } 

.NET CMS(从签名中省略了其余链的快速修复)

我可以使用其根不在受信任证书存储中的证书重现您的问题,并确认将证书链添加到cmsSigner / signedCms Certificates集合并不能避免无法将A certificate chain could not be built to a trusted root authority错误。

您可以通过设置cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;来成功cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly;

但是,如果这样做,您将无法获得签名中的其余链。 这可能不是你想要的。

顺便说X509Certificate ,在您的示例中,您使用X509Certificate来获取链中的证书数组,但是将它们传递给X509Certificate2Collection (请注意其中的“2”)。 X509Certificate2派生自X509Certificate ,但如果它实际上不是你放入其中一个集合的X509Certificate2 ,那么如果某些东西在集合上迭代,你将得到一个强制转换错误(在添加错误类型的证书时你没有收到错误遗憾的是,因为X509Certificate2Collection还派生自X509CertificateCollection并inheritance其添加方法)。

添加使用BouncyCastle创建分离的PKCS7签名的示例代码(由于软件性)而没有证书存储。

它使用.net X509Certificate2实例作为输入参数。 集合中的第一个证书必须与私钥链接以签署数据。

此外,我还要注意,无法使用.net X509Certificate2.Private密钥属性从远程Windows证书库读取与证书关联的私钥。 默认情况下,私钥未使用X509Store(@“\ remotemachine \ MY”,StoreLocation.LocalMachine)加载证书,并且在本地计算机上访问X509Certificate2.PrivateKey属性时,它将失败,并显示错误“密钥集不存在”。

 public void SignWithBouncyCastle(Collection netCertificates) { // first cert have to be linked with private key var signCert = netCertificates[0]; var Cert = Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(signCert); var data = Encoding.ASCII.GetBytes(Cert.SubjectDN.ToString()); var bcCertificates = netCertificates.Select(_ => Org.BouncyCastle.Security.DotNetUtilities.FromX509Certificate(_)).ToList(); var x509Certs = X509StoreFactory.Create("Certificate/Collection", new X509CollectionStoreParameters(bcCertificates)); var msg = new CmsProcessableByteArray(data); var gen = new CmsSignedDataGenerator(); var privateKey = Org.BouncyCastle.Security.DotNetUtilities.GetKeyPair(signCert.PrivateKey).Private; gen.AddSigner(privateKey, Cert, CmsSignedDataGenerator.DigestSha256); gen.AddCertificates(x509Certs); var signature = gen.Generate(msg, false).GetEncoded(); Trace.TraceInformation("signed"); CheckSignature(data, signature); Trace.TraceInformation("checked"); try { CheckSignature(new byte[100], signature); } catch (CryptographicException cex) { Trace.TraceInformation("signature was checked for modified data '{0}'", cex.Message); } } void CheckSignature(byte[] data, byte[] signature) { var ci = new ContentInfo(data); SignedCms signedCms = new SignedCms(ci, true); signedCms.Decode(signature); foreach (X509Certificate cert in signedCms.Certificates) Trace.TraceInformation("certificate found {0}", cert.Subject); signedCms.CheckSignature(true); } 

为了清楚起见,我不是安全或加密专家..但根据我的知识,对于能够validation签名的接收者,您用于签名的证书链中的根证书必须已经是接收者的受信任根

如果接收方在其商店中已经没有根证书,并且标记为受信任的根…则无论您如何对数据进行签名……它将在接收方端validation失败。 这是设计的。

在信任链中查看更多信息

因此,我看到的唯一真正的问题解决方案是确保根证书在两端都配置为受信任的根目录…通常由证书颁发机构完成 。

企业应用程序场景 – 通常在企业中,IT部门中的某些组(可以访问域中的所有计算机 – 如域管理员)将通过确保域中的每台计算机都具有此组拥有的根证书来启用此方案,每台计算机都作为受信任的根,并且企业中的应用程序开发人员通常会请求新的证书与其应用程序一起使用,该证书具有返回到已经分发到域中所有计算机的根证书的信任链。

在贵公司找到该组的联系人,让他们签发可用于签名的证书。

Internet应用程序方案 – 已建立的证书颁发机构拥有其根证书,并与OS供应商合作以确保其根证书位于受信任存储中,因为操作系统供应商将操作系统发送给其客户。 (使用盗版操作系统的一个原因可能是有害的。它不仅仅是病毒/恶意软件……)。 这就是为什么当您使用VeriSign颁发的证书对数据进行签名时,签名可以由世界上大多数其他机器validation。