This week I have been working on a project where two systems that exist in separate security domains need to be able to communicate sensitive information securely. The communications platform that I was using was Windows Communication Foundation (WCF).
I first started by implementing my web-service in WCF using the standard “basicHttpBinding” which doesn’t provide (by default) any authentication or message protection support. I did this because I wanted to focus on the implementation of my business logic first and then see whether the promise of WCF holds true about just being able to turn on security.
As far as security options go I could use either message or transport level security. I’m more of a message orientated guy so I opted to go down that route which meant that I could choose between the following approaches for authentication and protection:
- Windows Authentication (SSPI context token)
- Username and Password (only for authentication)
Out of that list the first one is definitely not viable because there is no way to establish a trusted connection and the second option whilst providing authentication couldn’t provide message protection and would need to rely on certificates.
Since I would need to be working with certificates I decided to just use them for everything (including authentication), but the next challenge was – how do I go about it?
A Simple Starting Point
I am a big fan of starting with something simple that works and then introducing only necessary complexity from there, so for the purposes of this little blog post I am going to expose a “hello world” service and expose it first using the “basicHttpBinding”. The service contract looks like this:
Given such a simple contract, the implementation should be fairly obvious:
I like to host my services inside console applications initially because I find it makes them easier to debug, then as I get ready for production I like to convert that console application into a Windows Service so that I can easily stop and start the web-service and know that it is definitely not available. The hosting code inside a console application for this is fairly basic:
All of these classes exist within the “ServerSide” namespace, although if you are building this service for real the host program, the service contract, and its implementation might be in different assemblies, and possibly different namespaces.
If you tried to execute the program now it would simply fall over because there is no matching configuration entries in the application configuration file – here is what it should look like when it crashes as a point of reference:
The minimal configuration file to get this working is presented below. The easy way to remember what you need to do is repeat “ABC” to yourself – address, binding, and contract.
In this case the address is derived from the base address “http://localhost:8999/HelloWorld/“, the binding is “basicHttpBinding” and the contract is the interface that the HelloWorld class implements, “IHelloWorld”. When we instantiate the service host, it uses the type name of the implementation class to find the <service /> node and then determine how to expose each service contract.
If you happen to be running Windows Vista you will need to run Visual Studio with administrative permissions to do this work. The reason is that self hosted services use HTTP.SYS (background) which allows multiple processes to listen on the same address and port, but map different URL spaces – but in order to do the mapping your process needs to be granted sufficient rights or it needs to be set up manually before hand. It is far easier to run with administrative rights during development in this case.
Whilst this service will start up and run we need some way of providing client applications a way of generating their web-service proxy classes and to do that we need to expose a WSDL contract and some service metadata. This means more configuration changes.
To do this we simply add a custom behavior to the service which tells the WCF plumbing to expose the WSDL contract:
Once this is done, all we need to do is hit the service with a browser at “http://localhost:8999/HelloWorld/” and we get the following page:
The link on that page will allow you to pull down the WSDL contact and we can now use this build up our client code. In this case my client program is just another console application to which I add a service reference (right mouse click on the console project and select “Add Service Reference…”).
I’m actually using Visual Studio 2008 Beta 1 on this machine so if you are using Beta 2 the dialog is a little bit different, but basically works the same way. You input the address of the web-service (no need to append the “?wsdl”, as it will auto-discover) and then give the reference a name. I typically give it the name of the service and append the word “Proxy” to avoid any ambiguity.
Once that is done implementing a test client is pretty simple:
One of the things that I don’t like about the way that the WCF proxy generator works is how it tends to over complicate the client-side configuration file. I’m not going to post what it generates here for the sake of brevity (going well so far eh?), but I will post a sample of what it can be reduced to:
All the settings that get generated have a place, but unless you need to tweak them you can probably live with the defaults. The code above basically replicates the client version of what the server has, except the client doesn’t expose any metadata and the name is what is used by the WCF proxy to find its configuration (in a world where you might have WCF calls going to different machines with different configurations).
If we run both the client and server now we can see that we have a simple working solution where the client is contacting the server and displaying its output.
Now that we have a simple working solution its time to look at getting a solution that uses certificates to authenticate the client and server with each other might look like.
Creating the Certificates
Before we can start using certificates to authenticate the client and server we actually need two certificates that we can use, one for the client and one for the server. To do this we use the “MakeCert.exe” utility with the following arguments (note, it is executed twice):
The first certificate that was created will be used by the server to authenticate itself to the client, and the second certificate will be used by the client to authenticate itself to the server.
Here is a brief explanation of what the various switches mean:
- -pe tells us that the certificates will be exportable which will be useful because we will need to move them around.
- -ss tells us which store to put them in, in this case My = Personal.
- -sr tells us which part of the registry to put them into, in this case we are going for local machine because ultimately we would want to run this stuff as a Windows Service and having to edit the store for a specific user account is too much of a hassle (at least for a demo).
- -a tells us what kind of algorithm we are going to use for hashing.
- -sky tells us what the purpose of the keys are, in this case we are going to use them for encryption (exchange) rather than just signing.
- -n tells us who the key is issued to. In the enterprise this is often the server hostname, but this is all on one box so we will issue it to ServerSide and ClientSide and then override the DNS identity mapping.
Now that the certificates are created we need to export them and re-import duplicates into the location that WCF expects to find them. This is actually where things get a little bit confusing and its certainly where I often find myself scratching my head.
We first need to bring up the Microsoft Management Console which allows us to browse the local machine’s cache of certificates. To do this launch “mmc.exe”, and you should be presented (after the UAC goodness in Vista) a window titled “Console1”.
We can’t use the “certmgr.msc” file because it is limited in that it only opens the current user certificate store. From “Console1” we need to add the Certificates MMC Snap-in, and when you do that you need to make sure that you are adding it for the computer, not the current user.
Once that is done you can complete the wizard and OK your way out to a functional MMC window, at which point you need to navigate to the “Personal” node to see our client side and server side certificates.
There are actually four different occasions where these certificates will be used.
- The client will use ClientSide to represent its identity to the server.
- The server will use ClientSide to validate the clients identity.
- The server will use ServerSide to represent its identity to the client.
- The client will use ServerSide to validate the servers identity.
Truth be told, this isn’t exactly how it would work in a fully fledged production scenario.
What would actually happen is that the server and client would validate each others identity by following a chain of trust associated with each certificate to a root certification authority which they both trusted (although it might be a different CA for each certificate as Windows can trust multiple). Over the Internet this might be a public one like VeriSign, or it could be a root CA inside the enterprise – in any case it is actually unlikely that the server would have access to the client certificate and vice versa.
What we will actually do to avoid all this infrastructure burden is use a “peer trust” model where each side does indeed have access to the certificates and can validate the messages being sent.
In order to support this we need to export the certificates from the certificate store and import copies into the TrustedPeople store so that WCF can find them for validation purposes.
When you export the certificates there is no need to export the private key as this is not required for validation purposes. Once you have finished this process, the TrustedPeople node inside the certificate store should look as follows (although obviously you won’t have a certificate issued to “mitch.denny” – or at least I hope not!):
Once this is done we can start the process of cutting our simple working demonstration over to using certificates. Sometimes I reflect on what certificates haven’t become more popular for signing things like e-mails, but when you have to go through the above it becomes a little bit clearer.
Configuring the Server
The first step in configuring the server is telling it that we want to protect the message using certificates. The easiest way to do this is switch over to the “wsHttpBinding” and customize the binding. The picture below shows the added <bindings /> element and the modified <endpoint /> element:
Next we need to tell the server which certificate it will use to identify itself, which certificates it can accept from the client and the fact that we are going to be using a peer-trust certificate validation approach. To do this we extend the existing “customBehavior” that we used to get metadata exchange working (WSDL contracts):
Notice that we pull the server certificate from the “My” (or Personal) store. Also notice that I haven’t specified anything about the client certificate. This is because of the <authentication /> element telling WCF to do peer-trust which means WCF will look for the certificate (containing only a public key) that the client provides in the “TrustedPeople” store.
Each store has a different configuration of certificate based on what we did setting them up. The “My” store contains a copies of the certificates with the private key, where as the “TrustedPeople” store contains copies of the certificates with only the public keys. You can see the Public Key Infrastructure underpinning all of this.
Now that we have the server configured, we need to look at the client because it needs to know to send the messages in the right format and also which certificates to use.
Configuring the Client
The client configuration is essentially a mirror image of the server configuration file, except that it will only issue one client certificate and it only needs to validate one server.
Once again we have to update the binding configuration to use a custom version of “wsHttpBinding” that uses certificates to support message level security.
Because the client doesn’t host the service we then need to configure a custom behavior on the endpoint. This custom behavior definition didn’t exist before like it did on the server so it needs to be added from scratch.
We are now almost there. If we run the code it will still crash, but for a relatively minor reason. That reason is that we are attempting to communicate to a host called “localhost”, yet the server is identifying itself as “ServerSide” as specified in the certificate. The WCF infrastructure considers this to be an error (correctly). However it is fairly common that this happens, especially with temporary certificates or within the enterprise. To work around this we explicitly tell the client endpoint definition what identity assertion to expect from the server, regardless of what hostname we are contacting it on – this is yet another configuration entry:
Finally, with all of the configuration done we can now run the program and see that the client is now securely communicating to the server. The most obvious sign that something is different is the extra time it takes to execute the code. This is because the handshake between the client and server takes more processing power and bandwidth.
Setting up secure, certificate based communications between a client and server using WCF can take a little bit of effort. Most of the time it is because we only do this configuration every now and then and when subsequently forget where the certificates need to go, and how to configure WCF to do precisely what we want. One of the reasons I wrote this post was because I had trouble finding a simple example of how to get this working on the web to refresh my memory, so hopefully you will find this useful as you are trying to remember how to do this yourself.
P.S. If you like a copy of the code I used for this post you can download it from here.