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

发布于 2024-11-08 19:25:21 字数 552 浏览 0 评论 0原文

我的程序包含 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 应用程序。

My program contains 2 root certs I know and trust.
I have to verify certs of trustcenters and "user" certs issued by the trustcenters which all originate from these 2 root certs.

I use X509Chain class to verify but that only works if the root cert is in the windows certificate store.

I'm looking for a way to verify the certs without importing theeses root certs - somehow tell the X509Chain class that I do trust this root certs and it should check just the certs in the chain and nothing else.

Actual code:

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

Edit: It's a .NET 2.0 Winforms application.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

萝莉病 2024-11-15 19:25:21

我在 dotnet/corefx 上打开了一个问题,他们回复如下:

如果AllowUnknownCertificateAuthority是唯一设置的标志,则
chain.Build() 将返回 true 如果

  • 链在自签名证书中正确终止(通过
    ExtraStore,或搜索持久存储)

  • 根据请求的撤销,没有任何证书无效
    政策

  • 所有证书在(可选)下均有效
    ApplicationPolicy 或CertificatePolicy 值

  • 所有证书的 NotBefore 值均位于或之前
    VerificationTime 和所有证书的 NotAfter 值是
    (在-或-)验证时间之后。

如果未指定该标志,则会添加附加约束:

自签名证书必须在系统上注册为可信证书(例如在 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);

I opened an Issue on dotnet/corefx and they replied as follows:

If AllowUnknownCertificateAuthority is the only flag set then
chain.Build() will return true if

  • The chain correctly terminated in a self-signed certificate (via
    ExtraStore, or searched persisted stores)

  • None of the certificates are invalid per the requested revocation
    policy

  • All of the certificates are valid under the (optional)
    ApplicationPolicy or CertificatePolicy values

  • All of the certificates' NotBefore values are at-or-before
    VerificationTime and all of the certificates' NotAfter values are
    (at-or-)after VerificationTime.

If that flag is not specified then an additional constraint is added:

The self-signed certificate must be registered as trusted on the system (e.g. in the LM\Root store).

So, Build() returns true, you know that a time-valid non-revoked chain
is present. The thing to do at that point is read
chain.ChainElements[chain.ChainElements.Count - 1].Certificate and
determine if it is a certificate that you trust. I recommend comparing
chainRoot.RawData to a byte[] representing a certificate that you
trust as a root in context (that is, byte-for-byte compare rather than
using a thumbprint value).

(If other flags are set then other constraints are also relaxed)

So you should do it this way:

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);
热血少△年 2024-11-15 19:25:21

编辑

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

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

我删除了原始答案,因此没有人使用糟糕的安全解决方案。

EDIT

Over the years we found several issues with the original X509Chain solution I had posted here due to X509Chain performing incorrect behaviors for certain edge cases. Thus I can no longer recommend using X509Chain for this problem. Our product has since moved to using Bouncy Castle to do all of our certificate chain verification and it has held up to all of our testing and always works as expected.

The basis of our new solution can be found here: Build certificate chain in BouncyCastle in C#

I have removed the original answer so no one is using a bad security solution.

银河中√捞星星 2024-11-15 19:25:21

获得此信息的方法是编写自定义验证。

如果您处于 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();
        }
    }
}

The way to obtain this would be to write a custom validation.

If you are in a WCF context this is done by subclassing the System.IdentityModel.Selectors.X509CertificateValidator and specifying the custom validation on the serviceBehavior object in web.config:

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

But if you are just looking at a way to accept SSL certs from another host you can modify the system.net settings in the web.config file:

Below is an example of a X509CertificateValidator that tests if the clients cert is present in the LocalMachine/Personal store. (Which is not what you need but might be useful as an example.

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();
        }
    }
}
乄_柒ぐ汐 2024-11-15 19:25:21

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

我的任务还包括编写一个 Windows 窗体应用程序来安装证书,前提是该证书是根据我国政府已知的“国家根证书”颁发的。允许颁发证书来验证与国家 Web 服务的连接的 CA 数量也有限,因此我拥有一组有限的证书,这些证书可以位于链中,并且可能在目标计算机上丢失。我在应用程序的子目录“cert”中收集了 CA 的所有公钥和政府根证书:
chaincertificate

在 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 窗体应用程序的操作员非常肯定能够访问要安装的有效证书。该软件的目标只是过滤证书列表,以帮助他仅在计算机的机器存储中安装正确的证书(该软件还安装中间证书和根证书的公钥,以确保该证书的运行时行为Web 服务客户端是正确的)。

If you know which certificates can be root and intermediate certificates for the certificate to check, you can load the public keys of the root and intermediate certificates in the ChainPolicy.ExtraStore collection of the X509Chain object.

My task was also to write a Windows Forms application to install a certificate, only if it was issued dependent on the known "National Root certificate" of my country's government. There also is a limited number of CA's that are allowed to issue certificates to authenticate connections to the national web services, so I had a limited set of certificates that can be in the chain and might be missing on the target machine. I collected all public keys of the CA's and the government root certificates in a subdirectory "cert" of the application:
chain certificates

In Visual Studio, I added the directory cert to the solution and marked all files in this directory as embedded resource. This allowed me to enumerate the collection of "trusted" certificates in my c# library code, to build a chain to check the certificate even if the issuer certificate is not installed. I made a wrapper class for X509Chain for this purpose:

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

In the calling function, I could now successfully check if an unknown certificate derives from the national root certificate:

    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;

To complete the picture: to check the root certificate (that usually is installed because it is included in Windows Update, but in theory could be missing as well), I compare the friendly name and thumbprint to the published values:

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

I am not sure if this check is secure at all, but in my case the operator of the Windows Forms application is quite sure to have access to a valid certificate to be installed. The goal of the software is just to filter the certificates list to help him install only the correct certificate in the machine store of the computer (the software also installs the public keys of the intermediate and root certificate, to ensure that the runtime behavior of the web service client is correct).

月棠 2024-11-15 19:25:21

我刚刚扩展了 @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
}

I just extended the code from @Tristan with a check that the root certificate is one of the certificates added to the 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
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文