Escape the sandbox: Access native methods from an applet

Find out how you can directly invoke the Win32 API -- the IE-free way

Applet developers deploying on a Win32 platform sometimes find it necessary to invoke native Win32 methods from within an applet. Not realizing that other options are available, they may turn to Active X or JDirect, both of which rely on Internet Explorer on the client side. While this may be acceptable for some controlled intranets, in many cases it creates an impossible restriction. In this article, I'll show how to implement the same functionality using a Netscape client.

Previous articles (see Resources) have covered both creating signed applets that can access resources "outside the sandbox" and using native methods from within Java code using JNI. In this article, we're going to combine these two concepts to effectively create a Microsoft-free version of JDirect. We'll also use LiveConnect (built into Netscape Communicator) to invoke native methods directly from JavaScript.

While our example covers the Win32 API explicitly, you can easily extend the approach to run native code from an applet on any platform running the Netscape browser -- just create a different version of the native library.

Two features of Netscape's applet security model make implementing this functionality tricky. First, the native library must reside on the local drive on the client PC. While sensible, this policy requires that a DLL created using JNI be downloaded to the client.

An even trickier feature requires any class that directly invokes a native method to be loaded by the system class loader rather than by the applet class loader. Netscape looks for classes in its own classpath first and loads classes found there with its system class loader. If it can't find a class there, it looks in the applet's codebase. If a class is loaded from the codebase, it gets loaded by an AppletClassLoader and thus has fewer privileges. Therefore, the class that invokes native methods must reside in Netscape's classpath by the time it's first loaded by any applet.

In our example, we'll invoke two very simple Win32 methods called GetUserName() and GetComputerName(). GetUserName() returns the Windows user name, and GetComputerName() returns the Windows computerID. We can use this approach with any methods in the Win32 API. I've broken down our task into the following steps:

  1. Create the native library and Java "wrapper" class that uses JNI
  2. Create the Java applet class
  3. Create a signed jar file
  4. Create HTML code with JavaScript
  5. Deploy the applet

Create the native library and Java JNI class

The class file that invokes native methods needs to be downloaded to the client machine when the applet is started. This step is just standard JNI and is well described in a number of references (see Resources). This UserInfo class doesn't need to do much -- it simply contains two native methods declarations:

     public class UserInfo {
      public native String getUserName() throws Error;
       public native String getComputerName() throws Error;
     }

To create our native interface, we need to compile UserInfo, then run the javah utility (on the Windows platform) on the generated class file to produce a C header file. This file is produced automatically and shouldn't require any editing on your part.

JNI requires that we use the function prototypes in this header file to develop our native library. So, we import this header file into a Windows C development environment (I used Microsoft Visual C++ version 5.0) and create a C/C++ file that implements the methods defined in the native interface (in this case just invocations of the two Win32 methods described above). The C code looks like this:

#include <windows.h> #include <jni.h> /* * Class: UserInfo * Method: getUserName * Signature: ()Ljava/lang/String; */

JNIEXPORT jstring JNICALL Java_UserInfo_getUserName (JNIEnv * env, jobject obj) { char *ptr; char nameBuff[255]; long length; ptr = nameBuff; GetUserName(ptr, &length ); return (*env)->NewStringUTF(env, ptr); }

/* * Class: UserInfo * Method: getComputerName * Signature: ()Ljava/lang/String; */ JNIEXPORT jstring JNICALL Java_UserInfo_getComputerName (JNIEnv * env, jobject obj) { char *ptr; char nameBuff[255]; long length; ptr = nameBuff; GetComputerName(ptr, &length ); return (*env)->NewStringUTF(env, ptr); }

(Makes you glad you're a Java coder, doesn't it!)

Now use the create DLL option in Visual C++ to create a standalone DLL.

Whew! The hard part's over with. Now we can go back to writing Java.

Create Java applet classes

Netscape's security model, per our discussion above, requires that things occur in a particular order. Here's the sequence of events we need to follow:

  1. Load applet class
  2. Download DLL to client
  3. Download native method wrapper class to client
  4. Load wrapper class
  5. Load native DLL
  6. Invoke native methods

The UserInfoApplet class does the real work here (see Resources to download the source). The methods installDLL( ) (shown below) and installJNIClass( ) download the DLL and the UserInfo class. Both of these routines use the copyURL() method to copy from the specified URL to a specified location on the client machine. I adapted this method from the clever copyURL() utility described in "Java Tip 19: Java makes it easy to copy files from a Web site." Because these classes must request special privileges from the applet user, we must use the enablePrivilege() method from the Netscape Capabilities API (as shown in the following snippet). This method causes the browser to display a dialog, containing information about the applet signer (me, in this case) to the user.

Netscape's security dialog requests permission to venture "outside the sandbox"

In the case below, the user will be asked to allow access to the local filesystem.

public void installDLL ( ) { try { PrivilegeManager.enablePrivilege("UniversalFileAccess"); } catch (Exception e) { System.out.println ("Can't enable file Privilege!!!"); e.printStackTrace(); }

dllDest = findTempDir() + "userinfo.dll"; System.out.println("Copying " + urlSource + dllFileName + " to " + dllDest + " . . . " ); copyURL ( urlSource + dllFileName, dllDest ); }

A similar method, installJniWrapperClass(), installs the Java wrapper class in Netscape's classpath. To comply with Netscape's security model, I've used Netscape's CLASSPATH directory as the destination for the Java class file. The utility method parseClassPath() supports this approach.

Notice that I used a temporary Windows directory for the DLL destination (supported by the findTempDir() method). This is all right as long as you use System.load(), which uses an absolute path, to load the library. If you use System.loadlibrary() instead, the system will search the path specified by the Windows PATH directory for the DLL, and you must use a directory in this path as the destination. Using System.load() with an absolute path is probably a more portable approach.

The loadLibrary() method, which actually loads the DLL, must also venture outside the sandbox and, therefore, must also use the Capabilities API to ask for permission.

public void loadLibrary () { try { PrivilegeManager.enablePrivilege("UniversalLinkAccess"); } catch (Exception e) { System.out.println ("Can't enable link Privilege!!!"); e.printStackTrace(); }

try { System.out.println ("loading library " + dllDest + " . . . "); System.load ( dllDest ); } catch (Exception e) { System.out.println ("Can't load library!!!"); e.printStackTrace(); } catch (Error err) { System.out.println ("ERROR: Can't load library!!!"); err.printStackTrace(); } }

Once you've downloaded the required files and loaded your native library, you can use your native methods, as in the example destroy() below. Note that we create an instance of the wrapper class each time the method is invoked -- the wrapper class shouldn't be accessed until it exists in Netscape's classpath. If you declare the wrapper class as an instance variable in the applet, it will be referenced when the applet is loaded -- and before the wrapper class has been downloaded and made available -- which will cause exceptions.

public String getUserName() { String name; // we can't reference UserInfo class until AFTER it exists on // local classpath! UserInfo info = new UserInfo(); try { PrivilegeManager.enablePrivilege("UniversalLinkAccess"); } catch (Exception e) { System.out.println ("Can't enable link Privilege!!!"); e.printStackTrace(); }

try { name = info.getUserName(); } catch (Error er) { er.printStackTrace(); return ("Java Error on getUserName!") ; } return name; }

Of course, you can extend the Java classes above to use any Win32 API methods, or methods from any native API.

For security reasons, the source URL for downloaded files should be hardcoded in the applet class. While using applet param tags might seem to be the easiest and most configurable approach, this opens a big security risk. A malicious user could copy the signed jar and simply specify a different URL for the DLL or class file. The substitute file could then operate with all the privileges granted to the original applet.

Whether you choose to leave the downloaded files on the client, or remove them when the applet is complete (probably in the applet's destroy() method) is up to you and your particular deployment approach.

To compile these files, you need the netscape.security Privilege class in your classpath. Netscape's security packages are in the java40.jar file in Netscape's directory hierarchy (look in Program\Java\Classes). You can either add this jar to your classpath or unjar it in your existing classpath directory.

Create a signed jar file

This process is described on Netscape's site (see Resources), but we'll review the basic steps here.

First, obtain an object signing certificate from an organization like VeriSign, and then install the certificate on your local machine.

Once the certificate is installed, use Netscape's signtool utility (available free from Netscape's site) to create the signed jar file. For example, for a certificate database in c:\Program Files\Netscape\Users\steve_small, a certificate named Steve Small's VeriSign Trust Network ID, and files to be archived in the directory UserInfo, the signtool command would look like this:

signtool -d"c:\Program Files\Netscape\Users\steve_small"-k"Steve Small's VeriSign Trust Network ID"-Z UserInfo.jarUserInfo

Once you've created the jar file, the only step left is to create the HTML file and deploy the applet.

Create HTML code with JavaScript

In the following HTML page, we're using LiveConnect (built into Netscape) to invoke our native methods directly from JavaScript.

On slow networks or on dialup connections, it's possible for the JavaScript below to attempt to access applet methods before the applet has loaded, which will cause exceptions. I've put a dialog at the beginning of the JavaScript that requests the user to wait for the applet to load.

This page also contains JavaScript checks to determine the browser configuration. I recommend that you support a user base containing Netscape and Internet Explorer (IE) browsers. The best approach is probably to move this browser check to a top-level HTML page. If it's Communicator, go to a page containing an applet that uses the approach above; if it's IE, go to a page that uses either a JDirect applet or ActiveX.

<HTML>

<APPLET CODE="UserInfoApplet.class" WIDTH=150 HEIGHT=25 ARCHIVE="UserInfo.jar"> </APPLET>

<SCRIPT LANGUAGE="JavaScript"> alert("wait for applet to load . ..");

function getUser () { UserID=document.applets[0].getUserName(); java.lang.System.out.println("user ID: " + UserID); alert ("User Name returned to JavaScript by Java applet: " + UserID); }

function getComputer () { ComputerID = document.applets[0].getComputerName(); java.lang.System.out.println("computer ID: " + ComputerID); alert ("Computer Name returned to JavaScript by Java applet: " + ComputerID); }

config=navigator.userAgent; windows=false; netscape=false; if (config.indexOf("Mozilla") != -1) netscape=true; if (config.indexOf("Win") != -1) windows=true;

if ( (netscape==true) && (windows==true) ) { document.applets[0].installDLL(); document.applets[0].installJniWrapperClass(); document.applets[0].loadLibrary(); java.lang.System.out.println("invoking getUser in JavaScript . . ."); getUser(); java.lang.System.out.println("done invoking getUser in JavaScript . . ."); getComputer(); }

else alert("only supported on PC/Netscape platform!");

</SCRIPT>

<HEAD> <TITLE>Native Methods From Applet</TITLE>

</HEAD><BODY> <P> <P> <BOLD> Insert your HTML here . . . </BOLD> </BODY> </HTML>

Deploy the applet

Now that we've created our native DLL, our JNI Java wrapper class, our signed jar file, and our HTML file, we need to deploy them on a Web site. Just install all the files in your document base (you can, of course, create a directory for the applet code and specify a codebase).

1 2 Page 1