Поделиться через


Валидатор сертификатов X.509

В этом примере показано, как реализовать пользовательский проверяющий элемент сертификата X.509. Это полезно в случаях, когда ни один из встроенных режимов проверки сертификатов X.509 не подходит для требований приложения. В этом примере показана служба с настраиваемым валидатором, который принимает самоподписанные сертификаты. Клиент использует такой сертификат для проверки подлинности в службе.

Примечание. Так как любой пользователь может создать самоотдающийся сертификат, настраиваемый проверяющий элемент, используемый службой, является менее безопасным, чем поведение по умолчанию, предоставленное ChainTrust X509CertificateValidationMode. Прежде чем использовать эту логику проверки в рабочем коде, следует тщательно учитывать последствия для безопасности.

В этом примере показано, как:

  • Клиент может быть аутентифицирован с помощью сертификата X.509.

  • Сервер проверяет учетные данные клиента с использованием настраиваемого валидатора X509Certificate.

  • Сервер проходит проверку подлинности с помощью сертификата X.509 сервера.

Служба предоставляет одну конечную точку для взаимодействия со службой, определенной с помощью файла конфигурации App.config. Конечная точка состоит из адреса, привязки и контракта. Привязка настроена с использованием стандарта wsHttpBinding, в котором по умолчанию применяется WSSecurity и проверка подлинности с использованием клиентского сертификата. Поведение службы указывает настраиваемый режим проверки сертификатов клиента X.509 вместе с типом класса проверки. Поведение также указывает сертификат сервера с помощью элемента serviceCertificate. Сертификат сервера должен иметь то же значение для SubjectName, что и findValue в <serviceCertificate>.

  <system.serviceModel>
    <services>
      <service name="Microsoft.ServiceModel.Samples.CalculatorService"
               behaviorConfiguration="CalculatorServiceBehavior">
        <!-- use host/baseAddresses to configure base address -->
        <!-- provided by host -->
        <host>
          <baseAddresses>
            <add baseAddress =
                "http://localhost:8001/servicemodelsamples/service" />
          </baseAddresses>
        </host>
        <!-- use base address specified above, provide one endpoint -->
        <endpoint address="certificate"
               binding="wsHttpBinding"
               bindingConfiguration="Binding"
               contract="Microsoft.ServiceModel.Samples.ICalculator" />
      </service>
    </services>
    <bindings>
      <wsHttpBinding>
        <!-- X509 certificate binding -->
        <binding name="Binding">
          <security mode="Message">
            <message clientCredentialType="Certificate" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="CalculatorServiceBehavior">
          <serviceDebug includeExceptionDetailInFaults ="true"/>
          <serviceCredentials>
            <!-- The serviceCredentials behavior allows one -->
            <!-- to specify authentication constraints on -->
            <!-- client certificates. -->
            <clientCertificate>
              <!-- Setting the certificateValidationMode to -->
              <!-- Custom means that if the custom -->
              <!-- X509CertificateValidator does NOT throw -->
              <!-- an exception, then the provided certificate -->
              <!-- will be trusted without performing any -->
              <!-- validation beyond that performed by the custom -->
              <!-- validator. The security implications of this -->
              <!-- setting should be carefully considered before -->
              <!-- using Custom in production code. -->
              <authentication
                 certificateValidationMode="Custom"
                 customCertificateValidatorType =
"Microsoft.ServiceModel.Samples.CustomX509CertificateValidator, service" />
            </clientCertificate>
            <!-- The serviceCredentials behavior allows one to -->
            <!-- define a service certificate. -->
            <!-- A service certificate is used by a client to -->
            <!-- authenticate the service and provide message -->
            <!-- protection. This configuration references the -->
            <!-- "localhost" certificate installed during the setup -->
            <!-- instructions. -->
            <serviceCertificate findValue="localhost"
                 storeLocation="LocalMachine"
                 storeName="My" x509FindType="FindBySubjectName" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
      </system.serviceModel>

Конфигурация конечной точки клиента состоит из имени конфигурации, абсолютного адреса конечной точки службы, привязки и контракта. Привязка клиента настроена с соответствующим режимом и сообщением clientCredentialType.

<system.serviceModel>
    <client>
      <!-- X509 certificate based endpoint -->
      <endpoint name="Certificate"
        address=
        "http://localhost:8001/servicemodelsamples/service/certificate"
                binding="wsHttpBinding"
                bindingConfiguration="Binding"
                behaviorConfiguration="ClientCertificateBehavior"
                contract="Microsoft.ServiceModel.Samples.ICalculator">
      </endpoint>
    </client>
    <bindings>
        <wsHttpBinding>
            <!-- X509 certificate binding -->
            <binding name="Binding">
                <security mode="Message">
                    <message clientCredentialType="Certificate" />
               </security>
            </binding>
       </wsHttpBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="ClientCertificateBehavior">
          <clientCredentials>
            <serviceCertificate>
              <!-- Setting the certificateValidationMode to -->
              <!-- PeerOrChainTrust means that if the certificate -->
              <!-- is in the user's Trusted People store, then it -->
              <!-- is trusted without performing a validation of -->
              <!-- the certificate's issuer chain. -->
              <!-- This setting is used here for convenience so -->
              <!-- that the sample can be run without having to -->
              <!-- have certificates issued by a certification -->
              <!-- authority (CA). This setting is less secure -->
              <!-- than the default, ChainTrust. The security -->
              <!-- implications of this setting should be -->
              <!-- carefully considered before using -->
              <!-- PeerOrChainTrust in production code.-->
              <authentication
                  certificateValidationMode="PeerOrChainTrust" />
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
  </system.serviceModel>

Реализация клиента задает используемый сертификат клиента.

// Create a client with Certificate endpoint configuration
CalculatorClient client = new CalculatorClient("Certificate");
try
{
    client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "test1");

    // Call the Add service operation.
    double value1 = 100.00D;
    double value2 = 15.99D;
    double result = client.Add(value1, value2);
    Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);

    // Call the Subtract service operation.
    value1 = 145.00D;
    value2 = 76.54D;
    result = client.Subtract(value1, value2);
    Console.WriteLine("Subtract({0},{1}) = {2}", value1, value2, result);

    // Call the Multiply service operation.
    value1 = 9.00D;
    value2 = 81.25D;
    result = client.Multiply(value1, value2);
    Console.WriteLine("Multiply({0},{1}) = {2}", value1, value2, result);

    // Call the Divide service operation.
    value1 = 22.00D;
    value2 = 7.00D;
    result = client.Divide(value1, value2);
    Console.WriteLine("Divide({0},{1}) = {2}", value1, value2, result);
    client.Close();
}
catch (TimeoutException e)
{
    Console.WriteLine("Call timed out : {0}", e.Message);
    client.Abort();
}
catch (CommunicationException e)
{
    Console.WriteLine("Call failed : {0}", e.Message);
    client.Abort();
}
catch (Exception e)
{
    Console.WriteLine("Call failed : {0}", e.Message);
    client.Abort();
}

В этом примере используется пользовательский X509CertificateValidator для проверки сертификатов. Пример реализует CustomX509CertificateValidator, производный от X509CertificateValidator. Дополнительные сведения см. в документации X509CertificateValidator. Данный пример пользовательского валидатора реализует метод Validate, чтобы принять любой самовыпущенный сертификат X.509, как показано в следующем коде.

public class CustomX509CertificateValidator : X509CertificateValidator
{
  public override void Validate ( X509Certificate2 certificate )
  {
   // Only accept self-issued certificates
   if (certificate.Subject != certificate.Issuer)
     throw new Exception("Certificate is not self-issued");
   }
}

После реализации проверяющего элемента в коде службы узел службы должен быть проинформирован об используемом экземпляре проверяющего элемента. Это делается с помощью следующего кода.

serviceHost.Credentials.ClientCertificate.Authentication.CertificateValidationMode = X509CertificateValidationMode.Custom;
serviceHost.Credentials.ClientCertificate.Authentication.CustomCertificateValidator = new CustomX509CertificateValidator();

Или же можно сделать то же самое в конфигурации, как показано ниже.

<behaviors>
    <serviceBehaviors>
     <behavior name="CalculatorServiceBehavior">
       ...
   <serviceCredentials>
    <!--The serviceCredentials behavior allows one to specify -->
    <!--authentication constraints on client certificates.-->
    <clientCertificate>
    <!-- Setting the certificateValidationMode to Custom means -->
    <!--that if the custom X509CertificateValidator does NOT -->
    <!--throw an exception, then the provided certificate will -->
    <!--be trusted without performing any validation beyond that -->
    <!--performed by the custom validator. The security -->
    <!--implications of this setting should be carefully -->
    <!--considered before using Custom in production code. -->
    <authentication certificateValidationMode="Custom"
       customCertificateValidatorType =
"Microsoft.ServiceModel.Samples. CustomX509CertificateValidator, service" />
   </clientCertificate>
   </serviceCredentials>
   ...
  </behavior>
 </serviceBehaviors>
</behaviors>

При запуске примера запросы и ответы операции отображаются в окне консоли клиента. Клиент должен успешно вызывать все методы. Нажмите клавишу ВВОД в окне клиента, чтобы завершить работу клиента.

Настройка пакетного файла

Входящий в состав образца файл Setup.bat позволяет настроить для сервера соответствующие сертификаты, необходимые для выполнения резидентного приложения, которое требует обеспечения безопасности на основе сертификата сервера. Для работы этого пакетного файла на разных компьютерах или без хостинга его необходимо изменить.

Ниже приведен краткий обзор различных разделов пакетных файлов, чтобы их можно было изменить для выполнения в соответствующей конфигурации:

  • Создание сертификата сервера:

    Следующие строки из пакетного файла Setup.bat создают используемый сертификат сервера. Переменная %SERVER_NAME% указывает имя сервера. Измените эту переменную, чтобы указать собственное имя сервера. Значение по умолчанию — localhost.

    echo ************
    echo Server cert setup starting
    echo %SERVER_NAME%
    echo ************
    echo making server cert
    echo ************
    makecert.exe -sr LocalMachine -ss MY -a sha1 -n CN=%SERVER_NAME% -sky exchange -pe
    
  • Установка сертификата сервера в доверенное хранилище сертификатов клиента:

    Следующие строки в пакетном файле Setup.bat копируют сертификат сервера в хранилище доверенных пользователей клиента. Этот шаг требуется, так как сертификаты, созданные Makecert.exe, не являются неявно доверенными клиентской системой. Если у вас уже есть сертификат, основанный на доверенном корневом сертификате клиента, например, выданный корпорацией Майкрософт, этот шаг, в котором хранилище сертификатов клиента заполняется серверным сертификатом, не требуется.

    certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople
    
  • Создание сертификата клиента:

    Следующие строки из Setup.bat пакетного файла создают сертификат клиента для использования. Переменная %USER_NAME% указывает имя клиента. Это значение установлено как "test1", потому что именно это имя ищет клиентский код. При изменении значения %USER_NAME% необходимо изменить соответствующее значение в исходном файле Client.cs и перестроить клиент.

    Сертификат хранится в хранилище My (Personal) в расположении хранилища CurrentUser.

    echo ************
    echo Client cert setup starting
    echo %USER_NAME%
    echo ************
    echo making client cert
    echo ************
    makecert.exe -sr CurrentUser -ss MY -a sha1 -n CN=%USER_NAME% -sky exchange -pe
    
  • Установка сертификата клиента в доверенное хранилище сертификатов сервера:

    Следующие строки в пакетном файле Setup.bat копируют сертификат клиента в хранилище доверенных пользователей. Этот шаг необходим, так как сертификаты, созданные Makecert.exe, не являются неявно доверенными серверной системой. Если у вас уже есть сертификат, который входит в цепочку доверенных корневых сертификатов — например, выданный компанией Microsoft — то данный шаг по добавлению клиентского сертификата в хранилище сертификатов сервера не требуется.

    certmgr.exe -add -r CurrentUser -s My -c -n %USER_NAME% -r LocalMachine -s TrustedPeople
    

Для настройки и сборки примера

  1. Чтобы создать решение, следуйте инструкциям по созданию примеров Windows Communication Foundation.

  2. Чтобы запустить образец на одном или нескольких компьютерах, следуйте приведенным далее инструкциям.

Запуск примера на том же компьютере

  1. Запустите Setup.bat из примера папки установки в командной строке Visual Studio, открытой с правами администратора. При этом устанавливаются все сертификаты, необходимые для выполнения примера.

    Это важно

    Пакетный файл Setup.bat предназначен для запуска из командной строки Visual Studio. Переменная среды PATH в командной строке Visual Studio указывает на каталог, содержащий исполняемые файлы, необходимые скрипту Setup.bat.

  2. Запустите Service.exe из service\bin.

  3. Запустите Client.exe из \client\bin. Действие клиента отображается в клиентском консольном приложении.

  4. Если клиент и служба не могут взаимодействовать, см. рекомендации по устранению неисправностей для примеров WCF.

Для запуска примера на нескольких компьютерах

  1. Создайте каталог на компьютере службы.

  2. Скопируйте файлы программы службы из \service\bin в виртуальный каталог на компьютере службы. Кроме того, скопируйте Setup.bat, Cleanup.bat, GetComputerName.vbs и ImportClientCert.bat файлы на компьютер службы.

  3. Создайте каталог на клиентском компьютере для двоичных файлов клиента.

  4. Скопируйте файлы клиентской программы в каталог клиента на клиентском компьютере. Кроме того, скопируйте Setup.bat, Cleanup.batи ImportServiceCert.bat файлы в клиент.

  5. На сервере, в открытой с правами администратора командной строке разработчика для Visual Studio, выполните команду setup.bat service. При выполнении setup.bat с аргументом service создается сертификат службы с полным доменным именем компьютера и экспортируется сертификат службы в файл с именем Service.cer.

  6. Измените Service.exe.config, чтобы отразить новое имя сертификата (в findValue атрибуте <в serviceCertificate>), которое совпадает с полным доменным именем компьютера. Также измените имя компьютера в элементе <service>/<baseAddresses> с localhost на полное имя компьютера службы.

  7. Скопируйте файл Service.cer из каталога службы в клиентский каталог на клиентском компьютере.

  8. На клиенте, в командной строке разработчика для Visual Studio, открытой с правами администратора, выполните setup.bat client. При выполнении setup.bat с аргументом client создается сертификат клиента с именем client.com и экспортируется сертификат клиента в файл с именем Client.cer.

  9. В файле Client.exe.config на клиентском компьютере измените значение адреса конечной точки на соответствие новому адресу службы. Для этого замените localhost полным доменным именем сервера.

  10. Скопируйте файл Client.cer из клиентского каталога в каталог службы на сервере.

  11. На клиенте запустите ImportServiceCert.bat в командной строке разработчика для Visual Studio, открывшейся с правами администратора. При этом сертификат службы импортируется из файла Service.cer в хранилище CurrentUser — TrustedPeople.

  12. На сервере запустите ImportClientCert.bat в командной строке разработчика для Visual Studio, открывшейся с правами администратора. При этом сертификат клиента импортируется из файла Client.cer в хранилище LocalMachine — TrustedPeople.

  13. На серверном компьютере запустите Service.exe из окна командной строки.

  14. На клиентском компьютере запустите Client.exe из окна командной строки. Если клиент и служба не могут взаимодействовать, см. рекомендации по устранению неисправностей для примеров WCF.

Очистка после образца

  1. Запустите Cleanup.bat в папке примеров после завершения работы примера. Это удаляет сертификаты сервера и клиента из хранилища сертификатов.

Замечание

Этот скрипт не удаляет сертификаты службы на клиенте при запуске этого примера на компьютерах. Если вы запускали примеры из Windows Communication Foundation (WCF), использующие сертификаты на нескольких компьютерах, обязательно удалите сертификаты службы, установленные в хранилище CurrentUser — TrustedPeople. Для этого используйте следующую команду: certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name> Например, certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com.