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.

20 responses

  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.

    • Hi Blogesh!
      I have implemented this solution, but it did not work completely.
      When I made a request for a Java WebService the BinarySecurityToken Is sended not signed for the client and a I get an error: “Signature Invalid”
      If I try to add a signed BinarySecurityToken programatically then BinarySecurityToken is sended duplicated.
      Can you help me how I can send only one signed BinarySecurityToken in the SOAP Request ?

  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. 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.

  7. 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 ?

  8. 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 ?

  9. Pingback: Separate certificates for Transport and Message security in WCF | Edge of the World

  10. 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

  11. Hi guys!

    I have implemented this solution, but it did not work completely.

    When I made a request for a Java WebService the BinarySecurityToken Is sended not signed for the client and a I get an error: “Signature Invalid”

    If I try to add a signed BinarySecurityToken programatically then BinarySecurityToken is sended duplicated.

    Someone can help me how I can send only one signed BinarySecurityToken in the SOAP Request ?

  12. I have specified both the clientCertificate and the transportCertificate in the config file but only the transportCertificate is being picked up.

    • they are both being picked up. The issue is that the soap request is not producing WSSE node in the xml envelop. The service (java) is expecting a WSE. How did you make it work

Leave a reply to nhancers Cancel reply