Проверяющий элемент управления для сертификатов X.509

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

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

Таким образом, в этом образце демонстрируются указанные ниже возможности.

  • Подлинность клиента может проверяться с помощью сертификата X.509.

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

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

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

  <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

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

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

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

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

  8. На клиенте запустите setup.bat client командную строку разработчика для Visual Studio, открытую с правами администратора. При выполнении команды 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 — доверенный Люди. Для этого воспользуйтесь следующей командой: certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name>. Например: certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com.