Service client with Mutual Authentication (2-way client certificate authentication)

c#, ssl, wcf

Solution

It sounds like what you're describing is using client-side certificate authentication in SSL/TLS. In that case, the client certificate you're using definitely needs to have a private key, which is necessary in order to complete the SSL/TLS handshake.

Notice that when configuring the client, you need to tell the binding to use client certificates for transport authentication:

<security mode="Transport">
    <transport clientCredentialType="Certificate" />
</security>

And then tell it how to locate the certificate you want to use using the ClientCertificate behavior:

<behaviors>
  <endpointBehaviors>
    <behavior name="ClientCertificateBehavior">
      <clientCredentials>
        <clientCertificate findValue="CN=clienttempcert" storeLocation="CurrentUser"
          storeName="My" x509FindType="FindBySubjectDistinguishedName" />
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>

Notice that the certificate you specify key MUST HAVE a private key that the client can load (the service, however, does not need the private key at all).

Problem

I'm trying to create a connection to a web service through their WSDL. I was told the service's authentication is described as TLS with authentication by exchanging certificates. I generated the client via the "Add Service Reference" in visual studio. When I send a command, I expect to see the "handshake" in wire shark but I do not even see the "Client Hello" initiation being sent. The service's authentication is described here: http://en.wikipedia.org/wiki/Transport_Layer_Security#Description My client is writen in c# Here is the full program I am running to test the connection(it starts with Run()): ``` public class ClientExample { private const string Url = "https://xxxxxxxxx"; public static void Run() { ServicePointManager.ServerCertificateValidationCallback += ValidateCertificate; ServicePointManager.SecurityProtocol = SecurityProtocolType.Ssl3; PerformTest("Clear Cache", GetBinding()); } private static Binding GetBinding() { var bec = new BindingElementCollection { new TextMessageEncodingBindingElement(MessageVersion.Soap12, Encoding.UTF8), new HttpsTransportBindingElement{ RequireClientCertificate = true } }; return new CustomBinding(bec); } private static void PerformTest(string test, Binding binding) { try { Console.ResetColor(); Console.ForegroundColor = ConsoleColor.Green; Console.WriteLine(test); Console.ResetColor(); var client = GetClient(binding); SendMessage(client); } catch (Exception e) { DisplayError(e); } } private static MyClient GetClient(Binding binding) { var endpointAddress = new EndpointAddress(Url); var client = new MyClient(binding, endpointAddress); if (client.ClientCredentials != null) { client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindBySubjectName, "xxxxxxxxxxxxx"); } return client; } private static void SendMessage(ChargePointServiceClient client) { var response = client.clearCache("xxxxxxxxxxxxx", new ClearCacheRequest()); Console.WriteLine(ClearCacheDescription(response)); } private static string ClearCacheDescription(ClearCacheStatus response) { switch (response) { case ClearCacheStatus.Accepted: return "Accepted"; case ClearCacheStatus.Rejected: return "Rejected"; } return "Unkown"; } private static bool ValidateCertificate(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors sslpolicyerrors) { switch (sslpolicyerrors) { case SslPolicyErrors.None: return true; case SslPolicyErrors.RemoteCertificateChainErrors: DisplayWarningMessage("RemoteCertificateChainErrors"); return false; case SslPolicyErrors.RemoteCertificateNameMismatch: DisplayWarningMessage("RemoteCertificateNameMismatch"); return false; case SslPolicyErrors.RemoteCertificateNotAvailable: DisplayWarningMessage("RemoteCertificateNotAvailable"); return false; default: DisplayWarningMessage("Unkown Certificate Validation Error"); return false; } } private static void DisplayError(Exception exception) { if (exception == null) return; Console.BackgroundColor = ConsoleColor.DarkRed; Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(@"Exception"); Console.ResetColor(); Console.WriteLine(exception.Message); if (exception.InnerException != null) Console.WriteLine(); DisplayError(exception.InnerException); } private static void DisplayWarningMessage(string message) { Console.BackgroundColor = ConsoleColor.DarkYellow; Console.ForegroundColor = ConsoleColor.White; Console.WriteLine(message); Console.ResetColor(); } } ``` I logged all network traffic via system.diagnostic in my app.config: ``` <system.diagnostics> <sources> <source name="System.Net"> <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" traceOutputOptions="None" initializeData="network.log" /> </sharedListeners> <trace autoflush="true"/> </system.diagnostics> ``` Here are some of the log lines of interest: This confirms that a tls stream is created: ``` System.Net Information: 0 : [9040] TlsStream#50727427::.ctor(host=xxxxx, #certs=1) ``` A secure channel is created: ``` System.Net Information: 0 : [9040] SecureChannel#11159819::.ctor(hostname=xxxxxxx, #clientCertificates=1, encryptionPolicy=RequireEncryption) System.Net Information: 0 : [9040] Enumerating security packages: System.Net Information: 0 : [9040] Negotiate System.Net Information: 0 : [9040] NegoExtender System.Net Information: 0 : [9040] Kerberos System.Net Information: 0 : [9040] NTLM System.Net Information: 0 : [9040] TSSSP System.Net Information: 0 : [9040] pku2u System.Net Information: 0 : [9040] Schannel System.Net Information: 0 : [9040] Microsoft Unified Security Protocol Provider System.Net Information: 0 : [9040] LiveSSP System.Net Information: 0 : [9040] WDigest System.Net Information: 0 : [9040] CREDSSP System.Net Information: 0 : [9040] SecureChannel#11159819 - Attempting to restart the session using the user-provided certificate: [Version] ``` Not sure why a private key is being looked up: ``` System.Net Information: 0 : [9040] SecureChannel#11159819 - Left with 1 client certificates to choose from. System.Net Information: 0 : [9040] SecureChannel#11159819 - Trying to find a matching certificate in the certificate store. System.Net Information: 0 : [9040] SecureChannel#11159819 - Locating the private key for the certificate: [Version] ``` At this point in the log, I see that certificates are exchanged. Unfortunately wire shark doesn't confirm this... Now my program verifies the services certificate and starts processing ``` System.Net Information: 0 : [9040] SecureChannel#11159819 - Remote certificate was verified as valid by the user. System.Net Information: 0 : [9040] ProcessAuthentication(Protocol=Ssl3, Cipher=Rc4 128 bit strength, Hash=Sha1 160 bit strength, Key Exchange=RsaKeyX 2048 bit strength). ``` I then see some encrypted data exchanged and my clear cache command is sent I receive an encrypted response from the service but the message indicates a Fault. I assume this is because the authentication wasn't accepted by the service ``` System.Net Error: 0 : [9040] Exception in HttpWebRequest#46890055::GetResponse - The remote server returned an error: (500) Internal Server Error.. ``` Here is the filter I am using in wireshark (both IPs are the service not mine) ``` (ip.src == xxx.xxx.xxx.xx or ip.dst == xxx.xxx.xxx.xx) and ssl.handshake ``` Also, the certificate I am using does not have a private key. I assume that I shouldn't need one from the TLS documentation. So my question is why I do not see the Client Hello/Server Hello in wireshark when I run the program or how should I configure the client binding to initiate the client hello? (I tagged wcf because I assume wcf professionals might know the answer to my question. My solution will be independent of wcf because I do not have control of the services bindings)

Original source