5.4. Access Control
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.
5.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:
18.104.22.168. 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.
5.4.2. Java 1.1: Digitally Signed Classes
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.
5.4.3. Java 1.2: Permissions and Policies
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.
22.214.171.124. 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, 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.