WSE 2.0: Get Your .NET Web Services Security Up to Spec

n October 2003, Microsoft announced the technical preview of its Web Services Enhancements for .NET (WSE) 2.0 toolkit. This release implements some of the new Web Services Architecture (WSA) specifications that Microsoft, IBM, and BEA have been working on together, and it improves existing specifications such as WS-Security. This article examines the WS-Security improvements and shows how you can upgrade your Web services to WSE 2.0.

The following is a list of WSE 2.0’s new or improved security features, which provide new infrastructure services for designing and implementing Web servicesWSE 2.0:

  • Authentication of a Web service call against the Windows security
  • Role-based security through the use of security tokens (based on the Windows security)
  • Signing and encryption of user-defined SOAP headers
  • Support for Keberos security tokens
  • WS-Trust
  • WS-SecureConversation
  • A policy framework based on WS-Policy
  • SOAP messaging over transport channels like TCP
  • Support for the development of user-defined transport channels

Versatile Web Services with Choice of Transport Protocols
Web services?by their very name?imply that they must have something to do with the Web. But that’s not completely true. Look more closely at the SOAP specification and you’ll recognize that SOAP can be used independently of the transport protocol. But current Web services implementations exchange SOAP messages only through HTTP or HTTPS.

With WSE 2.0, you now can call Web service methods through other transport protocols, such as TCP, SMTP, MSMQ, or even third-party transport protocols. WSE 2.0 ships with the TCP transport channel, enabling the use of SOAP messaging for communication between distributed applications or even through application domains. Microsoft currently suggests using SOAP messaging for publisher/subscriber scenarios because of the loose coupling of both service endpoints. When you implement such a scenario using proprietary communication technologies like DCOM or .NET remoting, you will end up with a tightly coupled system?and such a system is not very easy to scale in the future.

Tightly coupled endpoints are unfortunately not the only disadvantages to using DCOM or .NET remoting. It has other disadvantages as well. Specifically, you cannot use specifications like WS-Security, WS-Policy, WS-Trust, WS-SecureConversation, and all the others through these communication technologies. For example, consider the following scenario: You are buying a book at an online store like Amazon. After adding your favorite books to your shopping cart, you must enter your payment and delivery information. Let’s assume that the system creates a SOAP message for this sensitive information and sends it to the payment agency and finally to the parcel service. Is this process secure? Definitely not!

The SOAP message is intended for two so-called intermediaries, but the first intermediary (the payment agency) doesn’t have to know anything about the information intended for the second intermediary (the parcel service). With WSE 2.0, you can now ensure that the first intermediary reads only the information intended for it by signing and encrypting the first part of the SOAP message. This procedure ensures that the second intermediary can’t read or alter the first part of the SOAP message, which is not intended for it. You currently can’t do this with DCOM or .NET remoting–not yet.

But how do you fit this evolution toward service-oriented applications (like the online store) into Microsoft’s Web services picture? In the past few years, Microsoft has recognized that its current communication technologies, like DCOM and .NET remoting, are too complex. Furthermore, everyone thought that Web services could be used only in conjunction with HTTP. But do you want to run your critical enterprise applications on a protocol that isn’t reliable or stable? Of course not! Exactly for this reason, Microsoft, IBM, and BEA have begun work on the WSA. The foundation of this architecture are the WS-* specifications.

User Authentication
Not only can you use tWSE 2.0to encrypt and sign SOAP messages independently of the transport protocol, it also enables you to attach user credentials in a standardized way. The WSE uses security tokens internally to represent security claims from Web service methods. A UsernameToken is used to attach information about the user to the SOAP message, for example. With this token, the WSE can authenticate the user, validate the password, and check whether the user has all rights to execute the Web service method.

To use WSE functionality on the client side, you have to add a reference to the assembly Microsoft.Web.Services, which is installed in the GAC of the local computer. Furthermore, you have to change the base class of the Web service proxy to Microsoft.Web.Services.WebServicesClientProtocol. The client then has more properties (such as SoapContext) available for manipulating the SOAP message. The following listing shows how a UsernameToken can be attached to a SOAP message:

using Microsoft.Web.Services;using Microsoft.Web.Services.Security;UsernameToken token = new UsernameToken("JohnDoe",    "Password", PasswordOption.SendPlainText);PDCRegistration proxy = new PDCRegistration();proxy.RequestSoapContext.Security.Tokens.Add(token);SignupRequest request = new SignupRequest();request.Address = "Microsoft One Way";request.CreditCardNumber = "123";request.Name = "Klaus Aschenbrenner";SignupResponse reponse =    proxy.SingupForPDC_UsernameToken(request);Console.WriteLine(response.RegistrationNumber);

The SOAP message with the UsernameToken is then sent to the Web service endpoint, where it must be determined whether the indicated user in the UsernameToken can be authenticated. For this server-side task, you can use the class UsernameTokenManager, which is implemented in the namespace Microsoft.Web.Services.Security.Tokens. To authenticate a user, you must derive only your own class from UsernameTokenManager and override the function AuthenticateToken. The function AuthenticateToken is called on the server side from the WSE when a SOAP message with a UsernameToken is received. This function’s only task is to return the correct password for the user specified in the UsernameToken. If the password returned and the password specified in the UsernameToken are the same, then the SOAP message is processed. Otherwise, the SOAP message is rejected.

To use the WSE on the server side for your Web service, you must register a SoapExtension that ships with the WSE. To do this, you must register the SoapExtension in the section in the web.config file of your Web service. After this step, your derived UsernameTokenManager must be registered within the WSE. The registration of the WSE lies in your web.config file, as the following listing shows:

                           

You must write the value of the attribute type in the web.config file within one line. I have broken it into several lines only for better readability.

After successfully registering the UsernameTokenManager in the web.config file, you can implement the class as follows:

using Microsoft.Web.Services;using Microsoft.Web.Services.Security;using Microsoft.Web.Services.Security.Tokens;public class AuthenticationManager : UsernameTokenManager{   protected override string AuthenticateToken(      UsernameToken token)   {      if (token.Username =="JohnDoe") return "Password";      return "";   }}

As you can see, the function AuthenticateToken receives as a parameter the UsernameToken from the SOAP message. With the property UsernameToken.Username, you can determine the user name for which you must return the correct password. In my primitive implementation, I just return the hard-coded password of the user name JohnDoe. In a production Web service, you can determine the password from other resources like XML-Files, databases, LDAP directories, and so on.

The following listing shows the SOAP header for the UsernameToken, which is sent within the SOAP message to the Web service endpoint:

         ...                           JohnDoe            Password            ...            ...                        ...   

As you can see, the password is sent in clear text. This is because of the option PasswordOption.SendPlainText in the client code. If you use this option, Microsoft recommends using a secure protocol like HTTPS. But you can also use the option PasswordOption.SendHashed to transfer the encrypted password across the wire.

User Authorization
The current WSE implementation provides the functionality to authorize a Web service call with the enclosed UsernameToken. To accomplish this, the SOAP message must be signed with the UsernameToken. When the WSE receives such a Web service call, it calls the Win32 API function LogonUser with the username and the password from the UsernameToken as parameters. If the call to this function is successful, the property Principal of the UsernameToken is initialized with the authorized user.

This property implements the interface System.Security.Principal.IPrincipal, which enables you to use the function IPrincipal.IsInRole to determine whether the user of the current request is in a specified role. To use this feature, you must send the password of the UsernameToken in plain text. But you can sign or encrypt the plain password with a SecurityToken. If you want to use this built-in WSE feature, you don’t have to implement your own UsernameTokenManager because the WSE authenticates the request internally.

The following listing shows how you can authorize a Web service call against a windows group (The Web service call is processed only if the user of the current request is a member of the built-in windows group Administrators):

[WebMethod]public SignupResponse SignupForPDC(SignupRequest request){   if (IsInRole(@"BUILT	INAdministrators"))   {      return request.ProcessMessage();   }   else   {      throw new UnauthorizedAccessException(         "Your request was not authorized!");   }}private bool IsInRole(string role){   SecurityElementCollection elements =       RequestSoapContext.Current.Security.Elements;   foreach (ISecurityElement secElement in elements)   {      if (secElement is Signature)      {         Signature sig = (Signature)secElement;         if ((sig.SignatureOptions &             SignatureOptions.IncludeSoapBody) != 0)         {            SecurityToken sigToken = sig.SecurityToken;            if (sigToken is UsernameToken)            {               UsernameToken token =                   (UsernameToken)sigToken;               return token.Principal.IsInRole(role);            }         }      }   }   return false;}

The previous listings showed some possibilities for using authentication and authorization within Web services. The WSE provides some of these features automatically. The following section shows how you can make your SOAP messages more confidential by using a digital signature.

Signing SOAP Messages
When you assign only a UsernameToken to your SOAP message, you cannot assure that the message was not altered during transport. So what can you do to ensure an uncompromised message? You can sign your SOAP message with a XML digital signature. The signature itself is unique for your message. If someone changes only a bit of your message then the signature changes and the receiver of your message knows that the message was changed during transport. The signature itself contains a hash, which is computed with the entire content of your SOAP message. When the message arrives at the other endpoint, then the receiver also computes a hash with the content of the incoming message. This hash is then compared with the hash contained in the sent SOAP message. If both hashes match, the receiver knows that no one else has changed the message during the transport.

The following listing shows how you can sign a SOAP message with an X.509 certificate (This sample uses the function FindCertificateByKeyIdentifier to find the appropriate certificate with the requested ID):

public static string ClientBase64KeyID =    "ODytWwSUPj9/uGbXZTAdEhhzxLE=";public void CallWebService(){   PDCRegistrationProxy proxy = new       PDCRegistrationProxy();   SoapContext requestContext = proxy.      RequestSoapContext;   X509SecurityToken token = GetSigningToken();   if (token == null)      throw new Exception(         "X.509 certificate couldnt be found!");   SignupRequest request = new SignupRequest();   request.Name = "Klaus Aschenbrenner";   request.Address = Microsoft One Way";   request.CreditCardNumber = "123";   requestContext.Security.Tokens.Add(token);   requestContext.Security.Elements.Add(      new Signature(token));   SignupResponse response = proxy.SignupForPDC(request);   Console.WriteLine(response.RegistrationNumber);}private X509SecurityToken GetSigningToken(){   X509SecurityToken token = null;   X509CertificateStore store = X509CertificateStore.      CurrentUserStore(X509CertificateStore.MyStore);   if (!store.OpenRead()) return null;   X509CertificateCollection certs =       store.FindCertificateByKeyIdentifier      (Convert.FromBase64String(ClientBase64KeyID));   if (certs.Count > 0)      token = new X509SecurityToken(         (X509Certificate(certs[0]));   return token;}

When you use an XML digital signature to sign your SOAP message, then the section of the SOAP header contains a new section called . This new section contains all the information needed for the XML digital signature. Here you will find the following three subsections:

The section lists the elements that are signed in the SOAP message. For each element, a section is created. The section contains a unique ID (an URI) that refers to the signed element in the SOAP message. The following listing shows a simple example:

         ...               ...            ...   

For each ID listed in the section a corresponding element with the same ID exists within the SOAP message. Through this mechanism the WSE can create a link between the signature and the signed elements in the SOAP message. The section contains the value of the XML digital signature, and the section contains a reference to the SecurityToken that was used to sign the elements referred in the section.

When you use the default settings, the following elements of a SOAP 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

As you can see, a lot of pieces of the SOAP message are signed. Therefore, the size of the message is also increasing. So you also can sign only specified elements of a SOAP message. To indicate these elements, the WSE provides the property SignatureOptions in the class Signature. With this property you can explicitly say which elements must be signed. Furthermore, you can also sign user-defined SOAP headers. On the server side the WSE checks if the signature of your SOAP message is correct. If it’s not okay, the SOAP message is rejected through the WSE and is not processed further. The nice thing is that you don’t have to write any code to check the signature on the server side–it’s all done through the implementation of the WSE!

Encrypting SOAP Messages
When you sign a SOAP message, you can assure that no one alters it. But a smart snoop can see the content of the message. To prevent this, you can encrypt your SOAP message so that no one can read its contents.

The WSE provides the functionality to encrypt both the request to the Web service and the response back to the client. When you use a X.509 certificate, the sender encrypts the message with the public key of the sender’s X.509 certificate. Then the receiver uses his own private key of the X.509 certificate to decrypt the message.

To encrypt a SOAP message, the WSE provides the class EncryptedData. The following listing shows how you can use this class to encrypt a Web service call:

public void EncryptedWebServiceCall(){   PDCRegistration proxy = new PDCRegistration();   SoapContext requestContext = proxy.RequestSoapContext;   X509SecurityToken token = GetEncryptionToken();   if (token == null)      throw new Exception(         "X.509 certificate couldnt be found!");   SignupRequest request = new SignupRequest();   request.Name = "Klaus Aschenbrenner";   request.Address = Microsoft One Way";   request.CreditCardNumber = "123";   requestContext.Security.Elements.Add(      new EncryptedData(token));   SignupResponse response = proxy.SignupForPDC(request);   Console.WriteLine(response.RegistrationNumber);}private void X509SecurityToken GetEncryptionToken(){   // See function GetSigningToken() of the last    // listening...}

When you encrypt a SOAP message, then the content of the SOAP header changes: within the section , a new section called is created. This section contains all the information needed to encrypt the SOAP message. Furthermore, you again can find a reference list () that contains links to the elements of the SOAP message, which are encrypted.

The default behavior is that the body of the SOAP message is encrypted. As you have seen previously, the password of the UsernameToken must be sent in plain text when you want to authorize the user of a request against a windows group. With encryption, you can now encrypt a UsernameToken with a X.509 certificate. The following listing shows how you can encrypt a UsernameToken, which contains the password in plain text:

public void EncryptUsernameToken(){   PDCRegistration proxy = new PDCRegistration();   SoapContext requestContext = proxy.RequestSoapContext;   X509SecurityToken token = GetEncryptionToken();   if (token == null)      throw new Exception(         "X.509 certificate couldnt be found!");   SignupRequest request = new SignupRequest();   request.Name = "Klaus Aschenbrenner";   request.Address = Microsoft One Way";   request.CreditCardNumber = "123";   UsernameToken userToken = new UsernameToken(      "John Doe", "password",       PasswordOption.SendPlainText);   requestContext.Security.Tokens.Add(userToken);   requestContext.Security.Elements.Add(      new EncryptedData(userToken);   requestContext.Security.Elements.Add(      new EncryptedData(token, "#" + userToken.Id);   SignupResponse response = proxy.SignupForPDC(request);   Console.WriteLine(response.RegistrationNumber);}private void X509SecurityToken GetEncryptionToken(){   // See function GetSigningToken() of the last    // listening...}

The Future Is Blue: Indigo
Microsoft took the experience it gained from WS Enhancements and applied it to the next version of the .NET Enterprise Services (codenamed Indigo). Indigo was first presented at the PDC 2003 in Los Angeles. Indigo will be an integrative, extensible framework for distributed applications based on the .NET Framework and Web services. Indigo is based on the WS-* specifications and it also can work together with other platforms like Java. The following list shows the design goals of Indigo:

  • Indigo should help ISVs implement and deploy Web services very easily.
  • Indigo provides a declarative programming model for implementing powerful Web services. For example, you need only one attribute to make a Web service call reliable!
  • Indigo provides an implementation of the WS-* specifications.
  • Indigo will combine the best features of .NET remoting, MSMQ, and of the .NET Enterprise Services.
  • Any code developed today can be used in Indigo without any big changes.

Indigo will be released in between the release of Visual Studio Whidbey and Windows Longhorn and will be available on Windows 2000, Windows XP, and Windows 2003 Server as an additional download.

This article has examined the new security features in WS-Enhancements. In the current implementation, the WSE provides a lot of functions to ensure a secure message exchange between the sender and the receiver. Since WSE is a playground for the upcoming Indigo, you’ll find it very useful to play around with the WSE, gain experience, and then apply this experience when Indigo is released. Stay tuned…

Share the Post:
Share on facebook
Share on twitter
Share on linkedin

Overview

Recent Articles: