Sometimes, Windows Communication Foundation (WCF) can be tough going. Whilst things will often work straight out of the box, customisation can quickly get complicated. Equally though, the solution can sometimes turn out to be pretty simple but it is the journey to get there that is difficult.
Consider the following scenario:
In this configuration a WCF service was secured by Forefront Threat Management Gateway (TMG) 2010 so that the client must negotiate mutual SSL authentication with TMG before the message can be forwarded on to the service.
From the point of view of the service this was simple message level security because transport security was being taken care of by TMG. The service configuration was therefore the following:
<system.serviceModel> <behaviors> <serviceBehaviors> <behavior name="myServiceBehaviour"> <serviceCredentials> <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="WCFService.CustomUserNameValidator, WCFService" /> </serviceCredentials> </behavior> </serviceBehaviors> </behaviors> <bindings> <basicHttpBinding> <binding name="basicBinding"> <security mode="TransportWithMessageCredential"> <message clientCredentialType="UserName"/> </security> </binding> </basicHttpBinding> </bindings> <services> <service name="WCFService.Service1" behaviorConfiguration="myServiceBehaviour"> <endpoint address="https://www.myservice.com.local/service1.svc" binding="basicHttpBinding" bindingConfiguration="basicBinding" contract="WCFService.IService1" /> </service> </services></system.serviceModel>
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService1"> <security mode="TransportWithMessageCredential"> <transport clientCredentialType="Certificate" /> <message clientCredentialType="UserName" /> </security> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="https://www.myservice.com/Service1.svc" behaviorConfiguration="myEndpointBehaviour" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="Client.IService1" name="BasicHttpBinding_IService1" /> </client> <behaviors> <endpointBehaviors> <behavior name="myEndpointBehaviour"> <clientCredentials> <clientCertificate storeName="My" storeLocation="CurrentUser" findValue="CN=WCF client cert 2" /> </clientCredentials> </behavior> </endpointBehaviors> </behaviors></system.serviceModel>
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService1"> <security mode="Transport"> <transport clientCredentialType="Certificate" /> </security> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="https://www.myservice.com/Service1.svc" behaviorConfiguration="myEndpointBehaviour" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="Client.IService1" name="BasicHttpBinding_IService1" /> </client> <behaviors> <endpointBehaviors> <behavior name="myEndpointBehaviour"> <clientCredentials> <clientCertificate storeName="My" storeLocation="CurrentUser" findValue="CN=WCF client cert 2" /> </clientCredentials> </behavior> </endpointBehaviors> </behaviors></system.serviceModel>
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IService1"> <security mode="TransportWithMessageCredential"> <transport clientCredentialType="Certificate" /> <message clientCredentialType="UserName" /> </security> </binding> </basicHttpBinding> </bindings> <client> <endpoint address="https://www.myservice.com/Service1.svc" behaviorConfiguration="myEndpointBehaviour" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IService1" contract="Client.IService1" name="BasicHttpBinding_IService1" /> </client> <extensions> <behaviorExtensions> <add name="sendClientCertificate" type="WCFClient.SendClientCertificateBehaviorElement, WCFClient"/> </behaviorExtensions> </extensions> <behaviors> <endpointBehaviors> <behavior name="myEndpointBehaviour"> <clientCredentials> <clientCertificate storeName="My" storeLocation="CurrentUser" findValue="CN=WCF client cert 2" /> </clientCredentials> <sendClientCertificate /> </behavior> </endpointBehaviors> </behaviors></system.serviceModel>
namespace WCFClient{ using System; using System.ServiceModel.Channels; using System.ServiceModel.Description; public class SendClientCertificateBehavior : Attribute, IEndpointBehavior { #region IEndpointBehavior Members public void AddBindingParameters(ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters) { } public void ApplyClientBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime) { Binding bbinding = endpoint.Binding; BindingElementCollection bec = bbinding.CreateBindingElements(); TransportSecurityBindingElement tsp = bec.Find<TransportSecurityBindingElement>(); HttpsTransportBindingElement httpsBinding = bec.Find<HttpsTransportBindingElement>(); TextMessageEncodingBindingElement encoding = bec.Find<TextMessageEncodingBindingElement>(); httpsBinding.RequireClientCertificate = true; CustomBinding b = new CustomBinding(tsp, encoding, httpsBinding); endpoint.Binding = b; } public void ApplyDispatchBehavior(ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.EndpointDispatcher endpointDispatcher) { throw new NotImplementedException(); } public void Validate(ServiceEndpoint endpoint) { } #endregion }}
Written by Bradley Cotier