|
|
|
|
|
|
A practical solution for the deployment of Java Server Pages (3)In a first article, I described how to download servlet and JSPs archives like applets. In a follow-up, I showed how to remotely administrate these presentation archives, in particular to force a refresh at a scheduled time. I also presented how to handle special cases such as resources.
However we have a last issue to address, security that is the subject of this last article. Let’s consider the following situation:
Figure 1: Sandbox need A client company implements the archive-downloading package. It downloads presentation archives from its providers, Server Company 1 and 2. Server Company 1 archive includes Enterprise Java Bean (EJB) client code and Server Company 2 includes Java Messaging Service (JMS) client code. The archives are either jar files or Web Archives (war) files.
Since Client Company downloads its archives through Internet 1. It wants to be sure downloaded archives could only be sent 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 solution I present to address these requirements is to enhance the archive-downloading package to host archives in sandboxes like browsers host applets. I chose to rely on standard Java 2 security described in Java Security Architecture document you can download on http://java.sun.com/products/jdk/1.2/docs/guide/security/spec/security-spec.doc.html. Java 2 securityThe diagram below depicts Java 2 security components:
Figure 2: Java 2 security The hub component is the Security Manager. Its purpose is to determine whether or not particular operations should be permitted or denied. The security manager is implemented by SecurityManager class and subclasses. SecurityManager class and subclasses 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 checkmethod typically looks like this: 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. It is often the case in Java Application Servers. SecurityManager checkmethods 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. permissions is simply the collection of the permissions the protection domain will have. 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 if 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 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 is Java and Certificate is a class wrapping a certificate.
There is a last point to consider. We could build our PermissionCollection programmatically but it wouldn’t be flexible and convenient. A better solution is to declare somewhere that code coming from a given source with a given signature is granted a set of permissions. It 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, which relies on configuration files.
Java security is comprehensive and well documented. So I used it to support sandboxes inside a Java Server in a way as close as possible to the applet distribution model. UseA Server Company generates a key pair and stores it in a key store, using the keytool command as listed on Listing 1. Here it creates a key pair whose alias is hello. It is protected by a password helloPswd and stored in a key store named D:\JSPservlet\keystore. Then the Server Company 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 requires to run properly in a standard policy file. For instance, it describes helloMisc.jar security requirement with Listing 2. This policy states the archive classes, represented here by their code source require all permissions. It also states archive classes must be signed with the public key defined in keystore 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 listed 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 JSPservlet deployment descriptor. More precisely, the package first looks for an archive.policy file and if archive.policy 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. ImplementationLet’s start with a reminder of the tool structure.
Figure 3: Tool class diagram A special servlet, JSPservlet 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 it is present it means that the archive classes must run in a sandbox, second it gives the name of a default policy file 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 a Security Manager is not set then it cannot be called by Java API and hence cannot 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 like 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 Security Manager.
First let’s look at the constructor in Listing 5. If allPermissionPolicy property is not set, it loads the archive classes and resources and returns without attempting to apply a security. Otherwise, if a Security Manager is not set, it creates a new policy with new sun.security.provider.PolicyFile(). It must set before a java.security.policy system property, PolicyFile constructor uses to locate the policy file. Next the constructor sets this policy as the current policy and creates the security manager.
Then the constructor creates a baseURL, which is used later in classes definition. We will examine this point later on. For the moment, simply note that the base URL is made of the archive download location and of the archive name. It is the codeBase the administrator specifies in the archive policy file as you can see on Listing 2.
The next step of the constructor consists of loading the archive policy file either from archive.policy or from 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, I present on Listing 6. parseStream() defines classes and implements the sandbox. It relies on the ability to define a class inside a Protection Domain. If classes inside the domain are trying to do something the domain is not granted to do, the Access Controller throws a java.security.AccessControlException. Let’s look at a back trace. 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 the Protection domain didn’t have the needed permission and threw a java.security.AccessControlException.
Figure 4: AccessControlException The interesting part of parseStream() starts when it has identified a class. If allPermissionPolicy property is not 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 protectionDomain0 domain. Though this domain has no certificates, it doesn’t necessarily means it has no permissions. If the domain code source was granted permissions without signature (SignedBy parameter) like in Listing 7 policy file, policy.getPermissions() returns these permissions.
To handle the case where JarEntry.getCertificates() found certificates, parseStream() maintains a protectionDomains HashMap whose keys are certificates arrays and values Protection Domains. Thank to this HashMap, parseStream() uses only one Protection Domain per certificate array. It enumerates protectionDomains and checks if a domain has already the same certificate array. If it is the case, 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 like on Listing 2 policy file.
Callback issueIn the simple case where the Servlet container invokes JSPservlet that invokes the target servlet, the code above is enough. However we also have to handle the case where the target servlet asks for a resource, for 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. When you calls servlet A first, you get an AccessControlException in a class loader but if you call it again you get AccessControlException in JSPservlet and if you invokes servlet B before servlet A it works fine.
The problem happens because the target servlet code calls a class – for instance JSPservlet – requiring privileges it does not have. As we saw above, AccessController.checkPermission is invoked. It checks all code traversed by the execution thread up to its call have 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 been already accessed or not.
To address this issue, we need to use “privileged” code. Let’s refine the explanation above. AccessController checks all code traversed by the execution thread have permission for that access unless some code on the thread has been marked as "privileged". If a caller whose code is granted a permission is marked as "privileged" and all code invoked by this caller has also this permission, then AccessController allows the access.
Figure 5: Privilege code We granted allPermissionPolicy permissions to the tool code, so the only thing we need to do is to mark its code as privileged when it can be called by the target servlets, so at resource retrieval, class retrieval and servlet handling. To mark a code as privileged we must use AccessController.doPrivileged. This method is thoroughly described in JDK doc.
Listing 9 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 considerationsLet’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 this key store in a safe location because if you lose it you will never be able to sign archives again and because if someone with a 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 issued by this CA and wrapping your public key, whose corresponding private key is only known of you. To do it, you first build a PKCS#10 certificate request with: keytool -certreq -alias alias -keystore keystore.
PKCS stand for Public-Key Cryptography Standards and 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 where you are prompted for your CR. Then you 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, which import it in their key store using the same keytool command. It is important to note the difference between your and customers key stores. Yours contains both the certificate and the private key. You can sign archives. Theirs contains only the certificate. They can only check archive signatures.
Consider now the distribution of a signed archive. Assume many customers have anonymously downloaded a signed archive 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 is possible to disable the archive classes without impacting other Web Applications or requiring stopping the Java Server.
We can implement this mechanism using freely downloadable stuff 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 8 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, then it 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 periodically the repository and second to return the next scheduled CRL update. CRL update can become complicated and expensive especially if many CA are involved. Most revocations are not critical – for instance an employee certificate can be revoked when the employee moves to another department, therefore it is often practical to update the CRL only once a day or once a week. As also it is 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 is check(). It uses CRL getRevokedCertificate to find if a certificate has been revoked using its serial number and it throws an exception if it is the case. SummaryThe 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 provides also classes to check if the credentials you use are still valid. The major difficulty in this area is that it encompasses traditionally separate area of knowledge.
Coming back to the requirement of supporting archives downloaded like applets, it 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.
Installation
Constellations
Versions
Demo
Contact:support@pagebox.net |