Scott: DECK

Java gives browsers a sophisticated set of tools for protecting us all from applets.  Let's see how they work.

 


Chapter 10: Security In Detail..................................................................................................................

PROPERTIES...................................................................................................................................................................

The Properties File....................................................................................................................................................

Querying Properties..................................................................................................................................................

THE SECURITYMANAGER CLASS..........................................................................................................................

WRITING A SECURITYMANAGER..........................................................................................................................

Selective Access Restriction...................................................................................................................................

A Rogue Agent..........................................................................................................................................................

CONCLUSION...............................................................................................................................................................


Chapter 10: Security In Detail

Security was one of the hardest topics to deal with in this edition because Sun and the browser designers, are still wrestling with the issue themselves.  Within Java itself, security is pretty much a settled issue.  The mechanism for implementing security (the SecurityManager class) is in place.  What is unsettled is how browsers use this mechanism to protect their users.

 

The question is one of risk/reward.  How much risk are users willing to run to enjoy the, as yet undetermined, benefit of running Java applets? 

 

Netscape has taken a very cautious approach to the subject, with the result that applets under Netscape are severely restricted.  The security rules that Netscape applies to applets are:

Applets can not read or write files on the local file system.

Applets can only open network connections to the host that they were loaded from.

When an applet opens a popup window (such as a dialog box), Netscape will warn the user that it’s an applet window.

Properties

Java supports the concept of a “property”.  A property is essentially a global variable that Java programs (applet or application) can read/write via the System.getProperty and System.setProperty methods.  There is a group of “system” properties which exist and have a value in every situation, whether standalone, Netscape, appletviewer or HotJava.  Table 10.1 shows the system properties and what they mean.

Table 10.1: The system properties.

java.class.path  The value of “classpath” - the top level directory under which all classes to be loaded via the primordial classloader will be found.

java.class.version          The version of the Java packages.

java.home         The directory where the Java executables live.

java.vendor       The name of the vendor of this Java interpreter.

java.vendor.url  The URL of the vendors home page.

java.version      The version of Java in use.

file.separator     The character that separates files in a multi-file string.

line.separator    The character sequence that indicates the end of a line in this operating system.

path.separator   The character that separates directories in this operating system.

os.arch The “architecture” of this machine.  For Intel Pentium and 486 this will be “x86”.

os.name           The name of the operating system.  For Windows NT,  this will be “Windows NT”.

os.version         The version of the operating system.

user.dir The current directory.

user.home         The name of the users home directory.

user.name         The user’s login name.

As you can see, most of the system properties deal with the environment the browser operates in - current directory, user name, operating system version ... There can also be any number of “user” properties, essentially private properties that are meaningful only to specific applications or applets.

The Properties File

For HotJava and appletviewer, you can set properties for your computer via the ~/.hotjava/properties file where ~ stands for your home directory.  For reasons of its own, Netscape does not use the properties file.  All properties must be set using dialog boxes within Netscape.

All Windows programmers are familiar with the idea of initialization files such as win.ini.  These files usually contain a series of statements of the form:

key=value

where key is the name of something, and value is the value we want to initialize this something to.  Java supports the same concept in the properties file.  Listing 10.1 shows the properties file I use locally for applet development.

Listing 10.1: My properties file.

#AppletViewer

#Fri Feb 09 12:04:35  1996

firewallSet=false

appletviewer.version=1.0

package.restrict.access.netscape=false

proxySet=false

firewallHost=

package.restrict.access.sun=false

acl.read.applet=true

acl.write.applet=true

acl.read=/temp/

firewallPort=80

appletviewer.security.mode=unrestricted

acl.write=/temp/

As you can see, we define thirteen variables, and set each to some string value.  All of these variables are meaningful to appletviewer, though we could easily define our own variables and set them here manually.  Table 10.2 shows some of the properties that are unique to appletviewer and/or HotJava:

 

Table 10.2: appletviewer and HotJava properties.

awt.toolkit         The package name of the AWT package in use.

acl.read            The directory that applets are allowed to read.  All subdirectories of this directory are readable too.

acl.write            The directory that applets are allowed to write.  All subdirectories are writeable too.

appletviewer.version      The version of the appletviewer.

firewallSet         Set to “true” if we’re behind a firewall.

firewallProxyPort           Set to the http port number ot the firewall proxy.

firewallProxyHost          Set to the hostname of the firewall proxy.

firewallPort        Set to the http port number of the firewall.

firewallHost       Set to the hostname of the firewall.

proxySet           Set to true if we’re using a proxy.

cachingProxyPort          Set to the port number of the caching proxy.

cachingProxyHost         Set to the hostname of the caching proxy.

appletviewer.security.mode        Set to the security mode of the appletviewer.  appletviewer supports restricted and unrestricted class loading.

The most interesting of these, and the most important from a security standpoint, are the acl.read and acl.write properties.  acl stands for Access Control List, and that’s just what these properties are - lists of directories that an applet can access.  As an example, what if our properties file contained the following lines:

acl.read=/home/johnr;/temp;/usr/ajr

acl.write=/temp;/usr/ajr

Applets loaded on a computer that had this properties file would be allowed to read the directories /home/johnr, /temp and /usr/ajr AND all the subdirectories beneath them.  They would be able to write to /temp and /usr/ajr AND all the subdirectories beneath them.  It’s as easy as that.

Querying Properties

Having persistent properties is a wonderful thing, but how do we get at these properties from within an applet?  Listing 10.2 shows a simple applet that tries to get the value of each of these properties.

Listing 10.2: A property reading applet.

package chap10;

 

import java.awt.*;

import java.applet.Applet;

import java.lang.*;

import java.util.*;

 

/** An applet that tries to read and display various

properties.  Displays the properties in a List.

@author John Rodley

@version 1.0

*/

public class ch10_fig1 extends Applet {

 

  Vector props = new Vector(1);

 

/** Set the screen in border layout, put a List in the center

and then display all the properties in the list.

*/

  public void init() {

    setLayout( new BorderLayout());

    List l = new List();

    add( "Center", l );

    props.addElement( new String( "firewallSet" ));

    props.addElement( new String( "appletviewer.version" ));

    props.addElement(

          new String("package.restrict.access.netscape"));

    props.addElement( new String( "proxySet" ));

    props.addElement( new String( "firewallHost" ));

    props.addElement(

          new String("package.restrict.access.sun"));

    props.addElement( new String( "acl.read.applet" ));

    props.addElement( new String( "acl.write.applet" ));

    props.addElement( new String( "acl.read" ));

    props.addElement( new String( "firewallPort" ));

    props.addElement(new String("appletviewer.security.mode"));

    props.addElement( new String( "acl.write" ));

    props.addElement( new String( "xyzabc" ));

 

    for( int i = 0; i < props.size(); i++ ) {

      try {

        l.addItem((String)props.elementAt(i)+

           "="+System.getProperty((String)props.elementAt(i)));

        System.out.println( (String)props.elementAt(i)+

            "="+System.getProperty((String)props.elementAt(i)));

        }

      catch( Exception e ) {

          l.addItem( "Unable to read property "+

                (String)props.elementAt(i));

          System.out.println("Unable to read property "+

                (String)props.elementAt(i));

          }

        }

    }

}

 

In the init method we build a Vector of Strings containing all the variable names we expect to find in the properties file.  Then we run through this Vector calling System.getProperty for each variable name.  If the getProperty call throws an Exception, we catch it and print a message declaring that the property is somehow inaccessible.

If we run this applet against this properties file in appletviewer, we get the application window and standard output shown in Figure 10.1.

Figure 10.1: Output of the property reading applet.

<ch10_fig1.tif>

What happened?  Appletviewer’s SecurityManager goes by the rule that, by default, all properties should be inaccessible to applets.  Thus, our call to getProperty bombs for almost every property in the file.  Notice that even for properties that don’t exist, like the xyzabc property, we get the same reaction from Java, a thrown AppletSecurityException.  This protects the system from applets ferreting out properties by testing the system’s reaction to various words.

Strangely enough, our getProperty call succeeds for acl.read and acl.write.  Why?  Because of the two statements:

acl.write.applet=true

acl.read.applet=true

These two statements tell the appletviewer SecurityManager that the values of acl.read and acl.write can be returned to applets via getProperty.  You can do this with any property simply by appending .applet to the property name.  For instance, to make the property firewallSet visible to applets, we’d add the line:

firewallSet.applet=true

to our properties file.

Another thing to note about Figure 10.1 is that the Exception prints to standard output despite the fact that we catch, and purposely ignore it.  Security Exceptions will always print out at the lowest levels, for reasons that become obvious if you think about the purpose of security.  A SecurityException is meant to indicate that an applet tried something that wasn’t allowed.  If Java allowed applets to “hide” the occurrence of a SecurityException, it would defeat the purpose.  Thus, although we can catch and ignore security Exceptions, we can’t prevent them from generating a notice of their occurrence.

The SecurityManager Class

A standalone Java executable, such as HotJava, implements security by subclassing SecurityManager and attaching an instance of the new class to the System via System.setSecurityManager.  setSecurityManager can only be called once in the life of the VM, so that once a browser sets the SecurityManager, a rogue applet can not reset it.  By the time an applet executes, the browser will always already have set the SecurityManager.  Table 10.3 shows the SecurityManager class.

Table 10.3: The methods of the SecurityManager class.

Helper Methods

Method name    Arguments        Description

classDepth       String   Returns the index into the current class stack where the specified class is.  0 is top of stack.

classLoaderDepth                    

currentClassLoader                    Returns the class loader.  null if the primordial class loader is current.

getClassContext                        Returns an array of Classes that is the list of classes on the stack.

inClass String   Returns true if the String argument is in the Class.

inClassLoader               Returns true if there is a ClassLoader, false if primordial class loader in use.

 

The Network

Method                          Argument

CheckAccept    String, int          Can we accept connections on a socket?

CheckConnect   String, int          Can we connect to the specified network socket?

CheckListen      int         Can we listen to the specified local socket?

 

Threads and Processes

checkAccess    Thread or ThreadGroup  Can the specified Thread/ThreadGroup modify THIS ThreadGroup?

checkExec        String   Can we execute the specified system command?

checkExit          int         Has the system exited the virtual machine?

checkLink         String   Can we use the specified linked library?

 

The File System

checkRead        int or String       Can we read from the specified filename or file descriptor?

checkWrite        int or String       Can we write to the specified filename, or file descriptor?

 

Top-Level Windows

checkTopLevelWindow              Can we create a window with no warning on it?

 

Classes and Class Loading

checkCreateClassLoader                       Can we create a ClassLoader?  

checkPackageAccess   String   Can we use classes from the specified package?

checkPackageDefinition                        Can we define a new package?

 

Properties

checkPropertiesAccess             Can we read the list of properties?

checkPropertyAccess    String   Can we read the value of the specified property?

 

As you can see, there are protective methods guarding each of the resources that is vulnerable to abuse by misbehaved applets - Properties, Class Loading, Windows, Network I/O, File I/O and Thread/Processes.  Let’s look at how the protective methods of SecurityManager guard these resources.

SecurityManager is an abstract class.  An application that implements security, such as a browser, must define its own subclass of SecurityManager.  We can divide the SecurityManager into two big sections: check methods and helper methods.  The helper methods are utilities that security managers will find useful, and which we’ll talk more about later.  As for the check methods, Java puts all the sensitive resources in the system (file I/O, network I/O ...) behind a lockable security door.  The SecurityManager’s check methods are the dead-bolt lock of Java security. 

Whenever the interpreter runs into an instruction that accesses a protected resource, it turns that instruction into two distinct operations:

check if the access in the instruction is allowed

execute the instruction

To check if the access is allowed, the interpreter calls the check method that protects that resource.  If the check method returns, the access is allowed.  If the check method throws a SecurityException, then the next operation (execute the instruction) doesn’t happen because (harking back to Chapter 6) the thrown Exception has broken the flow of execution.  By design, there is nothing we (or any hacker) can do to get around this. 

Writing a SecurityManager

So much for the theory of SecurityManagers.  What does a real SecurityManager look like?  In Listing 10.3, we add a configurable SecurityManager to the AgentServer that predicates access to resources on the state of a simple boolean.

Listing 10.3: The AgentServer’s SecurityManager.

 

/** A class that implements security for the AgentServer.

Brute-force strategy that simply uses a flag for each of the

methods in SecurityManager.  Can configure itself using a

SecurityDialog. Initializes with ALL ACCESS ALLOWED.

*/

class AgentServerSecurityManager extends SecurityManager {

  public static Vector v = new Vector(1);

 

/** Build a vector of SecurityItems, one for each of the check

methods in SecurityManager.  Initialize each SecurityItem to

true (access allowed).  Can be changed later using configure.

*/

  public AgentServerSecurityManager() {

    v.addElement( new SecurityItem( "Accept", true ));

    v.addElement( new SecurityItem( "AccessThread", true ));

    v.addElement( new SecurityItem( "AccessThreadGroup", true ));

    v.addElement( new SecurityItem( "Connect", true ));

    v.addElement( new SecurityItem( "ConnectBoth", true ));

    v.addElement( new SecurityItem( "CreateClassLoader", true ));

    v.addElement( new SecurityItem( "Delete", true ));

    v.addElement( new SecurityItem( "Exec", true ));

    v.addElement( new SecurityItem( "Exit", true ));

    v.addElement( new SecurityItem( "Link", true ));

    v.addElement( new SecurityItem( "Listen", true ));

    v.addElement( new SecurityItem( "PackageAccess", true ));

    v.addElement( new SecurityItem( "PackageDefinition", true ));

    v.addElement( new SecurityItem( "PropertiesAccess", true ));

    v.addElement( new SecurityItem( "PropertyAccess", true ));

    v.addElement( new SecurityItem( "ReadFD", true ));

    v.addElement( new SecurityItem( "ReadName", true ));

    v.addElement( new SecurityItem( "ReadBoth", true ));

    v.addElement( new SecurityItem( "SetFactory", true ));

    v.addElement( new SecurityItem( "Window", true ));

    v.addElement( new SecurityItem( "WriteFD", true ));

    v.addElement( new SecurityItem( "WriteName", true ));

    }

 

/** Change the state of a SecurityItem based on the name of the

item.  This is called for each SecurityItem by the

SecurityDialog when the the OK button is hit.

@param  name  The name of the SecurityItem.

@param  state The state true/false we want to set the

SecurityItem to.

*/

  public void setFlagState( String name, boolean state ) {

    for( int i = 0; i < v.size(); i++ ) {

      SecurityItem si = (SecurityItem)v.elementAt(i);

      if( si.name.compareTo(name) == 0 ) {

        si.state = state;

        break;

        }       

      }

    }

/** Return the true/false state of the named SecurityItem.

@param  name  The name of the item we want to query.

*/

  boolean isSet( String name ) {

    for( int i = 0; i < v.size(); i++ ) {

      SecurityItem si = (SecurityItem)v.elementAt(i);

      if( si.name.compareTo(name) == 0 )

        return si.state;

      }

    return( false );

    }

 

/** Allow the user to configure this security manager by

filling out a SecurityDialog.

*/

  public void configure() {

    dumpContext();

    SecurityDialog sd = new SecurityDialog(AgentServer.f);

    sd.ShowAndLayout();

    }

 

 

/**  Checks to see if a socket connection to the specified

port on the specified host has been accepted.

*/

  public void checkAccept(String host, int port) {

    if( isSet( "Accept" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/** Checks to see if the specified Thread is allowed to

modify the Thread group.

*/

  public void checkAccess(Thread t) {

    if( isSet( "AccessThread" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if the specified Thread group is allowed

to modify this group.

*/

  public void checkAccess(ThreadGroup tg) {

    if( isSet( "AccessThreadGroup" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if a socket has connected to the specified

port on the the specified host.

*/

  public void checkConnect(String host, int port) {

    if( isSet( "Connect" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if the current execution context and the

indicated execution context are both allowed to connect to the

     indicated host and port.

*/

  public void checkConnect(String host, int port, Object o) {

    if( isSet( "ConnectBoth" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if the ClassLoader has been created.

*/

  public void checkCreateClassLoader() {

    if( isSet( "CreateClassLoader" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if a file with the specified system

dependent file name can be deleted.

*/

  public void checkDelete(String filename) {

    if( isSet( "Delete" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if the system command is executed by

trusted code.

*/

  public void checkExec(String cmdname) {

    if( isSet( "Exec" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if the system has exited the virtual

machine with an exit code.

*/

  public void checkExit(int i) {

    if( isSet( "Exit" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if the specified linked library exists.

*/

  public void  checkLink(String libname) {

    if( isSet( "Link" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if a server socket is listening to the

specified local port that it is bounded to.

*/

  public void  checkListen(int port) {

    if( isSet( "Listen" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if an applet can access a package.

*/

  public void  checkPackageAccess(String pkgname) {

    if( isSet( "PackageAccess" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if an applet can define classes in a

package.

*/

  public void  checkPackageDefinition(String pkgname)  {

    if( isSet( "PackageDefinition" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see who has access to the System properties.

*/

  public void  checkPropertiesAccess() {

    if( isSet( "PropertiesAccess" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see who has access to the System property

named by key.

*/

  public void  checkPropertyAccess(String property) {

    if( isSet( "PropertyAccess" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see who has access to the System property

named by key and def.

*/

  public void  checkPropertyAccess(String property, String s) {

    if( isSet( "PropertyAccess" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if an input file with the specified file

descriptor object gets created.

*/

  public void  checkRead(FileDescriptor fd) {

    if( isSet( "ReadFD" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if an input file with the specified

system dependent file name gets created.

*/

  public void  checkRead(String filename) {

    if( isSet( "ReadName" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if the current context or the indicated

context are both allowed to read the given file name.

*/

  public void  checkRead(String filename, Object o) {

    if( isSet( "ReadBoth" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if an applet can set a

networking-related object factory.

*/

  public void  checkSetFactory() {

    if( isSet( "SetFactory" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if top-level windows can be created by

the caller.

*/

  public boolean  checkTopLevelWindow(Object o) {

    if( isSet( "Window" ))

        return true;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if an output file with the specified

file descriptor object gets created.

*/

  public void  checkWrite(FileDescriptor fd)  {

    if( isSet( "WriteFD" ))

        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if an output file with the specified

system dependent file name gets created.

*/

public void  checkWrite(String filename)  {

     if( isSet( "Write" ))

        return;

     throw new AgentServerSecurityException();

   }

 

  }

 

/** A class representing a violation of the AgentServer's

security strategy.

*/

class AgentServerSecurityException

                    extends SecurityException {

  public AgentServerSecurityException() {

    super( "AgentServerSecurityException" );

    }

  }

 

/** A class for holding the name and current state of a

Security property. 

*/

class SecurityItem {

/** The name of the resource this security item is protecting. */

  public String name;

/** Set to true if the access to this resource is ALLOWED. */

  public boolean state;

 

/** constructor - force the user to initialize the name and

state of this property.

@param  label The name of this property.

@param  initState The initial state of this property.

*/

  public SecurityItem( String label, boolean initState ) {

    name = new String( label );

    state = initState;

    }

  }

 

/** A modal dialog box that allows the user to set the security

strategy for the AgentServer.

@version 1.0 1/4/1996

@author John Rodley

*/

class SecurityDialog extends Dialog {

     int selectIndex = -1;

     Frame parent;

     Panel ButtonPanel;

     public boolean bFinished = false;

  Vector cbV = new Vector(1);

 

/** Constructor.

*/

  public SecurityDialog(Frame p) {

     super(p, "Setup AgentServer Security", true);

       parent = p;

 

    // Set up all the graphical elements

    // Split the dialog main panel into three elements, top,

    // bottom and middle, via the BorderLayout.  The top and

    // bottom size themselves according to the preferred sizes

    // of the text on the top and the buttons on the bottom.

    // The Center panel, which the List fills, uses all the

    // space left in the middle.

       setLayout(new BorderLayout());

       ButtonPanel = new Panel();

     add( "South", ButtonPanel );

 

    Panel checkPanel = new Panel();

    checkPanel.setLayout( new GridLayout( 8, 3 ));

 

    Vector v = AgentServerSecurityManager.v;

    for( int i = 0; i < v.size(); i++ ) {

      Checkbox cb = new Checkbox(

        ((SecurityItem)v.elementAt(i)).name,

        null, ((SecurityItem)v.elementAt(i)).state);

      checkPanel.add( cb );

      cbV.addElement(cb );

      }

 

     add( "Center", checkPanel );   

 

       Button okbutton = new Button("OK");

     ButtonPanel.add( okbutton );

     Button cancelbutton = new Button( "Cancel" );

     ButtonPanel.add( cancelbutton );

     }

 

 

/** Size the dialog to something appropriate, then make it

non-resizeable so that users don't go resizing it themselves.

*/

  public void ShowAndLayout() {

     show();

     resize( 600, 300 );

     layout();

     setResizable(false);

     }

 

/** Process the OK and cancel buttons.  This also gets called

every time the user changes the state of one of our Checkboxes

so we explicitly ignore that case.  When OK is hit, roll

through the SecurityItem Vector setting the state of each of

the items based on the checkbox state.

*/

public boolean action(Event e, Object o) {

 

  if( e.target instanceof Checkbox ) {

    return false;

    }

     if( e.target instanceof Button ) {

    if( ((Button)e.target).getLabel().compareTo("OK") == 0 ) {

      for( int i = 0; i < cbV.size(); i++ ) {

        AgentServer.assm.setFlagState(

          ((Checkbox)cbV.elementAt(i)).getLabel(),

            ((Checkbox)cbV.elementAt(i)).getState());

        }

      }

           }

     bFinished = true;

     dispose();

     return true;

     }

}

 

In Listing 10.3, we define a class, SecurityItem, that holds the name of the resource, and a boolean, state, that tells whether access to the resource is allowed or not.  In the AgentServerSecurityManager constructor, we build a Vector of these SecurityItems, one for each resource, and set its initial state.  Each check method merely reads the current state of “its” SecurityItem, returning if the access is allowed, or throwing an Exception if the access is not allowed.  To facilitate our use of SecurityItems, we define two of our own methods, isSet and setFlagState, which allow us to set, and check the state of a SecurityItem based on its name. 

All these “locks” would be useless without some way to turn them on and off. Thus, we define a dialog box, SecurityDialog that simply allows us to set, or clear each of the SecurityItem.state booleans.

Now we have all the pieces of a brute-force security solution, we have to link them into the AgentServer.  Listing 10.4 shows the AgentServer modifications we make to setup our SecurityManager, as well as to call our SecurityDialog.

Listing 10.4: AgentServer mods to add SecurityManager and SecurityDialog.

public AgentServer() {

  ...

  m = new MenuBar();

  f.setMenuBar( m );

  Menu m1 = new Menu("File");

  m.add(m1);

  MenuItem m2 = new MenuItem( "Load test" );

  m1.add( m2 );

  m2 = new MenuItem( "Exit" );

  m1.add( m2 );

  m1 = new Menu("Options");

  m.add(m1);

  m2 = new MenuItem( "Configure" );

  m1.add( m2 );

  ...

  }

 

 

/** Handle any menu item picks.  Right now, only has two, Exit

and Load Test.  Exit sets the agentServers bRun flag to false,

causing the AgentServer.run to fall out of the endless while

loop.

@see AgentServer

*/

public boolean action( Event evt, Object o ) {     

  if( evt.target instanceof MenuItem )

    {

    if( evt.arg.toString().compareTo( "Exit" ) == 0 )

      {

      AgentServer.currentAgentServer.bRun = false;

      System.out.println( "action event "+evt );

      }

    else

      {

      if(evt.arg.toString().compareTo( "Load test" ) == 0 )

        {

        AgentServer.currentAgentServer.LoadTest();

        System.out.println( "action event "+evt );

        }

      else

        {

        if(evt.arg.toString().compareTo( "Configure" ) == 0 )

          {

          AgentServer.assm.configure();

          System.out.println( "action event "+evt );

          }

        }

      }

    }

  return( true );

  }

 

 

Up in AgentServer’s static main method, we instantiate our SecurityManager, then call System.setSecurityManager to make our SecurityManager the one, and only one, that Java will use for this application.  Once invoked, setSecurityManager can never be called again for the life of the application.  Once we’ve set our SecurityManager, Java will begin automatically calling its check methods whenever any security issues arise.

Down in the AgentServer constructor we add a new menu, Options, and a menu item, Configure.  When configure is selected, action will call AgentServerSecurityManager.configure which brings up our dialog box.

If you look at our check methods, they’re all fairly monotonous, simply throwing an Exception if the flag they depend on is clear.  This is simple, and effective, but it’s also far too broad to be useful to the AgentServer, or any browser for that matter.

Say you want to restrict Agents from making their own network connections.  In our current security setup, you’d open the security dialog and clear the “Connect” checkbox.  All well and good, so far.  The problem arises when you try to run our FileFinder agent with this flag cleared.  Figure 10.2 shows the AgentServers standard output when we run a FileFinder agent with connect “disallowed”.

Figure 10.2: FileFinder running with connect disallowed.

<ch10_fig2.tif>

As you might remember from Chapter 9, the FileFinder doesn’t do any direct network communication.  The problem is that our checkConnect method doesn’t distinguish the AgentServer itself making connections (which is allowed), from the Agent making connections (which is not allowed). 

Selective Access Restriction

Another security issue along the same lines is file I/O.  Way back in Chapter 3, we indicated that we might want to disallow Agents from doing direct file I/O.  Let’s give it a try.  In Listing 10.5, we implement checkWrite methods that distinguish between the AgentServer and the Agent, disallowing direct Agent writes, but allowing AgentServer writes, and Agent writes via the AgentContext interface.

Listing 10.5: The checkWrite methods, and their helper methods.

/**     Checks to see if an output file with the specified

file descriptor object gets created.

*/

  public void  checkWrite(FileDescriptor fd)  {

if( isAgent()) {

      if( isAgentGoingThroughAgentContext())

        return;

      }

    else

      return;

//    if( isSet( "WriteFD" ))

//        return;

    throw new AgentServerSecurityException();

    }

 

/**     Checks to see if an output file with the specified

system dependent file name gets created.

*/

  public void  checkWrite(String filename)  {

    if( isAgent()) {

      if( isAgentGoingThroughAgentContext())

        return;

      }

    else

      return;

    throw new AgentServerSecurityException();

    }

 

  }

/** Return true if Agent is on the stack.  This means we were

called from somewhere within an Agent.

@return true if an instance of Agent is somewhere on the stack.

*/

boolean isAgent() {

  Class c[] = getClassContext();

  for( int i = 0; i < c.length; i++ ) {

    if( subclassesAgent( c[i] )) {

      int AgentIndex = classDepth( c[i].getName() );     

      if( AgentIndex >= 0 )

        return( true );

      }

    }

  return( false );

  }

 

/** Return true if the specified class subclasses

agent.Agent.Agent.  All agents run over the net should subclass

Agent.  Runs through all the superclasses of c checking their

name against agent.Agent.Agent.

@param  c The class we're inquiring about.

@return true if c is a subclass of Agent, false otherwise.

*/

boolean subclassesAgent( Class c ) {

  Class cNext = c;

  while( true ) {

    Class c1 = cNext.getSuperclass();

    if( c1 == null )

      break;

    if( c1.getName().compareTo( "agent.Agent.Agent" ) == 0 )

      return( true );

    cNext = c1;

    }

  return( false );

  }

 

/** Return true if the Agent is calling us through the

AgentContext. false otherwise.  Accomplish this by looking at

the stack.  If the "classDepth" of the Agent is lower than that

of the AgentContext, or if the AgentContext isn't on the stack,

then the Agent is trying to go around us. 

*/

boolean isAgentGoingThroughAgentContext() {

  Class c[] = getClassContext();

  for( int i = 0; i < c.length; i++ ) {

    if( subclassesAgent( c[i] )) {

      int AContextIndex = classDepth( "agent.Server.SepContext" );

      int AgentIndex = classDepth( c[i].getName() );     

      // This is where we should catch an agent calling

      // File.write directly.

      if( AContextIndex < 0 )

        return( false );

 

      // This should never happen.  Indicates SepContext

      // calling back to Agent!!

      if( AgentIndex < AContextIndex )

        return( false );

      else

        return( true );

      }

    }

  System.out.println( "NO AGENT!!" );

  return( true );

  }

 

The checkWrite methods are fairly simple, in themselves.  First, we call isAgent, which tells us whether this call emanates from an instance of Agent.  If the call doesn’t come from an Agent, we allow it.  If the call does come from an Agent, then we call isAgentGoingThroughAgentContext, to see if the call goes through the AgentContext.  If it is, we allow the call.  If not, we throw an Exception.  Pretty easy huh?

The real work here is done by the two methods: isAgent and isAgentGoingThroughAgentContext.  isAgent gets the list of Classes on the stack by calling SecurityManager.getClassContext.  This gives us an array of Classes.  We go through these classes one by one, checking to see if they subclass Agent.  If any subclass of Agent is on the stack, then we were called, somehow, by Agent and we return tru. 

At this point, we know that an instance of Agent caused this call to checkWrite.  That’s all we know.  It could have been caused by a direct call to FileOutputStream.write, or it could have been some more convoluted invocation chain.  What we need to know now is whether the AgentContext was between the invocation by Agent and the call to writeCheck.  This is where isAgentGoingThroughAgentContext comes in.  Like isAgent, we get the list of Classes on the stack from getClassContext.  Then we run through the array looking for the Class that subclasses Agent.  When we find it, we SecurityManager.classDepth to find out where on the stack the AgentContext and the Agent reside.  As methods call each other, Java builds a stack of classes.  At the top of the stack (index 0) is the class you’re in when you look at the stack.  At index 1 is the class whose method called the current class, and so on ...  So, when we call classDepth, at the top of the stack (index 0) is AgentServerSecurityManager.  Somewhere further down (index 6 in the upcoming example) is our subclass of Agent.  All we need to know now is whether there’s an AgentContext on the stack between our Agent and our SecurityManager.  So, we compare the classDepth of our Agent against the classDepth of our AgentContext (SepContext).  If there is no AgentContext (AContextIndex < 0) or its classDepth is greater than the Agents, we return false (operation disallowed).

The only method we haven’t talked about is subclassesAgent.  This takes a class and determines whether that class subclasses agent.Agent.Agent.  We do this by repeatedly calling getSuperclass until we reach the base class and getSuperclass returns null.  We compare each superclass name to agent.Agent.Agent.

A Rogue Agent

Now we have a super-duper SecurityManager that implements a fairly sophisticated bit of security.  How do we test it?  What we need is a new Agent, one that doesn’t play by the rules.  Listing 10.6 shows EvilAgent, a modification of Chapter 9’s FileFinder designed specifically to provoke our new security mechanism.

Listing 10.6: The EvilAgent.

package agent.EvilAgent;

 

import java.lang.*;

import java.util.*;

import java.awt.*;

import java.io.*;

import agent.Agent.*;

import agent.EvilAgent.*;

// To catch the definition of AgentContext

import agent.Server.AgentContext;    

 

/** An Agent designed to provoke the SecurityManager on the

AgentServer by trying to write directly to a file rather than

through the AgentContext.  A demonstration of the capabilities

of the SecurityManager class.

@version 1.1

@author John Rodley

*/

public class EvilAgent extends Agent {

     ConfigurationDialog cfd;

     Vector args;

 

/** Constructor - does nothing by design, but it's useful to

leave the println in there just to convince yourself that the

Agent has been instantiated on the AgentServer.

*/

     public EvilAgent() {

           System.out.println( "EvilAgent constructor" );

           }

 

/** Put up a ConfigurationDialog that gets the arguments this

Agent needs to run on an AgentServer.

@param  frame The frame window of the browser, needed for the

dialog constructor.

*/

     public void configure( Frame frame ) {

           cfd = new ConfigurationDialog( frame );

           cfd.show();

           }

 

/** Return whatever arguments the configure method got from the

user as a Vector of Strings.

@return A Vector of Strings that are only meaningful to the

Agent itself, not to either the AgentLauncher or AgentServer.

*/

     public Vector getArguments() {

           return( cfd.args );          

           }

 

/** Configure the Agent with the specified Vector of Strings as

'arguments'. Called by the AgentLauncher, passing the arguments

it pried out of the LoadMessage.

@param  ar  A Vector of Strings identical to the one returned

to the AgentLauncher by getArguments. 

*/

     public void setArguments( Vector ar ) {

           args = ar;

           }

 

/** The run loop for this Agent.  Gets the top-level directory

which this Agent is allowed to read from the properties file

via the key acl.read, and checks all the files in that

directory against the filenamefilter specified by the user back

on the AgentLauncher.

*/

     public void run() {

           String topDirectory = System.getProperty( "acl.read" );

           if( topDirectory == null ) {

                System.out.println( "can't read this machine" );

                return;

                }

           System.out.println( "got value "+topDirectory

                      +" for property acl.read" );

           boolean keepGoing = true;

           String currentDirectory = new String(topDirectory);

           ac.reportStart( "" );

           ac.writeOutput(

  "<HTML><HEAD><TITLE>FileFinderOutput</TITLE></HEAD><BODY>" );

           String writeDirectory = System.getProperty( "acl.write" );

           if( writeDirectory == null ) {

                System.out.println( "can't write this machine" );

      return;

      }

 

    // Here is where we stop acting like the FileFinder Agent

    // and start misbehaving.

    System.out.println( "Creating "+

                writeDirectory+"EvilFile.txt" );

    try {

      // This is the call that provokes the

      // AgentServerSecurityManager into excepting

      FileOutputStream fos =

          new FileOutputStream(writeDirectory+"/EvilFile.txt");

      byte b[] = new byte[20];

      fos.write( b );

      }

    catch( IOException e )

      { System.out.println( "Bad file io "+e ); }

           }

  }

 

/** A dialog box for configuring a FileFinder Agent.  Allows

the user to enter up to 7 filenames to search for.

@see Dialog

*/

class ConfigurationDialog extends Dialog {

  Label theLabel;

  Button theButton;

  TextField tf[] = new TextField[7];

  Panel ButtonPanel;

  Panel TextFieldPanel;

  public Vector args;

 

/** constructor create a dialog with a certain title, lay it

out border style, add a prompt, 7 TextFields for entering the

file specs and OK and Cancel buttons.

@param  parent  The Frame that is the parent of this dialog.

*/

  public ConfigurationDialog(Frame parent) {

     super(parent, "Configure File Finder", true);

       setLayout(new BorderLayout());

     theLabel = new Label( "Enter up to 7 file specifications:" );

       add("North",theLabel);

     TextFieldPanel = new Panel();

       TextFieldPanel.setLayout( new GridLayout(7, 1 ));

     add("Center", TextFieldPanel );

       for( int i = 0; i < 7; i++ ) {

             tf[i] = new TextField( "", 25 );

           TextFieldPanel.add( tf[i] );

          }

     Dimension d = tf[0].preferredSize();

     ButtonPanel = new Panel();

       add( "South", ButtonPanel );

     theButton = new Button( "Ok" );

       ButtonPanel.add( theButton );

     setResizable(false);

    }

 

/** Deal with the user hitting either OK or Cancel.  In either

case, fill the argument Vector with whatever's in the

TextFields and dispose of the dialog.

*/

  public boolean action(Event e, Object o) {

     if( e.target instanceof Button )

          {

           args = new Vector(1);

          for( int i = 0; i < 7; i++ ) {

                  if( tf[i].getText().length() > 0 &&

                  (tf[i].getText().compareTo("") != 0 ))

                     {

                     byte b[] = new byte[tf[i].getText().length()];

                      tf[i].getText().getBytes( 0, b.length, b, 0 );     

                     args.addElement( b );

                     }

                }

             }

     dispose();

       return true;

     }

     }

 

FileFinder’s well-behaved run method has been replaced by a run method that tries to open a file and write directly to it.  The run method starts out well-behaved, reporting its startup via reportStart, and writing output to the results file via writeOutput.  This write works because it goes through the AgentContext.  Then EvilAgent gets into trouble.  He reads the acl.write property just to get a good directory to write into.  Then he tries to open a FileOutputStream in that directory.  This causes a call to writeCheck.  writeCheck discovers that there’s no AgentContext on the stack (classDepth returns -1) and throws an Exception.  Figure 10.3 shows the screen and standard output when our EvilAgent tries to run on an AgentServer.

Figure10.3: The EvilAgent meets his match.

<ch10_fig3.tif>

As you can see from the debugging output, the FileOutputStream constructor, in EvilAgent.run called checkWrite, which threw an AgentServerSecurityException.  The illegal output file was never opened, and the run method terminated.

Notice that the EvilAgent’s call to AgentContext.writeOutput did not throw an Exception even though it caused a call to the same FileOutputStream constructor (via AgentContext)!  The SecurityManager detected the AgentContext interface and allowed the file write on that call, but noticed the lack of an AgentContext in the misbehaved write call and threw an Exception.

This file-write prohibition is a good start at a security strategy.  A full-featured version would at least put a leash on network I/O and local program execution.  However, using the techniques we’ve developed here, we can quickly add any other bits of security we might need.

Conclusion

Java applet security is a work-in-progress.  In many cases, applet security restrictions are so severe that applets need to connect to a server daemon to get any useful work done at all.  In this chapter, we’ve seen how applets are restricted, and how they can tell what those restrictions are by querying the system properties. 

We’ve seen the mechanism, the SecurityManager class, that browsers use to protect the system against rogue applets.  We’ve implemented our own SecurityManager to protect the AgentServer from rogue Agents in the same way that browsers protect themselves from rogue applets.  And within that SecurityManager, we’ve implemented one piece of a more sophisticated security strategy that rivals some of the mechanisms that browsers themselves implement.