WCF: INTEROP

 

 

         

 

Pushpa Yadav (Microsoft India GTSC)

=================================================================================================================

 

SCENERIO 2:

WCF Client talking to some third party web service with message security.

 

ERROR:

The security header element 'Timestamp' with the 'Timestamp-b82acb5a-c863-428d-a8c9-0928ae3fe651' id must be signed.

  

STACK:

<ExceptionType>System.ServiceModel.Security.MessageSecurityException, System.ServiceModel, Version=3.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</ExceptionType>

<Message>The security header element 'Timestamp' with the 'Timestamp-b82acb5a-c863-428d-a8c9-0928ae3fe651' id must be signed.</Message>

<StackTrace>

at System.ServiceModel.Security.ReceiveSecurityHeaderElementManager.EnsureAllRequiredSecurityHeaderTargetsWereProtected()

at System.ServiceModel.Security.ReceiveSecurityHeader.Process(TimeSpan timeout, ChannelBinding channelBinding, ExtendedProtectionPolicy extendedProtectionPolicy)

at System.ServiceModel.Security.MessageSecurityProtocol.ProcessSecurityHeader(ReceiveSecurityHeader securityHeader, Message&amp; message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

at System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message&amp; message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message&amp; message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)

  

 REASON:

Due to Message security being used on client side, WCF Client expects the third party service to sign the time stamp in the response.

 

 EXPECTED RESPONSE:

<u:Timestamp u:Id="uuid-ee5b5caf-e7f8-4193-a481-3ccf5195007b-9">

<u:Created>2013-07-10T21:13:59.454Z</u:Created>

<u:Expires>2013-07-10T21:18:59.454Z</u:Expires>

</u:Timestamp>

  

BAD RESPONSE:

<wsu:Timestamp wsu:Id="Timestamp-b82acb5a-c863-428d-a8c9-0928ae3fe651">

<wsu:Created>2013-07-10T19:19:27Z</wsu:Created>

<wsu:Expires>2013-07-10T19:24:27Z</wsu:Expires>

</wsu:Timestamp>

 

SOLUTION:

We need to implement IRequest channel to strip the timestamp section from message header.

 

 

CLASS:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.ServiceModel.Channels;

using System.ServiceModel;

using System.IO;

using System.Xml;

using System.Xml.XPath;

using System.ServiceModel.Configuration;

 

namespace ConsoleApplication1

{

    class StrippingRequestChannel : IRequestChannel

    {

        IRequestChannel _innerChannel = null;

 

        public StrippingRequestChannel(IRequestChannel innerchannel)

        {

            _innerChannel = innerchannel;

            // hook up event handlers

            innerchannel.Closed += (sender, e) => {if (Closed != null) Closed(sender, e);};

            innerchannel.Closing += (sender, e) => {if (Closing != null) Closing(sender, e);};

            innerchannel.Faulted += (sender, e) => {if (Faulted != null) Faulted(sender, e);};

            innerchannel.Opened += (sender, e) => {if (Opened != null) Opened(sender, e);};

            innerchannel.Opening += (sender, e) => { if (Opening != null) Opening(sender, e); };

        }

 

        #region IRequestChannel Members

 

        public IAsyncResult BeginRequest(Message message, TimeSpan timeout, AsyncCallback callback, object state)

        {

            return _innerChannel.BeginRequest(message, timeout, callback, state);

        }

 

        public IAsyncResult BeginRequest(Message message, AsyncCallback callback, object state)

        {

            return _innerChannel.BeginRequest(message, callback, state);

        }

 

        public Message EndRequest(IAsyncResult result)

        {

            return _innerChannel.EndRequest(result);

        }

 

        public EndpointAddress RemoteAddress

        {

            get { return _innerChannel.RemoteAddress; }

        }

 

        // here must we process the request

        public Message Request(Message message, TimeSpan timeout)

        {

            Message ret = null;

            // get response first

            ret = _innerChannel.Request(message, timeout);

 

            // need to create a copy first

            MessageBuffer buffer = ret.CreateBufferedCopy(int.MaxValue);

 

 

            MemoryStream stream = new MemoryStream(1024);

            buffer.WriteMessage(stream);

            stream.Position = 0;

 

            // process XML using XmlDocument and XPathNavigator

            // note that this is not the most efficient way, but it's the simplest to demonstrate

            XmlDocument doc = new XmlDocument();

            doc.Load(stream);

 

            XPathNavigator n = doc.CreateNavigator();

           

            //if (n.MoveToFollowing("BinarySecurityToken", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"))

            //    n.DeleteSelf();

                   

            //if (n.MoveToFollowing("Signature", "http://www.w3.org/2000/09/xmldsig#"))

            //    n.DeleteSelf();

 

            if (n.MoveToFollowing("Timestamp", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"))

                n.DeleteSelf();

 

            n.MoveToRoot();

            XmlReader reader = n.ReadSubtree();

 

            // create message

            Message strippedMessage = Message.CreateMessage(reader, int.MaxValue, ret.Version);

            return strippedMessage;

        }

 

        public Message Request(Message message)

        {

            Message ret = Request(message, new TimeSpan(0, 1, 30));

            return ret;

        }

 

        public Uri Via

        {

            get { return _innerChannel.Via; }

        }

 

        #endregion

 

        #region IChannel Members

 

        public T GetProperty<T>() where T : class

        {

            return _innerChannel.GetProperty<T>();

        }

 

        #endregion

 

        #region ICommunicationObject Members

 

        public void Abort()

        {

            _innerChannel.Abort();

        }

 

        public IAsyncResult BeginClose(TimeSpan timeout, AsyncCallback callback, object state)

        {

            return _innerChannel.BeginClose(timeout, callback, state);

        }

 

        public IAsyncResult BeginClose(AsyncCallback callback, object state)

        {

            return _innerChannel.BeginClose(callback, state);

        }

 

        public IAsyncResult BeginOpen(TimeSpan timeout, AsyncCallback callback, object state)

        {

            return _innerChannel.BeginOpen(timeout, callback, state);

        }

 

        public IAsyncResult BeginOpen(AsyncCallback callback, object state)

        {

            return _innerChannel.BeginOpen(callback, state);

        }

 

        public void Close(TimeSpan timeout)

        {

            _innerChannel.Close(timeout);

        }

 

        public void Close()

        {

            _innerChannel.Close();

        }

 

        public event EventHandler Closed;

 

        public event EventHandler Closing;

 

        public void EndClose(IAsyncResult result)

        {

            _innerChannel.EndClose(result);

        }

 

        public void EndOpen(IAsyncResult result)

        {

            _innerChannel.EndOpen(result);

        }

 

        public event EventHandler Faulted;

 

        public void Open(TimeSpan timeout)

        {

            _innerChannel.Open(timeout);

        }

 

        public void Open()

        {

            _innerChannel.Open();

        }

 

        public event EventHandler Opened;

 

        public event EventHandler Opening;

 

        public CommunicationState State

        {

            get { return _innerChannel.State; }

        }

 

        #endregion

 

        #region IDisposable Members

 

        public void Dispose()

        {

            IDisposable d = _innerChannel as IDisposable;

            if (d != null)

            {

                d.Dispose();

            }

        }

 

        #endregion

    }

 

 

    class StrippingChannelFactory<TChannel> : ChannelFactoryBase<TChannel>

    {

        private IChannelFactory<TChannel> innerChannelFactory;

 

        public IChannelFactory<TChannel> InnerChannelFactory

        {

            get { return this.innerChannelFactory; }

            set { this.innerChannelFactory = value; }

        }

 

        public StrippingChannelFactory()

        { }

 

        protected override void OnOpen(TimeSpan timeout)

        {

            this.innerChannelFactory.Open(timeout);

        }

 

        protected override TChannel OnCreateChannel(EndpointAddress to, Uri via)

        {

            TChannel innerchannel = this.innerChannelFactory.CreateChannel(to, via);

            if (typeof(TChannel) == typeof(IRequestChannel))

            {

                StrippingRequestChannel CachereqCnl = new StrippingRequestChannel((IRequestChannel)innerchannel);

                return (TChannel)(object)CachereqCnl;

            }

            return innerchannel;

        }

 

        protected override IAsyncResult OnBeginOpen(TimeSpan timeout, AsyncCallback callback, object state)

        {

            throw new NotImplementedException();

        }

 

        protected override void OnEndOpen(IAsyncResult result)

        {

            throw new NotImplementedException();

        }

    }

 

    class StrippingChannelBindingElement : BindingElement

    {

        public StrippingChannelBindingElement()

        {

        }

 

        protected StrippingChannelBindingElement(StrippingChannelBindingElement other)

            : base(other)

        {

        }

 

        public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context)

        {

            StrippingChannelFactory<TChannel> Cachecf = new StrippingChannelFactory<TChannel>();

            Cachecf.InnerChannelFactory = context.BuildInnerChannelFactory<TChannel>();

            return Cachecf;

        }

 

        public override BindingElement Clone()

        {

            StrippingChannelBindingElement other = new StrippingChannelBindingElement(this);

            return other;

        }

 

        public override T GetProperty<T>(BindingContext context)

        {

            return context.GetInnerProperty<T>();

        }

    }

 

    class StrippingChannelBindingElementExtensionElement : BindingElementExtensionElement

    {

        public override Type BindingElementType

        {

            get { return typeof(StrippingChannelBindingElement); }

        }

 

        protected override BindingElement CreateBindingElement()

        {

            return new StrippingChannelBindingElement();

        }

    }

 

}

 

CONFIG:

<customBinding>

<binding name="NewBinding0">

<textMessageEncoding messageVersion="Soap12" />

<security allowSerializedSigningTokenOnReply="true" enableUnsecuredResponse="true"

authenticationMode="MutualCertificate" requireDerivedKeys="false"

securityHeaderLayout="Lax" includeTimestamp="false" messageProtectionOrder="SignBeforeEncrypt"

                messageSecurityVersion="WSSecurity10WSTrustFebruary2005WSSecureConversationFebruary2005WSSecurityPolicy11BasicSecurityProfile10"

requireSecurityContextCancellation="true">

<localClientSettings cacheCookies="true" detectReplays="false" />

<localServiceSettings detectReplays="false" />

</security>

<StrippingChannel/>

<httpTransport maxBufferPoolSize="2097152" maxReceivedMessageSize="2097152"

maxBufferSize="2097152" />

</binding>

</customBinding> 

 

<extensions>

<bindingElementExtensions>

<add name="StrippingChannel"

type="ConsoleApplication1.StrippingChannelBindingElementExtensionElement, ConsoleApplication1"/>

</bindingElementExtensions>

</extensions>