Chapter 22: Advanced Security

Jini from the beginning has used the Java security model to control the actions of proxies when they are run from clients. However, this does not control security of the transport aspects of remote method calls. Jini 2.0 introduced a wide-ranging set of mechanisms largely aimed at such issues as encryption, authorisation, etc

22.1. Introduction

Prior to version 2.0, Jini just used the standard Java security mechanism. This was designed to deal with code downloaded from a remote location, and was put in place to limit what foreign code could be in a local virtual machine. This is a capability-based model, in which the client grants to foreign code the capability of performing certain activities. So for example, foreign code cannot write to the local file system unless this permission has been granted. This was covered in detail on the chapter on Basic Security.

What this has ignored is a range of issues concerning the network transport. For example

  1. Integrity: have the classes and instance data reached the client in the form they started, or has someone along the way corrupted them?

  2. Authentication: did the data come from whom you expected, or did it come from someone else? The client may need to authenticate itself to the server, or vice-versa

  3. Authorisation: once you know who it came from, what rights will you grant it? This was covered in the Basic Security chapter

  4. Confidentiality: has the data been encrypted so that others cannot read it?

These are standard network security concerns. However, Jini gives them some special wrinkles. For example, instance data for a proxy may be sent from a server to a client either directly or via a lookup server. In addition to the instance data, class files often have to be loaded, and these may come from a third party HTTP server. There are even subtleties in where a service may be running: an activatable service doesn't run in the server that started it, but in a third party activation service.

22.2. Invocation constraints

The security considerations act as constraints on normal execution: something that might have been allowed will be restricted. For example, a client may put the constraint that communications be encrypted. It might not want to know many details of how this has been done (that sort of detail can be left to the middleware itself). But if it hasn't been done, then it will just not accept the communication.

Jini 2.0 defines a set of objects that specify constraints on behaviour. They don't specify how a constraint is implemented, just what the constraint is. Some of these are

  1. Integrity.YES: Detect when message contents (both requests and replies) have been altered by third parties, and if detected, refuse to process the message and throw an exception

  2. Integrity.NO: Do not detect when message contents have been altered by third parties

  3. In between YES and NO is DON'T CARE. There is no specific object to express this constraint (or lack of it). If you don't care whether it is checked or not, then you don't specify either a YES or NO constraint - you just don't mention the constraint at all

  4. Confidentiality.YES: Transmit message contents so that they cannot easily be interpreted by third parties (typically by using encryption)

  5. Confidentiality.NO: Transmit message contents in the clear (no use of encryption)

  6. Similarly, in between YES and NO is DON'T CARE. There is no specific object to express this constraint (or lack of it). You just don't use either the YES or NO object to mean that you don't care if it is confidential or not - this is common to all constraints

  7. ClientAuthentication.YES: The client must authenticate to the server

  8. ClientAuthentication.NO: Do not authenticate the client to the server. This has a special meaning in that the client will refuse to say who it is: the client remains anonymous. This may be important for applications where participants wish to preserve their privacy

  9. ServerAuthentication.YES : Authenticate the server to the client

  10. ServerAuthentication.NO : Do not authenticate the server to the client, so that the server remains anonymous

The JavaDoc for InvocationConstraint lists all its subclasses, and within each of these will be constant objects such as the above.

Each InvocationConstraint can potentially limit client or server activity. We can make up two sets of constraints: mandatory constraints that must be satisified and preferences that should be satisfied if they do not conflict with a mandatory one. For eaxmple, if both Integrity.YES and Integrity.No are specified as mandatory then any call must fail. If however one is specified as mandatory and the other as preferred, then the mandatory one must be satisfied.

An InvocationConstraints (note the plural) takes a set of mandatory and a set of preferred constraints. These can be specified as collections or arrays to a constructor.


class InvocationConstraints {
    InvocationConstraints(Collection reqs, Collection prefs);
    InvocationConstraints(InvocationConstraint[] reqs, 
                          InvocationConstraint[] prefs);
    InvocationConstraints(InvocationConstraint req, 
                          InvocationConstraint pref);

    ...
} 
      

22.3. Method constraints

Whenever a method call is made, constraint checks should be made. For example, a bank method withdraw() should always authenticate the caller. It should be done on each call, not just once - a certificate that is valid for one call may not be valid the next time a call is made.

The MethodConstraints interface allows each method to have its own set of constraints: for example a method that sends credit card details might require encryption whereas a "browse" request would not. A BasicMethodConstraints class is usually used as implementation of this interface. In this all methods can be set to use the same set of constraints or it can be set up on a per-method basis.


class BasicMethodConstraints {
     BasicMethodConstraints(InvocationConstraints constraints);
     BasicMethodConstraints(BasicMethodConstraints.MethodDesc[] descs); 

     ...
}
      

22.4. Logging

It is difficult to get security right, and hard to debug. The Logger is your friend here (see the chapter on Logging). Security is handled by the net.jini.security.Security and this writes to three loggers

  1. net.jini.security.integrity

  2. net.jini.security.trust

  3. net.jini.security.policy

Sample code to get logging information from the client is


    static final String TRUST_LOG = "net.jini.security.trust";
    static final String INTEGRITY_LOG = "net.jini.security.integrity";
    static final String POLICY_LOG = "net.jini.security.policy";

    static final Logger trustLogger = Logger.getLogger(TRUST_LOG);
    static final Logger integrityLogger = Logger.getLogger(INTEGRITY_LOG);
    static final Logger policyLogger = Logger.getLogger(POLICY_LOG);

    private static FileHandler trustFh;
    private static FileHandler integrityFh;
    private static FileHandler policyFh;

    private static void installLoggers() {
	try {
	    // this handler will save ALL log messages in the file
	    trustFh = new FileHandler("log.client.trust.txt");
	    integrityFh = new FileHandler("log.client.integrity.txt");
	    policyFh = new FileHandler("log.client.policy.txt");

	    // the format is simple rather than XML
	    trustFh.setFormatter(new SimpleFormatter());
	    integrityFh.setFormatter(new SimpleFormatter());
	    policyFh.setFormatter(new SimpleFormatter());

	    trustLogger.addHandler(trustFh);
	    integrityLogger.addHandler(integrityFh);
	    policyLogger.addHandler(policyFh);

	    trustLogger.setLevel(java.util.logging.Level.ALL);
	    integrityLogger.setLevel(java.util.logging.Level.ALL);
	    policyLogger.setLevel(java.util.logging.Level.ALL);
	} catch(Exception e) {
	    e.printStackTrace();
	}
    }
      

22.5. Protocols

There is a range of different protocols that can be used to shift data around the network. These include TCP and HTTP (which of course is layered above TCP), and those designed with security in mind such as HTTPS, SSL (now officially TLS) and others.

22.5.1 TCP

A service uses TCP by using a BasiJeriExporter with TCP server. Typically the exporter will be defined in a configuration file such as


	import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;

security.FileClassifierServer {

    /* class name for the service */
    serviceName = "rmi.FileClassifierImpl";

    exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
                                     new BasicILFactory()); 
}

      

TCP does not support any of the security mechanisms of this chapter. So we use it as a "bad example" once and then no longer consider it.

22.5.2 SSL

The server can use Jeri over SSL, by a configuration such as config/security/jeri-ssl-minimal.config


	  /* Configuration source file for an SSL server */

import java.security.Permission;

import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Integrity;
import net.jini.jeri.*;
import net.jini.jeri.ssl.*;

security.FileClassifierServer {

    /* class name for the service */
    serviceName = "rmi.FileClassifierImpl";

    /* Exporter for the server proxy */
    exporter =
	/* Use secure exporter */
	new BasicJeriExporter(
	    /* Use SSL transport */
	    SslServerEndpoint.getInstance(0),
            new BasicILFactory(
		/* Require integrity for all methods */
		new BasicMethodConstraints(
		    new InvocationConstraints(
				(InvocationConstraint[]) null, 
				(InvocationConstraint[]) null)),
		/* No Permission */
		null
            )
        );
}

	
SSL is designed to support encryption using a secret key mechanism following open negotiation. It can also support authentication of both client and server using public key certificates.

22.6. Proxy preparer

When a client gets a proxy from a server, the server may already have placed some constraints on it. But any of these constraints are those that the server requires, not those that the client may require. So the client has to set its own constraints on the service proxy. It does this by creating a new proxy from the original by adding in its own constraints. The is described by both an interface and sample implementations. The interface is ProxyPreparer


interface ProxyPreparer {
   Object prepareProxy(Object proxy)
                    throws RemoteException;
}
      
and an implementation is BasicProsyPreparer

class BasicProxyPreparer {
    BasicProxyPreparer();
    BasicProxyPreparer(boolean verify, 
                       MethodConstraints methodConstraints, 
                       Permission[] permissions);
    BasicProxyPreparer(boolean verify, 
                       Permission[] permissions);
} 
      
The second constructor is the one most likely to be used by a client: get a proxy from a service, create a basic proxy preparer with constraints and permissions (and whether or not to verify the proxy - see later) and use this to prepare a new proxy with the constraints and permissions. The new proxy is then used for all calls on the service.

A client that finds a file classifier and prepares a new service proxy, taking the proxy preparer from a configuration file is


	
package client;

import common.FileClassifier;
import common.MIMEType;

import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;

import java.rmi.RemoteException;

import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;

import java.util.logging.*;

/**
 * TestFileClassifierProxyPreparer.java
 */

public class TestFileClassifierProxyPreparer implements DiscoveryListener {

    private Configuration config;

    static final String TRUST_LOG = "net.jini.security.trust";
    static final String INTEGRITY_LOG = "net.jini.security.integrity";
    static final String POLICY_LOG = "net.jini.security.policy";
    static final Logger trustLogger = Logger.getLogger(TRUST_LOG);
    static final Logger integrityLogger = Logger.getLogger(INTEGRITY_LOG);
    static final Logger policyLogger = Logger.getLogger(POLICY_LOG);
    private static FileHandler trustFh;
    private static FileHandler integrityFh;
    private static FileHandler policyFh;


    public static void main(String argv[]) 
	throws ConfigurationException {

	installLoggers();

	new TestFileClassifierProxyPreparer(argv);

        // stay around long enough to receive replies
        try {
            Thread.currentThread().sleep(100000L);
        } catch(java.lang.InterruptedException e) {
            // do nothing
        }
    }

    public TestFileClassifierProxyPreparer(String[] argv) 
	throws ConfigurationException {
	config = ConfigurationProvider.getInstance(argv);

	System.setSecurityManager(new RMISecurityManager());

	LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
            System.exit(1);
        }

        discover.addDiscoveryListener(this);

    }

    private static void installLoggers() {
	try {
	    // this handler will save ALL log messages in the file
	    trustFh = new FileHandler("log.client.trust.txt");
	    integrityFh = new FileHandler("log.client.integrity.txt");
	    policyFh = new FileHandler("log.client.policy.txt");

	    // the format is simple rather than XML
	    trustFh.setFormatter(new SimpleFormatter());
	    integrityFh.setFormatter(new SimpleFormatter());
	    policyFh.setFormatter(new SimpleFormatter());

	    trustLogger.addHandler(trustFh);
	    integrityLogger.addHandler(integrityFh);
	    policyLogger.addHandler(policyFh);

	    trustLogger.setLevel(java.util.logging.Level.ALL);
	    integrityLogger.setLevel(java.util.logging.Level.ALL);
	    policyLogger.setLevel(java.util.logging.Level.ALL);
	} catch(Exception e) {
	    e.printStackTrace();
	}
    }

    public void discovered(DiscoveryEvent evt) {

        ServiceRegistrar[] registrars = evt.getRegistrars();
	Class [] classes = new Class[] {FileClassifier.class};
	FileClassifier classifier = null;
	ServiceTemplate template = new ServiceTemplate(null, classes, 
						       null);
 
        for (int n = 0; n < registrars.length; n++) {
	    System.out.println("Lookup service found");
            ServiceRegistrar registrar = registrars[n];
	    try {
		classifier = (FileClassifier) registrar.lookup(template);
	    } catch(java.rmi.RemoteException e) {
		e.printStackTrace();
		System.exit(4);
		continue;
	    }
	    if (classifier == null) {
		System.out.println("Classifier null");
		continue;
	    }

	    System.out.println("Getting the proxy");
	    // Get the proxy preparer
	    ProxyPreparer preparer = null;
	    try {
		preparer = 
		(ProxyPreparer) config.getEntry(
						"client.TestFileClassifierProxyPreparer",
						"preparer", ProxyPreparer.class,
						new BasicProxyPreparer());
	    } catch(ConfigurationException e) {
		e.printStackTrace();
		preparer = new BasicProxyPreparer();
	    }

	    // Prepare the new proxy
	    System.out.println("Preparing the proxy");
	    try {
		classifier = (FileClassifier) preparer.prepareProxy(classifier);
	    } catch(RemoteException e) {
		e.printStackTrace();
		System.exit(3);
	    } catch(java.lang.SecurityException e) {
		e.printStackTrace();
		System.exit(6);
	    }

	    // Use the service to classify a few file types
	    System.out.println("Calling the proxy");
	    MIMEType type;
	    try {
		String fileName;

		fileName = "file1.txt";
		type = classifier.getMIMEType(fileName);
		printType(fileName, type);

		fileName = "file2.rtf";
		type = classifier.getMIMEType(fileName);
		printType(fileName, type);

		fileName = "file3.abc";
		type = classifier.getMIMEType(fileName);
		printType(fileName, type);
	    } catch(java.rmi.RemoteException e) {
		System.out.println("Failed to call method");
		System.err.println(e.toString());
		System.exit(5);
		continue;
	    }
	    // success
	    System.exit(0);
	}
    }

    private void printType(String fileName, MIMEType type) {
	System.out.print("Type of " + fileName + " is ");
	if (type == null) {
	    System.out.println("null");
	} else {
	    System.out.println(type.toString());
	}
    }

    public void discarded(DiscoveryEvent evt) {
	// empty
    }
} // TestFileClassifier

      
A minimal configuration file for this client is config/security/preparer-minimal.config

	

import java.security.Permission;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Integrity;
import net.jini.security.BasicProxyPreparer;
import net.jini.constraint.BasicMethodConstraints;

client.TestFileClassifierProxyPreparer {


   preparer =
        new BasicProxyPreparer(
            /* Don't verify the proxy. */
            false,
            /* No constraints */
            new BasicMethodConstraints(
                new InvocationConstraints(
                    (InvocationConstraint[]) null,
                    (InvocationConstraint[]) null
                )
            ),
            new Permission[] {}
        );
}

      
This can be run directly by

	java ... client.TestFileClassifierProxyPreparer \
                 config/security/preparer-minimal.config
      
or from the Ant build files by

	ant run -DrunFile=client.TestFileClassifierProxyPreparer \ 
                -Dconfig=config/security/preparer-minimal.config
      

This client will run successfully with any service that does not impose any constraints on the client. So, for example, it will run with any service of earlier chapters which do not impose any contraints at all. However, using this configuration it will not run with some of the examples later in this chapter which do impose client-side constraints.

22.7. File classifier server

A file classifier server using configuration has already been given in the chapter Configuration. The version here is almost the same, with the addition of placing the service name in the configuration (since we might need to run different versions of the service for different security requirements).


	package config;

import java.rmi.RMISecurityManager;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.ExportException;

import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lease.Lease;
import net.jini.core.lookup.ServiceID ;
import net.jini.lease.LeaseListener;             
import net.jini.lease.LeaseRenewalEvent;         
import net.jini.lease.LeaseRenewalManager;       
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;
import net.jini.lookup.JoinManager;
import net.jini.id.UuidFactory;
import net.jini.id.Uuid;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.export.Exporter;

import rmi.RemoteFileClassifier;
import rmi.FileClassifierImpl;

import java.io.*;

/**
 * FileClassifierServerConfig.java
 */

public class FileClassifierServerConfig implements  LeaseListener {
    
    private LeaseRenewalManager leaseManager = new LeaseRenewalManager();
    private ServiceID serviceID = null;
    private RemoteFileClassifier impl;
    private File serviceIdFile;
    private Configuration config;

    public static void main(String args[]) {
	FileClassifierServerConfig s = new FileClassifierServerConfig(args);
	
        // keep server running forever to 
	// - allow time for locator discovery and
	// - keep re-registering the lease
	Object keepAlive = new Object();
	synchronized(keepAlive) {
	    try {
		keepAlive.wait();
	    } catch(java.lang.InterruptedException e) {
		// do nothing
	    }
	}
    }

    public FileClassifierServerConfig(String[] args) {
        System.setSecurityManager(new RMISecurityManager());

	try {
	    config = ConfigurationProvider.getInstance(args); 
	} catch(ConfigurationException e) {
	    System.err.println("Configuration error: " + e.toString());
	    System.exit(1);
	}

	Exporter exporter = null;
	try {
	    exporter = (Exporter) 
		config.getEntry( "config.FileClassifierServerConfig", 
				 "exporter", 
				 Exporter.class); 
	} catch(ConfigurationException e) {
	    e.printStackTrace();
	    System.exit(1);
	}

	// Create the service and its proxy
	try {
	    impl = new rmi.FileClassifierImpl();
	} catch(RemoteException e) {
	    e.printStackTrace();
	    System.exit(1);
	}

	Remote proxy = null;
	try {
	    proxy = exporter.export(impl);
	} catch(ExportException e) {
	    e.printStackTrace();
	    System.exit(1);
	}

	// register proxy with lookup services
	JoinManager joinMgr = null;
	try {
	    LookupDiscoveryManager mgr = 
		new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
					   null,  // unicast locators
					   null); // DiscoveryListener
	    joinMgr = new JoinManager(proxy, // service proxy
				      null,  // attr sets
				      serviceID,
				      mgr,   // DiscoveryManager
				      new LeaseRenewalManager());
	} catch(Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}
    }

    void getServiceID() {
	// Make up our own
	Uuid id = UuidFactory.generate();
	serviceID = new ServiceID(id.getMostSignificantBits(),
				  id.getLeastSignificantBits());
    }

    public void serviceIDNotify(ServiceID serviceID) {
	// called as a ServiceIDListener
	// Should save the id to permanent storage
	System.out.println("got service ID " + serviceID.toString());
    }

    public void discarded(DiscoveryEvent evt) {

    }

    public void notify(LeaseRenewalEvent evt) {
	System.out.println("Lease expired " + evt.toString());
    }   
    
} // FileClassifierServer

      

This server can be run using a configuration file such as a standard Jeri over TCP configuration config/security/jeri-tcp.config


	import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;

security.FileClassifierServer {

    /* class name for the service */
    serviceName = "rmi.FileClassifierImpl";

    exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
                                     new BasicILFactory()); 
}

      
This can be run from gthe command line by

	java ... security.FileClassifierServer \
                 config/security/jeri-tcp.config
      
or from the Ant build files by

	ant run -DrunFile=security.FileClassifierServer \ 
                -Dconfig=config/security/jeri-tcp.config
      

The server with the rmi.FileClassifierImpl and config/security/jeri-tcp.config configuration has no security features, and will be discarded by a client that imposes any constraints. However, by changing the service class or configuration file it can meet various client requirements.

To build the server and run it with various configuration files I use the Ant file security.FileClassifierServer.xml. This configuration file is a bit trickier than earlier ones in that it needs to set different parameters for different situations. For example, in the following section on Integrity ordinary HTTP URLs can be used but in the later section on Proxy Trust a different type of URL, HTTPMD ones must be used. This is controlled by various command line "defines" which can run specialised targets such as httpmd if the Ant file is run with a command-line define of -Ddo.trust=yes. This sets the property codebase.httpd to an HTTPMD URL. Otherwise, the target is not run and a default value of this property as an HTTP URL is used.

The Ant file is


	
      

22.8. Integrity

Integrity ensures that each method call sent from the client to server gets there in original form - that is, it is not altered in any way, and similarly that replies are not altered. It does not guarantee privacy - anyone can look at messages (that is the role of confidentiality). It also does not guarantee that the entity you are sending messages to is the one you think it is (that is the role of authentication).

22.8.1 Client

A client can enforce integrity by requiring that the proxy support the constraint Integrity.YES. With the example client above, this can be done by using the config/security/preparer-integrity.config configuration file


	  

import java.security.Permission;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Integrity;
import net.jini.security.BasicProxyPreparer;
import net.jini.constraint.BasicMethodConstraints;

client.TestFileClassifierProxyPreparer {


   preparer =
        new BasicProxyPreparer(
            /* Don't verify the proxy. */
            false,
            /*
             * Require integrity for all methods.
             */
            new BasicMethodConstraints(
                new InvocationConstraints(
                    new InvocationConstraint[] {
                        Integrity.YES
                    },
                    null
                )
            ),
            new Permission[] {}
        );
}

	

To run the client using this configuration, run


	java ... client.TestFileClassifierProxyPreparer \
                 config/security/preparer-integrity.config
	
or

	ant run -DrunFile=client.TestFileClassifierProxyPreparer \ 
                -Dconfig=config/security/preparer-integrity.config
	
instead of

	java ... client.TestFileClassifierProxyPreparer \
                 config/security/preparer-minimal.config
	
or

	ant run -DrunFile=client.TestFileClassifierProxyPreparer \ 
                -Dconfig=config/security/preparer-minimal.config
	
Note that only the configuration file has changed.

22.8.2 TCP server

TCP does not support integrity checking. Using TCP, we can expect integrity to fail. The server can use Jeri over TCP, by a configuration such as config/security/jeri-tcp.config


	import net.jini.jeri.BasicILFactory;
import net.jini.jeri.BasicJeriExporter;
import net.jini.jeri.tcp.TcpServerEndpoint;

security.FileClassifierServer {

    /* class name for the service */
    serviceName = "rmi.FileClassifierImpl";

    exporter = new BasicJeriExporter(TcpServerEndpoint.getInstance(0),
                                     new BasicILFactory()); 
}

      
It can be run by

	  java ... security.FileClassifierServer config/security/jeri-tcp.config
	
or from the Ant file by

	  ant run -DrunFile=security.FileClassifierServer \
                  -Dconfig=config/security/jeri-tcp.config
	

The client can find the service, and create a new proxy for it. But when it tries to call a method through this proxy, integrity checking will fail. This shows by the client throwing an exception


java.rmi.ConnectIOException: I/O exception connecting to 
BasicObjectEndpoint[e42fc746-e7c7-444b-bbc9-b124217439c4,
TcpEndpoint[127.0.0.1:43084]]; nested exception is:
     net.jini.io.UnsupportedConstraintException: 
         cannot satisfy constraint: Integrity.YES
      
This exception is thrown when the client attempts to call any method on the proxy. In the example above, it occurs in the first method call to the proxy

type = classifier.getMIMEType(fileName)
	

TCP not only fails to support integrity checking, it also fails to support any of the other security mechanisms of this chapter. We will not consider it any further in this chapter.

22.8.3 SSL server

SSL (or TLS) does support integrity checking. The server can use Jeri over SSL, by a configuration such as config/security/jeri-ssl-minimal.config


	/* Configuration source file for an SSL server */

import java.security.Permission;

import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Integrity;
import net.jini.jeri.*;
import net.jini.jeri.ssl.*;

security.FileClassifierServer {

    /* class name for the service */
    serviceName = "rmi.FileClassifierImpl";

    /* Exporter for the server proxy */
    exporter =
	/* Use secure exporter */
	new BasicJeriExporter(
	    /* Use SSL transport */
	    SslServerEndpoint.getInstance(0),
            new BasicILFactory(
		/* Require integrity for all methods */
		new BasicMethodConstraints(
		    new InvocationConstraints(
				(InvocationConstraint[]) null, 
				(InvocationConstraint[]) null)),
		/* No Permission */
		null
            )
        );
}

      
It can be run by

	  java ... security.FileClassifierServer config/security/jeri-ssl-minimal.config
	
or from the Ant file by

	  ant run -DrunFile=security.FileClassifierServer \
                  -Dconfig=config/security/jeri-ssl-minimal.config
	

The service used here is still the RMI service discussed in earlier chapters rmi.FileClassifierImpl


	  
package rmi;

import common.MIMEType;
import common.FileClassifier;

/**
 * FileClassifierImpl.java
 */

public class FileClassifierImpl implements RemoteFileClassifier {
    
    public MIMEType getMIMEType(String fileName) 
	throws java.rmi.RemoteException {
	System.out.println("Called with " + fileName);
        if (fileName.endsWith(".gif")) {
            return new MIMEType("image", "gif");
        } else if (fileName.endsWith(".jpeg")) {
            return new MIMEType("image", "jpeg");
        } else if (fileName.endsWith(".mpg")) {
            return new MIMEType("video", "mpeg");
        } else if (fileName.endsWith(".txt")) {
            return new MIMEType("text", "plain");
        } else if (fileName.endsWith(".html")) {
            return new MIMEType("text", "html");
        } else
            // fill in lots of other types,
            // but eventually give up and
            return new MIMEType(null, null);
    }


    public FileClassifierImpl() throws java.rmi.RemoteException {
	// empty constructor required by RMI
    }
    
} // FileClassifierImpl

	
The service needs no changes to satisfy integrity - integrity is supplied by the SSL protocol.

This service/server/configuration combination works with no exceptions thrown.

This section was the "teaser": Look how easy it is to implement advanced security! Just add a constraint to the client and use an appropriate protocol for the service! The following sections look at other aspects of security, and it does get a little more complex :-)

22.9. Proxy verification

When a client finds a service, it goes through several stages: it prepares a service description and asks lookup services if they have any services matching that description. If they do, then the lookup service downloads a MarshalledObject for the service. This basically contains two things: the instance data for the service and a URL for the class files of the service.

The service places class or jar files on the HTTP server. The client gets these files from the HTTP server. In Jini 1.2 both the service and client trust the HTTP server. For a secure system this trust should be demonstrable. The client needs to able to verify that the class files it got from the server are the class files that the service put there. This can be done in many ways, but the Jini team has come up with a neat way, called HTTPMD.

22.9.1 HTTPMD

There is no integrity or other security aspects in getting files from an HTTP server. The standard way of ensuring security means using a protocol such as HTTPS, but this is quite heavyweight, and not really suited to the purpose of proxy verification: while we can get a verified document from an HTTPS server, we still don't know whether or not to trust that server!

What we want to get from an HTTP server is a jar file for the classes that are provided by the service. It is only the service that knows if they are correct or not. That is, it doesn't really matter whether or not the HTTP server can be trusted, but whether we can get information from the service to verify the jar file. A common way of checking that two files are identical is to use a hash of each file. A hash is a number (often 128 bits) that is calculated from the file contents. Hash algorithms are designed with two properties

  1. If two files have the same hash, then it is "almost certain" that they are the same file (that is, have the same contents)

  2. Given a hash value, it is "nearly impossible" to create a file with that hash value

So a service can put a jar file on an HTTP server and a client can download it. If the hash calculated by the client is the same as the service thinks it should be, then the client can be "almost certain" that it has the correct file.

There are many hash algorithms. Popular ones are

  1. MD5

  2. SHA

When you get a marshalled object from a lookup service, it contains the URL for the class files. This URL was inserted by the service. If the URL contained the hash for the class files, then it would be possible for a client to verify that it had obtained the correct files. (Of course, this assumes that the lookup service and HTTP server are not in collusion to deliver false hash values - see later for trusting the lookup service.)

Jini defines an HTTPMD (HTTP + Message Digest) URL that adds the hash value as component of the URL. The scheme is changed from "http" to "httpmd" and the hash is added as an extra component, along with a statement of the hash algorithm. For example, a URL of


http:jan.newmarch.name/classes/FileClassifierServer-dl.jar
	
would change to

httpmd:jan.newmarch.name/classes/FileClassifierServer-dl.jar;\
md5=7ef2019216d0e9069308cec29b779bc0
	
using the MD5 hash algorithm.

The service can specify such a URL in its java.rmi.server.codebase property. There is a small hiccup: this is a non-standard protocol that is not recognised by the standard Java virtual machine. There is however a standard mechanism for adding new handlers to a JVM. The process for doing this is described by Brian Maso in the article A New Era for Java Protocol Handlers at http://java.sun.com/developer/onlineTraining/protocolhandlers. The HTTPMD handler is part of the Jini package. It only needs to be installed into the JVM. This is done by defining an appropriate property


java -Djava.protocol.handler.pkgs=net.jini.url ...
	
which looks for the class net.jini.url.httpmd.Handler whenever it needs to handle an HTTPMD document.

http://java.sun.com/developer/onlineTraining/protocolhandlers/

22.9.2 Calculating HTTPMD URLs

There are often tools available in any Operating System for calculation of message digests. For example, most Linux distributions include the md5sum command for MD5 digests and shasum for SHA digests. Use of such tools is O/S specific, and must be run by hand or automatically from some sort of script.

There is an message digest class in java.security package. This can be used in a platform-independent way. However, it won't directly handle an HTTPMD URL. Jini 2.0 includes the HttpmdUtil class which will calculate digests from a URL. It has two methods


class HttpmdUtil {
    static String computeDigest(URL url, 
                                String algorithm);
    static String computeDigestCodebase(String sourceDirectory, 
                                        String codebase);
}
	

The first method useful if you already have a jar file installed in an HTTP server and wish to calculate its digest. So for example you could call


HttpmdUtil.computeDigest("http://localhost/classes/ClassFiles.jar", "MD5");
	
The resulting digest could be appended to a new URL of type HTTPMD. Note that for this mechanism to be valid, the program using this must trust the HTTP server!

A simple program to calculate and print the hash value of a URL using this is PrintDigest


	  import net.jini.url.httpmd.HttpmdUtil;
import java.net.URL;

public class PrintDigest {

    public static void main(String[] args) {
	if (args.length == 0) {
	    System.out.println("");
	    return;
	}
	String codebase = args[0];
	try {
	    System.out.println(HttpmdUtil.computeDigest(
							new URL(codebase), 
							"MD5"));
	} catch(Exception e) {
	    System.out.println(codebase);
	}
    }
} 

	
We use this program in our Ant files which assume a local trusted HTTP server.

The second method is useful before deployment of the jar file to an HTTP server. It returns a new URL for a given URL with a new digest value. For example, the call


HttpmdUtil.computeDigestCodebase("dist", 
               "httpmd://localhost/classes/ClassFiles.jar;md5=0");
	
This strips the scheme, host and digest value from the URL and appends the directories and jar filename to the given directory. In this case it calculates the digest for the local file dist/classes/ClassFiles.jar. It then rebuilds the URL as e.g. httpmd://localhost/classes/ClassFiles.jar;md5=.... This only involves local trust. However, it does rely on a consistent directory convention between local files and HTTP URLs - and I didn't obey that convention :-(

A third technique is given in the Jini "hello" example source/vob/jive/src/com/sun/jini/example/hello. This is a sophisticated method and not for the faint-hearted. It installs a new RMIClassLoaderSpi called MdClassAnnotationProvider. The getClassAnnotation() method of this class uses the second method of the HttpmdUtil to generate an HTTPMD URL on demand from an HTTP URL.

22.9.3 Reggie

The standard setup for reggie does not recognise the HTTPMD protocol. I'm not sure why it is looking inside the marshalled objects for this, but it will cause an exception to be thrown in services if it does not understand it. This is an easy fix: add the property -Djava.protocol.handler.pkgs=net.jini.url to the command that starts reggie.

22.9.4 Proxy verifier

When the client gets a proxy for a service, it gets a marshalled object with instance data and a URL for the class files. Assuming that the URL has not been tampered with, it can download the class files from an HTTP server. If it is an HTTPMD URL then it can verify that the class files are correct - as long as it trusts the proxy. This is a tricky problem: how to verify that the proxy is correct when you have a - possibly - false and misleading proxy? Moreover, this possibly antagonistic proxy is the only way you have of talking to the service.

The only entity that can really verify that the proxy is correct is the original service. So can we send the proxy to the service and get it to tell us? Well, no: if we ask the untrusted proxy to send itself for verification to the service, then it might just lie and claim that, yes, it has done so. What we have to do is to get an object from the service that can perform verification locally - under the client's eyes, as it were.

The mechanism adopted by Jini to solve this is to use several levels of proxy: the (untrusted) service proxy is asked to deliver a "bootstrap" proxy that can deliver a verifier. This verifier is the object that will deliver the verdict on whether the proxy can be trusted, so it must be trustworthy itself. Jini ensures this by insisting that the class files for this verifier are local to the client and so are trusted just like any other local code.

The client needs to have a list of local verifiers that it trusts just because they are local. A standard set is given in the Jini library jsk-platform.jar file. This jar file contains the following verifiers:

  1. ConstraintTrustVerifier

  2. BasicJeriTrustVerifier

  3. SslTrustVerifier

  4. KerberosTrustVerifier

  5. ProxyTrustVerifier

  6. ConstrainableLookupLocatorTrustVerifier

  7. DiscoveryConstraintTrustVerifier

22.9.5 Client with proxy verification

To require trust from a service, the client must do three things

  1. Include jsk-platform.jar in its classpath to get a set of proxy verifiers

  2. Install an HTTPMD handler by the runtime property java.protocol.handler.pkgs=net.jini.url

  3. Specify trust checking by setting the first argument of BasicProxyPreparer to true. A configuration file to require trust checking (and nothing else) is config/security/preparer-trust.config

    
    		
    
    import java.security.Permission;
    import net.jini.core.constraint.InvocationConstraint;
    import net.jini.core.constraint.InvocationConstraints;
    import net.jini.core.constraint.Integrity;
    import net.jini.security.BasicProxyPreparer;
    import net.jini.constraint.BasicMethodConstraints;
    
    client.TestFileClassifierProxyPreparer {
    
       preparer =
            new BasicProxyPreparer(
                /* Verify the proxy. */
                true,
                /* No constraints */
                new BasicMethodConstraints(
                    new InvocationConstraints(
                        new InvocationConstraint[] {
                        },
                        null
                    )
                ),
                new Permission[] {}
            );
    }
    
    	      

    A command line to run this client is

    
    java ... client.TestFileClassifierProxyPreparer \
             config/security/preparer-trust.config
    	      
    or using Ant
    
    ant run -DrunFile=client.TestFileClassifierProxyPreparer \
            -Dconfig=config/security/preparer-trust.config
    	      

22.9.6 SSL trusted server

SSL will allow trust checking to be performed.

  1. The service does not need to be adapted, and can still be the rmi.FileClassifierImpl given above

  2. The server needs to install an HTTPMD handler by the runtime property java.protocol.handler.pkgs=net.jini.url

  3. The security.FileClassiferServer-dl.jar jar file needs to be created with contents common/MIMEType.class, common/FileClassifier.class, rmi/RemoteFileClassifier.class, rmi/FileClassifierImpl.class and copied to an HTTP server

  4. A hash needs to be performed on this security.FileClassiferServer-dl.jar jar file. In this tutorial we use the HttpmdUtil to caculate this as explained earlier (we trust the local HTTP server)

  5. The codebase should be an HTTPMD URL, including the hash value from the last step

The service does not need to specify anything other than that it uses SSL for transport (the server supplies the HTTPMD codebase). The server can use the config/security/jeri-ssl-minimal.config configuration


	  /* Configuration source file for an SSL server */

import java.security.Permission;

import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Integrity;
import net.jini.jeri.*;
import net.jini.jeri.ssl.*;

security.FileClassifierServer {

    /* class name for the service */
    serviceName = "rmi.FileClassifierImpl";

    /* Exporter for the server proxy */
    exporter =
	/* Use secure exporter */
	new BasicJeriExporter(
	    /* Use SSL transport */
	    SslServerEndpoint.getInstance(0),
            new BasicILFactory(
		/* Require integrity for all methods */
		new BasicMethodConstraints(
		    new InvocationConstraints(
				(InvocationConstraint[]) null, 
				(InvocationConstraint[]) null)),
		/* No Permission */
		null
            )
        );
}

	

A command line to run this server is


java ... -Djava.rmi.server.codebase=httpmd://... \
         security.FileClassiferServer \
         config/security/jeri-ssl-minimal.config
	
or from Ant

ant run -DrunFile=security.FileClassiferServer \
        -Dconfig=config/security/jeri-ssl-minimal.config \
        -Ddo.trust=yes
	      

This server/configuration will handle a client that requires trust verification. If the trust logger file for the client is examined it will contain lines such as


FINE: trust verifiers [net.jini.constraint.ConstraintTrustVerifier...]
Aug 16, 2004 9:59:35 PM net.jini.security.Security$Context isTrustedObject
FINE: net.jini.jeri.ssl.SslTrustVerifier@df1832 trusts 
      SslEndpoint[127.0.0.1:39693]
Aug 16, 2004 9:59:35 PM net.jini.security.Security$Context isTrustedObject
FINE: net.jini.jeri.BasicJeriTrustVerifier@1a116c9 trusts 
      BasicObjectEndpoint[...,SslEndpoint[...]]
Aug 16, 2004 9:59:35 PM net.jini.security.Security$Context isTrustedObject
FINE: net.jini.constraint.ConstraintTrustVerifier@1d1e730 trusts 
      InvocationConstraints[reqs: {}, prefs: {}]
Aug 16, 2004 9:59:35 PM net.jini.security.Security$Context isTrustedObject
FINE: net.jini.constraint.ConstraintTrustVerifier@1d1e730 trusts 
      BasicMethodConstraints{default => null}
Aug 16, 2004 9:59:35 PM net.jini.security.Security$Context isTrustedObject
FINE: net.jini.jeri.BasicJeriTrustVerifier@1a116c9 trusts 
      Proxy[RemoteFileClassifier,
            BasicInvocationHandler[BasicObjectEndpoint[...]]
	
This shows that the SslTrustVerifier trusts the SslEndpoint, and that because of that the BasicJeriTrustVerifier trusts the BasicObjectEndpoint which contains the SslEndpoint. Trust is also applied to the constraints after which the proxy is declared to be trusted.

It is important to note the limits of what has been achieved in this section: we have downloaded a proxy that we can trust - but whose proxy is it? We have been assured that the code we have got has not been tampered with by anyone else, but we could still be getting code from "Antagonistic Alice". All that we know at this point is that we have the code that Alice intended to send to us and that Mallory in the middle hasn't tampered with it!

22.9.7 Errors

  1. If you forget to include jsk-platform.jar in the client's classpath, then it won't be able to find the standard verifiers and won't be able to verify any proxies. The client will throw a SecurityException

    
    java.lang.SecurityException: object is not trusted: 
         Proxy[RemoteFileClassifier,BasicInvocationHandler[
               BasicObjectEndpoint[1c4c3ec0-f91e-46a6-827b-626575702a07,
                                   SslEndpoint[127.0.0.1:56641]]]]
         at net.jini.security.Security.verifyObjectTrust(Security.java:268)
         at net.jini.security.BasicProxyPreparer.verify(BasicProxyPreparer.java:309)
    	      
    The trust logger will also show
    
    FINE: trust verifiers []
    Aug 16, 2004 5:54:27 PM net.jini.security.Security$Context isTrustedObject
    FAILED: no verifier trusts Proxy[RemoteFileClassifier,BasicInvocationHandler[Bas
    icObjectEndpoint[1c4c3ec0-f91e-46a6-827b-626575702a07,SslEndpoint[127.0.0.1:5664
    1]]]]
    	      
    with the failure caused by an empty verifiers list

  2. If the server uses HTTP URLs instead of HTTPMD URLs, then the client is unable to perform an integrity check. This will result in the client throwing a SecurityException

    
    java.lang.SecurityException: URL does not provide integrity: 
         http://192.168.1.13/classes/security.FileClassifierServer-dl.jar
         at net.jini.security.Security.verifyCodebaseIntegrity(Security.java:343)
    	      
    The integrity logger will also show
    
    FINE: integrity verifiers [net.jini.url.httpmd.HttpmdIntegrityVerifier@1b5998f,
          net.jini.url.https.HttpsIntegrityVerifier@17494c8, 
          net.jini.url.file.FileIntegrityVerifier@d3db51]
    Aug 16, 2004 6:01:27 PM net.jini.security.Security verifyCodebaseIntegrity
    FAILED: no verifier verifies 
           http://192.168.1.13/classes/security.FileClassifierServer-dl.jar
    	      

  3. It is necessary to install the HTTPMD handler in the server, the client and in reggie. If you omit to include it in the server, then the discovery logger reports

    
    INFO: exception occurred during unicast discovery
     java.net.MalformedURLException: unknown protocol: httpmd
         at java.net.URL.>init<(URL.java:544)
    	      
    If you leave this handler out of the client it throws an exception during service discovery
    
    java.rmi.UnmarshalException: error unmarshalling return; nested exception is:
         java.net.MalformedURLException: unknown protocol: httpmd
         at com.sun.jini.reggie.RegistrarProxy.lookup(RegistrarProxy.java:130)
    	      
    If you omit to install the HTTPMD handler in reggie, then when the server runs, it gets an error thrown from reggie which shows in a message from the JoinManager logger:
    
    INFO: JoinManager - failure
    java.rmi.ServerException: RemoteException in server thread; nested exception is:
         java.rmi.UnmarshalException: unmarshalling method/arguments; 
              nested exception is:
         java.net.MalformedURLException: unknown protocol: httpmd
         at net.jini.jeri.BasicInvocationDispatcher.dispatch(...)
    	      

22.10. Confidentiality

A conversation is confidential if no-one can overhear it, or even if they can hear it then they cannot understand it. Typically applications use encryption to ensure that messages cannot be read by others.

22.10.1 Client

Either a client or a server can specify confidentiality. A client specifies this by making adding a Confidentiality.YES constraint to the proxy preparer. For example, the config/security/preparer-conf.config configuration file can contain


	  

import java.security.Permission;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Confidentiality;
import net.jini.security.BasicProxyPreparer;
import net.jini.constraint.BasicMethodConstraints;

client.TestFileClassifierProxyPreparer {


   preparer =
        new BasicProxyPreparer(
            /* Don't verify the proxy. */
            false,
            /*
             * Require integrity for all methods.
             */
            new BasicMethodConstraints(
                new InvocationConstraints(
                    new InvocationConstraint[] {
                        Confidentiality.YES
                    },
                    null
                )
            ),
            new Permission[] {}
        );
}

	

To run the client using this configuration, run


	java ... client.TestFileClassifierProxyPreparer \
                 config/security/preparer-conf.config
	
or

	ant run -DrunFile=client.TestFileClassifierProxyPreparer \ 
                -Dconfig=config/security/preparer-conf.config
	
Note that only the configuration file has changed.

22.10.2 SSL confidential server

SSL supports confidentiality. Indeed, that is the major purpose behind its design. So all that is needed is for a server to specify that it is using SSL, which can be done using the earlier config/security/jeri-ssl-minimal.config configuration file


	  /* Configuration source file for an SSL server */

import java.security.Permission;

import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Integrity;
import net.jini.jeri.*;
import net.jini.jeri.ssl.*;

security.FileClassifierServer {

    /* class name for the service */
    serviceName = "rmi.FileClassifierImpl";

    /* Exporter for the server proxy */
    exporter =
	/* Use secure exporter */
	new BasicJeriExporter(
	    /* Use SSL transport */
	    SslServerEndpoint.getInstance(0),
            new BasicILFactory(
		/* Require integrity for all methods */
		new BasicMethodConstraints(
		    new InvocationConstraints(
				(InvocationConstraint[]) null, 
				(InvocationConstraint[]) null)),
		/* No Permission */
		null
            )
        );
}

	

A command line to run this server is


java ... security.FileClassiferServer \
         config/security/jeri-ssl-minimal.config
	
or from Ant

ant run -DrunFile=security.FileClassiferServer \
        -Dconfig=config/security/jeri-ssl-minimal.config
	      

22.11. Mix 'n Match

So far we have tried the following combinations

  1. preparer-minimal.config and jeri-tcp.config" - no security on either side, and this works the same way as examples in earlier chapters without security

  2. preparer-integrity.config and >jeri-tcp.config - this failed due to lack of support by TCP for integrity checking

  3. preparer-integrity.config and jeri-ssl-minimal.config - this succeeds since SSL supports integrity checking

  4. preparer-trust.config and >jeri-ssl-minimal.config with HTTPMD URLs - this works because SSL supports trust of messages and the HTTPMD URLs allow the client to trust the HTTP server

  5. preparer-conf.config and jeri-ssl-minimal.config - this works because SSL supports confidentiality through encryption

We can try variations on these: for example, a client that requires trust and integrity with a server that requires encryption. This is just an additive process: add in the extra constraints to the appropriate configuration and ensure that the client or server has the correct runtime to handle the constraints. The client configuration in this case is config/security/preparer-trust-integrity.config


	

import java.security.Permission;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Integrity;
import net.jini.security.BasicProxyPreparer;
import net.jini.constraint.BasicMethodConstraints;

client.TestFileClassifierProxyPreparer {

   preparer =
        new BasicProxyPreparer(
            /* Verify the proxy. */
            true,
            /* No constraints */
            new BasicMethodConstraints(
                new InvocationConstraints(
                    new InvocationConstraint[] {
                        Integrity.YES
                    },
                    null
                )
            ),
            new Permission[] {}
        );
}

      
and the server configuration is config/security/jeri-ssl-conf.config configuration file

	/* Configuration source file for an SSL server with confidentiality */

import java.security.Permission;

import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.Confidentiality;
import net.jini.jeri.*;
import net.jini.jeri.ssl.*;

security.FileClassifierServer {

    /* class name for the service */
    serviceName = "rmi.FileClassifierImpl";

    /* Exporter for the server proxy */
    exporter =
	/* Use secure exporter */
	new BasicJeriExporter(
	    /* Use SSL transport */
	    SslServerEndpoint.getInstance(0),
            new BasicILFactory(
		/* Require confidentiality for all methods */
		new BasicMethodConstraints(
		    new InvocationConstraints(Confidentiality.YES, null)),
		/* No Permission */
		null
            )
        );
}

      

These can be run by


java ... client.TestFileClassifierProxyPreparer \
                 config/security/preparer-trust-integrity.config

java ... -Djava.rmi.server.codebase=httpmd://... \
         security.FileClassiferServer \
         config/security/jeri-ssl-conf.config
      
or from Ant

ant run -DrunFile=client.TestFileClassifierProxyPreparer \ 
         -Dconfig=config/security/preparer-trust-integrity.config

ant run -DrunFile=security.FileClassiferServer \
        -Dconfig=config/security/jeri-ssl-.config \
        -Ddo.trust=yes
      

This combination works satisfactorily. The client logs are indicative of what has happened. The trust logger shows (with much text elided)


FINE: HttpmdIntegrityVerifier verifies httpmd://...
FINE: trust verifiers [...]
FINE: SslTrustVerifier trusts SslEndpoint[...]
FINE: BasicJeriTrustVerifier trusts BasicObjectEndpoint[...]
FINE: ConstraintTrustVerifier trusts Confidentiality.YES
FINE: ConstraintTrustVerifier trusts 
           InvocationConstraints[reqs: {Confidentiality.YES}, prefs: {}]
FINE: ConstraintTrustVerifier trusts 
           BasicMethodConstraints{default => 
               InvocationConstraints[reqs: {Confidentiality.YES}, prefs: {}]}
FINE: BasicJeriTrustVerifier trusts Proxy[...]
      
This shows that the HttpmdIntegrityVerifier trusts the HTTPMD URL; The SslTrustVerifier trusts the SslEndpoint; the ConstraintTrustVerifier trusts Confidentiality; and hence the ConstraintTrustVerifier trusts the BasicMethodConstraints; and so finally, the BasicJeriTrustVerifier trusts the proxy.

Similarly, the integrity log shows


FINE: integrity verifiers [...]
FINE: HttpmdIntegrityVerifier verifies httpmd://...
      
showing that the HttpmdIntegrityVerifier verifies the HTTPMD URL.

By way of contrast, if the client is run with one constraint and the server is run with its opposite, then the constraints cannot be satisfied. For example, if the client has set Confidentiality.NO and the server has set Confidentiality.YES, then the client will throw an exception


java.rmi.ConnectIOException: I/O exception connecting to 
     BasicObjectEndpoint[...,SslEndpoint[...]]; nested exception is:
     net.jini.io.UnsupportedConstraintException: Constraints not supported: 
         InvocationConstraints[reqs: {Confidentiality.NO, 
                                      Confidentiality.YES}, prefs: {}]
      

22.12. Identity Management

If you want to give different individuals different access rights, then you need to be able to verify their identity. This means that they must have some way of expressing what their identity is in a form that you will recognise. People (and things) may have a number of identities: the father of a particular person, the staff id number, the driving license number, their name, and so on. Essentially, different labels for the one entity. The terminology adopted is that the entity is called a subject, and in Java this is represented by the Subject class in the javax.security.auth package. The different identities for a subject are called principals, and they too have a Java class, the Principal class.

A subject authenticates itself to a service using a principal and information to verify itself as that principal. For example, you log into a computer using your username as principal and password for verification. Other mechanisms could be used. For example, you verify yourself to the police officer who has just pulled you over by the photo on your drivers license.

A credential (such as a driver's license) is used to authenticate a subject to later services. In the computer world, these include be X.509 certificates and Kerberos tickets.

22.12.1 JAAS

A white paper describing JAAS is at USER AUTHENTICATION AND AUTHORIZATION IN THE JAVA(TM) PLATFORM (http://java.sun.com/security/jaas/doc/acsac.html), with other information such as "JavaTM Authentication and Authorization Service (JAAS) Reference Guide" which should be in the Java distribution directory docs/guide/security/jaas/JAASRefGuide.html and "JAAS Authentication" at http://java.sun.com/j2se/1.4.2/docs/guide/security/jgss/tutorials/AcnOnly.html

JAAS (Java Authentication and Authorization Service) is a framework for verifying common identities. These include

  1. JNDI

  2. KeyStore

  3. Kerberos

  4. Windows NT

  5. Unix

Information on these can be found at http://java.sun.com/j2se/1.4.2/docs/guide/security/jgss/tutorials/LoginConfigFile.html.

JAAS augments the standard Java security model by adding support for principals. So security access is not just granted on the properties of the code itself (signed, etc) but also on the principals running the code.

In order to use JAAS, you firstly need to create a LoginContext. This picks up information from a configuration file to decide which principal it is authenticating as, and how to do it. The configuration file is similar in concept to the Jini configuration files, but the syntax is different and the contents depend upon the mechanism used.

Once you have a context, you attempt to login(). If successful, you are then a recognised entity, and are a subject with identity. As a subject, you may be able to do more things than if you have no identity. For example, in an SSL interaction you will be able to present certificates for this identity if required. Similarly with Kerberos: if challenged, you have an identity and a ticket credential to prove it.

You then add the JAAS security checks to code by running it as the privileged subject. The code for this looks like


    LoginContext loginContext = 
	new LoginContext("...");
    if (loginContext == null) {
	// do some action without JAAS security
    } else {
	loginContext.login();
	Subject.doAsPrivileged(
	            loginContext.getSubject(),
		    new PrivilegedExceptionAction() {
		           public Object run() throws Exception {
				       // do the same action, but now
                                       // as a particular subject
				       return null;
			   }
	            },
	            null);
    }
	

22.12.2 Keystores

A keystore is a place to store certain types of credentials, such as X.509 certificates which are used by SSL. A keystore is manipulated by the keytool command. Conventionally, your own private (and associated public keys) are stored in a keystore... file while public keys from others are stored in a truststore... file.

We can create private keys for the client by


	  keytool -keystore keystore.client -genkey
	
This will prompt for X.509 information, which assumes that you are an individual working for an organisation

Enter keystore password:  client
What is your first and last name?
  [Unknown]:  Client
What is the name of your organizational unit?
  [Unknown]:  IT
What is the name of your organization?
  [Unknown]:  Monash
What is the name of your City or Locality?
  [Unknown]:  Melbourne
What is the name of your State or Province?
  [Unknown]:  Vic
What is the two-letter country code for this unit?
  [Unknown]:  AU
Is CN=Client, OU=IT, O=Monash, L=Melbourne, ST=Vic, C=AU correct?
  [no]:  yes

Enter key password for 
        (RETURN if same as keystore password):
	

Similarly, we can set up a keystore for the server


keytool -keystore keystore.server -genkey
Enter keystore password:  server
What is your first and last name?
  [Unknown]:  Server
What is the name of your organizational unit?
  [Unknown]:  IT
What is the name of your organization?
  [Unknown]:  Monash
What is the name of your City or Locality?
  [Unknown]:  Melbourne
What is the name of your State or Province?
  [Unknown]:  Vic
What is the two-letter country code for this unit?
  [Unknown]:  AU
Is CN=Server, OU=IT, O=Monash, L=Melbourne, ST=Vic, C=AU correct?
  [no]:  yes

Enter key password for 
        (RETURN if same as keystore password):
	

We can export the server's public key from the its keystore and import it into the client's truststore under the alias "Server" by


keytool -keystore keystore.server -export -file server.cert
Enter keystore password:  server
Certificate stored in file 

keytool -keystore truststore.client -import -file server.cert -alias Server
Enter keystore password:  client
Owner: CN=Server, OU=IT, O=Monash, L=Melbourne, ST=Vic, C=AU
Issuer: CN=Server, OU=IT, O=Monash, L=Melbourne, ST=Vic, C=AU
Serial number: 4123f906
Valid from: Thu Aug 19 10:49:10 EST 2004 until: Wed Nov 17 11:49:10 EST 2004
Certificate fingerprints:
         MD5:  6E:24:70:EB:E2:2C:A0:72:C5:B9:9B:95:72:39:87:B1
         SHA1: 2B:AB:0D:80:4F:DF:B8:66:3B:E7:49:66:3D:53:EC:C5:B8:3A:91:5E
Trust this certificate? [no]:  yes
Certificate was added to keystore
	
and similarly we can export the client's public key and import it into the server's truststore under the alias "Client"

keytool -keystore keystore.client -export -file client.cert
Enter keystore password:  client
Certificate stored in file 

keytool -keystore truststore.server -import -file client.cert -alias Client
Enter keystore password:  server
Owner: CN=Client, OU=IT, O=Monash, L=Melbourne, ST=Vic, C=AU
Issuer: CN=Client, OU=IT, O=Monash, L=Melbourne, ST=Vic, C=AU
Serial number: 4123f88d
Valid from: Thu Aug 19 10:47:09 EST 2004 until: Wed Nov 17 11:47:09 EST 2004
Certificate fingerprints:
         MD5:  EA:4A:67:E3:A6:58:2D:F4:52:00:FE:CF:2C:AC:7A:6A
         SHA1: 8C:68:E3:9C:E8:08:4A:33:F5:12:E4:9D:73:D6:EF:A4:A5:82:B2:79
Trust this certificate? [no]:  yes
Certificate was added to keystore
	

22.12.3 Authenticating Server

A server that is prepared to authenticate itself must be able to offer suitable credentials when challenged. For SSL, this would be an X.509 certificate, for Kerberos it would be a Kerberos ticket, etc. If JAAS is used to provide these credentials then it must be able to "login" to gets it authentication information.

The server code needs to be modified slightly to get a login context and then login using a principal to get a subject. If this succeeds then it can run the rest of the code as that subject. The changes to the file classifier server are given as static code to execute before creating the server. The security/FileClassifierServerAuth is


	  package security;

import java.rmi.RMISecurityManager;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.server.ExportException;

import java.security.PrivilegedExceptionAction;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import java.security.PrivilegedActionException;

import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceItem;
import net.jini.core.lookup.ServiceRegistration;
import net.jini.core.lease.Lease;
import net.jini.core.lookup.ServiceID ;
import net.jini.lease.LeaseListener;             
import net.jini.lease.LeaseRenewalEvent;         
import net.jini.lease.LeaseRenewalManager;       
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;
import net.jini.lookup.JoinManager;
import net.jini.id.UuidFactory;
import net.jini.id.Uuid;
import net.jini.discovery.LookupDiscoveryManager;
import net.jini.export.Exporter;

import rmi.RemoteFileClassifier;
import rmi.FileClassifierImpl;

import java.util.logging.*;

import java.io.*;

/**
 * FileClassifierServerAuth
 */

public class FileClassifierServerAuth implements  LeaseListener {
    
    private LeaseRenewalManager leaseManager = new LeaseRenewalManager();
    private ServiceID serviceID = null;
    private RemoteFileClassifier impl;
    private File serviceIdFile;
    private Configuration config;

    static final String TRUST_LOG = "net.jini.security.trust";
    static final String INTEGRITY_LOG = "net.jini.security.integrity";
    static final String POLICY_LOG = "net.jini.security.policy";
    static final Logger trustLogger = Logger.getLogger(TRUST_LOG);
    static final Logger integrityLogger = Logger.getLogger(INTEGRITY_LOG);
    static final Logger policyLogger = Logger.getLogger(POLICY_LOG);
    private static FileHandler trustFh;
    private static FileHandler integrityFh;
    private static FileHandler policyFh;


    private static FileClassifierServerAuth server;

    static final String DISCOVERY_LOG = "net.jini.security.trust";
    static final Logger logger = Logger.getLogger(DISCOVERY_LOG);
    private static FileHandler fh;

    public static void main(String args[]) {
	/*
	try {
	    // this handler will save ALL log messages in the file
	    fh = new FileHandler("mylog.svr.txt");
	    // the format is simple rather than XML
	    fh.setFormatter(new SimpleFormatter());
	    logger.addHandler(fh);
	} catch(Exception e) {
	    e.printStackTrace();
	}
	*/
	installLoggers();

	init(args);

	Object keepAlive = new Object();
	synchronized(keepAlive) {
	    try {
		keepAlive.wait();
	    } catch(java.lang.InterruptedException e) {
		// do nothing
	    }
	}
    }

    private static void init(final String[] args) {
	try {
	    LoginContext loginContext = 
		new LoginContext("security.FileClassifierServerAuth");
	    if (loginContext == null) {
		System.out.println("No login context");
		server = new FileClassifierServerAuth(args);
	    } else {
		loginContext.login();
		System.out.println("Login succeeded as " + 
				   loginContext.getSubject().toString());
		Subject.doAsPrivileged(
				       loginContext.getSubject(),
				       new PrivilegedExceptionAction() {
					   public Object run() throws Exception {
					       server = new FileClassifierServerAuth(args);
					       return null;
					   }
				       },
				       null);
	    }
	} catch(LoginException e) {
	    e.printStackTrace();
	    System.exit(3);
	} catch(PrivilegedActionException e) {
	    e.printStackTrace();
	    System.exit(3);
	}
    }

    public FileClassifierServerAuth(String[] args) {
        System.setSecurityManager(new RMISecurityManager());
	Exporter exporter = null;
	String serviceName = null;
	try {
	    config = ConfigurationProvider.getInstance(args); 

	    exporter = (Exporter) 
		config.getEntry( "security.FileClassifierServer", 
				 "exporter", 
				 Exporter.class);
	    serviceName = (String)
		config.getEntry( "security.FileClassifierServer", 
				 "serviceName", 
				 String.class);
	} catch(ConfigurationException e) {
	    System.err.println("Configuration error: " + e.toString());
	    System.exit(1);
	}

	// Create the service and its proxy
	try {
	    // impl = new security.FileClassifierImpl();
	    impl = (RemoteFileClassifier) Class.forName(serviceName).newInstance();
	} catch(Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}

	Remote proxy = null;
	try {
	    proxy = exporter.export(impl);
	    System.out.println("Proxy is " + proxy.toString());
	} catch(ExportException e) {
	    e.printStackTrace();
	    System.exit(1);
	}

	// register proxy with lookup services
	JoinManager joinMgr = null;
	try {
	    LookupDiscoveryManager mgr = 
		new LookupDiscoveryManager(LookupDiscovery.ALL_GROUPS,
					   null,  // unicast locators
					   null); // DiscoveryListener
	    joinMgr = new JoinManager(proxy, // service proxy
				      null,  // attr sets
				      serviceID,
				      mgr,   // DiscoveryManager
				      new LeaseRenewalManager());
	} catch(Exception e) {
	    e.printStackTrace();
	    System.exit(1);
	}
    }

    private static void installLoggers() {
	try {
	    // this handler will save ALL log messages in the file
	    trustFh = new FileHandler("log.server.trust.txt");
	    integrityFh = new FileHandler("log.server.integrity.txt");
	    policyFh = new FileHandler("log.server.policy.txt");

	    // the format is simple rather than XML
	    trustFh.setFormatter(new SimpleFormatter());
	    integrityFh.setFormatter(new SimpleFormatter());
	    policyFh.setFormatter(new SimpleFormatter());

	    trustLogger.addHandler(trustFh);
	    integrityLogger.addHandler(integrityFh);
	    policyLogger.addHandler(policyFh);

	    trustLogger.setLevel(java.util.logging.Level.ALL);
	    integrityLogger.setLevel(java.util.logging.Level.ALL);
	    policyLogger.setLevel(java.util.logging.Level.ALL);
	} catch(Exception e) {
	    e.printStackTrace();
	}
    }

    void getServiceID() {
	// Make up our own
	Uuid id = UuidFactory.generate();
	serviceID = new ServiceID(id.getMostSignificantBits(),
				  id.getLeastSignificantBits());
    }

    public void serviceIDNotify(ServiceID serviceID) {
	// called as a ServiceIDListener
	// Should save the id to permanent storage
	System.out.println("got service ID " + serviceID.toString());
    }

    public void discarded(DiscoveryEvent evt) {

    }

    public void notify(LeaseRenewalEvent evt) {
	System.out.println("Lease expired " + evt.toString());
    }   
    
} // FileClassifierServerAuth

	
This login context uses a hard-coded string to specify the name "security.FileClassifierServerAuth" used by JAAS to find information from the JAAS configuration file; this string could better be given in a Jini configuration file in a production environment.

A few other pieces need to be put in place in order that this server can authenticate itself

  1. The server needs to be run with an additional runtime property. The property java.security.auth.login.config needs to be set to the login configuration file, as in

    
    	java ... -Djava.security.auth.login.config=ssl-server.login ...
    	      

  2. The JAAS login file specifies how JAAS is to get its credentials. For example, for SSL it will need a certificate which it can get from a keystore. So for an SSL authenticating server the ssl-server.login could contain

    
    		/* JAAS login configuration file for SSL server */
    
    security.FileClassifierServerAuth {
        com.sun.security.auth.module.KeyStoreLoginModule required
            keyStoreAlias="mykey"
    	keyStoreURL="file:resources/security/keystore.server"
    	keyStorePasswordURL="file:resources/security/password.server";
    };
    
    	      
    The configuration name "security.FileClassifierServerAuth" is the same as the parameter to the LoginContext constructor. The file also specifies the alias to be used in looking up entries (the default is "mykey" if not specified during creation of the keystore), the keystore and a file that contains the password to access this keystore

  3. The password file password.server just contains the password we set earlier: "server".

This server can be run from the command line


java ... security.FileClassiferServerAuth \
         config/security/jeri-ssl-minimal.config
	
or from Ant

ant run -DrunFile=security.FileClassiferServerAuth \
        -Dconfig=config/security/jeri-ssl-minimal.config
	
The server does not need to set any constraints (since it just authenticates itself), so the minimal server configuration file can be used.

22.12.4 Client requiring authentication

The client can require that the server have a proof of identity, or that it identifies itself as a particular subject. This is just like asking "Do you have a card that proves you are over eighteen?" versus "Do you have a card that proves you are Joe Bloggs?".

The first case ("do you have a credential?") can be specified in the client configuration file by just adding the ServerAuthentication.YES constraint


	  

import java.security.Permission;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.ServerAuthentication;
import net.jini.security.BasicProxyPreparer;
import net.jini.constraint.BasicMethodConstraints;

client.TestFileClassifierProxyPreparer {

   preparer =
        new BasicProxyPreparer(
            /* Don't verify the proxy. */
            false,
            /* Require authentication as anyone  */
            new BasicMethodConstraints(
                new InvocationConstraints(
                    new InvocationConstraint[] {
                        ServerAuthentication.YES
                    },
                    null
                )
            ),
            new Permission[] {}
        );
}

	
The client is the previous TestFileClassiferProxyPreparer used throughout this chapter. However, although it doesn't look up any certicates it does seem to need a trust store to be specified - a bug? This can be done by adding the property to the runtime

	  -Djavax.net.ssl.trustStore=truststore.client
	

The second case requires specifying which principal(s) the server is required to authenticate as. The most common case is when the client requires a single principal as identity. The ServerMinPrincipal is used for this, with constructors for a single principal or for a set of principals. In order to get an SSL principal, we need to do something like pull it out of a keystore. This involves the steps of: get the users from a keystore and get a single user from this list. The KeyStores class in Jini allows these steps to be done from within a configuration file.

The client is still unaltered from TestFileClassifierProxyPreparer. The configuration file is now preparer-auth-server.config


	  /* config file to ask the server to authenticate itself */

import java.security.Permission;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.ServerAuthentication;
import net.jini.security.BasicProxyPreparer;
import net.jini.constraint.BasicMethodConstraints;
import com.sun.jini.config.KeyStores;
import net.jini.core.constraint.ServerMinPrincipal;

client.TestFileClassifierProxyPreparer {

   /* Keystore for getting principals */
   private static users=
        KeyStores.getKeyStore("file:resources/security/truststore.client", null);
   private static serverUser = 
        KeyStores.getX500Principal("server", users);

   preparer =
        new BasicProxyPreparer(
            /* Don't verify the proxy. */
            false,
            /* Require authentication as "server"  */
            new BasicMethodConstraints(
                new InvocationConstraints(
                    new InvocationConstraint[] {
                        ServerAuthentication.YES,
                        new ServerMinPrincipal(serverUser)
                    },
                    null
                )
            ),
            new Permission[] {}
        );
}

	

22.12.5 Alternative constraints

Classes such as ServerMinPrincipal can take a set of principals and AND them together: "are you Jan Newmarch AND are you over eighteen AND are you the father of Katy Newmarch?" A client may want to express a set of alternatives such as "do you have a driver's license OR do you have a social security card?" The ConstraintAlternatives class can be used to handle these cases.

22.12.6 Authenticating Client

The same mechanism used for a server to authenticate itself is used by the client. That is, it sets up a login context, logs in and then runs code as a particular subject. The modified code is client.TestFileClassifierAuth


	  
package client;

import common.FileClassifier;
import common.MIMEType;

import java.security.PrivilegedExceptionAction;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import java.security.PrivilegedActionException;

import java.rmi.RMISecurityManager;
import net.jini.discovery.LookupDiscovery;
import net.jini.discovery.DiscoveryListener;
import net.jini.discovery.DiscoveryEvent;
import net.jini.core.lookup.ServiceRegistrar;
import net.jini.core.lookup.ServiceTemplate;

import java.rmi.RemoteException;

import net.jini.security.BasicProxyPreparer;
import net.jini.security.ProxyPreparer;
import net.jini.config.Configuration;
import net.jini.config.ConfigurationException;
import net.jini.config.ConfigurationProvider;

import java.util.logging.*;

/**
 * TestFileClassifierAuth.java
 */

public class TestFileClassifierAuth implements DiscoveryListener {

    private Configuration config;

    static final String TRUST_LOG = "net.jini.security.trust";
    static final String INTEGRITY_LOG = "net.jini.security.integrity";
    static final String POLICY_LOG = "net.jini.security.policy";
    static final Logger trustLogger = Logger.getLogger(TRUST_LOG);
    static final Logger integrityLogger = Logger.getLogger(INTEGRITY_LOG);
    static final Logger policyLogger = Logger.getLogger(POLICY_LOG);
    private static FileHandler trustFh;
    private static FileHandler integrityFh;
    private static FileHandler policyFh;


    public static void main(String argv[]) 
	throws ConfigurationException {

	installLoggers();

	// Become a subject if possible
	init(argv);

        // stay around long enough to receive replies
        try {
            Thread.currentThread().sleep(100000L);
        } catch(java.lang.InterruptedException e) {
            // do nothing
        }
    }

    private static void init(final String[] args) {
	try {
	    LoginContext loginContext = 
		new LoginContext("security.TestFileClassifierAuth");
	    if (loginContext == null) {
		System.out.println("No login context");
		new TestFileClassifierAuth(args);
	    } else {
		loginContext.login();
		 System.out.println("Login succeeded as " + 
			   loginContext.getSubject().toString());
		Subject.doAsPrivileged(
				       loginContext.getSubject(),
				       new PrivilegedExceptionAction() {
					   public Object run() throws Exception {
					       new TestFileClassifierAuth(args);
					       return null;
					   }
				       },
				       null);
	    }
	} catch(LoginException e) {
	    e.printStackTrace();
	    System.exit(3);
	} catch(PrivilegedActionException e) {
	    e.printStackTrace();
	    System.exit(3);
	} catch(ConfigurationException e) {
	    e.printStackTrace();
	    System.exit(3);
	}
    }

    public TestFileClassifierAuth(String[] argv) 
	throws ConfigurationException {
	config = ConfigurationProvider.getInstance(argv);

	System.setSecurityManager(new RMISecurityManager());

	LookupDiscovery discover = null;
        try {
            discover = new LookupDiscovery(LookupDiscovery.ALL_GROUPS);
        } catch(Exception e) {
            System.err.println(e.toString());
            System.exit(1);
        }

        discover.addDiscoveryListener(this);

    }

    private static void installLoggers() {
	try {
	    // this handler will save ALL log messages in the file
	    trustFh = new FileHandler("log.client.trust.txt");
	    integrityFh = new FileHandler("log.client.integrity.txt");
	    policyFh = new FileHandler("log.client.policy.txt");

	    // the format is simple rather than XML
	    trustFh.setFormatter(new SimpleFormatter());
	    integrityFh.setFormatter(new SimpleFormatter());
	    policyFh.setFormatter(new SimpleFormatter());

	    trustLogger.addHandler(trustFh);
	    integrityLogger.addHandler(integrityFh);
	    policyLogger.addHandler(policyFh);

	    trustLogger.setLevel(java.util.logging.Level.ALL);
	    integrityLogger.setLevel(java.util.logging.Level.ALL);
	    policyLogger.setLevel(java.util.logging.Level.ALL);
	} catch(Exception e) {
	    e.printStackTrace();
	}
    }

    public void discovered(DiscoveryEvent evt) {

        ServiceRegistrar[] registrars = evt.getRegistrars();
	Class [] classes = new Class[] {FileClassifier.class};
	FileClassifier classifier = null;
	ServiceTemplate template = new ServiceTemplate(null, classes, 
						       null);
 
        for (int n = 0; n < registrars.length; n++) {
	    System.out.println("Lookup service found");
            ServiceRegistrar registrar = registrars[n];
	    try {
		classifier = (FileClassifier) registrar.lookup(template);
	    } catch(java.rmi.RemoteException e) {
		e.printStackTrace();
		System.exit(4);
		continue;
	    }
	    if (classifier == null) {
		System.out.println("Classifier null");
		continue;
	    }

	    System.out.println("Getting the proxy");
	    // Get the proxy preparer
	    ProxyPreparer preparer = null;
	    try {
		preparer = 
		(ProxyPreparer) config.getEntry(
						"client.TestFileClassifierProxyPreparer",
						"preparer", ProxyPreparer.class,
						new BasicProxyPreparer());
	    } catch(ConfigurationException e) {
		e.printStackTrace();
		preparer = new BasicProxyPreparer();
	    }

	    // Prepare the new proxy
	    System.out.println("Preparing the proxy");
	    try {
		classifier = (FileClassifier) preparer.prepareProxy(classifier);
	    } catch(RemoteException e) {
		e.printStackTrace();
		System.exit(3);
	    } catch(java.lang.SecurityException e) {
		e.printStackTrace();
		System.exit(6);
	    }

	    // Use the service to classify a few file types
	    System.out.println("Calling the proxy");
	    MIMEType type;
	    try {
		String fileName;

		fileName = "file1.txt";
		type = classifier.getMIMEType(fileName);
		printType(fileName, type);

		fileName = "file2.rtf";
		type = classifier.getMIMEType(fileName);
		printType(fileName, type);

		fileName = "file3.abc";
		type = classifier.getMIMEType(fileName);
		printType(fileName, type);
	    } catch(java.rmi.RemoteException e) {
		System.out.println("Failed to call method");
		System.err.println(e.toString());
		System.exit(5);
		continue;
	    }
	    // success
	    System.exit(0);
	}
    }

    private void printType(String fileName, MIMEType type) {
	System.out.print("Type of " + fileName + " is ");
	if (type == null) {
	    System.out.println("null");
	} else {
	    System.out.println(type.toString());
	}
    }

    public void discarded(DiscoveryEvent evt) {
	// empty
    }
} // TestFileClassifierAuth

	

Like the server, other pieces need to be in place order that this client can authenticate itself

  1. The client needs to be run with an additional runtime property. The property java.security.auth.login.config needs to be set to the login configuration file, as in

    
    	java ... -Djava.security.auth.login.config=ssl-client.login ...
    	      

  2. The JAAS login file specifies how JAAS is to get its credentials. For example, for SSL it will need a certificate which it can get from a keystore. So for an SSL authenticating server the ssl-client.login could contain

    
    		/* JAAS login configuration file for SSL server */
    
    security.TestFileClassifierAuth {
        com.sun.security.auth.module.KeyStoreLoginModule required
            keyStoreAlias="mykey"
    	keyStoreURL="file:resources/security/keystore.client"
    	keyStorePasswordURL="file:resources/security/password.client";
    };
    
    	      
    The configuration name "security.TestFileClassifierAuth" is the same as the parameter to the LoginContext constructor. The file also specifies the alias to be used in looking up entries (the default is "mykey" if not specified during creation of the keystore), the keystore and a file that contains the password to access this keystore

  3. The password file password.client just contains the password we set earlier: "client".

22.12.7 Server Requiring Authentication

If the server requires the client to authenticate as a particular user then it can be the config.FileClassifierServer. It does not need to have the authentication code itself. It can specify client authentication by the jeri-ssl-auth-client.config configuration file


	  /* Configuration source file for an SSL server */

import java.security.Permission;

import net.jini.constraint.BasicMethodConstraints;
import net.jini.core.constraint.InvocationConstraint;
import net.jini.core.constraint.InvocationConstraints;
import net.jini.core.constraint.ClientAuthentication;
import net.jini.core.constraint.ClientMinPrincipal;
import net.jini.jeri.*;
import net.jini.jeri.ssl.*;
import com.sun.jini.config.KeyStores;

security.FileClassifierServer {

    /* class name for the service */
    serviceName = "rmi.FileClassifierImpl";

   /* Keystore for getting principals */
   private static users=
        KeyStores.getKeyStore("file:resources/security/truststore.server", null);
   private static clientUser = 
        KeyStores.getX500Principal("client", users);

    /* Exporter for the server proxy */
    exporter =
	/* Use secure exporter */
	new BasicJeriExporter(
	    /* Use SSL transport */
	    SslServerEndpoint.getInstance(0),
            new BasicILFactory(
		/* Require integrity for all methods */
		new BasicMethodConstraints(
		    new InvocationConstraints(
                                new InvocationConstraint[] {
                                      ClientAuthentication.YES,
                                      new ClientMinPrincipal(clientUser)
                                },
				(InvocationConstraint[]) null)),
		/* No Permission */
		null
            )
        );
}

	

In addition to this, the server needs to be run with a define


	  -Djavax.net.ssl.trustStore=resources/security/truststore.server
	
to locate the truststore file it will use to verify certificates from the client.

22.13. Authorisation

Standard Java uses policy files to determine what foreign code is allowed to do. This policy is installed when the application starts, so is a static policy mechanism. In Jini 2.0, when a service is discovered, it may wish to ask for a policy to be applied at that time, ie dynamically. Extensions to the basic security model in JDK 1.4 allow this to occur, by permitting dynamic policy setting on class loaders.

In order to allow dynamic policy granting, the Java runtime must have the appropriate classes installed and trusted. This is the purpose of the jsk-policy.jar file from the Jini library. As part of the installation process for Jini, you are recommended to install this into the jre/lib/ext directory of your Java distribution. This allows the Java runtime to pick these up as trusted classes when it starts.

The runtime needs to be told about these classes. This can be done by using the runtime define


	-Djava.security.properties=security.properties
      
where security.properties is a file containing the single line saying which Jini class to use for dynamic policies

	policy.provider=net.jini.security.policy.DynamicPolicyProvider

      

For the client, an array of permissions specifies the permissions it will grant to a proxy. This array is set in the BasicProxyPreparer.

The server can set a permission in the BasicILFactory. This permission is used to perform server-side access control on incoming remote calls.

22.14. Conclusion

Ensuring security on the network is a complex task anyway, and the Jini possibilities of mobile code increases the security risks. This chapter has presented an "end-programmers" view of the new Jini 2.0 security. The architecture behind this is highly configurable, and what has been presented is one set of "plugins" to make it (relatively) easy for the programmer. However, if you want more control over any part of this process, then you can dig further into this architecture and "roll your own" for almost all parts of it.

22.15. Copyright

If you found this chapter of value, the full book "Foundations of Jini 2 Programming" is available from APress or Amazon .

This file is Copyright (©) 1999, 2000, 2001, 2003, 2004, 2005 by Jan Newmarch (http://jan.newmarch.name) jan@newmarch.name.

Creative Commons License This work is licensed under a Creative Commons License, the replacement for the earlier Open Content License.