added certificate check
reworked installer to expect a jar with the platform natives inside a jar now installs into java.io.tmpdir/.lwjglinstall/<timestamp> uninstaller checks for removed watermark file - tho it never gets removed *grumble*
This commit is contained in:
parent
26c6de6363
commit
5c82452d5e
|
@ -33,6 +33,8 @@ package org.lwjgl.util.applet;
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
import java.io.BufferedInputStream;
|
||||||
import java.io.BufferedOutputStream;
|
import java.io.BufferedOutputStream;
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileFilter;
|
import java.io.FileFilter;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
@ -41,9 +43,18 @@ import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.security.AccessController;
|
import java.security.AccessController;
|
||||||
import java.security.PrivilegedAction;
|
import java.security.PrivilegedAction;
|
||||||
|
import java.security.PrivilegedActionException;
|
||||||
import java.security.PrivilegedExceptionAction;
|
import java.security.PrivilegedExceptionAction;
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Iterator;
|
||||||
|
import java.util.jar.JarEntry;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.jar.JarInputStream;
|
||||||
|
|
||||||
import org.lwjgl.LWJGLUtil;
|
import org.lwjgl.LWJGLUtil;
|
||||||
|
import org.lwjgl.Sys;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -55,16 +66,6 @@ import org.lwjgl.LWJGLUtil;
|
||||||
*/
|
*/
|
||||||
public class LWJGLInstaller {
|
public class LWJGLInstaller {
|
||||||
|
|
||||||
/**
|
|
||||||
* Files to install for each supported platform
|
|
||||||
* @see org.lwjgl.LWJGLUtil#getPlatform()
|
|
||||||
*/
|
|
||||||
public static final String[][] PLATFORM_FILES = {
|
|
||||||
{ "lwjgl", "lwjgl-fmod3", "lwjgl-devil", "openal", "fmod", "IL", "ILU", "ILUT", "jinput-osx"},
|
|
||||||
{ "lwjgl", "lwjgl-fmod3", "lwjgl-devil", "openal", "fmod", "IL", "ILU", "ILUT", "jinput-linux"},
|
|
||||||
{ "lwjgl", "lwjgl-fmod3", "lwjgl-devil", "OpenAL32", "fmod", "DevIL", "ILU", "ILUT", "jinput-dx8", "jinput-raw"}
|
|
||||||
};
|
|
||||||
|
|
||||||
/** Whether the installer has been called */
|
/** Whether the installer has been called */
|
||||||
public static boolean installed;
|
public static boolean installed;
|
||||||
|
|
||||||
|
@ -74,6 +75,12 @@ public class LWJGLInstaller {
|
||||||
/** Buffer used when copying files */
|
/** Buffer used when copying files */
|
||||||
private static final byte[] COPY_BUFFER = new byte[4096];
|
private static final byte[] COPY_BUFFER = new byte[4096];
|
||||||
|
|
||||||
|
/** Directory all lwjgl installations go into */
|
||||||
|
public static String MASTER_INSTALL_DIR = ".lwjglinstall";
|
||||||
|
|
||||||
|
/** Name of the native jar we're expected to load and install */
|
||||||
|
public static final String NATIVES_PLATFORM_JAR = "/" + LWJGLUtil.getPlatformName() + "_natives.jar";
|
||||||
|
|
||||||
private LWJGLInstaller() {
|
private LWJGLInstaller() {
|
||||||
/* Unused */
|
/* Unused */
|
||||||
}
|
}
|
||||||
|
@ -99,11 +106,13 @@ public class LWJGLInstaller {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// libraries to validate and install
|
// Validate the certificates of the platform native jar
|
||||||
String[] libraries = PLATFORM_FILES[LWJGLUtil.getPlatform() - 1];
|
HashMap files = (HashMap)
|
||||||
|
AccessController.doPrivileged(new PrivilegedExceptionAction() {
|
||||||
// Validate the certificates of the native files
|
public Object run() throws PrivilegedActionException{
|
||||||
validateCertificates();
|
return validateCertificates();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// install shutdown installer hook
|
// install shutdown installer hook
|
||||||
if(!disableUninstall) {
|
if(!disableUninstall) {
|
||||||
|
@ -123,11 +132,8 @@ public class LWJGLInstaller {
|
||||||
String user_temp_dir = getPriviledgedString("java.io.tmpdir");
|
String user_temp_dir = getPriviledgedString("java.io.tmpdir");
|
||||||
final String path = createTemporaryDir(user_temp_dir);
|
final String path = createTemporaryDir(user_temp_dir);
|
||||||
|
|
||||||
// extract natives
|
// extract natives from jar
|
||||||
for (int i = 0; i < libraries.length; i++) {
|
writeFiles(files, path);
|
||||||
String library = System.mapLibraryName(libraries[i]);
|
|
||||||
extract(library, path);
|
|
||||||
}
|
|
||||||
|
|
||||||
AccessController.doPrivileged(new PrivilegedAction() {
|
AccessController.doPrivileged(new PrivilegedAction() {
|
||||||
public Object run() {
|
public Object run() {
|
||||||
|
@ -137,6 +143,8 @@ public class LWJGLInstaller {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
installed = true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
LWJGLUtil.log("Failed extraction e = " + e.getMessage());
|
LWJGLUtil.log("Failed extraction e = " + e.getMessage());
|
||||||
uninstall();
|
uninstall();
|
||||||
|
@ -153,8 +161,73 @@ public class LWJGLInstaller {
|
||||||
* installer, we can also be sure that the native libraries indeed are correct.
|
* installer, we can also be sure that the native libraries indeed are correct.
|
||||||
* @throws Exception If we encounter a certificate mismatch
|
* @throws Exception If we encounter a certificate mismatch
|
||||||
*/
|
*/
|
||||||
private static void validateCertificates() throws Exception {
|
private static HashMap validateCertificates() throws PrivilegedActionException {
|
||||||
/* TODO */
|
InputStream is = LWJGLInstaller.class.getResourceAsStream(NATIVES_PLATFORM_JAR);
|
||||||
|
if(is == null) {
|
||||||
|
throw new PrivilegedActionException(new Exception("Unable to open " + NATIVES_PLATFORM_JAR + ", which was expected to be on the classpath"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// get our certificate chain
|
||||||
|
Certificate[] ownCerts = LWJGLInstaller.class.getProtectionDomain().getCodeSource().getCertificates();
|
||||||
|
if(ownCerts == null || ownCerts.length == 0) {
|
||||||
|
throw new PrivilegedActionException(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
|
||||||
|
try {
|
||||||
|
HashMap files = new HashMap();
|
||||||
|
JarInputStream jis = new JarInputStream(is);
|
||||||
|
|
||||||
|
JarEntry native_entry = null;
|
||||||
|
while((native_entry = jis.getNextJarEntry()) != null) {
|
||||||
|
// 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();
|
||||||
|
copyFile(jis, baos, false);
|
||||||
|
files.put(native_entry.getName(), baos.toByteArray());
|
||||||
|
|
||||||
|
// now check the chain of an actual file
|
||||||
|
if(!validateCertificateChain(ownCerts, native_entry.getCertificates())) {
|
||||||
|
throw new Exception("Failed to validate certificate chain for " + native_entry.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return files;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new PrivilegedActionException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 boolean validateCertificateChain(Certificate[] ownCerts, Certificate[] native_certs) {
|
||||||
|
if(native_certs == null) {
|
||||||
|
LWJGLUtil.log("Unable to validate certificate chain. Native entry did not have a certificate chain at all");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ownCerts.length != native_certs.length) {
|
||||||
|
LWJGLUtil.log("Unable to validate certificate chain. Chain differs in length [" + ownCerts.length + " vs " + native_certs.length + "]");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int i=0; i<ownCerts.length; i++) {
|
||||||
|
if(!ownCerts[i].equals(native_certs[i])) {
|
||||||
|
LWJGLUtil.log("Certificate mismatch: " + ownCerts[i] + " != " + native_certs[i]);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -163,39 +236,20 @@ public class LWJGLInstaller {
|
||||||
* @param file File to extract
|
* @param file File to extract
|
||||||
* @param path Path to extract to
|
* @param path Path to extract to
|
||||||
*/
|
*/
|
||||||
private static void extract(final String file, final String path) {
|
private static void writeFiles(final HashMap files, final String path) {
|
||||||
AccessController.doPrivileged(new PrivilegedAction() {
|
AccessController.doPrivileged(new PrivilegedAction() {
|
||||||
public Object run() {
|
public Object run() {
|
||||||
// check for existing file, and get out
|
|
||||||
File out = new File(path + File.separator + file);
|
|
||||||
if (out.exists()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// create the new file and copy it to its destination
|
|
||||||
try {
|
try {
|
||||||
|
for(Iterator i = files.keySet().iterator(); i.hasNext();) {
|
||||||
|
String key = (String) i.next();
|
||||||
|
File out = new File(path + File.separator + key);
|
||||||
out.createNewFile();
|
out.createNewFile();
|
||||||
String in = "/native/" + LWJGLUtil.getPlatformName() + "/" + file;
|
InputStream is = new ByteArrayInputStream((byte[]) files.get(key));
|
||||||
OutputStream os = new BufferedOutputStream(new FileOutputStream(out));
|
OutputStream os = new BufferedOutputStream(new FileOutputStream(out));
|
||||||
InputStream is = new BufferedInputStream(getClass().getResourceAsStream(in));
|
copyFile(is, os, true);
|
||||||
|
|
||||||
// Sanity check
|
|
||||||
// ===========================================
|
|
||||||
if (os == null) {
|
|
||||||
LWJGLUtil.log("Unable to write to outputstream at " + out.getAbsolutePath());
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (is == null) {
|
|
||||||
LWJGLUtil.log("Unable to read classpath inputstream from " + in);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
// -------------------------------------------
|
|
||||||
|
|
||||||
// copy the actual file
|
|
||||||
copyFile(is, os);
|
|
||||||
} catch (IOException ioe) {
|
} catch (IOException ioe) {
|
||||||
LWJGLUtil.log("Exception while extracting " + file + ": " + ioe.getMessage());
|
LWJGLUtil.log("Exception while extracting: " + ioe.getMessage());
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
|
@ -209,12 +263,15 @@ public class LWJGLInstaller {
|
||||||
* @param os OutputStream to write to
|
* @param os OutputStream to write to
|
||||||
* @throws IOException if the copy process fail in any way
|
* @throws IOException if the copy process fail in any way
|
||||||
*/
|
*/
|
||||||
static void copyFile(InputStream is, OutputStream os) throws IOException {
|
static void copyFile(InputStream is, OutputStream os, boolean closeInput) throws IOException {
|
||||||
int len;
|
int len;
|
||||||
while ((len = is.read(COPY_BUFFER)) > 0) {
|
while ((len = is.read(COPY_BUFFER)) > 0) {
|
||||||
os.write(COPY_BUFFER, 0, len);
|
os.write(COPY_BUFFER, 0, len);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(closeInput) {
|
||||||
is.close();
|
is.close();
|
||||||
|
}
|
||||||
os.close();
|
os.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -229,8 +286,8 @@ public class LWJGLInstaller {
|
||||||
return (String) AccessController.doPrivileged(new PrivilegedExceptionAction() {
|
return (String) AccessController.doPrivileged(new PrivilegedExceptionAction() {
|
||||||
public Object run() throws Exception {
|
public Object run() throws Exception {
|
||||||
// create the temp directory
|
// create the temp directory
|
||||||
File tempDir = new File(user_temp_dir + File.separator + "lwjgl-" + System.currentTimeMillis());
|
File tempDir = new File(user_temp_dir + File.separator + ".lwjglinstall" + File.separator + System.currentTimeMillis());
|
||||||
if(!tempDir.mkdir()) {
|
if(!tempDir.mkdirs()) {
|
||||||
throw new IOException("Failed to create directory: " + tempDir);
|
throw new IOException("Failed to create directory: " + tempDir);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,6 +295,7 @@ public class LWJGLInstaller {
|
||||||
// TODO: Write some info to the file ?
|
// TODO: Write some info to the file ?
|
||||||
File watermark = new File(tempDir.getAbsolutePath() + File.separator + ".lwjglinstaller");
|
File watermark = new File(tempDir.getAbsolutePath() + File.separator + ".lwjglinstaller");
|
||||||
watermark.createNewFile();
|
watermark.createNewFile();
|
||||||
|
watermark.deleteOnExit();
|
||||||
return tempDir.getAbsolutePath();
|
return tempDir.getAbsolutePath();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -267,14 +325,14 @@ public class LWJGLInstaller {
|
||||||
AccessController.doPrivileged(new PrivilegedAction() {
|
AccessController.doPrivileged(new PrivilegedAction() {
|
||||||
public Object run() {
|
public Object run() {
|
||||||
String temp = System.getProperty("java.io.tmpdir");
|
String temp = System.getProperty("java.io.tmpdir");
|
||||||
File tempDir = new File(temp);
|
File tempDir = new File(temp + File.separator + MASTER_INSTALL_DIR);
|
||||||
File[] files = tempDir.listFiles(new FileFilter() {
|
File[] files = tempDir.listFiles(new FileFilter() {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* @see java.io.FileFilter#accept(java.io.File)
|
* @see java.io.FileFilter#accept(java.io.File)
|
||||||
*/
|
*/
|
||||||
public boolean accept(File pathname) {
|
public boolean accept(File pathname) {
|
||||||
return pathname.getAbsolutePath().indexOf("lwjgl") != -1 && isInstallDirectory(pathname);
|
return isStale(pathname);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -283,9 +341,9 @@ public class LWJGLInstaller {
|
||||||
* @param directory Directory to check
|
* @param directory Directory to check
|
||||||
* @return true if the directory is an install directory
|
* @return true if the directory is an install directory
|
||||||
*/
|
*/
|
||||||
private boolean isInstallDirectory(File directory) {
|
private boolean isStale(File directory) {
|
||||||
File installFile = new File(directory.getAbsolutePath() + File.separator + ".lwjglinstaller");
|
File installFile = new File(directory.getAbsolutePath() + File.separator + ".lwjglinstaller");
|
||||||
return installFile.exists();
|
return !installFile.exists();
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue