Troubleshooting client certificate authentication when calling web service from ASP.net web application

Recently I came across issues where client certificate authentication was failing when calling a web service from ASP.net web application. 

Worked through the steps mentioned in the following articles but couldn’t identify the cause of issue. 

How to call a Web service by using a client certificate for authentication in an ASP.NET Web application

https://support.microsoft.com/kb/901183 

Required permissions when calling a Web service using client certificate for authentication in an ASP.NET Web application

https://blogs.msdn.com/b/saurabh_singh/archive/2009/07/03/required-permissions-when-calling-a-web-service-using-client-certificate-for-authentication-in-an-asp-net-web-application.aspx

 

Finally wrote a sample application to troubleshoot the issue and was able to identify the cause of issues in different scenarios.

Prepared a checklist to verify the settings in similar scenarios: 

  • Client certificate should be added to “Local System” certificate store. 
  • Private key should be present for the certificate. 
  • Server certificate for the remote server should be added to "Trusted People" store of Local computer. 
  • Check permissions on certificate private key using WSE tool and we see Application pool identity has full control on the it. 

          Download Web Services Enhancements (WSE) 2.0 SP3 for Microsoft .NET tools to troubleshoot permissions on the certificate private key.

          https://www.microsoft.com/download/en/details.aspx?displaylang=en&id=23689    

  • We can add permissions using WSE tool or run WinHttpCertCfg tool to set permissions on certificate.

          WinHttpCertCfg.exe -g -c LOCAL_MACHINE\MY -s "<issued-to name of client certificate" -a "Network Service"

 

How to use the sample 

If steps given above do not help, you can use the sample I have created and collect System.Net traces. 

  • Ø Copy “sample” folder under the problem website and “samplewebservice” under problem web service. 
  • Ø Covert them to applications. 
  • Ø Test web service and verify if we are able to browse HelloWorld service. 
  • Ø Give application pool identity write access on “sample” folder so that it can create network.log. 
  • Ø Export client certificate without private key into myclientcert.cer file. 
  • Ø Copy the URL of web service. 
  • Ø Open sample folder and open default.aspx in a notepad. 
  • Ø Make following changes to default.aspx:

 

void Page_Load(Object sender, EventArgs e)

{

                // TODO: Replace C:\myclientcert.cer with the path of your certificate file.

            string certPath = @"C:\myclientcert.cer";

            // Instantiate the Web service proxy

            Service1 serv = new Service1();

            // Please use the URL of your web service here

            serv.Url = @"https://amol-wffsec.fareast.corp.microsoft.com/samplewebservice/Service1.asmx";

            

            // create an X509Certificate object from the information

            // in the certificate export file and add it to the

            // ClientCertificates collection of the Web service proxy

            serv.ClientCertificates.Add(

                X509Certificate.CreateFromCertFile(certPath));

            try

            {

                string result = serv.HelloWorld();             

                Label mylbl = new Label();

                Form.Controls.Add(mylbl);

                mylbl.Text = "Contacted Web Service and " + result;

            }

            catch (Exception ex)

            {

                if (ex is WebException)

                {

                    WebException we = ex as WebException;

                    WebResponse webResponse = we.Response;

                    //throw new Exception("Exception calling method. " + ex.Message);

                   string urldetails = "empty string";

                    if (serv != null)

                    {

                        urldetails = serv.ToString();

                    }

                                    else

                                    {

                                                urldetails = "service details not available";

                                    }

                    Label mylbl = new Label();

                    Form.Controls.Add(mylbl);

                    mylbl.Text = "Exception calling method. " + ex.Message + "::" + urldetails;

                }

                 }

}

 

  • Browse default.aspx under sample directory. 
  • For failure scenario, it will show us message like “Exception calling method…..”. 
  • Get network.log generated under sample directory. 

If the issue is with certificate on remote machine where web service is hosted we will see something like this is System.Net traces:

 

[Public Key]

Algorithm: RSA

Length: 2048

Key Blob: 30 82 01 0a 02 82 01 01 00 b7 87 95 b3 a8 dd 6d e1 0e 8b fc ee bb c7 47 3e f8 e5 4b 97 96 6a 6b 1b 14 f5 01 0e 0f 1d 5b a2 74 8....

System.Net Information: 0 : [4788] SecureChannel#62122136 - Remote certificate has errors:

System.Net Information: 0 : [4788] SecureChannel#62122136 - Unknown error.

System.Net Information: 0 : [4788] SecureChannel#62122136 - Remote certificate was verified as invalid by the user.

System.Net.Sockets Verbose: 0 : [4788] Socket#55433994::Dispose()

System.Net Error: 0 : [4788] Exception in the HttpWebRequest#45215040:: - The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

System.Net Error: 0 : [4788] Exception in the HttpWebRequest#45215040::EndGetResponse - The underlying connection was closed: Could not establish trust relationship for the SSL/TLS secure channel.

 

Similarly if the issue is with client certificate, we can easily identify that using System.Net traces. 

If you would like to enable System.net tracing for you application directly instead of using the sample, this can be done easily by using System.Diagnostics tag. “System.Diagnostics” tag need to be added to application web.config under “configuration” section. Please remember to give write permissions on the web site directory to the app pool identity account so that it can create network.log file.

<system.diagnostics>

            <sources>

                  <source name="System.Net" tracemode="includehex" maxdatasize="1024">

                        <listeners>

                              <add name="System.Net"/>

                        </listeners>

                  </source>

                  <source name="System.Net.Sockets">

                        <listeners>

                              <add name="System.Net"/>

                        </listeners>

                  </source>

                  <source name="System.Net.Cache">

                        <listeners>

                              <add name="System.Net"/>

                        </listeners>

                  </source>

            </sources>

            <switches>

                  <add name="System.Net" value="Verbose"/>

                  <add name="System.Net.Sockets" value="Verbose"/>

                  <add name="System.Net.Cache" value="Verbose"/>

            </switches>

            <sharedListeners>

                  <add name="System.Net" type="System.Diagnostics.TextWriterTraceListener" initializeData="network.log"/>

            </sharedListeners>

            <trace autoflush="true"/>

</system.diagnostics>