As I noted at the beginning of this chapter, the heart of the Java security architecture is access control: untrusted code simply must not be granted access to the sensitive parts of the Java API that would allow it to do malicious things. As we'll discuss in the following sections, the Java access-control model evolved significantly between Java 1.0 and Java 1.2. The Java 1.2 access-control model is relatively stable; it has not changed significantly in Java 1.3.
In this first release of Java, all Java code installed locally on the system is trusted implicitly. All code downloaded over the network, however, is untrusted and run in a restricted environment playfully called "the sandbox." The access-control policies of the sandbox are defined by the currently installed java.lang.SecurityManager object. When system code is about to perform a restricted operation, such as reading a file from the local filesystem, it first calls an appropriate method (such as checkRead()) of the currently installed SecurityManager object. If untrusted code is running, the SecurityManager throws a SecurityException that prevents the restricted operation from taking place.
The most common user of the SecurityManager class is a Java-enabled web browser, which installs a SecurityManager object to allow applets to run without damaging the host system. The precise details of the security policy are an implementation detail of the web browser, of course, but applets are typically restricted in the following ways:
An applet cannot read, write, rename, or delete files. It cannot query the length or modification date of a file or even check whether a given file exists. Similarly, an applet cannot create, list, or delete a directory.
An applet cannot connect to or accept a connection from any computer other than the one it was downloaded from. It cannot use any privileged ports (i.e., ports below and including port 1024).
An applet cannot perform system-level functions, such as loading a native library, spawning a new process, or exiting the Java interpreter. An applet cannot manipulate any threads or thread groups, except for those it creates itself. In Java 1.1 and later, applets cannot use the Java Reflection API to obtain information about the non-public members of classes, except for classes that were downloaded with the applet.
An applet cannot access certain graphics- and GUI-related facilities. It cannot initiate a print job or access the system clipboard or event queue. In addition, all windows created by an applet typically display a prominent visual indicator that they are "insecure," to prevent an applet from spoofing the appearance of some other application.
An applet cannot read certain system properties, notably the user.home and user.dir properties, that specify the user's home directory and current working directory.
An applet cannot circumvent these security restrictions by registering a new SecurityManager object.
Suppose that an applet (or some other untrusted code running in the sandbox) attempts to read the contents of the file /etc/passwd by passing this filename to the FileInputStream() constructor. The programmers who wrote the FileInputStream class were aware that the class provides access to a system resource (a file), so use of the class should therefore be subject to access control. For this reason, they coded the FileInputStream() constructor to use the SecurityManager class.
Every time FileInputStream() is called, it checks to see if a SecurityManager object has been installed. If so, the constructor calls the checkRead() method of that SecurityManager object, passing the filename (/etc/passwd, in this case) as the sole argument. The checkRead() method has no return value; it either returns normally or throws a SecurityException. If the method returns, the FileInputStream() constructor simply proceeds with whatever initialization is necessary and returns. Otherwise, it allows the SecurityException to propagate to the caller. When this happens, no FileInputStream object is created, and the applet does not gain access to the /etc/passwd file.
Java 1.1 retains the sandbox model of Java 1.0, but adds the java.security package and its digital signature capabilities. With these capabilities, Java classes can be digitally signed and verified. Thus, web browsers and other Java installations can be configured to trust downloaded code that bears a valid digital signature of a trusted entity. Such code is treated as if it were installed locally, so it is given full access to the Java APIs. In this release, the javakey program manages keys and digitally signs JAR files of Java code. Although Java 1.1 adds the important ability to trust digitally signed code that would otherwise be untrusted, it sticks to the basic sandbox model: trusted code gets full access and untrusted code gets totally restricted access.
Java 1.2 introduces major new access-control features into the Java security architecture. These features are implemented by new classes in the java.security package. The Policy class is one of the most important: it defines a Java security policy. A Policy object maps CodeSource objects to associated sets of Permission objects. A CodeSource object represents the source of a piece of Java code, which includes both the URL of the class file (and can be a local file) and a list of entities that have applied their digital signatures to the class file. The Permission objects associated with a CodeSource in the Policy define the permissions that are granted to code from a given source. Various Java APIs includes subclasses of Permission that represent different types of permissions. These include java.lang.RuntimePermission, java.io.FilePermission, and java.net.SocketPermission, for example.
Under this new access-control model, the SecurityManager class continues to be the central class; access-control requests are still made by invoking methods of a SecurityManager. However, the default SecurityManager implementation now delegates most of those requests to a new AccessController class that makes access decisions based on the Permission and Policy architecture.
The new Java 1.2 access-control architecture has several important features:
Code from different sources can be given different sets of permissions. In other words, the new architecture supports fine-grained levels of trust. Even locally installed code can be treated as untrusted or partially untrusted. Under this new architecture, only system classes and standard extensions run as fully trusted.
It is no longer necessary to define a custom subclass of SecurityManager to define a security policy. Policies can be configured by a system administrator by editing a text file or using the new policytool program.
The new architecture is not limited to a fixed set of access control methods in the SecurityManager class. New Permission subclasses can be defined easily to govern access to new system resources (which might be exposed, for example, by new standard extensions that include native code).
Let's return to the example of an applet that attempts to create a FileInputStream to read the file /etc/passwd. In Java 1.2, the FileInputStream() constructor behaves exactly the same as it does in Java 1.0 and Java 1.1: it looks to see if a SecurityManager is installed and, if so, calls its checkRead() method, passing the name of the file to be read.
What's new in Java 1.2 is the default behavior of the checkRead() method. Unless a program has replaced the default security manager with one of its own, the default implementation creates a FilePermission object to represent the access being requested. This FilePermission object has a target of "/etc/passwd" and an action of "read". The checkRead() method passes this FilePermission object to the static checkPermission() method of the java.security.AccessController class.
It is the AccessController and its checkPermission() method that do the real work of access control in Java 1.2. The method determines the CodeSource of each calling method and uses the current Policy object to determine the Permission objects associated with it. With this information, the AccessController can determine whether read access to the /etc/passwd file should be allowed.
The Permission class represents both the permissions granted by a Policy and the permissions requested by a method like the FileInputStream() constructor. When requesting a permission, Java typically uses a FilePermission (or other Permission subclass) with a very specific target, like "/etc/passwd". When granting a permission, however, a Policy commonly uses a FilePermission object with a wildcard target, such as "/etc/*", to represent many files. One of the key features of a Permission subclass such as FilePermission is that it defines an implies() method that can determine whether permission to read "/etc/*" implies permission to read "/etc/passwd".
Copyright © 2001 O'Reilly & Associates. All rights reserved.