Java 2 Platform Security
Topics in This Chapter
- Java Security Architecture
- Java Applet Security
- Java Web Start Security
- Java Security Management Tools
- J2ME Security Architecture
- Java Card Security Architecture
- Securing the Java Code
Sun's Java philosophy of "Write Once, Run Anywhere" has been an evolving success story since its inception, and it has revolutionized the computing industry by delivering to us the most capable platform for building and running a wide range of applications and services. In general, the Java platform provides a general-purpose object-oriented programming language and a standard runtime environment for developing and delivering secure, cross-platform application solutions that can be accessed and dynamically loaded over the network or run locally.
With the release of the Java 2 Platform, Sun categorized the Java technologies under three key major editions in order to simplify software development and deployment. The Java 2 Standard Edition (J2SE) provides the runtime environment and API technologies for developing and executing basic Java applications, and it also serves as the secure foundation for running Java enterprise applications. The Java 2 Enterprise Edition (J2EE), or the J2EE Platform, is a set of standards and API technologies for developing and deploying multi-tier business applications. To support Java on microdevices and embedded systems, Java 2 Micro Edition (J2ME) provides the runtime environment and API technologies for addressing the needs of consumer electronics and devices. With its widespread adoption, today Java technology is enabled and executed from smart cards to microdevices, handhelds to desktops, workstations to enterprise servers, mainframes to supercomputers, and so on.
To facilitate end-to-end security of the Java platform-based application solutions, the Java runtime environment (JRE) and the Java language provide a solid security foundation from the ground up by imposing strong format and structural constraints on the code and its execution environment. This distinguishes the Java platform from other application programming languages—it has a well-defined security architectural model for programming Java-based solutions and their secure execution.
In this chapter, we will explore the various Java platforms and the intricate details of their security architecture that contribute to the end-to-end security of Java-based application solutions. In particular, we will study Java security and the inherent features of the following technologies:
- J2SE security
- Java applet security
- Java Web start security
- Java security management tools
- J2ME security
- Java Card security
- Java Code obfuscation
Java Security Architecture
Security has been an integral part of Java technology from day one. Security is also an evolving design goal of the Java community—building and running secure and robust Java-based network applications. The primary reason for Java's success today as a secure execution environment is the intrinsic security of its architectural foundation—the Java Virtual Machine (JVM) and the Java language. This foundation achieves the basic Java security goal and its definitive ways for extending security capabilities to ensure features such as confidentiality, integrity, trust, and so forth. A second reason for its success is its ability to deliver an interoperable and platform-neutral security infrastructure that can be integrated with the security of the underlying operating system and services.
The Java Virtual Machine (JVM)
The JVM is an abstract computing engine that resides on a host computer. It is the execution environment for the Java programming language and has the primary responsibility for executing the compiled code by interpreting it in a machine-independent and cross-platform fashion. The JVM is often referred to as the Java runtime environment. While executing a Java program running on top of the JVM, the JVM insulates the application from the underlying differences of the operating systems, networks, and system hardware, thus ensuring cross-platform compatibility among all of the implementations of the Java platform.
The Java language allows creation of general-purpose programs called Java classes that represent a Java program or an application. The Java classes compile into a format called Java's executable bytecodes, which are quite similar to the machine language that can run on top of a JVM. The JVM also allows users to download and execute untrusted programs and applications from remote resources or over a network. To support delivery of Java components over the network, the JVM controls the primary security layer by protecting users and the environment from malicious programs. To enable security, the JVM enforces stringent measures ensuring systems security on the host client machine and its target server environments.
Distributing the executable Java bytecode over a network or running automatically inside a Web browser or a client's machine leads to different security risks and attacks, such as disclosure of the target environment to the untrusted applications and damage or modification of the client's private information and data. For example, Java applets downloaded from a network are not allowed to have access to, read from, or write to a local file system. They are also not allowed to create network connections to any host system except the one where they are deployed. On the other hand, stand-alone Java applications that reside and run locally as trusted applications are not subjected to these security features. The key issue is that allowing untrusted applications such as Java applets to be downloaded from a network via a Web browser and letting them access certain resources on the host computer paves the way for security breaches and becomes a potential avenue for the spread of viruses. To prevent known security breaches and threats, the JVM provides a built-in Java security architecture model, configurable security policies, access control mechanisms, and security extensions. Because of the built-in JVM safety features, Java programs can run safely and are more securely protected from known vulnerabilities.
The Java Language
Java is a general-purpose object-oriented programming language similar to C++. It delivers platform-neutral compiled code that can be executed using a JVM and is intended for use in distributed application environments, heterogeneous systems, and diverse network environments. The Java language is also designed to provide for the security and integrity of the application and its underlying systems at all levels—from the Java language constructs to the JVM runtime and from the class library to the complete application.
The several inherent features of the Java language that provide for the secure Java platform are as follows:
- The language defines all primitives with a specific size and all operations are defined to be in a specific order of execution. Thus, the code executed in different JVMs will not differ from the specified order of execution.
- The language provides access-control functionality on variables and methods in the object by defining name space management for type and procedure names. This secures the program by restricting access to its critical objects from untrusted code. For example, access is restricted by qualifying the type members as public, protected, private, package, etc.
- The Java language does not allow defining or dereferencing pointers, which means that programmers cannot forge a pointer to the memory or create code defining offset points to memory. All references to methods and instance variables in the class file are done via symbolic names. The elimination of pointers helps to prevent malicious programs like computer viruses and misuse of pointers such as accessing private methods directly by using a pointer starting from the object's pointer, or running off the end of an array.
- The Java object encapsulation supports "programming by contract," which allows the reuse of code that has already been tested.
- The Java language is a strongly typed language. During compile time, the Java compiler does extensive type checking for type mismatches. This mechanism guarantees that the runtime data type variables are compatible and consistent with the compile time information.
- The language allows declaring classes or methods as final. Any classes or methods that are declared as final cannot be overridden. This helps to protect the code from malicious attacks such as creating a subclass and substituting it for the original class and override methods.
- The Java Garbage Collection mechanism contributes to secure Java programs by providing a transparent storage allocation and recovering unused memory instead of deallocating the memory using manual intervention. This ensures program integrity during execution and prevents programmatic access to accidental and incorrect freeing of memory resulting in a JVM crash.
With these features, Java fulfills the promise of providing a secure programming language that gives the programmer the freedom to write and execute code locally or distribute it over a network.
Java Built-in Security Model
In the previous two sections, we briefly looked at the basic security features provided by the JVM and the Java language. As part of its security architecture, Java has a built-in policy-driven, domain-based security model. This allows implementing security policies, protecting/controlling access to resources, rule-based class loading, signing code and assigning levels of capability, and maintaining content privacy.
In the first release of the Sun Java Platform, the Java Development Kit 1.0.x (JDK) introduced the notion of a sandbox-based security model. This primarily supports downloading and running Java applets securely and avoids any potential risks to the user's resources. With the JDK 1.0 sandbox security model, all Java applications (excluding Java applets) executed locally can have full access to the resources available to the JVM. Application code downloaded from remote resources, such as Java applets, will have access only to the restricted resources provided within its sandbox. This sandbox security protects the Java applet user from potential risks because the downloaded applet cannot access or alter the user's resources beyond the sandbox.
The release of JDK 1.1.x introduced the notion of signed applets, which allowed downloading and executing applets as trusted code after verifying the applet signer's information. To facilitate signed applets, JDK 1.1.x added support for cryptographic algorithms that provide digital signature capabilities. With this support, a Java applet class could be signed with digital signatures in the Java archive format (JAR file). The JDK runtime will use the trusted public keys to verify the signers of the downloaded applet and then treat it as a trusted local application, granting access to its resources. Figure 3-1 shows the representation of a sandbox in the JDK 1.1 security model.
Figure 3-1 JDK 1.1 security model
Java 2 Security Model
The release of J2SE [J2SE] introduced a number of significant enhancements to JDK 1.1 and added such features as security extensions providing cryptographic services, digital certificate management, PKI management, and related tools. Some of the major changes in the Java 2 security architecture are as follows:
- Policy-driven restricted access control to JVM resources.
- Rules-based class loading and verification of byte code.
- System for signing code and assigning levels of capability.
- Policy-driven access to Java applets downloaded by a Web browser.
In the Java 2 security architecture, all code—regardless of whether it is run locally or downloaded remotely—can be subjected to a security policy configured by a JVM user or administrator. All code is configured to use a particular domain (equivalent to a sandbox) and a security policy that dictates whether the code can be run on a particular domain or not. Figure 3-2 illustrates the J2SE security architecture and its basic elements.
Figure 3-2 Java 2 Security architecture and basic elements
Let's take a more detailed look at those core elements of the Java 2 security architecture.
Protection Domains ( java.security.ProtectionDomain ): In J2SE, all local Java applications run unrestricted as trusted applications by default, but they can also be configured with access-control policies similar to what is defined in applets and remote applications. This is done by configuring a ProtectionDomain, which allows grouping of classes and instances and then associating them with a set of permissions between the resources. Protection domains are generally categorized as two domains: "system domain" and "application domain." All protected external resources, such as the file systems, networks, and so forth, are accessible only via system domains. The resources that are part of the single execution thread are considered an application domain. So in reality, an application that requires access to an external resource may have an application domain as well as a system domain. While executing code, the Java runtime maintains a mapping from code to protection domain and then to its permissions.
Protection domains are determined by the current security policy defined for a Java runtime environment. The domains are characterized using a set of permissions associated with a code source and location. The java.security.ProtectionDomain class encapsulates the characteristics of a protected domain, which encloses a set of classes and its granted set of permissions when being executed on behalf of a user.
Permissions ( java.security.Permission ): In essence, permissions determine whether access to a resource of the JVM is granted or denied. To be more precise, they give specified resources or classes running in that instance of the JVM the ability to permit or deny certain runtime operations. An applet or an application using a security manager can obtain access to a system resource only if it has permission. The Java Security API defines a hierarchy for Permission classes that can be used to configure a security policy. At the root, java.security.Permission is the abstract class, which represents access to a target resource; it can also include a set of operations to construct access on a particular resource. The Permission class contains several subclasses that represent access to different types of resources. The subclasses belong to their own packages that represent the APIs for the particular resource. Some of the commonly used Permission classes are as follows:
For wildcard permissions |
-java.security.AllPermission |
For named permissions |
-java.security.BasicPermission |
For file system |
-java.io.FilePermission |
For network |
-java.net.SocketPermission |
For properties |
-java.lang.PropertyPermission |
For runtime resources |
-java.lang.RuntimePermission |
For authentication |
-java.security.NetPermission |
For graphical resources |
-java.awt.AWTPermission |
Example 3-1 shows how to protect access to an object using permissions. The code shows the caller application with the required permission to access an object.
Example 3-1. Using Java permissions to protect access to an object
// Create the object that requires protection String protectedObj = "For trusted eyes only"; // create the required permission that will // protect the object. // Guard, represents an object that is used to protect // access to another object. Guard myGuard = new PropertyPermission ("java.home", "read"); // Create the guard GuardedObject gobj = new GuardedObject(protectedObj, myGuard); // Get the guarded object try { Object o = gobj.getObject(); } catch (AccessControlException e) { // Cannot access the object }
Permissions can also be defined using security policy configuration files (java.policy). For example, to grant access to read a file in "c:\temp\" (on Windows), the FilePermission can be defined in a security policy file (see Example 3-2).
Example 3-2. Setting Java permissions in policy configuration file
grant{ permission java.io.FilePermission "c:\\temp\\testFile", "read"; };
Policy: The Java 2 security policy defines the protection domains for all running Java code with access privileges and a set of permissions such as read and write access or making a connection to a host. The policy for a Java application is represented by a Policy object, which provides a way to declare permissions for granting access to its required resources. In general, all JVMs have security mechanisms built in that allow you to define permissions through a Java security policy file. A JVM makes use of a policy-driven access-control mechanism by dynamically mapping a static set of permissions defined in one or more policy configuration files. These entries are often referred to as grant entries. A user or an administrator externally configures the policy file for a J2SE runtime environment using an ASCII text file or a serialized binary file representing a Policy class. In a J2SE environment, the default system-wide security policy file java.policy is located at <JRE_HOME>/lib/security/ directory. The policy file location is defined in the security properties file with a java.security setting, which is located at <JRE_HOME>/lib/security/java.security.
Example 3-3 is a policy configuration file that specifies the permission for a signed JAR file loaded from "http://coresecuritypatterns.com/*" and signed by "javaguy," and then grants read/write access to all files in /export/home/test.
Example 3-3. Setting codebase and permissions in policy configuration file
grant signedBy "javaguy", codebase "http://coresecuritypatterns.com/*" { permission java.io.FilePermission "/export/home/test/*", "read,write"; };
The J2SE environment also provides a GUI-based tool called "policytool" for editing a security policy file, which is located at "<JAVA_HOME>/bin/policytool."
By default, the Java runtime uses the policy files located in:
${java.home}/jre/lib/security/java.policy ${user.home}/.java.policy
These policy files are specified in the default security file:
${java.home}/jre/lib/security/java.security
The effective policy of the JVM runtime environment will be the union of all permissions in all policy files. To specify an additional policy file, you can set the java.security.policy system property at the command line:
java -Djava.security.manager -Djava.security.policy=myURL MyClass
To ignore the policies in the java.security file and only use the custom policy, use '==' instead of '=':
java -Djava.security.manager -Djava.security.policy==Mylocation/My.policy MyClass
SecurityManager ( java.lang.SecurityManager ): Each Java application can have its own security manager that acts as its primary security guard against malicious attacks. The security manager enforces the required security policy of an application by performing runtime checks and authorizing access, thereby protecting resources from malicious operations. Under the hood, it uses the Java security policy file to decide which set of permissions are granted to the classes. However, when untrusted classes and third-party applications use the JVM, the Java security manager applies the security policy associated with the JVM to identify malicious operations. In many cases, where the threat model does not include malicious code being run in the JVM, the Java security manager is unnecessary. In cases where the SecurityManager detects a security policy violation, the JVM will throw an AccessControlException or a SecurityException.
In a Java application, the security manager is set by the setSecurityManager method in class System. And the current security manager is obtained via the getSecurityManager method (see Example 3-4).
Example 3-4. Using SecurityManager
SecurityManager mySecurityMgr = System.getSecurityManager(); if (mySecurityMgr != null) { mySecurityMgr.checkWrite(name); }
The class java.lang.SecurityManager consists of a number of checkXXXX methods like checkRead (String file) to determine access privileges to a file. The check methods call the SecurityManager.checkPermission method to find whether the calling application has permissions to perform the requested operation, based on the security policy file. If not, it throws a SecurityException.
If you wish to have your applications use a SecurityManager and security policy, start up the JVM with the -Djava.security.manager option and you can also specify a security policy file using the policies in the -Djava.security.policy option as JVM arguments. If you enable the Java Security Manager in your application but do not specify a security policy file, then the Java Security Manager uses the default security policies defined in the java.policy file in the $JAVA_HOME/jre/lib/security directory. Example 3-5 programmatically enables the security manager.
Example 3-5. Using SecurityManager for restricting access control
// Before the security manager is enabled, // this call is possible System.setProperty("java.version","Malicious: Delete"); try { // Enable the security manager SecurityManager sm = new SecurityManager(); System.setSecurityManager(sm); } catch (SecurityException se) { // SecurityManager already set } // After the security manager is enabled: // This call is no longer possible; // an AccessControlException is thrown System.setProperty ("java.version", "Malicious: Delete");
The security manager can also be installed from the command-line interface:
java -Djava.security.manager <ClassName>
AccessController ( java.security.AccessController ): The access controller mechanism performs a dynamic inspection and decides whether the access to a particular resource can be allowed or denied. From a programmer's standpoint, the Java access controller encapsulates the location, code source, and permissions to perform the particular operation. In a typical process, when a program executes an operation, it calls through the security manager, which delegates the request to the access controller, and then finally it gets access or denial to the resources. In the java.security.AccessController class, the checkPermission method is used to determine whether the access to the required resource is granted or denied. If a requested access is granted, the checkPermission method returns true; otherwise, the method throws an AccessControlException.
For example, to check read and write permission for a directory in the file system, you would use the code shown in Example 3-6.
Example 3-6. Using AccessController
try { AccessController.checkPermission (new FilePermission("/var/temp/*", "read,write")); } catch (SecurityException e) { // Does not have permission to access the directory }
Codebase: A URL location of class or JAR files are specified using codebase. The URL may refer to a location of a directory in the local file system or on the Internet. Example 3-7 retrieves all the permissions granted to a particular class that's been loaded from a code base. The permissions are effective only if the security manager is installed. The loaded class uses those permissions by executing Class.getProtectionDomain() and Policy.getPermissions().
Example 3-7. Using codebase class
URL codebase = null; try { // Get permissions for a URL codebase = new url("https://coresecuritypatterns.com/"); } catch (MalformedURLException e) { } catch (IOException e) { } // Construct a code source with the code base CodeSource cs = new CodeSource(codebase, null); // Get all granted permissions PermissionCollection pcoll = Policy.getPolicy().getPermissions(cs); // View each permission in the permission collection Enumeration enum = pcoll.elements(); for (; enum.hasMoreElements(); ) { Permission p = (Permission)enum.nextElement(); System.out.println("Permission " + p); }
To test Example 3-7, Example 3-8 is the policy file (test.policy), which provides permission to read all system properties.
Example 3-8. Policy file for testing permissions to a codebase
grant codebase "http://coresecuritypatterns.com/-" { // Give permission to read all system properties permission java.util.PropertyPermission "*", "read"; };
To ignore the default policies in the java.security file, and only use the specified policy, use '==' instead of '='. With the policy just presented, you may run the following:
java -Djava.security.policy==test.policy TestClass
CodeSource: The CodeSource allows representation of a URL from which a class was loaded and the certificate keys that were used to sign that class. It provides the same notion as codebase, but it encapsulates the codebase (URL) of the code where it is loaded and also the certificate keys that were used to verify the signed code. The CodeSource class and its two arguments to specify the code location and its associated certificate keys are as follows:
CodeSource(URL url, java.security.cert.Certificate certs[]);
To construct a code source with the code base and without using certificates, you would use the following:
CodeSource cs = new CodeSource(codebase, null);
Bytecode verifier: The Java bytecode verifier is an integral part of the JVM that plays the important role of verifying the code prior to execution. It ensures that the code was produced consistent with specifications by a trustworthy compiler, confirms the format of the class file, and proves that the series of Java byte codes are legal. With bytecode verification, the code is proved to be internally consistent following many of the rules and constraints defined by the Java language compiler. The bytecode verifier may also detect inconsistencies related to certain cases of array bound-checking and object-casting through runtime enforcement.
To manually control the level of bytecode verification, the options to the Java command with the V1.2 JRE are as follows:
- -Xverify:remote runs verification process on classes loaded over network (default)
- -Xverify:all verifies all classes loaded
- -Xverify:none does no verification
ClassLoader: The ClassLoader plays a distinct role in Java security, because it is primarily responsible for loading the Java classes into the JVM and then converting the raw data of a class into an internal data structure representing the class. From a security standpoint, class loaders can be used to establish security policies before executing untrusted code, to verify digital signatures, and so on. To enforce security, the class loader coordinates with the security manager and access controller of the JVM to determine the security policies of a Java application. The class loader further enforces security by defining the namespace separation between classes that are loaded from different locations, including networks. This ensures that classes loaded from multiple hosts will not communicate within the same JVM space, thus making it impossible for untrusted code to get information from trusted code. The class loader finds out the Java application's access privileges using the security manager, which applies the required security policy based on the requesting context of the caller application.
With the Java 2 platform, all Java applications have the capability of loading bootstrap classes, system classes, and application classes initially using an internal class loader (also referred to as primordial class loader). The primordial class loader uses a special class loader SecureClassLoader to protect the JVM from loading malicious classes. This java.security.SecureClassLoader class has a protected constructor that associates a loaded class to a protection domain. The SecureClassLoader also makes use of permissions set for the codebase. For instance, URLClassLoader is a subclass of the SecureClassLoader. URLClassLoader allows loading a class or location specified with a URL.
Refer to Example 3-9, which shows how a URLClassLoader can be used to load classes from a directory.
Example 3-9. Using URLClassLoader
// Create a File object on the root of the // directory containing the class file File file = new File("c:\\myclasses\\"); try { // Convert File to a URL URL url = file.toURL(); URL[] urls = new URL[]{url}; // Create a new class loader with the directory ClassLoader myclassloader = new URLClassLoader(urls); // Load in the class; // MyClass.class should be located in // the directory file:/c:/myclasses/com/security Class myclass = myclassloader.loadClass("com.security.MySecureClass"); } catch (MalformedURLException e) { } catch (ClassNotFoundException e) { }
Keystore and Keytool: The Java 2 platform provides a password-protected database facility for storing trusted certificate entries and key entries. The keytool allows the users to create, manage, and administer their own public/private key pairs and associated certificates that are intended for use in authentication services and in representing digital signatures.
We will take a look in greater detail at the usage of the Java keystore and keytool and how these tools help Java security in the section entitled "Java Security Management Tools," later in this chapter.