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:
220.127.116.11 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.
18.104.22.168 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".