[WCF]Secure a dynamically added message header via behavior extension (part 1)

 

Recently I receive some questions about how to sign/encrypt a custom message header which is dynamically added when calling WCF service.

 

Sure, sign/encrypt means that we’re using message layer security and in most cases, we’ll specify how the WCF message will be secured via MessageContract and ProtectionLevel. E.g.

 

[MessageContract]

    public class PatientRecord

    {

        [MessageHeader(ProtectionLevel = None)]

        public int recordID;

 

        [MessageHeader(ProtectionLevel = Sign)]

        public string patientName;

 

        [MessageHeader(ProtectionLevel = EncryptAndSign)]

        public string SSN;

 

        [MessageBodyMember(ProtectionLevel = None)]

        public string comments;

 

        [MessageBodyMember(ProtectionLevel = Sign)]

        public string diagnosis;

 

        [MessageBodyMember(ProtectionLevel = EncryptAndSign)]

        public string medicalHistory;

    }

 

And when you generate client-side service proxy, it will also get the message protection info from the service metadata(wsdl).

 

However, for those message headers that are added dynamically at runtime, there is no predefined protection information for them. Then, how can we secure them?

 

Well, Nicholas and Zulfiqar have provided two good articles describing how to achieve this task.

 

#Securing Custom Headers, Version 1

http://blogs.msdn.com/drnick/archive/2007/01/18/securing-custom-headers-version-1.aspx

 

#Setting ProtectionLevel from config file

http://zamd.net/2008/02/10/SettingProtectionLevelFromConfigFile.aspx

 

 

Since the two ones haven’t included all the detailed code, I’ll provide some detailed examples here(two examples in two parts). The example in this one will use a custom ContractBehavior to add the security protection info for our custom message header.

 

The entire example solution contains three projects, the WCF service, the WCF client and a shared class library contains the custom message header and custom ContractBehavior.

 

 

Shared class library

 

A very simple custom message header type

 

namespace SharedLib

{

    public class SimpleHeader : MessageHeader

    {

        public const string HEADER_NAME = "SimpleHeader";

        public const string HEADER_NAMESPACE = "urn:test";

 

        public string Content { get; set; }

 

        protected override void OnWriteHeaderContents(System.Xml.XmlDictionaryWriter writer, MessageVersion messageVersion)

        {

            writer.WriteString(Content);

        }

 

        public override string Name

        {

            get { return "SimpleHeader"; }

        }

 

        public override string Namespace

        {

            get { return "urn:test"; }

        }

    }

 

The custom contract behavior which plays the key role here:

 

namespace SharedLib

{

    public class SimpleContractBehavior : IContractBehavior

    {

        #region IContractBehavior Members

 

        public void AddBindingParameters(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Channels.BindingParameterCollection bindingParameters)

        {

            ChannelProtectionRequirements requirements = bindingParameters.Find<ChannelProtectionRequirements>();

 

            //add signature requirement for the custom header

            requirements.IncomingSignatureParts.ChannelParts.HeaderTypes.Add(

                new XmlQualifiedName(SimpleHeader.HEADER_NAME, SimpleHeader.HEADER_NAMESPACE)

                );

 

            //add encryption requirement ...

            requirements.IncomingEncryptionParts.ChannelParts.HeaderTypes.Add(

                new XmlQualifiedName(SimpleHeader.HEADER_NAME, SimpleHeader.HEADER_NAMESPACE)

                );

               

        }

 

        public void ApplyClientBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.ClientRuntime clientRuntime)

        {

           

        }

 

        public void ApplyDispatchBehavior(ContractDescription contractDescription, ServiceEndpoint endpoint, System.ServiceModel.Dispatcher.DispatchRuntime dispatchRuntime)

        {

           

        }

 

        public void Validate(ContractDescription contractDescription, ServiceEndpoint endpoint)

        {

          

        }

        #endregion

    }

}

 

As highlighted above, we use “AddBindingParameters” method of the ContractBehavior to inject the secured protection level info for our custom message header(we’ll add the header at client application code).

 

WCF Service

 

The service is nothing particular, as long as you use a binding and configure to use message layer security(windows or username or cert token):

 

namespace ServiceApp

{

    [ServiceContract]

    public interface ITestService

    {

        [OperationContract]

        string GetDescription();

    }

 

 

    public class TestService : ITestService

    {

        #region ITestService Members

 

        public string GetDescription()

        {

            OperationContext op = OperationContext.Current;

            int i = op.IncomingMessageHeaders.FindHeader(SimpleHeader.HEADER_NAME, SimpleHeader.HEADER_NAMESPACE);

            XmlDictionaryReader reader = op.IncomingMessageHeaders.GetReaderAtHeader(i);

 

            return "This is a simple test WCF service!\r\nHeaderContent: " + reader.ReadString();

        }

 

        #endregion

    }

 

    class Program

    {

        static ServiceHost _host = null;

 

        static void Main(string[] args)

        {

            Config_Service();

 

            Run_Service();

        }

 

        static void Config_Service()

        {

 

            _host = new ServiceHost(typeof(TestService), new Uri("http://localhost:11111/TestService"));

           

            //binding, use windows authentication for client

            WSHttpBinding binding = new WSHttpBinding( SecurityMode.Message);

            binding.Security.Message.ClientCredentialType = MessageCredentialType.Windows;

 

            //create service endpoint

            ServiceEndpoint sep = _host.AddServiceEndpoint(typeof(ITestService),binding,"");

           

            ServiceMetadataBehavior smb = new ServiceMetadataBehavior();

            smb.HttpGetEnabled = true;

 

            _host.Description.Behaviors.Add(smb);

 

            Console.WriteLine("service endpoint configured....");

        }

 

        static void Run_Service()

        {

            _host.Open();

            Console.WriteLine("service started............");

            Console.ReadLine();

        }

    }

}

 

 

WCF Client

 

Finally, our client application. Just simply generate the proxy via “Add ServiceReference” without anything particular change.

 

As I highlighted, two things important here:

 

1.    Inject our custom contractBehavior into the endpoint’s contract

2.    Add the message header into outgoing message

 

namespace ClientApp

{

    class Program

    {

        static void Main(string[] args)

        {

            CallService();

        }

 

        static void CallService()

        {

            TestSVC.TestServiceClient client = new TestSVC.TestServiceClient();

 

            client.Endpoint.Contract.Behaviors.Add(

                new SimpleContractBehavior()

                );

 

            using (OperationContextScope scope = new OperationContextScope((IContextChannel)client.InnerChannel))

            {

                OperationContext op = OperationContext.Current;

 

                op.OutgoingMessageHeaders.Add(

                    new SimpleHeader() { Content = "My test header content." }

                    );

 

                string ret = client.GetDescription();

 

                Console.WriteLine(ret);

 

            }

        }

    }

}

 

 

You can use the Message Logging to view the WCF message over transport layer so as to verify the output.

 

 

Well, this is the way using custom ContractBehavior, in the next part, I’ll demonstrate how to use an endpoint behavior to add the message header protection info.