Chapter 4. Claiming and verifying identity with passwords

This chapter covers

  • Password-based authentication
  • Plain-text passwords
  • Digest authentication

As you saw in the previous chapter, the SOAP specification allows headers to be used for extending SOAP. WS-Security defines standard security headers for SOAP. In the first demonstration of WS-Security, we sent a username in a standard header. We also discussed the code behind that demonstration.

Sending the username along with the request is one way to claim identity. Most services require a user to establish his identity before his requests are served. This is because:

  • Security restrictions require that services be provided only to authorized users. While it is not always necessary to determine user identity to figure out if a user is authorized, most often it is. For example, a low-end B2B integration service may not care who placed a purchase order as long as it is received from a trusted partner’s network. Examples of services that require us to provide our identity information are of course everywhere.
  • Service logic requires the knowledge of who the user is. For example, if you are checking email, the email service needs to know whose messages it needs to return.

A user claims an identity by sending an identifier such as a username, email address, or digital certificate. The identifier, by itself, is insufficient for a service to establish the user’s identity. Only in limited situations can the service trust the user-provided identifier without any additional proof. For example, if the identifier itself is so secretive that only its legitimate owner can know it, a service can establish user identity based on the identifier. But this is usually not the case. Identifiers may be public knowledge or may be easily guessable. An identifier by itself, in general, cannot establish user identity.

In most situations, an identity claim consists of an identifier as well as proof of identity. The proof of identity can be some secret that only the identity owner and the service know. For example, a user may provide a preregistered password as proof of his identity. In this chapter, we explore the use of passwords for authentication in web services.

Several important questions come up when considering the use of passwords for authentication in web services: What kind of passwords should be supported? How does a service know what each user’s password is? How do we protect the password from being stolen in transit, or simply being reused by replaying a past message as is? In this chapter, we will provide answers to all these questions. We will show two different kinds of password-based authentication in web services: clear-text password and password digest. In addition, we will study how a service validates the password. As usual, we will be illustrating all these concepts using working code that implements the standards.

Let us start with the simple case of clear-text username and password as an identity claim.

4.1. Authentication with username and password

In this section, we will show how WS-Security supports the use of username and password for authentication in web service invocations. This section has four parts. The first part, with the help of an example, describes how WS-Security supports the use of usernames and passwords for authentication. The second part shows the code for a sample client that uses username and password-based authentication. The third and fourth parts describe how a server can validate the information in the identity claim. The third part introduces a framework (Java Authentication and Authorization Service [JAAS]) you can use to keep the validation code generic, and the fourth part shows a sample service that uses the framework to validate username/password claims against a file-based password store.

Let’s start by running an example.

4.1.1. Example: Username and password in WS-Security

If you recall in figure 3.5 we were sending a username along with the SOAP message. In this section, we will show an example where we will also send a password along with the SOAP message. When the username and password are correct, the server processes the request and returns the result. If not, the server returns a fault.

Running the examples

Follow the steps outlined in table 4.1 to configure the examples and execute them. These examples are used throughout the chapter. Even if you do not understand them right now, by the end of the chapter, you will understand the significance of the configuration, the layout of the code, and the choices in developing the applications.

Table 4.1. Steps that illustrate how WS-Security supports username- and password–based authentication

Step

Action

How to

1 Set up your environment. If you have not already set up the environment required to run the examples in this book, please refer to chapter 2 to do so. ant deploy should install all the examples.
2 Customize your JAAS configuration file. Go to the conf folder in the samples you downloaded previously when setting up the environment described in chapter 2. You will find two files: example3-passwd.txt and example3-jaas.conf. Open the latter and look for the following lines. com.manning.samples.soasecimpl.jaas.
FileBasedAuthenticator
requisite
filePath="d:/work/eclipse/soas_code/conf/
example3-passwd.txt"; Change the filePath attribute in this entry to point the path to example3-passwd.txt on your box.
3 Configure Tomcat’s JVM to pick up your JAAS configuration file. To make the location of the conf file available to Tomcat JVM, set the JAVA_OPTS environment variable to –Djava.security.auth.login.config=path-to-example3-jaas.conf-file.
4 Restart Tomcat server. Run shutdown and startup scripts (.bat files if you are using Windows and .sh files if you are on Linux/Solaris/OS X) found in the bin directory of Tomcat.
5 If it is not already running, start TCP monitor. Run ant tcpmon so that you can observe the conversation. Check the “XML Format” check box to allow tcpmon to format shown requests and responses.
6 Run the example. Run ant demo –Dexample.id=3. You should be able to view the request-response pairs going through the tcpmon console.

If everything goes well, you will see the screen shown in figure 4.1.

Figure 4.1. Screenshot of tcpmon illustrating the rejection of a request when an incorrect password is used.

The tcpmon screen will show you six SOAP calls. Of these, we will only focus on the second and third calls in this section. The first is simply a call to get the WSDL. The fourth and fifth calls are discussed later in this chapter. The sixth call is described in the next chapter.

Examining the web service calls

Here is what the second and third calls captured by tcpmon do. Both of them invoke a web service—one does so with the correct password, goodpass, and the other does so with an incorrect password, badpass (shown in figure 4.1). As you would expect, the service responds with the correct answer when given the correct password and a fault when given a wrong one.

In listing 4.1, we look at the headers in the second call captured by tcpmon, the one where we send the correct password.

Listing 4.1. Example of a security header with username and password
<soapenv:Header>
  <wsse:Security
    soapenv:actor="..." soapenv:mustUnderstand="0"
    xmlns:wsse=".../oasis-200401-wsswssecurity-secext-1.0.xsd">
    <wsse:UsernameToken>
        <wsse:Username>chap</wsse:Username>
        <wsse:Password>goodpass</wsse:Password>
    </wsse:UsernameToken>
  </wsse:Security>
</soapenv:Header>

The Security header entry is easy to understand. It contains the username and password in clear-text. Who adds these credentials on the client-side? How do they add them to the Security header entry? Who processes the entries on the server-side? How do they do it? Figure 4.2 shows an overview of the implementation of username/password-based authentication in this example.

Figure 4.2. Overview of an implementation of username and clear-text password-based authentication scheme

As the figure shows, a client-side handler called ClientSideWSSecurityHandler adds the username and password to the Security header; the first server-side handler, called WSSecurityUsernameHandler, takes the username and password out and creates a context. Then, the second handler, called JAASAuthenticationHandler, validates the claim (the username and password) stored in the context. If the validation passes, information on the authenticated user (subject) is stored in the context so that the end service (BrokerageService) can use it.

First, let’s see how the username and password are inserted by the client-side handler.

4.1.2. Implementing username/password scheme: client-side

In the previous chapter, we saw how a client application can set a username in the MessageContext that can be used by the ClientSideWSSecurityHandler to create the UsernameToken element in the Security header. Exactly the same technique is used here. This time, in addition to username, the client application, example3/BrokerageServiceTestCase.java, also sets the password in the context, as shown in listing 4.2.

Listing 4.2. Client code adding username/password to context before invoking the service
Call dii = service.createCall(portName, operationName);
dii.setProperty
  (Constants.USERNAME_MSG_CONTEXT_PROPERTY, username);
dii.setProperty
  (Constants.PASSWORD_MSG_CONTEXT_PROPERTY, password);
Float quote = (Float) dii.invoke(new Object[] {"MSFT"});

A client-side handler, ClientSideWSSecurityHandler, reads the username and password now available in the context and adds them to the Security header entry in listing 4.1. Listing 4.3 shows a snippet of this handler’s code.

Listing 4.3. Reading username/password from context and setting the UsernameToken in the security header entry

The code in listing 4.3 inserts the username and password fields into the header elements of the SOAP message. As shown in figure 4.2, the client-side SOAP engine now transmits the SOAP message, with the security header in it, to the service. The server-side now has the task of reading the identity claim and validating it.

4.1.3. JAAS: A generic framework for authentication

When using a password to authenticate a user, an application needs to verify the password presented by the user. For this purpose, the application can consult a password store. Historically, there have been several locations where applications stored passwords: In Unix, passwords were traditionally stored in the file system. Database applications store passwords in the database itself. Most modern applications include support for accessing the passwords from directory using a protocol named Lightweight Directory Access Protocol (LDAP).

How does the service consult a password-store? As shown in figure 4.3, the service can use a store-specific API. This approach is simple and direct. Most importantly, it works.

Figure 4.3. Simplest way to implement authentication: the application consults a specific password store.

Still, most architects do not prefer to use a store-specific API to consult the password store because it ties the application to a specific store. For example, if we write an application today to access passwords from a file, we will need to revamp a portion of that application tomorrow when we need to access passwords from a database.

The solution is simple: We need a standardized API to access any password store. Pluggable Authentication Module (PAM) provided such API for use on UNIX. In Java, this API is specified by JAAS. JAAS lets us configure the application to use any password store as long as there is a Java class that implements the LoginModule interface for that store. There are several LoginModule implementations available.

Figure 4.4. JAAS provides a standard API for applications to authenticate against any password store. Administrators can configure JAAS to use arbitrary password stores. Interaction with each password store happens through a store-specific JAAS LoginModule implementation.

JAAS turns out to be the right choice to implement authentication for username/passwords in web services. Web services are often made available for use by partners; as a result our password stores may evolve over time, extending to other partners and external users. Any implementation that does not support such evolution will not work well in the integration world.

Any application using JAAS will have the following three parts:

  1. Code that invokes JAAS APIs to carry out authentication, as a part of the application.
  2. JAAS configuration to specify the password stores to authenticate against.
  3. Store-specific implementation of JAAS LoginModule API for each password store specified in the configuration.

In the rest of this section, we will look at each of these parts with annotated code samples.

Application code to authenticate using JAAS

Listing 4.4 uses JAAS API to log in. This piece of code is from the file JAASAuthenticationHandler.java in the example3 folder.

Listing 4.4. Logging in using JAAS API

To use JAAS for authentication, we need to initialize JAAS first. This is done by creating an instance of javax.security.auth.login.LoginContext . The constructor of LoginContext takes two arguments. The first is the name of the application that is invoking JAAS for authentication. JAAS uses this name to look up its configuration file and load the login modules configured for the application.

To understand the second argument, we should ask ourselves how JAAS can get the username and password submitted by the client. We are not explicitly passing them to JAAS anywhere in this code. Instead, through the second argument of the LoginContext constructor, we are passing an object whose methods can be called by JAAS to get the username and password. This generality is required to support a diverse set of authentication mechanisms. For example, a mechanism might want to challenge the user to present his date of birth or answer a secret question. JAAS takes care of this possibility by specifying that an implementation of the javax.secuity.auth.callback.CallbackHandler interface be provided when constructing a LoginContext instance. JAAS will call back this implementation to get hold of user credentials as part of the login process.

Once JAAS is initialized by constructing a LoginContext instance, the application can call the login method of LoginContext to carry out the authentication . At this time, JAAS uses the callback handler we supplied in to get the username and password. If the login fails, JAAS throws an exception.

If the login succeeds, JAAS authentication module provides a Subject that encapsulates all the information about the user who just logged in. See the callout for more information on what a Subject instance contains. In this case, we are saving the Subject instance in the message context so that subsequent handlers and the service itself can use it to determine who called the service.

 

Note: What is in a javax.secuity.auth.Subject instance?

Think of Subject as a collection of all the information about a user or an entity. For example, a Subject may have a username, a password, and a list of groups to which the named user belongs. A Subject can have one or more identities, each known as a security principal. Principals are implemented in Java as instances of classes implementing the java.security.Principal instance. Think of a Principal as a name associated with the Subject. For example, a UsernamePrincipal may specify the name of the user, whereas a MemberOfGroupPrincipal may specify the name of a group to which the user belongs. A Subject can also have one or more security credentials, some that can be shared publicly and some that need to be private. For example, a password is a private credential.

 

There is one missing piece in the preceding code: the callback handler that JAAS needs to extract the username and password submitted by the user. In interactive applications, these handlers might pop up dialog boxes to ask the user for the name and password. In the example illustrated in figure 4.2, the username and password submitted by the client as part of the Security header entry are extracted and saved in MessageContext by a handler named WSSecurityUsernameHandler. The JAASAuthenticationHandler that is subsequently invoked can use a callback handler that will read the username and password from MessageContext instead of asking any external agent. Listing 4.5 shows the code that implements such a callback handler.

Listing 4.5. JAAS CallbackHandler implementation to read username and password from MessageContext

The constructor of MessageContextBackedCallbackHandler is given a MessageContext instance that can be looked up for username and password values when asked by JAAS framework. During authentication, the handle method is called by JAAS with a list of callbacks to be answered. If the callback is NameCallback —that is, if JAAS asks for the username—the callback handler will submit the username available from the message context. Similarly, if the callback is PasswordCallback , the callback hander will submit the password available from the message context.

Now you know how an application can invoke JAAS for authentication. Where exactly does the application specify what JAAS should do as part of the authentication process? For example, what password store(s) should JAAS consult during authentication? Should JAAS do any additional checks such as making sure that the request originated from a trusted network? All this information is specified by the JAAS configuration file. Let us see next how JAAS can be configured to authenticate against one or more stores for an application.

Configuring JAAS

JAAS needs to be provided with the location of a configuration file. Revisit table 4.1, where we have given the instructions to run the examples in this chapter. You will see that in step 3, we set the java.security.auth.login.config system property of Tomcat’s JVM to the path of our JAAS configuration file. A part of the configuration file for this example, conf/example3-jaas.conf is shown in listing 4.6.

Listing 4.6. Extract from a JAAS configuration file

Here is how to read this configuration snippet. For the application named in , JAAS must use the login modules whose class names are listed as shown in . For each login module listed for the application, a flag indicates how JAAS should combine the module’s result with the results of other login modules that are also listed as part of the application’s JAAS configuration. Possible values of this flag and their meanings are:

  • required This module must succeed if a login has to succeed. Regardless of whether this module succeeds or fails, subsequent modules will be tried anyway. This is useful if we have subsequent modules that should always be called, such as audit modules that record all login attempts.
  • requisite Same as required, except that subsequent login modules will not be tried if this login module fails.
  • sufficient Login is considered successful if this module succeeds; modules subsequent to this one are not tried if this one succeeds.
  • optional Regardless of the result of this login module, subsequent modules will be tried.

Figure 4.5 summarizes the behavior you would see for each these flags.

Figure 4.5. Flow charts depicting how the result of a JAAS login module can be combined with the results of the rest of the modules configured for an application.

Each login module listed for authentication in an application may need its own customizations. JAAS facilitates this by allowing for each login module an arbitrary number of options in the form of name=value pairs. For example, in in listing 4.6, the path to the password file is set for the FileBasedAuthenticator login module.

Now you know what JAAS configuration files look like. For each application, you specify one or more login module classes, specify whether each of the modules is required, requisite, sufficient, or optional, and set any options required by the module.

We will next see how to implement a JAAS login module.

Implementing JAAS login module

The way we’ve explained it so far, any server can load a LoginModule to authenticate against any store as long as the module implements a specific API assumed by JAAS. LoginModule implementations already exist for most password stores in use; we do not have to code them in general. Sun’s Java Development Kit (JDK) provides frequently required modules such as the ones for checking against an LDAP store and using native authentication mechanisms in UNIX/NT. See the javadocs for the com.sun.security.auth.module package for more details.

In our example introduced in 4.1.1, we could have used such readily available implementations. Some of the authentication mechanisms proposed by WS-Security use special techniques that are not supported by the JAAS module implementations in Sun’s JDK. We will show you in this section how to implement a simple JAAS LoginModule. We will enhance it as our requirements grow.

Figure 4.6 shows the sequence of interactions between the application and login modules via JAAS APIs. We saw previously that an application starts off the login process by constructing a LoginContext with the application name and a callback handler instance as arguments. Initially, JAAS loads the login module classes configured for the named application, instantiates them using default constructors, and calls initialize on each of them. As a part of the initialization, JAAS supplies the login module instance with the Subject to populate, the call-back handler to use during login, configured options, and a container for sharing login state with other modules.

Figure 4.6. Sequence diagram showing the inner workings of JAAS.

The application uses the constructed LoginContext to login. Since more than one module might be configured, the login in each module is a two-step process. As a first step, the JAAS framework calls the login() function of each login module configured. The net result of these calls will be a success or a failure to login. If the result is successful the commit() function is called on each module. If it is a failure, the abort() function is called on each. Login modules should not fill in the principal/credential information into Subject until the commit() function is called. Whatever principals or credentials are added to the Subject by the module should be cleaned up when logout() is called.

Listing 4.7 is the relevant snippet of the code that handles the login portion, from the file FileBasedAuthenticator.java in the folder named jaas. The reader is encouraged to look at the file to understand how to set the subject in commit() and clean it up in logout().

Listing 4.7. Code extract from a JAAS login module

Upon a call to login(), the login module creates an array of callbacks and populates it with one call back per credential it requires for authentication. In this example, the login module creates two callbacks, one for username and one for password .

The callbacks are implementations of the interface javax.security.auth.callback.Callback. The first argument is the prompt to the user. In our application, we do not need it. But, as we explained, JAAS is a general-purpose framework, to be used even in GUI applications, which is where this prompt is used. The PasswordCallback takes additional argument whether to echo responses to the screen.

If we want the application to ask for additional information, all we need to do is provide additional callbacks. For example, we implemented NonceCallback in our example for later use. Of course, the callback handler should understand what is required. For most username and password schemes, these simple callbacks would suffice.

The callbacks are passed to the application’s CallbackHandler so that it can provide the credentials required for authentication. You have already seen a sample implementation of CallbackHandler in listing 4.5. If the application’s CallbackHandler cannot provide a value for one of the callbacks, it will throw an UnsupportedCallbackException . When that happens, the login module throws a LoginException in turn to indicate that login has failed. Otherwise, the login module reads the credentials and provided by the application’s CallbackHandler and verifies them to see whether the identity claim is genuine or fake.

Congratulations! If you followed along so far, you not only know how to use JAAS in an application, you also can write a JAAS login module implementation for your own password scheme. To summarize what we learned in this section: we can use JAAS to work with any password store. We can even change the choice of password store with a simple configuration change without affecting the rest of the system. For most popular store choices such as LDAP, we can use readily available login modules. For other stores, we can create our login module implementations. In the next section, we will see how server-side handler can use JAAS to verify the username and password.

4.1.4. Implementing username/password scheme: server-side validation

In this section, we will see how we can use JAAS to validate user credentials. Since we have covered JAAS already, we are going to provide only the web service-specific APIs here.

In the examples here, we will use JAX-RPC handlers to process the username and password, just as in the previous chapter. Recall that the username and password come as a part of the UsernameToken element in the Security header. We have the option of writing a single handler that extracts these credentials and verifies them using a password store. Since we already demonstrated in the previous chapter a handler that saves credentials from UsernameToken in the message context, we will use that handler here, too. We will write a second handler that takes the username and password from the context and authenticates. This strategy will allow us to replace the logic we use to fetch identity claims and the logic needed to verify the claims independently of each other. Figure 4.7 provides an overview of the solution.

Figure 4.7. Overview of the server-side implementation

Let’s look at the first handler, WSSecurityUsernameHandler. It is exactly the same as listing 3.3 except in listing 4.8 the following lines have been added. As before, we removed the exception logic to make the code simpler.

Listing 4.8. Extract from WSSecurityUsernameHandler: Reads username and password from the WS-Security header and saves them in the MessageContext

We first get the password from the Password element in the UsernameToken header entry . As we need to somehow communicate the username and password to the downstream handlers and service, we use the message context to do that, by setting properties . Finally, we remove the header, since we are done processing it .

The next handler, JAASAuthenticationHandler, is entirely new. It needs to take the username and password and verify them. As explained earlier, we can verify them in multiple ways. Ultimately, the server needs to convince itself that the message could have come only from the person identified by the username in the header. To make the code independent of password store and its APIs, we will use the JAAS that accesses any password store with JAAS LoginModules, as follows:

public class JAASAuthenticationHandler
    extends ExtendedGenericHandler {
  //...
}

Just like WSSecurityUsernameHandler, this class extends ExtendedGenericHandler. At initialization time, it needs to read in the application name to use when using JAAS for authentication. As we saw in the last chapter, JAX-RPC provides handlers with the opportunity to read in handler configuration info in the init method.

All we’re doing here is accessing the handler configuration and reading the application name to use when initializing JAAS.

The next method in the handler is handleRequest. It takes the username and password from the message context and validates via JAAS, as explained in detail in the previous subsection on JAAS.

So far, we have seen how to do authentication with username and password. There is one crucial property of the mechanism we have studied so far that should catch the attention of security practitioners: we exchanged the username and password in clear-text. We all know that it is a bad idea to transmit user credentials in clear-text. Anybody who is snooping on the network can get usernames and passwords and reuse them with the same service or different services. In the next section, we’ll look alternatives to transmitting passwords in clear-text.

4.2. Using password digest for authentication

Although there is a simple sledgehammer solution to the clear-text password problem, it is not the right kind of solution for SOA for the reasons discussed in chapter 1. The sledgehammer approach is to use transport-level security by routing every SOAP message through SSL/TLS. That would result in encryption of the entirety of messages exchanged, thus protecting the password from a man-in-the-middle (MIM) attack. As we discussed in chapter 1, transport-level security is not good enough when there are more than two parties involved in a message exchange and when different parts of a message should be encrypted differently. SOA is often used for integrating multiple applications in the context of a single high-level business process. In such cases, blanket encryption using SSL/TLS is not a feasible solution.

So, it is natural to ask if there is a way to hide the password without encrypting the entirety of a message as in SSL/TLS. This scheme must not only foil any attempt to extract the password, but also disallow simple replay of the whole message. Password digest-based authentication accomplishes these goals.

In this section, first we will introduce the theory behind the password digest-based authentication. Next, we will show how to implement that scheme, first on the client-side then on the server-side. We will use JAAS to implement the validation on the server-side and reuse the code from the previous section.

4.2.1. How password digest authentication works

The problem with sending the password in plain text is that the MIM can grab it. What if we do not send the password? What if we send something else instead of a password to prove that we have the password? For example, instead of the password p, we can send f(p), where f is a function that transforms password p. The receiver can recompute f(p) using its knowledge of the user’s password[1] and match the result with the value submitted by the user. In this scheme, we’ve eliminated sending the password, but the MIM may still steal the password if he can compute p from f(p). That is, from the proof of the password, the attacker may get the password itself.

1 Later we will account for the fact that password stores on the server-side seldom store the user’s password as is. Instead, they store each password p in a transformed form p' from which it is not easy to recover the original password. The receiver can never compute f(p). It can only compute f(p'). For this reason, assume that we mean p' wherever we use p in this section.

We can solve this problem by using one-way functions. One-way functions safeguard the password from intruders. These functions do not have easily computable inverse functions. That is, given f(p), for all practical purposes, the attacker cannot compute p. Secure Hash Algorithm-1 (SHA-1) is an example of such a function.

Still, the scheme is not completely secure. The MIM need not compute p from f(p) to send a fake message. He can simply use f(p) as is. So, we need to somehow change f every time we use it so that f(p) cannot be replayed. So, what we really need is a way to change the one-way function each time we send the password.

We can get such a function by taking in another argument. That is, instead of sending f(p), we send f(p,n), where n is called nonce. It is a number that is never repeated—a number that is only used once—nonce.

The use of nonce introduces complexity on the receiver side. A simple function can be agreed upon a priori by the sender and the receiver. How can they agree upon the nonce, given that it must change every time? One obvious solution is to send the nonce (n) along with f(p,n).

Surely, a MIM can also see the nonce n when it’s sent along with f(p,n). That means he too can send the same nonce n and f(n,p). The situation is no better than sending f(p), unless the receiver accepts a nonce only once. Any attempts by the MIM to replay the same nonce n and f(p,n) will fail.

Can a receiver endlessly remember all the nonce values it has seen? Any such design imposes an undue burden on the receivers. We can make the scheme easier for receivers if we add the current date to the nonce. That is, we send f(p,n,d) instead of f(p,n).

Let’s examine why this design works. The receiver can remember all the nonce values it has seen that day. If a MIM attempts a replay attack the same day, the receiver will detect it, as there is a repetition of the nonce. If MIM attempts a replay attack on a different day, f(p,n,d) will not match, as the date d is a part of the digest computation.

In practice, a day may also be too long a timeframe to remember all nonce values used within that time so a timestamp t is used instead of date d. Requests older than a short while (say, five minutes) are rejected by the receiver. The value f(p,n,t) is commonly referred to as a password digest.

The password digest authentication process is shown in figure 4.8.

Figure 4.8. Authentication with password digests

As you’ll learn next, WS-Security combines all these ideas in password digest-based authentication.

4.2.2. Password digest authentication in action

A password digest is computed as a function of the nonce, timestamp, and the password. As long as the time disparity between clocks on the server and client-sides is not too much, this scheme works well. Look back at the example you ran when reading section 4.1.1; if you look at the fourth and fifth calls (the first was a WSDL GET; the second and third relied on clear-text passwords) captured by tcpmon, you will see how a WS-Security UsernameToken can be used to carry a password digest.

Listing 4.9. Example of a security header with a username and password digest

In listing 4.9 the Password element in UsernameToken is identified to be a PasswordDigest by its Type attribute. WS-Security’s UsernameToken profile specifies the password digest function f as follows.

f(p,n,t) = Base64(SHA-1(n.t.p))

where . is the concatenation operator, p is the UTF-8 representation of the password, n is the nonce (raw bytes, not the base64-encoded representation you see in ), and t is the UTF-8 representation of the timestamp (in UTC) in the format shown in . SHA-1 is a popular one-way hash function. UTF-8 and base64 are the names of encodings; see the corresponding callouts for more.

 

Note: What is UTF-8 encoding?

How are characters represented digitally? Digital computers look at all data as 0s and 1s. To represent characters in digital form, we need to convert them into numbers. Character set standards such as ASCII and Unicode standardize the bits used to represent characters. For example, in ASCII, character A is represented using 65, B is represented using 66, and so on. A password such as “ABC” is really 01000001 01000010 01000011 when stored digitally. The number assigned to each character in a character set standard is called its code position. Some character set standards only define the positions for a small number of characters, whereas others define the positions for a large set of characters. For example, ASCII defines 128 code positions, whereas Unicode defines the code positions for thousands of characters. Both of them are identical if we limit ourselves to the first 128 code positions defined in Unicode.

Large character sets will result in bloated file sizes if used naively. For example, as Unicode has a large number of characters, if we use 4 bytes to represent each character in a text file, a file consisting of characters from just the first 128 code positions will use four times the bytes required to store equivalent ASCII data. This problem can be solved by using variable-length encoding schemes, where the number of bytes used to represent a code position is not fixed. UTF-8 is one such encoding scheme. It uses 1 to 4 bytes to represent each Unicode code position, based on the code position. It is designed such that a UTF-8 encoded file consisting of simply ASCII characters is identical to an ASCII-encoded file consisting of the same characters.

What is base64 encoding? Base64 can take any data and convert it into printable ASCII. It is an easily computable function that is easy to reverse, too. If we want to send binary data in XML, the preferred choice is to convert it into base64. When we encrypt or compute digests, typically, we produce binary data, even though we start from printable ASCII. In most of the code examples in this chapter, you will see that we use base64 to encode such binary data before putting it into XML.

 

When computing password digests, it is important to be clear about the encoding used to represent the characters in the password. WS-Security mandates the use of UTF-8 when adding a password to the digest computation.

Let us now take a look at how we can implement the password digest scheme; that is, send the password digest as a header entry from the client-side and understand it and validate it on the server-side. Figure 4.9 provides an overview of our implementation strategy.

The left side of figure 4.9 shows how the client-side produces the header entry for the password digest using a handler. The handler inserts the header entry into the message as shown in the middle of the figure. The right side of the figure shows how the server takes the digest and passes that information via MessageContext to the JAASAuthenticationHandler. The JAASAuthenticationHandler, as the name suggests, uses JAAS to validate the password digest.

Figure 4.9. Overview of a username/password digest-based authentication scheme.

We will elaborate on each of the components in the figure in the following subsections: first the client-side then the server-side.

4.2.3. Implementing password digests: client-side

In this section, we’ll see how password digest–based authentication is implemented in ClientSideWSSecurityHandler.java, a client-side handler. The handler code we describe here extends the code you have seen before for clear-text password-based authentication. Table 4.2 shows the implementation logic broken down into steps.

Table 4.2. Client-side implementation logic for password digest based-authentication

Step

Action

Implementation strategy

1 Initialize. Create a nonce generator using java.security.SecureRandom. Read configuration to figure out how many bytes we want to use in nonce. Create an instance of java.security.MessageDigest. Create a javax.xml.datatype.DatatypeFactory instance needed to instantiate a javax.xml.datatype.XMLGregorianCalendar instance for the timestamp portion.
2 Generate a nonce. Use the nonce generator created during initialization to generate a nonce.
3 Take the current time as the timestamp. Use the XMLGregorianCalendar instance available from XML DatatypeFactory to format current time in UTC as required by the WS-Security specification.
4 Convert password to the form stored on the server-side. Assuming that the password on the server-side is stored in SHA-1 hashed form, use the MessageDigest instance created in step 1 to do the same hash on the client-side as well.
5 Use the nonce, timestamp, and the password to generate a digest. Use MessageDigest instance to create a digest of password (in the form stored on the server-side), nonce, and timestamp.
6 Add the password digest using a UsernameToken element in the Security header entry. Use DOM and SAAJ APIs to add the required elements in the Security header entry

Let us now discuss each of these steps in detail.

Step 1: Initializing the handler

In the init() method of ClientSideWSSecurityHandler, we initialize the objects needed when handling messages. The objects initialized include a nonce generator, a message digester, and a date formatter (listing 4.10).

Listing 4.10. Initialization code in ClientSideWSSecurityHandler

Here’s what we do in this code. We create a random number generator, an instance of java.security.SecureRandom , which we will use later for generating nonce values. The algorithm we use for random number generation is SHA1PRNG, a SHA-1 based pseudo-random number generation algorithm.

We create a digester object that can compute SHA-1 . In Java, such a facility is built into the class java.security.MessageDigest.

We create an xmlDatatypeFactory that will be needed later to format timestamps in the form that WS-Security mandates.

Since the nonce can be any number of bytes, we read it in as a configuration parameter .

Step 2: Generating the nonce

Listing 4.11 illustrates how we generate the nonce. We create a byte array of the size read from client configuration in step 1 and use the nonceGenerator that was also initialized in step 1 to generate a nonce of the configured size.

Listing 4.11. Generating nonce
byte[] nonce = new byte[numBytesInNonce];
nonceGenerator.nextBytes(nonce);

The nonce to use in password digest is now available as a byte array.

Step 3: Generating the timestamp

Next we need to generate the timestamp in a neutral time zone so that the digest scheme can work across time zones. Listing 4.12 shows the code that generates a UTC timestamp in the format specified by WS-Security.

Listing 4.12. Generating the timestamp
XMLGregorianCalendar now =
     xmlDatatypeFactory.newXMLGregorianCalendar
     (new GregorianCalendar
         (TimeZone.getTimeZone("UTC")));
String created = now.toXMLFormat();

The timestamp to embed in the password digest is now available in the right format.

Step 4: Getting and transforming the password

To compute the digest, we need three pieces of information: the nonce, the timestamp, and the password. The first two are taken care of in steps 2 and 3, respectively. Let us now focus on the password.

Recall that the client sets the username and password in the message context in order to make them available to handlers. Our handler can get the password from the message context. Since this code is virtually same as listing 4.3, we are not repeating it here.

There is one catch that we need to address here. On the server-side, passwords are not stored in their original form to reduce the risk of unauthorized access. They get stored in a cryptic format that cannot be decrypted easily. The exact format depends on the store the server-side uses. Therefore, the digest computation code on the server-side cannot get the original password from the store. It can only get what is stored in the password store. If password digests are to work in practice, the client also has to use the same type of password for digest computation as the sender.

In our example, we are going to assume that the password is stored on the server-side as a base64-encoded SHA-1 hash of the password. So, our client code also has to obtain a base64 encoded SHA-1 hash of the password before computing the digest.

The code in listing 4.13 takes the bytes that the password would consist of in UTF-8 encoding, creates a SHA-1 hash of that, and encodes the result in base64. The final result is used in place of the password for computing the digest.

Listing 4.13. Generating the transformed password
//as the service only has a hashed version of the
//password, we need to hash it before including it
//in the digest
byte[] utf8Password = password.getBytes("UTF-8");
byte[] sha1Password = digester.digest(utf8Password);
byte[] base64EncodedSHA1Password =
       Base64.encodeBase64(sha1Password);

The password is now available in the form it should be in when computing the digest.

Step 5: Generating the digest

Finally, we have all the components for computing a digest: nonce, timestamp, and the right form of the password. We compute the digest using the code in listing 4.14.

Listing 4.14. Generating the digest
digester.update(nonce);
digester.update(created.getBytes("UTF-8"));
digester.update(base64EncodedSHA1Password);
byte[] digest =
        Base64.encodeBase64(digester.digest());

In these statements, we add the nonce, timestamp, and password to the digester. In the final statement of the code, we generate the digest and encoding it in base64.

Step 6: Attaching the headers and sending the message

The last step in the client-side handler is to attach the headers to the message. We showed in listing 4.3 how to add elements to the headers. The only trick here is to add the attributes to the elements, too, signifying the encodings. For example, the following snippet sets the encoding type to be base64 for nonce:

nonceElement.addAttribute
  (encodingTypeAttrName,
   Constants.WS_SECURITY_BASE64_ENCODING_TYPE);

Please read through the file example3/ClientSideWSSecurityHandler.java for a full working example. As you may recall, chapter 3 showed you the code to insert header entries into the SOAP message. This file will show you how to write client-side handlers that insert the right headers to claim identity.

In this section, you have seen how the client inserts the password digest as the header entry. As illustrated in figure 4.9, we need to take this header entry to the server-side and validate the digest. In the next section, we will see how we can do that.

4.2.4. Implementing password digests: server-side validation

The server-side handler essentially has to repeat the calculations made by the client. In addition, it needs to take care that the nonce is not repeated, as we discussed earlier. In this section, first we will show the algorithm for the server-side handler. Next, we will discuss the code that implements the tricky portions of the algorithm. We will also present cues to understand the complete implementation. Look at figure 4.9 to get an overview of our implementation approach.

Table 4.3 shows the implementation logic broken down into steps.

Table 4.3. Server-side implementation logic for password digest-based authentication

Step

Action

Implementation Strategy

1 Gather the digest information in security header entry. We use a JAX-RPC handler, WSSecurityUsernameHandler, to gather all the required data from the security header and put it in the message context for use by the downstream handlers.
2 Invoke JAAS APIs for authentication. We use a JAX-RPC handler, JAASAuthenticationHandler, to invoke JAAS APIs for authentication. Callbacks of JAAS modules for information such as the username, password digest, nonce, and timestamp are answered by MessageContextBackedCallbackHandler using the information stored in the message context during step 1.
3 Use custom JAAS login modules to do digest-based authentication. We configure JAAS with custom login modules that verify the password digest and check on nonce/timestamp to prevent replay attacks.

Let us now discuss each of these steps in detail.

Step 1: Gathering digest info in the security header entry

First, in the WSSecurityUsernameHandler class, we extract the username and password elements from the Security header just as in listing 4.8. The only additional code needed here is to get the nonce and the creation timestamp. Listing 4.15 shows the additional code used for reading the nonce. The creation timestamp is simply read in as a string and stored in message context, just like a username. That code is not shown here.

Listing 4.15. Reading the nonce from the security header

We first examine the EncodingType attribute on the Nonce element to check whether the nonce is base64-encoded. If so, we decode the base64 encoding of the nonce and store it in message context .

Step 2: Invoking JAAS for digest-based authentication

After we get the credentials (username, nonce, timestamp, and password digest), how do we validate them? There is an easy approach: we simply repeat the calculation done on the client-side and compare the digests. Since we already introduced a JAAS module (FileBasedAuthenticator, shown in listing 4.7) for password verification, we are going to extend it to validate the digest as well. A JAX-RPC handler named JAASAuthenticationHandler will invoke this login module and others described in the next section via JAAS.

The JAAS login modules for digest-based authentication will need to get the nonce and timestamp along with the username and digest. So, as shown in listing 4.16, we extend the MessageContextBackedCallbackHandler to supply those values as well.

Listing 4.16. Handling the callbacks for digest authentication

Since we use the same custom JAAS module for both clear-text passwords and digest-based authentications, one of the callbacks our custom JAAS module makes is for the password type . In the case of digest authentication, our JAAS module would of course need to know the nonce and timestamp. It asks for these using nonceCallback and timestampCallback . These callback classes are standard classes bundled with JDK. You can take a look at the implementation of these classes in the jaas folder.

The actual verification of the credentials happens in the JAAS module. It is more involved than verifying the password, as we have to deal with nonce timeouts and so on. We will take a look at it next.

Step 3: Custom JAAS modules implementation for digest authentication

Let’s see what we need to do to validate the credentials. All this code is implemented in the jaas folder:

  1. Make sure that the timestamp is within tolerance limits; reject otherwise This code is implemented in the file TimeCheckJAASModule.java.
  2. Make sure that the digest matches; reject otherwise This code is very similar to the computation on the client-side and is available in the file FileBasedAuthenticator.java.
  3. Make sure that the nonce is new; store if it is or reject otherwise This code is available in the file NonceCheckJAASModule.java.

Each of these three different functions is implemented as a separate login module under JAAS so that we can use each of these login handlers in other situations as well. For example, we can imagine a scenario where we allow a user to log in only if the timestamp is within the limits. Such a scheme may be useful in some time-sensitive web requests.

In the configuration file, we specify that in order to log in, the user will have to pass all the login requirements—that is, all the JAAS modules will have to allow the user in.

The configuration file looks like listing 4.17.

Listing 4.17. JAAS configuration for digest mechanism

First, we configured TimeCheckJAASModule to check the timestamp . Since this is a requisite module, if it fails, the whole login process fails and the user’s credentials are rejected. Notice that we can send the required options to that module via the arguments.

Next, we configured the FileBasedAuthenticator module to compute and verify the digest. As with , this is a requisite module.

Finally, we configured NonceCheckJAASModule . If this module succeeds we do not have to check any further.

A word of caution: in practice, it may be expensive to have so many login modules. Business scenarios may make such login modules necessary. For example, when two businesses merge, you may want to use the existing JAAS modules and configure them to allow employees of both organizations access to certain resources. We merely want to illustrate the technology so that you can use it in appropriate business scenarios, instead of suggesting it as a best practice.

Now, we will see how these modules are implemented. Since all these modules are quite similar, we will examine only one: NonceCheckJAASModule.

Implementing NonceCheckJAASModule

This module needs to store nonces so that it can detect reuse of nonce values. In practice, we would want this store to be persistent so that even if the server is restarted, the application is still secure. If the server restart takes longer than the tolerance limit, we do not need to persist the nonce values; they would have expired anyway. In our illustration, we are only going to store them in memory using NonceCache, an implementation of HashMap.

Before using this module, JAAS framework initializes it by calling the initialize method, as shown in listing 4.18. In this method, we read in the options provided for this module in the JAAS configuration. We also create a cache for nonce values. We read three configuration options in this intialization code.

Listing 4.18. NonceCheckJAASModule initialization

We first check whether the configuration mandates the availability of a nonce in every authentication request .

We read the name of the cache to use for storing nonce values . By default, if a name is not provided, the cache name is assumed to be global. This option allows concurrent use of this login module by different services in the same JVM. Each service can set up its JAAS configuration to use a different cache name.

Finally, we read the time to live (TTL) value for each nonce value in the named cache . If a TTL option is not set, a default is assumed.

If the cache is not there under the required name, a new cache is created with the right expiry period.

The code for login follows these guidelines: first get the required information from the calling party; then make the decision based on the obtained information. The code in listing 4.19 gets the information via callbacks.

Listing 4.19. Getting the nonce information from the user

Here, we create the required callback that is used to communicate with the calling party. The class NonceCallback is an implementation of the interface Callback and is available in the folder jaas.

If the calling party cannot handle the nonce callback function we generate a login exception . As explained earlier in the section on JAAS, the client for the JAAS module should implement the callback handler such that it understands and handles all the callbacks required by the JAAS module.

Next, we need to extract the information from nonce callback and verify that it is not a repeat, as shown in listing 4.20.

Listing 4.20. Verification that nonce is not repeated

In this code, we first get the nonce from the callback . The callback function stores it when the callback handler provides it with the nonce.

We look up the nonce cache for repetition of a nonce . As byte arrays are not good hash keys, we are using a base64 version of nonce as the key .

If the nonce is not a repeat, we store it in the cache and return success.

The other two modules are very similar to this one. For example, the time check, JAAS module gets different initialization options such as tolerance limit and uses a different callback handler and different logic to decide if it should let the user in.

4.3. Is password authentication the right solution for you?

In this chapter, we studied password-based authentication claims. That is, the user supplies the password as proof of his identity. We also studied a variation of the password—a password digest scheme. Can you use these authentication mechanisms for your solutions?

First let us understand why password-based authentication schemes are good. Using a password to access a system is very common. In fact, most of the web uses password-based schemes to allow users access to web resources. As such, users find it natural to use a password for access.

Moreover, passwords are easy to implement. After all, we have been using password-based schemes in computer systems long enough to acquire libraries, processes, and expertise to manage the systems. Many companies have processes to deal with lost passwords and so on.

Plain-text passwords are applicable in limited situations. For example, in an intranet, where there is no chance of snooping on the network, plain-text passwords pose no harm. Or, if the traffic itself is encrypted, for example via a VPN, there is no need to look beyond plain-text passwords for simple authentication. Or, some older systems may be integrated with only plain-text passwords.

If our situation does not qualify for plain-text passwords, we can still use digest authentication. In the next subsection, we examine why digest passwords are secure.

4.3.1. Why is the digest scheme secure?

Now that we have seen the digest scheme in action, let us see why it is secure. To understand why, let’s look at the actions of the potential attacker. He is listening on the wire and grabs the nonce, timestamp, and the digest. Here are the possibilities:

  • He sends those three exactly as they are within the tolerance. The server, since it remembers the nonce, will consider it as a replay attack and reject the credentials.
  • He sends these three pieces after the tolerance limit has expired. The server will not remember this nonce. Since the timestamp is outside the tolerance limit, the message is rejected as a possible replay.
  • He modifies the timestamp so that it falls within the hour. The server will compute the digest and match against this digest. Since the timestamp differs, the digests also will differ, causing the message to be rejected.

All these scenarios work if only the digest function has some nice properties:

  • It should be fast to compute. Every message needs to carry the password digest—it needs to be cheaply computed.
  • It should be difficult to compute the inverse for. While it is possible to compute the password by the brute-force method of trying every password, such a method should not succeed in a reasonable amount of time.
  • It should not map different inputs to same output; that is, the digest for two different passwords should not be the same. While this property is not a real requirement for password digests, we will see the need for this property in later chapters where we use digests for ensuring message integrity.

One of the standard functions used in digest computation is SHA-1. This function produces a digest of 160 bits from the input. Since the input is really three terms—nonce, timestamp, and password—we create the combined input as a concatenation of nonce, timestamp, and password in that order. The order allows for varying the length of the password without any padding.

Does SHA-1 stand up to our requirements? It is fast to compute and the inverse is difficult to compute. As with any function, since it coerces large inputs to small outputs, it will map several different inputs to the same digest. It is computationally difficult to figure out which inputs map to the same digest. So, for our purposes, SHA-1 will suffice.

4.3.2. Problems with digest authentication

One interesting aspect of digest authentication is that it is not as popular as it ought to be. The recommendation strongly endorses it; the language comes with ready-made libraries; it is simple enough to implement. What is the issue?

If you look at how we solved the issue of dependency on the store, you will remember that we used JAAS. The server never even has to know the password. In digest authentication, the server needs access to the password to compute the digest. There are two problems with this. One is that JAAS does not define a way to get the password unless the authentication already succeeded. The second issue is that most JAAS modules do not support digest-based authentication. So, unless we are ready to write a JAAS module for our store that supports digest-based authentication, we cannot use digest-based authentication.

Even if we are prepared to write our own JAAS module, there’s still a problem with digest-based authentication. It turns out that most password stores really do not store passwords at all for security reasons. They store only a transformed password. When they need to verify a password match for a given candidate, they apply the same transformation and verify for an exact match. The transformation function, just like the digest, is usually made to be difficult to invert. Therefore, the JAAS module cannot get the original password from the store. It means that the client—i.e., the sender—also has to use the same transformed password when computing the digest. How does it know which store uses which transformation algorithm? In general, there is no clear answer to this question.

So, in the final analysis, if digest mechanism has to work, the following problems need to be solved:

  • Clients need to know the transformation function applied to the passwords when they are stored in the password store.
  • Server needs to have a JAAS module that supports digest based authentication.

Ultimately, all these requirements make the implementation considerably more complicated than the clean implementation of simple username and password scheme. Therefore, password digest is not as popular as it could be.

4.3.3. Limitations of password-based schemes

We have seen where password-based schemes are applicable and how password digest schemes improve security for a password-based scheme. Despite all the advantages of familiarity and availability of good implementations, password-based schemes (both plain text and digest) have some limitations.

The overall security of any system is only as strong as its weakest link. In password-based authentication, the password is the weakest link. Password-based authentication is based on the assumption that only a legitimate user can know his correct password. All of us know the fallacy in this assumption.

Most users choose predictable passwords. For example, password is probably the most popular password. Most people use the names of their significant others or pets as passwords. Even if a password is not predictable, it is still susceptible to brute-force dictionary attacks. A hacker can use a program to try out all the words in a dictionary along with minor variations on them such as adding a number as a prefix or a suffix to each word. The hacker’s dictionary might even be customized per organization, to try terms frequently used in that organization.

IT administrators are very aware of this problem and seek to redress it by setting up a password policy and enforcing it using tools. For example, a password quality meter can stop users from registering predictable passwords. They can even force people to retire old passwords after a time period.

Sadly, even all those measures do not help us in securing passwords. Passwords can be “repurposed” by unethical administrators. It is quite common for people to reuse username and password combinations for several systems. Any administrator of a service can reuse that knowledge to gain unauthorized access to a different service.

Two popular technologies that form the basis for solutions without these drawbacks are Kerberos and digital certificates. We’ll discuss the first of these, Kerberos, in the next chapter. Digital certificates are introduced in chapters 6 and 7, where we will discuss them in conjunction with encryption and/or signatures.

4.4. Summary

Let’s consider the big picture: In SOA, a user can be an individual or an application. In either case, an identity claim needs to be authenticated. Password schemes are the simplest mechanisms for such authentication.

In this chapter, we covered two different password schemes for user authentication.

First we discussed plain-text passwords, which most of you may be familiar with. If you followed that part of the chapter, you learned how to use Username-Token in WS-Security to claim identity. You also learned how to use JAAS to validate the claims made in these tokens against a store and scheme of your choice. You also saw how we can stack up JAAS modules to create complex authentication mechanisms that can serve many useful business scenarios.

Even though JAAS is not a part of the WS-* standard, you will find it useful in most situations. It is especially helpful when you want to protect against changing password stores. In fact, we will reuse it in subsequent chapters where we use authentication schemes other than the ones presented here.

Next, we discussed the digest authentication scheme. We showed how it effectively prevents MIM attacks by using nonces and timestamps. We implemented the digest scheme by extending the earlier example with one more login module in the JAAS handler.

This chapter focused on using passwords to claim and verify identity. The schemes described here paid much attention to replay attacks, preventing a MIM (or a malicious service provider) from taking your credentials and repeating them to claim your identity.

You also may have observed several best practices: separation of code into different modules so that we can make use of them in different situations and use of configuration files to make the application flexible.

Finally, we described the suitability of the password-based schemes. They are appropriate for several different situations; however, they have serious limitations conceptually. That is, the use of a password as proof itself is a weakness in the system.

In the next three chapters, we will describe two technologies—Kerberos and public key infrastructure (PKI)—that will help you overcome the limitations of passwords in claiming and verifying identity.

Through most of this chapter, we have assumed that authentication can happen at a single point. We assumed that a single system has access to all the password/credential stores in order to authenticate any identity claim submitted to it. This assumption often proves false when businesses cooperate to offer their customers each other’s services without the need for additional authentication. We also have not seen how we can go on to determine whether a caller has sufficient privileges to make a request once we verify his identity. These topics will be discussed in part III of this book.

Suggestions for further reading

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.146.37.250