Partilhar via


X.509 Validador de Certificado

Este exemplo demonstra como implementar um validador de certificado X.509 personalizado. Isso é útil nos casos em que nenhum dos modos internos de Validação de Certificado X.509 é apropriado para os requisitos do aplicativo. Este exemplo mostra um serviço que tem um validador personalizado que aceita certificados autoemitidos. O cliente usa esse certificado para se autenticar no serviço.

Nota: Como qualquer pessoa pode construir um certificado auto-emitido, o validador personalizado usado pelo serviço é menos seguro do que o comportamento padrão fornecido pelo ChainTrust X509CertificateValidationMode. As implicações de segurança disso devem ser cuidadosamente consideradas antes de usar essa lógica de validação no código de produção.

Em resumo, este exemplo demonstra como:

  • O cliente pode ser autenticado usando um certificado X.509.

  • O servidor valida as credenciais do cliente em relação a um X509CertificateValidator personalizado.

  • O servidor é autenticado usando o certificado X.509 do servidor.

O serviço expõe um único ponto de extremidade para comunicação com o serviço, definido usando o arquivo de configuração App.config. O ponto de extremidade consiste em um endereço, uma vinculação e um contrato. A associação é configurada com um padrão wsHttpBinding que usa como padrão a autenticação de WSSecurity certificado de cliente e uso. O comportamento de serviço especifica o modo personalizado para validar certificados X.509 do cliente juntamente com o tipo da classe do validador. O comportamento também especifica o certificado do servidor usando o elemento serviceCertificate. O certificado do servidor deve conter o mesmo valor para o SubjectName que no 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>

A configuração do ponto de extremidade do cliente consiste em um nome de configuração, um endereço absoluto para o ponto de extremidade do serviço, a associação e o contrato. A ligação do cliente é configurada com o modo e a mensagem clientCredentialTypeapropriados.

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

A implementação do cliente define o certificado do cliente a ser usado.

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

Este exemplo usa um X509CertificateValidator personalizado para validar certificados. O exemplo implementa CustomX509CertificateValidator, derivado de X509CertificateValidator. Consulte a documentação sobre X509CertificateValidator para obter mais informações. Este exemplo de validador personalizado específico implementa o método Validate para aceitar qualquer certificado X.509 que é auto-emitido, conforme mostrado no código a seguir.

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

Depois que o validador é implementado no código de serviço, o host de serviço deve ser informado sobre a instância do validador a ser usada. Isso é feito usando o código a seguir.

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

Ou você pode fazer a mesma coisa na configuração da seguinte maneira.

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

Quando você executa o exemplo, as solicitações de operação e as respostas são exibidas na janela do console do cliente. O cliente deve chamar com êxito todos os métodos. Pressione ENTER na janela do cliente para desligar o cliente.

Arquivo em lote de instalação

O arquivo em lote Setup.bat incluído neste exemplo permite configurar o servidor com certificados relevantes para executar um aplicativo auto-hospedado que requer segurança baseada em certificado de servidor. Esse arquivo em lotes deve ser modificado para funcionar entre computadores ou para funcionar em um caso não hospedado.

O seguinte fornece uma breve visão geral das diferentes seções dos arquivos em lote para que eles possam ser modificados para serem executados na configuração apropriada:

  • Criando o certificado do servidor:

    As linhas a seguir do arquivo em lotes Setup.bat criam o certificado do servidor a ser usado. A variável %SERVER_NAME% especifica o nome do servidor. Altere essa variável para especificar seu próprio nome de servidor. O valor padrão é 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
    
  • Instalando o certificado do servidor no armazenamento de certificados confiáveis do cliente:

    As linhas a seguir no arquivo em lote Setup.bat copiam o certificado do servidor para o armazenamento de pessoas confiáveis do cliente. Esta etapa é necessária, uma vez que os certificados gerados por Makecert.exe não são implicitamente confiáveis pelo sistema cliente. Se você já tiver um certificado enraizado em um certificado raiz confiável do cliente, por exemplo, um certificado emitido pela Microsoft, esta etapa de preencher o armazenamento de certificados do cliente com o certificado do servidor não será necessária.

    certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople
    
  • Criando o certificado do cliente:

    As linhas a seguir do arquivo em lotes Setup.bat criam o certificado de cliente a ser usado. A variável %USER_NAME% especifica o nome do cliente. Esse valor é definido como "test1" porque esse é o nome que o código do cliente procura. Se você alterar o valor de %USER_NAME%, deverá alterar o valor correspondente no arquivo de origem Client.cs e reconstruir o cliente.

    O certificado é armazenado em My (Personal) store sob o local de armazenamento 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
    
  • Instalando o certificado do cliente no armazenamento de certificados confiáveis do servidor:

    As linhas a seguir no arquivo em lote Setup.bat copiam o certificado do cliente para o armazenamento de pessoas confiáveis. Esta etapa é necessária porque os certificados gerados por Makecert.exe não são implicitamente confiáveis pelo sistema do servidor. Se você já tiver um certificado enraizado em um certificado raiz confiável — por exemplo, um certificado emitido pela Microsoft — esta etapa de preencher o armazenamento de certificados do servidor com o certificado do cliente não será necessária.

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

Para configurar e compilar o exemplo

  1. Para criar a solução, siga as instruções em Criando os exemplos do Windows Communication Foundation.

  2. Para executar o exemplo em uma configuração de computador único ou entre computadores, use as instruções a seguir.

Para executar o exemplo no mesmo computador

  1. Execute Setup.bat a partir da pasta de instalação de exemplo dentro de um prompt de comando do Visual Studio aberto com privilégios de administrador. Isso instala todos os certificados necessários para executar o exemplo.

    Importante

    O arquivo em lotes Setup.bat foi projetado para ser executado a partir de um prompt de comando do Visual Studio. A variável de ambiente PATH definida no prompt de comando do Visual Studio aponta para o diretório que contém executáveis exigidos pelo script Setup.bat.

  2. Inicie Service.exe a partir de service\bin.

  3. Inicie Client.exe a partir de \client\bin. A atividade do cliente é exibida no aplicativo de console do cliente.

  4. Se o cliente e o serviço não puderem se comunicar, consulte Dicas de solução de problemas para exemplos de WCF.

Para executar o exemplo em computadores

  1. Crie um diretório no computador de serviço.

  2. Copie os arquivos de programa de serviço de \service\bin para o diretório virtual no computador de serviço. Copie também os arquivos Setup.bat, Cleanup.bat, GetComputerName.vbs e ImportClientCert.bat para o computador de serviço.

  3. Crie um diretório no computador cliente para os binários do cliente.

  4. Copie os arquivos de programa cliente para o diretório do cliente no computador cliente. Copie também os arquivos Setup.bat, Cleanup.bat e ImportServiceCert.bat para o cliente.

  5. No servidor, execute setup.bat service em um prompt de comando do desenvolvedor para Visual Studio aberto com privilégios de administrador. A execução setup.bat com o service argumento cria um certificado de serviço com o nome de domínio totalmente qualificado do computador e exporta o certificado de serviço para um arquivo chamado Service.cer.

  6. Edite Service.exe.config para refletir o novo nome do certificado (no findValue atributo no <serviceCertificate>), que é o mesmo que o nome de domínio totalmente qualificado do computador. Altere também o nome do <computador no elemento service>/<baseAddresses> de localhost para nome totalmente qualificado do seu computador de serviço.

  7. Copie o arquivo Service.cer do diretório de serviço para o diretório do cliente no computador cliente.

  8. No cliente, execute setup.bat client em um prompt de comando do desenvolvedor para Visual Studio aberto com privilégios de administrador. A execução setup.bat com o client argumento cria um certificado de cliente chamado client.com e exporta o certificado de cliente para um arquivo chamado Client.cer.

  9. No arquivo Client.exe.config no computador cliente, altere o valor de endereço do ponto de extremidade para corresponder ao novo endereço do seu serviço. Faça isso substituindo localhost pelo nome de domínio totalmente qualificado do servidor.

  10. Copie o arquivo Client.cer do diretório do cliente para o diretório de serviço no servidor.

  11. No cliente, execute ImportServiceCert.bat em um prompt de comando do desenvolvedor para Visual Studio aberto com privilégios de administrador. Isso importa o certificado de serviço do arquivo Service.cer para o repositório CurrentUser - TrustedPeople.

  12. No servidor, execute ImportClientCert.bat em um prompt de comando do desenvolvedor para Visual Studio aberto com privilégios de administrador. Isso importa o certificado do cliente do arquivo Client.cer para o armazenamento LocalMachine - TrustedPeople.

  13. No computador servidor, inicie Service.exe a partir da janela do prompt de comando.

  14. No computador cliente, inicie Client.exe a partir de uma janela de prompt de comando. Se o cliente e o serviço não puderem se comunicar, consulte Dicas de solução de problemas para exemplos de WCF.

Para limpar após a amostra

  1. Execute Cleanup.bat na pasta de exemplos assim que terminar de executar o exemplo. Isso remove os certificados de servidor e cliente do armazenamento de certificados.

Nota

Esse script não remove certificados de serviço em um cliente ao executar este exemplo em computadores. Se você tiver executado exemplos do Windows Communication Foundation (WCF) que usam certificados entre computadores, certifique-se de limpar os certificados de serviço que foram instalados no repositório CurrentUser - TrustedPeople. Para fazer isso, use o seguinte comando: certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name> Por exemplo: certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com.