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

时间:2014-12-29 11:15:57

标签: c# cryptography digital-signature

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

问题是我找不到将证书链放到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;
}

有没有办法让它在没有将证书上传到本地商店的情况下工作?我应该使用任何替代签名者吗?

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

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

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

  • 这不是我想做的。

3 个答案:

答案 0 :(得分:3)

使用BouncyCastle for .NET

进行CMS签名示例

您可以使用.NET的BouncyCastle加密库,其中包含自己的X509证书和CMS签名机制。 Web上的很多示例和文档都是针对Java的,因为BouncyCastle首先是Java库。我已使用the answer to this Stackoverflow question作为证书和密钥加载的起点,并添加了CMS签名。您可能需要调整参数以产生您的用例所需的结果。

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

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<X509Certificate>();
    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<string>().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;

成功签名

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

顺便说一句,在您的示例中,您使用X509Certificate作为链中的证书数组,但将它们传递给X509Certificate2Collection(注意&#34; 2&#34;在那里)。 X509Certificate2来自X509Certificate,但如果它实际上不是您放入其中一个集合中的X509Certificate2,那么如果某些内容在集合上进行迭代,您将收到强制转换错误(您不幸的是,在添加错误类型的证书时不会收到错误,因为X509Certificate2Collection也派生自X509CertificateCollection并继承其添加方法。

答案 1 :(得分:3)

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

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

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

public void SignWithBouncyCastle(Collection<X509Certificate2> 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);
}

答案 2 :(得分:2)

要说清楚,我是没有安全或加密专家..但据我所知,接收者能够验证签名,您用于签名的证书链中的根证书,必须已经成为接收方的受信任的根

如果接收方的商店中已经没有根证书,并且已将其标记为受信任的根...那么您对数据的签名方式并不重要......它将无法在接收方端验证。这是设计的。

Chain of trust

了解详情

因此,我看到的唯一真正解决您问题的方法是确保根证书在两端都配置为受信任的根目录...通常由Certificate Authority完成。

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

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

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