HomeDigital EditionSys-Con RadioSearch Java Cd
Advanced Java AWT Book Reviews/Excerpts Client Server Corba Editorials Embedded Java Enterprise Java IDE's Industry Watch Integration Interviews Java Applet Java & Databases Java & Web Services Java Fundamentals Java Native Interface Java Servlets Java Beans J2ME Libraries .NET Object Orientation Observations/IMHO Product Reviews Scalability & Performance Security Server Side Source Code Straight Talking Swing Threads Using Java with others Wireless XML
 

A Practical Solution For The Deployment Of JavaServer Pages, by Alexis Grandemange

A Client Company implements an archive-downloading package. It downloads presentation archives from its providers, Server Companies 1 and 2. Server Company 1 archive includes Enterprise JavaBean client code; Server Company 2 includes Java Message Service (JMS) client code. The archives are either JAR files or Web Archive (WAR) files.

Since Client Company downloads its archives through the Internet:

  1. It wants to be sure downloaded archives can be sent only by its identified providers.
  2. It wants to be aware of the security requirements of its providers. For instance, does a downloaded archive need to write or remove files?
The first article in this series (JDJ, Vol. 6, issue 1) described how to download servlets and JSP archives like applets. Part 2 (JDJ, Vol. 6, issue 2) showed how to administrate these presentation archives remotely, in particular to force a refresh at a scheduled time. I also indicated how to handle special cases, such as resources.

The last issue to address is security. Let's consider the situation presented above, illustrated in Figure 1.


Figure 1

The solution I present to address these requirements is to enhance the archive-downloading package to host archives in sandboxes in the way that browsers host applets. I used the standard Java 2 security described in the Java Security Architecture document downloadable from http://java.sun.com/products/jdk/1.2/docs/guide/security/spec/security-spec.doc.html.

Java 2 Security
Figure 2 depicts Java 2 security components. The hub component is the Security Manager. Its purpose is to determine whether particular operations should be permitted or denied. The Security Manager is implemented by SecurityManager class and subclasses, which contain methods with names that begin with the word check. These methods are called by various methods in the Java libraries, including Java API, before those methods perform potentially sensitive operations. The invocation of such a check method typically looks like this:


Figure 2
SecurityManager security = System.getSecurityManager();
if (security != null) { security.checkXXX(argument, . . . ); }
There is at most one active SecurityManager instance that a method retrieves with System.getSecurityManager(). If getSecurityManager() returns null, the method doesn't check and is slightly faster. This is often the case in Java application servers. SecurityManager check methods rely on another component, the Access Controller, and call AccessController.checkPermission(perm).

The AccessController class manages Java 2 security, whereas the Security Manager that already existed in Java 1 remains mainly for upward compatibility.

A fundamental concept of Java 2 security is the protection domain. A domain encloses a set of classes whose instances are granted the same set of permissions. Permissions here are instances of Permission subclasses. They represent access to a system resource. For instance, the permission to read a file C:\TEMP\FileAccess.txt can be produced by:

new java.io.FilePermission("C:/TEMP/
FileAccess.txt", "read").
As you could guess, a protection domain is implemented by a ProtectionDomain class, whose constructor is ProtectionDomain(CodeSource codesource, PermissionCollection permissions). Let's start with the simplest parameter, permissions. This is simply the collection of the permissions the protection domain will have. The parameter of codesource is slightly more complex. It represents the origin of the protection domain classes and its credentials. Codesource is characterized by a set of public keys and a codebase URL, and its constructor is CodeSource(URL url, Certificate[] certs).

I need to give a short explanation of the credential issue here. To be sure a piece of code is coming from a source, we must check whether it contains something only the source can generate. The most common and standard mechanism is asymmetric keys. The source encrypts a signature with a private key and the destination uses a corresponding public key to decrypt and check that the signature is correct. It retrieves the public key from a certificate, which certifies that the key belongs to the source. As only the source has the private key, it is the only entity able to generate a signature that can be decrypted with the public key. It is the solution implemented in Java, and Certificate is a class wrapping a certificate.

A last point to consider: we could build our PermissionCollection programmatically, but it wouldn't be flexible and convenient. A better solution would be to declare somewhere that code coming from a given source with a given signature is granted a set of permissions. This is the purpose of the policies, implemented as Policy subclasses. A policy is a way to get a PermissionCollection, given a CodeSource, based on a configuration file or database. Sun provides a default policy implementation, PolicyFile, that relies on configuration files.

Java security is comprehensive and well documented. Accordingly, I used it to support sandboxes inside a Java server in a way that was as close as possible to the applet distribution model.

Use
A Server Company generates a key pair and stores it in a key store, using the keytool command as indicated in Listing 1. (Note: All listings appear below) Here it creates a key pair whose alias is hello. It's protected by a password helloPswd and stored in a key store named D:\JSPservlet\keystore. The Server Company then signs its archive with "jarsigner," for instance:

jarsigner -keystore D:\JSPservlet\keystore -storepass keystorePswd -keypass helloPswd helloMisc.jar hello
to sign a helloMisc.jar archive, using its hello key pair.

The Server Company also describes which authorizations the archive needs to run properly in a standard policy file. For instance, it describes the helloMisc.jar security requirement in Listing 2. This policy states that the archive classes, represented here by their codesource, require all permissions. It also states that archive classes must be signed with the public key defined in key store and aliased by hello.

The Client Company administrator imports the Server Company certificate into its key store. It can verify the required permissions in the policy file and check the certificate with the commands in Listing 3. If it agrees on permissions and certificates, it can add them to its environment without restarting its Java server because the archive-downloading package finds the policy files and key stores in the cache directory specified by the JSPservlet deployment descriptor. More precisely, the package first looks for an archive.policy file or, if that doesn't exist, for a java.policy file. Therefore, the administrator can choose to merge policy files of different providers or to keep them separate.

Once the administrator has completed this task, it or the Server Company can initiate a first download and users can access the Web application.

Implementation
Let's start with a reminder of the tool structure (see Figure 3).


Figure 3

JSPservlet, a special servlet, handles HTTP requests toward a Web application and forwards them to target servlets and JSPs with the help of a set of objects:

  1. JSPhandler objects manage Web applications and maintain a ClassEntry map. They also cache initialization parameters.
  2. ClassEntry objects manage archives and maintain a cache of target objects.
  3. JSPloader objects are target servlets class loaders and maintain a cache of target classes.
The security support is implemented in the class loader, JSPloader.

It implies, however, a minor modification of JSPhandler to support another parameter, allPermissionPolicy. This parameter has two functions: first, if present, it means that the archive classes must run in a sandbox; second, it gives the name of a default policy file that's used if the Java server doesn't set a Security Manager, which is often the case.

Setting a Security Manager is a JVM-wide action. As we saw before, if it isn't set, then it can't be called by Java API and hence can't get the opportunity to invoke the Access Controller and to enforce a policy. So we need to set a Security Manager if no one is active, but before we have to set a policy. Otherwise the Java server will get an AccessControlException, because Java 2 security doesn't have a built-in concept that local code is trusted. Therefore we need to grant our Java server permissions such as the right to read local disks. Listing 4 is the simplest allPermissionPolicy file we can define. It grants all permissions to all classes, which is the same behavior as running without a Security Manager.

First let's look at the constructor in Listing 5. If allPermissionPolicy property isn't set, it loads the archive classes and resources and returns without attempting to apply a security. Otherwise, if a Security Manager isn't set, it creates a new policy with new sun.security.provider.PolicyFile(). It must be set before a java.security.policy system property that PolicyFile constructor uses to locate the policy file. Next the constructor sets this policy as the current policy and creates the security manager.

The constructor then creates a base URL, which is used later in the classes definition. We'll examine this point shortly. For the moment, simply note that the base URL is made up of the archive download location and the archive name. It's the codeBase the administrator specifies in the archive policy file, as you can see in Listing 2.

The next step of the constructor consists of loading the archive policy file from either archive.policy or java.policy, as described above. It creates a policy but doesn't set it as the current policy: the Access Controller continues to apply allPermissionPolicy.

Eventually the constructor builds classes through the invocation of loadClassDataFS(). This method gets a JarInputStream on a local copy of the remote archive and passes it to a parseStream() method shown in Listing 6. parseStream(), which defines classes and implements the sandbox, relies on the ability to define a class inside a Protection Domain. If classes inside the domain are trying to do something the domain isn't allowed to do, the Access Controller throws a java.security.AccessControlException.

Let's look at a back trace (see Figure 4). Here I tried to run a TestServlet.FileAccess servlet from an unsigned archive. TestServlet.FileAccess invoked File.exists(). File.exists() invoked SecurityManager.checkRead(), which invoked AccessController.CheckPermission().AccessController.CheckPermission() found that the Protection domain didn't have the needed permission and threw a java.security.AccessControlException.


Figure 4

The interesting part of parseStream() starts when it has identified a class. If allPermissionPolicy property isn't set, no sandbox is enforced and parseStream() defines the class with defineClass(name, buf, 0, buf.length). Otherwise it extracts the class certificates with JarEntry.getCertificates(). If it finds no certificate, it defines the class in a protectionDomain() domain. Though this domain has no certificates, it doesn't necessarily mean it has no permissions. If the domain codesource was granted permissions without a signature (SignedBy parameter), such as in the Listing 7 policy file, policy.getPermissions() returns these permissions.

To handle a situation in which JarEntry.getCertificates() found certificates, parseStream() maintains a protectionDomains HashMap whose keys are certificates arrays and values are Protection Domains. Thanks to this HashMap, parseStream() uses only one Protection Domain per certificate array. It enumerates protectionDomains and checks to see if a domain already has the same certificate array. If it does, parseStream() defines the class in this domain. Otherwise it creates a new Protection Domain with the permissions returned by policy.getPermissions(), defines the class in this domain, and records the new domain in protectionDomains. The new Protection Domain can be granted permissions because a certificate matched a signature as seen in the Listing 2 policy file.

Callback Issue
When the servlet container invokes JSPservlet, which invokes the target servlet, the solution we presented above is enough. However, we also have to handle the case when the target servlet asks for a resource or a class, or calls back JSPservlet for another servlet using RequestDispatcher.include() or RequestDispatcher.forward().

The callback is likely to fail in a disconcerting way. If you call servlet A first, you get an AccessControlException in a class loader; if you call it again, you get AccessControlException in JSPservlet. If you invoke servlet B before servlet A, however, it works fine.

This problem happens because the target servlet code calls a class - JSPservlet, for example - requiring privileges it doesn't have. As we saw above, AccessController.CheckPermission is invoked. It checks to see that all code traversed by the execution thread up to its call has permission for that access. When the target servlet calls back JSPservlet, a piece of code - the target servlet itself - doesn't have the proper permission and AccessController throws an AccessControlException. The problem occurs randomly because, depending on the invocation order, resources have or have not already been accessed.

To address this issue, we need to use "privileged" code (see Figure 5). Let's refine the explanation above. AccessController checks whether all code traversed by the execution thread has permission for that access unless some code on the thread has been marked privileged. If a caller whose code is granted a permission is marked privileged and all code invoked by this caller also has this permission, then AccessController allows the access.


Figure 5

We granted allPermissionPolicy permissions to the tool code, so the only thing we need to do is mark the code privileged when it can be called by the target servlets at resource retrieval, class retrieval, and servlet handling. To do this we must use AccessController.doPrivileged. This method is thoroughly described in the JDK doc.

Listing 8 shows how I mark servlet handling as protected. As I use an anonymous inner class, I must declare local variables used in the privileged block as final. I also use PrivilegedExceptionAction interface to handle exceptions raised in the privileged block. Certificate Authority Considerations

Let's assume you are the server company administrator. You run the Listing 1 command to populate your key store with a self-signed certificate, and a private-key JARsigner needs to sign the archive. You should:

  1. Never distribute this key store, as it contains the private key allowing signing archives.
  2. Keep the key store in a safe location. If you lose it, you'll never be able to sign archives again, and if someone with malicious intent reads it, your security will be compromised.
Let's assume your customers accept only certificates issued by a given Certificate Authority (CA). You need to get a certificate that is issued by this CA and that wraps your public key, whose corresponding private key is known only to you. To do it, first build a PKCS#10 certificate request with:
keytool -certreq -alias alias -keystore keystore.

PKCS stands for Public-Key Cryptography Standards, which are RSA Laboratories specifications. PKCS#10 specifies the Certificate Request (CR) standard. Once you have your CR, you query a certificate to the CA, typically through an HTML form that prompts you for your CR. You then import this certificate in your key store with the command:

keytool -import -file certificate_issued_by_CA -alias alias -keystore keystore.
You also send the certificate to your customers, who import it in their key stores using the same keytool command. It's important to note the difference between your and your customers' key stores. Yours contains both the certificate and the private key. You can sign archives. Theirs contains only the certificate. They can check only archive signatures.

Now consider the distribution of a signed archive. Assume that many customers have anonymously downloaded a signed archive that someone published on a repository. They followed instructions, installed the certificate in their key store, and put the archive in production. Later someone revokes the certificate. If a CRL checking mechanism is embedded in the solution, it's possible to disable the archive classes without impacting other Web applications or needing to stop the Java server.

We can implement this mechanism using freely downloadable material because:

  1. Revoked certificates are stored in Certificate Revocation Lists (CRL), generally accessible through Lightweight Directory Access Protocol (LDAP).
  2. LDAP repositories and keytool use X509 certificates and CRL.
  3. Java 2 provides helper objects for X509 certificates and CRL.
  4. JNDI supports LDAP.
Listing 9 presents a CRLchecker class, which checks CRLs. Its core method, refresh(), gets an initial Directory Context, providing parameters like the LDAP URL, where the CRL is defined; the principal; and the password to use to connect to the CA directory. It then retrieves the CRL in a certificateRevocationList context attribute, creates a Java X509CRL object, and populates it with the attribute value.

The refresh() method is invoked by the constructor and by getNextUpdate(), whose purpose is first to refresh the repository periodically and second to return the next scheduled CRL update. CRL update can become complicated and expensive, especially if many CAs are involved. Most revocations aren't critical. An employee certificate, for example, can be revoked when the employee moves to another department. It's therefore often practical to update the CRL only once a day or once a week. As it's also expensive for an application to poll the CRL repository, CRL standard specifies a next update field containing a date the application can use as a hint to poll the repository.

The last CRLchecker method, check(), uses CRL getRevokedCertificate to learn whether a certificate has been revoked using its serial number. It throws an exception if this is the case.

Summary
The Java 2 framework has the flexibility required to implement sandboxes in an application server, still relying on the Java 2 policy files, keytool, and JARsigner. It also provides classes to check on whether the credentials you use are still valid. The major difficulty in this area is that it encompasses traditionally separate spheres of knowledge.

Regarding the requirement of supporting archives that are downloaded like applets, we see that this method clearly enhances its area of application. You can select Web applications on the Web and download them in your application servers, local or remote, without compromising your security. Readers can go to http://pagebox.net/ASversion.htm for sources, documentation, and binaries for Tomcat and Resin.

Author Bio
Alexis Grandemange is an architect and system designer. A Java programmer since 1996 with a background in C++ and COM, his main interest is J2EE with a focus on design, optimization, and performance issues.
[email protected]

Download Assoicated Source Files (Zip format ~ 4.58 KB)

 

All Rights Reserved
Copyright ©  2004 SYS-CON Media, Inc.
  E-mail: [email protected]

Java and Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries. SYS-CON Publications, Inc. is independent of Sun Microsystems, Inc.