Search the Catalog
Java Security

Java Security

By Scott Oaks
1st Edition May 1998
1-56592-403-7, Order Number: 4037
474 pages, $34.95

Chapter 1.
Java Application Security

In this chapter:
What Is Security?
The Java Sandbox
Applications, Applets, and Programs
Running a Java Application

When Java was first released by Sun Microsystems, it attracted the attention of programmers throughout the world. These developers were attracted to Java for different reasons: some were drawn to Java because of its cross-platform capabilities, some because of its ease of programming (especially compared to object-oriented languages like C++), some because of its robustness and memory management, some because of Java's security, and some for still other reasons.

Just as different developers came to Java with different expectations, so too did they bring different expectations as to what was meant by the ubiquitous phrase "Java is secure." Security means different things to different people, and many developers who had certain expectations about the word "security" were surprised to find that their expectations were not necessarily shared by the designers of Java.

This book discusses the features of Java that make it secure. In this book, we'll discuss why Java is said to be secure, what that security means (and doesn't mean), and--most importantly--how to use the security features of the Java platform within your own programs. This last point is actually the focus of this book: while some of Java's security features are automatically a part of all Java programs, many of them are not. In this book, we'll learn about all those features, and how to utilize them in our own Java applications.

What Is Security?

The first thing that we must do to facilitate our discussion of Java security is to discuss just what Java's security goals are. The term "security" is somewhat vague unless it is discussed in some context; different expectations of the term "security" might lead us to expect that Java programs would be:

In fact, while all of these features could be part of a secure system, only the first two were within the province of Java's 1.0 default security model. Other items in the list have been introduced in later versions of Java: authentication was added in 1.1, encryption is available as an extension to 1.2,[1] and auditing can be added to any Java program by providing an auditing security manager. Still others of these items will be added in the future. But the basic premise remains that Java security was originally and fundamentally designed to protect the information on a computer from being accessed or modified (including a modification that would introduce a virus) while still allowing the Java program to run on that computer.

The point driving this notion of security is the new distribution model for Java programs. One of the driving forces behind Java, of course, is its ability to download programs over a network and run those programs on another machine within the context of a Java-enabled browser (or within the context of other Java applications). Coupled with the widespread growth of Internet use--and the public-access nature of the Internet--Java's ability to bring programs to a user on an as-needed, just-in-time basis has been a strong reason for its rapid deployment and acceptance.

The nature of the Internet created a new and largely unprecedented requirement for programs to be free of viruses and Trojan horses. Computer users had always been used to purchasing shrink-wrapped software. Many soon began downloading software via ftp or other means and then running that software on their machines. But widespread downloading also led to a pervasive problem of malevolent attributes both in free and (ironically) in commercial software (a problem which continues unabated). The introduction of Java into this equation had the potential to multiply this problem by orders of magnitude, as computer users now download programs automatically and frequently.

For Java to succeed, it needed to circumvent the virus/trojan horse problems that plagued other models of software distribution. Hence, the early work on Java focused on just that issue: Java programs are considered safe because they cannot install, run, or propagate viruses, and because the program itself cannot perform any action that is harmful to the user's computing environment. And in this context, safety means security. This is not to say that the other issues in the above list are not important--each has its place and its importance (in fact, we'll spend a great deal of time in this book on the third and fourth topics in that list). But the issues of protecting information and preventing viruses were considered most important; hence, features to provide that level of security were the first to be adopted. Like all parts of Java, its security model is evolving (and has evolved through its various releases); many of the notions about security in our list will eventually make their way into Java.

One of the primary goals of this book, then, is to explain Java's security model and its evolution through releases. In the final analysis, whether or not Java is secure is a subjective judgment that individual users will have to make based on their own requirements. If all you want from Java is freedom from viruses, any release of Java should meet your needs. If you need to introduce authentication or encryption into your program, you'll need to use a 1.1 or later release of Java. If you have a requirement that all operations be audited, you'll need to build that auditing into your applications. If you really need conformance with a U.S. government-approved definition of security, Java is not the platform for you. We take a very pragmatic view of security in this book: the issue is not whether a system that lacks a particular feature qualifies as "secure" according to someone's definition of security. The issue is whether Java possesses the features that meet your needs.

When Java security is discussed, the discussion typically centers around Java's applet-based security model--the security model that is embodied by Java-enabled browsers. This model is designed for the Internet. For many users, this is not necessarily the most appropriate model: it is somewhat restrictive, and the security concerns on a private, corporate network are not the same as those on the Internet.

In this book, we take a different tack: the goal of this book is to show how to use the security model and how to write your own secure Java applications. While some of the information we present will be applicable to a browser environment, the security of any particular browser is ultimately up to the provider of the browser. Some browsers allow us to change the security policy the browser uses, but many do not. Hence, reading about the security manager in this book may help you understand how a particular browser works (and why it works that way), but that won't necessarily allow you to change the security model provided by that browser.

The Java Sandbox

Discussions of Java's security model often center around the idea of a sandbox model. The idea behind this model is that when you allow a program to be hosted on your computer, you want to provide an environment where the program can play (i.e., run), but you want to confine the program's play area within certain bounds. You may decide to give the program certain toys to play with (i.e., you may decide to let it have access to certain system resources), but in general, you want to make sure that the program is confined to its sandbox.

This analogy works better when you consider it from the view of a close relative rather than from the view of a parent. If you're a parent, you probably consider the purpose of a sandbox to be to provide a safe environment for your child to play in. When my niece Rachel visits me, however, I consider the purpose of a sandbox not (only) to be to protect her, but also to protect my grandmother's china from her. I love my niece, but I can't give her leave to run through my house; I enjoy running the latest cool applet on the Internet, but I can't give it leave to run through my filesystem.

The Java sandbox is responsible for protecting a number of resources, and it does so at a number of levels. Consider the resources of a typical machine as shown in Figure 1-1. The user's machine has access to many things:

Figure 1-1. A machine has access to many resources

 

Each of these resources needs to be protected, and those protections form the basis of Java's security model.

We can imagine a number of different-sized sandboxes in which a Java program might run:

The sandbox, then, is not a one-size-fits-all model. Expanding the boundaries of the sandbox is always based on the notion of trust: when my one-year-old niece comes to visit, there's very little in the sandbox for her to play with, but when my six-year-old godchild comes to visit, I trust that I might give her more things to play with. In the hands of some visitors, a toy with small removable parts would be dangerous, but when I trust the recipient, it's perfectly reasonable to include that item in the sandbox. And so it is with Java programs: in some cases, I might trust them to access my filesystem; in other cases, I might trust them to access only part of my filesystem; and in still other cases, I might not trust them to access my filesystem at all.

Applications, Applets, and Programs

It's no accident that this chapter has the word "application" in its title, because the Java security model is solely at the discretion of a Java application. When an applet runs inside the HotJava browser, HotJavaTM is the Java application that has determined the security policy for that applet. And although other popular browsers are not written in Java, they play the role of a Java application: it is still the case that the choice of security model is up to the browser and cannot be changed by the applet.

This makes the distinction between applications and applets a crucial one: applications can establish and modify their security policies while applets (generally) cannot. However, this distinction has diminished over time. Beginning with Java 1.2, users of Java applications have the opportunity to run an application within a sandbox that the user or system administrator has constructed. In the next section, we'll see how the same functionality can be achieved with Java 1.1 as well. Under these scenarios, the Java security model for applications is solely at the discretion of the user or system administrator.

This is a major change of perception for many users and developers of Java, who are used to considering the security differences between applets and applications as a significant differentiator between the two types of programs. There will, of course, always be particular programming differences between applets and applications: an applet extends the java.applet.Applet class and is written as a series of callbacks, while an application can be any class that has a static method called main(). When this programming distinction is important, we'll use the terms "applet" and "application" as appropriate. But we'll typically use the term "program" to refer to the Java code that we're running.

Anatomy of a Java Application

The anatomy of a typical Java application is shown in Figure 1-2. Each of the features of the Java platform that appears in a rectangle plays a role in the development of the Java sandbox. In particular, the elements of the Java sandbox are comprised of:

Figure 1-2. Anatomy of a Java application

 

The bytecode verifier
The bytecode verifier ensures that Java class files follow the rules of the Java language. In terms of resources, the bytecode verifier helps enforce memory protections for all Java programs. As the figure implies, not all files are subject to bytecode verification.

The class loader
One or more class loaders load classes that are not found on the CLASSPATH. In 1.2, class loaders are responsible for loading classes that are found on the CLASSPATH as well.

The access controller
In Java 1.2, the access controller allows (or prevents) most access from the core API to the operating system.

The security manager
The security manager is the primary interface between the core API and the operating system; it has the ultimate responsibility for allowing or preventing access to all system resources. In 1.2, the security manager uses the access controller for most (but not all) of those decisions; in 1.0 and 1.1, the security manager is solely responsible for those decisions.

The security package
The security package (that is, classes in the java.security package) forms the basis for authenticating signed Java classes. Although it is only a small box in this diagram, the security package is a complex API, and discussion of it is broken into several chapters of this book. This includes discussions of:

  • The security provider interface--the means by which different security implementations may be plugged into the security package
  • Message digests
  • Keys and certificates
  • Digital signatures
  • Encryption (an optional extension to the security package)
  • The security package was initially available in Java 1.1.

    The key database
    The key database is a set of keys used by the security manager and access controller to verify the digital signature that accompanies a signed class file. In the Java architecture, it is part of the security package, though it may be manifested as an external file or database.

    The last two items in this list have broad applicability beyond expanding the Java sandbox. With respect to the sandbox, digital signatures play an important role, because they provide authentication of who actually provided the Java class. As we'll see, this provides the ability for end users and system administrators to grant very specific privileges to individual classes or signers. But a digital signature might be used for other applications. Let's say that you're deploying a payroll application throughout a large corporation. When an employee sends a request to view his payroll information, you really want to make sure that the request came from that employee rather than from someone else in the corporation. Often, this type of application is secured by a simple password, but a more secure system could require a digitially signed request before it sent out the payroll information.

    We'll discuss security concerns in both these contexts in this book. In particular, two different examples will form the theme of the examples that are developed through this book:

    We'll develop a full implementation of the first of these examples; while we won't provide a complete payroll application, we will provide a number of examples of the security features required for such an application.

    Running a Java Application

    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]

    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.

    Security and the Operating System

    The security policy imposed by Java is augmented by the security features of the operating system on which Java is running. A Java application with a wide-open security policy may attempt to read the password file, but if the user running the application does not normally have permission to read the password file, the Java application will not succeed.

    The actual security policy that is in effect for a Java application will be the intersection of the security policy built into the application and the security policy of the operating system when the application is run. For the purposes of this book, we ignore the security features that the operating system may provide.

    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:

    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.

    Outline of the JavaRunner Application

    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 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 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.

    Built-in Java Application Security

    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.

    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:

    Secure Applications in 1.2 and 1.2 beta 2

    In releases of 1.2 up through beta 2, running a secure application requires use of a special class: the Launcher class (sun.misc.Launcher). To run an application under control of the Launcher, you would execute this command:

    piccolo% java sun.misc.Launcher Cat /etc/passwd
    

    In 1.2 beta, classes that are loaded from the CLASSPATH are not subject to the sandbox. In order to load those classes through a class loader and subject them to the sandbox, you must specify an alternate classpath for the classes that make up the application:

    piccolo% java -Djava.app.class.path=/classes sun.misc.Launcher \
    Cat /etc/passwd
    

    If the Cat class is found in /classes, it will be subject to the sandbox. If it is found in the CLASSPATH, it will not.

    Beginning in 1.2 beta 3, the Launcher class was incorporated into the virtual machine itself, but the syntax to use it changed in the last few beta releases. In FCS, the correct syntax is:

    piccolo% java -Djava.security.manager Cat /etc/passwd
    

    Hence, both the Launcher and JavaRunner are useful mechanisms for running Java applications; which one you use depends on your particular requirements.

    Summary

    Security is a multifaceted feature of the Java platform. There are a number of facilities within Java that allow you to write a Java application that implements a particular security policy, and this book will focus on each of those facilities in turn. Java-enabled browsers (including those like HotJava that are written in Java) are the ultimate proof of these features: these browsers have used the features of the Java platform to allow users to download and run code on their local systems without fear of viruses or other corruption.

    But the security features of Java need not be limited to the protections afforded to Java applets running in a browser: they can be applied as necessary to your own Java applications. This is done most easily by incorporating those features into a framework designed to run Java applications within a specified sandbox. The ability to define and modify that framework is one of the primary examples of this book. In addition, the security package allows us to create applications that use generic security features--such as digital signatures--for many purposes aside from expanding the Java sandbox. This other use of the security package will also be a constant theme throughout this book.

    In the next chapter, we'll look into the security features of the Java language itself--the first set of security features that are available to any Java application.


    1. 1.2 is now Java 2.

    2. See, for example, the end of Chapter 6.

    Back to: Java Security


    O'Reilly Home | O'Reilly Bookstores | How to Order | O'Reilly Contacts
    International | About O'Reilly | Affiliated Companies

    © 2001, O'Reilly & Associates, Inc.
    webmaster@oreilly.com