WS-Security and WSE 2.0

 

Service Oriented Architecture (SOA) is a distributed architecture design pattern. In this third article of the series, we will demonstrate the basic features of how WSE 2.0 implements the WS-Security specification. The next article in the series will use this knowledge to add security to our Mortgage Loan Service application which is being built as an example of a Service Oriented Architecture.

 

Now that you understand how to send messages, the next step is to understand how to secure them.

 

WS-Security is an approved specification developed under the OASIS (www.oasis-open.org) standards organization. It does not describe a protocol for securing your Web service, and it does not mandate that a particular technology be used. WS-Security describes a flexible mechanism to create protocols and security models for securing SOAP messages.

 

Security is such a complex topic that I will discuss it in two articles. This article will talk about how WS-Security's approach to developing protocols and models is implemented in WSE 2.0. The next article will discuss how to use this knowledge to develop a particular security model for our case study.

 

The WS-Security Specification

 

WS-Security describes independent mechanisms to:

§        describe assertions made about security

§        determine if a message has been altered (message integrity)

§        prevent a message from being read by an unauthorized party (message confidentiality).

 

These mechanisms can be used to provide end-to-end security for a message. Unlike SSL which only provides encryption between two points, message security must apply to all the intermediates through which a message flows. Consider an order that flows from an order entry application, to an inventory control system that checks for availability, to a billing system that processes the order, to a fulfillment system that ships it. Your credit card information should be encrypted so that only the billing system can read it. The inventory control system needs only to know the items requested and their quantity. If each of these intermediates is a separate division of a company, or a separate company, this message is said to cross a trust domain. Trust is the degree to which one entity believes the claims of another, or allows another to undertake actions on its behalf. Since different companies or divisions trust each other to varying degrees, the security models built with WS-Security have to allow for varying levels of trust. In our mortgage example, the Credit Agency, the Bank, and the Loan officer are different trust domains. The level of trust between the Mortgage decision service and the Bank Portal is much higher, than the level of trust between the Credit Agency and the Bank, or the Bank and the Loan officer.

 

Each of these mechanisms uses a security token to represent a security claim. For example, an X509 certificate represents an association between a public key and an identity that is verified by some third party. An X509 security token represents this association. If you know how WSE 2.0 implements these security tokens, you will understand how WSE 2.0 implements WS-Security. Each security token is associated with a particular type of security protocol such as username and password, X509 certificates, Kerberos or SAML. Each of these protocols has an associated specification. For example, X509 certificates are described in the Web Services Security X509 Certificate Token Profile specification. These tokens and the associated information are placed in the SOAP headers associated with the SOAP message as described in previous articles.

 

It is also important to be clear about what WS-Security and its associated specifications do not do. The specification tells you how to place a security token such as username and password, or X509 certificate in a SOAP message. How you use this token to authenticate a user is up to you. You can decide to store your passwords in plain text in a file that is available to everyone. You can decide to ignore an X509 certificate that is present in a given message. Although the specification recommends you do not do so, you can accept the X509 certificate even if you know it is no longer valid. While the specification allows you to implement different trust levels for different trust domains, it does not tell you how to determine those different levels of trust.  You decide how the security protocols you use are implemented.

 

WS-Security does not tell you how to develop a security model. While a discussion of security models is beyond the scope of these articles, one example should make clear their importance. Any sophisticated electronic commerce application has to deal with non-repudiation. When you make a purchase at a store's physical location, and sign a credit card slip, that signature can be used to prevent you from repudiating, or claiming you never made that transaction. WS-Security tells you how to place a digital signature in a message, but it does not tell you how to implement non-repudiation in your application.

 

User Names and Passwords

 

I will start with the simple Username example. I use username/password because everyone understands it, not because it is the best. In fact username/password is one of the least secure authentication mechanisms. I am going to use the TCP protocol to make the example setup simple. As we discussed in the previous article, this works with HTTP or any other transport.

 

The LoanService solution has the service for this example, the LoanRequest solution has the service client. The transport and addressing code in the LoanRequest should be easy to understand based on our discussion in the previous articles. The only difference is that we create a Username Security Token and add it to the tokens collection in the security object associated with the SoapContext instance for this message. Recall that the SoapContext class is what the WSE infrastructure uses to mediate between your code and the SOAP message header.

 

For simplicity I hard coded the username and password. Do NOT ever do this in production code. You may wonder why I emphasize this. This has been done in products that have been placed in the marketplace that have claimed to be secure.

 

// hardcode instead of prompting in dialog box

// do not ever even think of storing passwords

// in real code

UsernameToken token =  new UsernameToken("peter",

        "peterpassword", PasswordOption.SendHashed);

message.Context.Security.Tokens.Add(token);

 

A UsernameToken instance is created with the hashed password option. This hashes the password that is placed in the security header. The security token is then added to the message. If you examine the SOAP header you will see that the username and hashed password is added to the security header. The hashed password, not the clear text, is sent over the wire.

 

<wsse:Security soap:mustUnderstand="1">

  <wsu:Timestamp wsu:Id="Timestamp-d8aaaae0-c783

                    -4c14-a529-07222365e31d">

    <wsu:Created>2005-03-27T19:34:47Z</wsu:Created>

    <wsu:Expires>2005-03-27T19:39:47Z</wsu:Expires>

  </wsu:Timestamp>

  <wsse:UsernameToken wsu:Id="SecurityToken-

            8256de74-25f3-4531-92c7-be6e1d70367a">

      <wsse:Username>peter</wsse:Username>

      <wsse:Password Type="http://docs.oasis-open.org

            /wss/2004/01/oasis-200401-wss-username-

            token-profile-1.0#PasswordDigest">

            TZB7p6PGe0ltB+WrfusGLqb9zKo=

      </wsse:Password>

      <wsse:Nonce>

          UaTvnoFZHlEiuti6g/PtyA==

      </wsse:Nonce>

      <wsu:Created>

          2005-03-27T19:34:47Z

      </wsu:Created>

  </wsse:UsernameToken>

</wsse:Security>

 

You will notice that the message also has a creation time and a nonce added to the message. A nonce is a randomly generated number that is added to the message. The hash is created from the creation time and the nonce. Since the nonce is never repeated, a service can remember the nonce to foil replay attacks where the same message is sent a second time. The timestamp in the security header can be used to delimit the amount of time the security token is valid. You can set the TtlInSeconds property on the Security.Timestamp property.

 

The LoanService side is a little more complicated.  As discussed in the previous article, the configuration file has the allowRedirectedResponses element set to enable SOAP replies and faults to go to a different endpoint than the source. There are two pieces that must be implemented. The first is the authentication of the username/password. The second is the associated authorization. The authentication is handled by implementing a class derived from the UsernameTokenManager and overriding the AuthenticateToken method. Within this method you look up the user name and password. If you do not recognize the user name you throw a SoapException.  Otherwise you create a GenericPrincipal and associate it with the UsernameToken instance and return the password.

 

public class LoanServiceUsernameTokenManager :

                                 UsernameTokenManager

{

    protected override string AuthenticateToken

                                (UsernameToken token)

    {

       // look up password

       // don't ever even think about storing

       // passwords in real code

       GenericIdentity generic = new GenericIdentity

                                     (token.Username);

       token.Principal = new GenericPrincipal(generic,

                                                null);

       switch(token.Username)

         {

           case "peter":

               return "peterpassword";

           case "mary":

               return "marypassword";

           default:

               throw new SoapException("Invalid user",

                       SoapException.ClientFaultCode);

         }

    }

}

 

Here is the tricky part. You must return from this method exactly what was sent on the client side. In our example, the user entered plain text, so plain text must be entered here so that the WSE infrastructure can compare the hashes to see if they match. If they do not, an exception is thrown.  This gets very difficult if you wish to store the password with a one-way hash. To make that work,  you use the same one-way hash on the password on the client side to match what the system has stored. Check out Keith Brown's MSDN article for more information (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnwse/html/securusernametoken.asp) on how to get this to work securely. This article also explains why signing or encrypting with username tokens is not a good idea. Unfortunately, usernames and passwords often must be used. If passwords must be used, you should look into the possibility of using pass phrases which are much easier to remember.

 

You use a configuration file entry to notify the WSE infrastructure that you are using such a handler. You can use the WSE Settings dialog to simplify editing the configuration file. On the Security tab (see Figure 1) you can add a token handler with the Add button. Figure 2 shows how you edit the dialog. In the type edit field you enter the name of the class, and the assembly in which it resides. The other two entries are standard for user name tokens.

 


Figure 1 WSE Property Diagnostic Tab

 


Figure 2 Security Token Manager Window

 

The default behavior of the UsernameTokenManager is to validate against local Windows or Active Directory accounts.

 

The authorization is handled within the standard message handling. The appropriate token is located in the token collection associated with the security context for the message. The token is then processed as appropriate.

 

UsernameToken usernameToken = null;

foreach (UsernameToken token in

                    message.Context.Security.Tokens)

{

    usernameToken = token;

    break;

}

 

if (usernameToken.Username == "peter")

    result = "approved.";

else if (usernameToken.Username == "mary")

    result = "not approved.";

 

X509 Certificates

 

A much better protocol to use is X509 certificates. X509 certificates use public/private key encryption technology. An X509 certificate guarantees (through the digital signature of the issuing authority) that a given public key is associated with a user identity. The X509 protocol also contains a revocation protocol so that you can find out which certificates are no longer valid. Examples of reasons why a certificate is no longer valid might be that a person has left a company, or the associated private key has been compromised. Normally, the private key associated with the public key is stored separately from the certificate. For more information about using public and private keys, X509 certificates, and related issues, check out Brian Komar's book,  Microsoft Windows Server 2003 PKI and Certificate Security.

 

The Digital Signature example uses X509 certificates. A digital signature is used to detect if a message has been modified, or to identify the producer of a message, or to indicate that a message has been processed. The WS-Security specification bases its signing procedure on the XML Signature specification. Of course the WSE infrastructure takes care of all the details. The code in the file certificate.cs in both the LoanService and LoanRequest solutions shows how to get the certificate from a certificate store. The readme.txt file with the example has instructions for placing the sample certificates in the appropriate store.

 

The X509SecurityToken class represents the X509 certificate token. The X509Certificate class represents the X509 certificate itself. The X509Certificate instance is passed to the X509SecurityToken constructor. The token is then added to the token collection. The MessageSignature class represents the signed body of the SOAP message. The token that is passed to its constructor, in this case an X509 certificate, is used to sign the message. This signature is then added to the Elements collection of the security context for the message. When the message is processed by the WSE output security filter, the message will be signed with the private key associated with the X509 certificate.

 

X509Certificate clientCert = Certificate.

               GetCertificate(Certificate.clientCert);

X509SecurityToken clientToken = new X509SecurityToken

                                         (clientCert);

message.Context.Security.Tokens.Add(clientToken);

 

MessageSignature signedData = new MessageSignature

                                        (clientToken);

message.Context.Security.Elements.Add(signedData);

 

The server side must use the public key stored with the X509 certificate to verify the signature. To simulate real-life we check the "Allow test roots" and "Verify trust" checkboxes on the Security tab on the WSE Settings dialog (See Figure 1) for this example. For production code, make sure the "Allow test roots" box is unchecked. The WSE input security filter will verify the integrity of the signature for you.

 

On the server side we first look for a certificate that corresponds to an identity that we accept.

 

SecurityTokenCollection requestTokens =

                             context.Security.Tokens; 

bool valid = false;

foreach (SecurityToken token in requestTokens)

{

  X509SecurityToken tok = token as X509SecurityToken;

  if (tok != null)

  {

    cert = tok.Certificate;

    valid=Certificate.IsClientCertificateValid(cert);

    if (valid == true)

      break;

  }

}

 

Since we require that the SOAP body be signed, we check for this. If the message is not signed a SoapException is thrown.

 

bool foundSignedData = false;

foreach (ISecurityElement se in

                   message.Context.Security.Elements)

{

    if (se is MessageSignature)

    {

        foundSignedData = true;

        break;

    }

}

 

if (foundSignedData == false)

throw new SoapException("Message should be signed",

                      SoapException.ClientFaultCode);

 

The Encryption example is similar to this one. The EncryptedData class represents the encrypted part of the message. The outbound message is encrypted with the public key associated with the X509 certificate. The inbound message is decrypted with the private key associated with the X509 certificate. The WS-Security specification uses the XML Encryption specification for encrypting messages. The WSE security filters do the actual encryption and decryption for you.

 

One other part of the WS-Security specification is worth mentioning at this point. The specification defines the concept of reference id so that only parts of a message, or part of the security header block can be encrypted. For example, you can encrypt a security token. Different parts of the same message body could also be encrypted with different keys.

 

To do this you create a security token instance (i.e. X509SecurityToken). You can then extract its reference id, and use it in the EncryptedData constructor.

 

string tokenId=string.Format("#{0}",securityToken.Id);

EncryptedData encryptedToken = new EncryptedData

                           (encryptingToken, tokenId);

message.Context.Security.Elements.Add(encryptedToken);

 

Conclusion

 

This discussion does not exhaust what is in the WS-Security and its related specifications. Nor does it discuss tokens such as Kerberos or SAML, or other token types. It does provide a basic understanding of these specifications. The next article will use this understanding to add security to our Mortgage example.

 

Michael Stiefel is the principal of Reliable Software, Inc. where he does training and consulting in Microsoft technologies for companies ranging from startups to the Fortune 500. He is the co-author of “Application Development Using C# and .NET” published by Prentice-Hall. Go to www.reliablesoftware.com for more information about his development work and the training courses he offers.

 

All Content (c) 2000 - 2014 Reliable Software, Inc. All rights reserved.