Security plays an important role in distributed systems. The Jini security model is based on the JDK 1.2 security system.
Security for Jini is based on the JDK 1.2 security model.
This makes use of a SecurityManager
to grant
or deny access to resources.
A few of the examples may work fine without a security manager.
Most will require an appropriate security manager in place or RMI will
be unable to download class files.
Installing a suitable manager may be done by
System.setSecurityManager(new RMISecurityManager());
This should be done before any network-related calls.
The security manager will need to make use of a
security policy. This is typically
given in policy files which are in default locations or
are specified to the Java runtime. If policy.all
is a policy file in the current directory, then invoking the
runtime by
java -Djava.security.policy="policy.all" ...
will load the contents of the policy file.
A totally permissive policy file can contain
grant {
permission java.security.AllPermission "", "";
};
This will allow all permissions, and should never be used
outside of a test and development environment - and moreover, one
that is insulated from other potentially untrusted machines.
(Standalone is good here!)
The big advantage of this is that it gets you going on the
rest of Jini, without worrying about
security issues while you are grappling with other problems!
Granting all permissions to everyone is a very trusting act, in the potentially hostile world of the Internet. Not everyone is ``mister nice guy''. The client is vulnerable to attack because it is downloading code that satisfies a request for a service, and then executing that code. The checks that it is a genuine service are really non-existent: it has to implement the requested interface, and maybe satisfy conditions on associated Entry objects. If it passes these, then it can do anything.
As an example of a deliberately hostile service, a client asking for a simple file classifier could end up getting this object:
package hostile;
import common.MIMEType;
import common.FileClassifier;
/**
* HostileFileClassifier1.java
*/
public class HostileFileClassifier1 implements FileClassifier {
public MIMEType getMIMEType(String fileName) {
if (java.io.File.pathSeparator.equals("/")) {
// Unix - don't uncomment the next line!
// Runtime.getRuntime().exec("/bin/rm -rf /");
} else {
// DOS - don't uncomment the next line!
// Runtime.getRuntime().exec("format c: /u");
}
return null;
}
public HostileFileClassifier1() {
// empty
}
} // HostileFileClassifier1
This object would be exported from a hostile service to run completely in any
client unfortunate enough to download it.
It is not necessary to actually call a method on the downloaded object - the mere act of downloading can do the damage, if the object overrides the deserialization method:
package hostile;
import common.MIMEType;
import common.FileClassifier;
/**
* HostileFileClassifier2.java
*/
public class HostileFileClassifier2 implements FileClassifier,
java.io.Externalizable {
public MIMEType getMIMEType(String fileName) {
return null;
}
public void readExternal(java.io.ObjectInput in) {
if (java.io.File.pathSeparator.equals("/")) {
// Unix - don't uncomment the next line!
// Runtime.getRuntime().exec("/bin/rm -rf /");
} else {
// DOS - don't uncomment the next line!
// Runtime.getRuntime().exec("format c: /u");
}
}
public void writeExternal(java.io.ObjectOutput out)
throws java.io.IOException{
out.writeObject(this);
}
public HostileFileClassifier2() {
// empty
}
} // HostileFileClassifier2
The two classes above assume that clients will make requests for the implementation of a particular interface, and this means that the attacker would need to know this interface. It would require some knowledge of the clients it is attacking (that they will ask for this interface). At the moment, there are no standard interfaces, so this may not be a feasible way of attacking many clients. As interfaces such as those for a printer become specified and widely used, attacks based on hostile implementations of services may become more common.
Setting the security access to AllPermission
is easy and removes
all possible security issues that may hinder development of a Jini application.
But it leaves your system open, so that you must start using a more rigorous security
policy at some stage - hopefully before others have damaged your system.
The problem with moving away from this policy is that permissions are
additive rather than subtractive.
That is, you can't take permissions away from AllPermission
,
but have to start with an empty permission set and add to that.
Not giving enough permission can result in at least three cases:
rmi.FileClassifierServer
. The third occurs
for the client client.TestFileClassifier
.
There is a system property java.security.debug
that can be set to
print information about various types of access to the security mechanisms.
This can be used with a slack security policy to find out exactly what permissions
are being granted. Then, with the screws tightened, you can see where permission
is being denied. An appropriate value for this property is access
, as
in
java -Djava.security.debug=access ...
For example, running client.TestFileClassifier
with few permissions
granted may result in a trace including
...
access: access allowed (java.util.PropertyPermission socksProxyHost read)
access: access allowed (java.net.SocketPermission 127.0.0.1:1174 accept,resolve)
access: access denied (java.net.SocketPermission 130.102.176.249:1024 accept,resolve)
access: access denied (java.net.SocketPermission 130.102.176.249:1025 accept,resolve)
access: access denied (java.net.SocketPermission 130.102.176.249:1027 accept,resolve)
...
The denied access is an attempt to make a socket accept or resolve request
on my laptop (IP address 130.102.176.249), probably for RMI-related
sockets. Since the client just sits there indefinitely making this request
on one random port after another, this
permission needs to be opened up as the client otherwise appears to just hang.
The safest way for a Jini client or service to be part of a Jini federation is through abstinence: that is, refuse to take part. This doesn't get you very far in populating a federation. The JDK 1.2 security model allows a number of ways in which more permissive activity may take place:
grant {
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
permission java.net.SocketPermission "*.edu.au:80", "connect";
}
grant codebase "http://sunshade.dstc.edu.au/classes/" {
permission java.security.AllPermission "", "";
}
grant signedBy "sysadmin" {
permission java.security.AllPermission "", "";
}
In order to partake in a Jini federation, a service must become ``sufficiently'' visible. A service needs to find a service locator before it can advertise its services. This can be by unicast to particular locations or by multicast.
Unicast discovery does not need any particular permissions to be set. The discovery can be done without any policy file needed.
For the multicast case, the service must have DiscoveryPermission
for each group that it is trying to join. For all groups, the wildcard ``*''
can be used. So to join all groups, the permission granted should be
permission net.jini.discovery.DiscoveryPermission "*";
To join, say, the groups printers
and toasters
, the permission
would be
permission net.jini.discovery.DiscoveryPermission,
"printers, toasters";
Once this permission is given, the service will make a multicast broadcast on
224.0.1.84. Socket permission for these requests and announcements
must be given by
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
The service may export a UnicastRemoteObject
. This will require
listening on a port for requests. The default constructor will assign a
random port (above 1024) for this. If desired, this port may be specified
by other constructors. This will require further socket permissions, such as
permission java.net.SocketPermission "localhost:1024-", "connect,accept";
permission java.net.SocketPermission "*.dstc.edu.au:1024-", "connect,accept";
to accept connections on any port above 1024 from the localhost
or any computer in the dstc.edu.au
domain.
A number of parameters may be set by preferences, such as
net.jini.discovery.ttl
. It does no harm to allow the Jini
system to look for these parameters, and this may be allowed by
permission java.util.PropertyPermission "net.jini.discovery.*", "read";
A fairly minimal policy file suitable for a service exporting an RMI object could then be
grant {
permission net.jini.discovery.DiscoveryPermission "*";
// multicast request address
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
// multicast announcement address
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
// RMI connections
permission java.net.SocketPermission "*.canberra.edu.au:1024-", "connect,accept";
permission java.net.SocketPermission "130.102.176.249:1024-", "connect,accept";
permission java.net.SocketPermission "127.0.0.1:1024-", "connect,accept";
// reading parameters
// like net.jini.discovery.debug!
permission java.util.PropertyPermission "net.jini.discovery.*", "read";
};
The client is most at risk in the Jini environment. The service exports objects; the lookup locator stores objects, but does not ``bring them to life'' and execute any of their methods; but the client brings an external object into its address space and runs it using all of the permissions that it has as a process running in an operating system. So it will run under the permissions of a particular user, in a particular directory, with user accesses to the local file system and network. It could destroy files, make network connections to undesirable sites (or desirable, depending on your tastes!) and download images from them, start processes to send obnoxious mail to anyone in your address book, and generally make a mess of your electronic identity!
A client using multicast search to find service locators will need to grant discovery permission and multicast announcement permission, just like the service
permission net.jini.discovery.DiscoveryPermission "*";
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
RMI connections on random ports may also be needed
permission java.net.SocketPermission "*.dstc.edu.au:1024-", "connect,accept"
In addition to these, class definitions will probably need to be uploaded so that services can actually run in the client. This is the most serious risk area for the client, as the code contained in these class definitions will be run in the client, and any errors or malicious code will have their effect because of this. The client view of the different levels of trust are shown in figure 12.1.
Many services will just make use of whatever HTTP server is running on their system, and this will probably be on port 80. Permission to connect on this port can be granted by
permission java.net.SocketPermission "127.0.0.1:80", "connect,accept";
permission java.net.SocketPermission "*.dstc.edu.au:80", "connect,accept";
However, while this will allow code to be downloaded on port 80, it may not block some malicious attempts. Any user can start an HTTP server on any port (Windows) or above 1024 (Unix). A service can then set its codebase to whatever port the HTTP server is using. Perhaps these other ports should be blocked. Unfortunately, RMI uses random ports, so these ports need to be open. So it is not probably possible to close all holes for hostile code to be downloaded to a client. What you do is a second stage defence: given that hostile code may reach you, set the JDK security so that hostile (or just buggy) code cannot perform harmful actions in the client.
A fairly minimal policy file suitable for a client could then be
grant {
permission net.jini.discovery.DiscoveryPermission "*";
// multicast request address
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
// multicast announcement address
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
// RMI connections
// DANGER
// HTTP connections - this is where external code may come in - careful!!!
permission java.net.SocketPermission "127.0.0.1:1024-", "connect,accept";
permission java.net.SocketPermission "*.canberra.edu.au:1024-", "connect,accept";
permission java.net.SocketPermission "130.102.176.249:1024-", "connect,accept";
// DANGER
// HTTP connections - this is where external code may come in - careful!!!
permission java.net.SocketPermission "127.0.0.1:80", "connect,accept";
permission java.net.SocketPermission "*.dstc.edu.au:80", "connect,accept";
// reading parameters
// like net.jini.discovery.debug!
permission java.util.PropertyPermission "net.jini.discovery.*", "read";
};
A service is specified by an interface. In many cases, an RMI proxy will be
delivered to the client which implements this interface. Depending on the
interface, this can be used by the client to attack the service. The
FileClassifier
interface is safe. But in a later chapter
we look at how a client can upload a new MIME type to a service, and this
extended interface exposes a service to attack.
The relevant method from the MutableFileClassifier
interface is
public void addType(String suffix, MIMEType type)
throws java.rmi.RemoteException;
This allows a client to pass an object of type MIMEType
up to
the service, where it will presumably try to add it to a list of existing
MIME types. The MIMEType
class is an ordinary class, not an
interface. Nevertheless, it can be subclassed, and this subclass can perform
the tricks discussed earlier to make an attack.
This particular attack can be avoided by ensuring that the parameters to any
method call in an interface are all final
classes. If the class
MIMEType
was defined by
public final class MIMEType {...}
then it would not be possible to subclass it. No attack could be made by a
subclass, since no subclass could be made!
There aren't enough Jini services defined yet to know if making all parameters
final
is a good enough solution.
Services will transfer objects to be run within clients. This chapter has been concerned about the security policies that will allow this, and the restrictions that may need to be in place. The major protection to clients at the moment is that there are no standardised service interfaces, so attackers do not yet know what hostile objects to write.
A lookup service exports an object that implements ServiceRegistrar
.
It does not use the same mechanism as a service would to get its code into a
client. Instead, the lookup service replies directly to unicast connections
with a registrar object, or responds to multicast requests by establishing
a unicast connection to the requester and again sending a registrar.
The mechanism is different, but it is clearly documented in the Jini specifications
and it is quite easy to write an application that performs at least this
much of the discovery protocols.
The end result of lookup discovery is that the lookup service will have downloaded
registrar objects.
The registrar objects run in both clients and services -
they both need to find lookup services. The ServiceRegistrar
interface
is standardised by the Jini specification, so it is fairly easy to write a hostile
lookup service that can attack both clients and services.
While it is unlikely that anyone will knowingly make a unicast connection to a hostile lookup service, someone might get tricked into it. There are already some quite unscrupulous Web sites that will offer ``free'' services on production of a credit card (to the user's later cost). There is every probability that they will try to entice Jini clients if they see a profit in doing so. Also, anyone with access to the network within broadcast range of clients and services (i.e. on your local network) can start lookup services which will be found by multicast discovery.
The only real counter to this attack is to require that all connections
that can result in downloaded code should be covered by digital certificates,
so that all downloaded code must be signed. This covers all possible
ports, since an HTTP server can be started on any port on a Windows machine.
The objects that are downloaded in the Sun implementation of the lookup service,
reggie
, are all in reggie-dl.jar
. This is not signed
by any certificates. If you are worried about an attack through this route, you
should sign this file, as well as the jar files of any services you wish to use.
The Jini distribution includes a transaction manager called ``mahalo''. This uses the new activation methods of RMI in JDK 1.2. Without worrying about any other arguments, the call to run this transaction manager is
java -jar mahalo.jar
(assuming the jar file is in the classpath).
The transaction manager is a Jini service, and will need class definitions
to be uploaded to clients. The class files are in mahalo-dl.jar
,
and will come from an HTTP server as specified in
the first command-line argument:
java -jar mahalo.jar http://jannote.dstc.edu.au/mahalo-dl.jar
(or appropriate HTTP address).
Runnig as a service, the transaction manager should set a security policy.
This will allow it to register the service with a lookup service, and allow
client access to it. In addition, the transaction manager needs to maintain
state about transactions in permanent storage. For this, it needs access
to the file system, and since it has a security manager installed this access
needs to be granted explicitly.
This is done using the normal java.security.policy
property
java -Djava.security.policy=policy.txn \
-jar mahalo.jar http://jannote.dstc.edu/au/mahalo-dl.jar
This will allow the service to be registered and uploaded, and also allow access
to the file system.
A suitable policy to allow all of this could be
grant {
// rmid wants this
permission java.net.SocketPermission "127.0.0.1:1098", "connect,resolve";
// other RMI calls want these, too
permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
permission java.net.SocketPermission "130.102.176.153:1024-", "connect,resolve";
// access to transaction manager log files
permission java.io.FilePermission "/tmp/mahalo_log", "read,write";
permission java.io.FilePermission "/tmp/mahalo_log/-", "read,write,delete";
// properties used by transaction manager
permission java.util.PropertyPermission "com.sun.jini.mahalo.managerName", "read";
permission java.util.PropertyPermission "com.sun.jini.use.registry", "read";
};
The activation system of JDK 1.2 takes a little getting used to, and causes
confusion to Jini newcomers since Sun implementations of major Jini services
(such as mahalo
) use it. An activatable service hands over
responsibility for execution to a third party,
an activation service. A service starts, registers itself
with this third party service, and then exits. The third party is responsible
for fielding calls to the service,and either awakening it or restoring it from
scratch to handle the call. There is a subtlety here: the service begins
execution in one JVM, but promptly delegates its execution to this third party
running in a different JVM! This third party is usually the rmid
service.
When the service is run, the
service has to be activated in some JVM - the one running
rmid
. This in turn may need its own security policy, which is
specified in an additional command-line argument policy.actvn
java -Djava.security.policy=policy.txn -jar mahalo.jar \
http://jannote.dstc.edu.au/mahalo-dl.jar \
policy.actvn
A suitable activation policy could be
grant {
// rmid wants this
permission java.net.SocketPermission "127.0.0.1:1098", "connect,resolve";
// other RMI calls want these, too
permission java.net.SocketPermission "127.0.0.1:1024-", "connect,resolve";
permission java.net.SocketPermission "130.102.176.153:1024-", "connect,resolve";
// access t transaction manager log files
permission java.io.FilePermission "/tmp/mahalo_log", "read,write";
permission java.io.FilePermission "/tmp/mahalo_log/-", "read,write,delete";
// properties used by transaction manager
permission java.util.PropertyPermission "com.sun.jini.mahalo.managerName", "read";
permission java.util.PropertyPermission "com.sun.jini.use.registry", "read";
// needed for activation
permission java.net.SocketPermission "224.0.1.84", "connect,accept,resolve";
permission java.io.FilePermission "/tmp/mahalo_log/-", "read";
permission java.util.PropertyPermission "com.sun.jini.thread.debug", "read";
permission java.lang.RuntimePermission "modifyThreadGroup";
permission java.lang.RuntimePermission "modifyThread";
// for dowloading mahalo-dl.jar from HTTP server
permission java.net.SocketPermission "*:8080", "connect,accept,resolve";
permission net.jini.discovery.DiscoveryPermission "*";
};
An activatable service runs within a JVM started by rmid
.
It does so with the same user identity as rmid
. So if
rmid
is run by, say, the superuser root
on
a Unix system, then all activatable services will run with that same user id,
root
. This is a security flaw, as any user on that system can
write and start an activatable service, and then write and run a client
that makes calls on the service. This is a way to run programs from an
arbitrary user that run with superuser priviliges.
My own machine has only a few users, and all of them I trust not to write
deliberately malicious programs (and right now, I am the only one of them
who can write Jini services). However, most may not be in such a fortunate
position. Consequently, rmid
should be run in such a way that
even if it is attacked, it will not be able to do any damage.
On Unix, there are two ways of reducing the risk
nobody
. This can be done
by changing rmid
to be setuid to this user. Note that the program
rmid
in the Java bin
directory is actually a shell script that
eventually calls a program such as bin/i386/green_threads/rmid
,
and it is this program that needs to be setuid
chroot
mechanism to run rmid
in a subdirectory
that appears to be the top-level directory `/'. This will make all other files
invisible to rmid
On an NT system, rmid
should be set up so that it only runs under
general user access rights.
The security problems of the last section have been partly addressed by
a tighter security mechanism introduced in JDK 1.3. These restrict what
activatable services can do by using a security mechanism that is under
the control of whoever starts rmid
. This means that there
has to be cooperation between whoever starts rmid
and
the person who starts an activatable service that will use rmid
.
The simplest mechanism is to just turn the new security system
off. This was discussed briefly in Chapter 3
and means running rmid
with an additional argument
rmid -J-Dsun.rmi.activation.execPolicy=none
All that rmid
then checks is that any activatable service
that registers with it is started on the same machine as rmid
.
This is the weak security mechanism in the JDK 1.2 version of
rmid
, that assumes
that users on the same machine are co-operative.
The default new mechanism can also be set explicitly
rmid -J-Dsun.rmi.activation.execPolicy=default
This requires an additional security policy file that will be used
by rmid
, and the location of this policy file is also
given on the command line for rmid
.
For example, the following command will start rmid
using
the new default mechanism
with the policy file set to /usr/local/jini1_1/rmid.policy
rmid -J-Djava.security.policy=/usr/local/jini1_1/rmid.policy
The policy file used by rmid
is a standard JDK 1.2 policy
file and grants permissions to do various things. For rmid
,
the main permission that has to be granted is to use the various options to
the activation commands. Granting option permissions is done using
the com.sun.rmi.rmid.ExecOptionPermission
permission.
For example, reggie
is an activatable service.
To run this on my system, I use the command
java -jar /usr/local/jini1_1/lib/reggie.jar \
http://jannote.dstc.edu.au:8080/reggie-dl.jar \
/usr/local/jini1_1/example/lookup/policy \
/tmp/reggie_log public
To run this with the JDK 1.3 rmid
I need to place the
following in the security policy file
grant {
permission com.sun.rmi.rmid.ExecOptionPermission
"/usr/local/jini1_1/lib/reggie.jar";
permission com.sun.rmi.rmid.ExecOptionPermission
"-Djava.rmi.server.codebase=http://jannote.dstc.edu.au:8080/reggie-dl.jar";
permission com.sun.rmi.rmid.ExecOptionPermission
"-Djava.security.policy=/usr/local/jini1_1/example/lookup/policy";
permission com.sun.rmi.rmid.ExecOptionPermission "-cp";
};
The permissions granted are, in turn,
reggie
from other activation
services
reggie
rmid
will not run reggie
.
Wildcards can be used but this will reduce the amount of security that
rmid
has over the activatable services it looks after.
There is a mismatch between the command I type to get reggie
running and the contents of the policy file. Not all of the command line
arguments I type are in the policy file. For example, what has happened
to the /tmp/reggie_log
argument? Well, arguments like
/usr/local/jini1_1/example/lookup/policy
are
property overrides that are defined to the JVM
in the form -D
as in
-Djava.security.policy=/usr/local/jini1_1/example/lookup/policy
On the other hand, the argument /tmp/reggie_log
is just
a simple command-line argument and not a properrty override at all.
The property overrides need to go in the policy file, but the
ordinary command line arguments do not.
So do you have to go through each argument in turn, to decide if it
is a property? No, that would be too tedious. Instead, you start with
an empty policy file, start rmid
and then start an
activatable service such as reggie
. Generally, this will
fail, with an exception message such as
Unable to invoke by reflection, the method: com.sun.jini.reggie.CreateLookup.create.
An exception was thrown by the invoked method.
java.lang.reflect.InvocationTargetException: java.rmi.activation.ActivateFailedException: failed to activate object; nested exception is:
java.security.AccessControlException: access denied (com.sun.rmi.rmid.ExecOptionPermission -Djava.security.policy=/usr/local/jini1_1/example/lookup/policy)
java.security.AccessControlException: access denied (com.sun.rmi.rmid.ExecOptionPermission -Djava.security.policy=/usr/local/jini1_1/example/lookup/policy)
In this exception message is the phrase
com.sun.rmi.rmid.ExecOptionPermission
-Djava.security.policy=/usr/local/jini1_1/example/lookup/policy.
The person who wants to run reggie
must communicate
this information to the person who controls rmid
so that they can place this information in the rmid
policy file. This needs to iterate through a few cycles to build up the
complete set of permissions for reggie
. Hmmm, that's tedious
too... but there isn't any other way. The document
http://developer.java.sun.com/developer/products/jini/execpolicy.html
gives policy files for the Jini services reggie
,
mahalo
and FrontEndSpace
.
There is a third choice in mechanisms, and that is to give an object
that will be used to establish the security access, but that is beyond
the scope of this chapter. It is discussed in the JDK 1.3 documentation
for rmid
.
Jini applications download and execute code from other sources. These include
ServiceRegistrar
objects from
lookup services. They then call methods such as lookup()
and
register()
.
notify()
method of foreign code
In a ``safe'' environment where all code can be trusted, no safeguards need to
be employed. However, most environments carry some kind of risk from hostile
agents. An attack will consist of a hostile agent implementing one of the
known interfaces (of ServiceRegistrar
, of a well-known service such
as the transaction manager, or of RemoteEventListener
) with code
that does not implement the implied ``contract'' of the interface but instead
tries to perform malicious acts. These acts may not even be deliberately hostile:
most programmers make at least some errors, and these errors may result in risky
behaviour.
There are all sorts of malicious acts that can be performed. Hostile code can simply terminate the application, but it can read sensitive files, alter sensitive files, forge messages to other applications, perform denial of service attacks such as filling the screen with useless windows, and so on.
It doesn't take much reading about security issues to instill a strong sense of paranoia, and possible over-reaction to security threats. If you can trust everyone on your local network (which you are already doing if you run a number of common network services such as NFS), then the techniques discussed in this section are probably overkill. If you can't, then paranoia may be a good frame of mind to be in!
The Java 1.2 security model is based on the traditional idea of ``protection domains''.
In Java, a protection domain is associated to classes based on their
CodeSource
, which consists of the URL from which the class file
was loaded, plus a set of digital certificates used to sign the class files.
For example, the class files for the LookupLocator
class are in the
file jini-core.jar
(in the lib
directory of the Jini
distribution). This class has a protection domain associated with the
CodeSource
for jin-core.jar
. (So do all the other
classes defined in this file.)
Information about protection domains and code sources can be found by code such as
java.security.ProtectionDomain domain = registrar.
getClass().getProtectionDomain();
java.security.CodeSource codeSource = domain.getCodeSource();
Information about the digital signatures attached to code can be found by
Object [] signers = registrar.getClass().getSigners();
if (signers == null) {
System.out.println("No signers");
} else {
System.out.println("Signers");
for (int m = 0; m < signers.length; m++)
System.out.println(signers[m].toString());
}
By default, no class files or jar files have digital signatures attached.
Digital signatures can be created using keytool
(part of the
standard Java distribution). These signatures are stored in
a keystore. From there, they can be used to sign classes
and jar files using jarsigner
, exported to other keystores and
generally spread around. Certificates don't mean anything unless you believe
that they really do guarantee that they refer to the ``real'' person, and
certificate authorities such as Verisign provide validation
techniques for this.
The above description is horribly brief, and is mainly intended as reminders
to those who already understand this stuff. For this section, I just created
certificates as needed using keytool
, although there was no
independant authority to verify them. A good explanation
of this area is given by Bill Venners at
http://www.artima.com/insidejvm/ed2/ch03Security1.html
None of the Java file in the standard distribution are signed. None of the files in the Jini distribution are signed either. For most of these it probably won't matter, since they are local files.
However, all of the Jini jar files ending in
-dl.jar
are downloaded to clients and services across the network,
and are Sun implementations of ``well-known'' interfaces. For example,
the ServiceRegistrar
object that you get from discovery has its
class files defined in reggie-dl.jar
, as a
com.sun.jini.reggie.RegistrarImpl_Stub
object. Hostile code
implementing the ServiceRegistrar
interface can be written
quite easily. If there is the possibility that hostile versions of lookup
services (or other Sun-supplied services)
may be set running on your network, then you should
only accept implementations of ServiceRegistrar
if they are signed by an authority you trust.
Interfaces to services such as printers will eventually be decided, and will become ``well known''. There should be no need to sign these interface files for security reasons, but an authority may wish to sign them for, say, copyright reasons. Any implementations of these interfaces are a different matter. Just like the cases above, these implementation class files will come to client machines from other machines on the local or even remote networks. These are the files that can have malicious implementations. If this is a possibility, you should only accept implementations of the interfaces if they are signed by an authority you trust.
Permissions are granted to protection domains based on their codesource. In the Sun
implementation, this is done in the policy files, by grant
blocks
grant codeBase "url" signedBy "signer" {
...
}
When code executes, it belongs to the protection domains of all classes on the call
stack above it. So for example, when the ServiceRegistration
object
in the complete.FileClassifierServer
is executing the register()
method, the following classes are on the call stack:
com.sun.jini.reggie.RegistrarImpl_Stub
class from
reggie-dl.jar
complete.FileClassifierServer
class, from the call
discovered()
discovered()
method
The permissions for a executing code are generally the intersection of all the permissions of the protection domains it is running in. Classes in the Java system core grant all permissions, but if you restrict the permissions granted to your own application code, to core Jini classes, or to code that comes across the network, you restrict what an executing method can do. For example, if multicast request permission is not granted to the Jini core classes then discovery cannot take place. This permission needs to be granted to the application code and also to the Jini core classes.
It may not be immediately apparent what protection domains are active at any point. For example, in the call above of
registrar.getClass().getProtectionDomain()
I fell into the assumption that the reggie-dl.jar
domain was active because
the method was called on the registrar
object. But it isn't: while
the call getClass()
is made on the registrar
, this completes
and returns a Class
object so that the call is made on this object,
which by then is just running in the system, the appplication and the core Jini
classes domains.
There are two exceptions to the intersection rule:
the first is that the RMI security manager
grants SocketPermission
to connect back to their codebase host.
The second is that methods may call the AccessController.doPrivileged()
method. This essentially prunes the class call stack, discarding all classes below
this one for the duration of the call. This is to allow permissions based on
this class's methods, even though the permissions may not
be granted by classes earlier in the call chain. This allows some methods to
continue to work even though the application has not granted the permission, and
means that the application does not have to generally grant permissions required
only by a small subset of code. For example, the Socket
class
needs access to file permissions in order to allow methods such as
getOutputStream()
to function. By using doPrivileged()
,
the class can limit the ``security breakout'' to particular methods in a
controlled manner. If you are running with security access debugging turned on,
this explains how a large number of accesses are granted even though the application has
not given many of the permissions.
Adding all these bits of information together leads to security policy files that restrict possible attacks
A policy file based on this might be
keystore "file:/home/jan/.keystore", "JKS";
// Permissions for downloaded classes
grant signedBy "Jan" {
permission java.net.SocketPermission "137.92.11.117:1024-", "connect,accept,resolve";
};
// Permissions for the Jini classes
grant codeBase "file:/home/jan/tmpdir/jini1_1/lib/-" signedBy "Jini" {
// The Jini classes shouldn't require more than these
permission java.util.PropertyPermission "net.jini.discovery.*", "read";
permission net.jini.discovery.DiscoveryPermission "*";
// multicast request address
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
// multicast announcement address
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
// RMI and HTTP
permission java.net.SocketPermission "127.0.0.1:1024-", "connect,accept";
permission java.net.SocketPermission "*.canberra.edu.au:1024-", "connect,accept";
permission java.net.SocketPermission "137.92.11.*:1024-", "connect,accept,resolve";
permission java.net.SocketPermission "130.102.176.*:1024-", "connect,accept,resolve";
permission java.net.SocketPermission "130.102.176.249:1024-", "connect,accept,resolve";
// permission java.net.SocketPermission "137.92.11.117:1024-", "connect,accept,resolve";
// debugging
permission java.lang.RuntimePermission "getProtectionDomain";
};
// Permissions for the application classes
grant codeBase "file:/home/jan/projects/jini/doc/-" {
permission java.util.PropertyPermission "net.jini.discovery.*", "read";
permission net.jini.discovery.DiscoveryPermission "*";
// multicast request address
permission java.net.SocketPermission "224.0.1.85", "connect,accept";
// multicast announcement address
permission java.net.SocketPermission "224.0.1.84", "connect,accept";
// RMI and HTTP
permission java.net.SocketPermission "127.0.0.1:1024-", "connect,accept";
permission java.net.SocketPermission "*.canberra.edu.au:1024-", "connect,accept";
permission java.net.SocketPermission "137.92.11.*:1024-", "connect,accept,resolve";
permission java.net.SocketPermission "130.102.176.*:1024-", "connect,accept,resolve";
permission java.net.SocketPermission "130.102.176.249:1024-", "connect,accept,resolve";
// permission java.net.SocketPermission "137.92.11.117:1024-", "connect,accept,resolve";
// debugging
permission java.lang.RuntimePermission "getProtectionDomain";
// Add in any file, etc, permissions needed by the application classes
};
If you found this chapter of value, the full book is available from APress or Amazon . There is a review of the book at Java Zone