Once a class has been loaded into the virtual machine and checked by the verifier, the second security mechanism of the Java platform springs into action: the security manager. This is the topic of the following sections.
1. Permission Checking
The security manager controls whether a specific operation is permitted. Operations checked by the security manager include the following:
- Creating a new class loader
- Exiting the virtual machine
- Accessing a field of another class by using reflection
- Accessing a file
- Opening a socket connection
- Starting a print job
- Accessing the system clipboard
- Accessing the AWT event queue
- Bringing up a top-level window
There are many other checks throughout the Java library.
The default behavior when running Java applications is to install no security manager, so all these operations are permitted. In contrast, applets relied on a very restrictive security policy. Stricter security also makes sense in other situations.
For example, suppose you run a Tomcat instance and allow collaborators or students to install servlets. You would not want any of them to call System.exit since this would terminate the Tomcat instance. You can set a security policy that causes calls to System.exit to throw a security exception instead of actually closing down the virtual machine. Here is what happens in detail. The exit method of the Runtime class calls the checkExit method of the security manager. Here is the entire code of the exit method:
public void exit(int status)
{
SecurityManager security = System.getSecurityManager();
if (security != null)
security.checkExit(status);
exitInternal(status);
}
The security manager now checks if the exit request came from the browser or an individual applet. If the security manager agrees with the exit request, the checkExit method simply returns and normal processing continues. However, if the security manager doesn’t want to grant the request, the checkExit method throws a SecurityException.
The exit method continues only if no exception occurred. It then calls the private native exitlnternat method that actually terminates the virtual machine. There is no other way to terminate the virtual machine, and since the exitlnternat method is private, it cannot be called from any other class. Thus, any code that attempts to exit the virtual machine must go through the exit method and thus through the checkExit security check without triggering a security exception.
Clearly, the integrity of the security policy depends on careful coding. The providers of system services in the standard library must always consult the security manager before attempting any sensitive operation.
The security manager of the Java platform allows both programmers and system administrators fine-grained control over individual security permissions. We will describe these features in the following section. First, we’ll summarize the Java 2 platform security model. We’ll then show how you can control permissions with policy files. Finally, we’ll explain how you can define your own permission types.
2. Java Platform Security
JDK 1.0 had a very simple security model: Local classes had full permissions, and remote classes were confined to the sandbox. Just like a child that can only play in a sandbox, remote code was only allowed to paint on the screen and interact with the user. The applet security manager denied all access to local resources. JDK 1.1 implemented a slight modification: Remote code that was signed by a trusted entity was granted the same permissions as local classes. However, both versions of the JDK used an all-or-nothing approach. Programs either had full access or they had to play in the sandbox.
Starting with Java 1.2, the Java platform has a much more flexible mechanism.
A security policy maps code sources to permission sets (see Figure 10.5).
A code source is specified by a code base and a set of certificates. The code base specifies the origin of the code. For example, the code base of remote applet code is the HTTP URL from which the applet was loaded. The code base of code in a JAR file is the file’s URL. A certificate, if present, is an assurance by some party that the code has not been tampered with. We’ll cover certificates later in this chapter.
A permission is any property that is checked by a security manager. The Java platform supports a number of permission classes, each encapsulating the details of a particular permission. For example, the following instance of the FilePermission class states that it is OK to read and write any file in the /tmp directory:
var p = new FilePermission(“/tmp/*”, “read,write”);
More importantly, the default implementation of the Policy class reads permissions from a permission file. Inside a permission file, the same read permission is expressed as
permission java.io.FilePermission “/tmp/*”, “read,write”;
We’ll discuss permission files in the next section.
Figure 10.6 shows the hierarchy of the permission classes that were supplied with Java 1.2. Many more permission classes have been added in subsequent Java releases.
In the preceding section, you saw that the SecurityManager class has security check methods such as checkExit. These methods exist only for the convenience of the programmer and for backward compatibility. They all map to standard permission checks. For example, here is the source code for the checkExit method:
public void checkExit()
{
checkPermission(new RuntimePermission(“exitVM”));
}
Each class has a protection domain—an object that encapsulates both the code source and the collection of permissions of the class. When the SecurityManager needs to check a permission, it looks at the classes of all methods currently on the call stack. It then gets the protection domains of all classes and asks each protection domain if its permission collection allows the operation currently being checked. If all domains agree, the check passes. Otherwise, a SecurityException is thrown.
Why do all methods on the call stack need to allow a particular operation? Let us work through an example. Suppose the init method of a servlet wants to open a file. It might call
var in = new FileReader(name);
The FineReader constructor calls the FileInputStream constructor, which calls the checkRead method of the security manager, which finally calls checkPermission with a FilePermission(name, “read”) object. Table 10.1 shows the call stack.
The FileInputStream and SecurityManager classes are system classes for which CodeSource is null and the permissions consist of an instance of the AllPermission class, which allows all operations. Clearly, their permissions alone can’t determine the outcome of the check. As you can see, the checkPermission method must take into account the restricted permissions of the applet class. By checking the entire call stack, the security mechanism ensures that one class can never ask another class to carry out a sensitive operation on its behalf.
3. The file java.policy in the Java platform’s home directory
The policy manager reads policy files that contain instructions for mapping code sources to permissions. Here is a typical policy file:
grant codeBase “http://www.horstmann.com/classes”
{
permission java.io.FilePermission “/tmp/*”, “read,write”;
};
This file grants permission to read and write files in the /tmp directory to all code that was downloaded from www.horstmann.com/classes.
You can install policy files in standard locations. By default, there are two locations:
- The file java.policy in the Java platform’s home directory
- The file .java.policy (notice the period at the beginning of the file name) in the user’s home directory
- The file .java.policy (notice the period at the beginning of the file name) in the user’s home directory
During testing, we don’t like to constantly modify the standard policy files. Therefore, we prefer to explicitly name the policy file required for each application. Place the permissions into a separate file—say, MyApp.policy. To apply the policy, you have two choices. You can set a system property inside your application’s main method:
System.setProperty(“java.security.policy”, “MyApp.policy”);
Alternatively, you can start the virtual machine as
java -Djava.security.policy=MyApp.policy MyApp
In these examples, the MyApp.policy file is added to the other policies in effect. If you add a second equal sign, for example:
java -Djava.security.policy==MyApp.policy MyApp
then your application will use only the specified policy file, and the standard policy files will be ignored.
As you saw previously, Java applications by default do not install a security manager. Therefore, you won’t see the effect of policy files until you install one. You can, of course, add a line
System.setSecurityManager(new SecurityManager());
into your main method. Or you can add the command-line option
-Djava.security.manager when starting the virtual machine.
java -Djava.security.manager -Djava.security.poticy=MyApp.poticy MyApp
In the remainder of this section, we’ll show you in detail how to describe permissions in the policy file. We’ll describe the entire policy file format except for code certificates which we cover later in this chapter.
A policy file contains a sequence of grant entries. Each entry has the following form:
grant codesource
{
permission1;
permission2;
…
};
The code source contains a code base (which can be omitted if the entry applies to code from all sources) and the names of trusted principals and certificate signers (which can be omitted if signatures are not required for this entry).
The code base is specified as
codeBase “url”
If the URL ends in a /, it refers to a directory. Otherwise, it is taken to be the name of a JAR file. For example,
grant codeBase “www.horstmann.com/ctasses/” { . . . };
grant codeBase “www.horstmann.com/ctasses/MyApp.jar” { . . . };
The code base is a URL and should always contain forward slashes as file separators, even for file URLs in Windows. For example,
grant codeBase “fite:C:/myapps/ctasses/” { . . . };
The permissions have the following structure:
permission className targetName, actionList;
The className is the fully qualified class name of the permission class (such as java.io.FilePermission). The targetName is a permission-specific value—for example, a file or directory name for the file permission, or a host and port for a socket permission. The actionList is also permission-specific. It is a list of actions, such as read or connect, separated by commas. Some permission classes don’t need target names and action lists. Table 10.2 lists the commonly used permission classes and their actions.
As you can see from Table 10.2, most permissions simply permit a particular operation. You can think of the operation as the target with an implied action “permit”. These permission classes all extend the BasicPermission class (see Figure 10.6). However, the targets for the file, socket, and property permissions are more complex, and we need to investigate them in detail.
File permission targets can have the following form:
For example, the following permission entry gives access to all files in the directory /myapp and any of its subdirectories:
permission java.io.FitePermission “/myapp/-“, “read,write,delete”;
You must use the \\ escape sequence to denote a backslash in a Windows file name.
permission java.io.FitePermission “c:\\myapp\\-“, “read,write,delete”;
Socket permission targets consist of a host and a port range. Host specifications have the following form:
hostname or IPaddress A single host
tocathost or the empty string The local host
*. domainSuffix Any host whose domain ends with the given
suffix
* All hosts
Port ranges are optional and have the form:
:n A single port
:n- All ports numbered n and above
:-n All ports numbered n and below
:n1-n2 All ports in the given range
Here is an example:
permission java.net.SocketPermission “*.horstmann.com:8000-8999”, “connect”;
Finally, property permission targets can have one of two forms:
property A specific property
propertyPrefix.* All properties with the given prefix
Examples are “java.home” and “java.vm.*”.
For example, the following permission entry allows a program to read all properties that start with java.vm:
permission java.utit.PropertyPermission “java.vm.*”, “read”;
You can use system properties in policy files. The token ${property} is replaced by the property value. For example, ${user.home} is replaced by the home directory of the user. Here is a typical use of this system property in a permission entry:
permission java.io.FitePermission “${user.home}”, “read,write”;
To create platform-independent policy files, it is a good idea to use the fite.separator property instead of explicit / or \\ separators. To make this simpler, the special notation ${/} is a shortcut for ${fite.separator}. For example,
permission java.io.FilePermission “${user.home}${/}-“, “read,write”;
is a portable entry for granting permission to read and write in the user’s home directory and any of its subdirectories.
4. Custom Permissions
In this section, you’ll see how you can supply your own permission class that users can refer to in their policy files.
To implement your permission class, extend the Permission class and supply the following methods:
- A constructor with two String parameters, for the target and the action list
- String getActions()
- boolean equals(Object other)
- int hashCode()
- boolean implies(Permission other)
The last method is the most important. Permissions have an ordering, in which more general permissions imply more specific ones. Consider the file permission
p1 = new FilePermission(“/tmp/-“, “read, write”);
This permission allows reading and writing of any file in the /tmp directory and any of its subdirectories.
This permission implies other, more specific permissions:
p2 = new FilePermission(“/tmp/-“, “read”);
p3 = new FilePermission(“/tmp/aFile”, “read, write”);
p4 = new FilePermission(“/tmp/aDirectory/-“, “write”);
In other words, a file permission p1 implies another file permission p2 if
- The target file set of p1 contains the target file set of p2.
- The action set of p1 contains the action set of p2.
Consider the following example of the use of the implies method. When the FileInputStream constructor wants to open a file for reading, it checks whether it has permission to do so. For that check, a specific file permission object is passed to the checkPermission method:
checkPermission(new FilePermission(fileName, “read”));
The security manager now asks all applicable permissions whether they imply this permission. If any one of them implies it, the check passes.
In particular, the AUPermission implies all other permissions.
If you define your own permission classes, you need to define a suitable notion of implication for your permission objects. Suppose, for example, that you define a TVPermission for a set-top box powered by Java technology. A permission
new TVPermission(“Tommy:2-12:1900-2200”, “watch,record”)
might allow Tommy to watch and record television channels 2-12 between 19:00 and 22:00. You need to implement the implies method so that this permission implies a more specific one, such as
new TVPermission(“Tommy:4:2000-2100”, “watch”)
5. Implementation of a Permission Class
In the next sample program, we implement a new permission for monitoring the insertion of text into a text area. The program ensures that you cannot add bad words such as sex, drugs, and C++ into a text area. We use a custom permission class so that the list of bad words can be supplied in a policy file.
The following subclass of JTextArea asks the security manager whether it is OK to add new text:
class WordCheckTextArea extends JTextArea
{
public void append(String text)
{
var p = new WordCheckPermission(text, “insert”);
SecurityManager manager = System.getSecurityManager();
if (manager != null) manager.checkPermission(p);
super.append(text);
}
}
If the security manager grants the WordCheckPermission, the text is appended. Otherwise, the checkPermission method throws an exception.
Word check permissions have two possible actions: insert (the permission to insert a specific text) and avoid (the permission to add any text that avoids certain bad words). You should run this program with the following policy file:
grant
{
permission permissions.WordCheckPermission “sex,drugs,C++”, “avoid”;
};
This policy file grants the permission to insert any text that avoids the bad words sex, drugs, and C++.
When designing the WordCheckPermission class, we must pay particular attention to the implies method. Here are the rules that control whether permission pi implies permission p2:
- If pi has action avoid and p2 has action insert, then the target of p2 must avoid all words in p1. For example, the permission
permissions.WordCheckPermission “sex,drugs,C++”, “avoid”
implies the permission
permissions.WordCheckPermission “Mary had a little lamb”, “insert”
- If p1 and p2 both have action avoid, then the word set of p2 must contain all words in the word set of p1. For example, the permission
permissions.WordCheckPermission “sex,drugs”, “avoid”
implies the permission
permissions.WordCheckPermission “sex,drugs,C++”, “avoid”
- If p1 and p2 both have action insert, then the text of p1 must contain the text of p2. For example, the permission
permissions.WordCheckPermission “Mary had a little lamb”, “insert”
implies the permission
permissions.WordCheckPermission “a little lamb”, “insert”
You can find the implementation of this class in Listing 10.4.
Note that to retrieve the permission target, you need to use the confusingly named getName method of the Permission class.
Since permissions are described by a pair of strings in policy files, permission classes need to be prepared to parse these strings. In particular, we use the following method to transform the comma-separated list of bad words of an avoid permission into a genuine Set:
public Set<String> badWordSet()
{
var set = new HashSet<String>();
set.addAll(List.of(getName().split(“,”)));
return set;
}
This code allows us to use the equals and containsAll methods to compare sets. As you saw in Chapter 9 of Volume I, the equals method of a set class finds two sets to be equal if they contain the same elements in any order. For example, the sets resulting from “sex,drugs,C++” and “C++,drugs,sex” are equal.
The program in Listing 10.5 shows how the WordCheckPermission class works. Type any text into the text field and click the Insert button. If the security check passes, the text is appended to the text area. If not, an error message is displayed (see Figure 10.7).
You have now seen how to configure Java platform security. Most commonly, you will simply tweak the standard permissions. For additional control, you can define custom permissions that can be configured in the same way as the standard permissions.
Source: Horstmann Cay S. (2019), Core Java. Volume II – Advanced Features, Pearson; 11th edition.