Implement Secure .NET Web Services with WS-Security

Web Services Enhancements 1.0 for Microsoft .NET (WSE) provides the functionality for Microsoft .NET Framework developers to support the latest Web services capabilities. WSE is the cornerstone of the GXA (Global XML Web-Services Architecture), an architecture of proposed Web services standards that Microsoft, IBM, and other companies have written. (Table 1 describes the core specifications of GXA.)

This article examines the GXA’s WS-Security spec and demonstrates how you can use it to implement secure .NET Web services by digitally signing, encrypting, and adding security credentials to SOAP messages.

How to Implement WSE
WSE is implemented as a SOAP extension and therefore must be registered within the web.config file of your Web service. To accommodate this task, the web.config file contains the element. Within this element you can configure all SOAP extensions, which should be available to your Web service at runtime.

I have added the following lines within the section of the web.config file:

                           

Note that the new element must be written on one line. I’ve divided it into several lines only for better reading. Adding this new text to web.config configures the SOAP extension on the server side. It is then ready to use.

To expose the functionality of the WSE to the client, you must derive the Web service proxy class from the class WebServicesClientProtocol, which lives in the namespace Microsoft.Web.Services. So when you add a Web reference to your project and want to use the WSE, you must modify the Reference.cs file manually and change the base class of the proxy to WebServicesClientProtocol. Currently, Visual Studio.NET does not provide an option to mark a Web service as WSE-enabled.

Once you’ve registered WSE and derived the Web service proxy class, both the client and server sides are ready to use the new features of the WSE.

RequestSoapContext
On the client side, all WSE features can be accessed through the proxy class and a property called RequestSoapContext. With this context object you can now encrypt SOAP messages, sign them, and assign user credentials to them. The following code shows how to get a reference to this object:

SoapContext myContext = myProxyClass.RequestSoapContext;

Traceable Client/Server Traffic
A very useful feature of the WSE is all the network traffic between the client and server can be traced. These tracing capabilities can be also configured in the web.config file. But before you can use this feature, you must create a new section called Microsoft.Web.Services. This task can be accomplish with the following code:

         

With this entry in web.config you configure a new section called Microsoft.Web.Services and the appropriate section handler Microsoft.Web.Services.Configuration.WebServicesConfiguration. After that, the tracing feature can be enabled with the following lines:

                           

All further requests from the client are written in the inputTrace.config file and the response from the Web service is written in the outputTrace.config file. How you can examine and interpret the content of both these files is shown later in this article.

If you have programmed Web services with the .NET Framework, you realize it has no platform-independent support for securing Web services across the Internet. With the introduction of WS-Security, that support now exists.

WS-Security offers the following new security functions:

  • Digitally signing SOAP messages:
    • using an X.509 certificate
    • using a user name and a password
    • using a custom binary token
  • Encrypting SOAP messages:
    • using an X.509 certificate
    • using a shared secret
    • using a custom binary token
  • Adding security credentials to the SOAP message
  • Signing a SOAP Request with a User Name and Password
    Let’s look more closely at digitally signing SOAP messages with a user name and a password, since this option is used very often in Web services. To work correctly, the Web service itself must provide a class that implements the interface IPasswordProvider:

    public interface IPasswordProvider{   public String GetPassword(UsernameToken token);}

    As you can see, there is only one method, GetPassword, which is made available by the implementing class. The WSE framework calls this method when it must authenticate a Web service request. The parameter of type UsernameToken provides more information about the incoming user request. As a result the method must return the user’s password. The property Username of the class UsernameToken contains the credentials of the user making the SOAP request. With it, returning the correct user password is very easy (e.g., querying from a database or XML file).

    After that the runtime creates a hash of the password and compares it to the password hash provided by the Web service request. If the hashes match, the request is processed. If not, the request is rejected.

    A value from the enumeration PasswordOption defines how the password is sent across the wire:

  • SendNone: No password is sent in the SOAP message.
  • SendHashed: A SHA-1 hash of the password is sent in the SOAP message.
  • SendPlainText: The password is sent in clear text. When using this option, a secure transport channel such as SSL should be used.

    The following listing shows how a SOAP request can be signed with a username and a password:

    // Create a UsernameTokenUsernameToken userToken = new UsernameToken(   Environment.UserName, "MyPassword",    PasswordOption.SendHashed);// Create proxy classMyProxyClass proxy = new MyProxyClass();SoapContext context = proxy.RequestSoapContext();// Add a security tokencontext.Security.Tokens.Add(userToken);// Sign the SOAP requestcontext.Security.Elements.Add(new Signature(userToken))// Call a methodproxy.HelloWorld();

    As you can see, you can sign a SOAP request by just adding a new instance of the class Signature to the collection Elements. Easy, isn’t it? But which bits were sent across the wire?

    Within the element you will find the section . This section is responsible for the additional information that WSE uses. The sub-section contains the user credentials sent with the SOAP request:

       MyUserName         GXLG8cYAao4CGppz60e/cHz0M0o=   ...

    Only the hash of the password is sent across the network. This hash is then compared to the hash from the password that the method GetPassword of the interface IPasswordProvider returned. If both hashes match, the SOAP request is successfully authenticated.

    The section follows . It contains additional information about the signature used to sign the SOAP request. Here you will find some sections that define various algorithms for signing the request. Every algorithm has a unique URI identifying it. The SOAP body contains one of these URIs, which identifies the algorithm used to sign the body:

       ......   

    The View from the Server Side
    Now let’s look on the server side of this Web service. As mentioned previously, you must implement the interface IPasswordProvider and register the implementing class in the WSE runtime. You do this in the section of the web.config file of the Web service:

                

    The attribute type of the element takes the class in the form of Namespace.ClassName, ClassName. I have implemented the interface as follows:

    public class PasswordProvider : IPasswordProvider{   public String GetPassword(UsernameToken token)   {      return "password";   }}

    In a real Web service you can query the password of the current user (token.Username) from a storage entity like a database or a XML file. The Web method I use is straightforward:

     [WebMethod]public String HelloWorld(){   SoapContext requestContext =       HttpSoapContext.RequestContext;   String strResult = ";   if (requestContext != null)   {      UsernameToken token = GetFirstUsernameToken(         requestContext.Security);      if (token != null)      {         strResult = "Hello World, " + token.Username;      }   }   return strResult;}private UsernameToken GetFirstUsernameToken(Security sec){   UsernameToken retval = null;   if (sec.Tokens.Count > 0)   {      foreach (SecurityToken tok in sec.Tokens)      {         retval = tok as UsernameToken;         if (retval != null) return retval;      }   }}

    With the private method GetFirstUsernameToken I find the first UsernameToken and return the string Hello World, including the name of the user. When you set breakpoints at the methods GetPassword and HelloWorld, you can see that the former is called before the latter. When the client sends an incorrect password with the SOAP request and you debug the Web service again, you find out that the method GetPassword is called but then the request is rejected. The WSE runtime prevents the code in the function HelloWorld from being executed.

    Encrypting SOAP Messages
    When you use the Web service infrastructure provided by the .NET Framework, Web service calls are not encrypted. That means anyone who understands Internet protocols can read your messages and change them! When such a message is signed with a signature, as shown in the previous section, the attacker has no chance to compromise it?but he can still read the content of the SOAP call!

    To avoid this security risk you can encrypt the SOAP bodies of your messages, so that a network sniffer cannot read them. An attacker will see only unimportant chars and will not be able to reconstruct the original SOAP call.

    For the encryption of SOAP messages the WSE offers you the following three possibilities:

  • Use an X.509 certificate
  • Use a shared secret
  • Use a custom binary token
  • The Web service itself must provide a class that implements the interface IDecryptionKeyProvider:

    public interface IDecryptionKeyProvider{   public DecryptionKey GetDecryptionKey(     String algorithmUri,      KeyInfo keyInfo);}

    When the WSE runtime decrypts a SOAP request, it calls the method GetDecryptionKey. This method must return a key that can decrypt the request. If this key cannot, because you perhaps supplied a wrong key, the Web service rejects the request and throws an exception of type SoapException.

    The following listing shows the required code to implement the IDecryptionKeyProvider interface. The function GetDecryptionKey returns a key based on the Triple-DES-Algorithm:

    public DecryptionKey GetDecryptionKey(String algorithmUri,    KeyInfo keyInfo){   byte [] keyBytes = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11,       12, 13, 14, 15, 16 };   byte [] ivBytes = { 1, 2, 3, 4, 5, 6, 7 };   foreach (KeyInfoClause clause in keyInfo)   {      if (((KeyInfoName)clause).Value == Solvion Symmetric          Key)      {         SymmetricAlgorithm algo = new             TripleDESCryptoServiceProvider();         algo.Key = keyBytes;         algo.IV = ivBytes;         SymmetricDecryptionKey key = new             SymmetricDecryptionKey(algo);         return key;      }   }   return null;}

    The class implementing the interface also must be registered in the file web.config, as shown here:

                               

    The Web method itself is programmed as simply as possible:

     [WebMethod]public String EncryptedSoapMessage(){   SoapContext requestContext =       HttpSoapContext.RequestContext;   String strResult = ;   if (requestContext != null)   {      strResult = Hello World from my encrypted Web          service.;   }   return strResult;}

    In the client code, I have written the method GetEncryptionKey, which returns the key for the encryption of the SOAP message. This key is then added to the proxy of the Web service, so that the WSE can encrypt the SOAP call:

    private void CallEncryptedSoapMessage(){   MyProxyClass proxy = new MyProxyClass();   SoapContext requestContext = proxy.RequestSoapContext;   EncryptionKey key = GetEncryptionKey();   try   {      requestContext.Security.Elements.Add(new          EncrytedData(key));      requestContext.Timestamp.Ttl = 60000;      Console.WriteLine(proxy.EncryptedSoapMessage());   }   catch (SoapException ex)   {      Console.WriteLine(ex.Message);   }}

    When you are debugging the Web service call, you can see that the method GetDecryptionKey is called before the Web method is executed. When you provide an incorrect key in the client code, you see that the WSE automatically rejects the call to the Web method. When you take a closer look at the trace files, you see that the whole body of the SOAP request is encrypted and it cannot be decrypted without the right key.

    The Cornerstone of the GXA
    This article has given you a brief introduction to the WSE and has shown how you can program secure Web services using WS-Security. This current WSE release offers you the possibilities to sign and encrypt Web service requests. Its actual implementation is the cornerstone of the GXA architecture, which provides many more proposal standards for Web services infrastructure issues.

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

    Overview

    Recent Articles: