Mutual SSL authentication with WCF: no CertificateRequest and CertificateVerify in handshake phase

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>