Thursday, January 25, 2007

WCF Security: wsHttpBinding with brokered Kerberos

Now that I have implemented a separate ASMX endpoint for interoperability with Flex 2, it was time to switch from basicHttpBinding to wsHttpBinding for better security (authentication, encryption, signing). Using this binding will provide message level security, preventing e.g. HTTP spies on the client from snooping the data exchange with our services.

Editing the WCF client and server configuration files is a quite daunting task, but luckily the
new version of WSSF offers nice wizards for configuring security for your services. The 'WCF security' menu allows you to easily add support for the most common providers: client X.509 certificates, Kerberos, ADAM, SQL Server, ActiveDirectory, server certificate.


Among the available providers, the Kerberos provider is the simplest to use if you don't want to use a certificate nor HTTPS/SSL, or you want/has to use Cassini (the VS2005 developer web-server), as shown in the figure (click to enlarge):


The wizard is straightforward, just remember to include metadata (MEX) in your service if you want to expose a WSDL file. Adding a list of authorized clients is not mandatory for this security mode. I recommend that you delete all content in the <system.serviceModel> element in the service config file before using the 'WCF Security' menu. This ensures a clean configuration settings section afterwards.

I then updated the service reference of our SmartClient application to get the correct settings for the client config file. This step is important, as the common configuration settings must be equal on both client and server for WCF communication to work.

With all the configuration finished, it was time for some testing. I got this error when invoking a WCF operation:

System.ServiceModel.Security.MessageSecurityException: The token provider cannot get tokens for target 'http://localhost.:1508/eApproval.Host/ProjectDocumentServices.svc'. ---> System.IdentityModel.Tokens.SecurityTokenValidationException: The NetworkCredentials provided were unable to create a Kerberos credential, see inner execption for details. ---> System.IdentityModel.Tokens.SecurityTokenException: InitializeSecurityContent failed. Ensure the service principal name is correct. ---> System.ComponentModel.Win32Exception: No credentials are available in the security package
--- End of inner exception stack trace ---


My client config file contained these security settings, which to me looked fine and dandy:

<security mode="Message">
<transport clientCredentialType="Windows" proxyCredentialType="None" realm="" />
<message clientCredentialType="Windows" negotiateServiceCredential="false" algorithmSuite="Default" establishSecurityContext="false" />
</security>

My suspects were the extra <message> element attributes not covered in Keith Brown's "Security in WCF" article: negotiateServiceCredential and establishSecurityContext. As usual with new Microsoft technology, the online help is rather lame, so some research was required.

Googling lead me to this excellent article by Michèle Leroux Bustamante: "Fundamentals of WCF Security" (6 pages). The 'Service Credentials and Negotiation' section contained the explanation for my error: "When negotiation is disabled for Windows client credentials, a Kerberos domain must exist". So by turning service identity negotiation on in both the client and the server config files, my client can now communicate securely with our WCF service. I also recommend turning on the 'secure session' context for better performance, unless your service is rarely used by the client.

A confusing part of the client side authentication settings for the 'Windows' security mode is the <endpoint/identity> element, which will typically contain your "user principal name" (UPN) in the <userPrincipalName> element when hosting with Cassini or self-hosting. The identity setting is not for specifying who the client user is, but for identityfying and authenticating the service itself. If you use IIS as the host, the identity must contain the "service principal name" (SPN). Note that if your IIS app pool identity is not NETWORK SERVICE, you must typically create the SPN explicitly.

The <identity> element is only used with NTLM or negotiated security. If you use non-negotiated security such as direct Kerberos, this element is not supported.

The security config now looks like this (server side settings):

<bindings><wsHttpBinding>

<binding name="BrokeredAuthenticationKerberosBinding">
<security mode="Message">
<message clientCredentialType="Windows" negotiateServiceCredential="true" establishSecurityContext="true" />

</security>
</binding>
</wsHttpBinding>
</bindings>

Checking the on-the-wire format with Fiddler shows that the request and response messages now are encrypted; without using HTTPS or SSL transport level security:

The system provided bindings and the WSSF wizards do a good job in guiding you in the WCF settings jungle; but when your automated guide gets lost, you'd better have some survival skills. Knowing the inner workings of WCF is a must for professional service developers, and I strongly recommend reading
Michèle's article. The same goes for her blogs and the WCF stuff at IDesign.

Fiddler is highly recommended and can be downloaded from http://www.fiddlertool.com/fiddler/.

7 comments:

Anonymous said...

Really good entry Kjell-Sverre. Looks like it might be a good idea to have a section called "Noteworthy blog entries" on the Service Factory community site :)

Don

Anonymous said...

Kjell, I like your post but I would like to add my comment about why the WCF Security GP is setting the negotiateServiceCredential attrbute to false. The reason is to enable the Kerberos protocol instead of using SPNEGO tunneling with WS-Trust that is used with the dafult "true" value. You can check this by looking at the picture of the tool you mentioned where the "BinaryExchange" element has the spnego uri value type. The error you got while trying to run your client was because you might not have a "Kerberos environment" set in place. This is basically a Windows domain with Active Directory. You can take a look about how to set the SPN here: http://msdn2.microsoft.com/en-us/library/ms735117.aspx

Thanks,
Hernan

Unknown said...

does it works for FLEX clients, as i being told, flex has no support for WS* , so WSHttpBinding is problematic with flex clients

Kjell-Sverre Jerijærvi said...

No, it does not (AFAIK); we have used basicHttpBinding + SSL to support Flex clients. Authenticate using username+password or send a STS ticket as a MessageHeader and implement your own ticket resolver mechanismn in your service.

Anonymous said...

Wonderful post! WCF Security guidance package should definitely think of including this info in their documentation.

Venkat said...

Hi,
Were you able to get Flex working with wsHttpBinding?
Also if so can you share with me the config file on the WCF service side?
And also the client code?

Kjell-Sverre Jerijærvi said...

No, we used a basic + SSL endpoint for Flex, and a WS* endpoint for our B2B integration needs.

I don't have any code as this was done for a previous employer. The client side code was done using the Cairngorm framework:
http://en.wikipedia.org/wiki/Cairngorm_(Flex_framework)