- Class Loaders
- Bytecode Verification
- Security Managers and Permissions
- User Authentication
- Digital Signatures
- Code Signing
- Encryption
Security Managers and Permissions
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. The security manager is a class that 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 such as these throughout the Java library.
The default behavior when running Java applications is that no security manager is installed, so all these operations are permitted. The applet viewer, on the other hand, enforces a security policy that is quite restrictive.
For example, applets are not allowed to exit the virtual machine. If they try calling the exit method, then a security exception is thrown. 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, then 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 exitInternal method that actually terminates the virtual machine. There is no other way of terminating the virtual machine, and because the exitInternal 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 describe these features in the following section. First, we summarize the Java 2 platform security model. We then show how you can control permissions with policy files. Finally, we explain how you can define your own permission types.
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 provided an all-or-nothing approach. Programs either had full access or they had to play in the sandbox.
Starting with Java SE 1.2, the Java platform has a much more flexible mechanism. A security policy maps code sources to permission sets (see Figure 9-6).
Figure 9-6 A security policy
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 is loaded. The code base of code in a JAR file is a file URL. A certificate, if present, is an assurance by some party that the code has not been tampered with. We 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 of which encapsulates the details of a particular permission. For example, the following instance of the FilePermission class states that it is okay to read and write any file in the /tmp directory.
FilePermission p = new FilePermission("/tmp/*", "read,write");
More important, 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 discuss permission files in the next section.
Figure 9-7 shows the hierarchy of the permission classes that were supplied with Java SE 1.2. Many more permission classes have been added in subsequent Java releases.
Figure 9-7 A part of the hierarchy of permission classes
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 into 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 that is currently being checked. If all domains agree, then 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 an applet wants to open a file. It might call
Reader in = new FileReader(name);
The FileReader 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 9-1 shows the call stack.
Table 9-1. Call Stack During Permission Checking
Class |
Method |
Code Source |
Permissions |
SecurityManager |
checkPermission |
null |
AllPermission |
SecurityManager |
checkRead |
null |
AllPermission |
FileInputStream |
constructor |
null |
AllPermission |
FileReader |
constructor |
null |
AllPermission |
applet |
init |
applet code source |
applet permissions |
. . . |
The FileInputStream and SecurityManager classes are system classes for which CodeSource is null and 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.
-
void checkPermission(Permission p) 1.2
checks whether this security manager grants the given permission. The method throws a SecurityException if the permission is not granted.
-
ProtectionDomain getProtectionDomain() 1.2
gets the protection domain for this class, or null if this class was loaded without a protection domain.
-
ProtectionDomain(CodeSource source, PermissionCollection permissions)
constructs a protection domain with the given code source and permissions.
-
CodeSource getCodeSource()
gets the code source of this protection domain.
-
boolean implies(Permission p)
returns true if the given permission is allowed by this protection domain.
-
Certificate[] getCertificates()
gets the certificate chain for class file signatures associated with this code source.
-
URL getLocation()
gets the code base of class files associated with this code source.
Security Policy Files
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 http://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 home directory
- The file .java.policy (notice the period at the beginning of the file name) in the user home directory
During testing, we don't like to constantly modify the standard policy files. Therefore, we prefer to explicitly name the policy file that is 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 applications' main method:
System.setProperty("java.security.policy", "MyApp.policy");
Alternatively, you can start the virtual machine as
java -Djava.security.policy=MyApp.policy MyApp
For applets, you instead use
appletviewer -J-Djava.security.policy=MyApplet.policy MyApplet.html
(You can use the -J option of the appletviewer to pass any command-line argument to the virtual machine.)
In these examples, the MyApp.policy file is added to the other policies in effect. If you add a second equal sign, such as
java -Djava.security.policy==MyApp.policy MyApp
then your application uses only the specified policy file, and the standard policy files are 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.policy=MyApp.policy MyApp
In the remainder of this section, we show you in detail how to describe permissions in the policy file. We 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 /, then it refers to a directory. Otherwise, it is taken to be the name of a JAR file. For example,
grant codeBase "www.horstmann.com/classes/" { . . . }; grant codeBase "www.horstmann.com/classes/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 "file:C:/myapps/classes/" { . . . };
The permissions have the following structure:
permission className targetName, actionList;
The class name is the fully qualified class name of the permission class (such as java.io.FilePermission). The target name 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 9-2 lists the commonly used permission classes and their actions.
Table 9-2. Permissions and Their Associated Targets and Actions
Permission |
Target |
Action |
java.io.FilePermission |
file target (see text) |
read, write, execute, delete |
java.net.SocketPermission |
socket target (see text) |
accept, connect, listen, resolve |
java.util.PropertyPermission |
property target (see text) |
read, write |
java.lang.RuntimePermission |
createClassLoader getClassLoader setContextClassLoader enableContextClassLoaderOverride createSecurityManager setSecurityManager exitVM getenv.variableName shutdownHooks setFactory setIO modifyThread stopThread modifyThreadGroup getProtectionDomain readFileDescriptor writeFileDescriptor loadLibrary.libraryName accessClassInPackage.packageName defineClassInPackage.packageName accessDeclaredMembers.className queuePrintJob getStackTrace setDefaultUncaughtExceptionHandler preferences usePolicy |
(none) |
java.awt.AWTPermission |
showWindowWithoutWarningBanner accessClipboard accessEventQueue createRobot fullScreenExclusive listenToAllAWTEvents readDisplayPixels replaceKeyboardFocusManager watchMousePointer setWindowAlwaysOnTop setAppletStub |
(none) |
java.net.NetPermission |
setDefaultAuthenticator specifyStreamHandler requestPasswordAuthentication setProxySelector getProxySelector setCookieHandler getCookieHandler setResponseCache getResponseCache |
(none) |
java.lang.reflect.ReflectPermission |
suppressAccessChecks |
(none) |
java.io.SerializablePermission |
enableSubclassImplementation enableSubstitution |
(none) |
java.security.SecurityPermission |
createAccessControlContext getDomainCombiner getPolicy setPolicy getProperty.keyName setProperty.keyName insertProvider.providerName removeProvider.providerName setSystemScope setIdentityPublicKey setIdentityInfo addIdentityCertificate removeIdentityCertificate printIdentity clearProviderProperties.providerName putProviderProperty.providerName removeProviderProperty.providerName getSignerPrivateKey setSignerKeyPair |
(none) |
java.security.AllPermission |
(none) |
(none) |
javax.audio.AudioPermission |
play record |
(none) |
javax.security.auth.AuthPermission |
doAs doAsPrivileged getSubject getSubjectFromDomainCombiner setReadOnly modifyPrincipals modifyPublicCredentials modifyPrivateCredentials refreshCredential destroyCredential createLoginContext.contextName getLoginConfiguration setLoginConfiguration refreshLoginConfiguration |
(none) |
java.util.logging.LoggingPermission |
control |
(none) |
java.sql.SQLPermission |
setLog |
(none) |
As you can see from Table 9-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 9-7 on page 774). 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:
file |
a file |
directory/ |
a directory |
directory/* |
all files in the directory |
* |
all files in the current directory |
directory/- |
all files in the directory or one of its subdirectories |
- |
all files in the current directory or one of its subdirectories |
<<ALL FILES>> |
all files in the file system |
For example, the following permission entry gives access to all files in the directory /myapp and any of its subdirectories.
permission java.io.FilePermission "/myapp/-", "read,write,delete";
You must use the \\ escape sequence to denote a backslash in a Windows file name.
permission java.io.FilePermission "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 |
localhost 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.util.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.FilePermission "${user.home}", "read,write";
To create platform-independent policy files, it is a good idea to use the file.separator property instead of explicit / or \\ separators. To make this simpler, the special notation ${/} is a shortcut for ${file.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.
Figure 9-8 The policy tool
Custom Permissions
In this section, you see how you can supply your own permission class that users can refer to in their policy files.
To implement your permission class, you 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()
- 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, then the check passes.
In particular, the AllPermission implies all other permissions.
If you define your own permission classes, then 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")
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 okay to add new text:
class WordCheckTextArea extends JTextArea { public void append(String text) { WordCheckPermission 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, then 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 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 p1 implies permission p2.
-
If p1 has action avoid and p2 has action insert, then the target of p2 must avoid all words in p1. For example, the permission
WordCheckPermission "sex,drugs,C++", "avoid"
implies the permission
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
WordCheckPermission "sex,drugs", "avoid"
implies the permission
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
WordCheckPermission "Mary had a little lamb", "insert"
implies the permission
WordCheckPermission "a little lamb", "insert"
You can find the implementation of this class in Listing 9-4.
Note that you retrieve the permission target with the confusingly named getName method of the Permission class.
Because 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() { Set<String> set = new HashSet<String>(); set.addAll(Arrays.asList(getName().split(","))); return set; }
This code allows us to use the equals and containsAll methods to compare sets. As you saw in Chapter 2, 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 9-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 9-9).
Figure 9-9 The PermissionTest program
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.
Listing 9-4. WordCheckPermission.java
1. import java.security.*; 2. import java.util.*; 3. 4. /** 5. * A permission that checks for bad words. 6. * @version 1.00 1999-10-23 7. * @author Cay Horstmann 8. */ 9. public class WordCheckPermission extends Permission 10. { 11. /** 12. * Constructs a word check permission 13. * @param target a comma separated word list 14. * @param anAction "insert" or "avoid" 15. */ 16. public WordCheckPermission(String target, String anAction) 17. { 18. super(target); 19. action = anAction; 20. } 21. 22. public String getActions() 23. { 24. return action; 25. } 26. 27. public boolean equals(Object other) 28. { 29. if (other == null) return false; 30. if (!getClass().equals(other.getClass())) return false; 31. WordCheckPermission b = (WordCheckPermission) other; 32. if (!action.equals(b.action)) return false; 33. if (action.equals("insert")) return getName().equals(b.getName()); 34. else if (action.equals("avoid")) return badWordSet().equals(b.badWordSet()); 35. else return false; 36. } 37. 38. public int hashCode() 39. { 40. return getName().hashCode() + action.hashCode(); 41. } 42. 43. public boolean implies(Permission other) 44. { 45. if (!(other instanceof WordCheckPermission)) return false; 46. WordCheckPermission b = (WordCheckPermission) other; 47. if (action.equals("insert")) 48. { 49. return b.action.equals("insert") && getName().indexOf(b.getName()) >= 0; 50. } 51. else if (action.equals("avoid")) 52. { 53. if (b.action.equals("avoid")) return b.badWordSet().containsAll(badWordSet()); 54. else if (b.action.equals("insert")) 55. { 56. for (String badWord : badWordSet()) 57. if (b.getName().indexOf(badWord) >= 0) return false; 58. return true; 59. } 60. else return false; 61. } 62. else return false; 63. } 64. 65. /** 66. * Gets the bad words that this permission rule describes. 67. * @return a set of the bad words 68. */ 69. public Set<String> badWordSet() 70. { 71. Set<String> set = new HashSet<String>(); 72. set.addAll(Arrays.asList(getName().split(","))); 73. return set; 74. } 75. 76. private String action; 77. }
Listing 9-5. PermissionTest.java
1. import java.awt.*; 2. import java.awt.event.*; 3. import javax.swing.*; 4. 5. /** 6. * This class demonstrates the custom WordCheckPermission. 7. * @version 1.03 2007-10-06 8. * @author Cay Horstmann 9. */ 10. public class PermissionTest 11. { 12. public static void main(String[] args) 13. { 14. System.setProperty("java.security.policy", "PermissionTest.policy"); 15. System.setSecurityManager(new SecurityManager()); 16. EventQueue.invokeLater(new Runnable() 17. { 18. public void run() 19. { 20. JFrame frame = new PermissionTestFrame(); 21. frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 22. frame.setVisible(true); 23. } 24. }); 25. } 26. } 27. 28. /** 29. * This frame contains a text field for inserting words into a text area that is protected 30. * from "bad words". 31. */ 32. class PermissionTestFrame extends JFrame 33. { 34. public PermissionTestFrame() 35. { 36. setTitle("PermissionTest"); 37. setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT); 38. 39. textField = new JTextField(20); 40. JPanel panel = new JPanel(); 41. panel.add(textField); 42. JButton openButton = new JButton("Insert"); 43. panel.add(openButton); 44. openButton.addActionListener(new ActionListener() 45. { 46. public void actionPerformed(ActionEvent event) 47. { 48. insertWords(textField.getText()); 49. } 50. }); 51. 52. add(panel, BorderLayout.NORTH); 53. 54. textArea = new WordCheckTextArea(); 55. add(new JScrollPane(textArea), BorderLayout.CENTER); 56. } 57. 58. /** 59. * Tries to insert words into the text area. Displays a dialog if the attempt fails. 60. * @param words the words to insert 61. */ 62. public void insertWords(String words) 63. { 64. try 65. { 66. textArea.append(words + "\n"); 67. } 68. catch (SecurityException e) 69. { 70. JOptionPane.showMessageDialog(this, "I am sorry, but I cannot do that."); 71. } 72. } 73. 74. private JTextField textField; 75. private WordCheckTextArea textArea; 76. private static final int DEFAULT_WIDTH = 400; 77. private static final int DEFAULT_HEIGHT = 300; 78. } 79. 80. /** 81. * A text area whose append method makes a security check to see that no bad words are added. 82. */ 83. class WordCheckTextArea extends JTextArea 84. { 85. public void append(String text) 86. { 87. WordCheckPermission p = new WordCheckPermission(text, "insert"); 88. SecurityManager manager = System.getSecurityManager(); 89. if (manager != null) manager.checkPermission(p); 90. super.append(text); 91. } 92. }
-
Permission(String name)
constructs a permission with the given target name.
-
String getName()
returns the target name of this permission.
-
boolean implies(Permission other)
checks whether this permission implies the other permission. That is the case if the other permission describes a more specific condition that is a consequence of the condition described by this permission.