Introduction
As far I can could find in my research, WCF does not make it easy to (figure out) how to enable both Transport-Layer client certificates and SOAP, message-layer client credentials. It took me a few days of messing around and researching before I was finally able to do it. It turns out it can be with a customBinding with a few small elements added to your config files...it's the fact that WCF hides it rather well that's the tough part.
Hopefully this will unhide it for someone out there.
Service Configuration
Here's how it's setup for the service side of things. I basically just added the security
element to allow for UserName authentication and the httpsTransport
for requiring Client Certificates
<customBinding>
<binding name="CustomBinding">
<textMessageEncoding messageVersion="Soap11" />
<security authenticationMode="UserNameOverTransport" />
<httpsTransport requireClientCertificate="true" />
</binding>
</customBinding>
<services>
<service name="MyNameSpace.WCFService.Something">
<endpoint
address="" binding="customBinding"
bindingConfiguration="CustomBinding"
name="CDARequestEndpoint"
contract="MyNameSpace.WCFService.ISomething"
bindingNamespace="urn:hl7-org:v3">
</endpoint>
</service>
</services>
Client Configuration
On the client side, since I'm working in Visual Studio, all I did was add a service reference to the WCF service configured above and it pulled down all the correct configuration and added to my app.config file (I have a Winforms test app). You could also just manually add this configuration to your config file and it would most likely work.
The only thing I added was the behavior shown below and associated it to the endpoint.
<system.ServiceModel>
<bindings>
<customBinding>
<binding name="CustomEndpointBinding">
<textMessageEncoding messageVersion="Soap11" />
<security authenticationMode="UserNameOverTransport" />
<httpsTransport requireClientCertificate="true" />
</binding>
</customBinding>
</bindings>
<behaviors>
<endpointBehaviors>
<behavior name="ohBehave">
<clientCredentials useIdentityConfiguration="false">
<clientCertificate findValue="6D0DBF387484B25A16D0E3E53DBB178A366DA954" storeLocation="CurrentUser"
x509FindType="FindByThumbprint" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<client>
<endpoint address="https://myservice.ca/MyService/Contract.svc"
binding="customBinding" bindingConfiguration="CustomEndpointBinding" behaviorConfiguration="ohBehave"
contract="MyService.Contract" name="CustomEndpointBinding" />
</client>
</system.serviceModel>
If you don't want to add the client certifcate using a behavior in the config, you can do it through code like so:
MyService.proxyClient client = new MyService.proxyClient("CustomEndpointBinding");
client.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindByThumbprint, "6D0DBF387484B25A16D0E3E53DBB178A366DA954");
When it comes to the WCF Client Credentials, those will always have to be added programatically as far as I know
client.ClientCredentials.UserName.UserName = clinicAdAcount;
client.ClientCredentials.UserName.Password = clinicPassword;
You can apparently create a behaviorExtension, but we'll leave that for another day.
Now, just be sure to set IIS to "Require SSL" and "Client Certificates" set to "Require"
Armed with this info, your WCF service should now require 2-layer authentication.
Please feel free to tweet @ me with any questions, critiques or comments.