WCF: INTEROP - SCENERIOS
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& 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)
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", "https://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"))
// n.DeleteSelf();
//if (n.MoveToFollowing("Signature", "https://www.w3.org/2000/09/xmldsig#"))
// n.DeleteSelf();
if (n.MoveToFollowing("Timestamp", "https://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>
====================================================================================================
Scenario 3:
WCF talking to Java service with following requirements:
1. Send request over SSL.
2. Signing the body using the client cert for authentication and no encrypted key in the soap envelope.
3. In addition to client cert, we need to pass a user name token as secondary token for authentication.
Solution:
With Mutual Cert mode, we can sign the request using the client cert at soap envelope level. But how do we add the extra userName token ?
string baseAddress = "https://saurabh.fareast.corp.microsoft.com/test1/Service1.svc";
ChannelFactory<ServiceReference1.IService1> factory = new ChannelFactory<ServiceReference1.IService1>(GetBinding(), new EndpointAddress(baseAddress));
factory.Credentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindByIssuerName, "MSIT Enterprise CA 1");
factory.Credentials.UserName.UserName = "saurabh";
factory.Credentials.UserName.Password = "123";
factory.Credentials.ServiceCertificate.SetDefaultCertificate(StoreLocation.LocalMachine, StoreName.My, X509FindType.FindByIssuerName, "saurabh.fareast.corp.microsoft.com");
ServiceReference1.IService1 proxy = factory.CreateChannel();
Console.WriteLine(proxy.GetData(123));
static Binding GetBinding()
{
CustomBinding myCustomBinding = new CustomBinding();
AsymmetricSecurityBindingElement mySecurity = new AsymmetricSecurityBindingElement();
mySecurity.MessageProtectionOrder = MessageProtectionOrder.SignBeforeEncrypt;
mySecurity.KeyEntropyMode = SecurityKeyEntropyMode.ClientEntropy;
mySecurity.AllowInsecureTransport = true;
mySecurity.EnableUnsecuredResponse = true;
mySecurity.SecurityHeaderLayout = SecurityHeaderLayout.Lax;
X509SecurityTokenParameters myCert = new X509SecurityTokenParameters();
myCert.RequireDerivedKeys = false;
myCert.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
myCert.X509ReferenceStyle = X509KeyIdentifierClauseType.RawDataKeyIdentifier;
//Main Signing Token we want to use
//mySecurity.ProtectionTokenParameters = myCert;
mySecurity.InitiatorTokenParameters = myCert;
//Encryption token used by client
X509SecurityTokenParameters myEncryptionCert = new X509SecurityTokenParameters();
myEncryptionCert.RequireDerivedKeys = false;
myEncryptionCert.InclusionMode = SecurityTokenInclusionMode.Never;
myEncryptionCert.X509ReferenceStyle = X509KeyIdentifierClauseType.RawDataKeyIdentifier;
mySecurity.RecipientTokenParameters = myEncryptionCert;
UserNameSecurityTokenParameters myToken = new UserNameSecurityTokenParameters();
myToken.RequireDerivedKeys = false;
myToken.InclusionMode = SecurityTokenInclusionMode.AlwaysToRecipient;
//Supporting token/credentials
mySecurity.EndpointSupportingTokenParameters.Signed.Add(myToken);
myCustomBinding.Elements.Add(mySecurity);
HttpsTransportBindingElement myTrasnport = new HttpsTransportBindingElement();
myCustomBinding.Elements.Add(myTrasnport);
return myCustomBinding;
}
Why using Asymmetric binding ?
With this I aim to specify an initiator token, which is the client cert used for signing the request..
Further I specify a recipient token as x509cert which will be used to “ENCRYPT” the request.. But won’t set it..
So the request is never going with encrypted key.
And most important, I can set the extra secondary token required by the java service.
Request created from above configuration:
Hope this helps !