Compartilhar via


Validador de certificado X.509

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 auto-emitidos. O cliente usa esse certificado para autenticar no serviço.

Observação: 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 se 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 se comunicar com o serviço, definido usando o arquivo de configuração App.config. O ponto de extremidade consiste em um endereço, uma associação e um contrato. A associação é configurada com um wsHttpBinding padrão que assume como padrão o uso de WSSecurity e autenticação de certificado do cliente. O comportamento do serviço especifica o modo personalizado para validar certificados X.509 do cliente junto com o tipo da classe de validador. O comportamento também especifica o certificado do servidor usando o elemento serviceCertificate. O certificado do servidor deve conter o mesmo valor de SubjectName como findValue em <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 de serviço, a associação e o contrato. A associação do cliente é configurada com o modo e a mensagem clientCredentialType apropriados.

<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. Veja a documentação sobre X509CertificateValidator para obter mais informações. Este exemplo de validador personalizado em particular implementa o método Validate para aceitar qualquer certificado X.509 que seja 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 for implementado no código de serviço, o host de serviço deverá ser informado sobre a instância de validador a ser usada. Isso é feito usando o seguinte código.

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

Ou você pode fazer a mesma coisa na configuração conforme segue.

<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 a amostra, 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 de configuração em lote

O arquivo Setup.bat em lote, incluído com esta amostra, permite que você configure o servidor com os certificados relevantes para executar um aplicativo auto-hospedado que exige segurança baseada em certificado do servidor. Esse arquivo em lote precisa ser modificado para funcionar em computadores ou em um caso não hospedado.

A seguir está breve visão geral das diferentes seções dos arquivos em lote que podem ser modificados para executar a configuração apropriada:

  • Criação do certificado do servidor:

    As linhas a seguir do arquivo em lote 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 o nome do seu próprio 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
    
  • Instalação do certificado do servidor no repositório de certificados confiáveis do cliente:

    As linhas a seguir no arquivo em lote Setup.bat copiam o certificado do servidor no repositório de pessoas confiáveis do cliente. Essa etapa é necessária porque os certificados gerados por Makecert.exe não são implicitamente confiáveis para o sistema do cliente. Se você já tiver um certificado com raiz em um certificado raiz confiável do cliente, por exemplo, um certificado emitido pela Microsoft, essa etapa de preenchimento do repositório 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
    
  • Criação do certificado do cliente:

    As linhas a seguir do arquivo em lote Setup.bat criam o certificado do 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 recompilar o cliente.

    O certificado é armazenado no repositório Meu (Pessoal) no local do repositório 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
    
  • Instalação do certificado do cliente no repositório de certificados confiáveis do servidor:

    As linhas a seguir no arquivo em lote Setup.bat copiam o certificado do servidor no repositório de pessoas confiáveis do cliente. Essa etapa é necessária porque os certificados gerados por Makecert.exe não são implicitamente confiáveis para o sistema do servidor. Se você já tiver um certificado com raiz em um certificado raiz confiável do cliente, como um certificado emitido pela Microsoft, por exemplo, essa etapa de preenchimento do repositório de certificados do servidor com certificados do servidor não será necessária.

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

Para configurar e compilar a amostra

  1. Para compilar a solução, siga as instruções contidas em Compilar as amostras do Windows Communication Foundation.

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

Para executar a amostra no mesmo computador

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

    Importante

    O arquivo de lote 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 do service\bin.

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

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

Para executar a amostra em vários computadores

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

  2. Copie os arquivos do 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 do programa do 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 de setup.bat com o argumento service 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 que ele reflita o novo nome do certificado (no atributo findValue em <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> do localhost para o nome totalmente qualificado do 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 de setup.bat com o argumento client cria um certificado de cliente chamado client.com e exporta o certificado do cliente para um arquivo chamado Client.cer.

  9. No arquivo Client.exe.config do computador cliente, altere o valor do endereço do ponto de extremidade para que ele corresponda ao novo endereço do 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 importará 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 importará o certificado do cliente do arquivo Client.cer para o repositório LocalMachine – TrustedPeople.

  13. No computador do servidor, inicialize Service.exe na janela do prompt de comando.

  14. No computador cliente, inicialize Client.exe na janela do prompt de comando. Se o cliente e o serviço não puderem se comunicar, confira Dicas de solução de problemas para exemplos de WCF.

Para fazer uma limpeza após o exemplo

  1. Execute Cleanup.bat na pasta de exemplos depois de concluir a execução do exemplo. Dessa forma, os certificados do servidor e do cliente são removidos do repositório de certificados.

Observação

Esse script não remove os certificados de serviço em um cliente na execução dessa amostra em vários computadores. Se você tiver executado exemplos do WCF (Windows Communication Foundation) que usam certificados em computadores, lembre-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.