Team LiB
Previous Section Next Section

6.4. Access Control

As we 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. Since then, the access control model has been relatively stable; it has not changed significantly since Java 1.2. The next sections provide a brief history of the evolution of Java security as it developed from Java 1.0 to Java 1.2, which marked the last major changes to the security model.

6.4.1. Java 1.0: The Sandbox

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

6.4.1.1 How the sandbox works

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.

6.4.2. Java 1.1: Digitally Signed Classes

Java 1.1 retained the sandbox model of Java 1.0 but added 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 added 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.

6.4.3. Java 1.2: Permissions and Policies

Java 1.2 introduced substantial access control features into the Java security architecture. These features are implemented by 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 include 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 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 delegates most of those requests to an AccessController class that makes access decisions based on the Permission and Policy architecture.

The 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 architecture supports fine-grained levels of trust. Even locally installed code can be treated as untrusted or partially untrusted. Under this 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 policytool program, described in Chapter 8.

  • The architecture is not limited to a fixed set of access control methods in the SecurityManager class. Permission subclasses can be defined easily to govern access to system resources (which might be exposed, for example, by standard extensions that include native code).

6.4.3.1 How policies and permissions work

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 and later, 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 changed as of 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 as of 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".

    Team LiB
    Previous Section Next Section