24
votes

I'm working on a WCF service that is to be consumed by a client that is not developed by me and also it's not .NET (possibly Java).

In any case, the service should support mutual SSL authentication, where both the service and the client authenticate with certificates X.509 certs at the transport layer. The certificates have been exchanged between parties at a prior moment.

My problem is that I cannot seem to get the right WCF configuration such that client certificate authentication works correctly. What I expect is that, as part of the TLS handshake, the server also includes a Certificate Request, as seen below:

enter image description here

Following this, the client should answer with a `Certificate Verify' among other things:

enter image description here

The (latest) service configuration is this one. I'm using a custom binding, with authentication mode set to MutualSslNegotiated.

<bindings>
  <customBinding>
    <binding name="CarShareSecureHttpBindingCustom">
      <textMessageEncoding messageVersion="Soap11" />
      <security authenticationMode="MutualSslNegotiated"/>
      <httpsTransport requireClientCertificate="true" />
    </binding>
  </customBinding>
</bindings>

...

<serviceBehaviors>
  <behavior name="ServiceBehavior">
    <serviceMetadata httpGetEnabled="false" httpsGetEnabled="true" />
    <serviceDebug includeExceptionDetailInFaults="false" httpHelpPageEnabled="false" />
    <serviceCredentials>
      <serviceCertificate findValue="..." storeLocation="LocalMachine" x509FindType="FindByIssuerName" storeName="My" />
      <clientCertificate>
        <certificate findValue="..." storeName="My" storeLocation="LocalMachine" x509FindType="FindByIssuerName"/>
      </clientCertificate>
    </serviceCredentials>
  </behavior>
</serviceBehaviors>

The Server Hello part of the handshake looks like this for all service configurations I have tried, with no CertificateRequest. enter image description here

Other things I should mention:

  • The service is self hosted and listening on a non-default port (not 443). The server SSL certificate has been bound to this port.
  • I have also tried a basicHttpBinding and a wsHttpBidning with security mode set to Transport and client authentication set to Certificate, with no results (same results actually).

Any ideas would be appreciated.

2
Did you try to enable WCF tracing and see if there are any detailed errors in there?UserControl
@UserControl: Yes, tracing is enabled but there are no related errors unfortunately.Marcel N.
Wild shot - Any chance there are some root certificates from the client cert trust chain missing on the server side? That is, you have the client's cert, but some root certs in its chain are missing...?David W
@DavidW: Thanks David, however an error caused by what you described would have appeared a bit later in the SSL negotiation. In any case, I have solved the issue today but haven't got around to post the answer yet. Will do it in a bit. Everything was related to the way the SSL cert was bound to the port I was using. Turns out you have to enable client certificate negotiation manually. Apparently IIS creates the mapping (maybe even for port 443 - didn't test it) with it disabled too, so I'm not sure what the deal is.Marcel N.
Ahh, okay then. Glad you got it solved!David W

2 Answers

14
votes

OK, after a few more tries I figured it out. Posting this in case others run into the same issue.

I should continue by mentioning that this behavior really needs to be mentioned somewhere on MSDN, in a location that is really visible for anyone looking for WCF security information and not buried deep in some tool's documentation.

The platforms where I've been able to reproduce and fix this: Windows 8.1 x64 and Windows Server 2008 R2 Standard.

As I mentioned, my issue was that I could not configure WCF security such that the service would require client certificates. A common confusion that I noticed while looking for a solution is that many people believe that the client can send the certificate if it has it, unchallenged. This is, of course, not the case - the server needs to ask for it first and, moreover, specify which CAs are allowed through a CertificateRequest reply.

To summarize, my situation was:

  • Service is self-hosted.
  • Service runs on HTTPS, on a non standard port (not 443 but 9000).

This meant that I had to create an SSL certificate binding for port 9000 by using netsh.exe http add sslcert. Well, the binding had been created but there was a catch. I only found the issue after running netsh http show sslcert just to check on my binding:

 IP:port                      : 0.0.0.0:9000
 Certificate Hash             : ...
 Application ID               : ...
 Certificate Store Name       : MY
 Verify Client Certificate Revocation : Enabled
 Verify Revocation Using Cached Client Certificate Only : Disabled
 Usage Check                  : Enabled
 Revocation Freshness Time    : 0
 URL Retrieval Timeout        : 0
 Ctl Identifier               : (null)
 Ctl Store Name               : (null)
 DS Mapper Usage              : Disabled
 -->Negotiate Client Certificate : Disabled

The culprit was the last property of the binding, "Negotiate Client Certificate", documented here. Apparently, by default, this property is disabled. You need to enable it explicitly while creating the binding.

Recreating binding with the statement below solved the issue:

netsh.exe http add sslcert ipport=0.0.0.0:9000 certhash=... appid=... certstorename=MY verifyclientcertrevocation=Enable VerifyRevocationWithCachedClientCertOnly=Disable UsageCheck=Enable clientcertnegotiation=Enable

Prior to checking the bindings I tried hosting a simple WCF service in IIS and enable client certificate authentication from there. It was very curious to see that although there was no CertificateRequest issued by IIS, it still failed with a 403.7. Even IIS didn't create the binding with the appropriate parameters.

Anyway, now it works and this is how you can fix it.

Not to forget, the service configuration changed as well (the binding security) in order to allow certificate negotiation:

<customBinding>
  <binding name="CustomHttpBindingCustom" receiveTimeout="01:00:00">
    <textMessageEncoding messageVersion="Soap11" />
    <security authenticationMode="SecureConversation" requireSecurityContextCancellation="true">
      <secureConversationBootstrap allowInsecureTransport="false" authenticationMode="MutualSslNegotiated" requireSecurityContextCancellation="true"></secureConversationBootstrap>
    </security>
    <httpsTransport requireClientCertificate="true" />
  </binding>
</customBinding>
0
votes

I had the same issue when my bosses were questioning why was our IIS hosted WCF service which implemented "2 way SSL" (mutual certificate authentication) not observed to be sending "Certificate Request" in the TLS handshake. After some investigation, we finally found that the certificate port binding configuration for Negotiate Client Certificate is disabled.

Get the current binding information by running the below.

netsh http show sslcert

Get the certificate hash and the application GUID from the first record (or the relevant SSL port), then run the following netsh command using an administrator console on the IIS server.

netsh http add sslcert ipport=0.0.0.0:443 certhash=xxxx appid={xxxxx} clientcertnegotiation=enable

Note that if an existing binding already exists for the IIS address and port, the following error will be reported.

SSL Certificate add failed, Error: 183 Cannot create a file when that file already exists.

Run the delete command to remove the existing binding before retrying to add it back again.

netsh http delete sslcert ipport=0.0.0.0:443

After the reconfiguration, the observed Wireshark TLS handshake became as expected. However, in my opinion, this setting doesn't matter in the end as the client certification is used for authentication whether during the initial handshake or afterwards within the encrypted exchange and 2 way SSL is achieved.