OWASP ESAPI Authenticator tutorial

Shortlink: http://wp.me/p5Jvc-bL

The internet is slowly but steadily becoming all pervasive in our lives. From rudimentary surfing for information, it has become a repository for storing private information and conversations. We regularly use it to do financial transactions and also share personally identifiable information. Availability of such information in the public domain is harmful and prone to misuse. Therefore it is necessary that we use suitable techniques to protect our information shared on the internet.

The Open Web Application Security Project (OWASP) is an organization focused on improving the security of software. Their aim is to make software security visible so that we can make informed decisions around application security. To assist developers in their endeavor to implement secure applications, OWASP provides the ESAPI (The OWASP Enterprise Security API) a free, open source web application security control library.

The ESAPI library implementation is supported in multiple programming languages like PHP, .NET, Python, Java etc. As I have experience in Java, I aim to use the Java implementation for further elaboration. I downloaded the ESAPI 2.0.1 (esapi-2.0.1-dist.zip) from this download link. The libs folder within the zip contains all the ESAPI jar dependencies.

The ESAPI provides a number of interfaces to cover variety of facets of application security, namely:

  • Authentication (Authenticator)
  • Role based access control (AccessController)
  • HTTP specific handler (HTTPUtilities)
  • HTML/XML encoding (Encoder)
  • Data encryption (Encryptor)
  • OS commands protection (Executor)
  • Detect security acts (IntrusionDetector)
  • Crytographically random numbers/strings (Randomizer)
  • Application data validation (Validator)

This blog post is going to deal with the first option Authenticator. ESAPI will primarily be used in the context of web applications. Instead of setting up a live web application to demonstrate the usage of ESAPI, I will be mocking HTTP objects using Mockito framework version 1.9.5. The downloads are available here with the source.

Here’s my test setup structure. I have created a java project in Eclipse with Java 7 as my JRE. Added the mockito-all-1.9.5.jar, esapi-2.0.1.jar and all jars within the libs folder of the esapi-2.0.1-dist.zip.

Now let’s begin with ESAPI Authenticator interface. The first thing we need to do is set up the ESAPI configuration. The ESAPI is setup with ‘default’ security configuration. It allows developer to leverage the default setting and modify/enhance settings whenever the need arises. The most vital tool in this configuration scaffolding is the ESAPI.properties file. Whenever developers want to override the default, they should provide suitable value for pertinent key. To understand this let’s look at what we will be doing for Authenticator.

The ESAPI.properties file will be part of the source code folder. The following are the contents of the ESAPI.properties file for Authenticator.

Authenticator.UsernameParameterName=userName
Authenticator.PasswordParameterName=password

ESAPI.Authenticator=com.esapi.authenticator.CustomAuthenticator
Authenticator.IdleTimeoutDuration=100000
Authenticator.AbsoluteTimeoutDuration=100000

How each of these property definition is used will be explained in the subsequent walk thru. The org.owasp.esapi.Authenticator interface defines methods for creating/handling user credentials. To provide consistency and reuse ESAPI also provides an abstract implementation of the Authenticator interface AbstractAuthenticator. The AbstractAuthenticator class provides standard implementation for non-user specific methods. The ESAPI framework also provides a standard file based implementation via the FileBasedAuthenticator class.

To implement your own custom based Authenticator implementation extend the abstract class AbstractAuthenticator. Let’s call the custom implementation class CustomAuthenticator. To inform ESAPI to use this custom authenticator, we assign the fully qualified class name of CustomAuthenticator as value to the key ESAPI.Authenticator in the ESAPI.properties file.

The source code of the CustomAuthenticator class is shown below:

package com.esapi.authenticator;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

import org.owasp.esapi.Authenticator;
import org.owasp.esapi.User;
import org.owasp.esapi.errors.AuthenticationException;
import org.owasp.esapi.errors.EncryptionException;
import org.owasp.esapi.reference.AbstractAuthenticator;
import org.owasp.esapi.reference.DefaultUser;
import org.owasp.esapi.reference.FileBasedAuthenticator;

import com.esapi.accesscontrol.AccessControl;

public class CustomAuthenticator extends AbstractAuthenticator {
	
	private static final CustomAuthenticator authImpl  = new CustomAuthenticator();
	
	private static Map<String, String> userCredentials = new HashMap<String, String>();
	
	static {
		userCredentials.put("jsmith", "abc123");
	}
	
	private CustomAuthenticator() {
		
	}
	
	public static Authenticator getInstance(){
		return authImpl;
	}


	@Override
	public boolean verifyPassword(User user, String password) {
		String userid = user.getAccountName();
		String value = userCredentials.get(userid);
		if (userid != null && value != null){
			if (password.equals(value)) {
				return true;
			} else {
				return false;
			}
		}
		return false;
	}

	@Override
	public User createUser(String accountName, String password1,
			String password2) throws AuthenticationException {
		
		checkPassword(accountName, password1, password2);
		
		User user = getUser(accountName);
		if (user != null) {
			throw new AuthenticationException("User Exists", 
					"User " + accountName + " exists.");
		}
		
		DefaultUser newUser = loadUser(accountName);
		newUser.resetCSRFToken();
		
		userCredentials.put(accountName, password1);
		
		return newUser;
	}

	private DefaultUser loadUser(String accountName) 
			throws AuthenticationException {
		
		DefaultUser newUser = new DefaultUser(accountName);
		newUser.enable();
		
		Set<String> roles = new HashSet<String>();
		roles.add(AccessControl.DATA_ENTRY_OPERATOR);
		newUser.addRoles(roles);
		newUser.setScreenName("John Smith");
		
		return newUser;
	}

	private void checkPassword(String accountName, String password1,
			String password2) throws AuthenticationException {
		if (password1 != null && password2 != null) {
			if (! password1.equals(password2)) {
				throw new AuthenticationException("Password mismatch", 
					"User " + accountName + " needs a matching password entries.");
			}
		} else {
			throw new AuthenticationException("Password required", 
				"User " + accountName + " needs password information.");
		}
	}

	@Override
	public String generateStrongPassword() {
		return FileBasedAuthenticator.getInstance()
				.generateStrongPassword();
	}

	@Override
	public String generateStrongPassword(User user, String oldPassword) {
		return FileBasedAuthenticator.getInstance()
				.generateStrongPassword(user, oldPassword);
	}

	@Override
	public void changePassword(User user, String currentPassword,
			String newPassword, String newPassword2)
			throws AuthenticationException {
		
		String userId = user.getAccountName();
		this.checkPassword(userId, newPassword, newPassword2);
		if (this.verifyPassword(user, currentPassword)) {
			userCredentials.put(userId, newPassword);
		} else {
			throw new AuthenticationException("Password Invalid", 
					"Please enter the correct password.");
		}
	}

	@Override
	public User getUser(long accountId) {
		return null;
	}
	
	@Override
	public User getUser(String accountName) {
		if (userCredentials.containsKey(accountName)) {
			try {
				DefaultUser user = loadUser(accountName);
				return user;
			} catch(AuthenticationException ex) {
				return null;
			}
		}
		return null;
	}

	@Override
	public Set getUserNames() {
		throw new UnsupportedOperationException("The Authenticator " +
				"does not support this operation.");
	}

	@Override
	public String hashPassword(String password, String accountName)
			throws EncryptionException {
		return FileBasedAuthenticator.getInstance()
				.hashPassword(password, accountName);
	}

	@Override
	public void removeUser(String accountName) throws AuthenticationException {
		userCredentials.remove(accountName);
	}

	@Override
	public void verifyAccountNameStrength(String accountName)
			throws AuthenticationException {
		throw new UnsupportedOperationException("The Authenticator " +
				"does not support this operation.");
		
	}

	@Override
	public void verifyPasswordStrength(String oldPassword, String newPassword,
			User user) throws AuthenticationException {
		throw new UnsupportedOperationException("The Authenticator " +
				"does not support this operation.");
		
	}
}

The CustomAuthenticator class leverages the default behavior of the AbstractAuthenticator class and overrides only the User specific methods. Let’s understand the custom behavior. The CustomAuthenticator class is implemented as a singleton class. The instance object of the class is stored as a private static variable in the CustomAuthenticator class. This instance is made accessible to consumer entities via the getInstance static method. All custom or default implementations of the various ESAPI facets (Authenticator, Validator etc) are available via ESAPI class’s static methods. These methods internally use the org.owasp.esapi.util.ObjFactory’s make method to create appropriate interface implementations. The method looks for two things within the interface implementation classes. First if there exists a getInstance method and second if there exists a constructor with no arguments. In that order it triggers the method/constructor invocation using reflection to return back an instance of the interface implementation to the invoking entity.

Next we have created ‘userCredentials’ HashMap. This Map contains username and password as a key value pair. For our custom implementation the userCredentials map is the authentication information repository. In a more ‘real life’ scenario the map can be replaced by a connection like object to a suitable repository like LDAP, file, XML, database etc. For our demonstration I have filled this map with a single userid ‘jsmith’ and his security credential. Before moving on to overridden methods, just wanted to cover something. ESAPI provides an interface User which abstracts User related information. Developers are expected to create custom implementation of the User interface or use the DefaultUser implementation made available by the framework. Instead of reinventing the wheel I propose to use the default implementation.

Let’s look at the first overridden method verifyPassword implementation:

@Override
public boolean verifyPassword(User user, String password) {
	String userid = user.getAccountName();
	String value = userCredentials.get(userid);
	if (userid != null && value != null){
		if (password.equals(value)) {
			return true;
		} else {
			return false;
		}
	}
	return false;
}

The method accepts two arguments the User instance and password string. The userid/account name is retrieved from the User object. A lookup is done in the userCredentials Map and corresponding password value is retrieved. In case the retrieved value matches the password received as argument, the method returns true.

Next we look at three methods createUser, loadUser and checkPassword method. The method createUser is an overridden implementation, while the checkPassword and loadUser methods are supplementary methods created to support the createUser method.

@Override
public User createUser(String accountName, String password1,
		String password2) throws AuthenticationException {
	
	checkPassword(accountName, password1, password2);
	
	User user = getUser(accountName);
	if (user != null) {
		throw new AuthenticationException("User Exists", 
			"User " + accountName + " exists.");
	}
		
	DefaultUser newUser = loadUser(accountName);
	newUser.resetCSRFToken();
	
	userCredentials.put(accountName, password1);
		
	return newUser;
}
private void checkPassword(String accountName, String password1,
		String password2) throws AuthenticationException {
	if (password1 != null && password2 != null) {
		if (! password1.equals(password2)) {
			throw new AuthenticationException("Password mismatch", 
				"User " + accountName + " needs a matching password entries.");
		}
	} else {
		throw new AuthenticationException("Password required", 
			"User " + accountName + " needs password information.");
	}
}
private DefaultUser loadUser(String accountName) throws AuthenticationException {
		
	DefaultUser newUser = new DefaultUser(accountName);
	newUser.enable();
		
	Set<String> roles = new HashSet<String>();
	roles.add(AccessControl.DATA_ENTRY_OPERATOR);
	newUser.addRoles(roles);
	newUser.setScreenName("John Smith");
	
	return newUser;
}

The createUser method first invokes the checkPassword method to verify if the password values are not null and are matching values. Next it checks if the user exists using the interface’s getUser method. If not, then it creates a standardized User instance object using the loadUser method, updates the userCredentials map and returns the instantiated User object.

Some applications provide default passwords to new users. To support this functionality the Authenticator interface provides overloaded versions of the generateStrongPassword. I have leveraged the FileBasedAuthenticator’s implementation. You are free to implement your own custom strong password generation routine. The same thing is done for hashPassword method.

@Override
public String generateStrongPassword() {
	return FileBasedAuthenticator.getInstance()
		.generateStrongPassword();
}

@Override
public String generateStrongPassword(User user, String oldPassword) {
	return FileBasedAuthenticator.getInstance()
		.generateStrongPassword(user, oldPassword);
}
@Override
public String hashPassword(String password, String accountName)
	throws EncryptionException {
	return FileBasedAuthenticator.getInstance()
		.hashPassword(password, accountName);
}

The next overridden method changePassword, checks if the two values of new password are the same and that the user and old password are a valid combination.

@Override
public void changePassword(User user, String currentPassword,
	String newPassword, String newPassword2)
		throws AuthenticationException {
		
	String userId = user.getAccountName();
	this.checkPassword(userId, newPassword, newPassword2);
	if (this.verifyPassword(user, currentPassword)) {
		userCredentials.put(userId, newPassword);
	} else {
		throw new AuthenticationException("Password Invalid", 
			"Please enter the correct password.");
	}
}

The next overridden method is getUser. It has two overloaded versions. The first one which accepts accountId as an argument currently returns null. The second one accepts account name / userid as input argument. The input argument is checked against userCredentials map to check if the user exists. In case the user exists, an appropriately instantiated User instance is returned back to the invoking application.

@Override
public User getUser(long accountId) {
	return null;
}
	
@Override
public User getUser(String accountName) {
	if (userCredentials.containsKey(accountName)) {
		try {
			DefaultUser user = loadUser(accountName);
			return user;
		} catch(AuthenticationException ex) {
			return null;
		}
	}
	return null;
}

The following overriden method implementations namely getUserNames, verifyAccountNameStrength and verifyPasswordStrength I am not interested in. Hence they throw an UnsupportedOperationException.

@Override
public Set getUserNames() {
	throw new UnsupportedOperationException("The Authenticator " +
		"does not support this operation.");
}

@Override
public String hashPassword(String password, String accountName)
	throws EncryptionException {
	return FileBasedAuthenticator.getInstance()
		.hashPassword(password, accountName);
}

@Override
public void verifyAccountNameStrength(String accountName)
		throws AuthenticationException {
	throw new UnsupportedOperationException("The Authenticator " +
		"does not support this operation.");		
}

@Override
public void verifyPasswordStrength(String oldPassword, String newPassword,
	User user) throws AuthenticationException {
	throw new UnsupportedOperationException("The Authenticator " +
		"does not support this operation.");
	
}

The overridden method removeUser removes the entry for the userid from the userCredentials map.

@Override
public void removeUser(String accountName) throws AuthenticationException {
	userCredentials.remove(accountName);
}

Now that we are done with creating a CustomAuthenticator, let’s create a test class to validate our implementation. Refer the source code of the test class HTTPAuthenticationTest.

package com.esapi.http.test;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.mockito.Mockito;
import org.owasp.esapi.Authenticator;
import org.owasp.esapi.ESAPI;
import org.owasp.esapi.HTTPUtilities;
import org.owasp.esapi.User;
import org.owasp.esapi.errors.AuthenticationException;

public class HTTPAuthenticationTest {

	public static void main(String[] args) {
		HttpServletRequest req = Mockito.mock(HttpServletRequest.class);
		HttpServletResponse res = Mockito.mock(HttpServletResponse.class);
		HttpSession session = Mockito.mock(HttpSession.class);
		
		Mockito.when(req.getParameter("userName")).thenReturn("jsmith");
		Mockito.when(req.getParameter("password")).thenReturn("abc123");
		Mockito.when(req.getRequestURL()).thenReturn(
			new StringBuffer("https://wwww.google.com"));
		Mockito.when(req.getMethod()).thenReturn("POST");
		Mockito.when(req.getSession()).thenReturn(session);
		Mockito.when(req.getSession(false)).thenReturn(session);
		java.util.Date currentDt = new java.util.Date();
		long duration = currentDt.getTime();
		Mockito.when(session.getLastAccessedTime())
			.thenReturn(duration);
		Mockito.when(session.getCreationTime())
			.thenReturn(duration);
		
		HTTPUtilities httpUtil = ESAPI.httpUtilities();
		httpUtil.setCurrentHTTP(req, res);
		
		Authenticator auth = ESAPI.authenticator();
		try {
			User user = auth.login();
			user.addSession(session);
			System.out.println("User Name: " + user.getAccountName() + 
					" id: " + user.getAccountId());
			auth.setCurrentUser(user);
			user.logout();
		} catch (AuthenticationException e) {
			e.printStackTrace();
		}	
	}
}

In the main method, refer to code between lines 17 and 19. Here we use Mockito mock object framework to create mock HTTP objects for HTTPServletRequest, HTTPServletResponse and HTTPSession.

Next piece of code between the lines 21 and 33 helps set up the expected return values of the HTTPServletRequest and HTTPSession instance objects.

	HTTPUtilities httpUtil = ESAPI.httpUtilities();
	httpUtil.setCurrentHTTP(req, res);
	
	Authenticator auth = ESAPI.authenticator();
	try {
		User user = auth.login();
		user.addSession(session);
		System.out.println("User Name: " + user.getAccountName() + 
			" id: " + user.getAccountId());
		auth.setCurrentUser(user);
		user.logout();
	} catch (AuthenticationException e) {
		e.printStackTrace();
	}

In line 1 and 2 of the above code snippet, we use ESAPI provided HTTPUtilities class. We get a reference to the current thread’s instance using httpUtilities static method of ESAPI. We assign the current thread’s HTTPServletrequest and HTTPServletResponse objects to the HTTPUtilities. This ensures that the current request and response objects are available for further processing. Next in line 4 we get a handle to the Authenticator interface via ESAPI’s authenticator static method. Our CustomAuthenticator instance is returned by the authenticator method.

At line 6, the login method of the authenticator instance is invoked. It utilizes the login method defined in AbstractAuthenticator. The method uses login information maintained in the request object. The handle to the request object is made available by the HTTPUtilities class. The login method internally obtains the username and password information from the request object. The parameter to use for username and password is defined in ESAPI.properties. In our case refer lines 1 and 2 and the keys Authenticator.UsernameParameterName and Authenticator.PasswordParameterName. The values for these keys have been assigned in the request object. Refer lines 21 and 22 of the HTTPAuthenticationTest class or the code snippet below.

Mockito.when(req.getParameter("userName")).thenReturn("jsmith");
Mockito.when(req.getParameter("password")).thenReturn("abc123");

I have defined the user name as ‘jsmith’ and his password as ‘abc123’. Next I have also set the requestURL, http method of request and creation time and last accessed time of session object. Refer code snippet below:

	Mockito.when(req.getRequestURL()).thenReturn(
		new StringBuffer("https://wwww.google.com"));
	Mockito.when(req.getMethod()).thenReturn("POST");
	Mockito.when(req.getSession()).thenReturn(session);
	Mockito.when(req.getSession(false)).thenReturn(session);
	java.util.Date currentDt = new java.util.Date();
	long duration = currentDt.getTime();
	Mockito.when(session.getLastAccessedTime())
		.thenReturn(duration);
	Mockito.when(session.getCreationTime())
		.thenReturn(duration);

The login method internally invokes DefaultUser’s(Default implementation class for User interface) loginWithPassword method. The method checks if the user is enabled, not locked, not expired and has a valid username password combination. Next it validates the session’s creation time and last access time.
The session last access time is compared with idle time out value defined by Authenticator.IdleTimeoutDuration key in ESAPI.properties file. The session creation time is compare with absolute time out value defined in Authenticator.AbsoluteTimeoutDuration key in ESAPI.properties file.

The subsequent code after login method invocation is self-explanatory.

Thats all on ESAPI’s Authenticator interface.

Advertisements

4 thoughts on “OWASP ESAPI Authenticator tutorial

  1. Great post, can you confirm how these credentials are passed over HTTP? I am assuming its not in the clear or in a simple encoded form such as basic authentication (based64)?

  2. Vinay, exceptional post! Thank you. In your code you make references to “com.esapi.accesscontrol.AccessControl;”. Is this a simple enum class containing role names? Can you provide the example code for this?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s