Security Briefs

Security Features in WSE 3.0

Keith Brown

Code download available at:SecurityBriefs0511.exe(116 KB)

Contents

Here Comes WSE 3.0
Turnkey Security Assertions
Using a Turnkey Assertion via Policy
Turnkey Assertion via Code
Custom Policy Assertions
State Management
Conclusion

I've been spending a lot of time lately building secure Web services with the Microsoft® .NET Framework 2.0, and Web Services Enhancements (WSE) 3.0 has been a lifesaver for me, so I thought it would be appropriate to dedicate a column to security features in this new product. This is the first version that has been built around Visual Studio® 2005, and in fact WSE 3.0 won't run on version 1.1 of the .NET Framework, so all this information is still a bit cutting edge and subject to change by the final release. For this column, I'm using the Beta release of WSE 3.0.

Here Comes WSE 3.0

If you've used previous versions of WSE, you'll appreciate having the WSE settings tool built right into Visual Studio 2005, because it makes creating security policies a breeze. While this is similar to older versions of WSE, a number of things have changed. A set of turnkey security policy assertions takes a lot of the guesswork out of securing a Web service. If you have read my earlier article on the Username token (available at Securing the Username Token with WSE 2.0), you'll see firsthand how tricky it can be to build a bulletproof authentication system using WS-Security. Using a turnkey policy assertion will help you avoid many of the pitfalls mentioned in that article.

Building session-oriented systems has gotten a lot easier as well, with the stateful security context token (SCT) in WSE 3.0. By flipping a switch in your Web.config file, you can instruct WSE to keep enough state in its SCT that it can reestablish a WS-SecureConversation session with a remote Web service. With a stateful SCT, you get a measure of reliability (sessions can be restarted transparently if they are broken) plus support for sessions across Web farms.

In most Web service deployments, using a policy file to describe your security requirements is a great way to give the administrator the flexibility she needs to keep your Web service accessible and secure even when the security environment of the service changes. But in some cases, you might want to close this down to restrict the configuration options to the most basic requirements needed to support your application.

In previous versions of WSE, the policy model for specifying security configuration was divorced from the code-based model. In other words, if you didn't want to use a policy file as WSE defined it, you were forced to piece together the WS-Security header yourself. In WSE 3.0, you can create policy assertions in code, supplying the same parameters that you would normally supply via a policy file. This is a really important feature, especially given the complexity of implementing some of the new turnkey assertions. You wouldn't want to have to do it by hand!

Finally, one of my favorite features is the clean new model for building custom policy assertions. This is a great way to add custom authorization to a Web service, and it's gotten a lot easier in Web Services Enhancements 3.0.

Turnkey Security Assertions

Turnkey security assertions are the most important new security feature, so I'll focus on them first. The WSE team spent quite some time developing a number of strategies for securing Web services. They looked at server authentication with secure sockets layer (SSL) or WS-Security using an X.509 certificate, client authentication with either a certificate or a Username token, as well as other more specialized cases such as using Kerberos for mutual authentication and channel security.

Before I enumerate the strategies used by each of the turnkey assertions, I want to point out what they all have in common: each strategy seeks to provide authentication, message integrity, and confidentiality. However, each assertion approaches these security goals in different ways.

There are six turnkey assertions in the June CTP. The first is AnonymousOverCertificateSecurity, where the Web service authenticates itself to callers via its X.509 certificate. All callers are anonymous. Message-level integrity and confidentiality are supplied via a symmetric key that is exchanged using the server's public key. This model is logically similar to what you'd get if you surfed to your favorite online bookstore and purchased a book. The bookstore has too many customers to bother authenticating each of them, but it's happy to accept a credit card number and billing address, passing on the duty of authenticating the transaction to the bank.

In many business-to-business Web service scenarios, both the client and the server are assigned certificates. In this case, you would choose one of the following two assertions: CertificateMutualAuthenticationProfile or MutualCertificateSecurity. Logically these are very similar; both client and server authenticate by presenting their X.509 certificates to each other and prove possession of the corresponding private key. Message-level integrity and confidentiality are provided with a symmetric key exchanged using the server's public key. The first assertion relies on the existing WS-Security 1.0 spec, while the second uses the draft WS-Security 1.1 spec, which includes some new features such as encrypted SOAP headers.

While using certificates to authenticate clients is a great choice, often it's simply not feasible because of the overhead of maintaining the public key infrastructure (PKI) required for issuing and managing client certificates. Often it's more natural to authenticate clients with a simple user name and password. There are two turnkey assertions that support this: UsernameOverTransportSecurity and UsernameOverCertificateSecurity. You'd use UsernameOverTransportSecurity if you're relying on SSL to authenticate the server and provide integrity and confidentiality for the channel. UsernameOverCertificateSecurity is useful when SSL doesn't work for you (for example, when you have intermediaries and want end-to-end authentication).

And finally, if you're building Web services for an intranet where Kerberos is supported, which is the case in modern Windows® domain environments, the KerberosSecurity assertion is a very natural choice. Because Kerberos uses purely conventional cryptography to authenticate a client and service, it doesn't place nearly as heavy a load on the service when compared to certificate-based solutions. But the biggest benefit by far in a Windows domain environment is the support for single sign on. By using the client's default logon credentials to authenticate, there's no need to query the client for a password. And of course PKI isn't required either. The server can use Windows groups as roles to authorize access to resources or simply impersonate the client and let Windows handle the access checks.

Using a Turnkey Assertion via Policy

Using the WSE settings tool, it's easy to create a security policy with one of the turnkey assertions. If you installed WSE 3.0 with the Visual Developer option, you should see a WSE Settings 3.0 option at the bottom of your context menu when you right-click a project in the Solution Explorer. Selecting that will allow you to enable your Web service project for WSE. On the Policy tab, you can add a new policy using the security settings wizard. Each policy has a name, and you'll use that name to refer to your policy from code. When adding a new policy, you'll quickly come to the page shown in Figure 1. The authentication method you select here will determine which security policy assertion you end up with.

Figure 1 Choose an Authentication Method

Figure 1** Choose an Authentication Method **

The wording in this dialog may confuse you at first, but when you choose an authentication method, you're deciding how the client will be authenticated. So if you wanted to use the AnonymousOverCertificateSecurity assertion that I just described, you'd pick the Anonymous option. Similarly, to use KerberosSecurity, you'd select the Windows option. The Certificate option leads you towards the two certificate-based mutual authentication assertions, and the final selection will depend on whether you choose to use WS-Security 1.1 extensions, a checkbox for which shows up later in the wizard. The Username option leads you towards one of the two Username assertions, and the final selection between transport and message-level security oddly enough occurs in the same spot: if you choose to enable WS-Security 1.1 extensions, you'll end up using the UsernameOverCertificateSecurity assertion, otherwise you'll end up with UsernameOverTransportSecurity. Look at the summary page in the wizard to confirm that you have the class of assertion you wanted. Figure 2 shows an example.

Figure 2 Summary Page

Figure 2** Summary Page **

Depending on whether you're securing a service or client application, you'll often be given the choice of specifying credentials in the policy or specifying them via code. It's most convenient to specify service certificates via policy, you just need to ensure that the certificate is installed on the machine where your client or service will be deployed. On the other hand, the user name and password identifying a client should be set dynamically in code by calling SetClientCredential. Putting cleartext passwords in configuration files is a really bad idea.

A WSE policy file is simply a container of named policies. Once you've got your policies set up, you need to tell WSE which one to use. For a service, this is as easy as adding a PolicyAttribute to the class that implements the service. On the client side, I generally just call SetPolicy to choose the policy I want to use, although if you want to impress your friends, use a partial class on your proxy to add a PolicyAttribute. Figure 3 shows how I prefer to set policy. Note that you'll need a using statement for the Microsoft.Web.Services3 namespace to make your compiler happy with this sample code.

Figure 3 Wiring Policy for Services and Clients

[Policy("MyServerPolicy")] public class MyService : WebService { [WebMethod] public void DoSomething() { ... } } class ClientApp { static void Main(string[] args) { MyServiceWse proxy = GetProxy(); proxy.DoSomething(); } static MyServiceWse GetProxy() { MyServiceWse proxy = new MyServiceWse(); proxy.SetPolicy("MyClientPolicy"); return proxy; } }

Turnkey Assertion via Code

It turns out that under the covers, when WSE initializes its security settings from a policy file, it actually creates an instance of a class that represents the policy assertion, then asks that instance to initialize its settings from the XML in your policy file. Once you know this, it's easy to skip that step and simply create the policy dynamically in your code. This technique lets you choose which settings you want an administrator to control at the time of deployment and which settings you want hardcoded. Of course you can shoot yourself in the foot if you hardcode too much configuration, so tread very carefully here.

A WSE policy is an ordered collection of policy assertions. Ordering is important when you have more than one assertion, as you'll see when I discuss custom policy assertions later. But for now I'll keep things simple and show you how to dynamically create a policy with a single turnkey security assertion.

Step one is to create the turnkey assertion and configure it. I've listed the classes representing these assertions below. They match the six turnkey assertions I described earlier:

  • AnonymousOverCertificateAssertion
  • CertificateMutualAuthenticationProfileAssertion
  • MutualCertificateAssertion
  • UsernameOverTransportAssertion
  • UsernameOverCertificateAssertion
  • KerberosAssertion

Imagine you wanted to use the popular UsernameOverCertificateAssertion. Figure 4 shows an XML policy that you might create using the wizard. This is a great way to learn to code against the assertion, as the attributes you see correspond directly to the properties on the assertion class. Figure 5 shows how you'd dynamically construct the equivalent assertion in code. If you look at these two figures side by side, you'll see how easy it is to go from one to the other.

Figure 5 Coding a Policy by Hand

// here are the using statements required... using System.Security.Cryptography.X509Certificates; using Microsoft.Web.Services3.Design; using Microsoft.Web.Services3.Security; // and here is the code to construct the assertion... UsernameOverCertificateAssertion a = new UsernameOverCertificateAssertion(); // usernameOverCertificateSecurity element a.EstablishSecurityContext = false; a.RenewExpiredSecurityContext = true; a.SignatureConfirmation = false; a.DeriveKeys = true; a.ProtectionOrder = ProtectionOrder.SignBeforeEncrypting; // serviceToken/x509 element a.X509TokenProvider = new X509TokenProvider(StoreLocation.LocalMachine, StoreName.My, "CN=WSE2QuickStartServer", X509FindType.FindBySubjectDistinguishedName); SignatureOptions so = SignatureOptions.IncludeAddressing | SignatureOptions.IncludeTimestamp | SignatureOptions.IncludeSoapBody; // protection/request element a.Protection.Request.SignatureOptions = so; a.Protection.Request.EncryptBody = true; // protection/response element a.Protection.Response.SignatureOptions = so; a.Protection.Response.EncryptBody = true; // protection/fault element a.Protection.Fault.SignatureOptions = so; a.Protection.Fault.EncryptBody = false;

Figure 4 Sample Policy Configured in XML

<policy name="MyServiceSecurityPolicy"> <usernameOverCertificateSecurity establishSecurityContext="false" renewExpiredSecurityContext="true" signatureConfirmation="false" protectionOrder="SignBeforeEncrypting" deriveKeys="true" actor=""> <serviceToken> <x509 storeLocation="LocalMachine" storeName="My" findValue="CN=WSE2QuickStartServer" findType="FindBySubjectDistinguishedName" /> </serviceToken> <protection> <request signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" /> <response signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="true" /> <fault signatureOptions="IncludeAddressing, IncludeTimestamp, IncludeSoapBody" encryptBody="false" /> </protection> </usernameOverCertificateSecurity> </policy>

Now that you've got a policy assertion, you need to wrap it in a Policy object so that WSE can use it. Creating a policy is a no-brainer— just do this:

// create a policy, then add in // our assertion Policy policy = new Policy(); Policy.Assertions.Add(a);

The last step is to wire up the policy to your service or client proxy. To wire it to a client proxy, just call SetPolicy as I did in Figure 3, passing the policy object instead of a string. SetPolicy is overloaded to accept either a string representing a named policy in an XML policy file or a dynamically constructed Policy object.

Wiring up a service is a little bit different. You'll need to create a new class that derives from WSE's Policy class, use your constructor to build the assertion, then add it to your Assertions collection. Here's an example:

public class MyServerSecurityPolicy : Policy { public Policy() { ... // construct assertion here Assertions.Add(a); } }

Now you can use the PolicyAttribute just like before to indicate which policy to use for your service, only this time you'll specify the policy as a type instead of a string:

[Policy(typeof(MyServerSecurityPolicy))] public class MyService : WebService { [WebMethod] public void DoSomething() { ... } }

Custom Policy Assertions

As I said, a policy is just an ordered collection of policy assertions. Those policy assertions are actually instantiated when WSE is initializing a service or proxy at run time. Each assertion is then asked to provide code for implementing its particular policy in the WSE message-processing pipeline. So think of policy assertions as templates for laying out the pipeline that will process each message; the policy determines the order steps will execute.

When I wrote the WSE security hands-on lab, I built a logging security assertion that could be placed anywhere in the pipeline. For example, if you wanted to see what an outgoing message looked like before the security assertion encrypted it, you'd order the policy so that the logging assertion came before the turnkey security assertion. Heck, if you also wanted to see what the message looked like after it was encrypted, you could place another instance of the logging assertion after the security assertion, and just point it to another file. Here's what the XML for this policy might look like (I've omitted the details of the security policy assertion):

<policy name="MyServicePolicy"> <myTrace output="beforeSecurity.xml"/> <usernameOverX509Security ... /> <myTrace output="afterSecurity.xml"/> </policy>

Since WSE has no idea what the <myTrace> element is for, I had to tell it the name of the corresponding policy assertion class that I wrote to handle logging:

<extensions> <extension name="myTrace" type="MyAssertion, MyAssembly"/> <!-- omitted the standard WSE entries --> </extensions>

This simple assertion is not only a great way to help debug a Web service using WSE 3.0, but it's also a good example to follow if you want to try writing your own custom assertions.

So why am I bothering to talk about this feature in a column dedicated to security? Policy assertions are a great way to hoist common code out of a Web service, or even a set of Web services. Performing security checks in the WSE pipeline before the message actually reaches the Web method is an easy way to help application developers who might otherwise forget to make those security checks on their own.

There are a lot of security-related features that you can pull up into the pipeline like this. For example, if clients use certificates to authenticate to your service, you might want to map those certificates onto real Windows user accounts so that you can make requests to back-end servers using Kerberos credentials. Role-based access checks are another example. Any security feature you can pull up into the pipeline is a good thing, as it helps ensure a consistent security policy will be applied across all your Web methods.

What about validation of requests? Validating user input is an important step in building any secure system. If you can hoist some of that up into the pipeline, more power to you. Aaron Skonnard did some work in this space for WSE 2.0, so if you're interested in seeing an example, read the XML Files column in the March 2004 issue at WS-Policy and WSE 2.0 Assertion Handlers). If you think you might want to build your own custom policy assertion, start with my trace assertion and work from there, but let me leave you with some hints that will help your process.

Each policy assertion gets a chance to inject objects called filters into the pipeline that actually implement the policy assertion. The filter architecture is really simple. You must derive a class from SoapFilter and override one method: ProcessMessage. The following is an example of a filter that just lets the message flow through without doing anything:

class NoOpFilter : SoapFilter { public override SoapFilterResult ProcessMessage( SoapEnvelope envelope) { return SoapFilterResult.Continue; } }

There are four separate pipelines in WSE:

  • The sender's output pipeline
  • The receiver's input pipeline
  • The receiver's output pipeline
  • The sender's input pipeline

Note that the order I listed these pipelines follows the natural flow of a request/response message pair from the sender to the receiver and back to the sender. Because a policy assertion might need to behave differently in each of these four spots, the policy assertion is asked to provide filters for all four locations. In fact, you override four methods in your policy assertion class to supply the following filters:

  • CreateClientOutputFilter
  • CreateServiceInputFilter
  • CreateServiceOutputFilter
  • CreateClientInputFilter

Note that if your policy assertion is only meant to run in the service process, you can simply throw an exception if you are asked to create a filter for a client pipeline, because the client functions will never be called for a service pipeline. The same holds true for a client-side assertion.

In my logging policy assertion, all four filters do the exact same thing: just log the current message to a named flat file. So in my policy assertion I simply hand out an instance of the same filter class for all four methods. Additionally, I override the ReadXml method on my policy assertion so that I can configure the name of the file for each of the filters based on attributes in the XML policy file. I pass this value along to the filter I'm creating.

Things start to get interesting when you construct a policy with multiple assertions. Remember that policy determines the order of processing in the pipeline. Imagine you write the following policy and use it for both your client and your service:

<policy name="MyPolicy"> <assertionOne/> <assertionTwo/> </policy>

Figure 6** Assertions Map to Filters  **

Because assertionOne is listed first, it'll be asked to inject its filters first. Then assertionTwo will inject its filters. The resulting pipelines for the client and service will be ordered as shown in Figure 6, with the order of processing for a typical request/response message pair indicated in the diagram.

Here's how I remember this ordering—messages flow between your program and the wire. Consider your program to be at the top of the policy, and the wire to be at the bottom. It works this way on both the client and server side, making it possible to share policy files between client and server without having to reorder them.

State Management

Sometimes you will want your filters to be stateful, either for the duration of a single request/response pair, or perhaps across several different requests. WSE provides three state bags that you can use for this purpose. MessageState allows state to be shared for the duration of a message as it flows through a single pipeline. OperationState is designed for messages that are paired into request/response pairs: you can write OperationState during the request and read it back during the response. And finally, SessionState, which is managed on a per-proxy basis, allows you to store state across requests.

All of these state bags are available to you via properties of the SoapContext class, which itself is surfaced via the Context property of the SoapEnvelope class. If you look back at my filter example, you'll notice that the SoapEnvelope is passed to your filter, so these state bags are easily accessible.

Conclusion

While developing the logging assertion, I was impressed with the logic the WSE 3.0 pipeline architecture displays. Even without documentation I was able to get a simple logging assertion up and running in about 30 minutes. Thanks to the WSE team for a job well done! My experience shows that WSE 3.0 features will be appreciated by novice and advanced developers alike. For me, the custom policy assertion architecture gives me the flexibility I need to build security plumbing that developers can then easily reuse in their applications via policy. If you are new to Web services, the turnkey security policy assertions in WSE 3.0 will help you build systems using best practices and avoid many of the subtle pitfalls inherent in Web service security. You'll also be well prepared for Windows Communication Foundation (WCF) because WSE 3.0 was designed to be wire-compatible with WCF (formerly code-named "Indigo"). For more information on WSE 3.0, see Niels Flensted-Jensen's article on WSE and Authorization Manager in this issue, as well as Mark Fussell's WSE 3.0 overview article at What's New in Web Services Enhancements (WSE) 3.0.

Send your questions or comments for Keith to  briefs@microsoft.com.

Keith Brown is a co-founder of Pluralsight, a Microsoft .NET training provider. Keith is the author of Pluralsight's Applied .NET Security course, as well as several books, including The .NET Developer's Guide to Windows Security, which is available both in print and on the Web.