Click here to Skip to main content
Click here to Skip to main content

An easy way to use certificates for WCF security

By , 30 Apr 2007
 

Introduction

Security has an important role in any distributed application and Windows Communication Foundation (known as WCF or Indigo), the new Microsoft communication framework, implements many security standards and has a wide range of features available.

One of the most important aspects of security is authentication. WCF can be configured to use many authentication methods:

  • Anonymous caller
  • User name and password
  • Certificate
  • Windows
  • CardSpace

In this article I will show you how to configure WCF with certificates to authenticate service clients and server using an alternative approach.

If you want to exactly understand my implementation, continue reading the next section. If you simply want to understand how to configure WCF using certificates jump directly to the Quick start tutorial section.

Background concepts

The next sections assume that you are familiar with many WCF and security concepts. See the External resources section if you want to review some of these concepts or for more information.

The problem

The use of certificates for authentication is not new, but is still one of the most common way to authenticate a subject. WCF has a built-in support for certificates that conform to the Web Services Security (WS-Security) standards.

The problem with the default configurations and examples available is that all the certificates must be installed in the Certificate Store, which basically is a central location where Windows saves all the certificates (used also for other applications: Internet Explorer, ...).

Why this solution causes some problems? The easy answer is because it is not easy to correctly configure all the certificates. For more details:

  • When you deploy your service to the server you must install in the Certificate Store all the certificates used (in different locations based on the use of the certificate).
    This operation must be executed using an installation program, a script file or a batch file. For this reason, it is difficult to deploy the application using an xcopy/ClickOnce installation.
  • Each client must also install the certificate used to authenticate itself always in the Certificate Store. This is easy if you have a small number of clients but very difficult if you must manually configure each computer (in addition, for the client, you can't use an xcopy/ClickOnce installation).
  • You must give to the running process (like ASP.NET) the permissions to read the certificate private key. This step usually requires changing the file system permission. This again requires a script file or an installation which is not always easy.
  • If you are using a shared hosting probably you can't install certificates or change certificate permissions.
  • As a developer I like to have each project isolated from the others. I want to be able to easy test different configurations or applications, I like to simply download the latest version from the code repository and run it, without any special configuration. Using the Certificate Store I must always remember to install or uninstall the certificates each time.

At the following MSDN page you can see an example of a configuration using certificates and a description of how to install certificates using the classic solution: MSDN: Message Security Certificate.

These are the reasons behind my decision to try a different approach that I will describe in the following sections.

The solution

My goal is to find an easy way to use certificates without using Certificate Store. WCF can be easily extended; in this article I will show you how to extend WCF to load the certificates from files.

I known that storing certificates on the file system is less secure, but I think that with some attention this can be a useful alternative. See the Disadvantages section for a discussion of the possible problems of my approach.

Consider that with my solution, I simply change how the certificates are loaded, all the advantages of using WCF (standards, proved code, ...) are still valid. Most important you must still use most of the settings required to use certificates. For a complete and working example, I suggest to look at the sample project in the zip file or follow the Quick start tutorial section to implement this solution on your own project.

To understand how this solution works, continue reading the next section.

Implementation details

Loading a certificate from a file is quite easy, you must simply use the System.Security.Cryptography.X509Certificates.X509Certificate2 class:

//Load the certificate from a file
X509Certificate2 certificate =
        new X509Certificate2(fullpath, password);    

The first parameter is the path of the certificate file, the second parameter is the password used to encrypt the private key (if present).

With X509Certificate2 class you can load 2 kinds of files:

  • .cer file - Used to store a public key
  • .pfx file - Used to store a public+private key (optional encrypted with a password)

You can obtain these files from a public certification authority or create your self-signed certificates using makecert.exe and pvk2pfx.exe, both available as Visual Studio Tools. Here an example on how to create a certificate:

makecert -r -pe -n "CN=CompanyXYZ Server" -b 01/01/2007 -e 01/01/2010
        -sky exchange Server.cer -sv Server.pvk
pvk2pfx.exe -pvk Server.pvk -spc Server.cer -pfx Server.pfx    

The first command (makecert) generates a public key (in this case Server.cer) and a private key (in this case Server.pvk). The second command (pvk2pfx) merges the 2 files on a single .pfx file (in this case Server.pfx).

When executing makecert and pvk2pfx, you can insert a password used to encrypt the private key.

Service authentication

This section describes how to configure the server with the certificate used to authenticate the service.

First you must add a reference to the DevAge.ServiceModel.dll. This assembly (included in the sample project with the full code) contains some classes used to load the certificate from a file that I will soon describe.

Then you must generate the service certificate and put it in a secure directory. For ASP.NET web site, I think that a good solution is to use the App_Data directory, which is automatically configured to not allow public access. I have generated in the App_Data directory 2 files: Server.cer and Server.pfx.

Usually you can specify the certificate for the service inside the system.serviceModel section of the .config file like this:

<behaviors>
  <serviceBehaviors>
    <behavior name="serviceCredentialBehavior">
      <serviceCredentials>
        <serviceCertificate findValue="Contoso.com"
                            storeLocation="LocalMachine"
                            storeName="My"
                            x509FindType="FindBySubjectName" />
      </serviceCredentials>
    </behavior>
  </serviceBehaviors>
</behaviors>    

This configuration basically tells WCF where to find the certificate inside the Certificate Store.

To load the certificate from file, unfortunately you can't simply change the configuration, but you must set the ServiceHost.Credentials.ServiceCertificate.Certificate property.

I have created a new configuration section where you can specify where to find the server certificate:

<devage.serviceModel>
<services>
  <add name="MathService"
        serverCertificate="App_Data\Server.pfx"
                />
</services>
</devage.serviceModel>    

Then I have created a new ServiceHost derived class, DevAge.ComponentModel.CertificateServiceHost, which automatically reads the new configuration section and sets the ServiceHost.Credentials.ServiceCertificate.Certificate property.

If you directly create the ServiceHost class you can simply change your code to create the DevAge.ComponentModel.CertificateServiceHost instead.
If you use ASP.NET (that automatically creates the ServiceHost class) you must configure the .svc file in this way:

<% @ServiceHost Language="C#" Debug="true"
        Service="MathService"
        CodeBehind="~/App_Code/MathService.cs"
        Factory="DevAge.ServiceModel.CertificateServiceHostFactory" %>    

Note the Factory property that uses a specific factory class to create my DevAge.ComponentModel.CertificateServiceHost.
This configuration replaces the standard serviceCertificate section.

NOTE: When you will create the proxy client with svcutil you will see an identity section inside the endpoint section:

<identity>
  <certificate encodedValue="...." />
</identity>

This section must be recreated each time you change the service certificate, because it contains the public identification of the certificate generated at design time. This value is used by the client to be sure to speak with the expected service.

Client authentication

This section describes how to use certificates to authenticate each client.

You must create a certificate for each client (or share the same certificate for more than one client). You can use the same command to generate self-signed certificates or obtain it from a certification authority.

In my example I have generated in the client directory 2 files: Client.cer and Client.pfx.

Normally each client can configure the application with the certificate using the configuration below:

<behaviors>
  <endpointBehaviors>
    <behavior name="ClientCredentialsBehavior">
      <clientCredentials>
        <clientCertificate findValue="Cohowinery.com"
             storeLocation="CurrentUser"
             storeName="My"
             x509FindType="FindBySubjectName" />
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>    

This configuration always uses the Certificate Store to locate the right certificate to use and like in the previous example, there isn't a way to directly use a certificate file. So I have again created a new configuration section to specify the certificate file name:

<devage.serviceModel>
<endPoints>
  <add contract="Client.MathService.IMathService"
        clientCertificate="Client.pfx" />
</endPoints>
</devage.serviceModel>    

To manually set the certificate for the client you can set the ClientProxy.ClientCredentials.ClientCertificate.Certificate property.

Unfortunately in this case, there isn't a way to automatically set this property by reading the new section (or at least I cannot find a way...) so you must configure each proxy with code like this:

//Create the client proxy as usual
MathService.MathServiceClient service =
         new Client.MathService.MathServiceClient();

//Configure the client reading the devage.serviceModel configuration section
DevAge.ServiceModel.Proxy<MathService.IMathService>.Configure(service);    

The DevAge.ServiceModel.Proxy class reads the configuration section and sets the ClientProxy.ClientCredentials.ClientCertificate.Certificate property.

If the certificate used by the client is trusted by the server (usually must be added in the Trusted people of the Certificate Store) your configuration is complete. If otherwise the certificate is self-signed or anyway is not trusted by the server you must configure the server to accept the certificate.

To solve this issue, I have implemented a custom X509CertificateValidator class, DevAge.ServiceModel.CustomCertificateValidator, which can be configured for each service. You must extend the devage.serviceModel section of the .config file of the service (on the server) like this:

<devage.serviceModel>
<services>
  <add name="MathService"
        serverCertificate="App_Data\Server.pfx"
        clientCertificates="App_Data\Client1.cer,App_Data\Client2.cer"
                />
</services>
</devage.serviceModel>    

Note the new clientCertificates property which contains a list of client certificates to consider trusted.

You must copy inside the App_Data server directory all the client public key files (in this case Client1.cer and Client2.cer files).

The CertificateServiceHost class automatically reads this new section and sets the X509CertificateValidator using code like this:

X509ClientCertificateAuthentication authentication =
            serviceHost.Credentials.ClientCertificate.Authentication;

authentication.CertificateValidationMode =
        System.ServiceModel.Security.X509CertificateValidationMode.Custom;

authentication.CustomCertificateValidator =
    new CustomCertificateValidator(clientCertificates);    

If the certificate file has a password, you can specify it within the file name using this format: filename|password. For example you can specify the serverCertificate attribute of the web.config with this value: App_Data\Server.pfx|yourpassword.
Remember also that you can specify 2 different password for the *.pvk and for the *.pfx files, the password to use is the password specified for the *.pfx file. See the makecert and pvk2pfx documentation for more details on how to set passwords.

Disadvantages

Storing the certificates on files is easier but is less secure because a malicious user can steal the private key. The public key can be shared without problems but if someone steals the private key your security is compromised. With the private key a malicious user can decrypt the messages or impersonate your service or a client.

I think anyway that usually if someone can access your file system you probably have many other security problems. Consider also that if someone has access to your file system, he or she can probably (depends on the type of the attack) also access the Certificate Store.

My suggestion is to correctly evaluate your application and your security requirements. In many cases, I think that saving certificate on a file is quite secure and can be useful.

Finally consider that you can use a mixed configuration, for example reading the server certificate inside the Certificate Store and the client certificates on file system.

Quick start tutorial

Here a simple tutorial to use certificates with a service hosted on IIS/ASP.NET and a console client application. I will create a simple Math service with a single Sum method.

Creating the service

Create a new web site project; add a reference to the DevAge.ServiceModel.dll assembly (that you can find in the sample project).

Add these files:

  • App_Code/MathService.cs

    using System;
    using System.ServiceModel;
    using System.Runtime.Serialization;
    
    [ServiceContract()]
    public interface IMathService
    {
        [OperationContract]
        double Sum(double a, double b);
    }
    
    public class MathService : IMathService
    {
        public double Sum(double a, double b)
        {
            return a + b;
        }
    }    
  • MathService.svc

    <% @ServiceHost Language="C#" Debug="true"
            Service="MathService"
            CodeBehind="~/App_Code/MathService.cs"
            Factory="DevAge.ServiceModel.CertificateServiceHostFactory" %>    
  • Web.config

    <?xml version="1.0"?>
    <configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">
      <configSections>
        <!-- Add devage.serviceModel section handler -->
        <section name="devage.serviceModel"
            type="DevAge.ServiceModel.Configuration.Section, 
                        DevAge.ServiceModel" />
      </configSections>
    
      <system.serviceModel>
        <services>
          <service name="MathService" behaviorConfiguration="behavior1">
            <endpoint contract="IMathService"
                binding="wsHttpBinding" bindingConfiguration="binding1"/>
          </service>
        </services>
    
        <behaviors>
          <serviceBehaviors>
            <behavior name="behavior1">
              <serviceMetadata httpGetEnabled="true"/>
              <serviceDebug includeExceptionDetailInFaults="true"/>
            </behavior>
    
          </serviceBehaviors>
        </behaviors>
        <bindings>
          <wsHttpBinding>
            <binding name="binding1">
              <security mode="Message">
    
                <message clientCredentialType="Certificate" />
              </security>
            </binding>
          </wsHttpBinding>
        </bindings>
      </system.serviceModel>
    
      <!-- Specific DevAge.ServiceModel configuration used
            to set the client and server certificate -->
      <devage.serviceModel>
        <!-- List of service type each with the relative server
            certificate and client certificate list -->
        <services>
          <add name="MathService"
                serverCertificate="App_Data\Server.pfx"
                clientCertificates=""
                        />
        </services>
    
      </devage.serviceModel>
    </configuration>    

Now you must obtain a server certificate, you can buy it from a certification authority or create a self-signed certificate with the following procedure:
Open a Visual Studio Command Prompt, go to the App_Data directory and execute these commands (don't insert the password when asked):

makecert -r -pe -n "CN=CompanyXYZ Server" -b 01/01/2007 -e 01/01/2010
        -sky exchange Server.cer -sv Server.pvk
pvk2pfx.exe -pvk Server.pvk -spc Server.cer -pfx Server.pfx    

Run the project on Visual Studio to start the ASP.NET Development Server and try to navigate to the MathService.svc page.

Creating the client

Create a new Console Application project using "Client" as the project name; add a reference to the DevAge.ServiceModel.dll assembly (that you can find in the sample project). Using Visual Studio you must right click on the project and select "Add Service Reference". Insert the URL of your service (probably something like http://localhost:1281/Server/MathService.svc) and the name MathService.

Create a new Program.cs file to test your service like this:

using System;
using System.Collections.Generic;
using System.Text;

namespace Client
{
class Program
{
    static void Main(string[] args)
    {
        MathService.MathServiceClient service =
                new MathService.MathServiceClient();

        //Configure the client reading the devage.serviceModel 
        //configuration section
        DevAge.ServiceModel.Proxy<MathService.IMathService>.Configure(service);

        try
        {
            double val = service.Sum(10, 20);

            Console.WriteLine("OK");
        }
        catch (Exception ex)
        {
            service.Abort();

            Console.WriteLine(ex.ToString());
        }

        Console.WriteLine("Press Enter key to exit...");
        Console.ReadLine();
    }
}
}    

Open the app.config file, save in a temp file the encodedValue attribute in the certificate tag. Replace the app.config file with the configuration below, but remember to restore the previous encodedValue attribute and check the address attribute to point to the right service URL:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <configSections>

    <!-- Add devage.serviceModel section handler -->
    <section name="devage.serviceModel"
        type="DevAge.ServiceModel.Configuration.Section, DevAge.ServiceModel" />
  </configSections>
  <system.serviceModel>
    <bindings>
      <wsHttpBinding>

        <binding name="binding1" >
          <security mode="Message">
            <!-- Set the client authentication mode -->
            <message clientCredentialType="Certificate" />
          </security>
        </binding>

      </wsHttpBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="behavior1">
          <clientCredentials>

            <serviceCertificate>
              <authentication certificateValidationMode="None" />
            </serviceCertificate>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>

    </behaviors>
    <client>
      <endpoint address="http://localhost:1281/Server/MathService.svc"
          binding="wsHttpBinding" bindingConfiguration="binding1"
          contract="Client.MathService.IMathService"
          behaviorConfiguration="behavior1"
           name="endpoint1" >
        <identity>
          <!--This is the value used to check the server certificate identity,
            this value is generated by svcutil -->
          <certificate encodedValue="TODO Here use your specific encodedValue" />
        </identity>
      </endpoint>
    </client>
  </system.serviceModel>
  <!-- Specific DevAge.ServiceModel configuration -->
  <devage.serviceModel>

    <!-- List of endPoints each with the relative client certificate
         to use for authentication. -->
    <endPoints>
      <add contract="Client.MathService.IMathService"
            clientCertificate="..\..\Client.pfx" />
    </endPoints>
  </devage.serviceModel>
</configuration>    

Now we must generate the client certificate. Again this certificate can be obtained from a certification authority or created with the procedure below:
Open a Visual Studio Command Prompt, go to the client project directory and execute these commands (don't insert the password when asked):

makecert -r -pe -n "CN=CompanyXYZ Client" -b 01/01/2007 -e 01/01/2010
        -sky exchange Client.cer -sv Client.pvk
pvk2pfx.exe -pvk Client.pvk -spc Client.cer -pfx Client.pfx    

Finally copy the Client.cer file in the server App_Data directory and modify the web.config server file adding the client certificate like this:

<!-- Specific DevAge.ServiceModel configuration used to set
    the client and server certificate -->
<devage.serviceModel>

    <!-- List of service type each with the relative server
          certificate and client certificate list -->
    <services>
      <add name="MathService"
            serverCertificate="App_Data\Server.pfx"
            clientCertificates="App_Data\Client.cer"
                    />
    </services>
</devage.serviceModel>    

Note the clientCertificates attribute that now contains the Client.cer file.

Now you can test your client application.

Sample project

In the sample project, I have created a full working example composed of:

  • Server - a service hosted by a web site project
  • Client - the service client implemented as a console application
  • DevAge.ServiceModel - the library that contains the new implementation used to load the certificates from a file

You can simply open the solution with Visual Studio 2005 and run the Client project to test the code.

Conclusion

I think that this article show the advantages and disadvantages of loading the certificates from files without using the Certificate Store. Security is important and I know that this solution is not perfect, but can be a useful alternative. Consider anyway that it is always easy to change the configuration files to use the classic approach.

Write to me if you find errors (on code or documentation) or if you have problems, questions or suggestions. I will appreciate any feedback.

External resources

History

  • 30 Apr 2007: First release

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Author

Davide Icardi
Software Developer
Italy Italy
Member
No Biography provided

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
Hint: For improved responsiveness ensure Javascript is enabled and choose 'Normal' from the Layout dropdown and hit 'Update'.
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionSecuring WCF Service with self signed certificates programmaticallymemberha123zem25 Mar '13 - 22:51 
very nice article , thnx
SuggestionAnother option [modified]memberVasil Trifonov29 Oct '12 - 22:57 
You can check out this blog post - Securing WCF Service with Self Signed Certificates programmatically.   Won't need installing any certificate.
GeneralMy vote of 5memberMember 411986428 Jun '12 - 2:22 
Nice one... very usefull
QuestionIs that mandatory that the Server should run in "Cohowinery.com" domain. ?`memberMember 411986428 Jun '12 - 2:20 
Is that mandatory that the Server should run in "Cohowinery.com" domain. ?   I have some doubts about Certificates.   1.) Are we creating this certificate for specifically for the Domain. ?   2.) Do we need to buy Certificate for each client ? Or only server is required ?
QuestionCertificates with passwordmembertiswingpower16 Mar '12 - 11:16 
I tried using makecert specifying a password, and putting the password in the config files, but when the application tries to access the file, it's throwing an invalid network password error. I'm certain that the password is correct. Did I miss anything? It's erroring out in the constructor...
QuestionThis doesnt appear to work with a asp.net website, only a console appmemberdemus6820 Jul '11 - 7:24 
I can create 2 projects, both pointing at the same service. One a console app, the other an asp.net website. The console app authenticates with the service succesfully, however the asp.net website fails with the following exception running in casini:   "The client certificate is not...
GeneralMy vote of 5membersivanphani18 Jul '11 - 6:17 
gud 1
GeneralMy vote of 5membersivanphani18 Jul '11 - 6:17 
good one
GeneralIs there any way to do this with a .net 3.0 service reference?memberPaul Shaffer27 May '11 - 13:47 
I've been unable to get this working for a wcf 3 service. I'm stuck on the error: "The service certificate is not provided. Specify a service certificate in ServiceCredentials." when generating the service reference in vs2008. I can't find any way to hook up a private certificate placed in...
QuestionHow to use with WindsorServiceHostFactorymemberWil32426 Oct '10 - 6:51 
Hi,   I was wondering if you know how I can use your code with the WindsorServiceHostFactory. I know that you can't have more than one host factory; however I need the benifits provided in both of them. I even tried creating a cusom service host that contained your code for...
GeneralMy vote of 5memberhulu031628 Jun '10 - 23:57 
excellent work, just fit my needs
GeneralI think I need something closemembersdmcnitt1 Mar '10 - 7:15 
Thanks for this article. I think we need to do this or something close. We are new to using services and would like to replace our interface files between our internal product and external installs.   We are trying to prevent a security failure as described in the following scenario:...
Questionproblem with the serviceReferencememberlastalaica29 Jan '10 - 4:19 
Hi all,   I've read all this tutorial but there is something that bother me ... It's this line : DevAge.ServiceModel.Proxy<MathService.IMathService>.Configure(service);   It's because I don't use the same templates that windows gave when you create a new WCF service......
AnswerRe: problem with the serviceReferencememberize_man4 Feb '10 - 21:26 
Hi   I am thinking about using this approach in my design, did you come up with a solution to the problem?
GeneralHTTPSmemberneil brightcliffe21 Jan '10 - 23:46 
Excellent sample,   Almost exactly what I need. I need the server to accept the messages on the HTTPS port though, have you any idea how this can be done?   Many many many thanks. Neil.
GeneralRe: HTTPSmemberlastalaica29 Jan '10 - 4:49 
Hi, I don't know if you already have the answer but there it is   you're supposed to have in your web.config something like that : <behavior name="behavior1" > <serviceDebug includeExceptionDetailInFaults="True"/> <serviceMetadata httpGetEnabled="true"...
GeneralRe: HTTPSmemberneil brightcliffe29 Jan '10 - 5:08 
Thanks for that, I'll give it a shot.
GeneralRe: HTTPSmembertheLUGGAGE1 Sep '10 - 23:56 
Niel,   Could you tell us if you got it working on HTTPS. I also need it working on HTTPS but I'm completely new to the whole WCF and security stuff so I was wondering if you where able to use https with the above hints.   Any help or conformation is welcome
GeneralRe: HTTPSmemberneil bright2 Sep '10 - 7:46 
Hi, sorry but my project was handed to someone else as I left the company. So I never implemented it.   Regards, Neil.
GeneralRe: HTTPSmemberneil bright2 Sep '10 - 7:48 
I do believe that the   <serviceMetadata httpGetEnabled="true" />   should just need changing to   <serviceMetadata httpsGetEnabled="true" />
GeneralThe specified network password is not correct.membertony.debenport26 Oct '09 - 4:35 
Thanks for the good article. I've got your example to work on my local machine with no issues but am seeing the following error message when I tried to deploy on the internet (Discountasp.net) Any ideas on how to resolve this issue?     Description: An unhandled exception...
GeneralRe: The specified network password is not correct.memberNuno M C Rodrigues27 Oct '09 - 10:05 
I had the same error.       Localy on a XP machine, perfect.     On the Dev server (WinSrv2003), perfect.     On Production server, "The specified network password is not correct".       On...
GeneralRe: The specified network password is not correct.membertony.debenport28 Oct '09 - 6:13 
Nuno, Thanks! You pointed me in the right direction. When I set the storageflag to Exportable it didn't work, but according to my isp provider the keys are stored in the Machine Key store. The code below worked.   new X509Certificate2(fullPath, password, X509KeyStorageFlags.MachineKeySet);
GeneralRe: The specified network password is not correct.memberwolfgang_hg22 Mar '10 - 5:11 
Hi,   this helped me, too, but I have further details: the call to "pvk2pfx.exe" for creating the server pfx shows a password input window, but does NOT set this password to the pfx file! This results on a Windows7 + IIS 7.5 in an error message "The specified network password is not...
GeneralRe: The specified network password is not correct.memberwael Refaat3 Aug '11 - 0:52 
this is the answer I was looking for, now it works Wael R. Roushdy

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web02 | 2.6.130516.1 | Last Updated 30 Apr 2007
Article Copyright 2007 by Davide Icardi
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid