Implementing a UserNameForCertificate Security Using WCF

Overview

In UserNameForCertificate security, the service and client are secured using message-level security. The service is authenticated with an X.509 certificate. The client authenticates using a user name and password (UserNameSecurityToken). You will require adding a reference to System.ServiceModel and System.IdentityModel to your Client/Service application.

Creating a WCF Service

I assume that you have installed the .NET 3.0 framework and Visual Studio 2005 extensions for WCF. To create a WCF service, go to File->New->Project, expand Visual C#, and select Net Framework 3.0; select WCF Service Library, and say OK. The project so created cannot be directly hosted in IIS; in the example provided here, the service is hosted in IIS. Here is how you can host the service in IIS.

  1. Make an App_Code folder in the project.
  2. Move all *.cs files to the App_Code folder.
  3. Add a file ServiceName.svc to your project and write the following line in it:
    < %@ servicehost debug="true" language="c#"
                     service="WCFServiceLibrary.Service" %>
    
    where WCFServiceLibrary is the Namespace and Service is your service class.
  4. Add a web.config file.
  5. Make sure that the Build output of the project compilation goes to bin.
  6. Make a virtual directory in IIS with the same name as project and point to the folder containing *.svc file.

Creating the Service

Step 1: Define a Contract

To create the Service, you need to define a Contract. A contract is an interface that defines the signature of service methods. Here is how you do it.

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.Runtime.Serialization;

namespace WCFServiceLibrary
{
   [ServiceContract]
   public interface IService
   {
      [OperationContract]
         string Hello();
      [OperationContract]
         string HelloDataContract(Data dataContractValue);
   }
}

The ServiceContract attribute indicates that the interface defines a Service contract for WCF; the attribute can be directly applied to a class. The OperationContract attribute is applied to methods and indicates that the method will be visible to the service clients.

Step 2: Define the Service class or implement IService

//ServiceBehavior tells service to include exception details as
//service faults and send to client
[ServiceBehavior(IncludeExceptionDetailInFaults = true)]
public class Service : IService
{
   public string Hello()
   {
      //here is how you can retrieve userNameToken
      //If you want to get only user name use
      //OperationContext.Current.ServiceSecurityContext.
      //PrimaryIdentity.Name
      UserNameSecurityToken token = null;
      string message = "Hello ";
      foreach (SupportingTokenSpecification
               supportingTokenSpecification in
               OperationContext.Current.SupportingTokens)
      {
         if (supportingTokenSpecification.SecurityToken is
             UserNameSecurityToken)
         {
            token = (UserNameSecurityToken)
               supportingTokenSpecification.SecurityToken;

            break;
         }
      }
      if (token != null)
      {
         message = message + token.UserName;
      }

      return message;
   }
}

Step 3: Create the Service configuration file

Add a web.config file to the Service project and add the following XML. You can see in the XML below that there are three important sections:

  1. Endpoint: The endpoint is where you define the address of the service and associate bindings and behaviors with the service.
  2. Bindings: Bindings are objects used to specify the communication details that are required to connect to the endpoint of a Windows Communication Foundation (WCF) service.

    See the binding named UserNameForCertificateBinding; it has a security element that is used to specify the type of security supported by the endpoint.

    Security has following attributes:

    1. authenticationMode = "UserNameForCertificate". This indicates that you are using UserNameForCertificate.
    2. messageProtectionOrder="SignBeforeEncrypt". This indicates that the message is first signed and then encrypted before it is transmitted on the network.
  3. Behaviors: As the name indicates, behaviors define the behavior of the service at runtime.

    See the behavior named ServiceBehaviorUserName. It uses the userNameAuthentication element to specify that you are using a custom authentication and a customUserNamePasswordValidatorType attribute defines the name of the class that will handle the authentication. See the Implementing Custom User name validator section for the implementation of custom authentication. if you do not require custom authentication, just remove the userNameAuthentication element from the behavior.

  4. <?xml version="1.0" encoding="utf-8" ?>
    <configuration>
       <system.serviceModel>
          <services>
             <service name="WCFServiceLibrary.Service"
                behaviorConfiguration="ServiceBehaviorUserName">
             <!-- This endpoint is exposed at the base address
                  provided by the host-->
                <endpoint address=""
                          binding="customBinding"
                          contract="WCFServiceLibrary.IService"
                          bindingConfiguration=
                             "UserNameForCertificateBinding" />
    
                <endpoint contract="IMetadataExchange"
                          binding="mexHttpBinding" address="mex" />
             </service>
          </services>
    
          <bindings>
             <customBinding>
                <binding name="UserNameForCertificateBinding">
                   <textMessageEncoding messageVersion="Soap12"
                      writeEncoding="utf-8">
                      <readerQuotas maxDepth="32"
                         maxStringContentLength="999999999"
                         maxArrayLength="999999999"
                         maxBytesPerRead="4096"
                         maxNameTableCharCount="999999999" />
                   </textMessageEncoding>
                   <security authenticationMode=
                      "UserNameForCertificate"
                      messageProtectionOrder="SignBeforeEncrypt"
                      requireDerivedKeys="true"
                      includeTimestamp=
                         "true" messageSecurityVersion=
                         "WSSecurity11WSTrustFebruary2005WSSecure
                          ConversationFebruary2005WSSecurity
                          Policy11BasicSecurityProfile10">
                   </security>
                   <httpTransport authenticationScheme="Anonymous"/>
                </binding>
             </customBinding>
          </bindings>
    
          <behaviors>
             <serviceBehaviors>
                <behavior name="ServiceBehaviors">
                   <serviceMetadata httpGetEnabled="true" />
                </behavior>
                <behavior name="ServiceBehaviorUserName" >
                   <serviceMetadata httpGetEnabled="true" />
                   <serviceCredentials>
                     <userNameAuthentication
                        userNamePasswordValidationMode="Custom"
                        customUserNamePasswordValidatorType=
                        "WCFServiceLibrary.CustomUserNameValidator,
                         WCFServiceLibrary" />
                     <serviceCertificate
                        storeLocation="LocalMachine"
                        storeName="My"
                        findValue="CN=ServiceCertficate"
                        x509FindType=
                           "FindBySubjectDistinguishedName"/>
                    </serviceCredentials>
                </behavior>
             </serviceBehaviors>
          </behaviors>
       </system.serviceModel>
    
       <system.web>
          <!--
             Set compilation debug="true" to insert debugging
             symbols into the compiled page. Because this affects
             performance, set this value to true only during
             development.
          -->
             <compilation debug="true"/>
       </system.web>
    </configuration>
    
  5. To run this service, you must create a certificate named ServiceCertficate. You can create this certificate by executing the following command at the Visual Studio command prompt:

    makecert -sk  ServiceCertficate
             -n   "CN=ServiceCertficate"
             -ss  My
             -sr  LocalMachine
             -sp  "Microsoft Enhanced Cryptographic Provider v1.0"
             -sky exchange -r C:\ServiceCertficate.cer
    

    The command above creates a certificate in the personal store of LocalMachine and exports the public key to C:\ServiceCertficate.cer.

    Import C:\ServiceCertficate.cer in personal store of
           "Current/loggedin" windows user
    
  6. Give read access on the following folder to ASP.NET user/Current user

    C:\Documents and Settings\All Users\Application Data\
       Microsoft\Crypto\RSA\MachineKeys
    
  7. Now, compile the application and access the URL http://localhost/ProjectName/ServiceName.svc. You should get an error-free page that gives you information to generate a service proxy.

Implementing a UserNameForCertificate Security Using WCF

Creating the Client

The client I have used is a Windows application, so I will explain in the context of the same. However, it remains valid for any other client also.

Step 1: Create the proxy

Create the proxy by executing the following command on the Visual Studio command prompt:

http://localhost/ProjectName/ServiceName.svc?wsdl

This will generate two files: a ServiceName.cs file and a output.config file.

Step 2: Create the application

Create a Windows application and add a reference to the System.ServiceModel and System.IdentityModel namespace. Add ServiceName.cs and output.config files to this project. Rename the output.config to app.config.

Step 3: Alert the client

The app.config generated with SVCUTIL defines binding elements for UserNameForCertificate security but you need to tell the client about the Service certificate; this is done by defining a behavior. See the behavior named UserNameForCertificateBehaviour in the configuration XML below. It has a clientCredentials element where you give information about the Service's Certificate to the client. So finally, the app.config should look like this:

<?xml version="1.0" encoding="utf-8"?
<configuration>
   <system.serviceModel>
      <bindings>
         <customBinding>
            <binding name="CustomBinding_IService">
               <security defaultAlgorithmSuite="Default"
                  authenticationMode="UserNameForCertificate"
                  requireDerivedKeys="true"
                  securityHeaderLayout="Strict"
                  includeTimestamp="true"
                  keyEntropyMode="CombinedEntropy"
                  messageProtectionOrder="SignBeforeEncrypt"
                  messageSecurityVersion=
                     "WSSecurity11WSTrustFebruary2005
                     WSSecureConversationFebruary2005
                     WSSecurityPolicy11BasicSecurityProfile10"
                  requireSignatureConfirmation="false">

                  <localClientSettings cacheCookies="false"
                     detectReplays="false"
                     replayCacheSize="900000"
                     maxClockSkew="00:05:00"
                     replayWindow="00:05:00"
                     sessionKeyRenewalInterval="10:00:00"
                     sessionKeyRolloverInterval="00:05:00"
                     reconnectTransportOnFailure="true"
                     timestampValidityDuration="00:05:00"
                     cookieRenewalThresholdPercentage="60" />

                  <localServiceSettings detectReplays="false"
                     issuedCookieLifetime="10:00:00"
                     maxStatefulNegotiations="128"
                     replayCacheSize="900000"
                     maxClockSkew="00:05:00"
                     negotiationTimeout="00:01:00"
                     replayWindow="00:05:00"
                     inactivityTimeout="00:02:00"
                     sessionKeyRenewalInterval="15:00:00"
                     sessionKeyRolloverInterval="00:05:00"
                     reconnectTransportOnFailure="true"
                     maxPendingSessions="128"
                     maxCachedCookies="1000"
                     timestampValidityDuration="00:05:00" />
               </security>
               <textMessageEncoding maxReadPoolSize="64"
                  maxWritePoolSize="16"
                  messageVersion="Soap12"
                  writeEncoding="utf-8">
                  <readerQuotas maxDepth="32"
                     maxStringContentLength="999999999"
                     maxArrayLength="999999999"
                     maxBytesPerRead="4096"
                     maxNameTableCharCount="999999999" />
               </textMessageEncoding>
               <httpTransport manualAddressing="false"
                  maxBufferPoolSize="524288"
                  maxReceivedMessageSize="65536"
                  allowCookies="false"
                  authenticationScheme="Anonymous"
                  bypassProxyOnLocal="false"
                  hostNameComparisonMode="StrongWildcard"
                  keepAliveEnabled="true"
                  maxBufferSize="65536"
                  proxyAuthenticationScheme="Anonymous"
                  realm="" transferMode="Buffered"
                  unsafeConnectionNtlmAuthentication="false"
                  useDefaultWebProxy="true" />
            </binding>
         </customBinding>
      </bindings>
      <client>
         <endpoint address="http://localhost/WCFService/Service.svc"
                   binding="customBinding"
                   bindingConfiguration="CustomBinding_IService"
                   contract="IService"
                   behaviorConfiguration=
                      "UserNameForCertificateBehaviour">
            <identity>
               <dns value="ServiceCertficate"/>
             </identity>
         </endpoint>
      </client>

      <behaviors>
         <endpointBehaviors>
            <behavior name="UserNameForCertificateBehaviour">
               <clientCredentials>
                  <serviceCertificate>
                     <defaultCertificate storeLocation="CurrentUser"
                        storeName="My"
                        findValue="CN=ServiceCertficate"
                        x509FindType="FindBySubjectDistinguishedName"/>
                     <authentication revocationMode="NoCheck"
                        certificateValidationMode="None">
                     </authentication>
                  </serviceCertificate>
               </clientCredentials>
            </behavior>
         </endpointBehaviors>
      </behaviors>
   </system.serviceModel>
</configuration>

Step 4: Compile and run

Now, compile and run the client. You should be able to connect to the service.

Implementing Custom User Name Validator

By default, Windows authentication is used in UserNameForCertificate security. There are several scenarioes where you would like to have your own authentication mechanism. The Validate method of the UserNamePasswordValidator class is one that validates the User name security token. To implement custom authentication, derive a class from UserNamePasswordValidator and override the validate method. The code is shown below.

using System;
using System.Collections.Generic;
using System.Text;
using System.ServiceModel;
using System.IdentityModel.Selectors;
using System.Runtime.Serialization;
using System.Security.Principal;

namespace WCFServiceLibrary
{
   class CustomUserNameValidator:UserNamePasswordValidator
   {
      public override void Validate(string userName, string password)
         {
            //I am allowing all the users
            if (null == userName || null == password)
            {
               throw new Exception("Invalid User name/Password.");
            }
            //Put the code for authentication here

         }
      }
   }
}

You also need to tell the Service that you want it to use CustomUserNameValidator. This is done by defining a behavior element in the service configuration file (see web.config in the example).

<behaviors>
   <serviceBehaviors>
      <behavior name="ServiceBehaviorUserName" >
         <serviceMetadata httpGetEnabled="true" />
            <serviceCredentials>
               <userNameAuthentication
                  userNamePasswordValidationMode="Custom"
                  customUserNamePasswordValidatorType=
                     "WCFServiceLibrary.CustomUserNameValidator,
                      WCFServiceLibrary" />
                  <serviceCertificate storeLocation="LocalMachine"
                     storeName="My"
                     findValue="CN=ServiceCertficate"
                     x509FindType="FindBySubjectDistinguishedName"/>
            </serviceCredentials>
      </behavior>
   </serviceBehaviors>
</behaviors>

Conclusion

You may feel that the article here is slightly short, but my objective is to show you how to do it rather than going into concepts that you can get from MSDN. You can get the source code of this article and follow the instructions given in Readme.txt to run the code.



About the Author

Vijay Pandey

I am working as Software engineer at Pune, India.

Downloads

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • IBM Worklight is a mobile application development platform that lets you extend your business to mobile devices. It is designed to provide an open, comprehensive platform to build, run and manage HTML5, hybrid and native mobile apps.

  • On-demand Event Event Date: October 23, 2014 Despite the current "virtualize everything" mentality, there are advantages to utilizing physical hardware for certain tasks. This is especially true for backups. In many cases, it is clearly in an organization's best interest to make use of physical, purpose-built backup appliances rather than relying on virtual backup software (VBA - Virtual Backup Appliances). Join us for this webcast to learn why physical appliances are preferable to virtual backup appliances, …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds