Java Security: Access Control

Although, application developers usually needn’t bother about the low-level functions of Java security architecture, an overview of the different components and their interaction is required for smooth understanding.

The heart of the Java security architecture is SecurityManager class. Note that not all the actions, taken by various methods of Java libraries, are potentially safe. A small list of these actions is given below:

  • Read/write/delete files
  • Read/write system properties
  • Listen on a local port number
  • Accept a socket connection from a specified host and port number
  • Open a socket connection to a specified host and port number
  • Modify a thread (change its priority, stop it, and so on)
  • Create a new class loader
  • Create a new process
  • Exit from an application
  • Load a class from a specified package
  • Add a new class to a specified package
  • Load a dynamic library containing native methods

Who decided which core actions are unsafe. Obviously, Java security designers made that decision. Anyway, for each of these potentially unsafe actions (see list below), SecurityManager class has a method (with name starting with ‘check’) which determines (by consulting specified policy) if the action is allowed. For example, given a security policy, checkRead() and checkWrite() method define if a thread is allowed to read from or write to a specified file respectively.

These check methods are invoked by various methods of Java library before performing those sensitive operations. The call of such a check method typically looks like this:

SecurityManager security = System.getSecurityManager();

if (security != null) {

security.checkXXX(argument,…);

}

So, the security manager has the opportunity to allow or disallow the operation after consulting a given security policy. This is done by typically creating a Permission object (representing access right being sought) and passing it to a special method checkPermission(). Current implementation simply passes the Permission object to the static checkPermission() method of AccessControiier class, which is the actual workhorse of the security core. This essentially gathers an array of classes representing current calling thread’s stack. It then inspects the permissions (in the form of ProtectionDomains) associated with each class in this stack. If any class does not have the permission being tested, an AccessControiException is thrown. Otherwise the method returns silently.

1. Installing Built-in Security Manager

By default, the Java interpreter does not provide a security boundary for a Java application. If we want to run a Java application under the supervision of a security manager, we can explicitly inform it in many ways. One way to do this is setting the system property java.security.manager using command line argument as follows:

java -Djava.security.manager SomeApp

This instructs the interpreter to install an instance of the system’s default security manager (java. lang.SecurityManager). Alternatively, an application can install it using the following piece of code:

SecurityManager sm = new SecurityManager();

System.setSecurityManager(sm);

The first line creates an instance of the security manager and the second line installs it.

2. Policy Files

A policy specifies a set of allowable permission(s) to be used by the code from various sources and running as various principals and is represented by a java.security.Policy object. A single Policy object is installed in the runtime at any given time. Java’s default policy implementation expects policy to be specified in files called policy configuration files. The policy information contained in these configuration files is loaded in the Policy object during its creation. A security manager installed for an application consults this Policy object and allows or disallows actions requested by the application. The location of policy files may be specified either in the security property file or at runtime (as command line argument). The security property file java.security can be found in in the directory java.home\lib\security in Windows and java.home/lib/security in Unix like OS where java.home refers to the directory that hosts the Java Runtime Environment (JRE). The locations (urls) of policy files are specified in this property file as the values of properties whose names have the form

policy.url.n

where possible values of n are 1,2, 3,… The policy file locations are specified as values of these properties in the following form:

policy.url.n=URL

where URL is the location of the policy file. The java.security file typically specifies two policy files as follows:

policy.url.1=file:${java.home}/lib/security/java.policy

policy.url.2=file:${user.home}/.java.policy

The file java.policy can indeed be found in the directory java.home\lib\security in Windows and java.home/iib/security in Unix like OS. This file is called system-wide default policy file as it is consulted first by the Java’s built-in security manager. Similarly, a user may place his/her own policy in file .java.policy which should be stored in the user’s home directory. Other policy files may also be specified subsequently using the value of n as 3, 4, 5,…. A security manager loads the permissions specified in all those policy files in order to take a decision. Make sure that the value of n is continuous.

This has a problem as those steps are required after every reinstallation of Java or after redeployment in a different JVM. However, it may be useful for granting permissions to code that are seldom redeployed.

Additional or a different policy file may also be specified using command line argument -Djava.

security.poiicy. Here is an example:

java -Djava.security.manager -Djava.security.policy=policyURL SomeApp

where policyURL is the location of the policy file, which is loaded in addition to all the policy files that are specified in the security property file java.security. This policyURL may be any regular URL including the name of a policy file in the current directory. Note that in a policy file, we basically allow permission. So, once a permission is allowed in a policy file, it cannot be withdrawn in the subsequent policy files. However, newer permissions can always be given in the subsequent policy files.

It is always a good idea to use forward slash(/), instead of backslash (\) as the directory separator as it works both in Windows as well as Linux like OS. Note that, on Windows systems, whenever we specify a file path in a string, we need to include two backslashes for each actual single backslash in the path.

A different policy file is specified in Djava.security.policy argument using == (double equals) as follows:

java -Djava.security.manager -Djava.security.policy==my.policy SomeApp

Here, security manager will ONLY load the policy file my.policy ignoring all other policy files specified in the security property file. Note that for appletviewer, correct command line argument

is -J-Djava.security.policy. Also make sure that the value of policy.allowSystemProperty in java.security file is true to work with Djava.security.policy command line argument.

An application that uses policy files is described in Section 16.16

3. Policy File Syntax

In general, a permission merely states that some “thing” can take some “actions” on some “target”. For example, in a multi-user OS, a permission means if a user (thing) can read (action) a file (target). Permissions are specified in a policy file using predefined syntax. A policy file contains a list of entries. An entry may be of two types: keystore entry or grant entry. There may be zero or one keystore entry and zero or more grant entries.

3.1. Keystore Entry

This entry is used to provide the keystore information to retrieve the public keys of the signers specified in the grant entries. If any grant entries specify signer aliases, or principal aliases (see next section), this entry is required and may appear anywhere outside the grant entries in the policy file. It takes the following form:

keystore “URL”, “type”, “provider”;

where url is the location of the keystore, optional type if the keystore type (default is jks) and optional provider is keystore provider (default is sun). Here is an example of keystore entry:

keystore “jerry.jks”;

The URL of the keystore is relative to the policy file location.

3.2. Grant Entry

In this entry, actual permissions to be allowed are specified. Each grant entry has zero or more permission entries. It may also have signedBy, CodeBase and principal entries that collectively specify the code which the permissions have to be applied on. A grant entry takes the following form:

grant signedBy “signer_names”, CodeBase “URL”,

principal principal_class_name “principal_name”, principal principal_class_name “principal_name”,

… {

permission permission_class_name “target_name”, “action”, signedBy “signer_names”;

permission permission_class_name “target_name”, “action”, signedBy “signer_names”;

};

A signedBy clause indicates that permissions are applicable only to the code which was signed by the private key corresponding to the public key in the keystore (indicated by keystore entry) entry specified by signedBy value. Consider the following example:

keystore “jerry.jks”, “jks”, “SUN”;

grant signedBy “tom” {

permission java.io.FilePermission “../data/*”, “read”;

};

This means “any code in a JAR file, which is signed using the private key corresponding to the public key certificate in the keystore whose entry is aliased by tom, has read permission to all files under the sibling data directory”.

The value of signedBy may be a comma-separated list of aliases to the keystore. For example, signedBy “tom, jerry” means, the code must be signed using the private keys corresponding to the public key certificates in the keystore whose entry is aliased by tom AND jerry to have the designated permission.

The signedBy clause is optional and if absent, permissions are applied to all the codes, signed by it or not.

The CodeBase clause is used to allow permission to that code originated from a location specified by the CodeBase value. Consider the following example:

grant CodeBase “file:/E:/ajp/sec/sm/jerry/*” {

};

This says that all class files under the specified directory have the listed permissions. Use forward slash(/), not backslash (\), as the directory separator. Moreover, the characters at the end determine the exact meaning of the CodeBase value.

  • A JAR file name matches only the classes in the JAR file.
  • A trailing “/” matches all class files (but not JAR files) in the specified directory.
  • Trailing “/*” matches all class and JAR files contained in the specified directory.
  • A trailing “/-” matches all class and JAR files in the directory and recursively all files in subdirectories contained in that directory.

The CodeBase clause is optional and if absent, permissions are applied to the code originated from any source.

3.3. Permission Entry

A permission entry starts with the keyword permission followed by the following piece of information

  • A permission class name representing a permission type
  • The name of a target, permission has to be applied on
  • Optional comma separated actions that are allowed to be performed on the target.
  • Optional signedBy name/value pair

A permission class is a subclass (directly or indirectly) of abstract java.security permission class and represents access to a system resource. Examples of permission classes include FilePermission, SocketPermission, RuntimePermission, PropertyPermission etc. A target is a resource, which the permission is specified for. The “actions” list tells the actions that are permitted for the resource. Here is an example:

permission java.io.FilePermission ”/tmp/*”, ”read,write”;

This grants read/write access to all files in /tmp (in Linux). The action is not required for classes such as java.lang.RuntimePermission.

The signedBy clause for a permission entry is optional. If present, it indicates that the permission class itself must be signed by the specified alias(es) in order to grant the permission.

permission aClass ”aTarget”, “actions”, signedBy ”tom”;

This says that for the permission aClass, actions on aTarget are granted if the permission class aClass is placed in a JAR file, which was signed by the private key corresponding to the public key in the certificate specified by the alias tom. The signedBy clause is ignored for system classes, since they are not subject to policy restrictions.

Examples of Permission Entry

Allows accepting socket connection from 172.16.5.82

permission java.net.SocketPermission ”172.16.5.82”, “accept”;

Allows connecting to port 6789 on 172.16.4.120.82

permission java.net.SocketPermission ”172.16.4.120”, “connect”;

Allows accepting connections on, connect to, or listen on any port 999 and above on the local host.

permission java.net.SocketPermission ”localhost:999-”, ”accept,connect,listen”;

4. Custom Permission Class

New permission classes may be created if built-in permission classes are not adequate. Consider the following scenario:

A department has many students who want to get printouts from a printer. However, only “tom” has the permission to do so. Since, no build-in permission class does not fit well for this situation, we write our own permission class PrintPermission. All permission classes must extend either

Permission or BasicPermission (which itself is a subclass of Permission ). The Permission defines more complex permissions that require names and actions, whereas BasicPermission defines simpler permissions that only require a name. Our PrintPermission also requires only a name and hence can be extended from BasicPermission as follows:

public class PrintPermission extends java.security.BasicPermission {

public PrintPermission(String target) { super(target);

}

}

We do not override the methods of BasicPermission as we don’t want extra functions. However, we invoke super class constructor. We also write another minimal class Student having a single method print() as follows:

//Student.java public class Student {

String name;

public Student(String name) { this.name = name; }

public void print() {

SecurityManager sm = System.getSecurityManager();

if (sm != null) {

sm.checkPermission(new PrintPermission(name));

}

//print document here

}

}

To check if a student really has print permission, this class

  • Calls getsecurityManager() to get the currently installed security manager.
  • If there is a security manager (i.e. the result is not null), then
  • Create a PrintPermission object, and
  • Invoke security manager’s checkPermission() method passing newly created object.

The underlying security framework consults the security policy to see if the student indeed has this permission. A sample application can then be written as:

//Dept.java public class Dept {

public static void main(String args[]) {

Student s = new Student(”tom”);

s.print();

}

}

Below is a sample policy file that allows print permission for “tom”.

//sample.policy grant {

permission PrintPermission “tom”;

};

Compile all the source files and start the application using the following command:

java -Djava.security.manager -Djava.security.policy=sample.policy

Dept

Source: Uttam Kumar Roy (2015), Advanced Java programming, Oxford University Press.

Leave a Reply

Your email address will not be published. Required fields are marked *