X.509 证书验证程序

此示例演示如何实现自定义 X.509 证书验证程序。 在内置 X.509 证书验证模式都不适合应用程序要求的情况下,这非常有用。 此示例显示了一个服务,该服务具有接受自颁发的证书的自定义验证程序。 客户端使用此类证书向服务进行身份验证。

注意:由于任何人都可以构造自颁发的证书,服务使用的自定义验证程序的安全性低于 ChainTrust X509CertificateValidationMode 提供的默认行为。 在生产代码中使用此验证逻辑之前,应仔细考虑这一点的安全影响。

在摘要中,此示例演示了如何:

  • 可以使用 X.509 证书对客户端进行身份验证。

  • 服务器如何根据自定义 X509CertificateValidator 验证客户端凭据。

  • 服务器使用服务器的 X.509 证书进行身份验证。

该服务公开了一个终结点,用于与服务通信,该终结点使用配置文件 App.config定义。终结点由地址、绑定和协定组成。 绑定是使用标准 wsHttpBinding 配置的,该标准绑定默认使用 WSSecurity 和客户端证书身份验证。 服务行为指定用于验证客户端 X.509 证书以及验证程序类类型的自定义模式。 该行为还使用 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 验证证书。 此示例实现了从X509CertificateValidator派生的CustomX509CertificateValidator。 有关详细信息,请参阅相关 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>

运行示例时,操作请求和响应将显示在客户端控制台窗口中。 客户端应成功调用所有方法。 在客户端窗口中按 Enter 关闭客户端。

设置批处理文件

通过运行此示例随附的 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 生成的证书。 如果已有一个证书,该证书已植根于客户端受信任的根证书(例如Microsoft颁发的证书),则不需要使用服务器证书填充客户端证书存储区。

    certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople
    
  • 创建客户端证书:

    Setup.bat 批处理文件中的以下行创建要使用的客户端证书。 %USER_NAME% 变量指定客户端名称。 此值设置为“test1”,因为这是客户端代码查找的名称。 如果更改 %USER_NAME% 的值,则必须更改Client.cs源文件中的相应值并重新生成客户端。

    证书存储在 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. 使用管理员权限从 Visual Studio 命令提示符内的示例安装文件夹运行 Setup.bat。 这会安装运行示例所需的所有证书。

    重要

    Setup.bat 批处理文件设计为通过 Visual Studio 命令提示符运行。 在 Visual Studio 命令提示中设置的 PATH 环境变量指向包含 Setup.bat 脚本所需的可执行文件的目录。

  2. 从 service\bin 启动 Service.exe。

  3. 从 \client\bin 启动 Client.exe。 客户端活动显示在客户端控制台应用程序中。

  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 以反映新的证书名称(在 > 的 属性中),该名称与计算机的完全限定域名相同。 另外,将 <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. 在客户端上,使用管理员权限打开的 Visual Studio 开发人员命令提示符中运行 ImportServiceCert.bat。 这会将服务证书从 Service.cer 文件导入 CurrentUser - TrustedPeople 存储中。

  12. 在服务器上,使用管理员权限打开的 Visual Studio 开发人员命令提示符中运行 ImportClientCert.bat。 这会将客户端证书从 Client.cer 文件导入 LocalMachine - TrustedPeople 存储。

  13. 在服务器计算机上,从命令提示符窗口启动 Service.exe。

  14. 在客户端计算机上,从命令提示符窗口启动 Client.exe。 如果客户端和服务无法通信,请参阅 WCF 示例 故障排除提示。

运行示例后进行清理

  1. 运行完示例后,在示例文件夹中运行 Cleanup.bat。 这会从证书存储中删除服务器和客户端证书。

注释

在跨计算机运行此示例时,此脚本不会删除客户端上的服务证书。 如果您运行了在不同计算机上使用证书的 Windows Communication Foundation (WCF) 示例,请确保清除已安装在当前用户 - 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