diff --git a/fml/common/cpw/mods/fml/common/CertificateHelper.java b/fml/common/cpw/mods/fml/common/CertificateHelper.java new file mode 100644 index 0000000000..7f7da1eba4 --- /dev/null +++ b/fml/common/cpw/mods/fml/common/CertificateHelper.java @@ -0,0 +1,52 @@ +package cpw.mods.fml.common; + +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.cert.Certificate; + +public class CertificateHelper { + + private static final String HEXES = "0123456789abcdef"; + + public static String getFingerprint(Certificate certificate) + { + try + { + MessageDigest md = MessageDigest.getInstance("SHA-1"); + byte[] der = certificate.getEncoded(); + md.update(der); + byte[] digest = md.digest(); + return hexify(digest); + } + catch (Exception e) + { + return null; + } + } + + public static String getFingerprint(ByteBuffer buffer) + { + try + { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update(buffer); + byte[] chksum = digest.digest(); + return hexify(chksum); + } + catch (Exception e) + { + return null; + } + } + + private static String hexify(byte[] chksum) + { + final StringBuilder hex = new StringBuilder( 2 * chksum.length ); + for ( final byte b : chksum ) { + hex.append(HEXES.charAt((b & 0xF0) >> 4)) + .append(HEXES.charAt((b & 0x0F))); + } + return hex.toString(); + } + +} diff --git a/fml/common/cpw/mods/fml/common/asm/FMLSanityChecker.java b/fml/common/cpw/mods/fml/common/asm/FMLSanityChecker.java index 4c59358836..3f6a5d51b9 100644 --- a/fml/common/cpw/mods/fml/common/asm/FMLSanityChecker.java +++ b/fml/common/cpw/mods/fml/common/asm/FMLSanityChecker.java @@ -1,5 +1,9 @@ package cpw.mods.fml.common.asm; +import java.io.ObjectInputStream.GetField; +import java.net.JarURLConnection; +import java.security.CodeSource; +import java.security.cert.Certificate; import java.util.Map; import javax.swing.JOptionPane; @@ -9,11 +13,14 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.Opcodes; +import cpw.mods.fml.common.CertificateHelper; +import cpw.mods.fml.common.FMLLog; import cpw.mods.fml.relauncher.IFMLCallHook; import cpw.mods.fml.relauncher.RelaunchClassLoader; public class FMLSanityChecker implements IFMLCallHook { + private static final String FMLFINGERPRINT = ""; static class MLDetectorClassVisitor extends ClassVisitor { private boolean foundMarker = false; @@ -38,6 +45,29 @@ public class FMLSanityChecker implements IFMLCallHook @Override public Void call() throws Exception { + CodeSource codeSource = getClass().getProtectionDomain().getCodeSource(); + boolean goodFML = false; + if (codeSource.getLocation().getProtocol().equals("jar")) + { + Certificate[] certificates = codeSource.getCertificates(); + if (certificates!=null && certificates.length>0) + { + Certificate certificate = certificates[0]; + String fingerprint = CertificateHelper.getFingerprint(certificate); + if (fingerprint.equals(FMLFINGERPRINT)) + { + goodFML = true; + } + } + } + else + { + goodFML = true; + } + if (!goodFML) + { + FMLLog.severe("FML appears to be missing it's signature data. This is not a good thing"); + } byte[] mlClass = cl.getClassBytes("ModLoader"); // Only care in obfuscated env if (mlClass == null) diff --git a/fml/common/cpw/mods/fml/relauncher/RelaunchClassLoader.java b/fml/common/cpw/mods/fml/relauncher/RelaunchClassLoader.java index 5037690a98..f37c3b88aa 100644 --- a/fml/common/cpw/mods/fml/relauncher/RelaunchClassLoader.java +++ b/fml/common/cpw/mods/fml/relauncher/RelaunchClassLoader.java @@ -3,8 +3,12 @@ package cpw.mods.fml.relauncher; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; +import java.net.JarURLConnection; import java.net.URL; import java.net.URLClassLoader; +import java.net.URLConnection; +import java.security.CodeSigner; +import java.security.CodeSource; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -13,8 +17,15 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.jar.Attributes.Name; +import java.util.jar.Attributes; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; import java.util.logging.Level; +import cpw.mods.fml.common.FMLLog; + public class RelaunchClassLoader extends URLClassLoader { private List sources; @@ -26,6 +37,9 @@ public class RelaunchClassLoader extends URLClassLoader private Set classLoaderExceptions = new HashSet(); private Set transformerExceptions = new HashSet(); + private Map packageManifests = new HashMap(); + + private static Manifest EMPTY = new Manifest(); public RelaunchClassLoader(URL[] sources) { @@ -41,6 +55,7 @@ public class RelaunchClassLoader extends URLClassLoader // standard classloader exclusions addClassLoaderExclusion("java."); addClassLoaderExclusion("sun."); + addClassLoaderExclusion("org.lwjgl."); addClassLoaderExclusion("cpw.mods.fml.relauncher."); addClassLoaderExclusion("net.minecraftforge.classloading."); @@ -103,18 +118,58 @@ public class RelaunchClassLoader extends URLClassLoader try { + CodeSigner[] signers = null; int lastDot = name.lastIndexOf('.'); - if (lastDot > -1) + String pkgname = name.substring(0, lastDot); + String fName = name.replace('.', '/').concat(".class"); + String pkgPath = pkgname.replace('.', '/'); + URLConnection urlConnection = findCodeSourceConnectionFor(fName); + if (urlConnection instanceof JarURLConnection && lastDot > -1) { - String pkgname = name.substring(0, lastDot); - if (getPackage(pkgname)==null) + JarURLConnection jarUrlConn = (JarURLConnection)urlConnection; + JarFile jf = jarUrlConn.getJarFile(); + if (jf != null && jf.getManifest() != null) { - definePackage(pkgname, null, null, null, null, null, null, null); + Manifest mf = jf.getManifest(); + JarEntry ent = jf.getJarEntry(fName); + Package pkg = getPackage(pkgname); + if (pkg == null) + { + pkg = definePackage(pkgname, mf, jarUrlConn.getJarFileURL()); + packageManifests.put(pkg, mf); + // need to read the bytes to force the signing to be checked + getClassBytes(name); + signers = ent.getCodeSigners(); + } + else + { + if (pkg.isSealed() && !pkg.isSealed(jarUrlConn.getJarFileURL())) + { + FMLLog.severe("The jar file %s is trying to seal already secured path %s", jf.getName(), pkgname); + } + else if (isSealed(pkgname, mf)) + { + FMLLog.severe("The jar file %s has a security seal for path %s, but that path is defined and not secure", jf.getName(), pkgname); + } + } + } + } + else if (lastDot > -1) + { + Package pkg = getPackage(pkgname); + if (pkg == null) + { + pkg = definePackage(pkgname, null, null, null, null, null, null, null); + packageManifests.put(pkg, EMPTY); + } + else if (pkg.isSealed()) + { + FMLLog.severe("The URL %s is defining elements for sealed path %s", urlConnection.getURL(), pkgname); } } byte[] basicClass = getClassBytes(name); byte[] transformedClass = runTransformers(name, basicClass); - Class cl = defineClass(name, transformedClass, 0, transformedClass.length); + Class cl = defineClass(name, transformedClass, 0, transformedClass.length, new CodeSource(urlConnection.getURL(), signers)); cachedClasses.put(name, cl); return cl; } @@ -125,33 +180,39 @@ public class RelaunchClassLoader extends URLClassLoader } } - public byte[] getClassBytes(String name) throws IOException + private boolean isSealed(String path, Manifest man) { - InputStream classStream = null; - try - { - URL classResource = findResource(name.replace('.', '/').concat(".class")); - if (classResource == null) - { - return null; - } - classStream = classResource.openStream(); - return readFully(classStream); + Attributes attr = man.getAttributes(path); + String sealed = null; + if (attr != null) { + sealed = attr.getValue(Name.SEALED); } - finally - { - if (classStream != null) - { - try - { - classStream.close(); - } - catch (IOException e) - { - // Swallow the close exception - } + if (sealed == null) { + if ((attr = man.getMainAttributes()) != null) { + sealed = attr.getValue(Name.SEALED); } } + return "true".equalsIgnoreCase(sealed); + } + + private URLConnection findCodeSourceConnectionFor(String name) + { + URL res = findResource(name); + if (res != null) + { + try + { + return res.openConnection(); + } + catch (IOException e) + { + throw new RuntimeException(e); + } + } + else + { + return null; + } } private byte[] runTransformers(String name, byte[] basicClass) @@ -210,4 +271,33 @@ public class RelaunchClassLoader extends URLClassLoader { transformerExceptions.add(toExclude); } + + public byte[] getClassBytes(String name) throws IOException + { + InputStream classStream = null; + try + { + URL classResource = findResource(name.replace('.', '/').concat(".class")); + if (classResource == null) + { + return null; + } + classStream = classResource.openStream(); + return readFully(classStream); + } + finally + { + if (classStream != null) + { + try + { + classStream.close(); + } + catch (IOException e) + { + // Swallow the close exception + } + } + } + } } diff --git a/fml/common/cpw/mods/fml/relauncher/RelaunchLibraryManager.java b/fml/common/cpw/mods/fml/relauncher/RelaunchLibraryManager.java index a5f68896ab..4d83ca6c06 100644 --- a/fml/common/cpw/mods/fml/relauncher/RelaunchLibraryManager.java +++ b/fml/common/cpw/mods/fml/relauncher/RelaunchLibraryManager.java @@ -25,6 +25,7 @@ import java.util.jar.Attributes; import java.util.jar.JarFile; import java.util.logging.Level; +import cpw.mods.fml.common.CertificateHelper; import cpw.mods.fml.relauncher.IFMLLoadingPlugin.TransformerExclusions; public class RelaunchLibraryManager @@ -488,7 +489,6 @@ public class RelaunchLibraryManager return loadedLibraries; } - private static final String HEXES = "0123456789abcdef"; private static ByteBuffer downloadBuffer = ByteBuffer.allocateDirect(1 << 22); static IDownloadDisplay downloadMonitor; @@ -560,21 +560,6 @@ public class RelaunchLibraryManager private static String generateChecksum(ByteBuffer buffer) { - try - { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - digest.update(buffer); - byte[] chksum = digest.digest(); - final StringBuilder hex = new StringBuilder( 2 * chksum.length ); - for ( final byte b : chksum ) { - hex.append(HEXES.charAt((b & 0xF0) >> 4)) - .append(HEXES.charAt((b & 0x0F))); - } - return hex.toString(); - } - catch (Exception e) - { - return null; - } + return CertificateHelper.getFingerprint(buffer); } }