CMS 使用不在本地受信任证书存储中的证书链登录 .NET

时间:2023-03-26
本文介绍了CMS 使用不在本地受信任证书存储中的证书链登录 .NET的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着跟版网的小编来一起学习吧!

问题描述

我有存储在网络上的 X509 证书.我可以从远程 Windows 证书存储中读取链.我需要对一些数据进行签名并在签名中包含链,以便以后对其进行验证.

I have X509 certificates that are stored on the network. I can read the chain from remote windows certificate store. I need to sign some data and include chain to the signature to make it possible to validate it later.

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

The problem is that I can't find a way to put certificate chain to the CsmSigner. I have read that it takes certificate from constructor parameter and tries to build a chain with X509Chain.Build. It ignores Certificates list values and fails (obviously) because no certificate can be found in the local Windows cert store.

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

Please find below my test code (that works only if certificates were saved locally to the windows cert store)

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

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

Is there any way to make it work without uploading certificates to the local store? Should I use any alternative signer?

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

I can try to upload certificates to the store but the problems are that

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

  • I have to add and remove certificates (permissions have to be granted)

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

There are several processes that applies signature so cross-process synchronization have to be added.

这不是我想做的.

推荐答案

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

您可以使用 .NET 的 BouncyCastle 加密库,其中包含自己的 X509 证书和CMS 签约机制.Web 上的许多示例和文档都是针对 Java 的,因为 BouncyCastle 首先是一个 Java 库.我使用 这个 Stackoverflow 问题的答案 作为证书和密钥加载的起点,并添加了 CMS 签名.您可能需要调整参数才能为您的用例生成所需的结果.

Example CMS Signing with BouncyCastle for .NET

You could use the BouncyCastle crypto library for .NET, which contains its own X509 certificate and CMS signing machinery. A lot of the examples and documentation on the web are for Java, as BouncyCastle was a Java library first. I've used the answer to this Stackoverflow question as a starting point for the certificate and key loading, and added the CMS signing. You may have to tweak parameters to produce the results you want for your use case.

我已经让签名函数看起来和你的差不多,但请注意,私钥现在是一个单独的参数.

I've made the signing function look approximately like yours, but note the private key is a separate parameter now.

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:TempDavid.p12", "pin", out key);

      // Read CA cert
      var caCert = ReadCertFromFile(@"C:TempCA.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:TempSignature.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 证书 集合不能避免 A certificate chain could not be built to a trust root authority 错误.

.NET CMS (Quick-fix with rest of chain omitted from signature)

I can reproduce your problem with a certificate whose root is not in the trusted certificate store, and confirm that adding the certificate chain to the cmsSigner/signedCms Certificates collection does not avoid the A certificate chain could not be built to a trusted root authority error.

设置cmsSigner.IncludeOption = X509IncludeOption.EndCertOnly; 即可成功签名;

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

However, if you do this, you will not get the rest of the chain in the signature. This probably isn't what you want.

顺便说一句,在您的示例中,您将 X509Certificate 用于链中的证书数组,但将它们传递给 X509Certificate2Collection (注意2"在那里).X509Certificate2 派生自 X509Certificate,但如果它实际上不是您放入其中一个集合的 X509Certificate2,如果某些东西会遍历集合(不幸的是,添加错误类型的证书时不会出错,因为 X509Certificate2Collection 也派生自 X509CertificateCollection 并继承了它的 add 方法).

As an aside, in your example you are using X509Certificate for the array of certificates in the chain, but passing them to an X509Certificate2Collection (note the "2" in there). X509Certificate2 derives from X509Certificate, but if its not actually an X509Certificate2 that you put in one of those collections, you'll get a cast error if something iterates over the collection (you don't get an error when adding a certificate of the wrong type unfortunately, because X509Certificate2Collection also derives from X509CertificateCollection and inherits its add methods).

这篇关于CMS 使用不在本地受信任证书存储中的证书链登录 .NET的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!

上一篇:BouncyCastle PrivateKey 到 X509Certificate2 PrivateKey 下一篇:C# - 如何计算特定哈希算法的 ASN.1 DER 编码?

相关文章