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.