Skip to content
This repository has been archived by the owner on Mar 17, 2023. It is now read-only.

Commit

Permalink
Run signature verify before scan (#1)
Browse files Browse the repository at this point in the history
* Run signature verify before scan
* Update fmlloader dependency and mod name
  • Loading branch information
IzzelAliz authored Jun 2, 2022
1 parent 185f7d5 commit 57a940b
Show file tree
Hide file tree
Showing 2 changed files with 35 additions and 82 deletions.
15 changes: 8 additions & 7 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import java.time.format.DateTimeFormatter
import java.time.temporal.ChronoUnit

plugins {
id 'com.github.johnrengelman.shadow' version '6.1.0'
id 'com.github.johnrengelman.shadow' version '7.1.2'
id 'java-library'
id 'eclipse'
id 'idea'
Expand All @@ -15,10 +15,10 @@ version = '0.1.7'
group = 'org.teacon'
archivesBaseName = 'RemoteSync-FML'

sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17

compileJava {
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_1_8
sourceCompatibility = targetCompatibility = JavaVersion.VERSION_17
}

repositories {
Expand All @@ -32,8 +32,9 @@ repositories {
dependencies {
shadow group: 'org.bouncycastle', name: 'bcpg-jdk15on', version: '1.68'

implementation group: 'net.minecraftforge', name: 'forgespi', version: '3.2.0'
implementation group: 'cpw.mods', name: 'modlauncher', version: '8.0.6'
implementation group: 'net.minecraftforge', name: 'forgespi', version: '4.0.9'
implementation group: 'cpw.mods', name: 'modlauncher', version: '9.1.3'
implementation group: 'net.minecraftforge', name: 'fmlloader', version: '1.18.2-40.1.25'
implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.0'
implementation group: 'org.apache.logging.log4j', name: 'log4j-api', version: '2.17.1'
}
Expand All @@ -56,10 +57,10 @@ shadowJar {
jar {
manifest {
attributes([
"Specification-Title": "ModSync",
"Specification-Title": "RemoteSync",
"Specification-Vendor": "3TUSK",
"Specification-Version": "1.0",
"Implementation-Title": "ModSync",
"Implementation-Title": "RemoteSync",
"Implementation-Version": archiveVersion.get(),
"Implementation-Vendor": "3TUSK",
"Implementation-Timestamp": DateTimeFormatter.ISO_INSTANT.format(Instant.now().truncatedTo(ChronoUnit.SECONDS))
Expand Down
102 changes: 27 additions & 75 deletions src/main/java/org/teacon/sync/SyncedModLocator.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright (C) 2021 3TUSK
*
*
* 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; either
Expand All @@ -23,9 +23,8 @@
import com.google.gson.Gson;
import cpw.mods.modlauncher.Launcher;
import cpw.mods.modlauncher.api.IEnvironment;
import net.minecraftforge.fml.loading.moddiscovery.AbstractJarFileLocator;
import net.minecraftforge.forgespi.Environment;
import net.minecraftforge.forgespi.locating.IModFile;
import net.minecraftforge.forgespi.locating.IModLocator;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bouncycastle.openpgp.PGPCompressedData;
Expand All @@ -44,35 +43,24 @@
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Collection;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public final class SyncedModLocator implements IModLocator {
public final class SyncedModLocator extends AbstractJarFileLocator {

private static final Logger LOGGER = LogManager.getLogger("RemoteSync");

private static final Gson GSON = new Gson();

private IModLocator parent;

private final Path modDirBase;
private final Consumer<String> progressFeed;

private final PGPKeyStore keyStore;

private final CompletableFuture<Void> fetchPathsTask;

private Set<String> allowedFiles = Collections.emptySet();
private final Set<Path> invalidFiles = new HashSet<>();
private final CompletableFuture<Collection<Path>> fetchPathsTask;

public SyncedModLocator() throws Exception {
this.progressFeed = Launcher.INSTANCE.environment().getProperty(Environment.Keys.PROGRESSMESSAGE.get()).orElse(msg -> {});
Expand All @@ -82,7 +70,7 @@ public SyncedModLocator() throws Exception {
if (Files.exists(cfgPath)) {
cfg = GSON.fromJson(Files.newBufferedReader(cfgPath, StandardCharsets.UTF_8), Config.class);
} else {
LOGGER.warn("RemoteSync config remote_sync.json does not exist. All configurable values will use their default values instead.");
LOGGER.warn("RemoteSync config remote_sync.json does not exist. All configurable values will use their default values instead.");
cfg = new Config();
}
final Path keyStorePath = gameDir.resolve(cfg.keyRingPath);
Expand All @@ -108,24 +96,16 @@ public SyncedModLocator() throws Exception {
LOGGER.warn("Failed to fetch mod list from remote", e);
return new ModEntry[0];
}
}).whenComplete((entries, err) -> {
this.allowedFiles = Arrays.stream(entries).map(e -> e.name).collect(Collectors.toSet());
}).thenComposeAsync(entries -> CompletableFuture.allOf(
Arrays.stream(entries).flatMap(e -> Stream.of(
Utils.downloadIfMissingAsync(this.modDirBase.resolve(e.name), e.file, cfg.timeout, cfg.preferLocalCache, this.progressFeed),
Utils.downloadIfMissingAsync(this.modDirBase.resolve(e.name + ".sig"), e.sig, cfg.timeout, cfg.preferLocalCache, this.progressFeed)
)).toArray(CompletableFuture[]::new)
));
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
for (Path p : this.invalidFiles) {
try {
Files.deleteIfExists(p);
} catch (IOException ignore) {
// No-op
}
}
this.keyStore.saveTo(keyStorePath);
}, "RemoteSync Clean-up"));
}).thenComposeAsync(entries -> {
var futures = Arrays.stream(entries).flatMap(e -> Stream.of(
Utils.downloadIfMissingAsync(this.modDirBase.resolve(e.name), e.file, cfg.timeout, cfg.preferLocalCache, this.progressFeed),
Utils.downloadIfMissingAsync(this.modDirBase.resolve(e.name + ".sig"), e.sig, cfg.timeout, cfg.preferLocalCache, this.progressFeed)
)).toList();
return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenApply(v -> Arrays.stream(entries)
.map(it -> this.modDirBase.resolve(it.name))
.filter(this::isValid).toList());
});
}

private static PGPSignatureList getSigList(FileChannel fc) throws Exception {
Expand All @@ -134,32 +114,25 @@ private static PGPSignatureList getSigList(FileChannel fc) throws Exception {
BcPGPObjectFactory factory = new BcPGPObjectFactory(input);
Object o = factory.nextObject();
if (o instanceof PGPCompressedData) {
PGPCompressedData compressedData = (PGPCompressedData)o;
PGPCompressedData compressedData = (PGPCompressedData) o;
factory = new BcPGPObjectFactory(compressedData.getDataStream());
sigList = (PGPSignatureList)factory.nextObject();
sigList = (PGPSignatureList) factory.nextObject();
} else {
sigList = (PGPSignatureList)o;
sigList = (PGPSignatureList) o;
}
}
return sigList;
}

@Override
public List<IModFile> scanMods() {
public Stream<Path> scanCandidates() {
try {
this.fetchPathsTask.join();
return this.parent.scanMods()
.stream()
// Work around https://github.com/MinecraftForge/MinecraftForge/issues/6756
// The signature check MUST be in the place for best-effort safety
.filter(this::isValid)
.filter(mod -> this.allowedFiles.contains(mod.getFileName()))
.collect(Collectors.toList());
return this.fetchPathsTask.join().stream();
} catch (Exception e) {
LOGGER.error("Mod downloading worker encountered error and cannot continue. " +
"No mod will be loaded from the remote-synced locator. ", e);
System.setProperty("org.teacon.sync.failed", "true");
return Collections.emptyList();
return Stream.empty();
}
}

Expand All @@ -168,36 +141,15 @@ public String name() {
return "Remote Synced";
}

@Override
public Path findPath(IModFile modFile, String... path) {
return this.parent.findPath(modFile, path);
}

@Override
public void scanFile(IModFile modFile, Consumer<Path> pathConsumer) {
this.parent.scanFile(modFile, pathConsumer);
}

@Override
public Optional<Manifest> findManifest(Path file) {
return this.parent.findManifest(file);
}

@Override
public void initArguments(Map<String, ?> arguments) {
this.parent = Launcher.INSTANCE.environment()
.getProperty(Environment.Keys.MODDIRECTORYFACTORY.get())
.orElseThrow(() -> new RuntimeException("Cannot find ModDirectoryFactory"))
.build(this.modDirBase, "Remote Synced Backend");
}

@Override
public boolean isValid(IModFile modFile) {
private boolean isValid(Path modFile) {
LOGGER.debug("Verifying {}", modFile.getFileName());
this.progressFeed.accept("RemoteSync: verifying " + modFile.getFileName());
final Path modPath = modFile.getFilePath();
final Path sigPath = modPath.resolveSibling(modFile.getFileName() + ".sig");
try (FileChannel mod = FileChannel.open(modPath, StandardOpenOption.READ)) {
final Path sigPath = modFile.resolveSibling(modFile.getFileName() + ".sig");
try (FileChannel mod = FileChannel.open(modFile, StandardOpenOption.READ)) {
try (FileChannel sig = FileChannel.open(sigPath, StandardOpenOption.READ)) {
final PGPSignatureList sigList;
try {
Expand All @@ -215,8 +167,8 @@ public boolean isValid(IModFile modFile) {
LOGGER.debug("Verification pass for {}", modFile.getFileName());
} else {
LOGGER.warn("Verification fail for {}, will be excluded from loading", modFile.getFileName());
this.invalidFiles.add(modPath.toAbsolutePath().normalize());
this.invalidFiles.add(sigPath.toAbsolutePath().normalize());
Files.deleteIfExists(modFile.toAbsolutePath().normalize());
Files.deleteIfExists(sigPath.toAbsolutePath().normalize());
}
return pass;
}
Expand Down

0 comments on commit 57a940b

Please sign in to comment.