Policy-Based Authorization

As we saw, Java programs running under a security manager can perform only those operations for which they have been explicitly granted permission. The association of code, permissions and the specific conditions under which a piece of code has certain permissions is known as the authorization policy. We have already seen some examples of authorization policy representation in the form of policy files. Our aim in this section is to understand the abstract model behind the structure and behavior of these policies.

The Java access control model allows permissions to be associated with:

  1. Location of the code. Code location could be a file or HTTP URL and may represent a specific jar file, all classes in a directory (but not the jar files), all classes and jar files in a directory, or all classes and jar files in the directory tree. There is a special convention to ascertain code files represented by a particular URL. We talk more about it later.

  2. X.509 Certificates of code signers. Code in a jar file may be signed by one or more signers. The identity of a signer and the public key corresponding to the private key used for signing is stored in an X.509 certificate.

  3. User identities. Executing code could be associated with a user having one or more identities. This association of code and user identities happens at runtime. In this way it is different from the previous two associations (location and code signer).

  4. A combination of the above. The combination could involve none, all or any two of the above. In case none is specified, the permission is applicable to all code, irrespective of the location, signer and current user identities.

Let us map these concepts to Java interfaces and classes. Access to system resources and operations on them are encapsulated in concrete subclasses of abstract class java.security.Permission. A Permission class has two attributes, of type String, associated with it: name and actions, both of which could be empty. The actual meaning of these attributes depends on the specific Permission subclass. For example, java.io.FilePermission uses name to represent the pathname of files and actions to represent a comma-separated list of file operations such as "read,write". More than one Permission objects are often collected together in a java.security.PermissionCollection object.

The location of code (a URL) and the certificates of code signers (an array of Certificate objects) are encapsulated in a java.security.CodeSource object. Besides CodeSource, a set of permissions can also be constrained by identities of the current user. User identities are represented by instances of classes implementing interface java.security.Principal. A CodeSource and zero or more Principal objects, along with the PermissionCollection instance, make a java.security.ProtectionDomain. A ProtectionDomain essentially encapsulates the characteristics of a domain, which encloses a set of classes and user identities, and are granted the same set of permissions. A JVM running a program would have one or more ProtectionDomain instances. All these instances are grouped together in a single instance of a concrete subclass of abstract class java.security.Policy within a JVM. This relationship is further illustrated in Figure 5-1.

Figure 5-1. Java Access Control Classes.


By default, Sun's J2SE SDK uses sun.security.provider.PolicyFile class for security policy management. It is this class that parses the default and user-supplied policy files and initializes internal structures. But before we talk about these policy files, let us first complete the discussion on the security policy model.

Although only one Policy object is active at any given moment within a JVM, it is possible to have a different implementation for this class than the default one. This is done by developing a Policy subclass and performing either of the following:

  • Modify the %JRE_HOME%libsecuritysecurity.policy file so that property policy.provider is set to the fully qualified name of the newly developed Policy subclass. However, simply keeping this class in the classpath is not enough, as this class needs to be available to the bootstrap class loader.

  • Invoke static method setPolicy() on the Policy class with an instance of the newly developed Policy subclass as argument. This requires that the code making this invocation has been granted the appropriate permission and the new object is initialized properly.

Writing a new Policy subclass could be tricky and should not be attempted without proper understanding of its dynamic behavior and dependencies on other classes. The default PolicyFile implementation allows specification of policy rules in flat files with simple and intuitive syntax and is in wide use.

Java Policy Files

The default Policy implementation provided by the PolicyFile class initializes the singleton Policy instance by reading a number of policy files. These policy files are specified in the security configuration file %JRE_HOME%libsecurityjava.security as value of the properties policy.url.1, policy.url.2, policy.url.3 and so on. By default only two properties are specified: policy.url.1 pointing to a system-wide policy file and policy.url.2 pointing to a user-specific policy file.

  • System-wide policy file java.policy is stored in the directory %JRE_HOME%libsecurity. This file has policy rules common to all users using a particular J2SE SDK installation. For J2SE SDK installation, JRE_HOME is the jre subdirectory within the SDK installation directory. For only the JRE installation, JRE_HOME is the JRE installation directory.

  • User-specific policies are kept in the file .java.policy in the home directory of the user. You can get this directory by printing the Java system property user.dir on any OS. User specific policy file doesn't exist by default.

You can also specify a policy file specific to a particular JVM by setting the system property java.security.policy at the command line. Specifying this property with command line argument –Djava.security.policy=policy-file implies that the policies in policy-file should be added to those in system-wide and user-specific policy files. The resultant Policy instance is a union of policies in all the policy files. Specifying –Djava.security.policy==policy-file (note doubling of =) implies that system wide and user specific policy files are ignored and only policy-file policies are effective.

Keep in mind that these files are read and the Policy instance is initialized only when the security manager is enabled. If any of the above mentioned policy files are missing then it is silently ignored. This could mean unexpected behavior due to simple typos in specifying the filename. If you suspect something is amiss, then enable debug messages by setting the system property java.security.debug to policy as in –Djava.security.debug=policy and you will get a detailed report on processing of policy files.

Policy File Syntax

We have already come across a number of policy files. A policy file essentially consists of an optional keystore entry and zero or more grant entries.

The keystore entry specifies the location and optionally, the type of keystore storing the code signing certificates. It has the following syntax:

keystore "keystore_url" [, "keystore_type"];

Optional keystore_type could be either JKS or JCEKS, JKS being the default value. Recall that these are the keystore types supported by Sun's J2SE SDK.

The keystore_url could be an absolute or a relative pathname or URL. If it is relative, then it is taken as relative to the location of the policy file (and not the current directory, as one might assume). You can also use a pattern like ${prop} as a component of the pathname or URL where prop is a system property. At runtime, this pattern will be replaced by the value of the system property. For example, ${user.dir} gets replaced by the current working directory. Also, you can use the pattern ${/} as an OS independent symbol for path separator.

The program that parses policy files treats backslash ('') as an escape character. This has an interesting consequence on Windows pathnames—you must precede a backslash with another backslash. Let us look at some valid combinations of policy file location, keystore file location and keystore_url values in Table 5-2 for a Windows 2000 machine.

Table 5-2. Valid keystore_url values
Policy File LocationKeystore File LocationValid keystore_url values
C:workdirC:workdir est.ks
1. test.ks
2. file:test.ks
3. file:c:\workdir\test.ks
4. \workdir\test.ks[α]
5. /workdir/test.ks[α]
6. file:///workdir/test.ks[α]
7. file:${user.dir}${/}test.ks[β]
8. file://c:\workdir\test.ks

http://localhost/http://localhost/test.ks
9. test.ks
10. http://localhost/test.ks

http://localhost/C:workdir est.ks
11. file:${user.dir}${/}test.ks[β]
12. file:///workdir/test.ks


[α] C: is the system drive;

[β] C:workdir is the current working directory.

As you can see, the use of system properties in specifying keystore files can enhance the portability of policy files across different machines and operating systems.

A grant entry associates a set of permissions with code satisfying certain properties and has the following syntax:

grant [signedBy "signer_names",]
  [codeBase "URL",]
  [principal principal_class_name "principal_name",]+
  {
  [permission permission_class_name ["name",] ["action",]
          [signedBy "signer_names"];]+
  ...
  };

Let us look at the meaning of different keywords.

  • A URL followed by keyword codeBase indicates the code location. The syntax to specify this is similar to that of keystore_url, except for the fact that it must be in URL format. If present, the permissions apply only to code from this location, otherwise to all code. There is a specific convention for interpreting URL to determine what code belongs to a particular URL, as explained below:

    1. A URL ending with a specific jar file includes that jar file only.

    2. A URL ending with '/' includes all class files (but no jar files) in the specified directory.

    3. A URL ending with '/*' includes all class and jar files in the specified directory.

    4. A URL ending with '/-' includes all class and jar files in the directory tree rooted at the specified directory.

  • A list of comma-separated aliases followed by keyword signedBy indicates the public key certificate of code signers in the keystore identified by different aliases. If multiple aliases are specified then the code must be signed by all the certificate holders.

  • Terms following keyword principal specify a user identity. When one or more principals are specified then permissions are granted only if the execution environment is associated with all the specified identities of the current user. Variable principal_class_name specifies the identity type and must be a class derived from java.security.Principal. The format of the principal_name string depends on the type.

  • A specific permission class name follows the keyword permission within the body of grant entry. Depending on the specific permission, there may be a name and action associated with it. If the optional signedBy clause is present, then the permission class must be signed by the specified signer.

Go back to the policy files of the section A Quick Tour of Java Access Control Features and interpret those with the description in this section.

Permission Types

As we have noted, permission types are represented as classes derived from abstract class java.security.Permission. J2SE v1.4 defines a number of standard permission types. We have already come across a number of them: java.io.FilePermission and javax.security.auth.AuthPermission. An instance of Permission has a name and may include a list of actions. Interpretation of both the name and the actions, which are stored as String objects, is left to the concrete Permission class.

Listed below are some of the more frequently encountered permission types with brief description. Refer to Javadoc documentation for more complete information.

  • java.io.FilePermission. Represents permission to read, write, execute or delete files and directories. The actions string a comma-separated list of keywords from the set {read, write, execute, delete}, identifies the access modes. The permission name identifies the files and directories in the same way as a URL following the keyword codeBase of grant entry in a policy file. Special string "<<ALL FILES>>" matches any file.

  • java.net.SocketPermission. Represents permission to access networks via sockets. The name of this permission represents a hostname (a fully qualified domain name, an IPv4 address or an IPv6 address) and one or more port numbers. Examples of valid names include: "orion.nsr.hp.com:2950", "localhost:1024-". The action string is a comma separated list of keywords from the set {connect, listen, accept, resolve}.

  • java.util.PropertyPermission. Represents permission to read or write system properties. The name of the permission identifies the property such as "user.dir" or "java.home" and the actions string identifies the access mode as in "read", "write" or "read, write". It is possible to specify more than one property by having a trailing dot separated component as '*'. An applet capable of reading and modifying system properties can collect information about a user's environment and may be able to change the behavior of other programs relying on these properties.

  • java.lang.RuntimePermission. Depending upon the name of the permission it can represent the permission to invoke specific methods on certain objects. There are no actions associated with this permission type. Following is a partial list of self-explanatory names: createClassLoader, setSecurityManager, exitVM, setIO, stopThread, modifyThread, getProtectionDomain, loadLibrary.{libname}.

  • java.security.SecurityPermission. Similar to RuntimePermission, this permission type can take one of many names and allow invocation of corresponding methods. A partial list of these names follows: getPolicy, setPolicy, getSignerPrivateKey, setSignerKeyPair.

  • javax.net.ssl.SSLPermission. Two names associated with this permission, setHostnameVerifier and getSSLSessionContext, represent the permission to call method getHostnameVerifier on javax.net.ssl.HttpsURLConnection and method getSessionContext on javax.net.ssl.SSLSession objects, respectively.

  • java.security.AllPermission. Implies all other permissions.

  • javax.security.auth.AuthPermission. This permission has names to guard specific methods of the Policy and Subject class of javax.security.auth package and the LoginContext and Configuration classes of javax.security.auth.login package.

These permission classes are primarily for use by the library classes. As a programmer, you will rarely need to instantiate or work with these classes directly. One exception is the scenario when you are writing code to replace the default implementation of classes that are responsible for enforcing permissions. This would be the case if you develop a Policy class or supply your own SocketFactory, or develop any such class.

As an administrator or user, you should be using these permissions to write or modify policy files.

Enforcement of Permissions

We looked at Java classes that store permissions and its association with code and users at runtime within a JVM. We also looked at policy files and how one can specify different types of permissions for specific code and users. During startup, the concrete implementation of the Policy class parses policy files and converts grant entries into equivalent ProtectionDomain objects and maintains the data-structure required for validation of actions against granted permissions.

But how does JVM enforce these permissions? Who checks against this runtime data-structure and when? Let us take a specific operation—opening a file—and examine the relevant portions of the source code to get answers to these questions. As we find, only the entry point and the specific permission would be different for other operations. So this particular case also helps in understanding the general case of permission enforcement.

The code presented in this section has been taken from the source code that comes bundled with J2SE v1.4 SDK. Only portions that help in explaining the basic concept are shown and some processing and debug messages have been omitted.

We start with a constructor of FileInputStream that opens a file:

// A constructor of FileInpuStream class
public FileInputStream(File file) throws FileNotFoundException {
  String name = file.getPath();
  SecurityManager security = System.getSecurityManager();
  if (security != null)
    security.checkRead(name);
  // carry out rest of the job
}

This code invokes the checkRead() method of SecurityManager with the filename as argument. If the check succeeds, this method returns quietly. Otherwise an appropriate subclass of RuntimeException is thrown. Recall that a RuntimeException need not be declared explicitly.

Essentially, the code performing the operation itself is responsible for calling the appropriate method to make sure that it has the adequate permission to carry out the operation. All security sensitive operations of J2SE SDK follow this pattern. If you supply your own implementation for any of the standard functionality, such as an implementation of the Policy class, then it would be your responsibility to make appropriate checks.

Let us look at the relevant code in the SecurityManager class:

// Methods of class SecurityManager
public void checkRead(String file) {
  checkPermission(new FilePermission(file, "read"));
}
public void checkPermission(Permission perm) {
  java.security.AccessController.checkPermission(perm);
}

This code creates the FilePermission object and delegates the operation to the static method checkPermission() of the AccessController class. In fact, the role of SecurityManager in enforcing permissions is minimal and remnant of earlier versions of J2SE. J2SE v1.4 relies mostly on AccessController for actual checking. References to the SecurityManager are maintained only for backward compatibility.

Let us look into the checkPermission() method of AccessController:

// Method checkPermission of AccessController
public static void checkPermission(Permission perm)
    throws AccessControlException {
  AccessControlContext stack = getStackAccessControlContext();
  // if context is null, we had privileged system code on the
  // stack.
  if (stack == null)
    return;
  AccessControlContext acc = stack.optimize();
  acc.checkPermission(perm);
}

The method getStackAccessControlContext() is a native method of AccessController class that returns the AccessControlContext object of the currently running thread. This object encapsulates a stack of ProtectionDomain objects of all the callers on the stack. A non-null AccessControlContext object is further optimized by calling the optimize() method. This method, among other things, is responsible for getting rid of the ProtectionDomains not associated with the Principals of the current user, if any. Recall that a code will be running on behalf of the user represented by a Subject instance if it was invoked through Subject.doAs().

The method checkPermission() of AccessControlContext is responsible for checking the permission against all the ProtectionDomain objects on the stack:

// Method checkPermission of AccessControlContext
public void checkPermission(Permission perm)
    throws AccessControlException {
  // variable context is an array of ProtectionDomain objects
  for (int i=0; i< context.length; i++) {
    if (context[i] != null &&  !context[i].implies(perm))
      throw new AccessControlException("access denied "+perm, perm);
  }
}

Whether a protection domain has the permission being checked or not is determined by calling the implies() method. This method, in turn, would call the implies() method of each of the Permission objects of the ProtectionDomain. If the check against all the ProtectionDomain instances passes, this method returns quietly, otherwise it throws an AccessControlException.

It would be instructive to look at the call stack and the corresponding AccessControlContext, i.e., ProtectionDomain stack, for the example of the section Access Control Based on User involving jar files dfl.jar and df.jar. Figure 5-2 illustrates this.

Figure 5-2. Call Stack and Access Control Stack.


This concludes the discussion on the permission enforcement mechanism of Java.

As an attentive reader, you must have noticed that:

  1. The same permission class is used to represent the permission granted to code and/or user and the permission sought by a caller.

  2. Permission check uses the implies() method, a sort of inclusion test, and not the equals() method. This is why the check for read permission on a specific file succeeds even though the granted permission is on a directory tree having that file.

  3. To be able to carry out a controlled operation, not only the caller but all the code with methods on the calling stack should have the permission to carry out the operation.

  4. Access control can be expensive, especially when many different policies are in force for different pieces of code. We will more about it in the Performance Issues section.

We come across some of these principles while applying access control to a sample application later in this chapter.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.102.178