ONJava.com    
 Published on ONJava.com (http://www.onjava.com/)
 See this if you're having trouble printing code examples


Java vs. .NET Security, Part 3
Code Protection and Code Access Security (CAS)

by Denis Piliptchouk
01/28/2004

This is the third article in a series of Java vs. .NET security comparisons. It deals with the issues of code protection and distribution, and Code Access Security (CAS) mechanisms on those platforms. Previous articles of this series covered configuration and code containment in Part 1, and cryptography support and the mechanisms of communication protection in Part 2.

Once code or an algorithm has been written, it becomes an asset that requires protection. Such a protection is needed not only against theft, but also against unauthorized or unintended use. On the other hand, when somebody purchases a software package, he wants to be confident that he is not dealing with a counterfeit product. To answer all of these challenges, various techniques, broadly divided into cryptography-based and everything else, are employed for code protection and verification.

Code-access security is also known as policy-based security. It allows minimizing the risks of executing certain application code by providing policies restricting it to only a particular, well-defined set of operations that the code is permitted to execute. Of course, the underlying services (platform or application) have to actually carry out those checks for the policy to become effective.

Code Protection: General

Issues discussed in this section are applicable, to a certain degree, to both platforms. They also employ similar protection mechanisms to combat those problems.

The possibility of the reverse engineering of distributed bytecodes needs to be taken into account when planning the security aspects of an application, because bytecode formats are well-documented for both Java and .NET (see also GotDotNet), so any hardcoded data or algorithms may be easily restored with readily obtainable decompiling tools. This point is especially important for avoiding hardcoding user credentials or non-patented algorithms in client-based application modules.

O'Reilly Emerging Technology Conference.

While there is no ideal way around this issue, short of shipping encrypted code and providing just-in-time decryption, an average perpetrator's task may be made harder by using so called obfuscators; i.e., tools that intentionally scramble bytecodes by using unintelligible names and moving entry points around. In addition to the obfuscator tool available with VS.2003, a number of decompiling/obfuscating tools can be found at the Microsoft web site. For Java, a great number of commercial or free Java decompilers and obfuscators can be found by running a simple search on the Web.

Finally, OS-level protection mechanisms need to be utilized, along with the platform ones, in order to ensure good protection of the stored data. All of the hard work at the platform level is useless if code or data can be obtained and inspected as raw binary files. Therefore, any normal OS operation security rules (like ACL, minimizing attack surface, the principle of "least privilege," etc.) should be employed in addition to the platform-specific ones, in order to ensure blanket protection.

Certificate Management

Before addressing cryptography-based code protection features, the issue of certificate management in general needs to be covered, because all cryptography-based solutions deal, in one way or another, with certificates or keys. First of all, certificates need to be created and stored, and then accessed from the applications. Both platforms supply tools to issue certificate requests, as well as APIs for accessing the stored certificates.

.NET, as usual, heavily relies on Windows certificate stores to deal with certificates — they are used to store X509 certificates and certificate chains of trusted signers. There are a number of tools included with the .NET SDK to help accessing those stores, manage certificates, and sign assemblies using those certificates.

.NET's Certificate API is represented in its System.Security.Cryptography.X509Certificates namespace, where the X509Certificate class is of particular interest to us. Unfortunately, this class is rather poorly designed; it does not support accessing certificates in certificate stores, but works only with binary ASN.1 DER format, and does not provide any way to use it in asymmetrical encryption. The official suggestion from Microsoft is to stick with using unmanaged CryptoAPI (CAPI) functions, in particular CryptExportKey/CryptImportKey. See MSDN articles for details of bridging .NET's managed certificate implementation with CAPI.

Another, much better alternative is using WSE (already covered in Part 2). It provides the Microsoft.Web.Services.Security.X509 namespace with several useful classes, among them another version of X509Certificate, derived from the .NET-supplied one. This class recognizes the shortcomings of its predecessor and provides a very convenient interface for accessing certificate stores, as well as extracting public/private key information in a format appropriate for asymmetric encryption. As an added benefit, it can read certificates stored in Base64 text format. Together with the X509CertificateStore class, they make .NET's certificate API pretty well rounded. The following MSDN example shows how they can be used together:

// Open and read the Personal certificate store for 
// the local machine account.
X509CertificateStore myStore = 
  X509CertificateStore.LocalMachineStore(
  X509CertificateStore.MyStore);
myStore.OpenRead();

// Search for all certificates named "My Certificate" 
// add all matching certificates 
// to the certificate collection.
X509CertificateCollection myCerts = 
  myStore.FindCertificateBySubjectString(
          "My Certificate");
X509Certificate myCert = null;

// Find the first certificate in the collection 
// that matches the supplied name, if any.
if (myCerts.Count > 0)
{
  myCert = myCerts[0];
}

// Make sure that we have a certificate 
// that can be used for encryption.
if (myCert == null || 
    !myCert.SupportsDataEncryption)
{
  throw new ApplicationException(
    "Service is not able to encrypt the response");
  return null;
}

The Java platform implements RFC 3280 in the Certification Path API, which is supplied in the default "SUN" provider. This API, however, allows read-only — retrieving, accessing attributes, etc. — access to certificates, because they are considered to be immutable entities in Java. Classes implementing Certification Path API, belong to the JCA framework and can be found in the java.security.cert package. There are three classes of interest there:

Java uses so-called keystores for storing certificates. They can have different formats, as supplied by JCA providers (see Part 2); the default is Sun's proprietary JKS format. There are common keystores that contain keys, both public and private (or symmetric, if desired), and truststores, which are used to establish certificate chains. The JVM uses truststores (lib/security/cacert by default) to store the trusted certificates, and keystores for accessing key information. Having keystores as separate files is a nice feature in Java, as it is easy to move them around and manage them (compared to .NET's reliance on CAPI containers). Both stores can be specified as parameters on the command line, or accessed directly from code:

java -Djavax.net.ssl.keyStore=MyKeyStore 
    -Djavax.net.ssl.keyStorePassword=password 
    -Djavax.net.ssl.trustStore=MyTrust MyClass

Compared to the standard .NET certificate implementation, Java provides very convenient facilities of working with certificates. The examples below demonstrate how easy it is to obtain certificates from a keystore:

FileInputStream fin = new FileInputStream("MyKeyStore");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(fin,"password");
Certificate cert = ks.getCertificate("MyEntry");

or from a file:

FileInputStream fin = 
            new FileInputStream("MyCert.cer");
CertificateFactory factory = 
            CertificateFactory.getInstance("X.509");
X509Certificate cert = (X509Certificate)            
            factory.generateCertificate(fin);

Java provides a tool for generating keys, keytool, which has a number of options. Among them are importing/exporting certificates, creating test certificates, creating certificate signing requests, etc. This tool picks up the keystore types (JKS, PKCS#12, etc.) defined in the java.security configuration file, and can use plugged-in provider types to operate on various types of keystores. By default, keytool generates only X.509 v1 certificates, which may be restricting for some applications.

Becoming a Certificate Authority (CA) on your own is problematic, but not impossible, with Java. One can either purchase a commercial library, or build his or her own CA using sun.security.x509 classes, although they only work with JKS keystores. However, the latter solution is neither portable nor documented. There are also a number of open source libraries that allow you to deal with certificate management, including CA functionality. A good free implementation is OpenSSL.

Note: Java provides a solid API for dealing with certificates. .NET programmers have to turn to unmanaged CAPI functions to access certificates, unless they use WSE, which adds a lot of useful functionality.

Code Protection: Cryptographic

Cryptography-based mechanisms include certificates, digital signatures, and message digests, which are used to "shrink-wrap" distributed software and data. They establish software origin and verify its integrity with a high degree of reliability, subject to the strength of the underlying cryptography algorithm.

In their simplest forms, CRC or digests are used to verify software integrity. For more involved implementations, Message Authentication Code (MAC), or Hash-based MAC (HMAC), specified in RFC 2104, may be applied, which add cryptographic protection (using symmetric secret keys) for improved protection. Both platforms support most common digest, MAC, and HMAC functions in their respective cryptography namespaces. See Part 2 of this series for details of supported algorithms. Numerous code samples are available on the Web for both Java and .NET (also see MSDN).

These sample applications for .NET and Java demonstrate signing capabilities.

For application distribution, .NET supports software signing to prove application or publisher identities. For the first task, it provides so-called strong names, and for the second, signing with publisher certificates. These approaches are complementary and independent; they can be used individually or jointly, thus proving the identities of both the application and publisher. The users can configure .NET CAS policy based either on strong names, or on the software publisher, because they both provide strong assurances about the signed code. Because of their high levels of trust, strong-named assemblies can call only strong-named assemblies.

Strong names are used to prove authenticity of the assembly itself, but they have no ties to the author, as it is not required to use the developer's certificate to sign an assembly. A strong name can be viewed as a descendent of GUID mechanism, applied to the assembly names, and composed of text name, version number, culture information, plus the assembly's digital signature and a unique public key, all stored within the assembly's manifest, as shown in Figure 1:

Figure 1
Figure 1. Manifest of a Strong-Named Assembly

The same key pair should not be reused for signing multiple assemblies — unique key pairs should be generated for signing different assemblies. Note that a version is not guaranteed by the strong name due to applying CLR versioning policy, as the same key will be reused to sign a newer version of the particular assembly. .NET provides a tool named sn.exe to generate key pairs and perform a number of validation steps related to strong names. Generated keys can either be picked up by AssemblyLinker, or added to the assembly declaratively by using an attribute:

[assembly: AssemblyKeyFile(@"CommonLib.snk")]

Clients, linked against a strong-named assembly, store a PublicKeyToken representing the assembly, an eight-byte hash of the full public key used to create its strong name, as shown in Figure 2. This is done transparently by the compiler when a strong-named assembly is referenced. This is an example of early binding.

Figure 2
Figure 2. Manifest with Public Key Token

Late binding can be achieved by using Assembly.Load and calling the full display name of the assembly, including its token:

Assembly.Load("CommonLibrary,
   Version= 1:0:1385:17444,
   Culture=neutral,
   PublicKeyToken=F5BE3B7C2C523B5D");

The CLR always verifies an assembly's key token at runtime, when it loads the referenced assembly. Two strong names are configured in security policy by default: one for Microsoft code, another for .NET's components submitted to ECMA for standardization.

Publisher certificates establish the identity of the code distributor by requiring him to use a personal certificate to sign a whole assembly or individual files in an assembly. The signature is then stored inside of the file and verified by the CLR at runtime. .NET provides the Signcode.exe tool to perform the publisher signing operation. To do its job, it should have access to both the publisher certificate (with a valid trust chain), and the private key for that certificate, which will be used to sign the file(s).

Publisher certificates, as opposed to the strong names concept, are used to sign multiple applications and cannot uniquely identify a particular code module, nor are they intended to. Their intent is to identify a broad set of applications as originating from a particular vendor, in order to assign appropriate permissions based on the level of trust in this company.

As far as signing distributives goes, Java offers a single route that is similar the publisher-signing paradigm in .NET. However, there are significant differences in the approaches, since JAR specifications permit multiple signers and signing of a subset of the JAR's content.

Quite surprisingly, in Sun's default distribution of JDK 1.4.2, only jce.jar is signed -- all of the other libraries do not have any signature. As a standard part of the JDK, Sun ships the jarsigner tool, which works with Java keystores to obtain private keys and certificates for signing and verification to validate certification chains. This tool operates on existing JAR files (it does not create them), which are a standard distribution format in Java.

jarsigner -keystore PrivateKeystore.jks 
          -sigfile DP -signedjar DemoApp_Sig.jar 
          DemoApp.jar denis

When a JAR file is signed, its manifest is updated and two new files are added to the META-INF directory of the archive (see the JAR Guide for details): class signature and signature block.

The manifest file is called MANIFEST.MF and contains digest records of all signed files in the archive (which may be a subset of the archive!). Those records conform to the RFC 822 header specification and consist of a file name and one or more tuples (digest algorithm, file digest). As a rule, either SHA1 or MD5 digest algorithms are used. It is the manifest itself, not the physical JAR file, that is signed, so it is important to understand that once a JAR is signed, its manifest should not change -- otherwise, all signatures will be invalidated.

Manifest-Version: 1.0
Created-By: 1.4.2-beta (Sun Microsystems Inc.)

Name: javax/crypto/KeyGeneratorSpi.class
SHA1-Digest: HxiOMRd8iUmo2/fulEI1QH7I2Do=

Name: javax/crypto/spec/DHGenParameterSpec.class
SHA1-Digest: zU+QpzVweIcLXLjmHLKpVo55k0Q=

A signature file represents a signer, and an archive contains as many of these files as there are signatures on it. File names vary, but they all have the same extension, so they looks like <Name>.SF. Signature files contain "digests of digests" they consist of entries with digests of all digest records in the manifest file at the time of signing. Those records conform to RFC 822 header specification and have the same format as the manifest's ones. Additionally, this file also contains a digest for the entire manifest, which implies that the JAR manifest may not be changed once signed. Incidentally, this means that all signers have to sign the same set of files in the JAR — otherwise, if new files have been added to the JAR prior to generating another signature, their digests will be appended to the manifest and invalidate already existing signatures.

An important point to observe is that when a JAR file is signed, all of the files inside it are signed, not only the JAR itself. Up until JDK 1.2.1, signed code had a serious bug: it was possible to alter or replace the contents of a signed JAR, and the altered class was still allowed to run. This problem was rectified starting with version 1.2.1 by signing each class inside of a signed JAR.

Signature-Version: 1.0
Created-By: 1.4.2-beta (Sun Microsystems Inc.)
SHA1-Digest-Manifest: qo3ltsjRkMm/qPyC8xrJ9BN/+pY=

Name: javax/crypto/KeyGeneratorSpi.class
SHA1-Digest: FkNlQ5G8vkiE8KZ8OMjP+Jogq9g=

Name: javax/crypto/spec/DHGenParameterSpec.class
SHA1-Digest: d/WLNnbH9jJWc1NnZ7s8ByAOS6M=

A block signature file contains the binary signature of the SF file and all public certificates needed for verification. This file is always created along with the SF one, and they are added to the archive in pairs. The file name is borrowed from the signature file, and the extension reflects the signature algorithm (RSA|DSA|PGP), so the whole name looks like <Name>.RSA.

The JAR-signing flexibility comes from separating digest and signature generation, which adds a level of indirection to the whole process. When signing or verifying, individual signers operate on the manifest file, not the physical JAR archive, since it is the manifest entries that are signed. This allows for an archive to be signed by multiple entities and to add/delete/modify additional files in the signed JAR, as long as it does not affect the manifest (see the explanations in the signature file paragraph).

Note: Strong names in .NET offer an improved approach to versioning. JAR files, on the other hand, have more options for signing, so this category is a draw.

Code Protection: Non-Cryptographic

Once a piece of software's origin and integrity have been established, non-cryptographic approaches may be used to ensure that the code can not be used in an unintended manner. In particular, this implies that the platform's package- and class-protection mechanisms cannot be subverted by illegally joining those packages or using class derivation to gain access to protected or internal members. These types of protection are generally used to supplement CAS and aimed at preventing unauthorized execution or source code exposure.

Reflection mechanisms on both platforms allow for easy programmatic access to code details and very late binding of arbitrary code, or even utilize code generation capabilities -- see, for instance,.NET technology samples, or the Java Reflection tutorial. One common example of such a threat would be a spyware application, which secretly opens installed applications and inspects/executes their functionality in addition to its officially advertised function. To prevent such code abuse, granting reflection permissions (System.Security.Permissions.ReflectionPermission in .NET, java.lang.reflect.ReflectPermission in Java) in CAS policy should be done sparingly and only to highly trusted code, in order to restrict capabilities for unauthorized code inspection and execution.

In .NET, application modules are called assemblies, and located at runtime by a so-called probing algorithm. By default, this algorithm searches for dependent assemblies only in the main assembly's directory, its subdirectories, and the Global Assembly Cache (GAC). Such a design is used to guard against possible attempts to access code outside of the assembly's "home." Note that it does not prevent loading and executing external assemblies via reflection, so CAS permissions should be applied as well.

Types in .NET are organized into namespaces. One may extend an already established namespace in his own assemblies, but will not gain any additional information by doing so, since the keyword internal is applied at the assembly, and not namespace, level. Strong names are used as a cryptographically strong measure against replacement of the existing types.

If the designer wants to completely prohibit inheritance from a class or method overloading, the class or method may be declared sealed. As an additional means of protection against source browsing, the C# language defines a #line hidden directive to protect against stepping into the code with a debugger. This directive instructs the compiler to avoid generating debugging information for the affected area of code.

During execution of a Java application, class loaders are responsible for checking at loading/verification time that the loaded class is not going to violate any package protection rules (i.e., does not try to join a sealed or protected package). Particular attention is paid to the integrity of system classes in java.* packages — starting with version 1.3, the class-loading delegation model ensures that these are always loaded by the null, or primordial class loader (see "Secure Class Loading" for details).

The Java platform defines the following options for protecting packages from joining:

A sample Java application, demonstrating declarative access control to packages, can be obtained here.

Note: Configuration options in Java add a convenient method for declarative code protection, which gives it a slight edge over .NET in this category.

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:

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:

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:

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.

Code Access Security: Access Checks

Code access checks are performed explicitly; the code (either an application, or a system library acting on its behalf) calls the appropriate Security Manager to verify that the application has the required permissions to perform an operation. This check results in an operation known as a stack walk: the Runtime verifies that each caller up the call tree has the required permissions to execute the requested operation. This operation is aimed to protect against a luring attack, where a privileged component is misled by a caller into executing dangerous operations on its behalf. When a stack walk is performed prior to executing an operation, the system can detect that the caller is not allowed to do what it is requesting, and abort the execution with an exception.

Privileged code may be used to deal with luring attacks without compromising overall system security, and yet provide useful functionality. Normally, the most restrictive set of permissions for all of the code on the current thread stack determines the effective permission set. To bypass this restriction, a special permission can be assigned to a small portion of code to perform a reduced set of restricted actions on behalf of under-trusted callers. All of the clients can now access the protected resource in a safe manner using that privileged component, without compromising security. For instance, an application may be using fonts, which requires opening font files in protected system areas. Only trusted code has to be given permissions for file I/O, but any caller, even without this permission, can safely access the component itself and use fonts.

Finally, one has to keep in mind that code access security mechanisms of both platforms sit on top of the corresponding OS access controls, which are usually role or identity-based. So, for example, even if Java/.NET's access control allows a particular component to read all of the files on the system drive, the requests might still be denied at the OS level.

A .NET assembly has a choice of using either imperative or declarative checks (demands) for individual permissions. Declarative (attribute) checks have the added benefit of being stored in metadata, and thus are available for analyzing and reporting by .NET tools like permview.exe. In either case, the check results in a stack walk. Declarative checks can be used from an assembly down to an individual properties level.

//this declaration demands FullTrust 
//from the caller of this assembly 

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


//An example of a declarative permission 
//demand on a method

[CustomPermissionAttribute(SecurityAction.Demand, 
  Unrestricted = true)] 
public static string ReadData() 
{ //Read from a custom resource. }


//Performing the same check imperatively

public static void ReadData() 
{ 
  CustomPermission MyPermission = new 
    CustomPermission(PermissionState.Unrestricted); 
  MyPermission.Demand(); 
  //Read from a custom resource. 
}

In addition to ordinary code access checks, an application can declaratively specify LinkDemand or InheritanceDemand actions, which allow a type to require that anybody trying to reference it or inherit from it possess particular permission(s). The former applies to the immediate requestor only, while the latter applies to all inheritance chain. Presence of those demands in the managed code triggers a check for the appropriate permission(s) at JIT time.

LinkDemand has a special application with strong-named assemblies in .NET, because such assemblies may have a higher level of trust from the user. To avoid their unintended malicious usage, .NET places an implicit LinkDemand for their callers to have been granted FullTrust; otherwise, a SecurityException is thrown during JIT compilation, when an under-privileged assembly tries to reference the strong-named assembly. The following implicit declarations are inserted by CLR:

[PermissionSet(SecurityAction.LinkDemand, 
   Name="FullTrust")]

[PermissionSet(SecurityAction.InheritanceDemand, 
   Name="FullTrust")]

Consequently, if a strong-named assembly is intended for use by partially trusted assemblies (i.e., from code without FullTrust), it has to be marked by a special attribute -- [assembly:AllowPartiallyTrustedCallers], which effectively removes implicit LinkDemand checks for FullTrust. All other assembly/class/method level security checks are still in place and enforceable, so it is possible that a caller may still not possess enough privileges to utilize a strong-named assembly decorated with this attribute.

.NET assemblies have an option to specify their security requirements at the assembly load time. Here, in addition to individual permissions, they can operate on one of the built-in non-modifiable PermissionSets. There are three options for those requirements: RequestMinimum, RequestOptional, and RequestRefuse.

If the Minimum requirement cannot be satisfied, the assembly will not load at all. Optional permissions may enable certain features. Application of the RequestOptional modifier limits the permission set granted to the assembly to only optional and minimal permissions (see the formula in the following paragraph). RequestRefuse explicitly deprives the assembly of certain permissions (in case they were granted) in order to minimize chances that an assembly can be tricked into doing something harmful.

//Requesting minimum assembly permissions
//The request is placed on the assembly level.

using System.Security.Permissions; 
[assembly:SecurityPermissionAttribute(
   SecurityAction.RequestMinimum, 
   Flags = SecurityPermissionFlag.UnmanagedCode)] 
namespace MyNamespace 
{ 
	...
}

CLR determines the final set of assembly permissions using the granted permissions, as specified in .NET CAS policy, plus the load-time modifiers described earlier. The formula applied is (P - Policy-derived permissions): G = M + (O<<P) - R, where M and O default to P, and R to Nothing.

Applications on .NET platform may affect the stack-walking process by executing special operations on individual permissions or permission sets: Assert, Deny, PermitOnly. The application itself should be granted the affected permissions, as well as the SecurityPermission that grants the rights to make assertions.

The Assert option explicitly succeeds the stack walk (for the given PermissionSet or any subset of it, as determined by the Intersect function), even if the upstream callers do not have the required permissions (it fails if sets intersections are not empty). Deny and PermitOnly effectively restrict the available permission sets for the downstream callers.

This demo application demonstrates the effects of applying stack-walk modifications. Figure 5 represents an overview of the Code Access Security permission grants and checks in .NET:

Figure 5
Figure 5. NET CAS Operation

In Java, permissions are normally checked by the SecurityManager (or installed derivative), by using the checkPermission function. It defines a helper for each major group of permissions, such as checkWrite for the write action of FilePermission. All checks are imperative; there are no declarative code access checks in Java language. Each JVM can have at most one SecurityManager (or derivative) installed -- once set, they cannot be replaced, for security reasons. Browsers always start SecurityManager, so any Internet Java application executes with enabled security. Locally started JVMs have to install a SecurityManager before exercising the first sensitive operation; this can also be done programmatically:

System.setSecurityManager(new SecurityManager());

or using a command-line option:

java -Djava.security.manager MyClass

In Java 2, when determining application permissions, SecurityManager delegates the call to java.security.AccessController, which obtains current snapshot of AccessControllerContext to determine which permissions are present. SecurityManager's operations may be influenced by the java.security.DomainController implementation, if one exists. It instructs an existing SecurityManager to perform additional operations before security checks, thus allowing security system extensibility without re-implementing its core classes. JAAS uses this functionality to add principal-based security checks to the original code-based Java security (see section "User Access Security" in Part 4).

When making access control decisions, the checkPermission method stops checking if it reaches a caller that was marked as "privileged" via a doPrivileged call without a context argument. If that caller's domain has the specified permission, no further checking is done and checkPermission returns quietly, indicating that the requested access is allowed. If that domain does not have the specified permission, an exception is thrown, as usual.

Writing privileged code in Java is achieved by implementing the java.security.PrivilegedAction or PrivilegedExceptionAction interfaces. This approach is somewhat limiting, as it does not allow specifying the exact permissions to be asserted, while still requiring the callers to possess others -- it is an "all or nothing" proposition.

public class PrivilegedClass implements PrivilegedAction {
    public Object run() {
        //perform privileged operation
        ...
        return null;
    }
}

Suppose the current thread traversed m callers, in the order of caller 1 to caller 2 to caller M, which invoked the checkPermission method. This method determines whether access is granted or denied based on the following algorithm:

i = m;
while (i > 0) {

  if (caller i's domain 
      does not have the permission)
          throw AccessControlException

  else if (caller i is marked as privileged) {
          if (a context was specified 
              in the call to doPrivileged) 
             context.checkPermission(permission)
          return;
  }
  i = i - 1;
}

// Next, check the context inherited when
// the thread was created. Whenever a new thread 
// is created, the AccessControlContext at that 
// time is stored and associated with the new 
// thread, as the "inherited" context.

inheritedContext.checkPermission(permission);

A complete application demonstrating privileged code in Java can be found here.

Note: .NET arms developers with an impressive arsenal of various features for access checks, easily surpassing Java in this respect.

Conclusions

In this article, code protection and Code Access Security features of Java and .NET platforms were reviewed. While code protection came out more or less even, CAS features in .NET are significantly better than the ones Java can offer, with a single exception -- flexibility. Java, as it is often the case, offers ease and configurability in policy handling that .NET cannot match.

The final article of this series, Part 4, is going to cover authentication and User Access Security (UAS) on both platforms, as well as provide overall conclusions to the series.

Demo Applications

Denis Piliptchouk is a senior technical member of BEA's AquaLogic Enterprise Security group, participates in OASIS WSS and WS-I BSP standards committees, and regularly contributes to industry publications.


Return to ONJava.com.

Copyright © 2009 O'Reilly Media, Inc.