Как проверить сертификат X509 без импорта корневого сертификата? - PullRequest
9 голосов
/ 23 мая 2011

Моя программа содержит 2 корневых сертификата, которые я знаю и которым доверяю.Я должен проверить сертификаты центров доверия и «пользовательских» сертификатов, выпущенных центрами доверия, которые все происходят из этих 2 корневых сертификатов.

Я использую класс X509Chain для проверки, но это работает, только если корневой сертификат находится в сертификате Windowsstore.

Я ищу способ проверки сертификатов без импорта корневых сертификатов thees - как-то сообщить классу 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.

Ответы [ 5 ]

4 голосов
/ 30 сентября 2015

Я нашел решение для этого, чтобы не полагаться на результат метода сборки, а вместо этого проверять свойство ChainStatus.(Не тестировался на .NET 2.0, но это единственное решение, которое я нашел для этой распространенной проблемы)Метод build возвращает true, даже если вы не добавляете сертификаты в ExtraStore, что полностью отрицает цель проверки.Я не рекомендую использовать этот флаг по любой причине.

3 голосов
/ 12 июня 2018

Я открыл Проблема в dotnet / corefx, и они ответили следующим образом:

Если AllowUnknownCertificateAuthority является единственным установленным флагом, тогда chain.Build() вернет true, если

  • Цепочка правильно завершена в самозаверяющем сертификате (через ExtraStore или в сохраненных сохраненных хранилищах)

  • Ни один из сертификатов недействителен для запрошенного отзываpolicy

  • Все сертификаты действительны при значениях (необязательных) ApplicationPolicy или CertificatePolicy

  • Все значения NotBefore сертификатов находятся поor-before VerificationTime и все значения NotAfter сертификатов (at-or-) после VerificationTime.

Если этот флаг не указан, то добавляется дополнительное ограничение:

Самоподписанный сертификат должен быть зарегистрирован как доверенный в системе (например, вLM \ Root store).

Итак, 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);
1 голос
/ 29 марта 2013

Если вы знаете, какие сертификаты могут быть корневыми и промежуточными сертификатами для проверки сертификата, вы можете загрузить открытые ключи корневых и промежуточных сертификатов в коллекцию ChainPolicy.ExtraStore объекта X509Chain.

Моей задачей было также написать приложение Windows Forms для установки сертификата, только если оно было выпущено в зависимости от известного «Национального корневого сертификата» правительства моей страны. Также имеется ограниченное количество центров сертификации, которым разрешено выдавать сертификаты для проверки подлинности соединений с национальными веб-службами, поэтому у меня был ограниченный набор сертификатов, которые могут быть в цепочке и могут отсутствовать на целевом компьютере. Я собрал все открытые ключи CA и правительственные корневые сертификаты в подкаталоге «cert» приложения: chain certificates

В Visual Studio я добавил сертификат сертификата в решение и пометил все файлы в этом каталоге как встроенный ресурс. Это позволило мне перечислить коллекцию «доверенных» сертификатов в коде моей библиотеки 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 наверняка будет иметь доступ к действительному сертификату, который будет установлен. Цель программного обеспечения - просто отфильтровать список сертификатов, чтобы помочь ему установить только правильный сертификат в хранилище компьютеров компьютера (программное обеспечение также устанавливает открытые ключи промежуточного и корневого сертификатов, чтобы гарантировать, что поведение среды выполнения клиент веб-сервиса правильный).

1 голос
/ 23 мая 2011

Способ получить это - написать пользовательскую проверку.

Если вы находитесь в контексте WCF, это делается путем создания подкласса System.IdentityModel.Selectors.X509CertificateValidator и указания пользовательской проверки для объекта serviceBehavior в 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>

Но если вы просто ищете способ принять SSL-сертификаты от другого хоста, вы можете изменить настройки system.net в файле web.config:

Ниже приведен пример 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();
        }
    }
}
0 голосов
/ 16 декабря 2016

Я только что расширил код с @ 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
}
...