Wednesday, May 31, 2006

User Centric Security Model

Java Security Series Part 5

Till now we have been looking at the security model with the code perspective. The focus of this perspective was to prevent malicious code from causing harm to our secure systems. However, in a distributed system, not only is it important to run trusted code, but also we need to trust the user of the code - and this is the focus of User Centric Security model.

In Java 1.3, this model was introduced using the JAAS framework - Java Authorization and Authentication Service. However, with 1.4, it has been merged into Core Java.

Authentication

For protecting a resource against unwarranted users, the system should first be able to "authenticate" the user. The system should first verify in a secure fashion that the user of the system trying to access a protected resource is a known entity which it trusts and this process of verification is called Authentication.

The user of the system should first tell the system that he/she is a "somebody" - a particular name - who the system recognizes and based on this recognition, the system may further grant access to the protected resource. The "somebody" could be an identifier for the user such as user name, Employee Id, Certificate or any token which the system recognizes. The verification process will need the user to provide to the system enough proof that he/she is really the "somebody".

On successful authentication, the user is referred to as the Subject and the name with which he/she is identified as the Principal. A point implicit here is that a subject can have multiple principals attached to it because of multiple authentication processes. This may be needed by a system which has multiple sub-systems each of which recognizes the subject with multiple identities.

Authentication Model

JAAS authentication model is based on the Pluggable Authentication Model (PAM) framework. This architecture decouples the actual authenication logic from the application and thus allows the flexibility of configuring the application to any security requirements later, by just plugging in a different authentication mechanism. The application does not have to change or be modified to support a newer authentication mechanism. This model is also extensible to support multiple authentication mechanisms at the same time, allowing stacking of the authentication mechanisms. Stack attributes to the authentication mechanisms can then be specified in the configuration affecting the stack ordering thus allowing multiple overall authentiction result. The
values of the stack attributes are -

  • Required - An authentication mechansism with this value should succeed for the overall authentication to succeed. The next mechanism in the list is evaluated by the login process in any case.
  • Requisite - An authentication mechanism with this value should succeed for the overall authentication to succeed. The next mechanism in the list is evaluated only if this succeeds. Other wise the overall authentication result is said to be failed and the control returns to the application.
  • Sufficient - An authentication mechanism with this value is not required for the overall authentication to succeed. The next mechanism in the list is evaluated only if this fails. Other wise the overall authentication result is said to be succeeded and the control is immediately returned to the application.
  • Optional - An authentication mechanism with this value is not required for the overall authentication to succeed. The next mechanism in the list is evaluated by the login process in any case.
The overall authentication succeeds only if all Required and Requisite LoginModules succeed. If a Sufficient LoginModule is configured and succeeds, then only the Required and Requisite LoginModules prior to that Sufficient LoginModule need to have succeeded for the overall authentication to succeed. If no Required or Requisite LoginModules are configured for an application, then at least one Sufficient or Optional LoginModule must succeed.

Authentication using LoginContext and LoginModule

javax.security.auth.login.LoginContext abstracts the PAM framework for authentication. When an application needs to authenticate a user, it uses this class to start the authentication process. An instance of this class is created passing the "Authentication Realm" to which the login is required. The authentication realm abstracts a set of login configuration as abstracted by javax.security.auth.login.Configuration to get the configuration of authentication mechanisms to use. The configuration basically contains a collection of authentication mechanism configurations -

  • Authentication mechanism class name
  • Authentication mechanism specific configuration details in name-value pairs
  • Stack config property - Required Requisite Sufficient Optional
These sets of configuration information are indexed by strings which is the authentication realm. The sample below gives an example of a typical configuration for an auth realm called MyLogin -

MyLogin {
com.sun.security.auth.module.UnixLoginModule required;
com.sun.security.auth.module.Krb5LoginModule optional
useTicketCache="true"
ticketCache="${user.home}${/}tickets";
};
After creating the LoginContext object, the user calls the login method. LoginContext object is
as shown below -

public final class LoginContext {
// important constructors
public LoginContext(String authRealm) {...}
public LoginContext(String authRealm, CallbackHandler h) {...}
// two phase auth process
public void login() {...}
public void logout() {...}
// get the authenticated Subject
public Subject getSubject() {...}
}
The login method kicks starts the two-phase authentication process (detailed in the next
section). It calls login() and commit() method on each of the configured login mechanism objects. These login mechanism objects need to implement the interface
javax.security.auth.spi.LoginModule as shown below -

public interface LoginModule {
// 1st authentication phase
boolean login();
// 2nd authentication phase
boolean commit();
boolean abort();
boolean logout();
}

The LoginContext object passes in all the authentication specific configuration settings from the configuration as a Map of name-value pairs which the LoginModule can use for the actuual authentication - possibly to connect to database store etc to validate the passwords. Some times the individual LoginModule object may need to communicate with the user to get some details for the authentication purpose - for example prompt the user to enter user name and password. This communication with the user is achieved through the javax.security.auth.callback.CallbackHandler object. The application needs to implement this object and pass its reference to the LoginModule object in its constructor. The LoginModule will then request whatever it needs by calling its handle method by passing a list of callback objects abstracting the LoginModules's needs. A typical callback handler implementation could be as shown below -

public interface CallbackHandler {
public void handle(Callback[] callbacks) {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof NameCallback) {
// prompt the user for a username
NameCallback nc = (NameCallback)callbacks[i];
// ignore the provided defaultName
System.err.print(nc.getPrompt());
System.err.flush();
nc.setName((new BufferedReader
(new InputStreamReader(System.in))).readLine());
}
else if (callbacks[i] instanceof PasswordCallback) {
// prompt the user for sensitive information
PasswordCallback pc = (PasswordCallback)callbacks[i];
System.err.print(pc.getPrompt());
System.err.flush();
pc.setPassword(readPassword(System.in));
}
else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
}

2-Phase authentication

The 2-phase authentication involves -
  • login() call on all LoginModules
  • On successful overall authentication (see above), commit() call on all LoginModules
  • On unsuccessful overall authentication, abort() call on all LoginModules
LoginContext object calls login() on each of the configured the LoginModule objects. LoginModule objects are expected to implement the authentication process, but not update the Subject with the Principal. LoginModules are assumed to store the result privately. LoginModule objects after authenticating, either returns true indicating a successful authentication or throws LoginException indicating authentication failure. A false return indicates that the LoginModule is not really in the context of this Login process and can be ignored.

A point to note here is that if a LoginModule has Stack flag Requisite and its login() operation was a failure, or the Stack flag is Sufficient and the login() operation of the LoginModule is a success, login() is not called on subsequent LoginModules. This is as required by the Stack flag meaning (see above). However, looking at the code, if any LoginModule throws LoginException during the login() phase, it seems that login() of subsequent LoginModules is not called. This may be a bug.

If overall authentication is successful, the authentication process then proceeds to call commit() on all LoginModule objects. LoginModules are now expected to update the Subject with principals specific to LoginModules. On successful completion of this step, the user can get a successfully authenticated subject from the LoginContext and the login process is considered completed successfully.

On the other hand, if overall authentication is not successful, then abort() is called on each of the LoginModules. Each of the LoginModules are expected to cleanup anything relevant to it. abort() may also be called on each of the LoginModules if the commit() phase fails after a successful login() phase. After calling abort(), the original LoginException is thrown back.