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

ole*_*ksa 9 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;
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

  • 这不是我想做的.

sof*_*ess 7

使用 BouncyCastle for .NET 的 CMS 签名示例

您可以使用适用于 .NET的BouncyCastle加密库,其中包含自己的 X509 证书和 CMS 签名机制。网络上的许多示例和文档都是针对 Java 的,因为 BouncyCastle 首先是一个 Java 库。我已经使用这个 Stackoverflow 问题的答案作为证书和密钥加载的起点,并添加了 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;
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

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