WCF: INTEROP
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& message, SecurityToken requiredSigningToken, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
at System.ServiceModel.Security.AsymmetricSecurityProtocol.VerifyIncomingMessageCore(Message& message, String actor, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
at System.ServiceModel.Security.MessageSecurityProtocol.VerifyIncomingMessage(Message& message, TimeSpan timeout, SecurityProtocolCorrelationState[] correlationStates)
SCENERIO:
WCF Client talking to some third party web service with message security.
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>
More scenarios will be added ....