Share via


Web Services Enhancements 2.0 Support for WS-Policy

 

Simon Horrell
DevelopMentor

July 2004

Applies to:
   Microsoft® ASP.NET
   Web Services Enhancements (WSE) 2.0 for Microsoft® .NET
   WS-Policy specification

Summary: This article describes support for WS-Policy in WSE 2.0, outlines the policy options available, and shows examples of their usage. (38 printed pages)

Download the associated WSE2PolicySampleCode.msi code sample.

Contents

Introduction
WSE 2.0 and Policy
Specifying That a Security Token Must Be Present in a Message
Specifying That a Signature Must Be Present in a Message
Specifying Message Expiry
Specifying That a Message Must Be Encrypted
Specifying a Secure Session to Exchange Multiple Messages
Using the WSE Configuration Editor
Writing and Configuring Custom Policy Assertions
Conclusion
References
Related Books

Introduction

To interact with a Web service, one needs to be aware of its requirements, capabilities, and preferences. These policies make it possible for an application to select and use Web services in more meaningful ways.

For example, an application that wants to interact with a secure Web service needs to know what authentication information it must present in the message, and whether the message has to be signed and/or encrypted (and how, and what parts). Without this information, it is impossible for the application to know for sure what the wire-level communication with the service should look like. This information is referred to as the policy of the Web service.

This policy information must be expressed in a standard way to allow sharing of policies between applications developed and running on disparate platforms. Furthermore, it must be machine-readable to allow applications (or more likely, the Web service infrastructure) to build code to enforce the policies at runtime.

With this in mind, Microsoft, IBM, BEA, and SAP created the Web Services Policy Framework (WS-Policy). [1] It provides a model and an extensible XML syntax to describe and communicate policies. For more information about the various policy-related specifications (WS-Policy [1], WS-PolicyAttachment [2], WS-PolicyAssertions [3], WS-SecurityPolicy [4]), and how they work, see "Understanding WS-Policy" by Aaron Skonnard. [5]

Microsoft recently provided support for WS-Policy in the Web Services Enhancements (WSE) 2.0 for Microsoft .NET. [6] This article describes the policy options available in WSE 2.0 and shows examples of their usage.

WSE 2.0 and Policy

The Microsoft Web Services Enhancements (WSE) 2.0 includes a partial implementation of the WS-Policy Framework, as well as providing support for several of the standard assertions detailed in the WS-SecurityPolicy specification, such as those that require a security token to be present in a message, or require a message to be signed and/or encrypted, or specify a message expiration time. It also allows custom assertions to be supported through the implementation of custom policy assertion handlers.

WSE 1.0 has no support for WS-Policy, so building a Web service using WS-Security [7] to enforce security, for instance, means manually writing code to check whether expected tokens are present in the message, and whether the message is signed and/or encrypted in the way you expect. With WSE 2.0, it is possible to achieve the same results by simply authoring a policy file. The WSE 2.0 plumbing inspects the policy file and uses the information contained in it to enforce the policy at runtime.

Note   Unless otherwise specified, when I refer to WSE in this article, I mean WSE 2.0.

The WS-PolicyAttachment specification outlines how a particular policy is associated with the subject it applies to (e.g., a Web service endpoint). WSE 2.0 currently has no support for WS-PolicyAttachment, so the policy attachment is also defined in the policy file, along with the policies themselves. The structure of a policy file is shown below.

<policyDocument xmlns="https://schemas.microsoft.com/wse/2003/06/Policy">

  <mappings>

    <endpoint uri="http://www.develop.com/someapp/someservice.asmx">

      <operation requestAction="http://www.develop.com/someapp/someservice/someop">
        <request policy="#policy1" />
        <response policy="#policy2"/>
        <fault policy="#policy3"/>
      </operation>

      <defaultOperation>
        <request policy="#policy4" />
        <response policy="#policy5" />
        <request fault="#policy6" />
      </defaultOperation>

    </endpoint>

  </mappings> 

  <policies 
    xmlns:wsu=
"http://docs.oasis-open.org/wss/
  2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
    xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy">
  
    <wsp:Policy wsu:Id="policy1">
      <!-- Policy assertions go here -->
    </wsp:Policy>

    <wsp:Policy wsu:Id="policy2">
      <!-- Policy assertions go here -->
    </wsp:Policy>

    <!-- Other policies ommitted ... -->

  <policies>

</policyDocument>

The root of the policy file is policyDocument from the https://microsoft.com/wse/2003/06/PolicyDocument namespace. The policyDocument element contains a policies element that contains one or more Policy elements, each one containing a policy definition and its policy assertions. The policy assertions supported by WSE 2.0 are the main focus of this article and will be explored later. The policyDocument element also contains a mappings element that contains one or more endpoint elements.

Each endpoint element represents a Web service endpoint at which messages can be targeted, as specified by its uri attribute. Each operation element identifies a single Web service operation within the endpoint. The requestAction attribute represents the operation's "action" which corresponds to the Action SOAP header defined by WS-Addressing (which in turn maps to the SOAPAction HTTP header for SOAP over HTTP). Note that by default the ASMX Web services framework uses the SOAPAction HTTP header to determine which Web method, within a single Web service endpoint, the request is for. The defaultOperation element refers to all operations within the endpoint for which no operation element is defined. If no defaultOperation element is specified for an endpoint, any messages sent to that endpoint with an "action" that does not match any of the operation elements defined for the endpoint will automatically fail. Both the operation and defaultOperation elements allow a separate policy to be specified for the operation's request message, response message and fault message using the policy attribute of the request, response and fault elements respectively.

There is also a defaultEndpoint element that can contain operation elements and/or a defaultOperation element that allows policies to be specified for all endpoints that don't explicitly have a policy specified.

A request policy is enforced when request messages leave the consumer of the Web service endpoint (client) and arrive at the Web service endpoint (service). Messages are checked against the client's request policy just before the client sends them, and if a message does not satisfy the policy then WSE can modify the message in an attempt to enforce the policy. For instance, the client's request policy may say that a request message body must be encrypted. If a request message without an encrypted body is about to be sent by the client then the infrastructure will attempt to encrypt it according to the policy details before sending it. If a request message received at the service does not conform to the service's request policy, then WSE returns a SOAP fault without executing the target operation. For instance, if the service's request policy specifies that a message's body must be signed and it isn't, then it will be rejected. The response/fault policy is similarly enforced when response/fault messages leave the service and arrive at the client. Again, outgoing response/fault messages from the service are checked against the service's response/fault policy and automatically enforced where possible and incoming response/fault messages to the client are validated against the client's response/fault policy.

Having separate policies makes perfect sense, as the conditions placed on request, response and fault messages may be quite different. Often, for example, an application will only need to specify either a request or a response policy, but not both. If the request, response or fault elements have an empty policy attribute then it means that no policy is in force. Likewise, if the response or fault elements are missing then it means that no policy is in force. If the request element is missing it means that no policy is in force for the client but will result in a policy verification exception being thrown at the server stating that no policy could be found.

In this case the operation identified by the action http://www.develop.com/someapp/someservice/someop at the endpoint http://www.develop.com/someapp/someservice.asmx is mapped to three different policies—a policy with ID #policy1 is in effect for the incoming request message, a policy with ID #policy2 is enforced for the outgoing response message and a policy with ID #policy3 is associated with any outgoing fault message. Any other operation at the same endpoint—for example one identified by the action http://www.develop.com/someapp/someservice/someotherop—is also mapped to three different policies by the defaultOperation element: #policy4 for the incoming request message, #policy5 for the outgoing response message and #policy6 for any outgoing fault message.

You must tell WSE where to find the policy file in effect for an application. You do this in the application's configuration file—this will be web.config for Microsoft ASP.NET applications, and Assemblyname.exe.config for all other .NET executables. An example configuration file is shown here. Note: line breaks within attribute values (specifically the type attribute here) are only provided for clarity. Real configuration files must not include any line breaks in attribute values.

<configuration>

  <!-- Must configure section handler for microsoft.web.services section -->
  <configSections>
    <section 
      name="microsoft.web.services2" 
      type="Microsoft.Web.Services2.Configuration.WebServicesConfiguration,
            Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, 
            PublicKeyToken=31bf3856ad364e35" />
  </configSections>

  <system.web>
    <webServices>
      <!-- WSE 2.0 configured as a SOAP extension -->
      <soapExtensionTypes>
        <add 
          type="Microsoft.Web.Services2.WebServicesExtension, 
                Microsoft.Web.Services2, Version=2.0.0.0, Culture=neutral, 
                PublicKeyToken=31bf3856ad364e35" 
          priority="1" 
          group="0" />
      </soapExtensionTypes>
    </webServices>
  </system.web>

  <microsoft.web.services2>
    <policy>
      <cache name="policies.xml" />
    </policy>
  </microsoft.web.services2>

</configuration>

Notice how the WSE 2.0 runtime has been configured as a SOAP extension for the application (this is how WSE is bootstrapped for .ASMX server applications), and how the microsoft.web.services2 configuration section must be configured before the WSE 2.0 microsoft.web.services2 configuration element can be used.

One thing the WS-Policy Framework does not address is how policies are discovered and exchanged. For example, in order for a potential Web service consumer to figure out how and whether it can communicate with a Web service, it must first obtain that service's policy via some form of policy negotiation. Such policy negotiation is defined as part of the WS-MetadataExchange specification [10], although this specification is not supported by WSE 2.0.

Now it is time to look at some specific examples of how WSE 2.0 supports some of the standard assertions defined in the WS-SecurityPolicy specification.

Specifying That a Security Token Must Be Present in a Message

The SecurityToken assertion, defined in the WS-SecurityPolicy specification, requires that a message contain some kind of security token (such as an X.509 certificate), which makes a claim about the sender, such as who the sender is. The format of the SecurityToken assertion is shown below.

<wsee:SecurityToken 
  wsp:Usage="..." 
  xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy"
  xmlns:wsse="https://schemas.xmlsoap.org/ws/2002/12/secext">

  <wsse:TokenType>...</wsse:TokenType>
  <wsse:TokenIssuer>...</wsse:TokenIssuer>
  <wsse:Claims>...</wsse:Claims>
  <!-- Token type-specific information -->

</wsse:SecurityToken>

At the moment, WSE 2.0 supports five well-known token types, as shown below, as well as custom token types.

URI Description
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3 X.509 v3 certificate
https://schemas.xmlsoap.org/ws/2003/12/kerberos/Kerberosv5ST Kerberos V5 service ticket
http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#UsernameToken Username token defined in WS-Security
https://schemas.xmlsoap.org/ws/2004/04/security/sc/sct SecurityContextToken defined in WS-SecureConversation
https://schemas.xmlsoap.org/ws/2004/04/security/sc/dt DerivedKeyToken defined in WS-SecureConversation

Note that, at the time of writing, the token type defined in the WS-SecurityPolicy specification is currently a QName and not a URI. The WS-Security specification has now been officially replaced by the OASIS WSS specification. In moving from one specification to the other, one of the changes was to replace all uses of QNames with URIs for well-known values. However, this change has not yet been applied through to the WS-SecurityPolicy specification. The decision was taken with WSE 2.0 to preempt this change and so URIs are used to represent the token type.

Sending a UsernameToken

Probably the simplest and most generic security token to start with is the UsernameToken that allows a user's name and, optionally, password to be specified. Here is a policy that specifies that a UsernameToken must be present in a message.

<policyDocument 
  xmlns="https://microsoft.com/wse/2003/06/PolicyDocument">

  <mappings>
    <endpoint uri="https://localhost/policyapp/secureservice.asmx">
      <!-- Corresponds to the PassToken operation -->
      <operation 
        requestAction=
          "http://develop.com/policyapp/secureservice/passtoken”>
        <request policy="#PresentAUserNameToken"/>
      </operation>
    </endpoint>
  </mappings>

  <policies 
    xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-wssecurity-utility-1.0.xsd"       
    xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy">
    
    <wsp:Policy wsu:Id="PresentAUserNameToken">
      <SecurityToken wsp:Usage="wsp:Required" 
        xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
        <TokenType>
        http://docs.oasis-open.org/wss/2004/01/
          oasis-200401-wss-username-token-profile-1.0#UsernameToken
        </TokenType>
      </SecurityToken>
    </wsp:Policy>
  
  </policies>

</policyDocument>

Note that for a UsernameToken, the TokenIssuer element must be absent.

In order to enforce the policy assertion on behalf of the Web service consumer, WSE knows it has to send a message that contains a UsernameToken, but it doesn't know which UserNameToken. To locate a token that it needs to enforce a policy, WSE first looks in the Security.Tokens collection of the current RequestSoapContext. If a matching token cannot be found it then looks in a token cache represented by the Microsoft.Web.Services2.Security.Policy.PolicyEnforcementSecurityTokenCache class. If a matching token still cannot be found, then WSE tries to construct one if it can.

In this case the Web service consumer has chosen to populate the PolicyEnforcementSecurityTokenCache with a specific UsernameToken before it makes the call to the Web service proxy as shown.

SecureServiceWse s = new SecureServiceWse();
UsernameToken tok = 
  new UsernameToken(
    "LAP-SIMON\simonh", 
    "notreallymypwd",
    PasswordOption.SendPlainText);
PolicyEnforcementSecurityTokenCache.GlobalCache.Add(tok);
s.PassToken(); // Target Web service operation is invoked causing policy 
to be applied

The message receiver will typically want to authenticate a security token and then use the information in it to authorize what the owner of that token is, or is not, allowed to do. Such authorization may be based on who the token owner is or what functional roles the token owner is a member of.

When WSE receives a message containing a UsernameToken whose password is sent in plaintext, it will automatically attempt to authenticate the UsernameToken credentials against a Windows® account. If this is successful, then roles equate to Windows NT® groups, and the authenticated user's role memberships are decided by their Windows NT group memberships.

However, it is unrealistic to expect that passwords be sent in the clear. In the previous policy, no particular claims were made about the UsernameToken—not even that a password need be sent. The policy below, however, mandates that a secure hash of the password must be present.

<wsp:Policy wsu:Id="PresentAUserNameToken2">
  <SecurityToken wsp:Usage="wsp:Required" 
    xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext" 
    xmlns:wsse="https://schemas.xmlsoap.org/ws/2002/12/secext">
    <TokenType>
http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-username-token-profile-1.0#UsernameToken
    </TokenType>
    <Claims>
      <UsePassword wsp:Usage="wsp:Required" 
        Type="wsse:PasswordDigest"/>
    </Claims>
  </SecurityToken>
</wsp:Policy>

The default value of the Type attribute, if not present, is wsse:PasswordText (plaintext password).

In this case, the Web service consumer code will change slightly.

SecureServiceWse s = new SecureServiceWse();
UsernameToken tok = 
  new UsernameToken(
    "LAP-SIMON\simonh", 
    "notreallymypwd",
    PasswordOption.SendHashed);
PolicyEnforcementSecurityTokenCache.GlobalCache.Add(tok);
s.PassToken();

Remember, though, that now WSE cannot map the UsernameToken to a Windows account because it doesn't have a plaintext password.

If this is the case, or if the credentials contained in a UsernameToken do not map to a Windows account, then it is possible for the Web service to authenticate incoming UsernameTokens for itself by providing a token manager implementation. This makes it possible to map credentials to a Principal in whatever way it wants using a custom UsernameTokenManager implementation as shown below.

public class MySecurityTokenManager : UsernameTokenManager   {
  protected override string AuthenticateToken(UsernameToken token) {
  string pwd;
  switch(token.Username) {
    case "Simon":
      pwd = token.Username+"-pwd";
      token.Principal = new GenericPrincipal( 
        new GenericIdentity(token.Username),
        new string[] {"SomeRole","SomeOtherRole"} );
      break;
    case "Guest":
      pwd = token.Username+"-pwd";
      token.Principal = new GenericPrincipal( 
        new GenericIdentity(token.Username), 
        new string[] {"SomeRole"} );
      break;
    default:
      pwd = base.AuthenticateToken(token);
      break;
    }
    return pwd;
  }
} 

Notice how the token manager creates its own Principal based on the user's name in the provided UsernameToken, but with role membership. It then assigns it to the UsernameToken Principal property.

The token manager must be registered in the Web service's web.config, as shown here.

<configuration>
  ...
  <microsoft.web.services2>  
    ...
    <security>
      ...
      <securityTokenManager 
        qname="wsse:UsernameToken" 
        xmlns:wsse=
"http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-wssecurity-secext-1.0.xsd" 
        type="tokenmanager.MySecurityTokenManager, tokenmanager" />
    </security>
  </microsoft.web.services2>
</configuration>

Whether the UsernameToken is authenticated using a Windows account or using a custom authentication technique, the result of that authentication is available to the target of the message. The following example illustrates how to use this information to enforce fine-grained authorization within, say, a Web service method implementation.

// Assuming the first token is the one we want the principal for
Principal p = 
  RequestSoapContext.Current.Security.Tokens[0].Principal;
if (p!=null && p.Identity.Name==@"LAP-SIMON\simonh")
{
  // Principal has been checked and is now authorized to do some stuff
}

However, if the Web service is purely interested in allowing, or disallowing, access to a particular operation based on the result of authentication, then this could be expressed declaratively as shown here, without the Web service method having to provide any additional code at all.

<policyDocument 
  xmlns="https://microsoft.com/wse/2003/06/PolicyDocument">

  <mappings>
    <endpoint uri="https://localhost/policyapp/secureservice.asmx">
      <operation 
        requestAction="http://develop.com/policyapp/secureservice/passtoken">
        <request policy="#PresentAUserNameToken"/>
      </operation>
    </endpoint>
  </mappings>

  <policies 
    xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-wssecurity-utility-1.0.xsd "       
    xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy">
    
    <wsp:Policy wsu:Id="PresentAUserNameToken">
      <SecurityToken wsp:Usage="wsp:Required" 
        xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext"
        xmlns:wsse="https://schemas.xmlsoap.org/ws/2002/12/secext">
        <TokenType>
http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-username-token-profile-1.0#UsernameToken
        </TokenType>
        <Claims>
          <SubjectName 
            MatchType="wsse:Exact">LAP-SIMON\simonh</SubjectName>
        </Claims>
      </SecurityToken>
    </wsp:Policy>
  
  </policies>

</policyDocument>

Note that in this case, the Claims element specifies an exact match for the user name. In this case, only user LAP-SIMON\simonh is allowed to call the Web method. The MatchType attribute can be wsse:Exact (exact match), wsse:Regexp (regular expression match), or wsse:Prefix (prefix match), which is the default.

More likely, perhaps, is that access to an operation would be determined not by the user associated with the token, but by the role membership of the user. It is possible for the method implementer to write code to achieve this.

// Assuming the first token is the one we want the principal for
Principal p = 
  RequestSoapContext.Current.Security.Tokens[0].Principal;
if (p!=null && 
    (p.IsInRole(@"LAP-SIMON\SomeRole")|| 
     p.IsInRole(@"LAP-SIMON\SomeOtherRole"))
{
  // Principal' role has been checked and is now authorized to do some stuff
}

However, it is also possible to configure this kind of role-based method access declaratively in a policy. The policy below shows how to limit access to the Web method to members of either of the LAP-SIMON\SomeRole or LAP-SIMON\SomeOtherRole roles.

<wsp:Policy wsu:Id="PresentAUserNameToken2">
  <wsp:OneOrMore>
    <SecurityToken wsp:Usage="wsp:Required" 
      xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
      <TokenType>
http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-username-token-profile-1.0#UsernameToken
      </TokenType>
      <Claims xmlns:wse="https://schemas.microsoft.com/wse/2003/06/Policy">
        <wse:Role value="LAP-SIMON\SomeRole" />
      </Claims>
    </SecurityToken>
    <SecurityToken wsp:Usage="wsp:Required" 
      xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
      <TokenType>
http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-username-token-profile-1.0#UsernameToken
      </TokenType>
      <Claims xmlns:wse="https://schemas.microsoft.com/wse/2003/06/Policy">
        <wse:Role value="LAP-SIMON\SomeOtherRole" />
      </Claims>
    </SecurityToken>
  </wsp:OneOrMore>
</wsp:Policy>

The UsernameToken is pretty simple to use and generically applicable to most security scenarios on most platforms, but it has limitations. Passwords are a pain to manage and, in the case where the password is hashed (which you typically want), it has been shown that WSE can't automatically map a UsernameToken to a Windows account. In cases where you know your Web service consumer's credentials will map to a Windows account on the server hosting the Web service, but you don't want to send passwords in plaintext, then you will probably want to use a KerberosToken instead.

Sending a KerberosToken

If WSE 2.0 is installed on Windows Server™ 2003 or Windows® XP with Service Pack 1, then it has support for Kerberos authentication in the form of a KerberosToken. Kerberos has the benefit of being an open security standard, thus promoting interoperability between WSE-enabled Web service applications running on Windows and Web service applications running on non-Windows platforms.

The policy below shows how to configure a Web service operation to accept a Kerberos ticket as a security token.

<wsp:Policy wsu:Id="PassKerberosToken">
  <SecurityToken wsp:Usage="wsp:Required" 
    xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
    <TokenType>
    https://schemas.xmlsoap.org/ws/2003/12/kerberos/Kerberosv5ST
    </TokenType> 
  </SecurityToken>
</wsp:Policy>

Again, in this case, because the exact details of a particular Kerberos ticket have not been specified, the Web service consumer using this policy must populate the PolicyEnforcementSecurityTokenCache with a specific token—this time a KerberosToken—before it makes the call to the Web service proxy, as shown here.

string name = "host/" + System.Net.Dns.GetHostName();
KerberosToken tok = new KerberosToken(name);
PolicyEnforcementSecurityTokenCache.GlobalCache.Add(tok);
s.PassToken();

The KerberosToken is created from the current Windows user's security context—its Principal property is automatically set to this user—so it wraps a Kerberos ticket that allows the Web service consumer (running on behalf of the current user) to communicate with the host specified (the same machine, in this case).

A policy detailing a particular Kerberos ticket to use is shown below.

<wsp:Policy wsu:Id="PassKerberosToken">
  <SecurityToken wsp:Usage="wsp:Required" 
    xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
    <TokenType>
    https://schemas.xmlsoap.org/ws/2003/12/kerberos/Kerberosv5ST
    </TokenType>
    <TokenIssuer>DM</TokenIssuer>
    <Claims>
      <SubjectName>DM\simonh</SubjectName>
      <ServiceName>host/LAP-SIMON</ServiceName>
    </Claims>
  </SecurityToken>
</wsp:Policy>

In this case, all the Web service consumer using this policy needs to do is call the Web service method, as WSE has all the information it needs to obtain a Kerberos ticket and wrap it as a KerberosToken.

s.PassToken();

When the message is received by WSE, it automatically maps the Kerberos ticket to a Windows user account.

It may be that the machine hosting either the Web service consumer or the Web service doesn't support Kerberos, so using a KerberosToken is not an option. It may also be that using a UsernameToken is not an option—a UsernameToken works fine when passing messages from client to server but won't work for messages sent from server to client as the client has no way of verifying the server's password. In this case, perhaps an X.509 certificate may be a better choice as the means of specifying credentials.

Sending an X509SecurityToken

WSE 2.0 supports X.509 certificates in the form of an X509SecurityToken. While individual users often won't have their own certificates, organizations often will. This mode of operation is therefore better suited to situations where the client wants to know the server, or, in business-to-business or Enterprise Application Integration scenarios, involving inter- and intra-organization communication.

This policy shows how to accept an X.509 certificate as a security token.

<wsp:Policy wsu:Id="PassX509CertificateToken">
  <SecurityToken wsp:Usage="wsp:Required" 
    xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
    <TokenType>
http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-x509-token-profile-1.0#X509v3
    </TokenType> 
    <Claims>
      <SubjectName>CN=WSE2QuickStartClient</SubjectName>
    </Claims>
  </SecurityToken>
</wsp:Policy>  

In this case, only a message containing a token representing the certificate with the common name of WSE2QuickStartClient is accepted.

There is no need for the Web service consumer to add the required X509SecurityToken to the PolicyEnforcementSecurityTokenCache because WSE knows the name of the certificate from the Claims element, and so it can load it from a certificate store itself. Thus, the Web service consumer becomes even simpler than before, as shown here.

SecureServiceWse s = new SecureServiceWse();
s.PassToken();

Of course, if the above policy were missing the Claims element, signifying that any certificate could be presented, the Web service consumer would have to manually create an X509SecurityToken from a certificate loaded from the appropriate certificate store, and add it to the PolicyEnforcementSecurityTokenCache at runtime.

Before going on, it might be helpful to understand how to obtain and install the certificates needed to try out the examples for yourself.

The first step is to obtain a certificate. You could create a certificate yourself using the makecert utility, for instance. Alternatively, you could create a certificate request and send it to a certificate authority, such as Verisign, which will then issue a certificate. Microsoft® Certificate Services will allow you to create a certificate request and issue it for localized use. Finally, you could use an existing certificate. The WSE 2.0 samples come bundled with two sample certificates signed by the "Root agency" for testing purposes: one for a Web service consumer to use (common name of WSE2QuickStartClient), and one for a Web service to use (common name of WSE2QuickStartServer). All of the certificate-related examples in this article use these certificates. Note that in the WSE 2.0 Readme documentation for the sample code, there is a warning about performance issues when using these sample certificates. It reads as follows:

"Certain cryptographics operations may perform slowly using makecert.exe generated certificates. Certificates issued from a true Certificate Authority do not have this problem. This is a known issue."

The second step is to install the certificates in both the "CurrentUser" certificate store (representing the currently logged-in user) and the "LocalMachine" certificate store. This can be done by running the MMC Certificates snap-in. Certificates, including any in their issuer certificate chain, that are needed by code that is running in the guise of an account that has login permissions (i.e., the Web service consumer) typically go into the "CurrentUser" certificate store. Certificates, including any in their issuer certificate chain, needed by code running as an account with no login permissions (i.e., the Web service) typically go into the "LocalMachine" certificate store. The WSE 2.0 samples contain instructions for installing the sample certificates mentioned above. There is a .pfx file (containing a private and public key) for the server that installs into the "LocalMachine" certificate store and a .pfx file for the client that installs into the "CurrentUser" certificate store. There is also a .cer file (containing just a public key) for the server that installs into the "CurrentUser" certificate store. All of the certificate-related examples in this article assume this installation.

As mentioned earlier, sometimes it may be necessary for your code to choose which certificates it needs at runtime and to load them programmatically from the relevant certificate store. However, sometimes the certificates required are specified in a declarative policy file (as in the previous policy shown) and they will be loaded by WSE on your behalf. To do this, WSE will need to know which certificate store to use. The certificate store WSE uses by default is the "LocalMachine" store, which makes perfect sense for a Web service when it is running under a security account that doesn't have login permissions (e.g., the ASP.NET worker process account). However, to get the previous example to work, the Web service consumer needs WSE to obtain the WSE2QuickStartClient certificate from the "CurrentUser" store, in which case its configuration file has to look like this.

<configuration>
  ...
  <microsoft.web.services2>  
    ...
    <security>
      ...
      <x509 storeLocation="CurrentUser" />
    </security>      
  </microsoft.web.services2>
</configuration>

The receiver of a message that contains any certificate needs to check that it can trust the information in the certificate. Before a certificate can be trusted, its certification chain needs to be checked, to ensure that it extends up to a trusted root certificate authority. Once that has been done, a check needs to be carried out that ensures the certificate hasn't been revoked since it was issued. The following attributes of the X509 element, shown above in the above configuration file, are useful in this regard, as they assist WSE in carrying out certificate verification.

Attribute Description
storeLocation Specifies what certificate store WSE retrieves X.509 certificates from when verifying trust in a certificate. The default is LocalMachine.
verifyTrust Specifies whether WSE checks that a certificate's issuer chain extends to a trusted root authority. The default is true.
allowTestRoot Specifies whether WSE modifies the trust verification process to allow X.509 certificates signed by a test root to pass the verification. The default is false.
allowRevocationUrlRetrieval Specifies whether WSE resolves URLs during certificate-revocation checking. When it is set to false, only cached URLs are used. The default value is true.
allowUrlRetrieval Specifies whether WSE resolves URLs while checking the certificate's issuer chain. When it is set to false, only cached URLs are used. The default value is false

The sample certificates that come with the WSE sample code do not have an issuer chain that extends to a trusted root authority—they are signed by the test "Root agency". One way to get the last example to work would be to change the Web service's web.config to look like the one shown below.

<configuration>
  ...
  <microsoft.web.services>  
    ...
    <security>
      <x509 allowTestRoot="true" />
    </security>      
  </microsoft.web.services>
</configuration>

Currently WSE does not support the automatic mapping of X.509 certificates to Windows user accounts, although you could write code that plugs into the X.509 authentication system yourself that mapped certificates against your own store of Windows credentials.

Specifying That a Signature Must Be Present in a Message

Just sending a security token as part of a message doesn't mean very much; anyone could have sent it. What the receiver of the message needs is proof that the message really was sent by the token's owner, i.e., proof that the sender of the message lays valid claim to the credentials sent in the token. This can be achieved by digitally signing all, or parts, of the message with some 'secret key' associated with the token that only the token owner could know—perhaps a shared secret (e.g., a password), or maybe a private key. If the receiver can verify the signature, using perhaps the shared secret or maybe a matching public key, then the receiver has demonstrated that the sender was able to show knowledge of something associated with the token that only the sender could know.

However, even if the receiver does know for sure the origin of those parts of the message that were signed, anyone could have tampered with the message during transit. A signature over some data is generated not from the data itself but from a digest (one-way hash) of the data. If the receiver verifies the signature and runs the same one-way hash over the data and gets the same digest, then the receiver knows the message has not been tampered with since it was signed. A signature, therefore, provides proof that those parts of the message that were signed did indeed come from the purported sender and have not been changed since they were sent.

By authoring the correct policy, you can get WSE to sign messages before they are sent and verified as they are received. The Integrity assertion, defined in the WS-SecurityPolicy specification, is used to indicate a required signature format as laid out in the WS-Security specification. The structure of an Integrity assertion is shown below.

<wsse:Integrity 
  wsp:Preference="..." 
  wsp:Usage="..." 
  xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy"
  xmlns:wsse="https://schemas.xmlsoap.org/ws/2002/12/secext">

  <wsse:Algorithm Type="..." URI="..." wsp:Preference="..."/>
  <wsse:TokenInfo>
    <wsse:SecurityToken>...</wsse:SecurityToken>
  </wsse:TokenInfo>
  <wsse:Claims>...</wsse:Claims>
  <wsse:MessageParts Dialect="..." Signer="...">
    ...
  </wsse:MessageParts>
<wsse:Integrity>

The WSE tokens that support signatures are UsernameToken, X509SecurityToken, KerberosToken, SecurityContextToken, DerivedKeyToken, and custom tokens.

Signing with a UserNameToken

The following policy file shows how to specify that any UsernameToken must sign a message.

<policyDocument 
  xmlns="https://microsoft.com/wse/2003/06/PolicyDocument">

  <mappings>
    <endpoint to="https://localhost/policyapp/secureservice.asmx">
      <operation 
        requestAction="http://develop.com/policyapp/secureservice/signwithtoken">
        <request policy="#SignBodyWithUserNameToken"/>
      </operation>
    </endpoint>
  </mappings>

  <policies 
    xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-wssecurity-utility-1.0.xsd "       
    xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy">

    <wsp:Policy wsu:Id="SignBodyWithUserNameToken">
      <Integrity wsp:Usage="wsp:Required" 
        xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
        <TokenInfo>
          <SecurityToken wsp:Usage="wsp:Required">
            <TokenType>
http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-username-token-profile-1.0#UsernameToken 
            </TokenType>
          </SecurityToken>
        </TokenInfo>
        <MessageParts 
          Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part">
          wsp:Body()
        </MessageParts>
      </Integrity>
    </wsp:Policy>

  </policies>

</policyDocument>

In this case, the user's password (along with a nonce and timestamp) is used to generate the key used to sign and verify the message.

The MessageParts element describes what parts of the message are to be signed. If you were to sign a message programmatically with WSE, without being specific about which parts you wanted to sign, then it would sign the following message parts by default:

soap:Envelope/soap:Header/soap:Body
soap:Envelope/soap:Header/wsse:Security/wsu:Timestamp
soap:Envelope/soap:Header/wsa:Action,
soap:Envelope/soap:Header/wsa:To
soap:Envelope/soap:Header/wsa:MessageID

...and also sign the following WS-Addressing headers if present:

soap:Envelope/soap:Header/wsa:ReplyTo
soap:Envelope/soap:Header/wsa:FaultTo
soap:Envelope/soap:Header/wsa:From

Of course, it is possible to not rely on this default and programmatically specify exactly what parts of the message you want signed. When declaratively specifying that a message be signed, WSE mandates that the MessageParts element must be present to indicate which parts of the message are to be signed. The MessageParts element uses a dialect to describe the message parts. Two dialects are defined in the WS-PolicyAssertions specification. The dialect http://www.w3.org/TR/1999/REC-xpath-19991116 means that XPath expressions are used to describe the message parts. Additionally, it defines a set of functions to simplify expressions that may be used to reference SOAP message elements. Although this dialect is nominated by the specification as the default, it is not supported by WSE. The other dialect, https://schemas.xmlsoap.org/2002/12/wsse\#part, defines functions to identify the message parts. The supported functions are shown below.

Function Description
wsp:Body() This function identifies the "body" of the message.
wsp:Header(x) This function identifies a "header" whose name is the specified QName, x.

As you can see, the policy above asserts that the body (soap:Envelope/soap:Header/soap:Body) of the message must be signed. It is possible to sign multiple parts of the message by having a single MessageParts element whose content is a white-space-separated list of functions, or by having multiple MessageParts elements. The following policy causes the message body and soap:Envelope/soap:Header/wsa:Action header to be signed.

<wsp:Policy wsu:Id="SignBodyAndHeaderWithUserNameToken">
  <Integrity wsp:Usage="wsp:Required" 
    xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
    <TokenInfo>
      <SecurityToken wsp:Usage="wsp:Required">
        <TokenType>
http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-username-token-profile-1.0#UsernameToken
        </TokenType>
      </SecurityToken>
    </TokenInfo>
    <MessageParts 
      Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part">
      wsp:Body()
    </MessageParts>
    <MessageParts 
      xmlns:wsa="https://schemas.xmlsoap.org/ws/2004/03/addressing" 
      Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part">
      wsp:Header(wsa:Action)
    </MessageParts>
  </Integrity>
</wsp:Policy>

Note that in WSE 2.0, it is only possible to enforce the Integrity assertion using the following message parts in the https://schemas.xmlsoap.org/2002/12/wsse\#part dialect: wsp:Body() or wsp:Header() for the WS-Addressing To, Action, From, ReplyTo, RelatesTo, Recipient, FaultTo and MessageID headers or any custom header. However, there are a couple of proprietary WSE-specific message parts that can be used to specify the Timestamp header (wse:Timestamp()) and all WS-Addressing headers (wse:Addressing()). Usage of the wse:Timestamp() message part is shown in a later example.

The Web service consumer code and Web service code for which either of the previous two policies will be enforced doesn't really change from that shown earlier, since WSE will automatically add the signature to the message as it is sent, and verify the signature as the message is received. The Web service consumer code is shown below.

SecureServiceWse s = new SecureServiceWse();
UsernameToken tok = 
  new UsernameToken(
    "LAP-SIMON\simonh", 
    "notreallymypwd",
    PasswordOption.SendHashed);
PolicyEnforcementSecurityTokenCache.GlobalCache.Add(tok);
s.SignWithToken();

Signing with an X509SecurityToken

If the policy was changed to the one below, then the specified X.509 certificate would be used to sign the body and the soap:Envelope/soap:Header/wsa:Action header of the message.

<wsp:Policy wsu:Id="SignBodyAndHeaderWithX509CertificateToken">
  <Integrity wsp:Usage="wsp:Required" 
    xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
    <TokenInfo>
      <SecurityToken>
        <TokenType>
http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-x509-token-profile-1.0#X509v3
        </TokenType> 
        <Claims>
          <SubjectName>CN=WSE2QuickStartClient</SubjectName>
        </Claims>
      </SecurityToken>
    </TokenInfo>
    <MessageParts 
      Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part" 
      xmlns:wsa="https://schemas.xmlsoap.org/ws/2004/03/addressing">
      wsp:Body() wsp:Header(wsa:Action)
    </MessageParts>
  </Integrity>
</wsp:Policy>

The certificate's private key is used to sign the message and the certificate's public key is used to verify the message.

There is not really anything for the Web service consumer or Web service to do in this case, since WSE will automatically add the signature to the message as it is sent and verify the signature as the message is received. All the Web service consumer needs to do is concentrate on the business logic by calling the method as shown below.

SecureServiceWse s = new SecureServiceWse();
s.SignWithToken();

Signing with a KerberosToken

The policy below requires that a message body be signed with a KerberosToken.

<wsp:Policy wsu:Id="SignBodyWithKerberosToken">
  <Integrity wsp:Usage="wsp:Required" 
    xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
    <TokenInfo>
      <SecurityToken wsp:Usage="wsp:Required">
        <TokenType>
        https://schemas.xmlsoap.org/ws/2003/12/kerberos/Kerberosv5ST 
        </TokenType>
      </SecurityToken>
    </TokenInfo>
    <MessageParts 
      Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part">
      wsp:Body()
    </MessageParts>
  </Integrity>
</wsp:Policy>

In this case, the message is signed and verified with the secret key inside the Kerberos ticket associated with the KerberosToken.

Specifying Message Expiry

Even when messages are signed they could still be replayed against the Web service and have an undesirable effect. To minimize the chances of this happening, it is possible to author a policy that specifies a message expiration time. This causes WSE to send the message with a Timestamp expiration equal to the more restrictive of the MessageAge policy assertion's Age attribute value (in seconds), any timestamp expiry that may have been set programmatically or the default value of 5 minutes. The MessageAge policy assertion's usage is shown in the policy illustrated below. Assuming that no timestamp was set programmatically, the message's Timestamp will have an expiration of 60 seconds.

<wsp:Policy wsu:Id="AgeMessage">
  <MessageAge 
    xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext"
    wsp:Usage="wsp:required" 
    Age="60" />
</wsp:Policy>

Any message received by WSE that has expired will be rejected. There is no onus on the Web service consumer or Web service to write any additional code at all.

Of course, it is essential to set the Age attribute of the MessageAge to a value (in seconds) that is big enough to allow the message to be processed in normal circumstances, but guard against replay attack. It helps also to sign the Timestamp header to prevent it from being tampered with. A later example shows how to use WSE to sign the Timestamp header based on a policy expression.

Specifying That a Message Must Be Encrypted

When a signature is applied to a SOAP message, it ensures that sender cannot repudiate the message, and that it has integrity. However, the message itself is sent in plaintext. If it is required that the message, or parts of it, must be private to sender and receiver, then the message must be encrypted.

WSE allows encryption requirements to be specified declaratively such that WSE will encrypt messages before they are sent and will decrypt them as they are received. The Confidentiality assertion, defined in the WS-SecurityPolicy specification, is used to indicate a required encryption format as laid out in the WS-Security specification. The format of the Confidentiality assertion is illustrated below.

<Confidentiality 
  wsp:Preference="..." 
  wsp:Usage="..." 
  xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy"
  xmlns:wsse="https://schemas.xmlsoap.org/ws/2002/12/secext">

  <Algorithm Type="..." URI="..." wsp:Preference="..."/>
  <KeyInfo>
    <SecurityToken .../>
    <SecurityTokenReference .../>
    ...
  </KeyInfo>
  <MessageParts Dialect="...">
  ...
  </MessageParts>
</Confidentiality>

The WSE tokens that support encryption are X509SecurityToken, KerberosToken, SecurityContextToken, DerivedKeyToken, and custom tokens. The policy below shows how to specify that a specified X509SecurityToken must encrypt a message's body.

<policyDocument 
  xmlns="https://microsoft.com/wse/2003/06/PolicyDocument">

  <mappings>
    <endpoint to="https://localhost/policyapp/secureservice.asmx">
      <operation 
        requestAction="http://develop.com/policyapp/secureservice/encryptwithtoken">
        <request policy="#EncryptBodyWithX509CertificateToken"/>
      </operation>
    </endpoint>
  </mappings>

  <policies 
xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-wssecurity-utility-1.0.xsd "       
    xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy">

    <wsp:Policy wsu:Id="EncryptBodyWithX509CertificateToken">
      <Confidentiality wsp:Usage="wsp:Required" 
        xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
        <KeyInfo>
          <SecurityToken wsp:Usage="wsp:Required">
            <TokenType>
http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-x509-token-profile-1.0#X509v3 
            </TokenType>
            <Claims>
              <SubjectName>CN=WSE2QuickStartServer</SubjectName>
            </Claims>
          </SecurityToken>
        </KeyInfo>
        <MessageParts 
          Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part">
          wsp:Body()
        </MessageParts>
      </Confidentiality>
    </wsp:Policy>

  </policies>

</policyDocument>

Rather than encrypting the data with the certificate's public key directly, a shared symmetric key, that is itself encrypted with the certificate's public key and included in the message, encrypts the data. The certificate's private key is used to decrypt the shared key that is then used to decrypt the data. It is much faster to use a symmetric key to encrypt/decrypt bulk data than using asymmetric keys.

Again, there is nothing for the Web service consumer and Web service to do, since WSE will automatically encrypt the message before it is sent and decrypt it as it received. In this case, all the Web service consumer needs to do is call the method as shown here.

SecureServiceWse s = new SecureServiceWse();
s.EncryptWithToken();

WSE must have permission to retrieve the private key from the local computer store so it can perform the decryption of the shared key. This means either running ASP.NET under an account that has the necessary permission (System or the key owner), or providing the ASP.NET account with the necessary permission. The latter can be achieved as follows.

  1. Open the WseCertificate tool.
  2. Select the Certificate Location as Local Computer and Store Name as Personal.
  3. Click Select Certificate from Store.
  4. Select the certificate needed to decrypt/sign the message.
  5. Click Open Private Key File Properties.
  6. On the Security tab, add either the "ASPNET" account (if running IIS 5) or the "Network Service" account (if running IIS 6), and then select the Read option.

Under the covers, this is basically modifying the permissions of the file (under the Machine Keys folder) to include the ASP.NET worker account. Without this, ASP.NET will not have privileges to access the private key file and hence cannot perform the decryption. There is a help topic on this very subject in the WSE 2 documentation (at ms-help://MS.WSE20.1033/wse/html/gxaconhowtomakex509certificatesaccessibletowse.htm).

Specifying a Secure Session to Exchange Multiple Messages

The policies shown so far have illustrated how to secure a single message. Each message is self-contained and must carry the credentials necessary to enforce its protection. This means that the credentials must be formulated with each message sent and authenticated with each message received.

Applications that expect to secure a whole conversation involving multiple messages may require facilities more like those offered by SSL, whereby credentials are exchanged upfront to establish a secure 'session' that protects a series of messages in a dialogue. Each message benefits from the protection offered by the session and credentials do not have to be passed with every message. The benefit of doing this is speed; although there is an initial upfront cost of exchanging and authenticating credentials, this is amortized over time since credentials do not have to be exchanged and authenticated for each message.

In this latter scenario, the single message authentication, integrity, and privacy features provided by WS-Security are not enough by themselves. To address this, the WS-SecureConversation specification builds on the WS-Security and WS-Trust specifications to define a higher-level protocol that is used to establish a security context that can be shared between applications. This security context identifies shared secret keys that can be used to secure a multi-message conversation between the applications. WSE 2 includes a sample implementation of WS-SecureConversation, and its WS-Policy implementation allows the requirements for a secure session to be declaratively specified to be enforced by WSE.

The shared security context is represented by another kind of security token, called a SecurityContextToken (SCT), which ultimately identifies one or more shared symmetric secret keys. Once the SecurityContextToken is obtained and authenticated by all parties, and as long as it hasn't expired, its secret keys can be used to sign and/or encrypt messages. As mentioned before, using a symmetric key is much more efficient than using an asymmetric key.

The SecurityContextToken is commonly created by a security token-issuing service, in accordance with the WS-Trust specification. Each party that shares the SecurityContextToken must trust this token user. The application wanting to establish the SecurityContextToken (the context initiator) sends a signed RequestSecurityToken (RST) message to the token issuer indicating which target endpoint it ultimately wishes to converse with. The context initiator must sign the RST message so that the token issuer, who can also determine the integrity of the message, can authenticate it. If all is well, the token issuer sends back a signed RequestSecurityTokenResponse (RSTR) message to the context initiator. Again, the token issuer signs the RSTR message so that the context initiator can be sure of where it came from and that it hasn't been tampered with. The RSTR message contains the requested SecurityContextToken and a Proof-of-Possession token.

The SecurityContextToken is for the target endpoint and either contains a secret key, contains enough information to generate a secret key, or implies a secret key. If the SecurityContextToken contains secret key material, then it is encrypted so that only the target endpoint can see it. The Proof-of-Possession token is for the context initiator and either contains the same secret key, or enough information to generate it, and is encrypted so that only the target endpoint can see it.

The context initiator can now send an initial message to the target endpoint that contains the SecurityContextToken, which may be signed by and/or encrypted with the shared secret key (or a shared secret key derived from it). Once the application at the target endpoint has authenticated with the token issuing service and validated the SecurityContextToken, then the SecurityContextToken is deemed to be authenticated and any information in it deemed to have been exchanged, most notably the secret key material, if present. Any subsequent messages sent by the context initiator to the target endpoint and secured as part of this security context need only contain a lightweight version of the SecurityContextToken containing its unique identifier.

Figure 1 shows the exchange necessary to establish a security context.

Figure 1. Establishing an SCT via a token-issuing service

As mentioned before WSE 2 supports WS-SecureConversation. The easiest way to set up a token issuer is to advise WSE that you would like it to act as the token issuer for all Web services that reside in a particular virtual directory. The way to do this is to configure the web.config in the virtual directory as shown below.

<configuration>
  <microsoft.web.services2>
    <security>
      <x509 allowTestRoot="true" />
    </security>
    <tokenIssuer>
      <serverToken>
        <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
          <wsse:SecurityTokenReference 
            xmlns:wsse=
"http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-wssecurity-secext-1.0.xsd">
            <wsse:KeyIdentifier 
              ValueType=
"http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-x509-token-profile-1.0#X509SubjectKeyIdentifier">
            bBwPfItvKp3b6TNDq+14qs58VJQ=
            </wsse:KeyIdentifier>
          </wsse:SecurityTokenReference>
        </KeyInfo>
      </serverToken>
      <autoIssueSecurityContextToken enabled="true" />
    </tokenIssuer>
    ...
  </microsoft.web.services2>
  ...
</configuration>

The tokenIssuer element's autoIssueSecurityContextToken element tells WSE to implement a token issuing service on behalf of all Web services in this virtual directory. The tokenIssuer element's serverToken element describes the token issuer's X.509 certificate used to sign the RequestSecurityTokenResponse. In this case, the X.509 certificate must have a private key and be accessible from the ASP.NET worker process.

The context initiator could establish a secure conversation programmatically. The WSE WS-Policy implementation, however, allows the requirements for a secure conversation to be declaratively specified to be enforced by WSE. The policy shown below will cause WSE to obtain and use a SecurityContextToken on behalf of the context initiator—the Web service consumer, in this case—in its communication with the Web service.

<policyDocument 
  xmlns="https://schemas.microsoft.com/wse/2003/06/Policy">
  <mappings>
    <endpoint to="https://localhost/policyapp/secureservice2.asmx">
      <operation requestAction="https://schemas.xmlsoap.org/ws/2004/04/security/trust/RST/SCT">
         <request policy="#SignRSTWithX509Certificate" />
         <response policy="#SignRSTRWithX509Certificate" />
      </operation>
      <defaultOperation>
         <request policy="#SignAndEncryptBodyWithSCT" />
         <response policy="#SignAndEncryptBodyWithSCT-1" />
      </defaultOperation>     </endpoint>
  </mappings>  
  <policies 
    xmlns:wsu=
"http://docs.oasis-open.org/wss/2004/01/
  oasis-200401-wss-wssecurity-utility-1.0.xsd" 
    xmlns:wsp=
      "https://schemas.xmlsoap.org/ws/2002/12/policy"> 
    <wsp:Policy wsu:Id="SignAndEncryptBodyWithSCT" 
xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy">
      <Integrity wsp:Usage="wsp:Required" xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
        <TokenInfo>
          <SecurityToken wse:IdentityToken="true">
            <TokenType>https://schemas.xmlsoap.org/ws/2004/04/security/sc/sct</TokenType>
            <Claims>
              <wse:BaseToken>
                <SecurityToken>
                  <TokenType>
                  http://docs.oasis-open.org/wss/2004/01/
                    oasis-200401-wss-x509-token-profile-1.0#X509v3
                  </TokenType>
                  <Claims>
                    <SubjectName>CN=WSE2QuickStartClient</SubjectName>
                  </Claims>
                </SecurityToken>
              </wse:BaseToken>
            </Claims>
          </SecurityToken>
        </TokenInfo>
        <MessageParts Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part">wsp:Body()</MessageParts>
      </Integrity>
      <Confidentiality wsp:Usage="wsp:Required" xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
        <KeyInfo>
          <SecurityToken wse:IdentityToken="true">
            <TokenType>https://schemas.xmlsoap.org/ws/2004/04/security/sc/sct</TokenType>
            <Claims>
              <wse:BaseToken>
                <SecurityToken>
                  <TokenType>
                  http://docs.oasis-open.org/wss/2004/01/
                    oasis-200401-wss-x509-token-profile-1.0#X509v3
                  </TokenType>
                  <Claims>
                    <SubjectName>CN=WSE2QuickStartClient</SubjectName>
                  </Claims>
                </SecurityToken>
              </wse:BaseToken>
            </Claims>
          </SecurityToken>
        </KeyInfo>
        <MessageParts Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part">wsp:Body()</MessageParts>
      </Confidentiality>
    </wsp:Policy>
    <wsp:Policy wsu:Id="SignAndEncryptBodyWithSCT-1">
      <Integrity wsp:Usage="wsp:Required" xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
        <TokenInfo>
          <SecurityToken wse:IdentityToken="true">
            <TokenType>https://schemas.xmlsoap.org/ws/2004/04/security/sc/sct</TokenType>
          </SecurityToken>
        </TokenInfo>
        <MessageParts Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part">wsp:Body()</MessageParts>
      </Integrity>
      <Confidentiality wsp:Usage="wsp:Required" 
xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
        <KeyInfo>
          <SecurityToken wse:IdentityToken="true">
            <TokenType>
              https://schemas.xmlsoap.org/ws/2004/04/security/sc/sct
            </TokenType>
          </SecurityToken>
        </KeyInfo>
        <MessageParts Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part">
      wsp:Body()
      </MessageParts>
      </Confidentiality>
    </wsp:Policy>
    <wsp:Policy wsu:Id="SignRSTWithX509Certificate" xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy">
      <Integrity wsp:Usage="wsp:Required" xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
        <TokenInfo>
          <SecurityToken>
            <TokenType>
            http://docs.oasis-open.org/wss/2004/01/
              oasis-200401-wss-x509-token-profile-1.0#X509v3
            </TokenType>
            <Claims>
              <SubjectName>CN=WSE2QuickStartClient</SubjectName>
            </Claims>
          </SecurityToken>
        </TokenInfo>
        <MessageParts Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part"
          xmlns:wsa="https://schemas.xmlsoap.org/ws/2004/03/addressing">
        wsp:Body() wsp:Header(wsa:To) wsp:Header(wsa:Action) 
wsp:Header(wsa:MessageID) wsp:Header(wsa:ReplyTo)
        wse:Timestamp()
        </MessageParts>
      </Integrity>
    </wsp:Policy>
    <wsp:Policy wsu:Id="SignRSTRWithX509Certificate" 
xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy">
      <Integrity wsp:Usage="wsp:Required" 
xmlns="https://schemas.xmlsoap.org/ws/2002/12/secext">
        <TokenInfo>
          <SecurityToken>
            <TokenType>
http://docs.oasis-open.org/wss/2004/01/
   oasis-200401-wss-x509-token-profile-1.0#X509v3
            </TokenType>
            <Claims>
              <SubjectName>CN=WSE2QuickStartServer</SubjectName>
            </Claims>
          </SecurityToken>
        </TokenInfo>
        <MessageParts Dialect="https://schemas.xmlsoap.org/2002/12/wsse#part" 
          xmlns:wsa="https://schemas.xmlsoap.org/ws/2004/03/addressing">
            wsp:Body() wsp:Header(wsa:To) wsp:Header(wsa:Action) 
            wsp:Header(wsa:MessageID) wsp:Header(wsa:RelatesTo) 
        wse:Timestamp()
        </MessageParts>
      </Integrity>
    </wsp:Policy>   
  </policies>
</policyDocument>

Notice that all operations, bar one, at the Web service's endpoint are configured with a request policy with ID SignAndEncryptBodyWithSCT. This requires any messages sent to the Web service to be signed and encrypted with a SecurityContextToken (returned by the token issuing service residing at the same address as the Web service's endpoint). This triggers WSE to enter into a negotiation with the token issuing service on behalf of the Web service consumer in order to retrieve the necessary SecurityContextToken that can be used to talk to the Web service. The X.509 certificate used to sign the RequestSecurityToken message is contained inside the BaseToken element under the Claims element. This certificate must reside in the "Current User" store.

Notice also that the operation with a request action of https://schemas.xmlsoap.org/ws/2004/04/security/trust/RST/SCT (the WS-SecureConversation action associated with the RequestSecurityToken message is configured with a request policy with ID SignRSTWithX509Certificate and a response policy with ID SignRSTRWithX509Certificate. The request policy causes WSE to send a RequestSecurityToken to the token-issuing service signed by the X.509 client certificate specified. The WSE token issuing service mandates that the body of the message containing the RequestSecurityToken be signed, along with any WS-Addressing headers present and the Timestamp header. The response policy causes WSE to check that the specified X.509 certificate signed the body of the message containing the RequestSecurityTokenResponse returned by the token-issuing service, along with any WS-Addressing headers present and the Timestamp header.

The code snippet below shows how simple the Web service consumer code is.

SecureService2Wse s2 = new SecureService2Wse();
s2.method1("Hello ");
s2.method2("World"); 

In this case both the requests and responses associated with method1 and method2 are sent within the same secure context.

Note that in this example, because WSE supplied the token issuing service in the same virtual directory as the target Web service, it knows the shared secret associated with any SecurityContextToken it issued. This means that if you inspect the SecurityContextToken issued in this example, you will see that it doesn't contain any shared secret key material.

For the purpose of simplicity, this example used a SecurityContextToken to sign and encrypt the body of all messages sent to the Web service. However, it is advisable to use a DerivedKeyToken derived from the SecurityContextToken instead, as this is much safer. If new keys are derived from the original secret key (which is never used itself to sign/encrypt data) at regular intervals, then they are used for less time, and therefore are used to sign/encrypt less data. First, the window of opportunity for attacking the key based on its usage is lessened. Second, if the key is compromised, then further keys cannot be deduced without knowledge of the original secret key. It turns out that if we had used the WSE configuration editor to author a policy file describing a secure conversation then a DerivedKeyToken would have been specified.

Using the WSE Configuration Editor

There is a GUI configuration editor tool that comes with WSE 2.0 for authoring WSE configuration files and, in particular, security-related policy files. It is called WseConfigEditor2.exe and is found in the Tools\ConfigEditor subdirectory of the WSE 2.0 installation directory. I would heartily endorse using this tool for authoring security policies because writing a policy file by hand is very tedious and error-prone. If the correct WSE installation option is chosen, the configuration editor is also integrated into Visual Studio as the "WSE Settings Tool" add-in available by right-clicking the project in Solution Explorer. Pay particular attention to the diagnostics option that allows the tracing of all policy checking and enforcement decisions made by WSE, as this can be a very useful debugging aid.

Writing and Configuring Custom Policy Assertions

If the standard policy assertions covered here don't satisfy your requirements, then it is also possible to write custom policy assertion handlers that fit right into the WSE policy framework. Details of how to do this are shown in the March 2004 edition of MSDN Magazine in the "XML Files" column [11]. The sample code that accompanies this article also demonstrates a simple custom policy assertion handler that enforces a maximum message size constraint.

Conclusion

The WS-Policy family of specifications provides a standard way to express the requirements, capabilities, and preferences of Web service applications and make it easier for them to "reason" with each other. Their interoperable, declarative nature makes it possible for Web service infrastructure to provide support for enforcing policy assertions on behalf of a Web service application and, in this case, the developer has far less code to write. This is exactly what WSE 2.0 does: today it provides support for some of the assertions defined in the WS-SecurityPolicy specification, as well as providing an extensible mechanism to allow custom assertions to be handled in the same manner.

References

[1] Web Services Policy Framework (WS-Policy) specification

[2] WS-PolicyAttachment specification

[3] WS-PolicyAssertions specification

[4] WS-SecurityPolicy specification

[5] Understanding WS-Policy by Aaron Skonnard

[6] Web Services Enhancements (WSE) 2.0 for Microsoft .NET

[7] WS-Security specification

[8] XML-Signature Syntax and Processing (Digital Signature specification)

[9] XML Encryption Syntax and Processing (specification)

[10] WS-MetadataExchange specification

[11] The XML Files: WS-Policy and WSE 2.0 Assertion Handlers by Aaron Skonnard

Web Services Enhancements: Understanding the WSE for .NET Enterprise Applications

.NET Web Services: Architecture and Implementation