One thing before I start my rant about java. It means two things: coffee and a programming language. Coffee, to be clear, is absolutely my friend. It makes me happy in the morning and keeps me awake at night. I treat it with respect and it helps me out.
Java the programming language, on the other hand, is not my friend. Let me explain.
At work we’ve been working on a piece of software that, from within a web browser, needs to open up a file on the user’s computer and send it up to a server. To make sure this works on pretty much every browser, the only real option is java.
So we look around for how to do this, even buy a source license from a commercial package that does something similar, and think we’ve got it figured out. The head programmer writes up a set of code that should do the trick, builds a signed jar file, and tests it out — it works! But only on the programmer’s computer.
Let me back up and explain something about java. Unlike most languages, it has two modes. The first is your standard “everything works” mode, where you write code, you run it like a normal program, and it just works. The second mode happens when you download java code from a web site and run it inside a web browser. In this case, there are a set of things you can’t do — you can’t make network connections, you can’t open local files, etc. — unless the code is signed and/or you specifically grant the program permission, either by clicking ‘Yes’ at an allow box or by modifying a java config file.
We’re trying to make java do both of those things — open a local file and make a network connection — so we have to, we thought, sign the code. We use a self-signed certificate for the moment, because we can let everyone who uses the code know that they have to just click ‘yes’ to allow the code, and because we don’t want to have to push out a new java config file to everyone’s machine.
So we take the code that works fine on the developer’s machine, and try it on several others — remember, this thing is supposed to be cross-platform. It doesn’t work — on any of them. And it gives a permission denied error on reading the file — the very thing we thought we’d resolved by signing the code.
It turns out that there was a specific bug in the security portion of the version of java on the developer’s system that allowed this code to work, but the code doesn’t work on any other version of java. Hence our search begins for a way to make it work.
As far as we can tell, there’s no way to debug the security mechanisms for java. (Not true, as we found out later.) We can insert lots of test-and-print statements, but those won’t tell us what specifically the security mechanism is looking for.
So we try all sorts of things, including messing with the security java.policy file, trying to grant access to code that comes from the web server (remember, we’ve tried the signing method, which isn’t working) to do the file read and network operations. That doesn’t work either, naturally. Granting access to ALL java programs to read and do network operations works, but that’s generally a Bad Thing, so we don’t want to do that.
I spend about 8 hours googling and trying different things to get it to work. Nothing.
Finally, on an obscure Tomcat debugging page, I find a reference to a variable that one can give on the java command line:
-Djava.security.debug=help. Much as one would expect, four angels stuck trumpets into the ceiling of my office and blasted a triumphant chord.
So — I run a standalone java instance with that commandline addition to find out what debugging flags to use. After trying a couple of things, I come up with a command line that basically involves turning on every flag except the one that does a stack dump after every security call, turning what might be a several-hundred-megabyte log file into only a 6MB log file (and this before any code actually really executes):
At this point, I find out that despite my best efforts to figure out a match for the codeBase definition in the policy file, when it comes time to execute the function that causes the permission denied to read file error, the codeBase that the security manager is comparing the java.policy file against is NULL!
The other thing we discover is that the permission denied error does not occur at the file open function call; it occurs at the function call a few lines later that determines the file length with
FileObj.length(). Comparing the two sections of code, I note that the open call is wrapped in a
doPrivileged() object; once more, the trumpets come down and blat at me.
We discover that the file
length() function, as well as the function to create a new file stream from the open filehandle all need to be wrapped in this
doPrivileged() object, which appears to be sort of like a sudo command for java. Oddly enough, the functions that actually read in the file stream don’t need to be wrapped in
This fix made the program work with the java.policy modifications; a bit later, we realized this might also fix it so that we could just sign the code, and as it turns out, it did!
So in order to get signed java code to do things outside the browser sandbox, you need to make sure to execute anything that’s considered “privileged” with
doPrivileged(). In order to find out what’s considered privileged, run your code with the
-Djava.security.debug= flags turned on. With any luck this will save someone else the weeks of searching we spent trying to figure this one out.