/* * Copyright (c) 2006 LWJGL Project * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of 'LWJGL' nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ package org.lwjgl.util.applet; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileFilter; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.AccessController; import java.security.PrivilegedAction; import java.security.PrivilegedExceptionAction; import java.security.cert.Certificate; import java.util.HashMap; import java.util.Iterator; import java.util.jar.JarEntry; import java.util.Enumeration; import java.util.jar.JarFile; import org.lwjgl.LWJGLUtil; /** *

* Installer class for installing LWJGL temporarily into a temp directory. * This class is used for installing LWJGL for use with applets. *

* @author Brian Matzon * @version $Revision$ * $Id$ */ public class LWJGLInstaller { /** Whether the installer has been called */ private static boolean installed; /** Directory all lwjgl installations go into */ private static final String MASTER_INSTALL_DIR = ".lwjglinstall"; /** Name of file that we use to tag 'live' installations */ private static final String WATERMARK_FILE = ".lwjglinuse"; /** Name of the native jar we're expected to load and install */ private static final String NATIVES_PLATFORM_JAR = "/" + LWJGLUtil.getPlatformName() + "_natives.jar"; private LWJGLInstaller() { /* Unused */ } /** * Create a temporary installation of LWJGL. * This will extract the relevant native files (for the platform) into * the user's temp directory, and instruct the LWJGL subsystem to load its * native files from there. * The file required by the installer, is gotten from the classloader via its * getResource command, and is expected to be named _natives.jar. * Note: Due to the nature of native libraries, we cannot actually uninstall the currently * loaded files, but rather the "last" installed. This means that the most recent install of LWJGL * will always be present in the users temp dir. When invoking the tempInstall method, all old installations * will be uninstalled first. * * @see java.lang.ClassLoader#getResource(String) */ public synchronized static void tempInstall() throws Exception { // only need to install once if (installed) { return; } try { // Validate the certificates of the platform native jar HashMap files = (HashMap) AccessController.doPrivileged(new PrivilegedExceptionAction() { public Object run() throws Exception { return validateCertificates(); } }); AccessController.doPrivileged(new PrivilegedAction() { public Object run() { uninstall(); return null; } }); // create a temporary dir for the native files String user_temp_dir = getPriviledgedString("java.io.tmpdir"); final String path = createTemporaryDir(user_temp_dir); // extract natives from jar writeFiles(files, path); AccessController.doPrivileged(new PrivilegedAction() { public Object run() { System.setProperty("org.lwjgl.librarypath", path); // Make sure jinput knows about the new path too System.setProperty("net.java.games.input.librarypath", path); return null; } }); installed = true; } catch (Exception e) { LWJGLUtil.log("Failed extraction e = " + e.getMessage()); uninstall(); throw e; } } /** * Validates the certificates of the native libraries. * When installing native libraries, it is imperative that we also check the certficates. * The reson for this, is that a user may inject a malicious jar to the classpath * before the "real" LWJGL jar, containing native libraries with unwanted code. * By forcing all the native libraries to have the same certificate as the signed * installer, we can also be sure that the native libraries indeed are correct. * @throws Exception If we encounter a certificate mismatch */ private static HashMap validateCertificates() throws Exception { // get our certificate chain Certificate[] ownCerts = LWJGLInstaller.class.getProtectionDomain().getCodeSource().getCertificates(); if(ownCerts == null || ownCerts.length == 0) { throw new Exception("Unable to get certificate chain for LWJGLInstaller"); } // check that each of the entries in the jar is signed by same certificate as LWJGLInstaller InputStream is = LWJGLInstaller.class.getResourceAsStream(NATIVES_PLATFORM_JAR); if(is == null) { throw new Exception("Unable to open " + NATIVES_PLATFORM_JAR + ", which was expected to be on the classpath"); } /* Copy the jar containing the natives to a tmp file and unpack and verify it from there. * A better way would have been to use JarInputStream, but there's a bug with JIS and * JarEntry.getCertificates() on java 1.5 (bug id. 6284489, duplicate id: 6348368). * JarEntry.getCodeSigners() does work on java 1.5 with JIS, but that API was introduced * in 1.5, and can't be relied upon for java 1.4. */ File tmp_jar_file = File.createTempFile("lwjgl", ".jar"); copyFile(is, new FileOutputStream(tmp_jar_file)); is.close(); JarFile jar_file = new JarFile(tmp_jar_file); JarEntry native_entry; HashMap files = new HashMap(); Enumeration jar_entries = jar_file.entries(); while (jar_entries.hasMoreElements()) { native_entry = (JarEntry)jar_entries.nextElement(); // skip directories and anything in directories // conveniently ignores the manifest if(native_entry.isDirectory() || native_entry.getName().indexOf('/') != -1) { continue; } // need to read the file, before the certificate is retrievable // since we dont want to do two reads, we store it in memory for later use ByteArrayOutputStream baos = new ByteArrayOutputStream(); InputStream jis = jar_file.getInputStream(native_entry); copyFile(jis, baos); files.put(native_entry.getName(), baos.toByteArray()); // now check the chain of an actual file validateCertificateChain(ownCerts, native_entry.getCertificates()); } return files; } /** * Validates the certificate chain for a single file * @param ownCerts Chain of certificates to check against * @param native_certs Chain of certificates to check * @return true if the chains match */ private static void validateCertificateChain(Certificate[] ownCerts, Certificate[] native_certs) throws Exception { if(native_certs == null) throw new Exception("Unable to validate certificate chain. Native entry did not have a certificate chain at all"); if(ownCerts.length != native_certs.length) throw new Exception("Unable to validate certificate chain. Chain differs in length [" + ownCerts.length + " vs " + native_certs.length + "]"); for(int i=0; i