WCF: Basic HTTP Profile with Mutual Certificate Authentication

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),
      new Uri(https://localhost:9500/service”));
 
    h.Open();
 
    Console.WriteLine(“Listening…”);
    Console.ReadLine();
  }
}
 
 
<?xml version=1.0 encoding=utf-8 ?>
<configuration>
      <system.serviceModel>
            <services>
                  <service type=Program>
                        <endpoint address=https://localhost:9500/service
                                          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
 
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>
                  <endpoint address=https://localhost:9500/service
                                     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.