WS-Security Drilldown in Web Services Enhancements 2.0

 

Don Smith
Microsoft Corporation

Updated August 2004

Applies to:

   SOAP Messaging
   Microsoft® .NET Framework
   WS-Security Specification
   WS-SecurityPolicy Specification
   WS-SecureConversation Specification
   Web Services Enhancements 2.0 (WSE 2.0) for Microsoft .NET

Download the WSE2SecurityDrilldownSample.exe source code from the Microsoft Download Center.

Summary: How to use Web Services Enhancements 2.0 to implement security, trust, and secure conversations in Web services architecture. Also covers the security-related changes from Web Services Enhancements 1.0. (46 printed pages)

Note   The initial release of this article covered the Technology Preview release of WSE 2.0 and has been updated to reflect the WSE 2.0 final release.

Contents

Introduction
Supplying Credentials to a Service
Controlling Authorization to a Service
Signing SOAP Messages
Encrypting SOAP Messages
Implementing a Trust Relationship
Implementing Secure Conversation
Expressing Assertions Through a Security Policy
Conclusion

Introduction

Developers and software architects are realizing Web service implementations are requiring more complex architectures as they struggle to fill the shoes of their distributed ancestors. As the specifications designed to support these architectures continue to evolve, toolsets compliant to these specifications are essential for interoperability and developer productivity. Web Services Enhancements 2.0 for Microsoft .NET (WSE 2.0) is an add-on to Visual Studio .NET and the .NET Framework that helps developers meet these complex business requirements. Specific to this article, WSE is used to apply message-level credentials, encryption, and digital signing to SOAP messages independent of the transport protocol. By building on these basic security principles, trust relationships can be established through multiple endpoints that may not directly trust each other. In addition to these scenarios, this article describes how policies can be used to ensure that the security requirements are being described and adhered to.

As you will soon see, this article uses quite a few source code listings to enforce many of the concepts presented. It's important to understand a large part of this source code can be reduced by the use of policies. The use of policies is a new feature in WSE 2.0 and its benefits are addressed in the last section of this article.

Supplying Credentials to a Service

Throughout the WSE 2.0 API, security tokens are used to represent claims, which are often related to the identity of the sender. There are numerous predefined security tokens in WSE 2.0. Figure 1 shows the built-in tokens and their relationship. All tokens ultimately inherit from the SecurityToken class. Extensibility points are denoted in braces.

Figure 1. Inheritance relationships between SecurityToken classes

WSE also supports custom binary security tokens and XML-based security tokens. You can see the SecurityToken and BinarySecurityToken classes are the extensibility points for custom tokens.

The following example shows how a sender creates a UsernameToken and attaches it to the request message. These tokens are attached to SOAP request and response messages so the receiving endpoint can verify the identity claim.

using Microsoft.Web.Services2.Security.Tokens;
...
// Create the Username token
UsernameToken token = new UsernameToken( "joeblow", "NoTelinNE1", 
   PasswordOption.SendPlainText );
// Create an instance of the web service proxy
WeblogProxy proxy = new WeblogProxy();
// Add the token to the request context
proxy.RequestSoapContext.Security.Tokens.Add( token );
// Call the web service
ConfirmedWeblogEntry confirmed = proxy.AddEntry( newEntry );
...

The SecurityToken class is an implementation of the System.Xml.IXmlElement interface, which represents an XML element in the .NET Framework. So, it isn't surprising to see the impact that the addition of this token has on the SOAP message.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="..." xmlns:wsu="..." xmlns:wsse="...">
  <soap:Header>
    ...
    <wsse:Security soap:mustUnderstand="1">
      <wsse:UsernameToken xmlns:wsu="http://docs.oasis-
open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" 
wsu:Id="SecurityToken-982915eb-12aa-41b2-a888-aef91202d7db">
wsu:Id="SecurityToken-c7ef4231-4397-4b4b-ab8d-0ea3fad1f79e">
        <wsse:Username>joeblow</wsse:Username>
        <wsse:Password Type="http://docs.oasis-
open.org/wss/2004/01/oasis-200401-wss-username-token-profile-
1.0#PasswordText">NoTelinNE1</wsse:Password>
        <wsse:Nonce>KwIi+XJqpiintiYCzqPi2A==</wsse:Nonce>
        <wsu:Created>2004-05-07T22:37:52Z</wsu:Created>
      </wsse:UsernameToken>
    </wsse:Security>
  </soap:Header>
  <soap:Body>
    <AddEntry xmlns="http://weblogs.contoso.com/wse/samples/2004/05">
      <entry>
        <title>Saw Van Helsing last night</title>
        <author>Joe Blow</author>
        <issued>2004-05-05T18:37:52.0030368-04:00</issued>
        <content>Great action flick.  The special effects were over the top.</content>
      </entry>
    </AddEntry>
  </soap:Body>
</soap:Envelope>

As you can see from the SOAP message fragment above, when a SecurityToken is added to the Tokens collection of the RequestSoapContext, a SOAP header containing wsse:UsernameToken is added to the request message.

Version differences: When sending a UsernameToken in WSE 1.0, the receiving end is forced to configure an IPasswordProvider implementation. WSE 2.0 has much better integration with Windows security and has eliminated the use of IPasswordProvider. In WSE 2.0, if SendPlainText is the PasswordOption used, the username and password is automatically authenticated against a Windows user account with no special configuration. We will discuss later how to encrypt the password. When custom authentication is desired, the SecurityTokenManager replaces the IPasswordProvider implementation.

As you can see in the following code sample, implementing custom authentication is as easy as overriding the AuthenticateToken method of a UsernameTokenManager.

public class WeblogAuthManager : UsernameTokenManager
{
   // This method returns the password for the provided username
   // WSE will make the determination if they match
   protected override string AuthenticateToken( UsernameToken token )
   {
      string password = "";
      string username = token.Username;
      string CustomCreds = "https://localhost/WeblogServiceSample/CustomCredentials.xml";

      // Read the XML document
      XmlTextReader reader = new XmlTextReader( CustomCreds );

      // Looking for the matching username
      while( reader.Read() )
      {
         if( (reader.LocalName == "Account") && (reader.GetAttribute( "username" ) == username) )
         {
            password = reader.GetAttribute( "password" );
            break;
         }
      }
      // Clean up and return the user's password
      reader.Close();
      return password;
   }
}

When setting up configuration, use the securityTokenManager element in the configuration file. This step enables WSE to locate the UsernameTokenManager performing authentication.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <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>
      <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>
    <security>
      <securityTokenManager type="WeblogService.WeblogAuthManager, 
WeblogService" xmlns:wsse="http://docs.oasis-
open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" 
qname="wsse:UsernameToken" />
    </security>
  </microsoft.web.services2>
</configuration>

Many business requirements mandate some type of authentication scheme, but almost all of these cases will require other aspects of security in addition to authentication. If adding the UsernameToken to the message is the only thing we did to the request message, then there is no guarantee the received message wasn't modified by an unintended party en route. Other aspects of security typically applied in addition to authentication include authorization, integrity, non-repudiation, and encryption.

The UsernameToken folder in the compressed file that accompanies this article is a sample of how to use a UsernameToken to pass credentials to a Web service.

Controlling Authorization to a Service

An authorization check is usually the step that a secure Web service performs immediately after authenticating the caller of the Web service. This distinction is important—just because the caller is determined to be a user on a particular system doesn't mean that user is authorized to carry out the operation the system attempting. The operation/user relationship in a number of systems looks like this:

  • Services (applications) are divided into operations.
  • Operations are only carried out by certain roles.
  • Users of the service are members of one or more roles.

WSE 2.0 facilitates role verification by integrating with the System.Security.Principal.IPrincipal interface. The abstract SecurityToken class exposes this IPrincipal via its Principal property. In WSE 2.0, this property is automatically populated whenever a KerberosToken is used or a UsernameToken with a plain-text password is used. The following code shows the function a service might call to perform role verification using the SecurityToken Principal property.

private bool AccessIsAllowed()
{
    SecurityElementCollection elements = RequestSoapContext.Current.Security.Elements;

    // Loop through all of the security tokens (could be more than one)
    foreach( ISecurityElement securityElement in elements ) 
    { 
        if( securityElement is MessageSignature ) 
        { 
            MessageSignature sig = (MessageSignature)securityElement; 
            if( (sig.SignatureOptions & SignatureOptions.IncludeSoapBody) != 0 ) 
            { 
                SecurityToken sigToken = sig.SigningToken; 
                // Find the token used to sign this message
                if( sigToken is UsernameToken ) 
                {
                   return token.Principal.IsInRole( @"BUILTIN\Users" ); 
                }
                else if( sigToken is KerberosToken )
                {
                   return token.Principal.Identity.IsAuthenticated;
                }
            } 
        } 
    } 
    return false; 
}

In the source code that accompanies this article, the Authorization folder contains a Visual Studio.NET solution that demonstrates role verification against a local Windows group and domain account.

As we've seen, both authentication and authorization in WSE 2.0 can be tightly integrated into the Windows security infrastructure. However, if the messages aren't signed, the integrity of the message cannot be verified and there is no proof the message was sent by the credentials supplied in the message.

Signing SOAP Messages

A security token by itself contains no proof of the claim being made, but a signature binds a proof-of-possession to a hash of the message. When the sender of a SOAP message signs the message with a security token, the receiver of that message can be relatively certain the message received was the same message sent. In other words, the message was not modified en route. This integrity check is possible because the sender includes an XML Digital Signature digest along with the SOAP message. A digest is a fixed-length binary fingerprint of a message. On the receiving end, WSE creates a digest using the same algorithm and compares it to the digest packaged with the message. If the message has been even slightly changed, the two digests, also called hashes, will not match and the receiver knows to fail the request.

Another benefit of message signing is the ability to fulfill a common business need called non-repudiation, which allows the receiver to prove the message was actually signed by a specific entity. Non-repudiation is possible because the message digest is mathematically combined with a SecurityToken value only the sender should know. The receiver can verify the digest by using a value associated with the SecurityToken. For example, if the SecurityToken is an X.509 certificate, the sender's value will be the certificate's private key and the receiver will verify the signature using the certificate's public key. When choosing a SecurityToken for the purposes of non-repudiation, in most cases it is better to select a user-specific token (e.g., a UsernameToken or a user-specific X509SecurityToken) rather than a machine-specific token.

The following code demonstrates how a sender might sign a request message with a KerberosToken. In many of the QuickStarts and WSE samples (including the source code for this article), System.Environment.MachineName and System.Net.Dns.GetHostName are often used when creating the TargetPrincipal string. These methods only work when the sender and receiver are on the same machine, which is not the case for most production environments. A more appropriate method is to use the System.Uri class when the endpoint URL is not "localhost".

// Create an instance of the proxy
WeblogProxy proxy = new WeblogProxy();

// Create a KerberosSecurityToken
string targetPrincipal = "host/" + new Uri( proxy.Url ).Host;
KerberosToken token = new KerberosToken( targetPrincipal );

// Add the SecurityToken to the Request Context
proxy.RequestSoapContext.Security.Tokens.Add( token );

// Sign the message with a signature object 
MessageSignature sig = new MessageSignature( token );
proxy.RequestSoapContext.Security.Elements.Add( sig );

The code above creates the SOAP message below (areas of the message have been removed to keep the size down). The KerberosToken is created from a Kerberos ticket issued by the Kerberos KDC (key distribution center) for the TargetPrincipal machine.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="..." xmlns:wsu="..." xmlns:wsse="...">
  <soap:Header>
    ...
    <wsse:Security soap:mustUnderstand="1">
      <wsse:BinarySecurityToken 
       ValueType="https://schemas.xmlsoap.org/ws/2003/12/kerberos/Kerberosv5ST"" 
       EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-
200401-wss-soap-message-security-1.0#Base64Binary" xmlns:wsu="..." 
       wsu:Id="SecurityToken-de1c75eb">
        YYIFezCCBX...FYxRDU24iu
      </wsse:BinarySecurityToken>
      <Signature xmlns="http://www.w3.org/2000/09/xmldsig#">
        <SignedInfo>
          <CanonicalizationMethod 
           Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
          <SignatureMethod 
           Algorithm="http://www.w3.org/2000/09/xmldsig#hmac-sha1" />
          <Reference URI="#Id-7d23277f">
            <Transforms>
              <Transform 
               Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#" />
            </Transforms>
            <DigestMethod 
             Algorithm="http://www.w3.org/2000/09/xmldsig#sha1" />
            <DigestValue>YYQberdPQ0/gViBEO9yPRrLySMw=</DigestValue>
          </Reference>
        </SignedInfo>
        <SignatureValue>ePS4LW/iu6lHL9plyJ5r9gtMtXk=</SignatureValue>
        <KeyInfo>
          <wsse:SecurityTokenReference>
            <wsse:Reference URI="#SecurityToken-de1c75eb" />
          </wsse:SecurityTokenReference>
        </KeyInfo>
      </Signature>
    </wsse:Security>
  </soap:Header>
  <soap:Body wsu:Id="Id-7d23277f">
    <AddEntry xmlns="http://weblogs.contoso.com/wse/samples/2004/05">
      <entry>
        <title>Saw Van Helsing last night</title>
        <author>Joe Blow</author>
        <issued>2004-05-15T00:31:03.2797984-04:00</issued>
        <content>
          Great action flick.  The special 
          effects were over the top.
        </content>
      </entry>
    </AddEntry>
  </soap:Body>
</soap:Envelope>

As you might have guessed, WSE 2.0 integration with the Windows security infrastructure doesn't stop with authentication and authorization. The receiver of this message doesn't have to write any code to verify the Kerberos ticket, because WSE will perform this verification automatically upon receiving the message using standard signature verification.

WSE developers are in control of which items in the message are signed. By default, when a Signature object is added as a RequestSoapContext security element, the following items in the request message are signed:

  • soap:Envelope/soap:Header/wsa:To
  • soap:Envelope/soap:Header/wsa:Action
  • soap:Envelope/soap:Header/wsa:MessageID
  • soap:Envelope/soap:Header/wsa:From/wsa:Address
  • soap:Envelope/soap:Header/wsu:Timestamp/wsu:Created
  • soap:Envelope/soap:Header/wsu:Timestamp/wsu:Expires
  • soap:Envelope/soap:Body

For an explanation of how each of the items in the Security header relate to digital signatures, or to see how to sign only specific parts of a message, I recommend WS-Security Authentication and Digital Signatures with Web Services Enhancements.

The Signing folder in the compressed file that accompanies this article is a sample of how to sign SOAP messages with a KerberosToken.

Encrypting SOAP Messages

It's important to understand that even signed messages are subject to prying eyes between the sender and receiver. Encryption is the process of mathematically scrambling the contents of a message with a value such that only the intended recipient will be able to decipher it. For example, when encrypting with an X509SecurityToken, the sender encrypts a message with the receiver's public key. Therefore, only the holder of the private key, the receiver, can decrypt the message. Encrypting messages ensures that only the intended recipient can comprehend the contents of the message.

To encrypt items in SOAP messages, WSE uses an EncryptedData object initialized with a SecurityToken object. The following code demonstrates how a sender can encrypt a SOAP message using an X.509 certificate. GetX509Token is a user-defined function, shown later in the article, which is used to create a token based on a certificate in the X.509 certificate store.

// Get the X509SecurityToken
SecurityToken token = GetX509Token();

// Create an instance of the proxy
WeblogProxy proxy = new WeblogProxy();

// Create and add the encrypted username token
EncryptedData encrypted = new EncryptedData( token );
proxy.RequestSoapContext.Security.Elements.Add( encrypted );

// Send the request
confirmed = proxy.AddEntry( newEntry );

As you can see in the following SOAP message fragment, the contents of the soap:Body element have been replaced with encrypted data.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="..." xmlns:wsu="..." xmlns:wsse="...">
  <soap:Header>
    ...
    <wsse:Security soap:mustUnderstand="1">
      <xenc:EncryptedKey xmlns:xenc="...">
        <xenc:EncryptionMethod 
         Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
        <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
          <wsse:SecurityTokenReference>
            <wsse:Reference URI="#SecurityToken-c590f9bf-
7321-4e4a-a5a0-98834e346f81" ValueType="http://docs.oasis-open.org/
wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3" />
          </wsse:SecurityTokenReference>
        </KeyInfo>
        <xenc:CipherData>
          <xenc:CipherValue>
            acahxPK/hlMBdjRVFd4a3...tMvpQerPa3TlVFlRD0=
          </xenc:CipherValue>
        </xenc:CipherData>
        <xenc:ReferenceList>
          <xenc:DataReference URI="#EncryptedContent-cf679170" />
        </xenc:ReferenceList>
      </xenc:EncryptedKey>
    </wsse:Security>
  </soap:Header>
  <soap:Body>
    <xenc:EncryptedData 
     Id="EncryptedContent-cf679170" 
     Type="http://www.w3.org/2001/04/xmlenc#Content" 
     xmlns:xenc="http://www.w3.org/2001/04/xmlenc#">
      <xenc:EncryptionMethod 
       Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
      <xenc:CipherData>
        <xenc:CipherValue>
          NBo9ANPuN7iBXJSM/WIzNgG...jizomfP+TqFwn0G8Yjg=
        </xenc:CipherValue>
      </xenc:CipherData>
    </xenc:EncryptedData>
  </soap:Body>
</soap:Envelope>

In the SOAP message above, the encrypted contents of the soap:Body are located in soap:Body/xenc:EncryptedData/xenc:CipherData/xenc:CipherValue and the ID on the xenc:EncryptedData element is referred to from the appropriate encrypting key (there's only one in this case).

By default, WSE will only encrypt the soap:Body element, but items in the SOAP message can be selectively signed and/or encrypted. The ability to sign/encrypt the security header was not available in WSE v.1.0. Earlier we saw how WSE will automatically authenticate messages against a Windows user account if provided with a plain-text UsernameToken. The integration is an attractive feature, but sending passwords as clear text isn't practical. The password needs to be encrypted. The following code demonstrates how to encrypt a plain-text username token with an X509SecurityToken.

// Get the X509SecurityToken
SecurityToken authToken = GetX509Token ();

// Create an instance of the proxy
WeblogProxy proxy = new WeblogProxy();

// Add the SecurityToken to the Request Context
proxy.RequestSoapContext.Security.Tokens.Add( authToken );

// Sign the message with a signature object 
proxy.RequestSoapContext.Security.Elements.Add( new Signature( authToken ) );

// Create the security token
authToken = new UsernameToken( Username, Password, PasswordOption.SendPlainText );

// Create and add the encrypted username token
EncryptedData encrypted = new EncryptedData( encrToken, "#" + authToken.Id );
proxy.RequestSoapContext.Security.Elements.Add( encrypted );

// Send the request
confirmed = proxy.AddEntry( newEntry );

When creating the EncryptedData object above, a reference to the wsse:UsernameToken ID is passed into the constructor. As you can see in the following SOAP message, the plain-text UsernameToken has been replaced with xenc:EncryptedData and it has an ID of EncryptedContent-a0bf2920.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:soap="..." xmlns:wsu="..." xmlns:wsse="...">
  <soap:Header>
    ...
    <wsse:Security soap:mustUnderstand="1">
      <xenc:EncryptedKey xmlns:xenc="...">
        <xenc:EncryptionMethod 
         Algorithm="http://www.w3.org/2001/04/xmlenc#rsa-1_5" />
        <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
          <wsse:SecurityTokenReference>
            <wsse:KeyIdentifier ValueType="http://docs.oasis-
open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-
1.0#X509SubjectKeyIdentifier">
              PTBv8366Lp0xwHT5nQYl3dhxcMQ=
            </wsse:KeyIdentifier>
          </wsse:SecurityTokenReference>
        </KeyInfo>
        <xenc:CipherData>
          <xenc:CipherValue>
            QKuraT1kaXZAtExp...9G+CuAnngPr4ZUcI=
          </xenc:CipherValue>
        </xenc:CipherData>
        <xenc:ReferenceList>
          <xenc:DataReference URI="#EncryptedContent-a0bf2920" />
        </xenc:ReferenceList>
      </xenc:EncryptedKey>
      <wsse:UsernameToken xmlns:wsu="..." wsu:Id="SecurityToken-d119b99b">
        <xenc:EncryptedData Id="EncryptedContent-a0bf2920" 
         Type="http://www.w3.org/2001/04/xmlenc#Content" xmlns:xenc="...">
          <xenc:EncryptionMethod 
           Algorithm="http://www.w3.org/2001/04/xmlenc#tripledes-cbc" />
          <xenc:CipherData>
            <xenc:CipherValue>
              oojjtSa1iRsVon...8SiDFQYTRCEXHreJau
            </xenc:CipherValue>
          </xenc:CipherData>
        </xenc:EncryptedData>
      </wsse:UsernameToken>
    </wsse:Security>
  </soap:Header>
  <soap:Body>
    <AddEntry xmlns="http://weblogs.contoso.com/wse/samples/2003/07">
      <entry>
        <title>Saw Terminator III last night</title>
        <author>Joe Blow</author>
        <issued>2003-07-16T18:05:32.8774608-05:00</issued>
        <content>The special effects were over the top.</content>
      </entry>
    </AddEntry>
  </soap:Body>
</soap:Envelope>

One of the advantages WS-Security encryption has over Secure Socket Layers (SSL) is that it's a message-oriented approach rather than a transport-specific solution. This allows a message to remain secure even through intermediaries (services between the sender and receiver). The rest of this article builds on the principles of authentication, digital signatures, and encryption to solve security requirements in complex business scenarios.

For more information on how WS-Security leverages XML Digital Signature and XML Encryption and how the messages are constructed, see Encrypting SOAP Messages Using Web Services Enhancements.

The Encryption folder in this article's associated compressed file contains sample code that demonstrates how to encrypt username tokens with plain-text passwords with an X.509 certificate.

Implementing a Trust Relationship

Recently I was in the market for a used car. By default, I don't believe used car salesmen are trying to get me the best deal possible. However, I do believe what my Dad tells me. So when my father referred me to a local used car lot®because he and the owner play golf together®I felt (more) comfortable that I wouldn't be taken advantage of. The WS-Trust specification defines trust as "...the characteristic that one entity is willing to rely upon a second entity to execute a set of actions and/or to make a set of assertions about a set of subjects and/or scopes." This means I don't have to directly trust used car salesmen, but I can if my Dad vouches for them.

In complex Web service architectures, it can be cumbersome and error-prone to require that all services directly trust all possible clients. Web services can be more flexible and managable by designating a separate authentication service to do the heavy lifting. A separate authentication service can exchange or issue tokens to consumers of a service or validate tokens for the service itself.

Figure 2. Requesting and using tokens from a security token service

In Figure 2, our Weblog service does not inherently trust clients. The token issuer, also called the Security Token Service (STS), is trusted by the service and is capable of authenticating the clients. In order for the service to accept messages from clients, the message must contain a security token issued by the token issuer. Let's get a high-level overview of what's happening here so the remainder of this topic will make more sense:

  1. The client first sends the token issuer a RequestSecurityToken (RST). For our purposes, it's not important this request be encrypted, but it is signed with an x.509 certificate the token issuer can authenticate. The token issuer has an httpHandler configured that will invoke a class that inherits from SecurityTokenService.
  2. This class will accept the RST and respond with a RequestSecurityTokenResponse (RSTR). The client will extract the issued security token from the RSTR and use it to sign and optionally encrypt the message to the service.
  3. When the service receives the message from the client, it will verify the security token as it would any other security token and respond accordingly.

Now let's take a more detailed look at what's happening behind the scenes in this scenario.

Note The pseudo-code and messages in this section are fictitious. Their purpose is to illustrate the concepts of WS-Trust rather than to show a working sample. While the possibility of issuing X.509 and Username tokens is theoretically possible, it has not been tested and is not recommended. In trust scenarios, the token issuer should be issuing SecurityContextTokens (covered in the next section), or custom security tokens that are out of the scope of this article.

Requesting a security token

The following pseudo-code shows how the client might sign and send a security token request to a security token service.

using Microsoft.Web.Services.Trust;
using Microsoft.Web.Services.Security;
...
// Create a token used for signing
UsernameToken signingToken = new UsernameToken("joeblow", "NoTelinNE1", 
PasswordOption.SendHashed );

// Create the proxy for the token service
string address = "https://localhost/IssueTokenService/tokenIssuer.ashx";
SecurityTokenServiceClient stsProxy;
stsProxy = new SecurityTokenServiceClient( new Uri( address ) );

// Sign the token request
stsProxy.RequestSoapContext.Security.Tokens.Add( signingToken );
Signature TokenSig = new Signature( signingToken );
stsProxy.RequestSoapContext.Security.Elements.Add( TokenSig );

// Create the security token request
RequestSecurityToken rst;
rst = new RequestSecurityToken( WSTrust.TokenTypes.X509v3 );

// Request the token from the issuer
RequestSecurityTokenResponse response;
response = stsProxy.RequestSecurityToken( rst );

The client first creates a UsernameToken that is used to sign the request to the token issuer. By passing the address of the token issuer to its constructor, a SecurityTokenServiceClient object is then created. This object is used to communicate with the token issuer and will construct the SOAP message below.

The RequestSecurityToken object represents the type of token being requested. We are requesting an x.509 token, but realistically we could have requested a security context token or custom security token. The RequestSecurityToken method of the token service accepts this RST and returns a RequestSecurityTokenResponse (RSTR).

<soap:Envelope 
 xmlns:wsa="http://.../addressing" 
 xmlns:wsse="http://...wssecurity-secext-1.0.xsd" 
 xmlns:wsu="http://...utility-1.0.xsd" 
 xmlns:soap="http://.../soap/envelope/">
  <soap:Header>
  </soap:Header>
  <soap:Body>
    <wsse:RequestSecurityToken>
      xmlns:wst="http://.../trust">
      <wst:TokenType>http://...#X509v3</wst:TokenType>
      <wst:RequestType>
        http://.../security/trust/Issue
      </wst:RequestType>
      <wst:Base>
        <wsse:UsernameToken 
          xmlns:wsu="http://...utility-1.0.xsd" 
          wsu:Id="SecurityToken-98d36c05">
           <wsse:Username>joeblow</wsse:Username>
           <wsse:Password 
             Type="http://...#PasswordText">
               NoTelinNE1
           </wsse:Password>
           <wsse:Nonce>KiHO7UQ3hcoFfTvH7Cpjkw==</wsse:Nonce>
           <wsu:Created>2004-05-22T04:31:23Z</wsu:Created>
        </wsse:UsernameToken>
      </wst:Base>
      <wsp:AppliesTo 
        xmlns:wsp="http://.../policy">
        <wsa:EndpointReference>
          <wsa:Address>
           https://localhost/TrustIssuer/tokenIssuer.ashx
          </wsa:Address>
        </wsa:EndpointReference>
      </wsp:AppliesTo>
      <wst:LifeTime>
        <wsu:Expires>2004-05-22T08:31:23Z</wsu:Expires>
      </wst:LifeTime>
    </wst:RequestSecurityToken>
  </soap:Body>
</soap:Envelope>

The wsse:RequestSecurityToken element provides the security token service everything it needs to issue the correct token. The value of the wsse:TokenType element is based on the RST we passed to the SecurityTokenServiceClient. The value of the wsse:RequestType element is a QName that indicates the type of request being made. The WS-Trust specification lists these three predefined values:

  • wsse:ReqIssue to request the issuance of a security token
  • wsse:ReqValidate to request a security token be validated
  • wsse:ReqExchange to request security tokens be exchanged

Figure 3 shows how wsse:Validate might be put to use.

Figure 3. A service validating a security token

We'll see other optional child elements of wsse:RequestSecurityToken when we discuss policy.

So far, the client has sent the security token request, which concludes Step 1 in Figure 3. Step 2 begins with constructing a security token response to the client's request.

Issuing a security token

A security token service is really just a Web service that responds to security token issue requests. As you may have noticed from the code in the previous section, the endpoint is implemented as an httpHandler. The following code shows the class our handler will invoke during token issue requests.

using Microsoft.Web.Services2.Security;
using Microsoft.Web.Services2.Security.X509;
using Microsoft.Web.Services2.Security.Tokens;

public class WeblogIssueService : SecurityTokenService
{
  public override RequestSecurityTokenResponse 
IssueSecurityToken(SecurityTokenMessage request)
  {
    SecurityToken secToken = GetSecurityToken();
    RequestSecurityTokenResponse response = new RequestSecurityTokenResponse( secToken );
    return response;
  }
    

Token issuers must inherit from SecurityTokenService and override the IssueSecurityToken method. The returned RSTR is created from a predefined X.509 security token.

We're not doing it here, but because the token issuer inherits from SecurityTokenService, the token used to sign the request can be interrogated using the protected RequestSigningToken property. Also, the response message can be signed and encrypted by setting the protected ResponseSigningToken and ResponseEncryptingToken properties respectively.

The config.web file below will be used by the token issuer service.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <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>
      <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>
    <httpHandlers>
      <add verb="*" path="tokenIssuer.ashx" type="TokenIssueSample.WeblogIssueService, 
TokenIssueSample" />
    </httpHandlers>
  </system.web>
  <microsoft.web.services>
    <security>
      <x509 allowTestRoot="true" verifyTrust="false" />
    </security>
    <tokenIssuer>
      <serverToken>
        <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
          <wsse:SecurityTokenReference 
           xmlns:wsse="http://...wssecurity-secext-1.0.xsd">
            <wsse:KeyIdentifier 
             ValueType="http://...#X509SubjectKeyIdentifier">
              PTBv8366Lp0xwHT5nQYl3dhxcMQ=
            </wsse:KeyIdentifier>
          </wsse:SecurityTokenReference>
        </KeyInfo>
      </serverToken>
    </tokenIssuer>
  </microsoft.web.services>
</configuration>

As you can see, an httpHandler has been configured to call our WeblogIssueService for all calls to tokenIssuer.ashx. httpHandlers are not specific to WSE or Web services, for that matter; rather, they are part of the ASP.NET infrastructure. The tokenIssuer section, on the other hand, is specific to our WSE token issuer. The serverToken element contains information WSE will use to sign the RSTR our SecurityTokenService is returning.

Below is the SOAP response from the token issuer to the client.

<?xml version="1.0" encoding="utf-8"?>
<soap:Envelope xmlns:wsa="http://.../addressing" 
  xmlns:wsse="http://...wssecurity-secext-1.0.xsd" 
  xmlns:wsu="http://...utility-1.0.xsd" 
  xmlns:soap="http://.../soap/envelope/">
  <soap:Header>
    [Headers snipped for brevity]
  </soap:Header>
  <soap:Body wsu:Id="Id-f7df0a08">
    <wst:RequestSecurityTokenResponse>
     xmlns:wst="http://.../trust">
      <wsp:AppliesTo xmlns:wsp="http://.../policy">
        <wsa:EndpointReference>
          <wsa:Address>
           http://.../weblogservice.asmx
          </wsa:Address>
        </wsa:EndpointReference>
      </wsp:AppliesTo>
      <wst:RequestedSecurityToken>
        <wsse:BinarySecurityToken wsu:Id="SecurityToken-d014b285">
        </wsse:BinarySecurityToken 
         ValueType="http://...#X509v3" 
         EncodingType="http://...#Base64Binary" 
         xmlns:wsu="http://...utility-1.0.xsd" 
         wsu:Id="SecurityToken-f4b427c0">
         [snipped the contents of the token]
      </wst:RequestedSecurityToken>
    </wst:RequestSecurityTokenResponse>
  </soap:Body>
</soap:Envelope>

In most cases, the wsse:RequestedSecurityToken will contain the actual token requested. This is true in our case with the wsse:BinarySecurityToken. However, if the message actually uses the requested token—if it was used for signing, for example—the wsse:RequestedSecurityToken element may contain a wsse:SecurityTokenReference to the token in the wsse:Security SOAP header.

Referring back to Figure 3, Step 2 has been completed and the client has the requested security token.

Using an issued security token

Whether a security token is created locally or requested from a token issuer, it is used the same way. Once the RSTR receives the response, the requested token is available on its SecurityToken property of the RSTR. Below is the remainder of the client code we started in the Requesting a Security Token section.

// Extract the token from the RSTR (the response)
SecurityToken issuedToken = response.RequestedSecurityToken.SecurityToken;

// Create an instance of the proxy
WeblogProxy proxy = new WeblogProxy();

// Add the SecurityToken to the Request Context
proxy.RequestSoapContext.Security.Tokens.Add( issuedToken );

// Sign the message with a signature object 
Signature svcSig = new Signature( issuedToken );
proxy.RequestSoapContext.Security.Elements.Add( svcSig );

// Send the request
ConfirmedWeblogEntry confirmed = proxy.AddEntry( newEntry );

Throughout this section, we've seen how WS-Trust can be applied to a straightforward (though fictitious) token-issuing scenario. The WS-Trust concepts of token issuance, validation, and exchange can be applied with other advanced Web service technologies to solve complex business requirements. Figure 4 shows how WS-Addressing might be coupled with token exchange to completely abstract the true security requirements of a Web service.

Figure 4. A UsernameToken being translated to an X509SecurityToken

Throughout this section, the Security Token Service (STS) issued an X.509 certificate to the requestor. While this is possible, the recommended approach is to issue a SecurityContextToken. The next section introduces this new security token in the context of a secure conversation.

Implementing Secure Conversation

So far we've seen how WSE 2.0 addresses a number of security-related business needs by providing a means to digitally sign and encrypt SOAP messages. We've also seen how the security tokens can be issued, validated, and exchanged when the receiver doesn't directly trust the sender of the message. Therefore, if a sender and receiver, who don't inherently trust each other, needed to send/receive messages that can't be read by unintended entities, a seemingly viable solution might be to encrypt all messages with a security token issued by a token-issuing service. The biggest problem with this approach is the size of each message. Message size may not be a problem for short-lived, one-way message scenarios, but it can become a performance bottleneck when the senders and receivers are engaging in multi-message communication (e.g., orchestration).

Secure conversation provides Web service architectures with a similar level of security and performance as SSL (TLS) provides for HTTP. In much the same way that SSL establishes a session key, Secure conversation uses security tokens based on the context in which they're used. A SecurityContextToken is applied in this context the way KerberosToken, X.509SecurityToken, and UsernameToken have been used in our other scenarios. Each of these classes ultimately inherits from the SecurityToken base class. In the same way symmetric keys are negotiated in SSL, there is upfront overhead in creating the SecurityContextToken, but once this is done, the messages are smaller and can be processed faster by both ends.

Note   This article is not comparing the performance of SecureConversation with SSL. Rather, the SSL relationship to HTTP is only used metaphorically in comparison to the SecureConversation relationship to Web services. As it turns out, SSL is faster than SecureConversation in most cases, but at the cost of a transport-level approach rather than a message-based approach.

In many cases, setting up a security token service is simply a matter of supplying the necessary configuration. Secure conversation uses a token issuer, also called a SecurityContextToken Service (SCTS), as described in WS-Trust. This issuer can either be the service engaging in the conversation, or a stand-alone SCTS. When the issuer and the service are one and the same (share the same endpoint address), enabling the SCTS is as easy as adding a single XML element to the service's web.config file. When the SCTS is independent of the service being consumed, WSE provides this SCTS as a built-in class, called SecurityContextTokenService. This class, or a derivation of it, must be configured as an httpHandler, just like our trust sample earlier. The web.config file below shows how to configure an SCTS that coexists with a Web service.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <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>
      <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.services>
    <security>
      <!-- This should NOT be done in production -->
      <x509 allowTestRoot="true" verifyTrust="false" />
    </security>
    <tokenIssuer>
      <serverToken> 
        <autoIssueSecurityContextToken enabled="true" />
        <KeyInfo xmlns="http://.../xmldsig#">
          <wsse:SecurityTokenReference
            xmlns:wsse="http://...wssecurity-secext-1.0.xsd">
            <wsse:KeyIdentifier 
ValueType="http://...token-profile-1.0#X509SubjectKeyIdentifier">
              kRdbDi6QWTlyMpB7xNN65c0vTN4=
            </wsse:KeyIdentifier>
          </wsse:SecurityTokenReference>
        </KeyInfo> 
      </serverToken> 
    </tokenIssuer> 
  </microsoft.web.services>
</configuration>

As I’m sure you suspected, it’s the <autoIssueSecurityContextToken> element that allows this otherwise normal service to issue SCTSs. The <tokenIssuer> element provides WSE with the token information it will use to sign the response token. The use of <serverToken> above requires the Web service and the security token service to be in the same virtual directory. If this were not the case, you would use <serviceToken> instead of <serverToken>. If this SCTS were a stand-alone service, the inclusion of an httpHandler section would make it look a lot like the Trust configuration in the last section. However, as you can see from Figure 5, the similarities between trusted token issuance and secure conversation go beyond how they are configured, but there are actually a number of differences.

Figure 5. Issuing a SecurityContextToken from a security token service

While negotiating the SecureContextToken in secure conversation, the RST message must be signed signature-capable SecurityToken (Step 1). This is necessary because the SCTS uses this token to encrypt the RSTR message (Step 2). The following code shows how a client might request the SecurityContextToken.

// Create a signing token   
SecurityToken signingToken = GetSigningToken();

// Create an encrypting token
SecurityToken encryptingToken = GetEncryptingToken();

// Create the proxy for the token service
string stsAddr = "https://localhost/TokenIssuer/secureConversation.ashx";
SecurityContextTokenServiceClient stsProxy;
stsProxy= new SecurityTokenServiceClient( new Uri( stsAddr ) );

SecurityContextToken contextToken;
contextToken = stsProxy.IssueSecurityContextTokenAuthenticated(
signingToken, encryptingToken );

Based on the code fragment above, it may seem an RST and RSTR are no longer being used—not true. These constructs are still used, only behind the scenes in the SecurityContextTokenServiceClient object. This object creates a WSTrust.TokenTypes.SecurityContextToken RST and signs the request with the SecurityToken provided (signingToken). The other SecurityToken (encryptingToken) is used to encrypt the contents of the <Entropy> element, which represents a random value used in the creation of the symmetric key that is ultimately used by the consumer and the service. The following fragment shows how the RST is manifested in the message.

<soap:Body wsu:Id="Id-54fd2a29-25b6-4111-bec6-5b950283f7ed">
  <wst:RequestSecurityToken
    xmlns:wst="http://.../trust">
   <wst:TokenType>http://.../security/sc/sct</wst:TokenType>
    <wst:RequestType>http://.../trust/Issue</wst:RequestType>
    <wst:Base>
      ...
    </wst:Base>
    <wst:Entropy>
      <xenc:EncryptedKey
 xmlns:xenc="http://.../xmlenc#">
        <xenc:EncryptionMethod Algorithm="http://.../xmlenc#rsa-1_5" />
        <KeyInfo xmlns="http://.../xmldsig#">
          ...
        </KeyInfo>
        <xenc:CipherData>
          ...
        </xenc:CipherData>
        <xenc:CarriedKeyName>
          SecurityToken-fe1a1230-7b73-47e6-8649-399aa84839fc
        </xenc:CarriedKeyName>
      </xenc:EncryptedKey>
    </wst:Entropy>
    <wsp:AppliesTo xmlns:wsp="http://.../policy">
      ...
    </wsp:AppliesTo>
    <wst:LifeTime>
      ...
    </wst:LifeTime>
  </wst:RequestSecurityToken>
</soap:Body> 

The response to this request contains an SCT embedded in an RSTR.

<soap:Body wsu:Id="Id-95152711">
  <wst:RequestSecurityTokenResponse xmlns:wst="http://.../trust">
    <wsp:AppliesTo xmlns:wsp="http://.../policy">
      ...
    </wsp:AppliesTo>
    <wst:RequestedSecurityToken>
      <wssc:SecurityContextToken wsu:Id="SecurityToken-66a360b2" 
        xmlns:wssc="http://.../sc">
        <wssc:Identifier>uuid:1718c91a-d562-4340-8418-58194ea831ab</wssc:Identifier>
      </wssc:SecurityContextToken>
    </wst:RequestedSecurityToken>
    <wst:Entropy>
      <xenc:EncryptedKey xmlns:xenc="http://.../xmlenc#">
        <xenc:EncryptionMethod Algorithm="http://.../xmlenc#kw-aes128" />
        <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
          <KeyName>SecurityToken-fe1a1230-7b73-47e6-8649-399aa84839fc</KeyName>
        </KeyInfo>
        <xenc:CipherData>
          <xenc:CipherValue>
            FZ6NuG6/Le52IzGBOLJIoAO4FBD15fG8
          </xenc:CipherValue>
        </xenc:CipherData>
      </xenc:EncryptedKey>
    </wst:Entropy>
    <wst:LifeTime>
      ...
    </wst:LifeTime>
  </wst:RequestSecurityTokenResponse>
</soap:Body>

The following code completes the code we started above by calling the ultimate service, signed and encrypted with the issued SecurityContextToken.

// Create an instance of the proxy
WeblogProxy proxy = new WeblogProxy();

// Add the SecurityContextToken to the Request Context
proxy.RequestSoapContext.Security.Tokens.Add( contextToken );

// Sign the message with a signature object 
proxy.RequestSoapContext.Security.Elements.Add( new Signature( contextToken ) );

// Encrypt the message with a signature object 
proxy.RequestSoapContext.Security.Elements.Add( new EncryptedData( contextToken ) );

// Call the web service
ConfirmedWeblogEntry confirmed = proxy.AddEntry( newEntry );

What should be obvious here is how the SecurityContextToken is used the same way the other SecurityTokens are used—including encryption.

In contrast to encrypted messages sent in a normal token-issuing scenario based solely on WS-Trust, the messages signed/encrypted with a SecurityContextToken have a substantially smaller payload. The following SOAP request was sent from the code above.

<soap:Envelope xmlns:soap="http://.../soap/envelope/" 
 [Namespaces snipped for brevity]
 xmlns:wsu="http://...wssecurity-utility-1.0.xsd">
 <soap:Header>
  ...
  <wsse:Security soap:mustUnderstand="1">
    <wsu:Timestamp wsu:Id="Timestamp-db732262">
     ...
    </wsu:Timestamp>
    <wssc:SecurityContextToken 
     xmlns:wssc="http://.../sc" 
     wsu:Id="SecurityToken-66a360b2">
      <wssc:Identifier>
       uuid:1718c91a-d562-4340-8418-58194ea831ab
      </wssc:Identifier>
    </wssc:SecurityContextToken>
    [Signature snipped for brevity]
  </wsse:Security>
 </soap:Header>
 <soap:Body wsu:Id="Id-5ac3aa46">
  <xenc:EncryptedData Id="EncryptedContent-1f0da781" 
   Type="http://.../xmlenc#Content" 
   xmlns:xenc="http://.../xmlenc#">
    <xenc:EncryptionMethod 
     Algorithm="http://.../xmlenc#aes128-cbc" />
    <KeyInfo xmlns="http://.../xmldsig#">
      <wsse:SecurityTokenReference>
        <wsse:Reference 
         URI="#SecurityToken-66a360b2" 
         ValueType="http://.../security/sc/sct" />
      </wsse:SecurityTokenReference>
    </KeyInfo>
    <xenc:CipherData>
      <xenc:CipherValue>[snipped]</xenc:CipherValue>
    </xenc:CipherData>
  </xenc:EncryptedData>
 </soap:Body>
</soap:Envelope>

The value of the wsu:Identifier element represents the context URI. Because the message can reference this URI, it doesn't have to include the token's base64-encoded key like the wsse:BinarySecurityToken does. This keeps the payload of the message substantially smaller.

If you would like to see the messages for yourself, use this article's sample code in conjunction with the WSE trace functionality. This section is illustrated in the SecureConversation folder.

Expressing Assertions Through a Security Policy

Security policy provides the means to declaratively express the security requirements of a Web service. The WS-SecurityPolicy specification defines security-related extensions to WS-Policy. Like WS-Policy, a security policy takes the form of an XML file. When configured to do so, WSE uses this file when SOAP messages are sent/received to ensure the condition of the assertions is true.

Abstracting these requirements from the developer has profound advantages. As we'll soon see, in many cases security policy that can reduce the amount of source code needed. This advantage can ease maintainability and reduce programmer error. Another advantage is the ability to change the security requirements of a Web service without recompiling the solution. Administrators can migrate from UsernameToken digital signatures to KerberosToken signatures just by modifying the policy file.

In discussions of security policy, the terms client and server are rarely used. Rather, the focus is on the request and response messages since intermediaries in a routed scenario aren't always pure clients or servers.

Figure 6. Policy representation between the sender and receiver

As you can see in Figure 6, policies are specific to requirements as they relate to sending and receiving SOAP messages. Send and receive policies are optional based on business needs. For example, in Figure 6, let's say the Weblog service requires all incoming messages be signed with a UsernameToken. If this is the only requirement, the service only needs a response policy that asserts this requirement, and the Weblog user only need a request policy that asserts this requirement whenever sending messages to blogs.dev4net.com. Because there are no requirements on the messages being returned from the service, there is no need for a request policy on the service and a response policy for the user.

The following policy file defines two policies. One policy is specific to http://blogs.dev4net.com; the other is applied for all other URLs.

<?xml version="1.0" encoding="utf-8"?>
<policyDocument xmlns="http://.../Policy">
  <mappings xmlns:wse="http://.../Policy">
    <defaultEndpoint>
      <defaultOperation>
        <request policy="#Sign-X509" />
        <response policy="" />
      </defaultOperation>
    </defaultEndpoint>
    <endpoint uri="http://blogs.dev4net.com">
      <defaultOperation>
        <request policy="#Sign-Username" />
        <response policy="" />
      </defaultOperation>
    </endpoint>
  </mappings>
  <policies xmlns:wsu="http://...wssecurity-utility-1.0.xsd" 
    xmlns:wsp="http://.../policy" 
    xmlns:wssp="http://.../secext" 
    xmlns:wse="http://.../Policy" 
    xmlns:wsse="http://...wssecurity-secext-1.0.xsd" 
    xmlns:wsa="http://.../addressing">
    <wsp:Policy wsu:Id="Sign-X509">
        [Snipped for brevity]
    </wsp:Policy>
    <wsp:Policy wsu:Id="Sign-Username">
        [Snipped for brevity]
    </wsp:Policy>
  </policies>
</policyDocument>

WSE 2.0 support for security policy is capable of describing assertions for digital signatures, encryption, and message freshness. Signatures and encryption can use Username, Kerberos, X.509, and SecurityContext tokens. WSE 2.0 policy support can also be extended when custom security tokens are needed.

From the listing above, you can see there are two main sections of a policy file: <mappings> and <policies>. Policy attributes in the mappings section refer to the wsu:Id attribute in each of the policies.

The policy itself is "direction agnostic". You can tell from examining the policy file above, the direction of the policy is defined by the <request> and <response> elements in the endpoint mapping for specific operations. Think back to our Weblog example that requires all messages to the service be signed with a UsernameToken. In this example, we only need the policy to assert if message integrity is being ensured with a UsernameToken. This policy will be configured as a request policy on the user's machine and the service's machine. In WSE, this is accomplished in the app.config and web.config, respectively.

The following fragment from a .NET configuration file configures policyCache.xml as the policy and would be the same for both the client and service.

<?xml version="1.0" encoding="utf-8"?>
<configuration>
  <configSections>
    <section name="microsoft.web.services2" 
type="Microsoft.Web.Services2.Configuration.WebServicesConfiguration, 
Microsoft.Web.Services, Version=2.0.0.0, Culture=neutral, 
PublicKeyToken=31bf3856ad364e35" />
  </configSections>
  <microsoft.web.services>
    <policy>
        <cache name="policyCache.xml" />
    </policy>
  </microsoft.web.services>
</configuration>

The following policy fragments illustrate how to apply various security tokens to the available assertions. The wsse:Claims elements were added after the Settings Tool created and configured the policies.

Note I would highly recommend that you become familiar with the WSE 2.0 Settings Tool and the associated policy creation wizard called the Security Settings Wizard. They are easy to use and save a lot of time by authoring most of the configuration and policy file modifications for you.

The following policy asserts that a message be digitally signed with a UsernameToken.

<wsp:Policy wsu:Id="Sign-Username">
  <wsp:MessagePredicate 
    wsp:Usage="wsp:Required" 
    Dialect="http://.../wsse#part">
    wsp:Body() wse:Timestamp()
  </wsp:MessagePredicate>
  <wssp:Integrity wsp:Usage="wsp:Required">
    <wssp:TokenInfo>
      <wssp:SecurityToken>
        <wssp:TokenType>
            http://.../security/sc/dk
        </wssp:TokenType>
        <wssp:Claims>
          <wse:Parent>
            <wssp:SecurityToken>
              <wssp:TokenType>
                http://...#UsernameToken
              </wssp:TokenType>
              <wssp:Claims>
                <wssp:SubjectName 
                    MatchType="wssp:Exact">
                    hostname\joeblow
                </wssp:SubjectName>
                <wsse:UsePassword Type=”wsse:PasswordText”
                  wsp:Usage=”wsp:Required”>NoTelinNE1
                </wsse:UsePassword>
              </wssp:Claims>
            </wssp:SecurityToken>
          </wse:Parent>
        </wssp:Claims>
      </wssp:SecurityToken>
    </wssp:TokenInfo>
    <wssp:MessageParts 
        Dialect="http://.../wsse#part">
        wsp:Body() wse:Timestamp()
    </wssp:MessageParts>
  </wssp:Integrity>
</wsp:Policy>

The Type attribute of the UserPassword element can either mandate a clear-text password (as shown) or a digest (hash) of the password which uses a wsse:PasswordDigest value.

The following policy asserts a message be encrypted with a KerberosToken.

<wsp:Policy wsu:Id="Encrypt-Kerberos">
  <wsp:MessagePredicate 
    wsp:Usage="wsp:Required" 
    Dialect="http://.../wsse#part">
    wsp:Body() wse:Timestamp()
  </wsp:MessagePredicate>
  <wssp:Confidentiality wsp:Usage="wsp:Required">
    <wssp:KeyInfo>
      <wssp:SecurityToken>
        <wssp:TokenType>
            http://.../kerberos/Kerberosv5ST
        </wssp:TokenType>
        <wssp:Claims>
          <wssp:ServiceName>
            dons-work-m1
          </wssp:ServiceName>
        </wssp:Claims>
      </wssp:SecurityToken>
    </wssp:KeyInfo>
    <wssp:MessageParts 
        Dialect="http://.../wsse#part">
        wsp:Body()</wssp:MessageParts>
  </wsse:Confidentiality>
</wsp:Policy>

The MessageParts element defines the part of the message affected by this policy. The Dialect attribute identifies the expression dialect being used. WSE 2.0 only supports https://schemas.xmlsoap.org/2002/12/wsse\#part. Possible values are wsp:Body(), wsp:Header("NameOfHeader"), wse:Timestamp(), wse:Addressing() and wse:UsernameToken().

The following policy asserts that a message be digitally signed with an X.509 Certificate.

<wsp:Policy wsu:Id="Sign-X.509">
  <wsp:MessagePredicate 
    wsp:Usage="wsp:Required" 
    Dialect="http://.../wsse#part">
    wsp:Body() ... wse:Timestamp()
  </wsp:MessagePredicate>
  <wssp:Integrity wsp:Usage="wsp:Required">
    <wssp:TokenInfo>
      <wssp:SecurityToken>
        <wssp:TokenType>http://...#X509v3</wssp:TokenType>
        <wssp:TokenIssuer>CN=Root Agency</wssp:TokenIssuer>
        <wssp:Claims>
          <wssp:SubjectName MatchType="wssp:Exact">
            CN=MsdnWse2SecuritySamplesClient
          </wssp:SubjectName>
          <wssp:X509Extension OID="2.5.29.14" 
            MatchType="wssp:Exact">
            ODytWwSUPj9/uGbXZTAdEhhzxLE=
          </wssp:X509Extension>
        </wssp:Claims>
      </wssp:SecurityToken>
    </wssp:TokenInfo>
    <wssp:MessageParts 
        Dialect="http://.../wsse#part">
        wsp:Body() ... wse:Timestamp()
    </wssp:MessageParts>
  </wssp:Integrity>
</wsp:Policy>

The following policy asserts a message be encrypted with a SecureContextToken from a specific Security Token Service (STS).

<wsp:Policy wsu:Id="Encrypt-SCT">
  <wsp:MessagePredicate 
    wsp:Usage="wsp:Required" 
    Dialect="http://.../wsse#part">
    wsp:Body() ... wse:Timestamp()
  </wsp:MessagePredicate>
  <wssp:Confidentiality wsp:Usage="wsp:Required">
    <wssp:KeyInfo>
      <wssp:SecurityToken>
        <wssp:TokenType>
            http://.../security/sc/dk
        </wssp:TokenType>
        <wssp:Claims>
          <wse:Parent>
            <wssp:SecurityToken>
              <wssp:TokenType>
                http://.../security/sc/sct
              </wssp:TokenType>
              <wssp:Claims>
                <wse:BaseToken>
                  <wssp:SecurityToken>
                    <wssp:TokenType>
                        http://...#UsernameToken
                    </wssp:TokenType>
                  </wssp:SecurityToken>
                </wse:BaseToken>
                <wse:IssuerToken>
                  <wssp:SecurityToken>
                    <wssp:TokenType>
                        http://...#X509v3
                    </wssp:TokenType>
                    <wssp:TokenIssuer>
                        CN=Root Agency
                    </wssp:TokenIssuer>
                    <wssp:Claims>
                      <wssp:SubjectName 
                        MatchType="wssp:Exact">
                        CN=MsdnWse2SecuritySamplesServer
                      </wssp:SubjectName>
                      <wssp:X509Extension OID="2.5.29.14" 
                        MatchType="wssp:Exact">
                        kRdbDi6QWTlyMpB7xNN65c0vTN4=
                      </wssp:X509Extension>
                    </wssp:Claims>
                  </wssp:SecurityToken>
                </wse:IssuerToken>
              </wssp:Claims>
            </wssp:SecurityToken>
          </wse:Parent>
        </wssp:Claims>
      </wssp:SecurityToken>
    </wssp:KeyInfo>
    <wssp:MessageParts 
        Dialect="http://.../wsse#part">
        wsp:Body()
    </wssp:MessageParts>
  </wssp:Confidentiality>
</wsp:Policy>

The SCT above describes its RST being signed with a UsernameToken and the RSTR being signed with an X509SecurityToken. This example can be further clarified by reviewing the PolicySecureContext.config file in the SecurityPolicy folder of the downloadable sample code that accompanies this article.

The following policy asserts that a message is not older than 20 seconds. Obviously, the message's expiration doesn't have anything to do with signing or encryption (that's why it's not a child of wsse:Confidentiality or wsse:Integrity). However, I included it here because reducing this value can help prevent against replay attacks.

<wsp:Policy wsu:Id="policy-ffe169eb-9cbd-4b85-a066-75addb792760" 
xmlns:wsp="https://schemas.xmlsoap.org/ws/2002/12/policy">
  <wsse:MessageAge wsp:Usage="wsp:Required" Age="20"/>
</wsp:Policy>

The WSE policy enforcer and validator support the MessageAge element, but the Settings Tool does not. To declare a message expiration with policy, this element must be added to the policy after the Settings Tool creates it.

So far we've seen how to express security requirements using policy from a claims-based approach. This approach is fine when the credentials are known ahead of time. Claims-based policies become problematic and sometimes impossible when the credentials are not known until runtime. In these situations, the credentials can be added to the security token cache. The policy enforcer will interrogate this cache in attempts to fulfill the security requirement. The following policy describes only the type of token needed to sign the message without any specific information about the token.

<wsp:Policy wsu:Id="Sign-Username">
  <wsp:MessagePredicate 
    wsp:Usage="wsp:Required" 
    Dialect="http://.../wsse#part">
    wsp:Body() wse:Timestamp()
  </wsp:MessagePredicate>
  <wssp:Integrity wsp:Usage="wsp:Required">
    <wssp:TokenInfo>
      <wssp:SecurityToken>
        <wssp:TokenType>
            http://.../security/sc/dk
        </wssp:TokenType>
        <wssp:Claims>
          <wse:Parent>
            <wssp:SecurityToken>
              <wssp:TokenType>
                http://...#UsernameToken
              </wssp:TokenType>
            </wssp:SecurityToken>
          </wse:Parent>
        </wssp:Claims>
      </wssp:SecurityToken>
    </wssp:TokenInfo>
    <wssp:MessageParts 
        Dialect="http://.../wsse#part">
        wsp:Body() wse:Timestamp()
    </wssp:MessageParts>
  </wssp:Integrity>
</wsp:Policy>

Notice how the entire wsse:Claims element has been removed. Attaching the specific UsernameToken is now in the hands of the sender's code. This code demonstrates how to attach a SecurityToken to the security token cache.

// Create an instance of the proxy
WeblogProxy proxy = new WeblogProxy();

// Create the UsernameToken
UsernameToken token = new UsernameToken( username, password, 
PasswordOption.SendPlainText );

// Add the UsernameToken to the token cache
PolicyEnforcementSecurityTokenCache.GlobalCache.Add( token );

// Call the web service
ConfirmedWeblogEntry confirmed = proxy.AddEntry( newEntry );

Notice how the token isn't explicitly added to the response context. The policy enforcer will add the appropriate token based on the policy. In fact, if the policy contains claims and tokens exist in the token cache, the policy enforcer will make the right decision based on the token type. The policy enforcer will not look in the RequestSoapContext so there is no need in adding the token to it when a policy is involved.

Conclusion

WSE 2.0 is a big step forward in conformity and interoperability within the Web services initiative. Building on WS-Security, WSE 2.0 improves the implementation of security, trust, and secured conversations in Web services architecture. Hopefully this article has provided you with a solid understanding of the security capabilities of WSE 2.0. If you haven't already done so, I encourage you to download and become familiar with the sample code that accompanies this article. It should help you get a better understanding of the requirements you must meet during the design and implementation of your Web service applications.