如何在不导入根证书的情况下验证X509证书?

22

我的程序包含2个根证书,我知道并且信任它们。我必须验证信任中心的证书和由信任中心颁发的“用户”证书,这些证书都源自这2个根证书。

我使用X509Chain类进行验证,但仅当根证书在Windows证书存储中时才能正常工作。

我正在寻找一种方法,在不导入这些根证书的情况下验证证书 - 以某种方式告诉X509Chain类,我确实信任这些根证书,并且应该仅检查链中的证书,而不检查其他证书。

实际代码:

        X509Chain chain = new X509Chain();
        chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
        chain.ChainPolicy.ExtraStore.Add(root); // i do trust this
        chain.ChainPolicy.ExtraStore.Add(trust);
        chain.Build(cert);

编辑:这是一个.NET 2.0 Winforms应用程序。


使用Bouncy Castle C#的PkixCertPathBuilder API怎么样? - Jaime Hablutzel
5个回答

17
我在dotnet/corefx上开了一个问题,他们的回复如下:
如果AllowUnknownCertificateAuthority是唯一设置的标志,则chain.Build()将返回true,如果:
  • 链正确终止于自签名证书(通过ExtraStore或搜索持久存储)
  • 没有证书根据请求的吊销策略无效
  • 所有证书都在(可选的)ApplicationPolicy或CertificatePolicy值下有效
  • 所有证书的NotBefore值都在VerificationTime之前或同时,而所有证书的NotAfter值都在VerificationTime之后或同时。
如果没有指定该标志,则添加了一个附加约束条件:“自签名证书必须在系统上注册为受信任的(例如,在LM\Root存储中)。所以,如果Build()返回true,则知道存在一个时间有效的未吊销链。此时要做的事情是读取chain.ChainElements[chain.ChainElements.Count - 1].Certificate并确定它是否是您信任的证书。我建议将chainRoot.RawData与表示上下文中信任的根证书的byte[]进行比较(即逐字节比较而不是使用拇指印值)。

所以你应该这样做:

X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.ExtraStore.Add(root);
chain.ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
var isValid = chain.Build(cert);

var chainRoot = chain.ChainElements[chain.ChainElements.Count - 1].Certificate;
isValid = isValid && chainRoot.RawData.SequenceEqual(root.RawData);

第一个要点似乎不是真的。我已经使用一个未知颁发者的证书进行了测试,它仍然返回true。但是链状态仍然正确地识别了问题。 - Mikkel K.
显然这不是真的。回应David的问题的人在一个新的问题中进行了澄清,AllowUnknownCertificateAuthority也可以抑制部分链问题。 - Shane Spoor

10

编辑

多年来,我们发现原始的X509Chain解决方案存在一些问题,由于在某些边缘情况下X509Chain执行不正确的行为,因此我不再推荐使用X509Chain解决此问题。我们的产品已经转向使用Bouncy Castle来进行所有证书链验证,并且它已经经过了我们所有的测试并始终按预期工作。

我们新解决方案的基础可以在这里找到:在C#中使用BouncyCastle构建证书链

我已删除原始答案,以免有人使用不安全的解决方案。


即使用于签署终端实体证书的根证书不同,这似乎也会返回“不受信任的根”。这并不是真正想要的行为。 - Sami J. Lehtinen
1
我同意你关于AllowUnknownCertificateAuthority的评论,当我将自己的CA证书添加到ExtraStore时,我得出了相同的结论。 - tul
实际上,即使没有添加任何CA证书,也会返回X509ChainStatusFlags.UntrustedRoot,这使得答案不可接受。 - volkovs

1
获取此功能的方法是编写自定义验证。
如果您在WCF上下文中,可以通过子类化System.IdentityModel.Selectors.X509CertificateValidator并在web.config中的serviceBehavior对象上指定自定义验证来完成此操作:
<serviceBehaviors>
    <behavior name="IdentityService">
      <serviceMetadata httpGetEnabled="true" />
      <serviceDebug includeExceptionDetailInFaults="true" />
      <serviceCredentials>
        <clientCertificate>
          <authentication customCertificateValidatorType="SSOUtilities.MatchInstalledCertificateCertificateValidator, SSOUtilities"
            certificateValidationMode="Custom" />
        </clientCertificate>
        <serviceCertificate findValue="CN=SSO ApplicationManagement"
          storeLocation="LocalMachine" storeName="My" />
      </serviceCredentials>
    </behavior>

但是,如果你只是想找到一种接受来自另一个主机的SSL证书的方法,你可以修改web.config文件中的system.net设置:

以下是一个X509CertificateValidator的示例,它测试客户端证书是否存在于LocalMachine/Personal存储中。(这不是您需要的,但可能作为一个示例很有用。)
using System.Collections.Generic;
using System.Linq;
using System.Security;
using System.Security.Cryptography.X509Certificates;

/// <summary>
/// This class can be injected into the WCF validation 
/// mechanism to create more strict certificate validation
/// based on the certificates common name. 
/// </summary>
public class MatchInstalledCertificateCertificateValidator
    : System.IdentityModel.Selectors.X509CertificateValidator
{
    /// <summary>
    /// Initializes a new instance of the MatchInstalledCertificateCertificateValidator class.
    /// </summary>
    public MatchInstalledCertificateCertificateValidator()
    {
    }

    /// <summary>
    /// Validates the certificate. Throws SecurityException if the certificate
    /// does not validate correctly.
    /// </summary>
    /// <param name="certificateToValidate">Certificate to validate</param>
    public override void Validate(X509Certificate2 certificateToValidate)
    {
        var log = SSOLog.GetLogger(this.GetType());
        log.Debug("Validating certificate: "
            + certificateToValidate.SubjectName.Name
            + " (" + certificateToValidate.Thumbprint + ")");

        if (!GetAcceptedCertificates().Where(cert => certificateToValidate.Thumbprint == cert.Thumbprint).Any())
        {
            log.Info(string.Format("Rejecting certificate: {0}, ({1})", certificateToValidate.SubjectName.Name, certificateToValidate.Thumbprint));
            throw new SecurityException("The certificate " + certificateToValidate
                + " with thumprint " + certificateToValidate.Thumbprint
                + " was not found in the certificate store");
        }

        log.Info(string.Format("Accepting certificate: {0}, ({1})", certificateToValidate.SubjectName.Name, certificateToValidate.Thumbprint));
    }

    /// <summary>
    /// Returns all accepted certificates which is the certificates present in 
    /// the LocalMachine/Personal store.
    /// </summary>
    /// <returns>A set of certificates considered valid by the validator</returns>
    private IEnumerable<X509Certificate2> GetAcceptedCertificates()
    {
        X509Store k = new X509Store(StoreName.My, StoreLocation.LocalMachine);

        try
        {
            k.Open(OpenFlags.ReadOnly | OpenFlags.OpenExistingOnly);
            foreach (var cert in k.Certificates)
            {
                yield return cert;
            }
        }
        finally
        {
            k.Close();
        }
    }
}

我已经编辑了问题,它是一个.NET 2.0 Winforms应用程序。 - RainerM

1

如果您知道哪些证书可以作为证书检查的根和中间证书,您可以在X509Chain对象的ChainPolicy.ExtraStore集合中加载根和中间证书的公钥。

我的任务还包括编写一个Windows窗体应用程序来安装证书,但仅当它是依赖于我国政府已知的“国家根证书”发行的证书时。也只有一定数量的CA被允许颁发证书以验证连接到国家网络服务,因此我有一组有限的证书可以在链中,并且可能在目标计算机上缺失。我在应用程序的子目录“cert”中收集了所有CA和政府根证书的公钥: chain certificates

在Visual Studio中,我将cert目录添加到解决方案中,并将该目录中的所有文件标记为嵌入式资源。这使我能够在我的C#库代码中枚举“受信任”的证书集合,以构建链以检查证书,即使未安装发行者证书。我为此编写了一个X509Chain的包装类:

private class X509TestChain : X509Chain, IDisposable
{
  public X509TestChain(X509Certificate2 oCert)
    : base(false)
  {
    try
    {
      ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
      ChainPolicy.VerificationFlags = X509VerificationFlags.AllowUnknownCertificateAuthority;
      if (!Build(oCert) || (ChainElements.Count <= 1))
      {
        Trace.WriteLine("X509Chain.Build failed with installed certificates.");
        Assembly asmExe = System.Reflection.Assembly.GetEntryAssembly();
        if (asmExe != null)
        {
          string[] asResources = asmExe.GetManifestResourceNames();
          foreach (string sResource in asResources)
          {
            if (sResource.IndexOf(".cert.") >= 0)
            {
              try
              {
                using (Stream str = asmExe.GetManifestResourceStream(sResource))
                using (BinaryReader br = new BinaryReader(str))
                {
                  byte[] abResCert = new byte[str.Length];
                  br.Read(abResCert, 0, abResCert.Length);
                  X509Certificate2 oResCert = new X509Certificate2(abResCert);
                  Trace.WriteLine("Adding extra certificate: " + oResCert.Subject);
                  ChainPolicy.ExtraStore.Add(oResCert);
                }
              }
              catch (Exception ex)
              {
                Trace.Write(ex);
              }
            }
          }
        }
        if (Build(oCert) && (ChainElements.Count > 1))
          Trace.WriteLine("X509Chain.Build succeeded with extra certificates.");
        else
          Trace.WriteLine("X509Chain.Build still fails with extra certificates.");
      }
    }
    catch (Exception ex)
    {
      Trace.Write(ex);
    }
  }

  public void Dispose()
  {
    try
    {
      Trace.WriteLine(string.Format("Dispose: remove {0} extra certificates.", ChainPolicy.ExtraStore.Count));
      ChainPolicy.ExtraStore.Clear();
    }
    catch (Exception ex)
    {
      Trace.Write(ex);
    }
  }
}

在调用函数中,我现在可以成功地检查未知证书是否来自国家根证书。
    bool bChainOK = false;
    using (X509TestChain oChain = new X509TestChain(oCert))
    {
      if ((oChain.ChainElements.Count > 0)
        && IsPKIOverheidRootCert(oChain.ChainElements[oChain.ChainElements.Count - 1].Certificate))
        bChainOK = true;
      if (!bChainOK)
      {
        TraceChain(oChain);
        sMessage = "Root certificate not present or not PKI Overheid (Staat der Nederlanden)";
        return false;
      }
    }
    return true;

补充说明一下:为了检查根证书(通常已安装,因为它包含在Windows更新中,但理论上也可能缺失),我会将友好名称和指纹与公布的值进行比较。
private static bool IsPKIOverheidRootCert(X509Certificate2 oCert)
{
  if (oCert != null)
  {
    string sFriendlyName = oCert.FriendlyName;
    if ((sFriendlyName.IndexOf("Staat der Nederlanden") >= 0)
      && (sFriendlyName.IndexOf(" Root CA") >= 0))
    {
      switch (oCert.Thumbprint)
      {
        case "101DFA3FD50BCBBB9BB5600C1955A41AF4733A04": // Staat der Nederlanden Root CA - G1
        case "59AF82799186C7B47507CBCF035746EB04DDB716": // Staat der Nederlanden Root CA - G2
        case "76E27EC14FDB82C1C0A675B505BE3D29B4EDDBBB": // Staat der Nederlanden EV Root CA
          return true;
      }
    }
  }
  return false;
}

我不确定这个检查是否安全,但在我的情况下,Windows Forms应用程序的操作员非常确定可以访问有效证书进行安装。该软件的目标仅是过滤证书列表,以帮助他仅在计算机的机器存储中安装正确的证书(该软件还安装中间和根证书的公钥,以确保Web服务客户端的运行时行为正确)。


1
我刚刚在@Tristan的代码基础上进行了扩展,并加入了一个检查,用于确保根证书是ExtraStore中添加的证书之一。
X509Chain chain = new X509Chain();
chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
chain.ChainPolicy.ExtraStore.Add(root);
chain.Build(cert);
if (chain.ChainStatus.Length == 1 &&
    chain.ChainStatus.First().Status == X509ChainStatusFlags.UntrustedRoot &&
    chain.ChainPolicy.ExtraStore.Contains(chain.ChainElements[chain.ChainElements.Count - 1].Certificate))
{
    // chain is valid, thus cert signed by root certificate 
    // and we expect that root is untrusted which the status flag tells us
    // but we check that it is a known certificate
}
else
{
    // not valid for one or more reasons
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接