Following up on this post I spent a really long time trying to get WCF (Nov CTP) to work with mutual authentication over the basic HTTP profile that I thought I’d try and do a complete write up of what I did in order to share it.
In the end ( like most things ) it turns out to be pretty easy to get what I was trying to do to work but I didn’t know where I was going wrong and so I spent an awfully long time on it.
I’m going to go through this step by step for my setup – note: this is not necessarily the “right” way to do it but it works for me and I want to be able to remember it 🙂
1) I’m using a certificate server rather than makecert. I set up a new certificate server in a VPC image and I called it “TestCA” – this is because I tend to dev on XP rather than Server 2003 which often causes me pain.
2) I visited my TestCA certificate server and I requested two certificates both using the “Advanced Request Form”.
i) “ClientCert”. I went for all the defaults on this apart from selecting that I want the private keys to be exportable. I gave this a name of “ClientCert” and filled in all the other fields such as email address with any old values.
ii) “ServerCert”. For this I gave the certificate a name of “localhost” ( or your machine name ). I think you need to do this to get things to work. I also selected “Server Authentication Type” for the type of certificate and made the keys exportable again.
3) I went and installed those newly issued certificates inside my VPC and used the MMC again to export them to my main PC. I exported;
i) The “ClientCert” from my CurrentUser\Personal store ( used a .pfx for this and exported the private key )
ii) The “localhost” from my CurrentUser\Personal store ( used a .pfx for this and exported the private key )
iii) The “TestCA” cert from my LocalMachine\Root store (used a .cer for this )
4) I imported the certs into the right place on my machine;
i) “ClientCert” into CurrentUser\Personal
ii) “localhost” into LocalMachine\Personal
iii) “TestCA” into LocalMachine\Root
So, now if I double click one of those certs in the MMC snap-in it shows up as “trusted” and good because I’ve made “TestCA” trusted.
5) I now went and built a new “hello world” style service. Here’s the code and the config;
using System;
using System.ServiceModel;
[ServiceContract]
interface IOperate
{
[OperationContract]
void Operate(string parameter);
}
class Program : IOperate
{
public void Operate(string parameter)
{
Console.WriteLine(“Received {0}”, parameter);
}
static void Main(string[] args)
{
ServiceHost h = new ServiceHost(typeof(Program),
h.Open();
Console.WriteLine(“Listening…”);
Console.ReadLine();
}
}
<?xml version=“1.0“ encoding=“utf-8“ ?>
<configuration>
<system.serviceModel>
<services>
<service type=“Program“>
contract=“IOperate“
binding=“basicHttpBinding“
bindingConfiguration=“bind“/>
</service>
</services>
<bindings>
<basicHttpBinding>
<binding name=“bind“>
<security mode=“Transport“>
<transport clientCredentialType=“Certificate“/>
</security>
</binding>
</basicHttpBinding>
</bindings>
</system.serviceModel>
</configuration>
6) I went and used HTTPCFG.EXE to configure up port 9500 for SSL using my “localhost” certificate from before which has a thumbprint of 65f279af32580041360d8a9b397baec0146759a0;
>httpcfg.exe set ssl -f 2 -m 1 -i 0.0.0.0:9500 -h 65f279af32580041360d8a9b397bae
c0146759a0
c0146759a0
some notes here: the “-f 2” comes from Gudge’s post here and is around mutual authentication. The “-m 1” I added myself and it’s to stop the attempt to do revocation checking when the certificate arrives. I did this whilst running as admin so I think I ducked a bit of extra work that you’d need to do in the real world.
7) I wrote a client to hit against the service…here’s the client code and the client config. The config includes the thumbprint for the certificate I called “ClientCert” back in step (2) above.
using System;
using System.ServiceModel;
[ServiceContract]
interface IOperate
{
[OperationContract]
void Operate(string parameter);
}
class Proxy : ClientBase<IOperate>
{
public void Operate(string parameter)
{
base.InnerProxy.Operate(parameter);
}
}
class Program
{
static void Main(string[] args)
{
Proxy p = new Proxy();
p.Operate(“Hello”);
Console.ReadLine();
}
}
<configuration>
<system.serviceModel>
<client>
contract=“IOperate“
binding=“basicHttpBinding“
bindingConfiguration=“bind“
behaviorConfiguration=“behave“/>
</client>
<bindings>
<basicHttpBinding>
<binding name=“bind“>
<security mode=“Transport“>
<transport
clientCredentialType=“Certificate“/>
</security>
</binding>
</basicHttpBinding>
</bindings>
<behaviors>
<behavior name=“behave“>
<clientCredentials>
<clientCertificate
x509FindType=“FindByThumbprint“
findValue=“2306186c1ba3deec91acd66f898edc0e765a5e9b“/>
</clientCredentials>
</behavior>
</behaviors>
</system.serviceModel>
</configuration>
That’s it – all worked for me and it sounds like it took me 10 minutes but it took me a awfully lot longer to actually get things working.
Why did it take so long?
Essentially I spent ages playing with makecert and never quite got what I wanted from it (my fault I think).
I also found that diagnostics seem to be a bit difficult. The server keeps replying with a “Yes/No” when presented with a client certificate but it gives you little information about how it’s making that decision. Probably a good thing but I was crying out for a “Give me verbose errors” mode – if there is one then someone letting me know would be great.
The one thing I’d say is that I found that just hitting against the server with Internet Explorer and trying to provide a client certificate from there worked as a reasonable diagnostic tool on the grounds that if IE couldn’t talk SSL to my service then I guessed that my client wouldn’t have a chance.