The first problem we got was that the self-signed SSL certificate was not accepted by SvcUtil.exe or "Add service reference":
The remote certificate is invalid according to the validation procedure. Could not establish trust relationship for the SSL/TLS secure channel. The remote certificate is invalid according to the validation procedure.
Sure enough, opening the URL in MSIE showed two certificate errors: issuer trust and certificate common name/site name mismatch. The first issue was easily solved, the latter was not. However, a workaround is to save the WSDL to a file and use that file as the input to SvcUtil.exe. Of course, the same invalid SSL certificate will come back and bite you when trying to use the proxy - more on this later.
I took the generated the proxy code and config output and merged it into my code. Unsurprisingly, the unit test failed with the message "no WS-Security headers found in request". I turned on message logging on both service and transport level using SvcConfigEditor.exe, remembering to set LogEntireMessage to true. Sure enough, no wsse:UsernameToken element in the soap:Header. Grief...
A lot of research lead me to the knowledge that WCF will by design not allow any sensitive data to be sent on an unsecure channel. Time to look at the generated output.config. After a few hours of frustrated config tuning and log inspections, the issue was solved: SvcUtil.exe generates config that combines Username credentials with Transport mode security. This looks OK and gives no config validation exception, but it is absolutely not correct. This is the correct config for using WS-Security over basicHttpBinding and SSL:
<transport clientCredentialType="None" proxyCredentialType="None"realm="" />
<message clientCredentialType="UserName" algorithmSuite="Default" />
WCF will not put any wsse: headers into the request unless TransportWithMessageCredential is used as the security mode. In addition, add the username and password using the .Username and .Password properties of the proxy.ClientCredentials.UserName object.
After a while it occurred to me that the Spring-WS generated WSDL contained no WS-Policy statements, thus there was no guidance in the WSDL for creating the security artifacts for the proxy and config.
Now my request went through to WebSphere! But, how long was I in paradise? Until the response came back:
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.
The problem is that WCF expects a timestamp in the response when it puts a wsse: timestamp in the request (see the transport level message log). I have not been able to turn the timestamp off using config for <basicHttpBinding>, but it can be done using a <customBinding> (see Kristian Kristensen's blog). Nicholas Allen provides code and a deeper explanation of the timestamp mechanism in his blog:
BindingElementCollection elements = proxy.Endpoint.Binding.CreateBindingElements();
elements.Find<SecurityBindingElement>().IncludeTimestamp = false;
proxy.Endpoint.Binding = new CustomBinding(elements);
While you wait for the WebSphere folks to install a valid certificate, you can intercept the certificate validation and override any SSL policy violations:
delegate(object sender, X509Certificate certificate, X509Chain chain,
//ignore invalid SSL: allow this client to communicate with unauthenticated serversreturn true;
At last my WCF client is able to invoke the web-service providing the expected WS-Security wsse:UsernameToken. What a wonderful interop world it is using WS-*.
Articles by IBM: Achieving Web services interoperability between the WebSphere Web Services Feature Pack and Windows Communication Foundation Part 1 and Part 2.