Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

SSL with Self-hosted WCF Service

4.86/5 (50 votes)
1 Mar 2008CPOL6 min read 3  
An article on the sometimes esoteric nature of certificates and WCF

Introduction

In order to perform any kind of SSL encryption between a client and a server, there need to be certificates in place. Certificates can seem a bit arcane to the uninitiated, especially when mixed in with some bizarre WCF configuration settings, but never fear, it's all here.

The reason I'm writing this article (my first) is that I really battled to get this working, and the information I need was scattered all over the place. I also ran into a lot of runtime errors which ended up being caused by setup issues much earlier in the process. Very very annoying. Hopefully this will save someone some trouble!

I'm assuming that you are familiar with the basics of WCF and are comfortable with configuration files and the like. I'm not including any source code as the service itself is actually not particularly important, SSL is all about setup.

Certificates

This example assumes that you (like me) don't have access to a nice shiny Certificate Authority (CA) and need to make use of the makecert tool. (Sparse information can be found here.)

Note: I have recently discovered this nifty Website, a free CA, who would have thought about it.

You need one certificate (cert) to act as your root authority, and one to act as the actual certificate to be used for the SSL, which needs to be signed by your root authority. If you don't set up the root authority your single certificate won't be trusted, and you will start to discover this through a series of extremely annoying WCF exceptions, long after the fact.
The following command (run in a the Visual Studio command prompt) will create your root certificate, no, I didn't come up with this myself, you can find some nice examples all over the place, for example here).

makecert -sv SignRoot.pvk -cy authority -r signroot.cer -a
    sha1 -n "CN=Dev Certification Authority" -ss my -sr localmachine

Take a look at the above links to see what each of these arguments mean, it isn't terribly important, but it's nice to know.

Once this command has been run and succeeded, you need to make this certificate a trusted authority. You do this by using the MMC snap in console. Go to the run window and type "mmc", hit enter. Then in the window that opens (called the "Microsoft Management Console, for those who care) perform the following actions.

File -> Add/Remove Snap-in -> Add… -> Double click Certificates -> Select Computer Account and Click Next -> Finish -> Close -> OK

Then select the Certificates (Local Computer) -> Personal -> Certificates node.

Personal Certificates

You should see a certificate called "Dev Certificate Authority" (or whatever else you decided to call it). Move this certificate from the current node to Certificates (Local Computer) -> Trusted Root Certification Authorities -> Certificates node, drag and drop works happily.

Trusted Certificates

Now you have NOT the cert you need :)
You have made yourself able to create trusted certs though, which is nice.
Now you have to create another cert, which you are actually going to use.
Run makecert again, but run it as follows...

makecert -iv SignRoot.pvk -ic signroot.cer -cy end -pe -n
    CN="localhost" -eku 1.3.6.1.5.5.7.3.1 -ss my -sr
    localmachine -sky exchange -sp
"Microsoft RSA SChannel Cryptographic Provider" -sy 12

Note that you are using the first certificate as the author for this latest one. This is important... where I have localhost you need to put the DNS name of your box. In other words, if you deploy your service such that its endpoint reads http://bob:10010/Service then the name needs to be bob. In addition, you are going to need to do this for each host you need to run as (yes, so one for bob and another one for localhost).

Get the signature of your cert by double clicking on the cert (Select the Certificates (Local Computer) ' Personal ' Certificates), opening the details tab, and scrolling down to the "Thumbprint" option.

Thumbprint with carefully sprayed values

Select the thumbprint and copy it. Put it in Notepad or any other text editor and replace the spaces with nothing. Keep this. (Take a look here for more on how to do this, although this link is rather unnecesary.)
You have your certs set up.

Configure a Port with an SSL certificate

Now you get to use another fun tool, httpcfg. (For more information, look here.) Firstly run the command below to check that you don't have anything running on a port you want.

httpcfg query ssl
Mine looks like this

Note that there are two ports, one will work for requests to chrise, one will work for localhost, keep track of which is which.

If this is your first time doing this, it should just return a newline. If there is already SSL set up on the exact IP you want to use (or if later on you need to delete any mistakes) you can use the following command, where the IP and the port are displayed as a result from the previous query.

httpcfg delete ssl -i 0.0.0.0:8888

When you have finished messing about with the above two commands, run the following command to associate a given certificate (thus allowing asymmetric encryption happiness) with a given port.
Note that the IP 0.0.0.0 is a special one indicating every IP on this PC.

httpcfg set ssl -i 0.0.0.0:8012 -h abababababababababababababab

Where the last bit (ab^n) is the thumbprint you kept earlier (you did keep it right?). Great, you now have an SSL enabled port.

The WCF Service

Finally!
Most of the work that needs to be done in WCF service for SSL is in the configuration. I prefer configuration files myself, but this can also be done in code.

The service configuration needs to look like this:

XML
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
 <system.serviceModel>
    <behaviors>
      <serviceBehaviors>
        <behavior name="NewBehavior">
          <serviceMetadata httpsGetEnabled="true"/>
         </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <basicHttpBinding>
        <binding name="Binding">
          <security mode="Transport">
            <transport clientCredentialType="None"/>
          </security>
         </binding>
      </basicHttpBinding>
    </bindings>
    <services>
      <service behaviorConfiguration="NewBehavior" 
            name="TestWCF.ServiceImplementation.TestWCFService">
        <endpoint
          address="https://chrise:10081/TestWCFService"
          binding="basicHttpBinding"
          bindingConfiguration="Binding"
          name="TestWCFService.Http"
          contract="TestWCF.ServiceContracts.ITestWCFService" />
        <host>
          <baseAddresses>
            <add baseAddress="https://chrise:10081/TestWCFService" />
          </baseAddresses>
        </host>
      </service>
    </services>
  </system.serviceModel>
 </configuration>

Note that the httpsGetEnabled is set to true. This means that even your mex is encrypted, nifty! Also note that chrise which is the hostname in this case MUST have a corresponding certificate, or no dice.
I'm using the basic HTTP binding. You could do it with wsHTTP as well, but what's the point of getting encrypted messages if your entire transport is encrypted anyways?
Note that clientCredentialType is set to none, this is just to simplify the whole solution, otherwise we get into a whole other article.

The client looks like this:

XML
<system.serviceModel>
    <bindings>
        <basicHttpBinding>
            <binding name="TestWCFService.Http">
                <security mode="Transport">
                    <transport clientCredentialType="None" 
                        proxyCredentialType="None"
                        realm="" />
                </security>
            </binding>
        </basicHttpBinding>
    </bindings>
    <client>
        <endpoint address=https://chrise:10081/TestWCFService 
                binding="basicHttpBinding"
            bindingConfiguration="TestWCFService.Http" 
                contract="TestWCF.Client.TestWCFService.ITestWCFService"
            name="TestWCFService.WSHttp" />
    </client>
</system.serviceModel>

And that should work!

Some Exceptions I Ran Into

Firstly, this will only work on your box. Why? Because no other computer in this world is dumb enough to trust your personal cert. In order to actually get your client working on another PC, you need to export your authority cert and your SSL cert, and then import them on the client's PC. This is why makecert is NOT FOR PRODUCTION. Get a real CA guys.

Secondly, you should be able to browse to the endpoint when the service is running. If when you do so, you get a popup asking you about the trustworthiness of the cert, your cert isn't trusted and the client won't be able to talk to the server.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)