I had to update our license library to add new license types when I came across an interesting problem.
Running in development mode against the source of atlassian-extras (which contains our license logic), everything worked just fine. Time to run the tests again, tag and create the jar. Unfortunately when testing the license creation on the website, it failed with a NullpointerException. Apart from the fact that the nullpointer should be caught, we realised that the license creation failed due to a mismatch of the public and private keys.

What happened?

Our library atlassian-extras ships with a public key file to verify the digital signature. We found that reading this key introduced a corruption caused by two factors.

  1. A bug in Maven 1.0 where the property maven.jar.compressed is not acknowledged
  2. A change in the stream implementation for compressed jars in Java Mustang build 14

Maven bug

Thanks to Scott, who remembered a problem with compressed jars, we were able to determine that this was indeed part of the problem. However, looking at the maven project.properties, the configuration suggested that compression was disabled for this target. A manual check of the jar created and a search on google pointed us to a bug in Maven where the _maven.jar.compressed_ property was not acknowledged and the jar was compressed regardless of whether the property was set or not.

Compressed jars stream implementation

Because the atlassian-extras code now had to deal with a compressed jar, the implementation of InputStream became important. The documentation is unfortunately not very clear about what the read(byte[] b) method will actually read. The following quotes are taken from the InputStream Java docs:

Reads some number of bytes from the input stream and stores them into the buffer array b. The number of bytes actually read is returned as an integer.


The number of bytes read is, at most, equal to the length of b.

The important words in the quote are marked bold. Is you can see the documentation is not clear about the fact how many bytes will be read. This is due to the fact that nobody knows how many bytes will reliably be read. A bigger warning rather then vague pointers wouldn’t hurt in the documentation though.
The following code was responsible for reading the key file and storing it in an byte[]. But looking at the key (see bottom), we realised that the last 5 bytes were missing. It appears that only *some number* and not the expected 443 bytes were read.

InputStream keyis = Thread.currentThread().getContextClassLoader().getResourceAsStream(licenseTypeStore.getPrivateKeyFileName());
byte[] encKey = new byte[keyis.available()];

After writing a test to prove that this was indeed the culprit, I’ve added the following method:

public static byte[] readKey(InputStream is) throws IOException
ByteArrayOutputStream bout = new ByteArrayOutputStream();
int len = 0;
byte[] bytes = new byte[512];
while ((len = is.read(bytes)) > -1) {
bout.write(bytes, 0, len);
return bout.toByteArray();

This time around the code will enter the while loop twice. The first time 438 out of 443 bytes are read and second time the remaining 5 bytes are added.
Last bytes of key within the compressed jar after single invocation of read():

40ad2c61 d042013a a162939c 77049586 fba5b69d 4d3aa4cf 535af200 00000000

Last bytes of key within uncompressed jar after single invocation of read():

40ad2c61 d042013a a162939c 77049586 fba5b69d 4d3aa4cf 535af205 2d2239ca

Lesson learned

Application code can not rely on the fact that a single call to read will actually read the same number of bytes reported by available(). Changes to the stream implementation for compressed jar files are causing the read method to only read a portion of the requested bytes in some cases. However invoking available() after the first read, will return only the remaining bytes which can be retrieved with an additional invocation of the read method.
You can find a bug report in the sun database here.

Lost in translation