Java's creators have often highlighted the JVM's three biggest benefits: automatic garbage collection, built-in security, and the ability to load classes from any network location. Automatic garbage collection is now taken for granted in most modern programming environments. However, network-mobile classloading and the Java security infrastructure have evolved on separate, parallel paths. As a result, completely satisfying the security needs of distributed systems based on mobile Java code has thus far been impossible. Jini's security model extends both Java's mobile code and security features, and combines their strengths, enabling secure distributed computing with mobile code.
The Jini security infrastructure has been in the works for almost two years. Starting in 2001, Sun's Jini team took the unusual step of releasing periodic snapshots of the early specifications and implementation to elicit feedback from the Jini community. Since then, the security project has also been a Jini community effort under the project name "Davis." At the time of this writing, the specifications resulting from Davis are winding their way through the Jini Decision Process. By the time you read this, most likely, both Jini community houses have approved the specifications, and they have become official Jini standards.
While no special contract or license prescribes the use of the new security model, many in the Jini community believe that security-conscious Jini projects will use these new specifications and APIs. The new specifications form the key innovations in Sun's Jini Starter Kit (JSK) 2.0. Thus, I refer to the security framework in Sun Microsystems' JSK 2.0 as "the" Jini security framework, even though other security-related Jini projects may spring up in the future. In this article, I describe the Jini security model and give you a taste of the new security-related APIs.
An overview of Jini security
Perhaps the best news about the Jini security framework is that it requires few changes to existing Jini services. Instead, it defines security as a deployment-time option. If you have an existing Jini service, you can take advantage of utilities in the new JSK to deploy that service securely. Jini security, therefore, shares that similarity with J2EE (Java 2 Platform, Enterprise Edition) security—it primarily belongs to the domains of system architecture and administration.
The basic unit of a Jini service remains the service proxy you register in lookup services. When clients discover that proxy—based on the interfaces the proxy implements—code for the proxy object not already available at the client downloads into the client following RMI (Remote Method Invocation) mobile code semantics. Following that, the client invokes methods on the proxy. As a client, you don't care or know how the proxy implements those methods: some proxy methods may involve network communication with a remote service implementation, whereas others might execute locally to the proxy.
The Jini security model adds three steps to that simple programming model: First, as a client, it allows you to decide whether to trust a proxy object at all. If a service proxy is supposed to represent your bank account, how do you know the proxy you just downloaded is in fact your account's proxy? The Jini security framework helps you decide that.
Next, once you place some trust in a downloaded proxy, you must ensure the proxy offers some guarantees in performing its work. If you want to deposit money in your account by invoking a method on that proxy, you may want to perform that action only if the proxy can guarantee the integrity and confidentiality of the information traversing the network. The Jini security model lets you place such constraints on a proxy.
Finally, you want to grant privileges to a proxy based on the trust you placed in that proxy. If your bank account requires a network connection to a utility company's server to help pay your bills, you must grant that connection permission to the proxy. On the other hand, if that proxy wishes to reformat your hard disk, you want to deny it that permission. The Jini security model's final aspect is its ability to dynamically grant permissions to a downloaded service proxy.
Rather than making programming changes to your proxy, you inject security-related information to that proxy at the time you make your service available on the network. Making a service available for remote invocation is broadly referred to as exporting that service. The Jini security framework defines a new exporting mechanism that supports security-conscious exporting of a Jini service.
That exporting mechanism, in turn, relies on a set of configuration options you must specify for a securely deployed Jini service. The security-related APIs define what configuration you may specify and provide static methods that rely on that configuration information to perform security-related tasks. JSK 2.0 employs configuration files when specifying service-deployment information. In the future, Jini-aware application servers will likely provide graphical user interfaces to allow an administrator to set deployment-time service options. Figure 1 shows an overview of the Jini security model, and its exporting and configuration infrastructure.
To choose your services' configuration options, you must understand how those options relate to the Jini security model's various aspects.
Constrain your proxy
To appreciate the problems the Jini security infrastructure solves, consider the communication patterns of a system built on network-mobile code. Figure 2 below illustrates the movement of objects and data in the presence of a distributed Jini system. First, the client downloads into its address space a proxy object for the service, including that proxy's code. It then invokes methods on that proxy, passing in method parameters, some of which may refer to other remote objects. While a Jini proxy may perform all of its computations locally to the client's address space, this example illustrates a remote service implementation. Code for any third-party objects that interact with the proxy also downloads into the client's address space.
As a Jini service operator, when a client downloads your service's proxy, you may want to restrict how a client—or what client—can invoke your proxy's methods. You may require, for instance, that a client authenticate itself before it invokes a method, and you may refuse access to clients you either don't recognize or don't want to interact with.
Similarly, a client may also place constraints on your service's proxy: it may require server authentication before invoking the service, or it may insist the proxy guarantee the integrity of data it sends back to a remote service implementation.
Observe that all interaction between client and server occurs through proxy objects. Therefore, to impose security restrictions on either the client or the server, those restrictions must be placed on their respective proxies.
In the Jini security model, to constrain a proxy, that proxy must implement the
net.jini.core.constraint.RemoteMethodControl interface in addition to the other application-specific interfaces it implements. In a typical deployment scenario, you use a smart proxy that implements that interface and embed your original service proxy into that constraint-aware wrapper. That allows you to leave the original proxy intact. This article's conclusion illustrates such a utility.
RemoteMethodControl defines a method to set an entire collection of constraints on a proxy:
public RemoteMethodControl setConstraints(MethodConstraints constraints);
When invoked, that method returns a copy of the proxy, with the set of constraints injected into it. Both the server and client can inject constraints on a service proxy: the server, before exporting the proxy, and the client, after retrieving the proxy from the network.
As its name suggests,
RemoteMethodControl constrains a proxy on the granularity of method calls.
MethodConstraints is but an immutable map of methods and a set of constraints on those methods. That map contains
java.lang.reflect.Methods as keys, each with an instance of
InvocationConstraints as value.
InvocationConstraints, in turn, is a collection of required and preferred constraints. When making decisions based on a proxy's constraints, all required constraints must be satisfied. In addition, all preferred constraints should be satisfied, if possible. If all required constraints cannot be satisfied, the method to which the constraint applies will not be invoked, and a
java.rmi.ConnectIOException will be thrown instead.
The constraints themselves are expressed as implementations of the
net.jini.core.constraint.InvocationConstraint marker interface. You can create any constraint you'd like. Note, however, that the client and server must both understand a proxy's constraints. If either encounters an unknown constraint type, it won't perform the method call since it can't possibly satisfy that unknown constraint.
Some constraints express requirements, whereas others might convey restrictions. For instance, a constraint might say, "I require you to authenticate," whereas another might say, "I'd like to stay anonymous and do not want to be authenticated." Conflicting constraints indicate a discord between the client's and server's requirements; the method call then won't proceed.
The JSK 2.0 defines four trust-related constraints:
Integrity:Ensures the integrity of network objects. That includes both data and classfile integrity (more on the latter below).
ServerAuthentication:Proves the subject on whose behalf the server executes. If the client does not trust that subject, the call terminates.
ClientAuthentication:Proves the subject on whose behalf the client executes. The server does not perform method invocation for an untrusted client.
Delegation:If a server needs to invoke third parties in the client's name, delegation allows the server to authenticate as the client.
If your Jini service wanted each client to authenticate, you would inject
ClientAuthentication.YES into your server's proxy when exporting that proxy. Likewise, if the client wanted to ensure your proxy guards a service's data and code integrity, it would enter
Integrity.YES into the service proxy once it downloaded that proxy from the network.
Another set of
InvocationConstraints define the principals a client or server must authenticate as:
ClientMinPrincipal:The server uses this constraint to tell a client it must authenticate at least as the specified set of principals. If your Jini service specifies the
ClientAuthenticationconstraint on the client, then it can specify this additional constraint to require, for example, that the client authenticate as
ClientMaxPrincipal:A client uses this constraint to limit how much information it reveals about itself to the server. While a client may authenticate as several principals, that client may want to limit which of those principals it reveals to the server.
ClientMinPrincipalType:When authenticating a client, the server can require that the client authenticate at least as one of the types of principals in the specified set. For instance, suppose you want to only expose your Jini service to clients that belong to your own company. Thus, you may require that one of the client's authenticated principals be a
com.mycompany.GeneralPrincipaltype. Each organization's department might create subtypes of that principal class, and all those subtypes would be acceptable to your Jini service.
ClientMaxPrincipalType:A client uses this constraint to limit how much information it reveals about itself to the server. For instance, if your client authenticates as both
com.goodcompany.CompanyPrincipal, the client may decide to reveal only its former identity to the service.
ServerMinPrincipal:If the client stipulates the
ServerAuthenticationconstraint, this additional constraint requires that the server authenticate at least as the specified set of principals.
The invocation constraint mechanism is not limited to specifying security-related requirements: the client or server may specify any kind of constraint on a remote invocation. The
net.jini.core.constraint package contains a few additional constraints related to quality of service:
ConnectionRelativeTime:Controls the maximum amount of time to wait for a proxy to establish a network connection to the remote service. For instance, your client might specify to your service's proxy that it wants to wait no longer than five seconds to establish a connection back to a remote implementation. If your service is too busy serving other clients, the time-conscious client might move on to another instance of the equivalent Jini service.
ConnectionAbsoluteTime:Controls the absolute point in time by which a network connection must be established.
Learn to trust a proxy
Notice that the client places its own security constraints in the downloaded Jini service proxy. In doing so, it relies on the proxy object to act in accord with those constraints. For instance, if the client wants the service to authenticate as a given principal, it must rely on the proxy object itself to perform that authentication. That is a direct consequence of the Jini architecture, where all network communication occurs through proxies. Since the code for that Jini service proxy can originate from any source, how can a client trust that proxy to follow the constraints placed on it?
The following analogy illustrates the challenges involved in entrusting a downloaded proxy object and the Jini solution to the problem: You receive a large package at your front door. Since you are not expecting a package, you want to verify its contents before opening it. But how do you verify the package's contents without first peeking inside?
Your only initial clue is the package's shipping label, which includes the sender's address and telephone number. You call that number, and, when the other party identifies itself, realize this package must be the mail-order product you ordered over the phone a couple of weeks before. Upon presenting the order number from the shipping label, the mail-order company confirms that the package is, indeed, from them. Since you trusted the company with your order, its customer representative confirming the shipment is enough assurance for you to go ahead and open the package. By phoning the mail-order company, you turned the trust question around: in essence, you asked the mail-order company if they trusted the package.
Trusting a downloaded Jini proxy poses a similar challenge: you must decide whether to trust a proxy object, but since that proxy's code may originate from untrusted network locations, you can't use the proxy itself in making that trust decision.
The key to solving that problem is to distinguish between untrusted code and an untrusted object. If some of the proxy's code is already available locally at the client and not downloaded from the network, you may have already decided to trust some code that makes up the proxy, even though you do not yet trust the proxy object itself. In the package analogy, you decided to trust the shipping label; with that information in hand, you contacted the sender and verified the package's contents.
The bootstrap proxy and the verifier
Thus, the first step in establishing proxy trust is to ask the proxy for an object made up entirely of trusted, local code. Once you've verified that all the code for that object is local and trusted by you, you call back to the server through that object, asking the server to authenticate itself.
Once you've obtained that small call-back, or bootstrap, proxy from the Jini service proxy, you can use the constraint mechanism on that bootstrap proxy to request server authentication and ensure the data's integrity on the network. Since the bootstrap proxy is composed of only trusted local code, you can be sure that it acts in accord with those constraints.
If the bootstrap proxy's call to the server succeeded—with server authentication and data integrity guaranteed—the server returns a verifier object. That verifier object enables the server to determine whether it trusts the service proxy. In essence, that verifier object is analogous to the shipping clerk requesting your order number. The shipping company's package trust "algorithm" is simply looking up the order number in a database of sent packages. A Jini service can use any algorithm to determine proxy trust (see the next section for one offered by the Jini security APIs).
Once the service vouches for the proxy object, you can start trusting the proxy, at least to the degree you trust the service, and assign access control policies to the proxy based on that trust. Figure 3 describes the steps of learning to trust a downloaded service proxy.
While most of the details are hidden from a developer, it is helpful to know how trust verification occurs on the API level. To ask the Jini service proxy for a bootstrap proxy, the service proxy must implement a special method in addition to the methods defined by its service interface:
The client uses reflection to discover that method. The
ProxyTrustIterator, in turn, may produce other proxy trust iterators that ultimately produce the bootstrap proxy object (or objects).
The bootstrap proxy, with trusted local code only, does not implement all of the service's interfaces. Instead, it must implement the
net.jini.security.proxytrust.ProxyTrust interface only and its single method:
TrustVerifier getVerifier() throws RemoteException;
That method is remote: it performs a remote call back to the server, asking that server for the
TrustVerifier suitable for verifying that service's proxy. Because that method invocation must travel through the network, it is essential that, first, the server authenticates itself and, second, that the bootstrap proxy and server ensure the integrity of both the data and the code on the wire when obtaining the verifier. (See below for a technique to ensure code integrity.)
TrustVerifier is a local object (to the client), and its only job is to determine whether a given Jini proxy can be trusted. Its single method is:
boolean isTrustedObject(Object jiniProxy, TrustVerifier.Context context)
TrustVerifier.Context parameter is used to pass context to the verifier, including a classloader that allows the loading of classes trusted for the verification purposes.
As a Jini service provider, you are free to implement your verifier to follow any algorithm for verifying your proxy. Because the client relies on you, the service provider, to determine trust for your own proxy, you should implement a proxy trust algorithm that can establish trust with a high degree of certainty.
The Jini security infrastructure provides a proxy trust algorithm that relies on object equality semantics: the verifier your server returns to the bootstrap proxy embeds a proxy canonical instance—a proxy you know is correct. The verifier then compares that canonical proxy with the client's downloaded proxy. If the two match, then the downloaded proxy can be trusted as much as the canonical one.
To use that algorithm, your Jini service proxy implements the
net.jini.security.proxytrust.TrustEquivalence interface and its single method:
boolean checkTrustEquivalence(Object obj)
The verifier you send to a client contains a canonical version of your Jini service proxy. When the verifier invokes
isTrustedObject(), it passes an instance of the downloaded Jini service proxy to the canonical proxy's
checkTrustEquivalence() method. Your canonical proxy then determines if the downloaded proxy is equivalent in trust to the canonical proxy. Figure 4 illustrates that process.
Ensure code integrity with HTTPMD URLs
When the bootstrap proxy asks the server for a verifier object, the RMI code mobility semantics apply to that interaction. The codebase is annotated into the marshalled (serialized) object as a series of URLs where the verifier's classes can download from. The client creates a classloader for each codebase annotation and attempts to load the needed classes from the network. In other words, that code download will likely use a separate network connection from the one used for the RMI call requesting the verifier object: the code for the verifier moves into the client out of band, typically via an HTTP connection. So no one can temper with the code while those classfiles move across the network, that out-of-band network connection must be secure as well.
The traditional means of securing an HTTP connection is via the TLS/SSL (Transport-Level Security/Secure Sockets Layer) protocol. While that is a viable option, it is also overkill for the purposes of supporting secure mobile code. SSL connections rely on a public key infrastructure to authenticate the server. Since a Jini service's bootstrap proxy is, in essence, part of the server, that bootstrap proxy does not have to care about where the code comes from, only whether the code received is identical to the code the server sent (whatever that server's identity may be). In addition, many HTTP servers running for the sole purpose of supporting mobile code may not have public key certificates issued by a recognized certificate authority.
One solution to ensuring code integrity while not requiring fully encrypted HTTP connections is to compute a cryptographically strong message digest on each jar file comprising the codebase. Each message digest is a long string value tied to the jar file's contents. Using a cryptographic algorithm to compute the message digest, it is unlikely two jar files with different content will yield the same message digest.
When the server annotates the codebase to the verifier's marshalled stream, it appends the message digest to the codebase URL along with the cryptographic algorithm's name used to compute that message digest. Upon downloading the needed jar files from the codebase URLs, the bootstrap proxy computes the message digest(s) for the jar files using the specified algorithm. If the value obtained from that calculation matches the value specified on the codebase URL, the bootstrap proxy can be confident about the integrity of the verifier objects' jar files. HTTP URLs with a message digest attached to them are termed HTTPMD URLs. Figure 5 illustrates the use of HTTPMD URLs.
net.jini.url.httpmd package provides several classes for computing message digests and for working with HTTPMD URLs. HTTPMD URLs prove useful for ensuring code integrity in any situation where mobile code is present and their use is not restricted to proxy verifier downloading.
Dynamically grant permissions
Once the verifier vouches for the integrity of the downloaded proxy, a Jini service client can decide whether to trust that proxy. Of course, if you're a service client, you may not trust a Jini service provider with full access to your client resources. Therefore, once you verify trust in the proxy, you must grant permissions to the downloaded proxy according to your trust level in the service provider.
In J2SE (Java 2 Platform, Standard Edition), the permissions granted to code are determined by what JVM protection domain that code belongs to. J2SE 1.4 defines a protection domain by a code's source (where the code comes from and who signed that code), by a principal executing the code, and by the classloader that loaded the code into the VM. A classloader can place classes into several protection domains based on the policy in effect at the time of code loading. Thus, a single classloader may be associated with multiple protection domains.
Because Jini trust is decided on the level of an object (the service proxy), not just code, and because you may not know in advance where code from a service proxy may download from, you must be able to grant permissions dynamically to all the protection domains of the proxy's classes.
What permissions are available to a protection domain is determined by the JVM's policy provider. A typical policy provider obtains policy information from static policy files. As an application's code requests access to various resources, the policy provider decides whether to grant those permissions based on the contents of that static policy file, the protection domains where the requesting code resides, and, possibly, the principal executing the code.
The Jini security framework provides a policy provider that not only supports policies sourced from static files, but also dynamic permission grants given at runtime. The
net.jini.security.policy.DynamicPolicy interface defines a policy provider with the capability to assign a set of permissions to all protection domains associated with a class's classloader:
void grant(Class cl, Principal principals, Permission permission)
That method grants the specified set of permissions to all the protection domains that have at least the specified set of principals and are associated with the classloader that loaded
net.jini.security.policy.DynamicPolicyProvider class implements
DynamicPolicy and wraps a file-based, static policy provider (
Stronger Jini security
While establishing proxy trust, assigning constraints to proxies, and granting dynamic permissions seem at first complex and laborious tasks, the Jini security model automates most of that work via configuration files and static methods that call into those configurations. In addition, dynamic proxies alleviate the need to alter existing Jini service proxies to conform to the security model.
The Jini security APIs also provide utilities that help conform with the new security model's requirements. The
net.jini.security.ProxyPreparer interface defines a single method that combines proxy trust, constraint placement, and dynamic permission grants. Its single method produces a version of the downloaded Jini service proxy that conforms to a client's security requirements:
Object prepareProxy(Object proxyObject)
You obtain an instance of
ProxyPreparer from an object that represents the configuration of your Jini service, or client:
net.jini.config.Configuration. That class, in turn, relies on whatever configuration information defines your system's security requirements. A forthcoming Jiniology article will introduce you to the art of creating and managing a secure Jini service's configuration.