I'm working on a small problem that involves connecting to a Web Service and using WS-Security and I'm using WCF. It's not going well and so I've been trying to trouble shoot exactly what's going on. WCF is pretty good about reporting error messages and generally pointing you in the right direction but in this case I've had little luck.
The problem is that the service calls are failing and I can't really tell what's happening exactly because WCF is actually injecting itself in the middle. Because the messages go over SSL I've not been able to do an Http trace either and due to the way that WS-Security works SSL can also not be turned off.
So my first thought was to just enable tracing and look at the messages.
What I'm trying to generate is this:
<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope
xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<soap:Header>
<wsse:Security xmlns:wsse="http://schemas.xmlsoap.org/ws/2002/07/secext" soap:mustUnderstand="0">
<wsse:UsernameToken>
<wsse:Username>SWT-I75O</wsse:Username>
<wsse:Password type="wsse:passwordText">$WTCO75O</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soap:Header>
<soap:Body>
<Message_Input xmlns=http://Message.com/interfaces>
<PingRequest/>
</Message_Ping_Input>
</soap:Body>
</soap:Envelope>
so I'm enabling message security over the connection with:
<basicHttpBinding>
<binding name="Message_Binding">
<security mode="TransportWithMessageCredential">
<transport clientCredentialType="None" proxyCredentialType="None"
realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
</security>
</binding>
</basicHttpBinding>
which is supposed to use message level security or ws-security. Running the client thusly though:
[TestMethod]
public void PingTest()
{
Message_Input input = new Message_Input();
input.PingRequest = "Test Value";
Message_BindingClient client = new Message_BindingClient();
//client.Endpoint.Binding = new WSHttpBinding("Message_wsBinding");
client.Endpoint.Binding = new BasicHttpBinding("Message_Binding");
client.ClientCredentials.UserName.UserName = "XXX";
client.ClientCredentials.UserName.Password = "xxx";
Output_Output output = client.Ping(input);
Assert.IsTrue(output != null, "Ping call failed. Result is null");
}
Call works and hits the server but it's returning a message error.
So the first thing I thought I should do is try to enable tracing. So I added the appropriate Message Logging config entries to the client .config file:
<system.serviceModel>
<diagnostics>
<messageLogging
logEntireMessage="true"
logMalformedMessages="true"
logMessagesAtServiceLevel="true"
logMessagesAtTransportLevel="true"
maxMessagesToLog="3000"
maxSizeOfMessageToLog="2000"/>
</diagnostics>
...
<system.serviceModel>
<system.diagnostics>
<sources>
<source name="System.ServiceModel.MessageLogging">
<listeners>
<add type="System.Diagnostics.DefaultTraceListener" name="Default">
<filter type="" />
</add>
<add initializeData="c:\traces.xml" type="System.Diagnostics.XmlWriterTraceListener"
name="messages">
<filter type="" />
</add>
</listeners>
</source>
</sources>
</system.diagnostics>
This enables a message level listener that outputs to a text file and when I run my client I get both input and output messages.
Looking at the input message however, I found that it wasn't generating at all what I was expecting it to:
<ApplicationData>
<TraceData>
<DataItem>
<MessageLogTraceRecord Time="2007-12-09T15:33:02.2280000-10:00" Source="ServiceLevelSendRequest" Type="System.ServiceModel.Dispatcher.OperationFormatter+OperationFormatterMessage" xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/">
<s:Header>
<Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none">document/http://facsia.gov.au/interfaces:Ping</Action>
<ActivityId CorrelationId="1168ef26-007c-485d-b430-473a35791395" xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">b48e36bf-34e9-49a3-a2ba-c17e7b30bac9</ActivityId>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<FaCSIADiagnosticInterface_Ping_Input xmlns="http://facsia.gov.au/interfaces">
<PingRequest>Test Value</PingRequest>
</FaCSIADiagnosticInterface_Ping_Input>
</s:Body>
</s:Envelope>
</MessageLogTraceRecord>
</DataItem>
</TraceData>
</ApplicationData>
Notice that the header here is not actually showing any username and password headers going to the server. However the message does show soap headers which indicates it's not sending a plain Soap envelope.
But this didn't look right to me. Everything I read about transport level security seems to indicate that the code I have should work, so why no security headers in the trace.
To be sure I decided maybe I need to really check the raw HTTP input and output, but this proves difficult to do since the request runs over secured HTTP. Fiddler doesn't work with SSL very well, but Charles does - sort of.
Charles lets you trace SSL messages by basically proxying the HTTPS request internally with a private certificate and then forwarding the request to the actual server. This works in some cases - if you're using a browser because the browser lets you bypass what effectively amounts to an invalid certificate.
However, using .NET Networking of any sort (Web Service Clients, HttpWebRequest etc.) by default will not work because requests balk at certificate errorrs of any kind. This is probably as it should be but it sure bites when you want to debug a request.
The trick to make this work is to turn off Certificate validation for test scenarios. I wrote about this a while ago - the solution is to create a specific certificate policy that just accepts any certificate.
There are two steps: First create the certificate policy class:
internal class AcceptAllCertificatePolicy : ICertificatePolicy
{
public AcceptAllCertificatePolicy()
{ }
public bool CheckValidationResult(ServicePoint sPoint,
X509Certificate cert, WebRequest wRequest, int certProb)
{ // *** Always accept
return true;
}
}
then go ahead an use it with the client:
client.Endpoint.Binding = new BasicHttpBinding("Message_Binding");
// client.ChannelFactory.Endpoint.Address = new EndpointAddress(https://localhost/wconnect/testpage.wwd);
ServicePointManager.CertificatePolicy = new AcceptAllCertificatePolicy();
client.ClientCredentials.UserName.UserName = "XXX";
client.ClientCredentials.UserName.Password = "xxx";
Output_Output output = client.Ping(input);
When I test this request now with Charles hooked up for Http tracing, the request goes through and I can examine the raw XML messages.
So surprise, surprise the Raw message looks very different from the Message Logging message:
<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:u="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd">
<s:Header>
<ActivityId CorrelationId="9785ff18-120a-4d53-900d-0d1071f470a8"
xmlns="http://schemas.microsoft.com/2004/09/ServiceModel/Diagnostics">595ef112-9f76-4ae1-87a2-12849cd6b1d3</ActivityId>
<o:Security s:mustUnderstand="1"
xmlns:o="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd">
<u:Timestamp u:Id="_0">
<u:Created>2007-12-10T01:36:46.122Z</u:Created>
<u:Expires>2007-12-10T01:41:46.122Z</u:Expires>
</u:Timestamp>
<o:UsernameToken u:Id="uuid-5b72d9d6-e51f-42bf-8bb2-4d6e4e66535d-1">
<o:Username>WWW</o:Username>
<o:Password o:Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">www</o:Password>
</o:UsernameToken>
</o:Security>
</s:Header>
<s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<Message_Input xmlns=http://Message.com/interfaces>
<PingRequest>Test Value</PingRequest>
</Message_Input>
</s:Body>
</s:Envelope>
and it in fact looks like WCF is sending a proper message to the server. Not only that but it even appears that the server is returning a valid SOAP response, however, WCF still fails with a message level error.
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
<SOAP-ENV:Body>
<ns:Message_Output xmlns:ns="http://Message.com/interfaces">
<ns:PingRequest>
2007-12-10T13:37:46
</ns:PingRequest>
</ns:Message_Output>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The error message is:
System.ServiceModel.Security.MessageSecurityException: Security processor was unable to find a security header in the message. This might be because the message is an unsecured fault or because there is a binding mismatch between the communicating parties. This can occur if the service is configured for security and the client is not using security..
[Updated with information from the comments - 11/10/2007]
So what is it?
Good question. The server is accepting the input and returning a SOAP response, but WCF doesn't like the result. It appears WCF is looking for additional headers in the message and failing.
As it turns out that's EXACTLY what the problem is. Thanks to one of the commenters and a previous post on the MSDN forums I was able to get this working by using the following code:
[TestMethod]
public void PingTest()
{
Message_Ping_Input input = new Message_Ping_Input();
input.PingRequest = "Test Value";
MessageClient client = new MessageClient();
client.ClientCredentials.UserName.UserName = "XXX";
client.ClientCredentials.UserName.Password = "xxx";
// *** Don't include TimeStamp header - so server won't expect it back
// *** This allows results returned without any Soap Headers
BindingElementCollection elements = client.Endpoint.Binding.CreateBindingElements();
elements.Find<SecurityBindingElement>().IncludeTimestamp = false;
client.Endpoint.Binding = new CustomBinding(elements);
Message_Ping_Output output = client.Ping(input);
Assert.IsNotNull(output, "No data structure returned");
Assert.IsNotNull(output.PingRequest, "No ping data value returned");
}
The issue is that WCF expects a TimeStamp Soap header in the response. If you look at the outbound response and the Soap headers you'll see that there's a timestamp there. The timestamp is expected to be returned on the return Soap response. Note that this is not a requirement of WS-Security so WCF is doing something 'special' here that is in effect breaking this service call.
The above code modifies the Binding configuration by explicitly removing the Timestamp from the outbound call which removes the requirement for the server to return it. And this makes WCF happy and the call goes through.
Other Posts you might also like