Developers who want to add security to peer-to-peer applications can choose from several toolkits that bundle many of the technologies outlined in this chapter: The Intel Peer-to-Peer Trusted Library is an open source cross-platform solution for C++. It includes support for peer identification and authentication, secure storage, and networking. The library allows developers to work with digital certificates, digital signatures, digital envelopes, public key encryption, and symmetric key encryption. Additionally, it has useful classes to support multithreading and synchronization across platforms. More information can be found at its home on SourceForge see http://sourceforge.net/projects/ptptl. Java programmers can use the security features found natively in the Java 2 Standard Edition platform version 1.4 (J2SE v1.4). The Java Cryptography Extension (JCE) offers support for identification and authentication using digital certificates, digital signatures and message digests. It also includes functions for symmetric and public key encryption. Finally, it provides an implementation of the Internet Proposed Standard Generic Security Services Application Programming Interface Version 2 (GSS-API) for securely exchanging messages between applications on a per-message basis. The GSS-API is particularly interesting in that it provides support for a number of underlying security mechanisms, such as Kerberos, to provide end-to-end security, including client identity, rights, confidentiality, and data integrity. It can be especially useful for peer-to-peer applications because it provides a significant amount of functionality that can be applied to short sessions. Note The GSS-API is also implemented in C and C++, Python, and other languages. See the "Additional Resources" section for a pointer to the specifications, including the language bindings. The Java Secure Socket Extension (JSSE) enables secure Internet communications using SSL (Secure Sockets Layer) and TLS (Transport Layer Security). The Java Authentication and Authorization Service (JAAS) enables services to authenticate and enforce access controls upon users. With JAAS, developers can leverage existing authentication and authorization services. Native support for Kerberos v.5 is included, and providers exist for authentication using Solaris, generic Unix, and Windows NT domains. Using JAAS to Provide Authentication and AuthorizationJAAS defines a number of core classes that are central to providing authentication and authorization. As mentioned earlier in this chapter, authentication confirms identity, and authorization provides access control. The JAAS core classes fall into three categories: common, authentication, and authorization. The JAAS common classes are Subject, Principal, and classes associated with credentials. A subject is any user of a computing service. This might be an actual person, or a running program or process. JAAS uses the subject to authorize access to resources. A principal is a name associated with a subject. A subject might have one or more names associated with it. If you use multiple systems, it is likely that you have multiple logon identifiers. One system might require a unique ID, such as a username, and perhaps another system might require a social security number. The concept of a principal can be used to map multiple named associations to your identity, and help promote single sign-on environments. A credential contains information used to authenticate a subject. The most common type of credential is a password. However, more secure credential representations exist, such as Kerberos tickets and public key certificates. JAAS also supports an extension mechanism that allows third-party credential implementations to be plugged-in. A subject's credentials are divided into two sets. One set contains the public credentials (public keys), and the other set contains the private credentials (passwords, private keys). Access to public credentials requires no special permissions, whereas access to private credentials is security-checked. The authentication framework is based on Pluggable and Stackable Authentication Modules (PAM). The intent is to enable different vendors and administrators to plug in different authentication modules based on unique security policies. The JAAS authentication classes are LoginContext, LoginModule, CallbackHandler, and Callback. These classes are central to JAAS authentication. The LoginContext class shown in Listing 10.1 is an implementation of the PAM framework. Listing 10.1 LoginContext Classpublic final class LoginContext { public LoginContext(String name) { } public LoginContext(String name, CallbackHandler callback) {} public LoginContext(String name, Subject subject) {} public LoginContext(String name, Subject subject, CallbackHandler callback) {} public void login() { } // two phase process public void logout() { } public Subject getSubject() { } // get the authenticated Subject } The name parameter identifies a login configuration file used to identify one or more login modules. If you are familiar with transactions, you will be familiar with the mechanics of authentication using pluggable authentication modules (see Listing 10.2). Listing 10.2 LoginModule Classpublic interface LoginModule { boolean login(); // 1st authentication phase boolean commit(); // 2nd authentication phase boolean abort(); boolean logout(); } To guarantee that either all LoginModules succeed or none succeed, the LoginContext performs the authentication steps in two phases:
If either the first phase or the second phase fails, the LoginContext invokes the abort method on each configured LoginModule. Each LoginModule then cleans up any login state information it had associated with the authentication attempt. As described in Sun's JAAS documentation (http://java.sun.com/security/jaas/doc/api.html), the following steps are performed to authenticate a Subject:
The following configuration file defines a single login module: /** Login Configuration for JWorkPlace **/ JWorkPlace { org.jworkplace.login.WorkPlaceLoginModule required debug=true; }; A login module can be associated with a service by setting the following property: -Djava.security.auth.login.config==/usr/JiniServices/CMS/config/login.config The LoginHandler class shown in Listing 10.3 creates a LoginContext, and implements the CallbackHandler interface. The CallbackHandler interface implements one method: void handle(Callback[] callbacks) throws java.io.IOException, UnsupportedCallbackException; Security services make requests for different types of information by passing Callbacks to the CallbackHandler. The CallbackHandler implementation decides how to retrieve and display the information depending on the Callbacks passed to it. For example, if the service needs a username and password to authenticate a user, it uses a NameCallback and PasswordCallback. The CallbackHandler can then prompt for the appropriate information. Listing 10.3 LoginHandler Classpackage org.jworkplace.login; import java.io.*; import java.util.*; import java.security.Principal; import javax.security.auth.*; import javax.security.auth.callback.*; import javax.security.auth.login.*; import javax.security.auth.spi.*; import com.sun.security.auth.*; public class LoginHandler implements CallbackHandler { LoginContext lc; NameCallback nameCallback; PasswordCallback passwordCallback; int loginTries = 0; String user; char[] password; // create the LoginContext using the JWorkPlace configuration public LoginHandler() { try { lc = new LoginContext("JWorkPlace", new Subject(), this); } catch (LoginException le) { le.printStackTrace(); System.exit(-1); } } // The Callback Interface // provides the types of callbacks used to interface with the LoginModule public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof TextOutputCallback) { // display the message according to the specified type TextOutputCallback toc = (TextOutputCallback)callbacks[i]; switch (toc.getMessageType()) { case TextOutputCallback.INFORMATION: System.out.println(toc.getMessage()); break; case TextOutputCallback.ERROR: System.out.println("Login error: " + toc.getMessage()); break; case TextOutputCallback.WARNING: System.out.println("Login warning: " + toc.getMessage()); break; default: throw new IOException("Unknown message type: " + toc.getMessageType()); } } else if (callbacks[i] instanceof NameCallback) { nameCallback = (NameCallback)callbacks[i]; nameCallback.setName(user); } else if (callbacks[i] instanceof PasswordCallback) { passwordCallback = (PasswordCallback)callbacks[i]; passwordCallback.setPassword(password); } else { throw new UnsupportedCallbackException (callbacks[i], "Unrecognized Callback"); } } } // user defined login method which simply counts the number of login attempts // and sets the user and password which will be used in the callback above public boolean login(String user, char[] password) { if(loginTries > 2) { System.out.println("Sorry charlie..."); return false; } else { this.user = user; this.password = password; } loginTries++; try { // attempt authentication on the LoginContext lc.login(); // authentication succeeded return true; } catch (AccountExpiredException aee) { System.out.println("Your account has expired. " + "Please notify your administrator."); } catch (CredentialExpiredException cee) { System.out.println("Your credentials have expired."); } catch (FailedLoginException fle) { System.out.println("Authentication Failed"); } catch (Exception e) { System.out.println("Unexpected Exception - unable to continue"); e.printStackTrace(); } // authentication did not succeed return false; } } The WorkPlacePrincipal class in Listing 10.4 implements the Principal interface. Listing 10.4 WorkPlacePrincipal Classpackage org.jworkplace.login; import java.security.Principal; public class WorkPlacePrincipal implements Principal, java.io.Serializable { private String name; public WorkPlacePrincipal(String name) { if (name == null) throw new NullPointerException("illegal null input"); this.name = name; } public String getName() { return name; } public String toString() { return("WorkPlacePrincipal: " + name); } public boolean equals(Object o) { if (o == null) return false; if (this == o) return true; if (!(o instanceof WorkPlacePrincipal)) return false; WorkPlacePrincipal that = (WorkPlacePrincipal)o; if (this.getName().equals(that.getName())) return true; return false; } public int hashCode() { return name.hashCode(); } } To initialize a LoginModule, the LoginContext calls each configured LoginModule's no-argument constructor: void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options); The WorkPlaceLoginModule class in Listing 10.5 implements the LoginModule interface. The specific validation of the username and password is not shown, as you would provide your own specific implementation. Listing 10.5 WorkPlaceLoginModule Classpackage org.jworkplace.login; import java.util.*; import java.io.IOException; import javax.security.auth.*; import javax.security.auth.callback.*; import javax.security.auth.login.*; import javax.security.auth.spi.*; public class WorkPlaceLoginModule implements LoginModule { // initial state private Subject subject; private CallbackHandler callbackHandler; private Map sharedState; private Map options; // configurable option private boolean debug = false; // the authentication status private boolean succeeded = false; private boolean commitSucceeded = false; // username and password private String username; private char[] password; private WorkPlacePrincipal userPrincipal; // the initialize method is called by the LoginContext public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = sharedState; this.options = options; } // this is the login method that is called by the LoginContext public boolean login() throws LoginException { // prompt for a username and password if (callbackHandler == null) throw new LoginException("Error: no CallbackHandler available "); // Two Callback are created, one for the name and one for the password Callback[] callbacks = new Callback[2]; callbacks[0] = new NameCallback("JWorkPlace username: "); callbacks[1] = new PasswordCallback("JWorkPlace password: ", false); try { // this is the callback to our LoginHandler callbackHandler.handle(callbacks); // get the username and password provided in the callback username = ((NameCallback)callbacks[0]).getName(); char[] tmpPassword = ((PasswordCallback)callbacks[1]).getPassword(); if (tmpPassword == null) { // treat a NULL password as an empty password tmpPassword = new char[0]; } password = new char[tmpPassword.length]; System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length); ((PasswordCallback)callbacks[1]).clearPassword(); } catch (java.io.IOException ioe) { throw new LoginException(ioe.toString()); } catch (UnsupportedCallbackException uce) { throw new LoginException("Error: " + uce.getCallback().toString()); } // verify the username/password // insert code to verify user / password here // Authentication succeeded succeeded = true; return true; // Authentication failed succeeded = false; throw new FailedLoginException("Password Incorrect"); } // the 2 phases of authentication success commit public boolean commit() throws LoginException { if (succeeded == false) { return false; } else { // add a Principal (authenticated identity) // to the Subject // assume the user we authenticated is the WorkPlacePrincipal userPrincipal = new WorkPlacePrincipal(username); if (!subject.getPrincipals().contains(userPrincipal)) { subject.getPrincipals().add(userPrincipal); } // clean out state username = null; for (int i = 0; i < password.length; i++) password[i] = ' '; password = null; commitSucceeded = true; return true; } } // the 2 phases of authentication failure abort public boolean abort() throws LoginException { if (succeeded == false) { return false; } else if (succeeded == true && commitSucceeded == false) { // login succeeded but overall authentication failed succeeded = false; username = null; if (password != null) { for (int i = 0; i < password.length; i++) password[i] = ' '; password = null; } userPrincipal = null; } else { // overall authentication succeeded and commit succeeded, // but someone else's commit failed logout(); } return true; } public boolean logout() throws LoginException { subject.getPrincipals().remove(userPrincipal); succeeded = false; succeeded = commitSucceeded; username = null; if (password != null) { for (int i = 0; i < password.length; i++) password[i] = ' '; password = null; } userPrincipal = null; return true; } } After successful authentication, JAAS provides the capability to enforce access controls on the principals associated with the authenticated subject. You will need to grant the necessary security permissions to your codebase. If you are using JDK 1.3, JAAS is not a part of the core distribution, and therefore requires a grant statement similar to the following: /** Java 2 Access Control Policy for the JAAS Application **/ /* grant the JAAS core library AllPermission */ grant codebase "file:/usr/java/jdk1.3/jre/lib/ext/jaas.jar" { permission java.security.AllPermission; }; /* grant the sample LoginModule AllPermission */ grant codebase "file:/usr/JWorkPlace/lib/login.jar" { permission java.security.AllPermission; }; grant codebase "file:/usr/JWorkPlace/lib/service.jar" { permission javax.security.auth.AuthPermission "createLoginContext"; permission javax.security.auth.AuthPermission "doAs"; permission java.util.PropertyPermission "java.home", "read"; permission org.jworkplace.login.AccountPermission "createAccount"; }; A login policy can be associated with a service by setting the following property: -Djava.security.policy==/usr/JiniServices/CMS/config/login.policy The following policy extends the Java 2 codebase policy with Subject-based access control. This grant entry provides the necessary permissions to perform a sensitive operation (createAccount) to any Subject that has an associated WorkPlacePrincipal. /** Subject-Based Access Control Policy for the JWorkPlace Application **/ grant codebase "file:/usr/JWorkPlace/lib/service.jar", Principal org.jworkplace.login.WorkPlacePrincipal * { permission org.jworkplace.login.AccountPermission "createAccount"; }; A login authorization policy can be associated with a service by setting the following property: -Djava.security.auth.policy==/usr/JiniServices/CMS/config/login_jaas.policy JAAS supplements the Java 2 security with architecture using the Subject.doAs method to dynamically associate an authenticated subject with the current AccessControlContext. The AccessController can base its decisions on both the executing code itself and the principals associated with the Subject. The WorkPlaceAction class in Listing 10.6 implements the PrivilegedAction interface. The PrivilegedAction interface is implemented by classes that require access control checks. The run method is called by the AccessController.doPrivileged method after enabling privileges. Listing 10.6 WorkPlaceAction Classpackage org.jworkplace.login; import java.io.File; import java.security.PrivilegedAction; public class WorkPlaceAction implements PrivilegedAction { // run method defined in the PrivilegedAction interface public Object run() { // get the security manager and check if permission has been granted SecurityManager sm = System.getSecurityManager(); sm.checkPermission(new AccountPermission("createAccount")); return null; } } Finally, the AccountPermission class shown in Listing 10.7 extends BasicPermission.BasicPermission provides a simple base class for creating new permission types. In this case, you create a new AccountPermission class that is checked in the WorkPlaceAction to verify that the thread of control associated with the current Subject has the necessary permission to create an account. Listing 10.7 AccountPermission Classpackage org.jworkplace.login; import java.security.BasicPermission; public final class AccountPermission extends BasicPermission implements java.io.Serializable { public AccountPermission(String name) { this(name,null); } public AccountPermission(String name, String action) { super(name); } } |