How to fake Transport Security in WCF
Published 4/14/2009 6:00 AM by Toran Billups 5 Comments
I have spent the last 18 months in and out of the WCF stack but until recently I had no reason to write a custom binding. This has been in part because WCF out of the box offers enough configuration options that I did not need to get my hands dirty. This was a big reason I took a step toward WCF and a step away from ASMX in 2007. Especially when it comes to SOAP and the usual WS* mess everyone in the web service world loves so much. With WCF you get all the SOAP 1.1 / 1.2 work done for you, among a ton of other messaging configurations. And if you are working in or around the enterprise like I do, you can also tap into the membership provider to leverage role based access with a few simple attributes.
In my first few "demo-ware" applications to get hype around WCF I used a simple WsHttpBinding coupled with the membership provider via service behavior.
But with these samples being just that, they never got implemented in our production environment. But late last year another developer came to me with a project that had a requirement to interop with someone outside of our network. I took on a mentor role as I thought I knew it all ... and we got a working solution ready for a migration to staging. And to my surprise I found that WCF required SSL be installed on the IIS server(s) when you use the security mode TransportWithMessageCredential inside a WsHttpBinding. This makes sense as you want to ensure SSL is setup when you start throwing around usernames and passwords.
In my case it turns out I had not done my homework with regards to our infrastructure because the production enviornment had a proxy that sits in front of the load balanced IIS farm. And it was on this proxy that our SSL certs are installed (instead of on the IIS box themselves). Here is where I ran into my dilemma ...
We needed a solution that would allow the WSDL to be pulled from an individual IIS server via the farm, yet ensure usernames were not being thrown around in plain text. The solution came from a cry of desperation on stackoverflow
The only answer I got on stackoverflow pointed me to this post
And from this post I was pointed to another
Armed with a new set of information it looked as if I could "fake" the Transport so the WCF service would "function" like the normal WsHttpBinding even if the IIS server itself did not have any sort of SSL cert installed.
I could have pointed to the posts above and called it quits, but what I found is that they did not seem to be 100% complete. This is where I come in.
The first class that you need to create will represent a custom binding element for our "faked" transport binding.
namespace App.BindingExtenions {
public class HttpsViaProxyTransportBindingElement : HttpTransportBindingElement, ITransportTokenAssertionProvider {
public override T GetProperty(BindingContext context) {
if (typeof(T) == typeof(ISecurityCapabilities))
return (T)(ISecurityCapabilities)new AutoSecuredHttpSecurityCapabilities();
return base.GetProperty(context);
}
public override BindingElement Clone() {
return new HttpsViaProxyTransportBindingElement();
}
public System.Xml.XmlElement GetTransportTokenAssertion() {
return null;
}
}
}
Next you will need to create a custom implementation of the ISecurityCapabilities interface to satisfy the dependency found in the above.
namespace App.BindingExtenions
{
public class AutoSecuredHttpSecurityCapabilities : ISecurityCapabilities
{
public ProtectionLevel SupportedRequestProtectionLevel
{
get { return ProtectionLevel.EncryptAndSign; }
}
public ProtectionLevel SupportedResponseProtectionLevel
{
get { return ProtectionLevel.EncryptAndSign; }
}
public bool SupportsClientAuthentication
{
get { return false; }
}
public bool SupportsClientWindowsIdentity
{
get { return false; }
}
public bool SupportsServerAuthentication
{
get { return true; }
}
}
}
And finally you will need to create one last class to represent the actual transport element
namespace App.BindingExtenions {
public class HttpsViaProxyTransportElement : HttpTransportElement {
public override Type BindingElementType {
get {
return typeof(HttpsViaProxyTransportBindingElement);
}
}
protected override TransportBindingElement CreateDefaultBindingElement() {
return new HttpsViaProxyTransportBindingElement();
}
}
}
Now you have the binding Element Extension needed for this custom WCF Binding.
After you have the above compile friendly, add a reference to the assembly from your WCF Service project.
Below is a very simple example of how to use this custom binding in your config file
And finally - the best part is that you do not need to add this custom assembly to your client app. Instead your client app can keep the config settings to reflect a normal WsHttpBinding. The only item you need to confirm is set in your client binding is the security section (shown in the below client config)
The final tweak needed is at the service level (in your WCF project) to avoid any Address Mismatch error you might get
namespace Service
{
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
public class ProductService : IProductService
{
public IEnumerable GetProductCollection()
{
return null;
}
}
}
Now when my client app connects to the web service via the proxy (https) I actually connect to the IIS server w/out any SSL cert installed. (A-Ha! We have faked the transport with message creds!)
If you want to investigate further, download the source here
Update:If you download the sample above keep in mind that I only included the class library needed to produce the assembly for the custom binding, not a sample WCF service project or sample client project. The 2 configuration files included are valid examples of how one might use this custom binding to setup communication between a WCF SOAP web service and a client application.
In line 9 of the example config file, there is a reference to a "ClearTextBinding" type. Your article doesn't mention what this is. Can you clarify?
Line 9 in the sample server config references the assembly "ClearTextBinding" because it contains the custom code to tell WCF about the binding details. If you compile the sample project you will notice the dll is named "ClearTextBinding.dll" - be sure to add a reference to this in your WCF service application. I will look back over my post tonight and revise it to include a mention of this step for others following who might also feel like they are missing something. Sorry for the confusion around this!
Thanks for this article, it's been a big help. Although I ran into a little problem configuring the maxReceivedMessageSize to a value greater than the default (65536) when implementing this binding. In line 17 of the server config file, i figured I could set the maxreceived message size in web config as follows HTTPSVIAPROXYTRANSPORT maxReceivedMessageSize="200000" But the value I was setting was being ignored, and I was getting exceptions whenever the message size exceeded default. As a hack to solve this, I added an override of the MaxReceivedMessageSize property the HttpsViaProxyTransportBindingElement class to always return Int32.Max. I guess that if I had wanted to make the binding as configurable as the standard bindings, I'd have to do a bit more work.
Can you provide a sample configuring this service to use the new binding in Code rather than configuration file? I'm having trouble writing the code that creates the custom binding the way you have it configured in the configuration file. Thanks.
Not bad but if your client doesn't use .NET ...? NO SSL, NO HTTPS AND YET, AN AUTHENTICATION USING DIGEST SO NO CLEAR DATA I have been using for some time this great module from Greg Reinacker http://www.rassoc.com/gregr/weblog/2002/07/09/web-services-security-http-digest-authentication-without-active-directory/ It allows to replace the IIS authentication to use a non Active Directory account. Works perfectly with Asp.NET and old style Web Service (asmx). Doesn't work directly with WCF because WCF uses a separated authentication mechanism from IIS, but I found out the trick. I posted it at the page.