Separate certificates for Transport and Message security in WCF

I’ve been busy of late writing my first book and doing so many other things that I haven’t had time to post anything on my blog. Now that I’ve got the book out of the way, I thought I should post something here. And what better topic than WCF :)

Recently, I had to interact with a financial institution using WCF for a Customer. The Service that the financial institution exposed was not written in WCF or .NET – not that it matters, but there were a number of specific things that had to be done to get it to work:

  • We needed to use transport security (https) that had to be encrypted using a specific X509 certificate
  • The body of the message had to be signed using another X509 certificate
  • The reply from the service did not have any security credentials attached to it – i.e. the transport was secure, but the message was not signed or encrypted

This may seem pretty straight forward – All you had to do is create a custom binding and specify something like this –

<custombinding>
  <binding name="Custom">
    <security messagesecurityversion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10" 
      authenticationmode="MutualCertificate" 
    />
    <httpstransport requireclientcertificate="true" 
      authenticationscheme="Negotiate" 
      usedefaultwebproxy="true" 
      manualaddressing="false" 
    />
  </binding>
</custombinding>

The problem is that you can specify only one certificate in  Client credentials  and both message security as well as transport security will use the same certificate – we want to use two separate ones.

The solution to this is to add a new behavior that takes care of this. But rather than creating the behavior from scratch, an easier alternative is to extend the ClientCredentials class to cater for this additional certificate. So, I decided to use the existing certificate stored in ClientCredentials for message security and to add a separate property to hold the certificate for the Transport as shown in the code below -

 
/// 
/// Class that extends Client Credentials so that the certificate for the
/// Transport layer encryption can be separate
/// 
public class MyCredentials : ClientCredentials
{
  /// <summary>
  /// The X509 Certificate that is to be used for https
  /// </summary>
  public X509Certificate2 TransportCertificate { get; set; }

  public MyCredentials(ClientCredentials existingCredentials)
    : base(existingCredentials)
  {
  }

  protected MyCredentials(MyCredentials other)
    : base(other)
  {
    TransportCertificate = other.TransportCertificate;
  }

  protected override ClientCredentials CloneCore()
  {
    return new MyCredentials(this);
  }

  public override SecurityTokenManager CreateSecurityTokenManager()
  {
    return new MyCredentialsSecurityTokenManager(this);
  }

  public void SetTransportCertificate(string subjectName, StoreLocation storeLocation, StoreName storeName)
  {
    SetTransportCertificate(storeLocation, storeName, X509FindType.FindBySubjectDistinguishedName, subjectName);
  }

  public void SetTransportCertificate(StoreLocation storeLocation, StoreName storeName, X509FindType x509FindType, string subjectName)
  {
    TransportCertificate = FindCertificate(storeLocation, storeName, x509FindType, subjectName);
  }

  private static X509Certificate2 FindCertificate(StoreLocation location, StoreName name,
    X509FindType findType, string findValue)
  {
    X509Store store = new X509Store(name, location);
    try
    {
      store.Open(OpenFlags.ReadOnly);
      X509Certificate2Collection col = store.Certificates.Find(findType, findValue, true);
      return col[0]; // return first certificate found
    }
    finally
    {
      store.Close();
    }
  }

}

As part of the class, I added some helper methods to set the Transport certificate from code and also overrode the CreateSecurityTokenManager method so that I can create my own SecurityTokenManager that figures out which certificate to use for what operation.

But again, rather than create this class from scratch, I just extended the ClientCredentialsSecurityTokenManager class that ClientCredentials uses. In it I overrode the CreateSecurityTokenProvider method so that when a certificate is requested for Transport security, we pass back TransportCertificate that is stored in the MyCredentials object as shown in the code below -

 
internal class MyCredentialsSecurityTokenManager :
    ClientCredentialsSecurityTokenManager
{
    MyCredentials credentials;

    public MyCredentialsSecurityTokenManager(MyCredentials credentials)
        : base(credentials)
    {
        this.credentials = credentials;
    }

    public override SecurityTokenProvider CreateSecurityTokenProvider(
        SecurityTokenRequirement requirement)
    {
        SecurityTokenProvider result = null;

        if (requirement.Properties.ContainsKey(ServiceModelSecurityTokenRequirement.TransportSchemeProperty) &&
            requirement.TokenType == SecurityTokenTypes.X509Certificate)
        {
            result = new X509SecurityTokenProvider(
                this.credentials.TransportCertificate);
        }
        else if (requirement.KeyUsage == SecurityKeyUsage.Signature &&
            requirement.TokenType == SecurityTokenTypes.X509Certificate)
        {
            result = new X509SecurityTokenProvider(
                this.credentials.ClientCertificate.Certificate);
        }
        else
        {
            result = base.CreateSecurityTokenProvider(requirement);
        }

        return result;
    }


}

The last step is create the stuff necessary to be able to specify this in your config file. For that I extended the ClientCredentialsElement, so that I can specify the Transport Certificate as a behavior using the code below -

 
class ClientCredentialsExtensionElement : ClientCredentialsElement
{
    ConfigurationPropertyCollection properties;

    public override Type BehaviorType
    {
        get 
        { 
            return typeof(MyCredentials); 
        }
    }

    [ConfigurationProperty("transportCertificate")]
    public X509InitiatorCertificateClientElement TransportCertificate 
    {
        get
        {
            return base["transportCertificate"] 
                as X509InitiatorCertificateClientElement;
        }
    }

    protected override ConfigurationPropertyCollection Properties
    {
        get
        {
            if (this.properties == null)
            {
                ConfigurationPropertyCollection properties = base.Properties;
                properties.Add(new ConfigurationProperty(
                    "transportCertificate",
                    typeof(X509InitiatorCertificateClientElement), 
                    null, null, null, 
                    ConfigurationPropertyOptions.None));
                this.properties = properties;
            }
            return this.properties;
        }
    }

    protected override object CreateBehavior()
    {
        MyCredentials creds = new MyCredentials(
            base.CreateBehavior() as ClientCredentials);

        PropertyInformationCollection properties = 
            ElementInformation.Properties;

        creds.SetTransportCertificate(TransportCertificate.StoreLocation,
                                        TransportCertificate.StoreName, 
                                        TransportCertificate.X509FindType, 
                                        TransportCertificate.FindValue);

        base.ApplyConfiguration(creds);
        return creds;
    }
}

With the changes made, you should be able to replace the clientCredential section in your config file with the clientCredentialsExtension section. Something like this -



<system.serviceModel>
...
 <extensions>
   <behaviorExtensions>
     <add name="clientCredentialsExtension" type="MyNamespace.ClientCredentialsExtensionElement, MyAssemblyName" />
   </behaviorExtensions>

 </extensions>
 <behaviors>
   <endpointBehaviors>
     <behavior name="SecureMessageAndTransportBehavior">

       <clientCredentialsExtension>


         <!--This cert is used for signing the message-->
         <clientCertificate findValue="YourMessageCertName"
                            storeLocation ="LocalMachine"
                            storeName="My"
                            x509FindType="FindBySubjectName"
                            />

         <!--This cert is used for the transport-->
         <transportCertificate findValue="YourTransportCertName"
                            storeLocation ="LocalMachine"
                            storeName="My"
                            x509FindType="FindBySubjectName"
                            />

       </clientCredentialsExtension>

     </behavior>
   </endpointBehaviors>
 </behaviors>

</system.serviceModel>

That’s it – you are all set to go. Just make sure that you set this behavior for your endpoint.

About these ads

~ by blogesh on October 8, 2009.

14 Responses to “Separate certificates for Transport and Message security in WCF”

  1. Hi there! Have you actually tested this? When i tried it out singning and transport uses the same certificate. The other side doesnt see any difference with or without the custom securitymanager

  2. Nvm! In the ServiceSecurityContext it has now two certificateSecurityClaims so it looks like it works. Do not know if it actually signs/encrypts with correct cert though.

    Do you know if one have to do anything special on the service-side for this to work? I dont receive any errors though when using it only on the client.

  3. Yes it does. I’ve tested it fairly thoroughly :)
    If you are writing the server, then yes, you have to do something similar on the server side. The code I’ve provided is for the client side.

  4. Btw, thanks alot for showing the world =) Very rough area to traverse this security-mumbo-jumbo

    I just need one for my service now also.

  5. You got any tips of how one can create one for the serverside? doesnt unfortunatelly have the ServiceModelSecurityTokenRequirement.TransportSchemeProperty

  6. Any chance of you posting sample code for the server side of this? I’m trying to Stub out the server side.

  7. Sorry… re-read my comment and saw that it comes off as rude.

    The client side code that you posted works perfectly for me running against a java service with these credentials.

    I’m trying to stub out the server side with the same certificate requirements in .NET so that I can write unit tests which are not dependent on the 3rd party web service which this hits.

  8. @DaveM – I could whip something up, but unfortunately don’t have the time to do it now :( Sorry.

  9. Thanks for the post.
    I have following BasicHttpBinding to connect to the service:

    I need to send a transport layer certificate and messages signed with another certificate. The endpoint is created in code as I get the URL from the Database. B’coz of this I have to create the Behavior also from code. How can I do that ?

  10. Thanks for the post.
    I have following BasicHttpBinding to connect to the service:
    security mode=”TransportWithMessageCredential”
    transport clientCredentialType=”None”
    message clientCredentialType=”Certificate”

    I need to send a transport layer certificate and messages signed with another certificate. The endpoint is created in code as I get the URL from the Database. B’coz of this I have to create the Behavior also from code. How can I do that ?

  11. [...] I did not solve anything myself, but rather was immensely helped by this little gem right here: https://blogesh.wordpress.com/2009/10/08/separate-certificates-for-transport-and-message-security-in…. That article provides a workable, simple and elegant workaround to an annoying limitation of the [...]

  12. Thanks Mahesh, works for me.

  13. Thank you very much for sharing. This was extremely helpful

  14. One question, on the third assumption that trasport was secured but the message was not signed or encrypted, if this is not the case then what changes we need to make. Messages returning from the service are also encrypted. In nutshell is complete message security + mutual ssl.

    Thanks

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

 
Follow

Get every new post delivered to your Inbox.

%d bloggers like this: