2
votes

I have built a WCF helloworld client and server. I want to use certificate authentication between them.

The error I get is "The caller was not authenticated by the service."

I have created two certificates using makecert.exe. The certificate on the client is installed under 'Personal' and 'Trusted People' and 'Third-Party Root Certification Authorities'. I copied the certificate as I don't know if it should be only under one heading

My server webconfig is as below

            <?xml version="1.0"?>
            <configuration>
              <appSettings>
                <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
              </appSettings>
              <system.web>
                <compilation debug="false" targetFramework="4.5.1"/>
                <httpRuntime targetFramework="4.5.1"/>
              </system.web>
              <system.serviceModel>
                <services>
                  <service behaviorConfiguration="BusinessToBusiness" name="TestHelloWork.Service1">

                    <endpoint address="" binding="wsHttpBinding"  bindingConfiguration="BindingConfig" contract="TestHelloWork.IService1" /> 
                            <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange"/>
                    <host>
                      <baseAddresses>
                        <add baseAddress="http://win-gat-web01:7777/Service1"/>
                      </baseAddresses>
                    </host>
                  </service>
                </services>
                <bindings>  
                  <wsHttpBinding>  
                    <binding name="BindingConfig">
                     <security>
                        <message clientCredentialType = "Certificate"/>
                     </security>
                    </binding>  
                  </wsHttpBinding>  
               </bindings>  
                <behaviors>
                  <endpointBehaviors>
                    <behavior name="webBehavior">
                      <webHttp/>
                    </behavior>
                  </endpointBehaviors>
                  <serviceBehaviors>
                    <behavior>
                      <!-- To avoid disclosing metadata information, set the values below to false before deployment -->
                      <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
                      <!-- To receive exception details in faults for debugging purposes, set the value below to true.  Set to false before deployment to avoid disclosing exception information -->
                      <serviceDebug includeExceptionDetailInFaults="true"/>
                    </behavior>
                    <behavior name="BusinessToBusiness">
                     <serviceCredentials>
                        <clientCertificate>
                           <authentication certificateValidationMode = "PeerTrust"/>
                        </clientCertificate>
                        <serviceCertificate findValue="WCfServer"
                storeLocation="LocalMachine"
                storeName="My"
                x509FindType="FindBySubjectName" />         
                     </serviceCredentials>
                     <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
                    </behavior>     
                  </serviceBehaviors>
                </behaviors>
                <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
              </system.serviceModel>
            </configuration>

The client side web.config is as below

            <?xml version="1.0"?>

            <!--
              For more information on how to configure your ASP.NET application, please visit
              http://go.microsoft.com/fwlink/?LinkId=169433
              -->

            <configuration>

                <system.web>
                  <compilation debug="true" targetFramework="4.5.1" />
                  <httpRuntime targetFramework="4.5.1" />
                </system.web>
              <system.serviceModel>
            <bindings>
              <wsHttpBinding>
                <binding name="WSHttpBinding_IService1">
                  <security>
                    <message clientCredentialType="Certificate" />

                  </security>
                </binding>
              </wsHttpBinding>
            </bindings>
            <client>
              <endpoint address="http://myserver:7777/Service.svc" binding="wsHttpBinding"
                bindingConfiguration="WSHttpBinding_IService1" contract="ServiceReference1.IService1"
                name="WSHttpBinding_IService1" behaviorConfiguration="CustomBehavior">
                <identity>
                  <dns value="WCfServer" />
                </identity>
              </endpoint>
            </client>
            <behaviors>
              <endpointBehaviors>
                <behavior name="CustomBehavior">
                  <clientCredentials>
                    <clientCertificate findValue="WcfClient" x509FindType="FindBySubjectName"

                      storeLocation="CurrentUser" storeName="My" />
                    <serviceCertificate>
                      <authentication certificateValidationMode="PeerTrust"/>
                    </serviceCertificate>
                  </clientCredentials>
                </behavior>
              </endpointBehaviors>
            </behaviors>
            </system.serviceModel>
            </configuration>

The client side certificate is installed under 'Personal'

Any idea what could be wrong? I have googled and realised that they should be on same domain? But they are. Also how will the domain work when my service is external?

2

2 Answers

3
votes

Try to enable CAPI2 log. It is special log (not enabled by default) that contains certificate validation information. If your problem lies is in certificate validation procedure failing you will find useful info there. Look for errors. In my case it was something like

  • A certificate chain processed, but terminated in a root certificate which is not trusted by the trust provider.
  • A certificate chain could not be built to a trusted root authority.
  • revocation function was unable to check revocation ...

CAPI2 log

In my case I've had

  • Client.pfx imported to CurrentUser\My (personal) store on client.
  • CA certificate (ClientCA) that issued client.pfx was imported to Trusted root authorities at both LocalMachine store on client and LocalMachine store on server.
  • server.pfx imported in LocalMachine\My (personal) store on server
  • CA certificate (ServerCA) that issued server.pfx imported to Trusted root authorities at both LocalMachine store on client and LocalMachine store on server.
  • CRL to Client CA available from both client and server
  • CRL to Server CA available from both client and server

But you are using PeerTrust certificate validation mode so according to this comment from WCF demos I have

Setting the certificateValidationMode to PeerOrChainTrust means that if the certificate is in the user's Trusted People store, then it will be 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 certificate authority (CA).

I would assume that certificates should be placed like this:

  • client.cer (only certificate from client.pfx) imported to Trusted people store in LocalMachine store on server
  • server.cer (only certificate from client.pfx) imported to Trusted people store in CurrentUser store on client

Verify that you have granted rights on server private key to AppPool that your IIS WCF service runs under (default pool is IIS APPPOOL\DefaultAppPool) It can be done using mmc or certlm.msc by right clilcking on server certificate then All Tasks -> Manage Private Keys .... Verify that you do NOT have selected AD because IIS APPPOOL is a local group. Add account IIS APPPOOL\your_pool_name and hit OK.

That will work if you have default settings in your AppPool like Identity is set to AplicationPoolIdentity and not a custom account (it is common to use managed service account from AD) and Load User Profile is set to true.

Add rights to server private key Default app pool settings

0
votes

The WCF certificate authentication issue you are encountering is most likely related to the options used when generating your self-signed certificates with MakeCert.

In particular, make sure your certificate supports the necessary options/purposes. (e.g. Intended Purposes field of the certificate should include an appropriate value, such as "Server Authentication" or "Client Authentication".)

The following link from Microsoft details the process.
https://docs.microsoft.com/en-us/dotnet/framework/wcf/feature-details/how-to-create-temporary-certificates-for-use-during-development

Note: our team has had good luck using the SelfCert tool for quickly generating development, self-signed certificates.