BUY THIS BOOK

Safari Books Online

What is this?

Looking to Reprint this content?


Java Security
Java Security By Scott Oaks
May 1998
Pages: 469

Cover | Table of Contents


Table of Contents

Chapter 1: Java Application Security
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.
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:
  • Safe from malevolent programs: Programs should not be allowed to harm a user's computing environment. This includes Trojan horses as well as harmful programs that can replicate themselves—computer viruses.
  • Non-intrusive: Programs should be prevented from discovering private information on the host computer or the host computer's network.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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:
  • Safe from malevolent programs: Programs should not be allowed to harm a user's computing environment. This includes Trojan horses as well as harmful programs that can replicate themselves—computer viruses.
  • Non-intrusive: Programs should be prevented from discovering private information on the host computer or the host computer's network.
  • Authenticated: The identity of parties involved in the program should be verified.
  • Encrypted: Data that the program sends and receives should be encrypted.
  • Audited: Potentially sensitive operations should always be logged.
  • Well-defined: A well-defined security specification would be followed.
  • Verified: Rules of operation should be set and verified.
  • Well-behaved: Programs should be prevented from consuming too many system resources.
  • C2 or B1 certified: Programs should have certification from the U.S. government that certain security procedures are included.
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, 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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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
  • Internally, it has access to its local memory (the computer's RAM).
  • Externally, it has access to its filesystem and to other machines on the local network.
  • For running applets, it also has access to a web server, which may be on its local (private) net, or may be on the Internet.
  • Data flows through this entire model, from the user's machine through the network and (possibly) to disk.
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:
  • A sandbox in which the program has access to the CPU, the screen, keyboard, and mouse, and to its own memory. This is the minimal sandbox—it contains just enough resources for a program to run.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
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:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
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.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 2: Java Language Security
The first components of the Java sandbox that we will examine are those components that are built into the Java language itself. These components primarily protect memory resources on the user's machine, although they have some benefit to the Java API as well. Hence, they are primarily concerned with guaranteeing the integrity of the memory of the machine that is hosting a program: in a nutshell, the security features within the Java language want to ensure that a program will be unable to discern or modify sensitive information that may reside in the memory of a user's machine. In terms of applets, these protections also mean that applets will be unable to determine information about each other; each applet is given, in essence, its own memory space in which to operate.
In this chapter, we'll look at the features of the Java language that provide this type of security. We'll also look at how these features are enforced, including a look at Java's bytecode verifier. With a few exceptions, the information in this chapter is largely informational; because the features we are going to discuss are immutable within the Java language, there are fewer programming considerations than we'll find in later chapters. However, the information we'll present here is crucial in understanding the entire Java security story; it is very helpful in ensuring that your Java environment is secure and in assessing the security risks that Java deployment might pose. The security of the Java environment is dependent on the security of each of its pieces, and the Java language forms the first fundamental piece of that security.
As we discuss the language features in this chapter, keep in mind that we're only dealing with the Java language itself—as is the common thread of this book, all security features we're going to discuss do not apply when the language in question is not Java. If you use Java's native interface to run arbitrary C code, that C code will be able to do pretty much anything it wants to do, even when it violates the precepts we're outlining in this chapter.
In this chapter, we're going to be concerned primarily with how Java operates on things that are in memory on a particular machine. Within a Java program, every entity—that is, every object reference and every primitive data element—has an access level associated with it. To review, this access level may be:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Java Language Security Constructs
In this chapter, we're going to be concerned primarily with how Java operates on things that are in memory on a particular machine. Within a Java program, every entity—that is, every object reference and every primitive data element—has an access level associated with it. To review, this access level may be:
  • private: The entity can only be accessed by code that is contained within the class that defines the entity.
  • Default (or package): The entity can be accessed by code that is contained within the class that defines the entity, or by a class that is contained in the same package as the class that defines the entity.
  • protected: The entity can only be accessed by code that is contained within the class that defines the entity, by classes within the same package as the defining class, or by a subclass of the defining class.
  • public: The entity can be accessed by code in any class.
The notion of assigning data entities an access level is certainly not exclusive to Java; it's a hallmark of many object-oriented languages. Since the Java language borrows heavily from C++, it's not surprising that it would borrow the basic notion of these access levels from C++ as well (although there are slight differences between the meanings of these access modifiers in Java and in C++).
As a result of this borrowing, the use of these access modifiers is generally thought of in terms of the advantage such modifiers bring to program design: one of the hallmarks of object-oriented design is that it permits data hiding and data encapsulation. This encapsulation ensures that objects may only be operated upon through the interface the object provides to the world, instead of being operated upon by directly manipulating the object's data elements. These and other design-related advantages are indeed important in developing large, robust, object-oriented systems. But in Java, these advantages are only part of the story.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Enforcement of the Java Language Rules
The list of rules we outlined above are fine in theory, but they must be enforced somehow. We've always been taught that overwriting the end of an array in C code is a bad thing, but I somehow still manage to do it accidentally all the time. There are also those who willfully attempt to overwrite the ends of arrays in an attempt to breach the security of a system. Without mechanisms to enforce these memory rules, they become simply guidelines and provide no sort of security at all.
This necessary enforcement happens at three different times in the development and deployment of a Java program: at compile time, at link time (that is, when a class is loaded into the virtual machine), and at runtime. Not all rules can be checked at each of these points, but certain checks are necessary at each point in order to ensure the memory security that we're after. As we'll see, enforcement of these rules (which is really the construction of this part of the Java sandbox) varies depending on the origin of the class in question.
The Java compiler is the first thing that is tasked with the job of enforcing Java's language rules. In particular, the compiler is responsible for enforcing all of the rules we outlined above except for the last two: the compiler cannot enforce array bound checking nor can it enforce all cases of illegal object casts.
The compiler does enforce certain cases of illegal object casts—namely, casts between objects that are known to be unrelated, such as the following code:
Vector v = new Vector();
String s = (String) v;
But the validity of a cast between an object of type X to type Y where Y is a subclass of X cannot be known at compile time, so the compiler must let such a construct pass.
Okay, the compiler has produced a Java program for us, and we're about to run the Java bytecode of that program. But if the program came from an unknown source, how do we know that the bytecodes we've received are actually legal?
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Summary
Because the notion of security in Java is pervasive, its implementation is equally pervasive. In this chapter, we've explored the security mechanisms that are built into the Java language itself. Essentially, at this level the security mechanisms are concerned with establishing a set of rules for the Java language that creates an environment where an object's view of memory is well-known and well-defined, so that a developer can ensure that items in memory cannot be accidentally or intentionally read, corrupted, or otherwise misused. We also took a brief look at Java's bytecode verifier, including why it is necessary, and why you should turn it on, even for Java applications.
It's important to keep in mind that the purpose of these security constraints is to protect the user's machine from a malicious piece of code and not to protect a piece of code from a malicious user. Java does not (and could not) prevent a user from acting on memory from outside the browser (with possibly harmful results).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 3: Java Class Loaders
In this chapter, we're going to explore Java's class loading mechanism—the mechanism by which files containing Java bytecodes are read into the Java virtual machine and converted into class definitions. The operation of Java programs depends on the class loader; given Java's desire to ensure security throughout its architecture, it should come as no surprise that class loaders are also a very important piece of the Java security story. The class loader normally works in conjunction with the security manager and access controller to provide the bulk of the protections associated with the Java sandbox.
The class loader is important in Java's security model because initially, only the class loader knows certain information about classes that have been loaded into the virtual machine. Only the class loader knows where a particular class originated, and only the class loader knows whether or not a particular class was signed (although the class loader arranges for the Class object itself to carry its signature with it). Hence, one of the keys to writing a secure Java application is to understand the role of the class loader and to write (or at least use) a secure class loader.
We'll address both those points in this chapter. We begin with an overview of how the class loader functions, and the features that its basic functions add to the overall security of the Java platform. We'll then look into writing our own class loader, the motivation for which will vary depending on the release of Java you're using and the type of application you are running.
As with the other elements of the Java sandbox, the ability to create and use a class loader is limited to Java applications. Java applets use the class loader provided for them by the browser in which they are running, and they are generally prohibited from creating their own class loader.
There are two instances where the class loader plays an important role in the Java security model: it must coordinate with Java's security manager or access controller, and it must enforce certain rules about the namespace used by Java classes.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Security and the Class Loader
There are two instances where the class loader plays an important role in the Java security model: it must coordinate with Java's security manager or access controller, and it must enforce certain rules about the namespace used by Java classes.
The class loader must coordinate with the security manager and access controller of the virtual machine in order to determine the security policy for a Java program. We'll explore this in more detail in the next few chapters when we discuss these various security mechanisms; for now, we'll just consider the motivation for the following connection.
As we know, a Java applet cannot (normally) read a file when the applet is being run in a browser such as HotJava. The HotJava browser itself, however, can read files, even while it is also running applets. Both the browser and the applets are using the same classes to (attempt to) read a file, so clearly there must be something that allows the java.io classes to determine that one case should fail while the other case should succeed. That differentiation is the by-product of the class loader: the class loader allows the security manager to find out particular information about the class, which allows the security manager to apply the correct security policy depending on the context of the request. When we discuss the security manager, we'll discuss the specific mechanics by which this can be achieved. For now, it is only important to keep in mind that the class loader is the piece of the Java architecture that is able to make this distinction. Since it loaded the class, it knows if the class came from the network (i.e., the class is part of the applet and should not be trusted) or if the class came from the local filesystem (i.e., the class is part of the browser and should be trusted). It also knows if the class was delivered with a digital signature, and the exact location from which the class was loaded. All these pieces of information may be used by the security manager and access controller to establish a security policy.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Anatomy of a Class Loader
When the Java virtual machine needs access to a particular class, it is up to a class loader to provide the class. The class loader goes through the following steps to load and define a class:
  1. If the class loader has already loaded this class, it should find the previously defined class object and return that object immediately.
  2. The security manager is consulted to see if this program is allowed to access the class in question. If it is not, a security exception is thrown. This step may be considered optional.
  3. Otherwise, an internal class loader is consulted to attempt to load the class from the CLASSPATH. If that succeeds, the class loader returns. This ensures that classes within the Java API will not be superseded by classes loaded from the network (or other location).
    The way this is done varies between 1.1 and 1.2. In 1.1, there is a single method (the findSystemClass() method) that handles this step. In 1.2, a class loader must delegate to another class loader to find classes that are on the CLASSPATH and call the findSystemClass() method to find classes that are in the core API.
  4. The security manager is consulted to see if this program is allowed to create the class in question. If it is not, a security exception is thrown. This step may be considered optional.
  5. The class file is read into an array of bytes. The mechanism by which the class loader reads the file and creates the byte array will vary depending on the class loader (which, after all, is one of the points of having different class loaders).
  6. The byte codes are run through the bytecode verifier.
  7. A Class object is constructed from the bytecodes. In the process, the methods defining the class are created. In Java 1.1 and later, this process also ensures that the name in the class file matches the name that the class loader thought it was asked to load.
  8. Before the class can be used, it must be resolved—which is to say that any classes that it immediately references must also be found by this class loader. The set of classes that are immediately referenced contains any classes that the class extends as well as any classes used by the static initializers of the class. Note that classes that are used only as instance variables, method parameters, or local variables are not normally loaded in this phase: they are loaded when the class actually references them (although certain compiler optimizations may require that these classes be loaded when the class is resolved).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Loading Classes
We'll now explore the details of how a class loader actually loads classes. There is a single method of the ClassLoader class (and all its subclasses) that accomplishes this:
public Class loadClass(String name)
Load and resolve the named class. A ClassNotFoundException is thrown if the class cannot be found.
This is the simplest way to use a class loader directly: it simply requires that the class loader be instantiated and then be used via the loadClass() method. Once the Class object has been constructed, there are three ways in which a method in the class can be executed:
  • A static method of the class can be executed using the native method interface of the Java virtual machine. This is the technique the Java virtual machine uses to execute the main() method of a Java application once the initial class has been loaded, but this is not generally a technique used by Java applications.
  • An object of the class can be constructed using the newInstance() method of the Class class, but only if the class has an accessible constructor that requires no arguments. Once the object has been constructed, methods with well-known signatures can be executed on the object. This is the technique that a program like appletviewer uses: it loads the initial class of the applet, constructs an instance of the applet (which calls the applet's no-argument constructor), and then calls the applet's init() method (among other methods).
  • Starting with JDK 1.1, the reflection API can be used to call a static method on the class, or to construct instances of the object and execute methods on that object. The reflection API allows more flexibility than the second choice, since it allows arguments to be passed to the constructor of the object. This is the technique that is used by our JavaRunner program.
The second case is more commonly implemented, if only because it's simpler (and it is applicable in all versions of Java). But consider the following modifications to our JavaRunner program:
public class JavaRunner implements Runnable {
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Implementing a Class Loader
Part of the security implications of a class loader depend upon its internal implementation. When you implement a class loader, you have two basic choices: you can extend the ClassLoader class, or you can extend the SecureClassLoader class. The second choice is preferred, but it is not an option for Java 1.1. If you're programming in 1.2, you may choose to use the URL class loader rather than implementing your own, but the information in this section will help you understand the security features of the URL class loader. In this section, then, we'll look at how to implement both default and secure class loaders.
Aside from the primordial class loader, all Java class loaders must extend the ClassLoader class (java.lang.ClassLoader). Since the ClassLoader class is abstract, it is necessary to subclass it to create a class loader.

Section 3.4.1.1: Protected methods in the ClassLoader class

In order to implement a class loader, we start with this method:
protected abstract Class loadClass(String name, boolean resolve)
protected Class loadClass(String name, boolean resolve)
Using the rules of the class loader, find the class with the given name and, if indicated by the resolve variable, ensure that the class is resolved. If the class is not found, this method should throw a ClassNotFoundException. This method is abstract in 1.1, but not in 1.2. In 1.2, you typically do not override this method.
The loadClass() method is passed a fully qualified class name (e.g., java.lang.String or com.xyz.XYZPayrollApplet), and it is expected to return a class object that represents the target class. If the class is not a system class, the loadClass() method is responsible for loading the bytes that define the class (e.g., from the network).
There are five final methods (listed below) in the ClassLoader class that a class loader can use to help it achieve its task.
protected final Class defineClass(String name, byte data[], int offset, int length)
protected final Class defineClass(String name, byte data[], int offset, int length, ProtectionDomain pd)
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Extensions to the Class Loader
When we implemented a class loader above, we had a fully operational class loader that paralleled the first class loaders that were used by Java's appletviewer or by a Java-enabled browser. However, there are other extensions to the class loader that are often useful.
We started with a complete class loader suitable for use in appletviewer-type programs where the classes are to be loaded from the network. This is good as far as it goes, but let's delve a little more into the security issues that surround that class loader.
In the world of Java-enabled browsers, an applet can retrieve classes from only one site—the CODEBASE specified in the applet's HTML tag. There are other reasons why an applet can only make a network connection to its CODEBASE (which we'll discuss in Chapter 4), but one of the reasons is contained in the discussion we outlined above: because classes loaded by the same class loader are considered to be in the same package, and an applet that loaded classes from multiple sites could run the risk of classes from different sites interfering with each other.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Miscellaneous Class Loading Topics
There are a few details that we haven't yet covered. These details are not directly related to the security aspects of the class loader, which is why we've saved them until now. If you're interested in the complete details of the class loader, we'll fill in the last few topics here.
Beginning with Java 1.2, class loading follows a delegation model. This new model permits a class loader to be instantiated with this constructor:
protected ClassLoader(ClassLoader delegate)
Create a class loader that is associated with the given class loader. This class loader delegates all operations to the delegate first: if the delegate is able to fulfill the operation, this class loader takes no action. For example, when the class loader is asked to load a class via the loadClass() method, it first calls the loadClass() method of the delegate. If that succeeds, the class returned by the delegate will ultimately be returned by this class. If that fails, the class loader then uses its original logic to complete its task:
public Class loadClass(String name) {
	Class cl;
	cl = delegate.loadClass(name);
	if (cl != null)
		return cl;
	// else continue with the loadClass() logic 
}
You may retrieve the delegate associated with a class loader with the following method.
public final ClassLoader getParent()
Return the class loader to which operations are being delegated. If there is no such class loader, return null.
You'll notice that we used delegation in all of our examples. This is pretty much a requirement: when the virtual machine starts, it creates a URL class loader that is based on the directories and JAR files present in your CLASSPATH. That class loader is the class loader that will be used to load the first class in your application (i.e., the JavaRunner class in our example).
That URL class loader is the only class loader that knows about the CLASSPATH. If the application will reference any other classes that are part of the CLASSPATH, you will be unable to find them unless you use the delegation model of class loading: the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Summary
The class loading mechanism is integral to Java's security features. Typically this integration is considered in light of the relationship between the class loader and the security manager. However, the class loader is important in its own right. The class loader must enforce the namespace separation between classes that are loaded from different sites (especially when these different sites are untrusted). Newer versions of the class loader (in Java 1.2) provide an easier route for developers of class loaders, and they provide more hooks into the access controller.
For sites that need a more flexible security policy, a different class loader may be desirable. For example, a class loader that allows programs within a protected, internal network to load class files from several machines on that internal network is particularly useful for extending the advantages that the Java model brings to program distribution. Other variations on this theme are possible—as long as the implementor remembers to keep the security requirements of Java's namespace model in mind when such variations are designed.
In the next chapters, we'll look in depth at Java's security manager and Java's protection domains, and see how the class loader and these features together further enforce Java's security policies.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 4: The Security Manager Class
When most people think of Java security, they think of the protections afforded to a Java program—and, more particularly, only by default to a Java applet—by Java's security manager. As we've seen, there are other important facets of Java's security story, but the role played by the security manager is of paramount importance in the degree to which your machine will be safe from malicious Java programs.
On one level, the Java security manager is simple to understand, and it's often summarized by saying that it prevents Java applets from accessing your local disk or local network. The real story is more complicated than that, however, with the result that Java's security manager is often misunderstood. In this chapter, we'll look into how the security manager actually works, what it can and can't do, and when it does—and doesn't—protect you. In this chapter, we're only going to look at the security manager in terms of its capabilities, with an emphasis on how those capabilities are used by popular browsers; we'll look into writing our own security manager in the next few chapters.
On a simple level, the security manager is responsible for determining most of the parameters of the Java sandbox—that is, it is ultimately up to the security manager to determine whether many particular operations should be permitted or rejected. If a Java program attempts to open a file, the security manager decides whether or not that operation should be permitted. If a Java program wants to connect to a particular machine on the network, it must first ask permission of the security manager. If a Java program wants to alter the state of certain threads, the security manager will intervene if such an operation is considered dangerous.
The security manager is of particular concern to authors and users of Java applets. In general, Java applications do not have security managers—unless the author of the application has provided one. Historically, that's been a somewhat unusual occurrence, even though there are many times when you might want a security manager in your Java application; this stems from the fact that before Java 1.2, writing a security manager was more difficult than it is now. Beginning in 1.2, there is a default, user-configurable security manager that is suitable for most applications, one which can even be installed via a command-line argument when starting an application. This brings the benefits of a security manager to an application without requiring any programming. And we'll show how to write your own (non-default) security manager for the
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Overview of the Security Manager
On a simple level, the security manager is responsible for determining most of the parameters of the Java sandbox—that is, it is ultimately up to the security manager to determine whether many particular operations should be permitted or rejected. If a Java program attempts to open a file, the security manager decides whether or not that operation should be permitted. If a Java program wants to connect to a particular machine on the network, it must first ask permission of the security manager. If a Java program wants to alter the state of certain threads, the security manager will intervene if such an operation is considered dangerous.
The security manager is of particular concern to authors and users of Java applets. In general, Java applications do not have security managers—unless the author of the application has provided one. Historically, that's been a somewhat unusual occurrence, even though there are many times when you might want a security manager in your Java application; this stems from the fact that before Java 1.2, writing a security manager was more difficult than it is now. Beginning in 1.2, there is a default, user-configurable security manager that is suitable for most applications, one which can even be installed via a command-line argument when starting an application. This brings the benefits of a security manager to an application without requiring any programming. And we'll show how to write your own (non-default) security manager for the JavaRunner program in Chapter 6.
But this point cannot be overemphasized: Java applications (at least by default) have no security manager, while Java applets (again, by default) have a very strict security manager. This leads to a common misconception that exists in the arena of Java security: it's common to think that because Java is said to be secure, it is always secure, and that running Java applications that have been installed locally is just as secure as running Java applets inside a Java-enabled browser. Nothing is further from the truth.
To illustrate this point, consider the following malicious code:
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Trusted and Untrusted Classes
In the discussion that follows, we make the distinction between trusted and untrusted classes. Generally, an implementation of a security manager allows more operations for trusted classes than for untrusted classes. Whether or not a class is trusted is a complex decision based upon many factors—not the least of which is the release of Java under which the program is running. The default notion of what constitutes a trusted class has changed significantly between releases of Java:
  • In Java 1.0, a class that is loaded from the CLASSPATH is considered trusted, and a class that is loaded from a class loader is considered untrusted.
  • In Java 1.1, that same rule applies, but a class that is loaded from a JAR file may carry with it a digital signature that allows it to be given extra privileges.
  • In Java 1.2, a class that is loaded from the core API is considered trusted and may perform any operation it wants to. Otherwise, classes are (by default) given privileges based upon where they were loaded from, including if they were loaded from the CLASSPATH. However, this applies only when certain command-line arguments are present; in the default method of loading applications, items from the CLASSPATH are generally considered trusted.
Nothing inherent in the design of the security manager requires security to be enforced as an all-or-nothing proposition for each class. It's possible to write a security manager that gives access to certain parts of the filesystem only to certain classes (even classes that came from the network), or to write a security manager that prohibits classes loaded from the CLASSPATH from performing operations that are normally permitted to classes loaded from the filesystem. A security manager can be as simple or as sophisticated as its author desires, with the result that the security manager can enforce a simple binary yes-or-no policy for operations, or it can enforce a very specialized, very detailed policy. This is true of all security managers in all versions of Java, though as we'll see in Chapter 5, one of the prime benefits of Java 1.2 is that it makes it much easier to achieve fine-grained security policies.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Using the Security Manager
We're now going to examine the public methods of the security manager so that we may understand how the security manager is used by applications and by the Java API.
There are two methods in the System class that are used to work with the security manager itself:
public static SecurityManager getSecurityManager()
Return a reference to the currently installed security manager object (or null if no security manager is in place). Once obtained, this object can be used to test against various security policies.
public static void setSecurityManager(SecurityManager sm)
Set the system's security manager to the given object. This method can only be called once, and once installed, the security manager cannot be removed. Attempting to call this method after a security manger has already been installed will result in a SecurityException.
These methods operate with the understanding that there is a single security manager in the virtual machine; the only operations that are possible on the security manager are setting it (that is, creating an instance of the security manager class and telling the virtual machine that the newly created object should be the security manager), and getting it (that is, asking the virtual machine to return the object that is the security manager so that a method might be invoked upon it).
We've already seen how you might use the getSecurityManager() method to retrieve the security manager and invoke an operation on it. Setting the security manager is a predictably simple operation:
public class TestSecurityManager {
    public static void main(String args[]) {
		System.setSecurityManager(new SecurityManagerImpl());
                ...do the work of the application...
    }
}
However, there's an important detail here: the setSecurityManager() method is written in such a way that it can only be called once. Once a particular security manager has been installed, that security manager will be used by every other class that runs in this virtual machine. Once the policy is established, it cannot be changed (although the policy itself might be very fluid).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Summary
In this chapter, we've had an overview of the most commonly known feature of Java's security story: the security manager. The security manager is responsible for arbitrating access to what we normally consider operating system features—files, network sockets, printers, etc. The goal of the security manager is to grant access to each class according to the amount of trust the user has in the class. Often, that means granting full access to trusted classes (that is, classes that have been loaded from the filesystem) while limiting access when the access is requested from an untrusted class (that is, a class that has been loaded from the network).
Although the security manager is the most commonly known feature of Java's security story, it's often misunderstood: there is no standard security manager among Java implementations, and Java applications, by default, have no security manager at all. Even with the popular Java-enabled browsers, the user often has latitude in what protections the security manager will be asked to enforce.
We examined in this chapter all the times when the security manager is asked to make a decision regarding access; such decisions range from the expected file and network access to more esoteric decisions, such as whether a frame needs a warning banner or what thread group a particular thread should belong to. This gave us a basic understanding of how the security manager can be used to enforce a specific policy, and the issues involved when defining such a policy. This knowledge will be used as a basis in the next few chapters, when we'll look at how to implement our own security manager.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Chapter 5: The Access Controller
In this chapter, we're going to examine Java's access controller. While the security manager is the key to the security model of the Java sandbox, the access controller is the mechanism that the security manager actually uses to enforce its protections. The security manager may be king, but the access controller is really the power behind the throne.
The access controller is actually somewhat redundant. The purpose of the security manager is to determine whether or not particular operations should be permitted or denied. The purpose of the access controller is really the same: it decides whether access to a critical system resource should be permitted or denied. Hence, the access controller can do everything the security manager can do.
The reason there is both an access controller and a security manager is mainly historical: the access controller is only available in Java 1.2 and subsequent releases. Before the access controller existed, the security manager had to rely on its internal logic to determine the security policy that should be in effect, and changing the security policy required changing the security manager itself. Starting with 1.2, the security manager is able to defer these decisions to the access controller. Since the security policy enforced by the access controller can be specified in a file, this allows a much more flexible mechanism for determining policies. The access controller also gives us a much simpler method of granting fine-grained, specific permissions to specific classes. That process was theoretically possibly with the security manager alone, but it was simply too hard to implement.
But the large body of pre-1.2 Java programs dictates that the primary interface to system security—that is, the security manager—cannot change; otherwise, existing code that implements or depends on the security manager would become obsolete. Hence, the introduction of the access controller did not replace the security manager—it supplemented the security manager. This relationship is illustrated in Figure 5.1. Typically, an operation proceeds through the program code into the Java API, through the security manager to the access controller, and finally into the operating system. In certain cases, however, the security manager may bypass the access controller. And native libraries are still outside the domain of either the security manager or the access controller (although the ability to load those libraries may be restricted, as we've seen).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The CodeSource Class
When we examined class loaders, we introduced the notion of a code source. A code source is a simple object that merely reflects the URL from which a class was loaded and the keys (if any) that were used to sign that class. The SecureClassLoader class (and its subclasses) are responsible for creating and manipulating these code source objects.
The CodeSource class (java.security.CodeSource ) has a few interesting methods:
public CodeSource(URL url, Certificate cers[])
Create a code source object for code that has been loaded from the specified URL. The optional array of certificates is the array of public keys that have signed the code that was loaded from this URL. These certificates are typically obtained from reading a signed JAR file, which we'll show in Chapter 12; if the code was not signed, this argument should be null.
public boolean equals(Object o)
Two code source objects are considered equal if they were loaded from the same URL (that is, the equals() method for the URL of the objects returns true) and the array of certificates is equal (that is, a comparison of each certificate in the array of certificates will return true).
public final URL getLocation()
Return the URL that was passed to the constructor of this object.
public final Certificate[] getCertificates ()
Return a copy of the array of certificates that were used to construct this code source object. The original certificates are not returned so that they cannot be modified accidentally (or maliciously).
public boolean implies(CodeSource cs)
Determine if the code source implies the parameter according to the rules of the Permission class (see later in this chapter). One code source implies another if it contains all the certificates of the parameter and if the URL of the parameter is implied by the URL of the target.
That's the extent of the CodeSource class. When we discussed the SecureClassLoader class in Chapter 3, we showed that the defineClass() method expected a
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Permissions
The basic entity that the access controller operates on is a permission object—an instance of the Permission class (java.security.Permission). The Permission class itself is an abstract class that represents a particular operation. The nomenclature here is a little misleading, because a permission object can reflect two things. When it is associated with a class (through a code source and a protection domain), a permission object represents an actual permission that has been granted to that class. Otherwise, a permission object allows us to ask if we have a specific permission.
For example, if we construct a permission object that represents access to a file, possession of that object does not mean that we have permission to access the file. Rather, possession of the object allows us to ask if we have permission to access the file.
An instance of the Permission class represents one specific permission. A set of permissions—e.g., all the permissions that are given to classes signed by a particular individual—is represented by an instance of the Permissions class (java.security.Permissions). As developers and administrators, we'll make extensive use of these classes, so we'll need to investigate them in depth.
Permissions have three properties:
A type
All permissions carry a basic type that identifies what the permission pertains to. A permission object to access a file will have a type of FilePermission; an object to create a window will have a type of AWTPermission; permission to use the XYZ company payroll application would have a type of XYZPayrollPermission.
A name
All permissions have a name that identifies the specific object that a permission relates to. A FilePermission has a name that is the name of the file to be accessed; an AWTPermission to create a window has a name of showWindowWithoutWarningBanner; permission to access a particular employee's payroll record would have the name of that employee. Names are often based on wildcards, so that a single file permission object may represent permission to access several files, and so on.
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
The Policy Class
The third building block for the access controller is the facility to specify which permissions should apply to which code sources. We call this global set of permissions the security policy; it is encapsulated by the Policy class (java.security.Policy).
public abstract class Policy
Establish the security policy for a Java program. The policy encapsulates a mapping between code sources and permission objects in such a way that classes loaded from particular locations or signed by specific individuals have the set of specified permissions.
A policy class is constructed as follows:
public Policy()
Create a policy class. The constructor should initialize the policy object according to its internal rules (e.g., by reading the java.policy file, as we'll describe later).
Like the security manager, only a single instance of the policy class can be installed in the virtual machine at any time. However, unlike the security manager, the actual instance of the policy class can be replaced. These two methods install and retrieve the policy:
public static Policy getPolicy()
Return the currently installed policy object.
public static void setPolicy(Policy p)
Install the given policy object, replacing whatever policy object was previously installed.
Getting and setting the policy object requires going through the checkProperty() method of the security manager. By default, this succeeds only if you already have been granted a security permission with the name of getPolicy or setPolicy (as appropriate). There's a bootstrapping issue involved when setting the policy, since granting permissions requires the policy to have been set. Hence, the initial policy is typically set by a class in the core API, as those classes always have permission to perform any operation.
There are two other methods in the Policy class:
public abstract Permissions getPermissions(CodeSource cs)
Create a permissions object that contains the set of permissions that should be granted to classes that came from the given code source (i.e., loaded from the code source's URL and signed by the keys in the code source).
Additional content appearing in this section has been removed.
Purchase this book now or read it online at Safari to get the whole thing!
Protection Domains
A protection domain is a grouping of a code source and permissions—that is, a protection domain represents all the permissions that are granted to a particular code source. In the default implementation of the Policy class, a protection domain is one grant entry in the file. A protection domain is an instance of the ProtectionDomain class (java.security.ProtectionDomain ) and is constructed as follows:
public ProtectionDomain(CodeSource cs, PermissionCollection p)
Construct a protection domain based on the given code source and set of permissions.
When associated with a class, a protection domain means that the given class was loaded from the site specified in the code source, was signed by the public keys specified in the code source, and should have permission to perform the set of operations represented in the permission collection object. Each class in the virtual machine may belong to one and only one protection domain, which is set by the class loader when the class is defined.
However, not all class loaders have a specific protection domain associated with them: classes that are loaded by the primordial class loader have no protection domain. In particular, this means that classes that exist as part of the system class path (that is, the Java API classes) have no explicit protection domain. We can think of these classes as belonging to the system protection domain.
A protection domain is set for a class inside the defineClass() method. A protection domain is assigned to a class depending upon one of the following cases:
  • The defineClass() method accepts a protection domain as a parameter. In this case, the given protection domain is assigned to the class. This case is typically unused, since that method exists in only the ClassLoader class and not in the SecureClassLoader class.
  • The defineClass() method accepts a code source as a parameter. In this case, the getPermissions() method of the SecureClassLoader is used to determine the protection domain for the code source. By default, this just uses the
Additional content appearing in this section has been removed.
Purchase this book now or