Skip to main content
Email Password   helpLost your password?

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:

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:

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:

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:

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:

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

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralThe specified network password is not correct. Pin
tony.debenport
5:35 26 Oct '09  
GeneralRe: The specified network password is not correct. Pin
Nuno M C Rodrigues
11:05 27 Oct '09  
GeneralRe: The specified network password is not correct. Pin
tony.debenport
7:13 28 Oct '09  
GeneralGreat Example, small change: Load certificate from refered assembly Pin
NunoPulga
4:25 22 Oct '09  
GeneralLittle bug when putting website online: file not found Pin
Manuvdp
3:20 5 Oct '09  
GeneralHelp - certificate problems.... Pin
jonna toon
7:04 27 May '09  
GeneralRe: Help - certificate problems.... Pin
NunoPulga
9:24 20 Oct '09  
AnswerRe: Help - certificate problems.... Pin
NunoPulga
10:19 20 Oct '09  
GeneralPlease Help Me with certification authority !!!! Pin
freger
6:23 11 Feb '09  
AnswerRe: Please Help Me with certification authority !!!! Pin
Davide Icardi
13:26 21 Feb '09  
GeneralnetTcpBinding Pin
divider
7:19 22 Jan '09  
GeneralGetting Error of Authentication on running client Pin
Vikas Gandhi
21:54 13 Nov '08  
GeneralGood Article - Update Writing? Pin
mklinker
11:36 7 Nov '08  
AnswerRe: Good Article - Update Writing? Pin
Davide Icardi
22:59 7 Nov '08  
GeneralIssue when AspNetCompatibillitymMode enabled Pin
Rhettc
5:14 19 Sep '08  
GeneralRe: Issue when AspNetCompatibillitymMode enabled Pin
Davide Icardi
23:43 2 Oct '08  
GeneralRe: Issue when AspNetCompatibillitymMode enabled Pin
Rhettc
0:35 8 Oct '08  
GeneralsingletonInstance instead of serviceType Pin
tsemer
6:29 15 Sep '08  
AnswerRe: singletonInstance instead of serviceType Pin
Davide Icardi
12:24 24 Oct '08  
GeneralMultiple certficate.... Pin
Himanshu Shu
21:10 30 Jul '08  
GeneralRe: Multiple certficate.... Pin
Davide Icardi
3:26 6 Oct '08  
GeneralQuery Regarding Client Pin
Himanshu Shu
1:27 28 Jul '08  
GeneralRe: Query Regarding Client Pin
Himanshu Shu
21:55 29 Jul '08  
GeneralRe: Query Regarding Client Pin
Davide Icardi
3:24 6 Oct '08  
QuestionWhat I have been searching for a long time now. Pin
Amour Rashid
20:01 19 May '08  


Last Updated 30 Apr 2007 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2009