If you read my previous post concerning
Transport Security on a Self-Hosted WCF service,
you may realize that I was using a digital certificate (X.509) on a
basicHttpBinding to encrypt our channel, but with no client
authentication. WCF supports several types of client authentication:
Basic, Digest, NTLM, Windows and Certificate. I'll focus this post on a
client authentication, or if you want, Transport Client Credential:
Certificate.
With transport security settled to certificate and a
client credential type as certificate were are able to mutually
authenticate the server to the client (the SSL connection) and the
client to the server (through the client credential).
The first thing to do is to enable transport security. I'll not go on this, because my previous post discusses exactly this, check it out.
Assuming
that you have already transport security running ok, you will need to
add a few more information into your configuration file:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<bindings>
<basicHttpBinding>
<binding name="bindingWithTransportSecurity">
<security mode="Transport" >
<transport clientCredentialType="Certificate" proxyCredentialType="None" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<serviceBehaviors>
<behavior name="myServiceBehavior">
<serviceMetadata httpsGetEnabled="true" />
</behavior>
</serviceBehaviors>
</behaviors>
<services>
<service behaviorConfiguration="myServiceBehavior" name="Demo.HelloWorldService">
<endpoint
address="https://MYSERVER:8888/demo"
binding="basicHttpBinding"
bindingConfiguration="bindingWithTransportSecurity"
contract="Demo.IHelloWorldService" />
<host>
<baseAddresses>
<add baseAddress="https://MYSERVER:8888/demo" />
</baseAddresses>
</host>
</service>
</services>
</system.serviceModel>
</configuration>
The thing here, is to define the client credential type has shown
in bold in the server config file. On the client side, your client
configuration file must be compliant with the server configuration. The
client configuration follows:
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.serviceModel>
<behaviors>
<endpointBehaviors>
<behavior name="clientBehavior">
<clientCredentials>
<clientCertificate
findValue="Abel Eduardo Pereira"
storeLocation="CurrentUser"
x509FindType="FindBySubjectName"
storeName="My" />
</clientCredentials>
</behavior>
</endpointBehaviors>
</behaviors>
<bindings>
<basicHttpBinding>
<binding name="basicHttpClientBinding">
<security mode="Transport">
<transport clientCredentialType="Certificate" proxyCredentialType="None" realm="" />
</security>
</binding>
</basicHttpBinding>
</bindings>
<client>
<endpoint
address="https://sdev44001:8888/demo
binding="basicHttpBinding"
bindingConfiguration="basicHttpClientBinding"
contract="Demo.Proxy.IHelloWorldService"
behaviorConfiguration="clientBehavior"/>
</client>
</system.serviceModel>
</configuration>
At bold, you can find the relevant parts in the client
configuration file. The first bolded part, specifies the certificate
that will be used for client authentication. This certificate was
generated as being a
Client Authentication type certificate from the Certificate Authority.
The second bolded part indicates that we will be using a digital certificate as the client credential.
This is all you have to do on your client and server configuration files. However, not enough to put working.
The
next step is to reconfigure the server certificate binding to the port
to allow client certificates, enabling mutual authentication. Assuming
that you have read and tried to do what's in my previous post, you
first need to clear the configuration you done with the HttpCfg.exe
tool:
C:\Program Files\Support Tools>httpcfg delete ssl -i 0.0.0.0:8888
HttpDeleteServiceConfiguration completed with 0.
The
previous command removes the certificate binding to the IP 0.0.0.0
(0.0.0.0 represents the machine address, so it will bind to every IP
addresses your machine has) and port 8888 you had previously
configured. You should ignore this command if you haven't done any
IP:Port - certificate binding.
Now, you need to configure to enable mutual authentication:
C:\Program Files\Support Tools>httpcfg set ssl -i 0.0.0.0:8888 -h e561943dac9441e1ecaafb064e86b047672adb2d -f 2 -m 1
HttpSetServiceConfiguration completed with 0.
In the blue part, we define the IP & port to which we want to bind your certificate used to enable transport security.
In
the red part, we indicate the certificate being used to enable
transport security. We specify the certificate by its SHA1 hash. Once
again, check the previous port for further details.
In the green
part, we indicate we want to negociate the client certificate. This is
the key point that enables the client credential to be accepted from
the server.
Finally, in the orange part, we specify the certificate
check mode. In this case, we are specifying that the client certificate
will not be verified for revogation (something you don't want to do in
a production environment).
Now we are almost done. Just a few
check ups and we are ready to hit F5. You will mainly need this if you
don't have a Certificate Authority available or if your certificates
are issued from the makecert.exe tool.
Your client has to trust
the server certificate (the certificate used enabling transport
security, - SSL). Yet, it needs the public key of the server
certificate.
If you don't have a Certificate Authority that can tell
you whether the server certificate is trustable or not you should add
the root certificate (that makes part of the server certificate) into
the Local Computer | Trusted Root Certificate Authorities. Also, you
need to install the server certificate public key on the client.
You can install the server certificate public key on the client, by exporting the server certificate to a file:



Now, go to the client and import the server certificate into
Current User store | Personal.
Now import the root certificate into
Current User store | Trusted Root Certification Authorities using the same procedure:

What
you have done so far was to give the server's certificate (containing
the public key) to the client and made the client trust this
certificate. This should only be necessary if you don't have a
certificate authority in place.
The next step is to make the client
certificate trustable by the server. You need to execute the inverse
procedure to allow the server to trust the client.
And now, the good part: run your service. Invoke a service operation from the client, and check the
OperationContext.Current.ServiceSecurityContext.PrimaryIdentity
The PrimaryIdentity should contain the client identity based on the client certificate.