Add signature reporting for mods, using new signature capture from ModLauncher. Need to figure out
how to reflect Minecraft's JAR signatures into here. Signed-off-by: cpw <cpw+github@weeksfamily.ca>
This commit is contained in:
parent
6ff6277efa
commit
ae160cad12
12 changed files with 132 additions and 67 deletions
|
@ -435,11 +435,11 @@ project(':forge') {
|
|||
installer 'org.ow2.asm:asm-tree:7.2'
|
||||
installer 'org.ow2.asm:asm-util:7.2'
|
||||
installer 'org.ow2.asm:asm-analysis:7.2'
|
||||
installer 'cpw.mods:modlauncher:7.0.+'
|
||||
installer 'cpw.mods:modlauncher:8.0.+'
|
||||
installer 'cpw.mods:grossjava9hacks:1.3.+'
|
||||
installer 'net.minecraftforge:accesstransformers:2.2.+:shadowed'
|
||||
installer 'net.minecraftforge:eventbus:3.0.+:service'
|
||||
installer 'net.minecraftforge:forgespi:3.1.+'
|
||||
installer 'net.minecraftforge:forgespi:3.2.+'
|
||||
installer 'net.minecraftforge:coremods:3.0.+'
|
||||
installer 'net.minecraftforge:unsafe:0.2.+'
|
||||
installer 'com.electronwill.night-config:core:3.6.2'
|
||||
|
|
|
@ -50,4 +50,8 @@ public class Java9BackportUtils
|
|||
emptyAction.run();
|
||||
}
|
||||
}
|
||||
|
||||
public static <T> Stream<T> toStream(final Optional<T> optional) {
|
||||
return optional.map(Stream::of).orElseGet(Stream::empty);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
package net.minecraftforge.fml.loading;
|
||||
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
@ -83,6 +84,12 @@ public class ModJarURLHandler extends URLStreamHandler
|
|||
}
|
||||
}
|
||||
|
||||
// Used to cache protectiondomains by "top level object" aka the modid
|
||||
@Override
|
||||
public URL getURL() {
|
||||
return LamdbaExceptionUtils.uncheck(()->new URL("modjar://"+modid));
|
||||
}
|
||||
|
||||
public Optional<Manifest> getManifest() {
|
||||
return manifest;
|
||||
}
|
||||
|
|
|
@ -55,4 +55,13 @@ public class StringUtils
|
|||
public static String parseStringFormat(final String input, final Map<String, String> properties) {
|
||||
return StrSubstitutor.replace(input, properties);
|
||||
}
|
||||
|
||||
public static String binToHex(final byte[] bytes) {
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
sb.append(Integer.toHexString((bytes[i]&0xf0) >>4));
|
||||
sb.append(Integer.toHexString(bytes[i]&0x0f));
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,20 +19,25 @@
|
|||
|
||||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.file.FileSystem;
|
||||
import java.nio.file.FileSystems;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.CodeSigner;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -79,20 +84,31 @@ public abstract class AbstractJarFileLocator implements IModLocator {
|
|||
LOGGER.debug(SCAN,"Scan finished: {}", file);
|
||||
}
|
||||
|
||||
static final Method ENSURE_INIT = LamdbaExceptionUtils.uncheck(()->JarFile.class.getDeclaredMethod("ensureInitialization"));
|
||||
static {
|
||||
ENSURE_INIT.setAccessible(true);
|
||||
}
|
||||
@Override
|
||||
public Optional<Manifest> findManifest(final Path file)
|
||||
{
|
||||
return findManifestAndSigners(file).getKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Optional<Manifest>, Optional<CodeSigner[]>> findManifestAndSigners(final Path file) {
|
||||
try (JarFile jf = new JarFile(file.toFile()))
|
||||
{
|
||||
final Manifest manifest = jf.getManifest();
|
||||
if (manifest != null) {
|
||||
// injection of signing stuff here
|
||||
if (manifest!=null) {
|
||||
final JarEntry jarEntry = jf.getJarEntry(JarFile.MANIFEST_NAME);
|
||||
LamdbaExceptionUtils.uncheck(() -> ENSURE_INIT.invoke(jf));
|
||||
return Pair.of(Optional.of(manifest), Optional.ofNullable(jarEntry.getCodeSigners()));
|
||||
}
|
||||
return Optional.ofNullable(manifest);
|
||||
return Pair.of(Optional.empty(), Optional.empty());
|
||||
}
|
||||
catch (IOException e)
|
||||
{
|
||||
return Optional.empty();
|
||||
return Pair.of(Optional.empty(), Optional.empty());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,7 @@ import net.minecraftforge.fml.loading.progress.StartupMessageManager;
|
|||
import net.minecraftforge.forgespi.Environment;
|
||||
import net.minecraftforge.forgespi.locating.IModFile;
|
||||
import net.minecraftforge.forgespi.locating.IModLocator;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
|
@ -45,6 +46,7 @@ import java.nio.file.FileSystems;
|
|||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.security.CodeSigner;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -57,6 +59,8 @@ import java.util.Objects;
|
|||
import java.util.Optional;
|
||||
import java.util.ServiceLoader;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.jar.JarEntry;
|
||||
import java.util.jar.JarFile;
|
||||
import java.util.jar.Manifest;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -215,6 +219,24 @@ public class ModDiscoverer {
|
|||
LOGGER.debug(SCAN,"Scan finished: {}", modFile);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<Optional<Manifest>, Optional<CodeSigner[]>> findManifestAndSigners(final Path file) {
|
||||
if (Files.isDirectory(mcJar)) {
|
||||
return Pair.of(Optional.empty(), Optional.empty());
|
||||
}
|
||||
try (JarFile jf = new JarFile(mcJar.toFile())) {
|
||||
final Manifest manifest = jf.getManifest();
|
||||
if (manifest!=null) {
|
||||
final JarEntry jarEntry = jf.getJarEntry(JarFile.MANIFEST_NAME);
|
||||
LamdbaExceptionUtils.uncheck(() -> AbstractJarFileLocator.ENSURE_INIT.invoke(jf));
|
||||
return Pair.of(Optional.of(manifest), Optional.ofNullable(jarEntry.getCodeSigners()));
|
||||
}
|
||||
} catch (IOException ioe) {
|
||||
return Pair.of(Optional.empty(), Optional.empty());
|
||||
}
|
||||
return Pair.of(Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<Manifest> findManifest(final Path file) {
|
||||
return Optional.empty();
|
||||
|
|
|
@ -20,16 +20,29 @@
|
|||
package net.minecraftforge.fml.loading.moddiscovery;
|
||||
|
||||
import com.google.common.base.Strings;
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import net.minecraftforge.fml.loading.Java9BackportUtils;
|
||||
import net.minecraftforge.fml.loading.StringUtils;
|
||||
import net.minecraftforge.forgespi.language.IConfigurable;
|
||||
import net.minecraftforge.forgespi.language.IModFileInfo;
|
||||
import net.minecraftforge.forgespi.language.IModInfo;
|
||||
import net.minecraftforge.forgespi.language.MavenVersionAdapter;
|
||||
import org.apache.commons.lang3.tuple.Pair;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.apache.maven.artifact.versioning.VersionRange;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import java.net.URL;
|
||||
import java.security.CodeSigner;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.NoSuchProviderException;
|
||||
import java.security.SignatureException;
|
||||
import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -55,6 +68,7 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
|||
// Caches the manifest of the mod jar as parsing the manifest can be expensive for
|
||||
// signed jars.
|
||||
private final Optional<Manifest> manifest;
|
||||
private final Optional<CodeSigner[]> signers;
|
||||
|
||||
ModFileInfo(final ModFile modFile, final IConfigurable config)
|
||||
{
|
||||
|
@ -83,7 +97,9 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
|||
this.modFile::getFileName,
|
||||
() -> this.mods.stream().map(IModInfo::getModId).collect(Collectors.joining(",", "{", "}")),
|
||||
() -> this.mods.stream().map(IModInfo::getVersion).map(Objects::toString).collect(Collectors.joining(",", "{", "}")));
|
||||
this.manifest = modFile.getLocator().findManifest(modFile.getFilePath());
|
||||
final Pair<Optional<Manifest>, Optional<CodeSigner[]>> manifestAndSigners = modFile.getLocator().findManifestAndSigners(modFile.getFilePath());
|
||||
this.manifest = manifestAndSigners.getKey();
|
||||
this.signers = manifestAndSigners.getValue();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -153,4 +169,42 @@ public class ModFileInfo implements IModFileInfo, IConfigurable
|
|||
{
|
||||
return Strings.isNullOrEmpty(license);
|
||||
}
|
||||
|
||||
public Optional<CodeSigner[]> getCodeSigners() {
|
||||
return this.signers;
|
||||
}
|
||||
|
||||
public Optional<String> getCodeSigningFingerprint() {
|
||||
return Java9BackportUtils.toStream(this.signers)
|
||||
.flatMap(csa->csa[0].getSignerCertPath().getCertificates().stream())
|
||||
.findFirst()
|
||||
.map(LamdbaExceptionUtils.rethrowFunction(Certificate::getEncoded))
|
||||
.map(bytes->LamdbaExceptionUtils.uncheck(()->MessageDigest.getInstance("SHA-256")).digest(bytes))
|
||||
.map(StringUtils::binToHex)
|
||||
.map(str-> String.join(":", str.split("(?<=\\G.{2})")));
|
||||
}
|
||||
|
||||
public Optional<String> getTrustData() {
|
||||
return Java9BackportUtils.toStream(this.signers)
|
||||
.flatMap(csa->csa[0].getSignerCertPath().getCertificates().stream())
|
||||
.findFirst()
|
||||
.map(X509Certificate.class::cast)
|
||||
.map(c->{
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(c.getSubjectX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
|
||||
boolean selfSigned = false;
|
||||
try {
|
||||
c.verify(c.getPublicKey());
|
||||
selfSigned = true;
|
||||
} catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchProviderException e) {
|
||||
// not self signed
|
||||
}
|
||||
if (selfSigned) {
|
||||
sb.append(" self-signed");
|
||||
} else {
|
||||
sb.append(" signed by ").append(c.getIssuerX500Principal().getName(X500Principal.RFC2253).split(",")[0]);
|
||||
};
|
||||
return sb.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,7 +19,6 @@
|
|||
|
||||
package net.minecraftforge.fml;
|
||||
|
||||
import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
|
||||
import net.minecraftforge.eventbus.api.Event;
|
||||
import net.minecraftforge.fml.config.ModConfig;
|
||||
import net.minecraftforge.fml.event.lifecycle.IModBusEvent;
|
||||
|
|
|
@ -82,9 +82,12 @@ public class ModList
|
|||
}
|
||||
|
||||
private String fileToLine(ModFile mf) {
|
||||
return mf.getFileName() + " " + mf.getModInfos().get(0).getDisplayName() + " " +
|
||||
mf.getModInfos().stream().map(mi -> mi.getModId() + "@" + mi.getVersion() + " " +
|
||||
getModContainerState(mi.getModId())).collect(Collectors.joining(", ", "{", "}"));
|
||||
return String.format("%-50.50s|%-30.30s|%-30.30s|%-20.20s|%-10.10s|%s", mf.getFileName(),
|
||||
mf.getModInfos().get(0).getDisplayName(),
|
||||
mf.getModInfos().get(0).getModId(),
|
||||
mf.getModInfos().get(0).getVersion(),
|
||||
getModContainerState(mf.getModInfos().get(0).getModId()),
|
||||
((ModFileInfo)mf.getModFileInfo()).getCodeSigningFingerprint().orElse("NOSIGNATURE"));
|
||||
}
|
||||
private String crashReport() {
|
||||
return "\n"+applyForEachModFile(this::fileToLine).collect(Collectors.joining("\n\t\t", "\t\t", ""));
|
||||
|
|
|
@ -41,7 +41,6 @@ import com.mojang.blaze3d.systems.RenderSystem;
|
|||
|
||||
import net.minecraft.client.Minecraft;
|
||||
import net.minecraft.client.gui.FontRenderer;
|
||||
import net.minecraft.client.gui.RenderComponentsUtil;
|
||||
import net.minecraft.client.gui.screen.Screen;
|
||||
import net.minecraft.client.gui.widget.TextFieldWidget;
|
||||
import net.minecraft.client.gui.widget.button.Button;
|
||||
|
@ -50,7 +49,6 @@ import net.minecraft.client.renderer.Tessellator;
|
|||
import net.minecraft.client.renderer.texture.DynamicTexture;
|
||||
import net.minecraft.client.renderer.texture.NativeImage;
|
||||
import net.minecraft.client.renderer.texture.TextureManager;
|
||||
import net.minecraft.client.resources.I18n;
|
||||
import net.minecraft.util.IReorderingProcessor;
|
||||
import net.minecraft.util.ResourceLocation;
|
||||
import net.minecraft.util.Util;
|
||||
|
@ -462,6 +460,9 @@ public class ModListScreen extends Screen
|
|||
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.license", selectedMod.getOwningFile().getLicense()));
|
||||
lines.add(null);
|
||||
lines.add(selectedMod.getDescription());
|
||||
lines.add(null);
|
||||
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.signature", selectedMod.getOwningFile().getCodeSigningFingerprint().orElse(ForgeI18n.parseMessage("fml.menu.mods.info.signature.unsigned"))));
|
||||
lines.add(ForgeI18n.parseMessage("fml.menu.mods.info.trust", selectedMod.getOwningFile().getTrustData().orElse(ForgeI18n.parseMessage("fml.menu.mods.info.trust.noauthority"))));
|
||||
|
||||
if ((vercheck.status == VersionChecker.Status.OUTDATED || vercheck.status == VersionChecker.Status.BETA_OUTDATED) && vercheck.changes.size() > 0)
|
||||
{
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* Minecraft Forge
|
||||
* Copyright (c) 2016-2020.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation version 2.1
|
||||
* of the License.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*/
|
||||
|
||||
package net.minecraftforge.fml.event.lifecycle;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Set;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import net.minecraftforge.fml.common.Mod;
|
||||
|
||||
|
||||
/**
|
||||
* A special event used when the {@link Mod#certificateFingerprint()} doesn't match the certificate loaded from the JAR
|
||||
* file. You could use this to log a warning that the code that is running might not be yours, for example.
|
||||
*/
|
||||
public class FMLFingerprintViolationEvent extends ModLifecycleEvent
|
||||
{
|
||||
|
||||
private final boolean isDirectory;
|
||||
private final Set<String> fingerprints;
|
||||
private final File source;
|
||||
private final String expectedFingerprint;
|
||||
|
||||
public FMLFingerprintViolationEvent(boolean isDirectory, File source, ImmutableSet<String> fingerprints, String expectedFingerprint)
|
||||
{
|
||||
super(null);
|
||||
this.isDirectory = isDirectory;
|
||||
this.source = source;
|
||||
this.fingerprints = fingerprints;
|
||||
this.expectedFingerprint = expectedFingerprint;
|
||||
}
|
||||
|
||||
public boolean isDirectory() { return isDirectory; }
|
||||
public Set<String> getFingerprints() { return fingerprints; }
|
||||
public File getSource() { return source; }
|
||||
public String getExpectedFingerprint() { return expectedFingerprint; }
|
||||
}
|
|
@ -14,6 +14,10 @@
|
|||
"fml.menu.mods.info.authors":"Authors: {0}",
|
||||
"fml.menu.mods.info.displayurl":"Homepage: {0}",
|
||||
"fml.menu.mods.info.license":"License: {0}",
|
||||
"fml.menu.mods.info.signature":"Signature: {0}",
|
||||
"fml.menu.mods.info.signature.unsigned":"UNSIGNED",
|
||||
"fml.menu.mods.info.trust": "Trust: {0}",
|
||||
"fml.menu.mods.info.trust.noauthority": "None",
|
||||
"fml.menu.mods.info.nochildmods":"No child mods found",
|
||||
"fml.menu.mods.info.childmods":"Child mods: {0}",
|
||||
"fml.menu.mods.info.updateavailable":"Update available: {0}",
|
||||
|
|
Loading…
Reference in a new issue