Thursday, March 26, 2015

Changes to applet JAR manifests for Java 7 onwards

Reading through this official page made me realise that it was not being specific about what needs to be done. The changes are being categorised into each Update without summarising what is the latest set of TODOs in order for your applet to be able to run properly. Here's my TL;DR of this page:
  • MANIFEST.MF (required)
    1. Required:
      1. Application-Name: My Applet Name
      2.  Permissions: sand-box/all-permission
    2. Optional:
      1. Codebase: {list of URLs}
      2. Trusted-Only: true (more secure)
      3. Trusted-Library: true (less secure than Trusted-Only)
      4. Application-Library-Allowable-Codebase: {list of URLs}
      5.  Caller-Allowable-Codebase: {list of URLs}
    3. {list of URLs}:
      1. Can be a single asterix as a wildcard '*' for liberal use of the JAR
      2. Can contain a mix of named/IP addresses
      3. Single line, no linebreaks
      4. Example: http://localhost:9090 https://localhost:9443 some-other.internal.url 172.16.0.1 
  • Signing (required)
    • Can be self-signed
    • Should not be expired
    • Within a secure intranet environment, it is possible to establish the following:
      • A self-signed certificate identified as a Root Certificate Authority (Root CA);
      • This certificate is added to all workstations within the network;
      • Generate a separate certificate for this application JAR file;
      • Sign the application certificate using the Root CA (e.g. 1 year expiry);
      •  Of course, you must be acquainted with the risks of being vulnerable to the CA certificate ever being compromised.
  • Workarounds:
    • Exception Site List
      • Add your URLs manually into individual workstation JRE Control Panel
    • Deployment Rule Set
      • Create an XML according to this page;
      • Better granular control over specific JRE versions you want your application to run using;
      • Wrap the XML inside a signed JAR (e.g. using above Root CA);
      • Push JAR into all workstations on startup.
With some luck using the above, you should not need to edit the java.policy where you have to end up exposing AllPermissions unwittingly causing a vulnerability you never intended to have.

Thursday, March 12, 2015

Generating archives on-the-fly in server memory

TL;DR - String byte array > text file > ZipEntry > ZipOutput > ByteArrayOutput > ServletOutput.

Looking into generating a jar file (essentially a zip archive), with a single text file within, that contains dynamically generated data, I located this and this which I'm attempting to combine for use.

The first part is straightforward; generating the content into the zip file as a ZipEntry with an explicit file name, all done in memory.

        try {
            String contents = "SomeStringA=SomeStringB";
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            ZipOutputStream zos = new ZipOutputStream(baos);
            ZipEntry entry = new ZipEntry("file.txt");
            zos.putNextEntry(entry);            zos.write(contents.getBytes(StandardCharsets.UTF_8));//or just "UTF-8" if you're on Java 6
            zos.closeEntry();
            zos.close();//The archive needs to know it is done
        } catch (IOException e) {
            e.printStackTrace();
        }

The second part, I'm still mucking around a bit. This snippet should work... but I have yet to test this out. I'll update again once it is confirmed.

         response.setContentType("application/zip");         response.setHeader("Content-Disposition", "attachment; filename=\"file.jar\"");
         response.getOutputStream().write(baos.toByteArray());
         response.getOutputStream().flush();

Of course, all this is going to be done in a servlet method the is expecting a response output, to which the zip file will be churned out to (as a JAR file). Don't forget to flush()!

Edit: After running through the actual stuff, I realise that the ZipOutputStream needs to be closed as the final action. The code snippet above is pretty much spot on otherwise.

Friday, March 6, 2015

Accessing resource files within JARs

There were two items I needed to access:
  1. A keystore (.jks) file that contains a third-party certificate;
  2. A text file within a separate JAR file added as a library as part of the WAR package for the project.
Accessing the keystore file
The resources I'd found (like this one), all point to the same place. Using the calling class, reference a relative path to where the resource is located:

getClass().getResourceAsStream("../resources/keystore.jks");

This will work as long it is according to the Java package structure. In order to better understand where my class is calling from exactly, I'd output this to the server log:

getClass().getResourceAsStream(".");
This will show you the full absolute path to the Java class file you've been working with, on the server.

The JKS file can then be accessed like this:
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream keyStoreFile = getClass().getResourceAsStream(keyStorePath);
keyStore.load(keyStoreFile, keyStorePassword.toCharArray());

Accessing the text file in a JAR
This resource is slightly trickier. Mostly because it is contained within a separate JAR file. But don't worry, there is no need to use JarFile for this. My project was already using Maven, and has a pom.xml for packaging. But because this JAR does not contain any Java source files, it doesn't belong in the repository. I had to manually add it to the WAR file classpath by modifying the pom.xml file.

I added this snippet to the section meant for maven-war-plugin:
<archive>
  <manifest>
    <addClasspath>true</addClasspath>
    <classpathPrefix>lib/</classpathPrefix>
  </manifest>
  <manifestEntries>
    <Class-Path>lib/target.jar</Class-Path>
  </manifestEntries>
</archive>

Separately, in the calling Java class, note that the earlier getClass().getResourceAsStream("."); does not work in this instance. This is because the class is in a different JAR file. Instead, according to this, you need to make use of its ClassLoader like so:

getClass().getClassLoader().getResourceAsStream("file.txt"));

Note that file.txt is in the root of the target.jar file, but because target.jar has already been added to the WAR classpath, its contents is immediately visible to the ClassLoader of the same WAR file.

You can then read it like any normal text file:

BufferedReader br = new BufferedReader(new InputStreamReader(getClass().getClassLoader().getResourceAsStream(path)));

That is all. Have fun!