ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

Java vs. .NET Security, Part 3
Pages: 1, 2, 3, 4

Code Access Security: Permissions

Code-access permissions represent authorization to access a protected resource or perform a dangerous operation, and form a foundation of CAS. They have to be explicitly requested from the caller either by the system or by application code, and their presence or absence determines the appropriate course of action.



Both Java and .NET supply an ample choice of permissions for a variety of system operations. The runtime systems carry out appropriate checks when a resource is accessed or an operation is requested. Additionally, both platforms provide the ability to augment those standard permission sets with custom permissions for protection of application-specific resources. Once developed, custom permissions have to be explicitly checked for (demanded) by the application's code, because the platform's libraries are not going to check for them.

.NET defines a richer selection here, providing permissions for role-based checks (to be covered in the "User Access Security" section of Part 4) and evidence-based checks. An interesting feature of the latter is the family of Identity permissions, which are used to identify an assembly by one of its traits -- for instance, its strong name (StrongNameIdentityPermission). Also, some of its permissions reflect close binding between the .NET platform and the underlying Windows OS (EventLogPermission, RegistryPermission). IsolatedStoragePermission is unique to .NET, and it allows low-trust code (Internet controls, for instance) to save and load a persistent state without revealing details of a computer's file and directory structure. Refer to MSDN documentation for the list of .NET Code Access and Identity permissions.

Adding a custom code access permission requires several steps. Note that if a custom permission is not designed for code access, it will not trigger a stack walk. The steps are:

  • Optionally, inherit from CodeAccessPermission (to trigger a stack walk).
  • Implement IPermission and IUnrestrictedPermission.
  • Optionally, implement ISerializable.
  • Implement XML encoding and decoding.
  • Optionally, add declarative security support through an Attribute class.
  • Add the new permission to CAS Policy by assigning it to a code group.
  • Make the permission's assembly trusted by .NET framework.

A sample of custom code-access permission can be found in the demo application. Also, check MSDN for additional examples of building and registering a custom permission with declarative support.

.NET permissions are grouped into NamedPermissionSets. The platform includes the following non-modifiable built-in sets: Nothing, Execution, FullTrust, Internet, LocalIntranet, SkipVerification. The FullTrust set is a special case, as it declares that this code does not have any restrictions and passes any permission check, even for custom permissions. By default, all local code (found in the local computer directories) is granted this privilege.

The above fixed permission sets can be demanded instead of regular permissions:

[assembly:PermissionSetAttribute(
         SecurityAction.RequestMinimum, 
         Name="LocalIntranet")]

In addition to those, custom permission sets may be defined, and a built-in Everything set can be modified. However, imperative code-access checks cannot be applied to varying permission sets (i.e., custom ones and Everything). This restriction is present because they may represent different permissions at different times, and .NET does not support dynamic policies, as it would require re-evaluation of the granted permissions.

Permissions, defined in Java, cover all important system features: file access, socket, display, reflection, security policy, etc. While the list is not as exhaustive as in .NET, it is complete enough to protect the underlying system from the ill-behaving code. See the JDK documentation for the complete list, and the Java permissions guide for more detailed discussions of their meaning and associated risks.

Developing a custom permission in Java is not a complicated process at all. The following steps are required:

  • Extend java.security.Permission or java.security.BasicPermission.
  • Add new permission to the JVM's policy by creating a grant entry.

Obviously, the custom permission's class or JAR file must be in the CLASSPATH (or in one of the standard JVM directories), so that JVM can locate it.

Below is a simple example of defining a custom permission. More examples can be found in the demo application or in the Java tutorial:

//permission class
public class CustomResPermission extends Permission {
  public CustomResPermission (String name, 
                            String action) {
  super(name,action);
  }
}

//library class
public class AccessCustomResource {
  public String getCustomRes() {
    SecurityManager mgr = 
         System.getSecurityManager();
    if (mgr == null) {
	  //shouldn't run without security!!!
	  throw new SecurityException();
    } else {
	  //see if read access to the resource 
	  //was granted
	  mgr.checkPermission(
        new CustomResPermission("ResAccess","read"));
    }
   //access the resource here
   String res = "Resource";
   return res;
  }
}

//client class
public class CustomResourceClient {
  public void useCustomRes() {
    AccessCustomResource accessor = 
     new AccessCustomResource();
    try {
      //assuming a SecurityManager has been 
      //installed earlier
      String res = accessor.getCustomRes();
    } catch(SecurityException ex) {
      //insufficient access rights
    }
  }
}

J2EE reuses Java's permissions mechanism for code-access security. Its specification defines a minimal subset of permissions, the so-called J2EE Security Permissions Set (see section 6.2 of the J2EE.1.4 specification). This is the minimal subset of permissions that a J2EE-compliant application might expect from a J2EE container (i.e., the application does not attempt to call functions requiring other permissions). Of course, it is up to individual vendors to extend it, and most commercially available J2EE application servers allow for much more extensive application security sets.

Note: .NET defines a richer sets-based permission structure than Java. At the same time, .NET permissions reveal their binding to the Windows OS.

Code Access Security: Policies

Code Access Security is evidence-based. Each application carries some evidence about its origin: location, signer, etc. This evidence can be discovered either by examining the application itself, or by a trusted entity: a class loader or a trusted host. Note that some forms of evidence are weaker than others, and, correspondingly, should be less trusted -- for instance, URL evidence, which can be susceptible to a number of attacks. Publisher evidence, on the other hand, is PKI-based and very robust, and it is not a likely target of an attack, unless the publisher's key has been compromised. A policy, maintained by a system administrator, groups applications based on their evidence, and assigns appropriate permissions to each group of applications.

Evidence for the .NET platform consists of various assembly properties. The set of assembly evidences, which CLR can obtain, defines its group memberships. Usually, each evidence corresponds to a unique MembershipCondition, which are represented by .NET classes. See MSDN for the complete listing of standard conditions. They all represent types of evidence acceptable by CLR. For completeness, here is the list of the standard evidences for the initial release: AppDirectory, Hash, Publisher, Site, Strong Name, URL, and Zone.

.NET's policy is hierarchical: it groups all applications into so-called Code Groups. An application is placed into a group by matching its Membership Condition (one per code group) with the evidence about the application's assembly. Those conditions are either derived from the evidence or custom-defined. Each group is assigned one of the pre-defined (standard or custom) NamedPermissionSet. Since an assembly can possess more than one type of evidence, it can be a member of multiple code groups. In this case, its total permission set will be a union of the sets from all groups (of a particular level) for which this assembly qualifies. Figure 3 depicts code-group hierarchy in the default machine policy (also see MSDN):

Figure 3
Figure 3. NET Default Code Groups

Custom groups may be added under any existing group (there is always a single root). Properly choosing the parent is an important task, because due to its hierarchical nature, the policy is navigated top-down, and the search never reaches a descendent node if its parents' MembershipCondition was not satisfied. In Figure 3 above, the Microsoft and ECMA nodes are not evaluated at all for non-local assemblies.

Built-in nodes can be modified or even deleted, but this should be done with care, as this may lead to the system's destabilization. Zones, identifying code, are defined by Windows and managed from Internet Explorer, which allows adding to or removing whole sites or directories from the groups. All code in non-local groups have special access rights back to the originating site, and assemblies from the intranet zone can also access their originating directory shares.

To add a custom code group using an existing NamedPermissionSet with an associated MembershipCondition, one only needs to run the caspol.exe tool. Note that this tool operates on groups' ordinal numbers rather than names:

caspol -addgroup 1.3 -site 
   www.MySite.com LocalIntranet

Actually, .NET has three independent policies, called Levels: Enterprise, Machine, and User. As a rule, a majority of the configuration process takes place on the Machine level — the other two levels grant FullTrust to everybody by default. An application can be a member of several groups on each level, depending on its evidence. As a minimum, all assemblies are member of the AllCode root group.

Policy traversal is performed in the following order: Enterprise, Machine, and then User, and from the top down. On each level, granted permission sets are determined as follows:

Level Set = Set1 U Set2 U ... U SetN

where 1..N - code groups matching assembly's evidence. System configuration determines whether union or intersection operation is used on the sets.

The final set of permissions is calculated as follows:

Final Set = Enterprise X Machine X User

Effectively, this is the least common denominator of all involved sets. However, the traversal order can be altered by using Exclusive and LevelFinal policy attributes. The former allows stopping intra-level traversal for an assembly; the latter, inter-level traversal. For instance, this can be used to ensure, on the Enterprise level, that a particular assembly always has enough rights to execute.

This demo application demonstrates the tasks involved in granting custom permissions in the policy and executing code that requires those permissions.

Each policy maintains a special list of assemblies, called trusted assemblies -- they have FullTrust for that policy level. Those assemblies are either part of CLR, or are specified in the CLR configuration entries, so CLR will try to use them. They all must have strong names, and have to be placed into the Global Assembly Cache (GAC) and explicitly added to the policy (GAC can be found in the %WINDIR%\assembly directory):

gacutil /i MyGreatAssembly.dll

caspol -user -addfulltrust MyGreatAssembly.dll

Figure 4 shows the Machine-level trusted assemblies:

Figure 4
Figure 4. NET Trusted Assemblies

For Java, two types of code evidence are accepted by the JVM -- codebase (URL, either web or local), from where it is accessed, and signer (effectively, the publisher of the code). Both evidences are optional: if omitted, all code is implied. Again, publisher evidence is more reliable, as it less prone to attacks. However, up until JDK 1.2.1, there was a bug in the SecurityManager's implementation that allowed replacing classes in a signed JAR file and then continuing to execute it, thus effectively stealing the signer's permissions.

Policy links together permissions and evidence by assigning proper rights to code, grouped by similar criteria. A JVM can use multiple policy files; two are defined in the default java.security:

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

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

This structure allows creating multi-level policy sets: network, machine, user. The resulting policy is computed as follows: Policy = Policy.1 U Policy.2 U ... U Policy.N JVM uses an extremely flexible approach to providing policy: the default setting can be overwritten by specifying a command-line parameter to the JVM:

//adds MyPolicyFile to the list of policies

java -Djava.security.policy=MyPolicyFile.txt

// replaces the existing policy with MyPolicyFile 

java -Djava.security.policy==MyPolicyFile.txt

Java policy has a flat structure: it is a series of grant statements, optionally followed by evidence, and a list of granted permissions. A piece of code may satisfy more than one clause's condition — the final set of granted permissions is a union of all matches:

grant [signedBy "signer1", ..., "signerN"] [codeBase "URL"] {

	permission <PermissionClassName> "TargetName", "Action"
	    [signedBy "signer1", ..., "signerN"];
	...

}

This demo application defines a custom permission in the policy and executes applications requiring that permission.

Even locally installed classes are granted different trust levels, depending on their location:

  • Boot classpath: $JAVA_HOME/lib, $JAVA_HOME/classes

    These classes automatically have the full trust and no security restrictions. Boot classpath can be changed both for compilation and runtime, using command-line parameters: -bootclasspath and -Xbootclasspath, respectively.

  • Extensions: $JAVA_HOME/lib/ext

    Any code (JAR or class files) in that directory is given full trust in the default java.policy:

    grant codeBase "file:{java.home}/lib/ext/*" {
    	permission java.security.AllPermission;
    }
  • Standard classpath: $CLASSPATH ("." by default)

    By default, have only few permissions to establish certain network connections and read environment properties. Again, the SecurityManager has to be installed (either from command line using the -Djava.security.manager switch, or by calling System.setSecurityManager) in order to execute those permissions.

Policy-based security causes problems for applets. It's unlikely that a web site's users will be editing their policy files before accessing a site. Java does not allow runtime modification to the policy, so the code writers (especially applets) simply cannot obtain the required execution permissions. IE and Netscape have incompatible (with Sun's JVM, too!) approaches to handling applet security. JavaSoft's Java plug-in is supposed to solve this problem by providing a common JRE, instead of the browser-provided VM.

If the applet code needs to step outside of the sandbox, the policy file has to be edited anyway, unless it is an RSA-signed applet. Those applets will either be given full trust (with user's blessing, or course), or if policy has an entry for the signer, it will be used. The following clause in the policy file will always prevent granting full trust to any RSA-signed applet:

grant {
  permission java.lang.RuntimePermission "usePolicy";
}

Note: Policy in .NET has a much more sophisticated structure than in Java, and it also works with many more types of evidences. Java defines very flexible approach to adding and overriding default policies -- something that .NET lacks completely.

Pages: 1, 2, 3, 4

Next Pagearrow