The parameters of the Java sandbox that we've outlined are possible elements of a Java application, but they are not required elements of an application. The remainder of this book will show us how and when those elements can be introduced into a Java application. First, however, we're going to discuss the techniques by which Java applications can be run.
There are two techniques that we'll introduce in this section: the JavaRunner technique and the Launcher technique. While both allow you to run an application securely, the examples in this chapter do not provide any security. We'll fill in the security pieces bit by bit, while we flesh out the security story. At that point, we'll show how to run Java applications securely.[2]
[2]See, for example, the end of Chapter 6, "Implementing Security Policies".
Typically, we're used to running Java applications simply by specifying on the command line the name of a class that contains a main() method. Consider this application that reads the file specified by a command-line argument:
public class Cat { public static void main(String args[]) { try { String s; FileReader fr = new FileReader(args[0]); BufferedReader br = new BufferedReader(fr); while ((s = br.readLine()) != null) System.out.println(s); } catch (Exception e) { System.out.println(e); } } }
This is a regular Java application; if we wanted to run it and print out the contents of the password file on a Unix system, we could run the command:
piccolo% java Cat /etc/passwd root:x:0:1:0000-Admin(0000):/:/usr/bin/csh daemon:x:1:1:0000-Admin(0000):/: bin:x:2:2:0000-Admin(0000):/usr/bin: ...
From a security point of view, this is a very rudimentary program. It contains none of the elements of the sandbox that we just listed; it has the default (wide-open) sandbox given by default to every Java application. This application can perform any operation it wants.
There are two ways in which we can add security features to this application. One way is to add to the application a class loader, a security manager, use of the access controller, and so on. This additional programming would set the bounds of the sandbox for this particular application.
The other route we can take is to run this application under the auspices of another application that we'll call JavaRunner. This is completely analogous to the way in which we typically run applets: appletviewer is a Java application that runs applets, and JavaRunner is a Java application that runs other applications. Java-Runner is responsible for establishing the parameters of the Java sandbox (that is, it ensures that appropriate class loaders, a security manager, and the like are all in place) before it invokes the target application, just as appletviewer establishes the parameters of the Java sandbox before it invokes the target applet.
This technique removes the difference (in terms of security) between an applet and an application: both types of programs are now subject to the Java sandbox. There are a number of circumstances in which this is useful:
If you download (or purchase) Java applications and want them to run in a sandbox.
If you want to ensure that your internally developed applications all run in the desired sandbox (without having to include that code in every application).
If you have a corporate or campus network and need to distribute Java applications under a new security model. Perhaps the new model will:
Give different security permissions to programs downloaded from within the corporate firewall than those from outside the corporate firewall (without requiring internal classes to be signed)
Authenticate users on the corporate network before allowing sensitive payroll data to be sent (even over the corporate network)
Encrypt that payroll data, so internal spies can't decipher it
Allow the user greater discretion over the resources granted to a particular program
Although the JavaRunner program is designed to run other applications, there is no reason why it cannot be modified to run applets as well. Such a modification would require some extra code to parse the HTML containing the applet tag and set up an instance of the AppletStub and AppletContext classes for the applet itself. We're not showing the code to do that only because it's not really relevant to the discussion of Java security--but the JavaRunner could easily be extended to become an appletviewer (or, with an appropriate Java bean that interprets HTML, a full-fledged browser). The advantage, of course, is that as author of the browser you would have full control over the security model the browser employs.
Here's the basic implementation of the JavaRunner application:
public class JavaRunner implements Runnable { final static int numArgs = 1; private Object args[]; private String className; JavaRunner(String className, Object args[]) { this.className = className; this.args = args; } void invokeMain(Class clazz) { Class argList[] = new Class[] { String[].class }; Method mainMethod = null; try { mainMethod = clazz.getMethod("main", argList); } catch (NoSuchMethodException nsme) { System.out.println("No main method in " + clazz.getName()); System.exit(-1); } try { mainMethod.invoke(null, args); } catch (Exception e) { Throwable t; if (e instanceof InvocationTargetException) t = ((InvocationTargetException) e) .getTargetException(); else t = e; System.out.println("Procedure exited with exception " + t); t.printStackTrace(); } } public void run() { Class target = null; try { target = Class.forName(className); invokeMain(target); } catch (ClassNotFoundException cnfe) { System.out.println("Can't load " + className); } } static Object[] getArgs(String args[]) { String passArgs[] = new String[args.length - numArgs]; for (int i = numArgs; i < args.length; i++) passArgs[i - numArgs] = args[i]; Object wrapArgs[] = new Object[1]; wrapArgs[0] = passArgs; return wrapArgs; } public static void main(String args[]) { if (args.length < 1) { System.err.println("usage: JavaRunner classfile"); System.exit(-1); } ThreadGroup tg = new ThreadGroup("JavaRunner Threadgroup"); Thread t = new Thread(tg, new JavaRunner(args[0], getArgs(args))); t.start(); try { t.join(); } catch (InterruptedException ie) { System.out.println("Thread was interrupted"); } } }
This is a fully functional (if not full-featured) version of the JavaRunner program; we can use it to run our Cat application like this:
piccolo% java JavaRunner Cat /etc/passwd root:x:0:1:0000-Admin(0000):/:/usr/bin/csh daemon:x:1:1:0000-Admin(0000):/: bin:x:2:2:0000-Admin(0000):/usr/bin: ...
This will give us exactly the same results as when we ran the program by hand. The invokeMain() method will use the Java reflection API to find the static main() method of the Cat class and then construct an appropriate argument list to pass to that method. Note that the use of the reflection API introduces a dependency on Java 1.1 for this program. You can write a similar program under Java 1.0, but not without using the native (C) interface to Java.
Note also that we construct a new thread group and thread, and run the main() method under control of that thread. The primary reason we do that will become clear in Chapter 6, "Implementing Security Policies" when we discuss thread security policies. But there's no reason why you couldn't expand this example to run multiple targets simultaneously, in which case each target should have its own thread and thread group anyway.
We've cheated a little bit here by using the forName() method of the Class class to find our target application class--we'll hear more about that in Chapter 3, "Java Class Loaders" when we discuss class loaders. For now, it will suffice to know that this will load our target class (assuming that the target class is found on the CLASSPATH). In addition, we still haven't done anything to set up a security manager or to enable the access controller. As a result, the sandbox for an application run under this program is non-existent: the bytecodes will not be verified, and there will be no restriction on any actions that the application may perform. But this is the example that we'll expand upon during the rest of this book as we add security features to it.
Don't think that the only function of a program like this is to run Java applications (or even Java applets). Consider the Java web server--it must dynamically invoke servlets for different web requests as those requests come in. An RMI server might operate similarly, perhaps even loading the code to perform its operations from a client machine. Although we stick with this example throughout the book, the need for security in server applications parallels the need for security in end-user applications .
Beginning in Java 1.2, the Java platform itself comes with a security model built into applications it runs. This model is based upon information in the user's CLASSPATH. Setting the CLASSPATH is the same operation in Java 1.1 and Java 1.2, but in Java 1.2, classes that are found on the CLASSPATH may optionally be subject to a security model. This allows you to run the application code in a user- or administrator-defined sandbox: in particular, it uses the access controller of Java 1.2 to provide the same security environment for the target application as a Java-enabled browser provides for an applet.
The successful use of this facility depends upon the class loader that the built-in application runner will use, as well as depending upon the environment set up by the access controller and security manager. We'll examine how these facilities interact with this method of running applications in the next few chapters. For now, we'll just outline how this method operates.
As always, Java applications are run on the command line as follows:
piccolo% java Cat /etc/passwd root:x:0:1:0000-Admin(0000):/:/usr/bin/csh daemon:x:1:1:0000-Admin(0000):/: bin:x:2:2:0000-Admin(0000):/usr/bin: ...
This example loads the Cat.class file from the user's CLASSPATH and runs the application with the single argument /etc/passwd. As always, when an application is run in this manner, the sandbox in which the application runs is unlimited: the application can perform any activity it wants to.
There is a very important difference between running these examples in Java 1.1 and running them in 1.2: in 1.2, classes that are loaded from the CLASSPATH will be loaded by a class loader. The addition of the class loader to the CLASSPATH allows us to build a sandbox for the application. However, none of these examples actually builds a sandbox yet. In order to build a sandbox for these examples, we must specify the -Djava.security.manager flag on the command line. This flag enables a security manager and access controller to be installed; we'll discuss the details of this option in Chapter 6, "Implementing Security Policies".
The -Djava.security.manager flag is only available in Java 1.2. Without it, Java applications in 1.2 behave exactly as they do in 1.1: they have a wide-open sandbox.
For historical reasons (and because it makes describing this facility easier), we'll refer to the ability to run applications with an optional argument to specify a sandbox as the Launcher. Given that the Launcher is a standard part of Java, you might ask why we're going to the trouble of implementing our own JavaRunner. One reason is simply to make our discussion clearer: it is easiest to understand the architecture of Java's security policy in the context of JavaRunner. Other reasons have to do with certain limitations that we'll discover about the Launcher:
The Launcher comes only with Java 1.2 and later releases; if you're still using 1.1, you'll have to use the JavaRunner program.
The Launcher can only run classes from the CLASSPATH--it cannot load classes from the network or from another location. However, simply because the program in question is an application does not mean we won't want to load its classes from a server--but we'll need JavaRunner to do that.
The security manager used by the Launcher does not have all the features we might desire. While most of its features are configurable through the access controller (also a feature of Java 1.2), there are certain advanced policies that we cannot configure in that way. These features can only be achieved with some programming on our part.
Hence, both the Launcher and JavaRunner are useful mechanisms for running Java applications; which one you use depends on your particular requirements.
Copyright © 2001 O'Reilly & Associates. All rights reserved.