Java Technology Home Page
A-Z Index

Java Developer Connection(SM)
Online Training

Downloads, APIs, Documentation
Java Developer Connection
Tutorials, Tech Articles, Training
Online Support
Community Discussion
News & Events from Everywhere
Products from Everywhere
How Java Technology is Used Worldwide
 
Training Index

Fundamentals of Java Security
Security

By MageLang Institute

Course Notes | Magercises | Module Intro

People often describe security as one of the key attributes that make JavaTM technology a superior programming environment. There are features of the language, virtual machine, and core class libraries that facilitate the creation of secure applications. While certain aspects of Java technology-related security are unavoidable--for instance, you can never do pointer arithmetic, it is not an all-or-nothing deal. This course explores how your Java programs are, or can be, secured at every level: from the language level to the virtual machine level and from the class library level to the application level. Each of these aspects are demonstrated, along with specific browser-level security concerns.

It is important to remember that security is never absolute--there are always trade-offs between security, ease of use, and performance. Because of these trade-offs, it is vital that all security elements be part of an open specification, subject to test and modification based on the experience of thousands of every day users. Thus, if there are fundamental shortcomings to the security model, they will quickly be found. Implementation deficiencies will also be found and corrected. (See Project Kimera at University of Washington and the Secure Internet Programming initiative at Princeton University for two such Java technology-security validation research efforts.) This openness is one of Java technology's strengths.

Language-Level Security

The Java programming language is often declared a secure language, but what does that really mean? To answer this question, you need a basic understanding of the ways that a specific computer language can cause vulnerabilities in applications, communications, and data integrity. Some of these ways include the removal of pointer arithmetic, the automatic reclamation of unreferenced memory, and the automatic checking of illegal array offsets.

The evolution of the Java programming language started with its identity as a subset of C++ with slightly different syntax. (Sun's Bill Joy called it C++--++.) This subset was designed to eliminate features of the C++ language that were deemed to be error-prone, or unsafe. In this sense the term code safety refers to the property that the Java programming language allows you to generate bug-free code that doesn't crash the program or user's machine. You can also think of this as robustness--the Java programming language helps prevent you from writing programs that are accidentally harmful. When you compile your programs with javac, the only compile-time warning is for using deprecated (or outdated) methods. Every other detected problem is an error that must be corrected.

Invalid Memory Access

There are many language features within the Java programming language that contribute to this behavior. First and foremost is the fact that the Java programming language does not let you perform pointer arithmetic. This is perhaps the most important language feature that contributes to the Java language's safety, since it is pointer arithmetic that leads to accessing inappropriate memory areas, which leads to runtime crashes.

Not permitting pointer arithmetic is not saying the Java programming language doesn't support pointers. In fact pointers in the Java programming language are called references, and they are fully supported. The existence of reference variables allows you to create data structures like linked lists, binary trees, or any other where you would normally think of using a pointer variable in a language like C or C++.

In addition to removing pointer arithmetic, the Java language specification clearly defines behavior to uninitialized variables. All heap-based memory is automatically initialized. However, all stack-based memory isn't. So, all class and instance variables are never set to undefined values, and all local variables must definitely be assigned before use or the source compiler is obligated to give you an error. [Note: Accessing blank final class/instance variables in initializer blocks falls into the area of local variables. If they are not definitely assigned before use, the compiler will generate an error here, too. For more on blank finals, see Other Language Features, below.]

Garbage Collection

Another feature of the Java language that contributes to safe or robust code is automatic garbage collection. Garbage collection is the runtime environment's ability to automatically release memory no longer referenced. Since all dynamic memory requests are allocated from the heap, the garbage collector only needs to monitor references to places within it. When an area of memory no longer has any references to it, the garbage collector releases the memory. In effect this places the freed memory block into the free storage pool, to be reallocated by another part of the program.

The Java language also has a concept of a stack for memory allocation. In fact, there can be multiple stacks within a program, one for each executing thread. However, the only memory allocated on the stack is for local variables of a method. When the method ends, the variable space is relinquished.

With the help of the garbage collector, a Java programmer no longer needs to determine when, or if, it is safe to release memory. The garbage collector will only release memory when it is safe and no longer referenced. With Sun's Java Runtime Environment (JRE), Version 1.1, on Solaris (also known as the green threads VM), in addition to running when out of free memory, the automatic running of the garbage collector every second or so can be turned off:

java/jre -noasyncgc MainClass args ...

and can be requested to run manually:

System.gc();

When -noasyncgc is enabled, the garbage collector will still run when out of free memory, just not automatically every second or so.

Sun's JRE 1.2 no longer supports the asynchronous running of the garbage collector nor the turning off of this capability with the -noasyncgc command-line option. Also, with JRE 1.2, the jre startup command has been removed.

Other Language Features

Many other features of the language also contribute to code safety. These include strong compile-time type-checking (no illegal casts). Strong type-checking ensures a programmer can't interpret a block of memory as anything other than what it is. For example, you can't cast an Object array to a String array, unless it happens to be a String array, thus allowing access to memory that is not officially within the array.

The Java programming language's access modifiers, public, protected, friendly, and private, contribute to robustness by controlling visibility of members. By properly using the modifiers, you can restrict access to data members, methods, and inner classes. [There is no keyword friendly. When no keyword is specified, this is the common name to that type of access.]

The Java programming language also provides the final modifier, which disallows subclassing when applied to class definitions and disallows overriding when applied to method definitions. For instance, use of this modifier within the java.* packages, like in the String class, allows these classes to be used safely. (What if someone could derive a mutable String class from java.lang.String--then that someone could change data members such as IP address within the InetAddress class after the security manager had a chance to approve the access.) You can also use the final modifier for data members (or variables in general). However, this doesn't always result in the desired behavior. When used with a variable whose type is a primitive, the value of the variable is immutable. However, if the variable is an object, the only thing final adds is that the variable cannot later point to another object. If the class of the object has methods that allow you to alter the contents of the object, then the contents of the object can change. One other use of final is called blank finals. When initializing the data members of a class, you can specify a data member as final, then initialize it in the constructor(s) of the class. As long as the data member is initialized in each and every constructor, the field cannot be accidentally altered after the object has been constructed.

JavaTM Virtual Machine-level Security1

The JavaTM Virtual Machine (JVM) relies on software technology to present a secure sandbox where Java programs run. This sandbox verifies all classes coming into the sandbox, does numerous runtime checks to ensure Java programs don't perform invalid operations, and presents a fence around the runtime environment to control all access outside the sandbox. While a normal Java language compiler should never generate bad Java classes, an error-free Java program will not need the runtime checking, and trusted programs will never try to do unauthorized operations, the JVM provides a safe place for Java programs to run in the world of the Internet. You can use an untrusted compiler or minimally tested program, or even download code from some unknown place on the Internet, and still not worry about the Java program corrupting your system. About the worst that could happen is denial-of-service, where the Java program consumes all the CPU resources.

Java Byte Codes

The machine language of the JVM is the Java byte codes, as defined in the JVM specification. The specification defines how class parts, like variables, methods, and modifiers, are stored in Java class files. A Java byte code-generating compiler takes a source file and converts the appropriate input to Java byte code instructions in a .class file. The input does NOT need to be based on the Java language, though. In fact, there are several cross compilers that take source files from other languages, like Ada, COBOL, or Delphi, and generate Java byte codes.

So, what does all this have to do with security? Well, security with Java technology is not simply a language-level construct. If it were, then a malicious individual could circumvent security simply by constructing malicious byte codes through a modified Java compiler, a compiler that generates byte code from a non-safe language, or even writing byte code by hand (similar to machine-language coding). For instance, Ada doesn't have the same built-in safety at the language level (for example, lack of pointers). If Java technology security was simply a language-level construct, then someone programming in Ada would circumvent all Java technology security checks.

For a list of the cross compilers that generate Java byte codes, see the excellent list maintained by Robert Tolksdorf. To see the actual byte codes of a class file, run javap -c ClassName to disassemble the class.

Byte Code Verification

When you tell the JRE to execute a particular class (java/jre ClassName), the natural progression is to load the class file, execute its main method, then load any supplemental classes. When each Java class file is loaded, before anything else can happen, the JVM inspects the file to verify the validity of the Java byte codes. Verification is a four-pass process. Going from the mundane in pass one, like ensuring the first four bytes of the file are 0xCAFEBABE, its magic number, to anything that can be verified without looking at the code in pass two, like checking for a superclass if the class isn't Object. Pass three does byte code verification, to ensure the opcodes are still valid. This pass performs checks verifying that method arguments are the proper type and opcode arguments are appropriate. The final pass, pass four, is not performed until a method is invoked. So, if a method is never invoked, it is possible that its byte codes could be invalid. The types of checks done with this pass are along the nature of verifying member access (both modifier permissions for public, protected, friendly, or private and existence).

So, why all this verification? When class files are loaded (possibly over the network or from an untrusted source) into a virtual machine, there is no way of telling how its byte codes were generated. Basically, in most cases, you can't trust the source of the Java class files. In fact, the default verification setting in JRE 1.1 is to only verify classes loaded from over the network. So, anything installed locally, via CLASSPATH, is not verified. In JRE 1.1, if you have installed something locally from a third-party and want to even verify that, too, you can do that. Or, you can completely disable verification. The three settings are all options to the java and jre commands:

  • -verifyremote - only perform verification process on classes loaded over network (default)
  • -verify - verify everything
  • -noverify - verify nothing

In JDK 1.2, the default setting for verification is the same, only verify classes loaded over the network. However, the system classes are specified from something other than the CLASSPATH environment variable, and the command line options for verification differ. The system classes are specified by either the System property sun.boot.class.path or the command line option -Xbootclasspath:directories and jar files. This is automatically set for you by the JRE and should never need to be set manually. If you set it to add something yourself be sure to include the original defaults, too. With regards to verification, if you wish to manually control the level of verification, the options to the java command with the V1.2 JRE are as follows:

  • -Xverify:remote - only perform verification process on classes loaded over network (default)
  • -Xverify:all - verify everything
  • -Xverify:none - verify nothing

If a verification error is encountered, instead of the JRE reporting a verification error or throwing an exception, it reports a can't find error, like: Can't find class MyClass.

Once all the verification has completed successfully, the verifier can replace opcodes to use quick instructions--to improve efficiency and assure verification won't be run again on a method. (If the verifier encountered these quick instructions in the .class file, it would result in a verification error.) By performing these checks once at class loading time, as opposed to repeatedly during execution, runtime efficiency is improved.

Class Loading

So, who or what does all this loading and verification? The responsibility falls on the ClassLoader. It is the ClassLoader that finds and loads the byte codes for the class definitions. Once loaded, they are verified before the ClassLoader can create actual classes.

In addition to finding, loading, and verifying, the ClassLoader performs many other security-related duties. First, the ClassLoader will not attempt to load any classes in java.* packages from over the network. This ensures that the JVM isn't tricked into using false representations of the core class libraries--ones that could break the Java security model. Second, the ClassLoader provides separate name spaces for classes loaded from different locations. So, classes with the same name loaded from different hosts will not clash. And, classes loaded from different hosts will not communicate within the JVM-space, possibly permitting untrusted programs to get information from trusted ones.

So, what's involved in creating a ClassLoader? Well, the class is your typical abstract class. It happens to have only one abstract method that needs to be overridden in subclasses:

  • protected abstract Class loadClass(String name, boolean resolve) throws ClassNotFoundException

In loadClass, you basically have to perform five operations. Additional operations, like decryption, can be performed here. However, they are not required. Also, if decryption is performed, the algorithm is embedded in the byte codes of the class loader. So, if someone decompiled the class loader, they would gain access to the encryption mechanism, therefore providing access to the decrypted byte codes. The basic five operations follow.

  1. Find out if the class is already loaded. The ClassLoader superclass maintains a private Hashtable of loaded classes. To find out if anything is loaded, ask with findLoadedClass() and findSystemClass(). It is necessary to check both, as not only will a class you ask for be loaded through the custom class loader, but all of its superclasses (like Object) will be loaded also.
    // within loadClass()
    Class c = findLoadedClass (name);
    if (c == null) {
      try {
        c = findSystemClass (name);
      } catch (Exception e) {
        // Ignore these exceptions.
      }
    }
    
  2. If not found, load the data for the class. This could be with JDBC out of a database, with a network connection based on a URL, or through some other mechanism that you determine. There is no magic in the loadClassData() method name. Call it anything you like.
    if (c == null) {
      // You provide/define loadClassData
      byte data[] = loadClassData(name);
    
  3. Call defineClass() to convert the bytes into a Class. This throws ClassFormatError if the byte array is invalid. For good measure, you should also throw a ClassNotFoundException if defineClass() returns null. This prevents accessing an erroneous class variable later.
      c = defineClass (name, data, 0, data.length);
      if (c == null)
        throw new ClassNotFoundException (name);
    
  4. Finally, resolve the class if requested. Until a class is resolved instances cannot be created nor methods called. The flag may be false to only test for the existence of a class.
    if (resolve)
      resolveClass (c);
    
  5. Lastly, return the newly created class.
    return c;
    

It is possible that the name passed into loadClass is really a location. You would then need to parse the name from the location and make the necessary changes throughout the code. There are many more operations that can be done in loading classes, which demonstrates the flexibility of the JVM.

To use a custom class loader, you would write code similar to the following:

ClassLoader loader = new CustomClassLoader(params);
Class c = loader.loadClass ("TheClass", true);
TheClass tc = (TheClass)c.newInstance();

Magercise

  1. Writing Your Own ClassLoader [JDK 1.1]

JDK 1.2 introduces the URLClassLoader to diminish the need to create custom class loaders. URLClassLoader is a special subclass of the newly introduced SecureClassLoader, where SecureClassLoader allows you to associate permissions based upon the source of a loaded class. (More on JRE 1.2's permissions model later.) With URLClassLoader, any class or location that can be specified completely with a URL (for example a file:, http:, or jar: URL) can be loaded directly through a URLClassLoader. If you need to do an operation like encryption or load class bytes directly out of a database, you only need to subclass URLClassLoader and extend for your specifics or define your own protocol handler.

The basics of using a URLClassLoader only require you to tell the class loader where to find the classes. It isn't necessary to subclass, unless you have more specific needs. Any URL that ends with a / is assumed to be a directory. Everything else is assumed to be a JAR file.

try {
  URL urlList[] = {
    new URL ("http://java.sun.com/share/classes/"),
    new URL ("http://www.jars.com/jediSearch.zip"),
    new URL ("http://java.miningco.com/library/weekly/"),
    new File ("myJar.jar").toURL()};
  ClassLoader loader = new URLClassLoader (urlList);
  Class c = loader.loadClass ("TheClass");
  TheClass tc = (TheClass)c.newInstance();
} catch (MalformedURLException e) {
  // load classes another way or display error message
}

Magercise

  1. Using URLClassLoader [JDK 1.2]

Runtime Checking

After classes are loaded, the JVM's next level of security performed is its runtime checking. Because of the late binding provided by the JVM, additional late (runtime) type checking is done of assignments and array bounds. While certain type-checking can be done at compile time, there are cases where the specific type of an object is not determinable until runtime. In these cases, the JVM ensures that only properly assignable operations are performed. While you can access subclasses as a superclass, you can never access something that is specifically the superclass, as a subclass. Also, the JVM ensures you do not try to pass off something that isn't part of the class hierarchy, as one of its own.

While removal of pointer arithmetic is a compile-type operation, array bounds checking is a runtime check. When an array is created, you specify its size. Then, when you access an element within the array, you specify a position. In a language like C or C++, you can specify any value as the position. If the value is larger than the array size, you will attempt to access an area of memory that is not part of the original array. With the Java language, arrays know their size. So, if you specify an invalid position, an ArrayIndexOutOfBoundsException is thrown, thus denying you access to memory space not belonging to the array.

Managing Security

Probably the most important aspect of Java technology security is the responsibility of the SecurityManager. Each running JVM has at most one SecurityManager installed. SecurityManager is a class in the java.lang package. So, you can subclass this and establish your own security manager using the System.setSecurityManager() method. Once a manager is installed, it cannot be replaced. So, once a program has set the security manager, a SecurityException will be thrown if another attempt is made. No one can maliciously alter its function by replacing it.

The SecurityManager allows you to establish a security policy such that you can trust or restrict the operations of a Java program. For instance, browsers come with a default SecurityManager predefined by the browser manufacturer. They prevent applets loaded over the network from reading or writing files from the client file system. They also allow you to only create network connections back to the originating host of the applet. Other restricted operations include classes loaded over the network cannot load libraries or consist of native methods. (This does not say applets cannot call native methods--only native methods cannot be part of the classes loaded over the network.) Applets can also not fork off new system processes or see System properties that would expose the username or working directories, among many other restrictions, too. All of these operations would give direct access to the underlying computer, which is not desirable when applets are loaded from the Internet.

The SecurityManager provides fine-grained control over what operations can be performed by code running within the virtual machine. Whenever a Java program (applet, application, or otherwise) attempts to do an operation that may be restricted, it checks with the SecurityManager to see if the operation is allowable or denied. If permissions are granted, then the operation is performed. Each check throws a SecurityException if it fails. This is a RuntimeException so does not need to be within a try/catch block. So, this utilizes a property of the Java language where exception handling is free as long as there are no exceptions thrown. That is, no comparisons need to be made on return codes.

To demonstrate the security policy mechanism, examine the Thread class. Whenever you try to stop(), interrupt(), suspend(), resume(), setPriority(), setName(), or setDaemon() a thread, access is checked. If you do not have permissions, you cannot alter the thread.

public void checkAccess() {
  SecurityManager security = 
    System.getSecurityManager();
  if (security != null) {
    security.checkAccess(this);
  }
}

So, what does checkAccess() do? Well, java.lang.SecurityManager always throws a SecurityException. By default, in applications, there is no security manager installed, so this isn't a problem. However, the way the security manager restricts access in applets is by only allowing a thread to alter threads in the same ThreadGroup. So, if you try to affect a Thread in another group, a SecurityException will be thrown. Otherwise, the operation will succeed.

When creating your own SecurityManager you normally do not directly subclass SecurityManager. Instead, most people tend to create a NullSecurityManager that extends SecurityManager but opens access to everything. Then, you subclass this manager overriding the checks you wish to restrict. For a list of the available checks you can perform, see the SecurityManager documention.

Magercise

  1. Writing Your Own SecurityManager

Access Controller and Permissions

Because of the limitations associated with having a single SecurityManager, the JDK 1.2 environment adds new capabilities to enhance security to very finely control access beyond the Java environment, for both local and remote code. One instance where you run into these limitations is when multiple clients each require their own SecurityManager be installed in order to run properly. This cannot be done, as only one can be installed. In order to be able to install both managers, you would have to get the source code for each SecurityManager subclass and merge the two into a single manager. The likelihood of this successfully happening is unlikely, subject to bugs, and not scalable--imagine trying to get the source code for five or ten different security managers and trying to merge them together.

The JDK 1.2 SecurityManager extends the JDK 1.1 concepts to rely on an internal access controller for the extending of privileges to very specific operations. Individual permissions are set using the policytool program. The initial set of defined policies are found in the java.policy file located in the lib/security directory under the installed runtime environment. Among other things, they enable reading the public System properties like java.version. By default, a second policy file, .java.policy, is consulted, found in each user's home directory. To extend permissions, instead of having to create a new SecurityManager, you need to add a policy with the policytool, adding the desired permission to the appropriate file. You can also specify a specific policy file by setting the java.security.policy System property. Or, if you wish to monitor security access, you can set the java.security.debug System property. To see a list of all debugging options, use the help setting, the all setting allows you to see everything:

java -Djava.security.debug=help foo

all       turn on all debugging

access    print all checkPermission results
jar       jar verification
policy    loading and granting
scl       permissions SecureClassLoader assigns

The following can be used with access:

stack     include stack trace
domain    dumps all domains in context
failure   before throwing exception, dump stack
          and domain that didn't have permission

When granting permissions, you can base them on who signed the code, where the code came from (the codebase), or grant them to everyone. The java.security.Permission class serves as the base class of all 1.2 permission-related classes. What an actual grant statement looks like in the policy file follows. This would allow write access to the local temporary directory, and all subdirectories recursively, for code signed by JavaJoe and downloaded from http://www.TrustedUserHome.foo. If an asterisks (*) was used instead of a dash (-), the permissions would be for only the specific directory, not its subdirectories, also.

grant signedBy "JavaJoe", codeBase 
    "http://www.TrustedUserHome.foo" {
  permission java.io.FilePermission "c:\\temp\-";
}

Besides granting such a permission with policytool, you can check within a program if a user has permissions by asking the AccessController. If they don't, then an AccessControlException will be thrown. This is a subclass of the more generic SecurityException, both of which are runtime exceptions. The following shows how to check if the user has permission to read the file test.out in the platform-specific temporary directory. (This does require the user to have read access to the java.io.tmpdir property, which isn't available by default.)

String tempPath = System.getProperty ("java.io.tmpdir");
File f = new File (tempPath, "test.out");
FilePermission perm = 
  new FilePermission(f.getAbsolutePath(), "read");
AccessController.checkPermission (perm);

For the system defined permissions, it is not necessary to manually check for permission to access something before accessing it. The system does this for you. However, if you define your own permissions you need to check for permission yourself when appropriate. A good example for this might be a specific property of a bean. To read the property, you need read permission of your user defined permission. To modify it you need write access.

private String state;
...
public String getState() {
  MyPermission p = new MyPermission ("state", "read");
  AccessController.checkPermission (p);
  // throws exception if no permission
  return state;
}

public void setState (String newValue) {
  MyPermission p = new MyPermission ("state", "write");
  AccessController.checkPermission (p);
  // throws exception if no permission
  state = newValue; 
}

If you wish to have applications run with the same security restrictions as applets, you can startup the runtime environment by setting the new java.security.manager System property:

java -Djava.security.manager MyClass

A Quick Look at policytool

To grant permissions in JDK 1.2 the tool is called policytool. To demonstrate, the following shows what would be necessary to make the java.io.tmpdir property readable by everyone in a new policy file.

Entering the policytool command on the command line starts up the tool. By default, it looks for the .java.policy file in your home directory. If this doesn't exist, you will have to close select OK on an error message screen. Once started, you will see the following screen:

Next, select Add Policy Entry to bring up the next screen.

If there were any existing policies in the file, you would see them listed here. The following shows the default policy file settings:

You need to add a permission so select the Add Permission button to bring up yet another screen:

Here is where you'll do most of the work when adding permissions. When you select the Permission drop-down list, this alters the other settings, so it is best to set this first. Since what you need to enable is access to a specific property, select the Property Permission setting, and the tool adds the corresponding Java class name to the text field to the right of the setting. For the Target Name, with properties, you have to enter this in yourself. For other permissions, there may be pre-existing settings, like for Net Permission with setDefaultAuthenticator() and requestPasswordAuthentication(). Once you've placed java.io.tmpdir in the Target Name entry, you then define what actions you wish to enable. Here, you can define read access, so as to be nondestructive. Since the code won't be signed, leave the last entry blank. Otherwise, you would place a code-signing alias in the field. The final screen looks like so:

Next, select OK to accept the setting. This adds the entry to the previous screen. Then select Done to go back to the original screen, which adds an entry for CodeBase ALL that says the permission is for all programs. Finally, you can save the setting with the Save As option under the File menu to a file. For this example, call it mage.policy and then exit out of the policytool program. If you were to examine the file, it would be as follows:

grant {
  permission java.util.PropertyPermission 
     "java.io.tmpdir", "read";
};

Next, create a program that tries to access this property. The following will get the name of the platform-specific temporary directory. Compile and run it to see that it works. Since it is an application, by default it will work.

import java.io.*;
public class ListDir {
  public static void main (String args[]) {
    String dirname = 
      System.getProperty ("java.io.tmpdir");
    System.out.println (dirname);
  }
}

If you were to run the program with the java command, it will run fine. On the other hand, if you were to run it with the same security manager as an applet (java -Djava.security.manager ListDir), you would see a security exception thrown:

Exception in thread "main" 
   java.security.AccessControlException: 
   access denied (
java.util.PropertyPermission java.io.tmpdir read)
   at java.security.AccessControlContext.
     checkPermission(Compiled Code)
   at java.security.AccessController.
     checkPermission(AccessController.java:403)
   at java.lang.SecurityManager.
     checkPermission(SecurityManager.java:554)
   at java.lang.SecurityManager.
     checkPropertyAccess(SecurityManager.java:1107)
   at java.lang.System.
     getProperty(System.java:475)
   at ListDir.main(Compiled Code)

However, if you were to run it with both the applet security manager and your new policy file, it will work fine again.

java -Djava.security.policy=mage.policy 
  -Djava.security.manager ListDir

If you had read access to this directory, you could get a directory listing. However, since you don't, you can't without adding another permission.

Magercise

  1. Using policytool [JDK 1.2]

API-Level Security Features

In addition to the language and virtual machine features already discussed, Java technology has several packages that provide classes useful for writing secure applications. In JDK 1.1, the three packages, java.security, java.security.acl, and java.security.interfaces, comprise the Java Cryptography Architecture (JCA). The JCA is a framework for providing cryptographic capabilities to Java programs. In addition to the JCA with JDK 1.1, JDK 1.2 greatly modifies the existing security packages, adds the java.security.spec and java.security.cert packages, and also offers the Java Cryptography Extension (JCE) (javax.crypto.*). Currently for the US and Canada only, the JCE adds a standard extension package to JDK 1.2 that offers secure streams, key generation, and cipher support, including the ability to easily integrate additional cryptographic algorithms.

For information on the JCA 1.1 specification, be sure to read the available online reference. For information on the JCA 1.2 security capabilities, including the new command-line security tools, see the updated guide. For early access to the JCE 1.2 packages, see the associated javadoc documentation.

The remaining parts of this section focus on the JCA 1.1 and JCA 1.2 capabilities. JCE 1.2 specific capabilities are not discussed. Keep in mind that the JCA 1.2 and JCE 1.2 APIs and capabilities are only available in beta versions so is subject to change. Also, Sun's JCE implementation is not exportable outside of US and Canada.

java.security

The java.security package consists mostly of abstract classes and interfaces that encapsulate security concepts such as certificates, keys, message digests, and signatures. Concrete implementation classes are not found here but are provided by the application programmer or a third-party package or product.

There is both a user side and a provider side to the security package. As the names may imply, programmers use the user side to create applications using crypto algorithms from the provider side. Companies provide algorithmic implementations on the provider side. To enable an encryption algorithm, a provider is installed in one of two ways:

  • Update the /lib/security/java.security file under the JRE installation directory to specify the new provider. There must be at least one installed. The default provider that comes with Sun's JDK is sun.security.provider.Sun. If you get your environment from another source, this provider could differ.
  • Add the new provider with Security.addProvider (Provider). While this mechanism may seem preferred, it actually isn't. If a better provider is found, where better means stronger or harder to break crypto algorithm, then the program needs to be recompiled and redistributed.

In JCA 1.1, the three classes that providers can offer implementations for are KeyPairGenerator, MessageDigest, and Signature. You then ask for a specific implementation by using the getInstance() method of each and the name of the cryptographic algorithm:

MessageDigest md = MessageDigest.getInstance ("MD5");
KeyPairGenerator kpg = 
  KeyPairGenerator.getInstance ("DSA");

The system then finds a provider that supports the algorithm, searching through its ordered list, and returns the specific subclass. You can also directly ask a specific provider:

kpg = KeyPairGenerator.getInstance ("DSA", "MageLang");

In JCA 1.2, you also have AlgorithmParameterGenerator, AlgorithmParameters, CertificateFactory, KeyFactory, KeyStore, and SecureRandom for providers to offer implementations.

Usage of the security capabilities is described below.

java.security.acl

The java.security.acl package defines support for access control lists. These can be used to restrict access to resources in any manner desired. The package consists of interfaces (and exceptions). The actual implementations, within Sun's JDK, are provided in the sun.security.acl package.

Basic usage goes as follows: for some object, or AclEntry, you define permissions, Permission implementer instances that specify user, Principal, access. This access can be yes they have the permission or no they don't. The no they don't case can be used with Group objects where a group does have permission to some AclEntry, but an individual doesn't, or vice versa.

java.security.interfaces

The final JCA 1.1 security package java.security.interfaces includes interfaces that are specific for using Digital Signature Algorithm (DSA). For instance, you probably want to initialize a DSAKeyPairGenerator after getting an instance:

DSAKeyPairGenerator dkpg = (DSAKeyPairGenerator)
  KeyPairGenerator.getInstance ("DSA");
DSAParams params = new DSAParams (aP, aQ, aG);
dkpg.initialize (params, new SecureRandom());

Don't worry about the specifics of the code yet. It will be explained shortly.

In JCA 1.2, the java.security.interfaces package includes interfaces that are specific to RSA (named after its inventors, Ron Rivest, Adi Shamir, and Leonard Adleman). In addition to RSA public key support, for RSA private keys there is support for both with Chinese Remainder Theorem (CRT) and without.

java.security.cert

The JCA 1.2 security package java.security.cert adds support for generating and using certificates, as well as reading them from places like JAR files via JarURLConnection and JarEntry. There are also specific classes and interfaces to support X.509 certificates.

java.security.spec

The java.security.spec package added with JCA 1.2 adds interfaces and classes that describe key specification formats. These classes allow you to easily create Java security keys based upon parameters generated from tools outside of the JDK.

Using the Security Packages

Now that you've seen an overview of the different security packages provided with the Core Java API, it is time to delve into the specifics. First, and simplest, the JCA includes support for digital fingerprints, or message digests, that provide the equivalent of checksums, or calculations based on the message content. A message is transferred in the clear and the original checksum is provided separately. The checksum is recalculated from the message and compared to the checksum. If the two are equivalent, the message is considered to be unaltered.

The second capability supported by the JCA is digital signatures. Digital signatures are used to authenticate the originator of a message. In the message digest example, it is possible to intercept the original message and checksum, create a new message and checksum, and send both along to the recipient. The recipient has no way of truly knowing who sent the original message. With digital signatures, the sender of a message is authenticated so the recipient knows who the message came from and the message itself hasn't been altered. The way the sender is authenticated is by encrypting the message digest, not the message.

Using a public-private key pair, the sender encrypts the digest using the sender's private key. The receiver decrypts the message with the sender's public key. As long as the public key is acquired in a trusted manner, you can then verify who sent the message and that it was unaltered. Identities of people needing to use digital signatures are managed with what are called certificates. And, identities are also used when working with access control lists, to manage access to specific resources. All of these different terms and capabilities will be described separately and in much greater detail.

Support for encrypted streams is part of the JCE, thus its export restriction.

Message Digests

A message digests is the fixed-length result of a one-way hash of the contents in the message, similar to a cryptographic checksum or cyclic redundancy check (CRC). This is a one-way hash--you cannot recover the original message contents from its digest. This can be easily seen given an example: there are literally an infinite number of wildly different messages that can be composed, each of differing lengths and content, but the digest itself is limited to a fixed-length, therefore it contains less information than is needed to reconstruct a message. So for each fixed-length digest, there are an infinite number of messages that share that digest, but they are not similar (due to the quality of the cryptographic function within the hashing algorithm), and it is virtually impossible to create a message with a predetermined digest.

The message digest is typically attached to the end of a clear-text message, or sent separately. While this does not provide secrecy for the message content, it does provide verification of the original content of the message. The recipient will recompute the message digest and compare it to the digest that was created by the sender. If the recomputed digest is the same, the message was not tampered with, or if it were, then a new digest would have been inserted (making it undetectable that the message was altered).

The digest acts as a checksum for the message because if the message is changed in transit in any way, the hash computed on the client end will not match the hash computed on the server end. But what is to prevent someone from changing the message and the hash? Nothing, unless the message digest is encrypted with the sender's private key. This way, anyone can decrypt the digest to verify that: the message is authentic (the sender was the actual originator) and the message arrived unaltered (the message digest computed by the recipient matches the decrypted message digest provided by the sender). But that leads to digital signatures, which is discussed in the next section.

So, how do you use message digests? Well, as with mostly everything else, its a multistep process:

  1. Get the message content as a byte[]. If coming from a file, read bytes from FileInputStream into ByteArrayOutputStream. Other sources would use similar methods, but remember to not treat content as characters.
    FileInputStream fis = 
      new FileInputStream (filename);
    BufferedInputStream bis = 
      new BufferedInputStream (fis);
    ByteArrayOutputStream baos = 
      new ByteArrayOutputStream();
    int ch;
    while ((ch = bis.read()) != -1) {
      baos.write (ch);
    }
    byte buffer[] = baos.toByteArray();
    
  2. Get a MessageDigest for the appropriate algorithm. The JDK provides implementations of SHA-1 (or SHA, from NIST FIPS 180-1) and MD5 (from MIT, algorithm RSA-MD5, as defined by RSA DSI in RFC 1321). If you request an algorithm where no provider is available, NoSuchAlgorithmException is thrown. The SHA algorithm results in a 20-byte digest, while MD5 is 16 bytes long.
    MessageDigest algorithm = 
      MessageDigest.getInstance ("SHA-1");
    

    or

    MessageDigest algorithm = 
      MessageDigest.getInstance ("MD5");
    
  3. Ensure the digest's buffer is empty. This isn't necessary the first time used. However, it is good practice to always empty the buffer out in case you later reuse it.
    algorithm.reset();
    
  4. Fill the digest's buffer with data to compute a message digest from.
    algorithm.update (buffer);
    
  5. Generate the digest. This does any necessary padding required by the algorithm.
    byte digest[] = algorithm.digest();
    
  6. Save or print digest bytes. If you happen to use Integer.toHexString(), remember that this doesn't print leading zeros.
    StringBuffer hexString = new StringBuffer();
    for (int i=0;i<digest.length;i++) {
      hexString.append (
        Integer.toHexString(0xFF & digest[i]));
      hexString.append (" ");
    }
    System.out.println (hextString.toString());
    

And, that's all there is to it. Examine the source for the MessageDigestEncoder for a complete demonstration, including a better hex string printer.

Another means of getting input to the MessageDigest is by adding a MessageDigestInputStream or MessageDigestOutputStream to your input or output stream respectively. They allow you to turn the digest on only for portions of the stream, in addition to the entire stream, if that behavior is desired.

As long as you trust the mechanism of delivering the digest, or possibly send the digest multiple times (or through multiple channels--print and electronic), message digests offer an easy way of ensuring a message is not altered. However, they do not authenticate the sender, nor encrypt the message contents. And, if both the message and the digest are intercepted, someone could easily send a new message with a valid digest in its place.

Digital Signatures

While you can use message digests to verify the contents of a message have not been altered, they do not verify the message came from the supposed sender. Since algorithms for the digests are public, anyone can create a message, generate the digest, and then say it came from anyone in the world. In order to authenticate a message and its sender, you need to use digital signatures. Digital signatures are a way to label messages or objects so that the creator of that message or object may be positively identified to the recipient or user. And, they also verify the contents are not altered.

To use digital signatures, there are several elements of cryptography that need to be understood. First, is the previously explained message digest. A digital signature is basically a message digest. It does not alter the original message contents. Instead, the digital signature digest is encrypted. The signing algorithms to generate this digest are public. However, they rely on a pair of keys to encrypt and decrypt the signature. At the sending side, a private key is used to encrypt the digest. At the receiving side, a public key is used to decrypt the digest, based on the algorithm and the message contents. As long as you trust the means of receiving the public key, you are then assured of the identity of the message originator and that the contents have not been altered.

What follows is a look at the individual pieces in more detail.

Public Key Cryptography

So, what is a public and private key? Before that question is answered, you need to understand two forms of cryptography: symmetric and asymmetric. With symmetric cryptography, the sender and recipient share the key used to encrypt and decrypt messages. If a third party intercepts the key, they can encrypt messages and pose as the sender, or decrypt messages and see what only the recipient should have. This type of cryptography requires you to control who has the keys. Symmetric cryptography offers neither identification of who is accessing something, nor authentication of any specific identity. Assuming these characteristics are desired, alternatives need to be examined.

On the other hand, with asymmetric cryptography, different keys are used for encryption and decryption. In fact, every person involved in a transaction has his or her own pair. For each individual, there is a private key, which only that person knows, and a public key, which everyone can know. To verify that someone is the sender of a message, that person would use his or her private key to encrypt the message or its digest. Then, the recipient can decrypt the message with the sender's public key. If the only thing encrypted is the digest, this truly just verifies the sender, but the world can read the message. This in fact is a digital signature.

Public key cryptography goes further if you encrypt the message, too. If you are sending a message, you can encrypt the message with the public key of the recipient. Then, only the recipient would be able to decrypt it. Only encrypting it with the recipient's public key doesn't authenticate the sender. So, you would need to encrypt it with the sender's private key, too. This provides both security of the data and verification of the sender. The recipient would then need to decrypt the message twice, once with the sender's public key and once with the recipient's private key. The following diagram demonstrates this (order of encrypting and decrypting doesn't matter).

JDK 1.1 comes with support for generating key pairs using DSA (Digital Signature Algorithm). If you are interested in support for RSA (invented by Rivest, Shamir, and Adleman), you can purchase a product like JSAFE from RSA Products. JSAFE also includes support for the symmetric crypto algorithms of DES, Triple-DES, RC2, RC4, and RC5. Baltimore Technologies also offers J/Crypto, which offers RSA public key cryptography, X.509 certificate handling, RC4, Triple-DES, a wide range of cipher, hashes, and key exchange algorithms. Cryptix is yet another one with similar offerings. Sun's JCE 1.2 includes a provider for DES, Diffie-Hellman, and RSA, as well as many others.

Generating a key pair under JCA 1.1 involves using the KeyPairGenerator class. As with MessageDigest, you ask the generator for an instance using a specific algorithm, in this case DSA. If a provider supporting the algorithm is available, you get its generator.

KeyPairGenerator kpg = 
  KeyPairGenerator.getInstance ("DSA");

Once you have a generator, you need to initialize it with initialize. In the case of the DSA key pair generator, initialization requires you to define a strength, like 512 or 1024 bits.

kpg.initialize(1024);

The DSAKeyPairGenerator also can be initialized with a set of DSAParams values. These values represent a p, q, and g value, where p is the prime used in the generation process, q is the subprime, and g is for the base. If these are not specified, like the direct call to initialize above, default values are used.

DSAKeyPairGenerator dkpg = (DSAKeyPairGenerator)
  KeyPairGenerator.getInstance ("DSA");
DSAParams params = new DSAParams (aP, aQ, aG);
dkpg.initialize (1024, params, new SecureRandom());

Once you have a key pair generator, you generate an actual KeyPair with the generateKeyPair() method:

KeyPair kp = kpg.generateKeyPair();

Once you generate a KeyPair, you probably want to save them to disk. That way the same key pair can be used repeatedly and you will not need to keep sending a different public key out for each message.

Moving to JDK 1.2 with the JCA 1.2, key pair generation can be done the same way. Or, you can use the KeyFactory class to convert a key from another environment, like RSA's BSAFE, into Java space.

RSAPrivateKeySpec rsaPrivateKeySpec = 
  new RSAPrivateKeySpec (aModulus, aPrivateExponent);
KeyFactory keyFactory = 
  KeyFactory.getInstance("RSA");
PrivateKey rsaPrivKey = 
  keyFactory.generatePrivate(rsaPrivateKeySpec);

Signing Data

Once you have a KeyPair, you are on your way to signing your message. Actual signing is a four-step process:

  1. Acquire a Signature object using DSA. The message digest algorithm is SHA-1, so you could ask for "SHA/DSA" and get the same type of object back.
    Signature sig = Signature.getInstance("DSA");
    
  2. To sign the message, you must initialize the Signature with the PrivateKey acquired from the generated KeyPair
    PrivateKey priv = kp.getPrivate();
    sig.initSign(priv);
    
  3. Provide Signature with message to sign
    sig.update (aByteArray);
    
  4. Sign it and get digest. Typically, this would be appended to the original message and sent away to the recipient.
    byte digest[] = sig.sign();
    

Verifying Data

If you were the recipient of the above message, you would verify that the message you received came from the designated sender. Verification is only a three-step process:

  1. Again, acquire a Signature object using DSA.
    Signature sig = Signature.getInstance("DSA");
    
  2. To verify the data, you must initialize the Signature with the sender's PublicKey. Normally, this would be acquired from a Certificate (Certificate.getPublicKey()), which will be described shortly. However, if everything were done within the same program, you would acquire it from the generated KeyPair.
    PublicKey pub = kp.getPublic();
    sig.initVerify(pub);
    
  3. Finally, using the message content and digest, verify the data:
    sig.update (content);
    if (sig.verify(digest)) {
      System.out.println ("Valid");
    } else {
      System.out.println ("Invalid");
    }
    

    If the verification was to be done in a separate program without using the Certificate interface to get the public key, the signer would need to write the PublicKey to an ObjectOutputStream:

    FileOutputStream fos = 
      new FileOutputStream ("key.dat");
    ObjectOutputStream oos = 
      new ObjectOutputStream (fos);
    oos.writeObject (pub);
    

    which the recipient would read back from an ObjectInputStream.

    FileInputStream fis = 
      new FileInputStream ("key.dat");
    ObjectInputStream ois = 
      new ObjectInputStream (fis);
    PublicKey pub  = 
      (PublicKey)ois.readObject ();
    

Authentication and Certificates

The problem with the public/private key scheme is the issue of public key distribution. A message can only be authenticated if you are sure the public key you received for the sender is really that sender's public key. How can you be assured that someone isn't providing you with a false public key, impersonating someone else? To deal with that possibility, you use certificates. A certificate consists of a public key, detailed information about the certificate owner (such as name, e-mail address, and so on) and other, application-specific data. To ensure the integrity of the certificate, it is signed by a certification authority (CA), a trusted entity whose public key is widely known and distributed. In essence, the CA vouches for the validity of the public key contained in the certificate, and ensures that the identity of the certificate owner reflects the actual person or entity.

Thus, when you are presented with a certificate, it acts as an official, verifiable identity document, carrying the full faith and trust of the CA. You can see that the CA has a lot to lose if they falsely accredit a certificate - this is a strong incentive for CAs to take care when signing certificates. Companies like VeriSign issue certificates and act as guarantor. The certificate is signed by the certification authority using their private key and can be looked at by decrypting with their public key. The CA is responsible for issuing certificates, so they take care to verify information before doing so. Most CAs offer different classes of certificates. The level of verification used determines the trustworthiness and price.

There are different types of certificate standards. The two most common are X.509 (currently at version 3, as of June 1996) and PGP (for Pretty Good Privacy). While the international telephone standards body originally created the X.509 standard (as ITU-T X.509), Phil Zimmermann created PGP. The primary difference between the two is in their application usage. While X.509 is used by Microsoft's Authenticode, Netscape's Object Signing, and Marimba's Channel Signing, to authenticate the originator of Internet objects, PGP is generally limited to email applications for encrypting, compressing, and authenticating messages and their attachments. However, each provides the same general information like the public key, identity of whose key it is, and who guarantees the authenticity of the identify of whose key it is. You can get additional information on public key infrastructure (or more specifically X.509) from Internet Engineering Task Force. PGP information is available from Network Associates.

JDK 1.1 comes with support interfaces for X.509 certificates. Standard classes to generate and use certificates to authenticate Internet objects is part of JCA 1.2.

Besides generating certificates, you can store them in a KeyStore. The following code will provide a list of the aliases who have Certificate objects in a the file mycerts. The password is password. The jks keystore is the keystore provider that Sun's JDK provides. If you forget the name, look in the java.security file for the keystore.type entry.

KeyStore store = KeyStore.getInstance ("jks");
FileInputStream fis 
  = new FileInputStream ("mycerts");
store.load (fis, "password".toCharArray());
Enumeration aliases = store.aliases();
while (aliases.hasMoreElements()) {
  System.out.println (aliases.nextElement());
}

To get the certificate for a specific alias, ask with the getCertificate() method. Then, from the certificate, you can get the public key.

Certificate cert = 
  store.getCertificate ("theAlias");
PublicKey pub = cert.getPublicKey();

Browser-level Security

Before looking at creating and using certificates, it is necessary to look at why they are necessary. Java applets are executable content, usually downloaded from unknown sources over the Internet. As previously mentioned, applets require the installation of their own security manager, by the browser vendor. In general, since you do not know from whom you are downloading an applet, applets are deemed 100% untrustworthy, forcing them to run within a sandbox. The applet cannot do anything outside of the JRE thanks to the applet's SecurityManager. And, users cannot change an applet's SecurityManager. About the only thing the applet can do within the sandbox is hog all the system resources, resulting in a denial of service attack, or spoof the user into thinking an applet came from a different source (possibly as a result of interception and modification).

What follows, is a look at the generalities of browser-level security, followed by specifics for Sun's appletviewer/HotJavaTM, Netscape Communicator 4.0, and Microsoft's Internet Explorer. Unfortunately, each is currently different. Signing Java applets is also discussed, but only for Netscape Communicator.

Executable Content

Fundamental to the concept of portable code fragments (Java classes), which can be downloaded and run on the client side, is the fact that clients are letting someone else's executable code run on their machine. The Java security mechanisms provide a way to restrict what the executable program may or may not do.

This problem is not restricted to Java technology alone; there are other executable content formats (most notably ActiveX controls) that pose a similar problem to the client. For instance, ActiveX uses Microsoft's Authenticode for code-signing. Authenticode does not prevent invalid memory references or bugs from malicious operations like crashing a system. It purely provides accountability for legal recourse by identifying the code publisher. With Authenticode, a trust policy is established with the signer, granting or revoking permissions based on their identity. On the other hand, with untrusted Java applets, the executable Java code is isolated to run within the JVM's sandbox.

Browser plug-ins also provide another form of executable content. Plug-ins provide the means to extend browser functionality by providing support for additional types of input. Instead of only displaying HTML text and images, browsers can display Adobe Acrobat files or Apple QuickTime movies with the help of plug-ins. They offer no accountability beyond that associated with trusting the source of the plug-in installation file. Once a plug-in is installed, they have free reign of the client system, to the extent of the browser's access. If a plug-in is buggy, malicious input could be designed to take advantage of these deficiencies, wreaking havoc on the client machine.

Applet Security Managers

Browser-level security for Java applets is based on a subclass of SecurityManager. The specific subclass depends on the browser:

  • AppletSecurityManager: from package sun.applet with JDK 1.1 and HotJava
  • AppletSecurity: from package sun.applet with JDK 1.2
  • AppletSecurity: from package netscape.security with Netscape Communicator 4.0
  • AppletSecurity: from package netscape.applet with Netscape Navigator 3.0
  • StandardSecurityManager: from package com.ms.security from Microsoft's Internet Explorer

In addition to custom security managers, browsers also have custom class loaders that ensure classes from different applet code bases are loaded in separate name spaces and other browser-specific class loading constraints.

The SecurityManager subclass provides the browser-specific granting or denying privileges to the applet. Browsers have tended to restrict everything that can't be proven safe.

For instance, applets are prevented from reading/writing local file system. Why? If someone could read /etc/passwd from an applet then send it back to the hostile server, someone could crack passwords. If someone could read arbitrary files, that same person might make a copy of your database, or copy sensitive e-mail messages and send back them to the hostile server.

In addition, applets are prevented from opening sockets to destinations other than the IP address from which the applet originated. Why? So applets can't do port scans inside the firewall, also, so they can't use the client machine as a source for attacks on other computers, so you can't forge e-mail from the client, and so on. This list could go on for a while.

Applets are also prevented from creating a ServerSocket for listening on a port. Why? An applet would be able to spoof a service on the client end. Say listen to port 25 (SMTP) and accept e-mail on behalf of the client.

Also, they are prevented from reading of some system properties, such as user name and home directory. Why? Information is the most valuable commodity to the hacker. Finding out user names and file system structure is a great first step towards breaking in. After all, if you don't know the username, what good is a password?

The security manager also prevents new windows from applets appearing as other trusted windows. While instantiating new frames are allowed, all such frames and dialogs have a banner tacked on to them which identifies the frame as having originated from an untrusted applet. (The actual message displayed depends on the browser.) This prevents the applet from, for example, spoofing a login prompt and capturing the user's password.

Real
Spoof/Java

To prevent applets from bypassing these security mechanisms, they cannot install their own ClassLoader or SecurityManager.

Extending Browser Privileges

While the general concepts of Java security are consistent across all browsers, the means of Java applets getting out of the JVM sandbox are, unfortunately, specific to each and every one of them. Hopefully, the permission/policy model coming with JCA 1.2 will unify this. However, until such a time, your best bet, at least on a corporate intranet, is to utilize Sun's Java Plug-in product. With the Java Plug-in, the latest JVM is installed on the client's machine whenever the client visits a web page configured to request it. When the JVM has been updated on the web server, the web client would automatically install the upgrade. This ensures compatibility across browsers and provides a JVM upgrade cycle outside of the general browser release cycle.

Before looking at how Netscape Communicator supports signing Java programs, let's look at what each browser offers in addition to signing. In most cases, the applet must be signed, in order to use these additional capabilities. Surprisingly, that isn't always the case.

Extending HotJava Privileges

Outside of code-signing, the HotJava browser (and appletviewer) allows a user to configure access to system properties, read/write files on the client machine, or get file information. This is done with the help of the properties file in the .hotjava directory (found in various places depending upon the environment. If on a multi-user system, it will be found under the user's home directory).

To make visible a System property that is normally invisible, you append .applet=true to the property name. So, to make visible the JRE installation directory (property name java.home) you would add the following line to the appropriate file, while HotJava is not running.

java.home.applet=true

Once this line is added, the next time you run HotJava or appletviewer, your program could have a call to System.getProperty ("java.home") and not throw a SecurityException.

On the other hand, if you wish to hide a property that is normally visible to an applet, you add a line that sets the property value to null. For example, to hide the java.version property (from an applet, but not an application), you would add the following line:

java.version=null

In addition to protecting System properties, HotJava (and appletviewer) offer access control lists for creating readable and writable client-side directories. The read property to specify in the properties file is acl.read, while the write property is acl.write. You can place specific files or whole directories into the access control list. The separator character is a colon (:) character. For example, the following would make the /temp directory and /users/jaz/config.dat file writable:

acl.write=/temp:/users/jaz/config.dat

Extending JDK 1.2 Applet Privileges

Besides physically signing code with JDK 1.2, you can use the policytool to grant permissions. These policies can either be specified in a user's .java.policy file in the user's home directory, or the system policy file java.policy found in the lib/security subdirectory, under the JRE installation directory.

To make visible a System property that is normally invisible, you would use policytool to grant the permission. It would add a line like the following to the file:

permission java.util.PropertyPermission 
  "java.home", "read";

With policytool, you can enable every possible JDK 1.2 permission-type, like for making writable directories, enabling socket connections to hosts, and much more. Each adds a similar line to the java.policy file. All of which can be done without signing any code. Permissions can be grants for everyone, anything downloaded from a specific host (codebase), or anything signed by a specific trusted user.

To remove permissions, you remove entries. This could do things like hide normally visible properties from user classes, however, system classes would still be able to see them.

Netscape Capabilities Classes

Netscape provides a Capabilities API for breaking out of the JVM sandbox with Netscape Communicator. The classes in the Capabilities API are found in the netscape.security package, located in the java40.jar file, located under the Communicator installation directory. If you wish to use these classes, you need to place this file in your CLASSPATH (after the JDK 1.1 classes.zip file) so you can compile. Running programs using this API within the browser isn't an issue as the package is automatically installed. While the netscape.security package is provided with Communicator, java.security is not.

You can also download the Capabilities API classes directly from Netscape (a much smaller zip file) and add this file to your CLASSPATH.

So, what do these classes do? Well, if you happen to have looked into the JCA 1.2 for JDK 1.2 beta3, it looks somewhat similar. (However, this changed with JDK1.2 beta4.) Basically, there is a privilege manager that controls access to targets. You enable and disable (revert) privileges when necessary. While the API doesn't force you to minimize the time privileges are enabled, it is best to request a privilege immediately before needing it then reverting back to the original state after using it. For instance, privileges can be enabled en masse, like UniversalPropertyRead for reading all normally hidden system properties:

import netscape.security.*;
...
try {
  PrivilegeManager.enablePrivilege (
    "UniversalPropertyRead");
  String home = System.getProperty (
    "java.home");
  PrivilegeManager.revertPrivilege (
    "UniversalPropertyRead");
  System.out.println ("java.home: " + home);
} catch (ForbiddenTargetException e) {
  System.err.println (
    "User denied access to read properties");
}

Or, you can enable privileges on a specific target, like reading a specific file. There is one strange thing about enabling privileges. The privileges must be used within the method enabled. If not, the enable request must be reissued. While the privileges are granted for the life of the applet, unique indicators are placed on the program stack to ensure usage within the method called. That is why the following example uses a FileInputStream, instead of a FileReader. The constructor for FileReader creates a FileInputStream. Since the file is not touched until the FileInputStream constructor, it is within the wrong call stack.

try {
  // cryptographic principals at [0]
  // codebase principals at [1]
  Principal prin = 
    PrivilegeManager.getMyPrincipals()[0];
  PrivilegeManager pm = 
    PrivilegeManager.getPrivilegeManager();
  String filename = getParameter ("filename");
  Target readTarget = Target.findTarget ("FileRead");
  readTarget.enablePrivilege (prin, filename);
  // read file
  // Don't use FileReader - it fails, wrong call stack
  FileInputStream fis = new FileInputStream (filename);
  InputStreamReader isr = new InputStreamReader (fis);
  BufferedReader br = new BufferedReader (isr);
  StringWriter sw = new StringWriter();
  String line;
  while ((line = br.readLine()) != null) {
    sw.write (line);
    sw.write ('\n');
  }
  br.close();
  setLayout (new BorderLayout());
  add (new TextArea (sw.toString()), 
    BorderLayout.CENTER);
} catch (ForbiddenTargetException e) {
  System.err.println (
    "User denied access to read file");
} catch (IOException e) {
  System.err.println ("Error reading file");
}

When the code is run and the request is made to enable a privilege the user doesn't have, a dialog pops up asking if the user trusts the applet. If they answer yes, the operation is permitted. Otherwise, the ForbiddenTargetException is thrown.

Notice that the Identity verified by field at the bottom of the dialog is blank. If you were to select the Certificate button, you would see an empty certificate. This means the applet has not been digitally signed. You'll learn how to sign your Java programs shortly so you can add these capabilities to applets loaded from a web server.

There are three risk levels that Netscape defines: low, medium, and high.

  • Low is for tasks like reading private system properties
  • Medium is for tasks like reading disk files
  • High is for tasks like connecting to remote machines, other than the applet server

Risk levels are automatic based on system target and customize the help message to the user. For a list of all the system targets, or capabilities to enable, see Netscape's list.

Magercise

  1. Using Netscape's Capabilities Classes

Using Internet Explorer's Permission Scoping

Microsoft's Internet Explorer offers a different model for assigning privileges. It uses a permission and zone-based approach for breaking out of the Java sandbox. In addition to the zones though, there is also a PolicyEngine. This class acts like Netscape's PrivilegeManager, allowing you to assert, revert, check, and deny privileges based on PermissionID constants and other specialized groupings.

Asserting permissions is basically that. Locate the appropriate PermissionID, and assert your right to that privileged operation. For example, if you want to be connected to some random machine on the Internet, you would assert PermissionID.NETIO. Then, an attempt to access a foreign machine other than the web server would succeed, if the class were signed with the appropriate permissions:

PolicyEngine.assertPermission(PermissionID.NETIO);
URL url = new URL ("http://www.magelang.com");
InputStream is = url.openConnection().getInputStream();
is.read();

When you are done with a permission, you should remember to revert it back. Also, if you've explicitly denied a permission (with denyPermission(), revertPermission() can be used to negate that effect also. And, like Netscape, even if your class has been explicitly granted a permission, you still have to assert enable the permission in the same call stack.

With checkPermission, you can check if you have access to a particular object, like a system property:

PropertyAccessRequest par =  
  new PropertyAccessRequest("user.name");
PolicyEngine.checkPermission(par);

If you develop with Microsoft's Developer Studio, you can test programs unsigned. Otherwise, you need to sign your classes with the signcode utility, described in Microsoft's Using Signcode with Java Permission Information document.

Signing Java Programs

Now that you've seen how the different browsers allow applets to extend outside the JRE without being signed, its time to look at what additional capabilities they can do once they are signed. Unfortunately, at his time there are many different types of signatures and ways these signatures can be used. The following sections look at the different browsers signing mechanisms and demonstrates Netscape's mechanism specifically.

Signing Applets for HotJava, Java Plug-in, and appletviewer

The JCA 1.1 provides support for the loading and authentication of signed classes. The JCA 1.2 extends these capabilities to offer a finer level of control to what signed classes can access. With JCA 1.2, security policy management is much more fine grained: a uniquely identified entity can be denied or granted specific permissions on a per policy basis. Once HotJava (and the Java Plug-in) is based on the JDK 1.2, these capabilities will automatically be available. Also, HotJava currently offers both domestic and global SSL versions, offering different (or even no) levels of support for secure socket (https) connections.

For an example using the JCA 1.1 tools, see the Signed Applet Example. For an example using the JCA 1.2 tools, see the Java Developer Connection article titled Signed Applets, Browsers, and File Access.

Signing Applets for Netscape Communicator

To use a trusted applet within Netscape Communicator requires a signing certificate. To get one, you contact an independent Certification Authority (CA) like Verisign or Thawte Certification. Or, if you wish to setup your own in-house CA, for intranet/extranet applications, you can purchase Netscape's Certificate Server.

Netscape's tool for object signing is called the Netscape Signing Tool. There are different versions available for the major platforms that Netscape supports. Previous versions of the tool were called zigbert and Page Signer. Those tools are no longer supported.

The Signing Tool program named is called signtool and is used to sign jar files for Netscape Communicator 4.x. Even though you use signtool to sign jar files, Communicator still prompts the user for permissions. So source code still has to be modified using the Capabilities API mechanism described in the earlier Netscape section.

While you can get free (Class 1) certificates for email use, in order to sign an object with Netscape's signtool, you must get a Class 2 or Class 3 certificate from one of these CAs supported. These run from $20 to $400, depending on the vendor and the class. And, the certificates acquired are specifically for use with the Netscape Signing Tool, not with HotJava, Java Plug-in, appletviewer, nor Internet Explorer.

Before you can install new keys and certificates, you must set the database password within Communicator. First select the Security icon in the toolbar for Communicator:

This brings up the Security Info window. From here, select the Passwords option:

This brings up the Passwords window. From here, select the Change Password button and enter in a password. Don't forget it. Exit out of Communicator completely after updating the certificate database.

For testing purposes, you can generate a test certificate with signtool. With the NT version of the Netscape Signing Tool, you must specify the appropriate Communicator User directory, where the password you just set would be saved. By default, this would be C:\Program Files\Netscape\Users\default. However, default should probably be replaced by the username you have established within the browser.

signtool -G"mytestcert" 
    -d "C:\Program Files\Netscape\Users\default"

The signtool program then goes through a series of prompts asking you information about your identity. Responses are in bold:

using certificate directory: 
  C:\Program Files\Netscape\Users\default

Enter certificate information.  All fields are optional. 
Acceptable characters are numbers, letters, spaces, and 
apostrophes.
certificate common name: Test Certificate
organization: Some Company X
organization unit: Mars
state or province: MA
country (must be exactly 2 characters): US
username: jane
email address: jane.doe@foo.foo
Enter Password or Pin for "Communicator Certificate DB":
generated public/private key pair
certificate request generated
certificate has been signed
certificate "mytestcert" added to database
Exported certificate to x509.raw and x509.cacert.

Once everything has run, signtool will modify the cert7.db and key3.db files in the directory specified by -d. Also, as the last line specifies, the files x509.raw and x509.cacert (cacert = CA Certificate) contain the certificate. This can be imported into other Communicator copies or saved onto disk. If you were to purchase a real certificate, the certificate would be stored in MyKey.p12 (private key), as well as the two previously mentioned files.

Once you have a signing certificate, you can sign the earlier program that demonstrated Netscape's Capabilities API. You can list the available signing certificates with the -l option or the -L option for all certificates, including the CAs.

To sign the ReadFileNS class file, perform the following steps:

  1. Compile the source file. (Make sure Netscape's Capabilities classes are in your CLASSPATH.)
  2. Make a signing directory. The signtool places everything in a particular directory tree into the jar file.
  3. Copy class files for applet (and any support files if necessary, although not in this case) into directory.
  4. Sign it: (Include a -d dirname if necessary)
    signtool -k mytestcert
             -Z readNS.jar
             signing
    

    This results in the following series, with you entering the Netscape certificate database password when prompted:

    using key "mytestcert"
    using certificate directory: 
        \progra~1\netscape\users\default
    Generating signing/META-INF/manifest.mf file..
    --> ReadFileNS.class
    adding signing/ReadFileNS.class to readNS.jar...
        (deflated 44%)
    Generating zigbert.sf file..
    Enter Password or Pin for 
        "Communicator Certificate DB":
    adding signing/META-INF/manifest.mf to readNS.jar...
        (deflated 15%)
    adding signing/META-INF/zigbert.sf to readNS.jar...
        (deflated 27%)
    adding signing/META-INF/zigbert.rsa to readNS.jar...
        (deflated 43%)
    tree "signing" signed successfully
    
  5. Verify the signature: (Include a -d dirname if necessary)
    signtool -v readNS.jar
    

    If everything went okay, this results in the following:

    using certificate directory: 
        \progra~1\netscape\users\default
    archive "readNS.jar" has passed crypto verification.
    
              status   path
        ------------   -------------------
            verified   ReadFileNS.class
    
  6. Once you have a signed jar file, create an HTML file that references it:
    <applet 
       code=ReadFileNS
       width=200
       height=200
       archive=readNS.jar>
    <param name="filename" value="C:\temp\file.txt">
    </applet>
    

    Now, when you run the program from a foreign web server, you will get the Java Security check window, as before and if you click the Certificate button, you will see the test certificate:

  7. Close the certificate window and Grant the access. Then, the applet displays the file in a TextArea, assuming the file exists. If you Deny the permission, a security exception will be thrown. This causes the User denied access to read file message to be displayed in the Java console.

During the development cycle, you can test Netscape's Capabilities-based architecture without signing anything. Once this is enabled, if you run across an applet from somewhere (including the open Internet) that requests additional privileges, you will be asked if you trust the applet. Be very careful and only trust what you are testing. To enable, place the following line in the prefs.js file in your subdirectory under the Users directory of your Communicator installation.

user_pref(
  "signed.applets.codebase_principal_support", true);

Then Netscape will recognize all code as signed. Be careful using this! Don't forget to remove it when finished testing!

Note: For completeness sake, the JavaScript security model within Communicator should be mentioned, as applets within the JRE can talk to JavaScript when the MAYSCRIPT option is included within the <APPLET> tag. However, it would require a long description into the specifics of JavaScript, so has been excluded. If you are interested, you can read Netscape's online documentation.

Magercise

  1. Signing Jar files for Netscape Communicator with signtool

Signing Applets for Internet Explorer

Internet Explorer supports signed CAB files, not signed JAR files. CAB is short for cabinet and is a Microsoft-only format. The tools for signing applets in CABs are found with Microsoft's SDK for Java.

As previously mentioned, you need to get certificates from a CA for signing. If you previously acquired one for signing Netscape objects, you have to get a different one for signing Microsoft CAB files. Running your applets from the Microsoft's Developer Studio bypasses this requirement during testing. And, if you aren't testing from Visual J++, you can make a test Software Publisher Certificate (SPC) for testing purposes.

For a complete description of generating a testing certificate and signing a CAB file outside of Visual J++, see Microsoft's Signing a Cabinet File with Java Permissions Using Signcode.

Conclusion

This tutorial has shown you many attributes built into Java technology to make it a more secure runtime environment. The built in architecture and the JDK 1.1 Java Cryptography Architecture provides the basis for developing secure and trustworthy applications. The JCA 1.2 capabilities take this even further. With the examples of breaking out of the Java sandbox, you've also seen how you can extend yourself out of the Java sandbox in a controlled manner. You should now understand and be able to create more secure Java programs.

Resources

In addition to the many links within the course text, the following offer additional Java security-related resources.

There are several books that offer various levels of Java security coverage (the last two are not Java specific):

Copyright © 1998 MageLang Institute. All Rights Reserved.

_______
1 As used on this web site, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.


[ This page was updated: 15-Nov-99 ]

Products & APIs | Developer Connection | Docs & Training | Online Support
Community Discussion | Industry News | Solutions Marketplace | Case Studies
Glossary - Applets - Tutorial - Employment - Business & Licensing - Java Store - Java in the Real World
FAQ | Feedback | Map | A-Z Index
For more information on Java technology
and other software from Sun Microsystems, call:
(800) 786-7638
Outside the U.S. and Canada, dial your country's AT&T Direct Access Number first.
Sun Microsystems, Inc.
Copyright © 1995-99 Sun Microsystems, Inc.
All Rights Reserved. Legal Terms. Privacy Policy.