[FEATURE] CertificatePinning, AGENT, min/max/recommend Java and other

This commit is contained in:
Gravita 2021-01-29 00:13:21 +07:00
parent 78265a7576
commit 4592eee953
12 changed files with 254 additions and 46 deletions

View file

@ -107,8 +107,9 @@ protected void initProps() {
properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey()); properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey());
properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512)); properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512));
properties.put("launcher.guardType", server.config.launcher.guardType); properties.put("launcher.guardType", server.config.launcher.guardType);
properties.put("launcher.isWarningMissArchJava", server.config.launcher.warningMissArchJava);
properties.put("launchercore.env", server.config.env); properties.put("launchercore.env", server.config.env);
properties.put("launcher.memory", server.config.launcher.memoryLimit);
properties.put("launcher.certificatePinning", server.config.launcher.certificatePinning);
properties.put("runtimeconfig.passwordEncryptKey", server.runtime.passwordEncryptKey); properties.put("runtimeconfig.passwordEncryptKey", server.runtime.passwordEncryptKey);
String launcherSalt = SecurityHelper.randomStringToken(); String launcherSalt = SecurityHelper.randomStringToken();
byte[] launcherSecureHash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256, byte[] launcherSecureHash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256,

View file

@ -93,7 +93,6 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) {
newConfig.launcher = new LauncherConf(); newConfig.launcher = new LauncherConf();
newConfig.launcher.guardType = "no"; newConfig.launcher.guardType = "no";
newConfig.launcher.compress = true; newConfig.launcher.compress = true;
newConfig.launcher.warningMissArchJava = true;
newConfig.launcher.attachLibraryBeforeProGuard = false; newConfig.launcher.attachLibraryBeforeProGuard = false;
newConfig.launcher.deleteTempFiles = true; newConfig.launcher.deleteTempFiles = true;
newConfig.launcher.enabledProGuard = true; newConfig.launcher.enabledProGuard = true;
@ -289,11 +288,14 @@ public static class LauncherConf {
public String guardType; public String guardType;
public boolean attachLibraryBeforeProGuard; public boolean attachLibraryBeforeProGuard;
public boolean compress; public boolean compress;
@Deprecated
public boolean warningMissArchJava; public boolean warningMissArchJava;
public boolean enabledProGuard; public boolean enabledProGuard;
public boolean stripLineNumbers; public boolean stripLineNumbers;
public boolean deleteTempFiles; public boolean deleteTempFiles;
public boolean proguardGenMappings; public boolean proguardGenMappings;
public boolean certificatePinning;
public int memoryLimit = 256;
} }
public static class NettyConfig { public static class NettyConfig {

View file

@ -26,11 +26,7 @@
classifier = 'clean' classifier = 'clean'
manifest.attributes("Main-Class": mainClassName, manifest.attributes("Main-Class": mainClassName,
"Premain-Class": mainAgentName, "Premain-Class": mainAgentName,
"Can-Redefine-Classes": "true", "Multi-Release": "true")
"Multi-Release": "true",
"Can-Retransform-Classes": "true",
"Can-Set-Native-Method-Prefix": "true",
"Multi-Release-Jar": "true")
} }
task sourcesJar(type: Jar) { task sourcesJar(type: Jar) {

View file

@ -22,6 +22,8 @@ public class ClientLauncherWrapper {
public static final String NO_JAVA_CHECK_PROPERTY = "launcher.noJavaCheck"; public static final String NO_JAVA_CHECK_PROPERTY = "launcher.noJavaCheck";
public static boolean noJavaCheck = Boolean.getBoolean(NO_JAVA_CHECK_PROPERTY); public static boolean noJavaCheck = Boolean.getBoolean(NO_JAVA_CHECK_PROPERTY);
public static boolean waitProcess = Boolean.getBoolean(WAIT_PROCESS_PROPERTY); public static boolean waitProcess = Boolean.getBoolean(WAIT_PROCESS_PROPERTY);
@LauncherInject("launcher.memory")
public static int launcherMemoryLimit;
public static class JavaVersion { public static class JavaVersion {
public final Path jvmDir; public final Path jvmDir;

View file

@ -10,6 +10,7 @@
import pro.gravit.launcher.guard.LauncherGuardManager; import pro.gravit.launcher.guard.LauncherGuardManager;
import pro.gravit.launcher.hasher.FileNameMatcher; import pro.gravit.launcher.hasher.FileNameMatcher;
import pro.gravit.launcher.hasher.HashedDir; import pro.gravit.launcher.hasher.HashedDir;
import pro.gravit.launcher.hasher.HashedEntry;
import pro.gravit.launcher.managers.ClientGsonManager; import pro.gravit.launcher.managers.ClientGsonManager;
import pro.gravit.launcher.modules.events.PreConfigPhase; import pro.gravit.launcher.modules.events.PreConfigPhase;
import pro.gravit.launcher.patches.FMLPatcher; import pro.gravit.launcher.patches.FMLPatcher;
@ -26,25 +27,24 @@
import pro.gravit.utils.helper.*; import pro.gravit.utils.helper.*;
import javax.swing.*; import javax.swing.*;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType; import java.lang.invoke.MethodType;
import java.net.InetSocketAddress; import java.net.*;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.URL;
import java.nio.file.FileVisitResult; import java.nio.file.FileVisitResult;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor; import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes; import java.nio.file.attribute.BasicFileAttributes;
import java.util.*; import java.util.*;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
public class ClientLauncherEntryPoint { public class ClientLauncherEntryPoint {
private static ClientClassLoader classLoader; private static ClassLoader classLoader;
private static ClientLauncherProcess.ClientParams readParams(SocketAddress address) throws IOException { private static ClientLauncherProcess.ClientParams readParams(SocketAddress address) throws IOException {
try (Socket socket = IOHelper.newSocket()) { try (Socket socket = IOHelper.newSocket()) {
@ -77,18 +77,20 @@ public static void main(String[] args) throws Throwable {
LauncherConfig.initModules(LauncherEngine.modulesManager); //INIT LauncherConfig.initModules(LauncherEngine.modulesManager); //INIT
LauncherEngine.modulesManager.initModules(null); LauncherEngine.modulesManager.initModules(null);
initGson(LauncherEngine.modulesManager); initGson(LauncherEngine.modulesManager);
LauncherEngine.verifyNoAgent();
LauncherEngine.modulesManager.invokeEvent(new PreConfigPhase()); LauncherEngine.modulesManager.invokeEvent(new PreConfigPhase());
engine.readKeys(); engine.readKeys();
LauncherGuardManager.initGuard(true); LauncherGuardManager.initGuard(true);
LogHelper.debug("Reading ClientLauncher params"); LogHelper.debug("Reading ClientLauncher params");
ClientLauncherProcess.ClientParams params = readParams(new InetSocketAddress("127.0.0.1", Launcher.getConfig().clientPort)); ClientLauncherProcess.ClientParams params = readParams(new InetSocketAddress("127.0.0.1", Launcher.getConfig().clientPort));
if(params.profile.classLoaderConfig != ClientProfile.ClassLoaderConfig.AGENT) {
LauncherEngine.verifyNoAgent();
}
ClientProfile profile = params.profile; ClientProfile profile = params.profile;
Launcher.profile = profile; Launcher.profile = profile;
AuthService.profile = profile; AuthService.profile = profile;
LauncherEngine.clientParams = params; LauncherEngine.clientParams = params;
Request.setSession(params.session); Request.setSession(params.session);
checkJVMBitsAndVersion(); checkJVMBitsAndVersion(params.profile.getMinJavaVersion(), params.profile.getRecommendJavaVersion(), params.profile.getMaxJavaVersion(), params.profile.isWarnMissJavaVersion());
LauncherEngine.modulesManager.invokeEvent(new ClientProcessInitPhase(engine, params)); LauncherEngine.modulesManager.invokeEvent(new ClientProcessInitPhase(engine, params));
Path clientDir = Paths.get(params.clientDir); Path clientDir = Paths.get(params.clientDir);
@ -103,10 +105,6 @@ public static void main(String[] args) throws Throwable {
if (a instanceof OptionalActionClassPath) if (a instanceof OptionalActionClassPath)
resolveClassPathStream(clientDir, ((OptionalActionClassPath) a).args).map(IOHelper::toURL).collect(Collectors.toCollection(() -> classpath)); resolveClassPathStream(clientDir, ((OptionalActionClassPath) a).args).map(IOHelper::toURL).collect(Collectors.toCollection(() -> classpath));
} }
classLoader = new ClientClassLoader(classpath.toArray(new URL[0]), ClassLoader.getSystemClassLoader());
Thread.currentThread().setContextClassLoader(classLoader);
classLoader.nativePath = clientDir.resolve("natives").toString();
LauncherEngine.modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(engine, classLoader, profile));
// Start client with WatchService monitoring // Start client with WatchService monitoring
boolean digest = !profile.isUpdateFastCheck(); boolean digest = !profile.isUpdateFastCheck();
LogHelper.debug("Restore sessions"); LogHelper.debug("Restore sessions");
@ -129,13 +127,34 @@ public static void main(String[] args) throws Throwable {
LogHelper.error(e); LogHelper.error(e);
} }
}; };
if(params.profile.classLoaderConfig == ClientProfile.ClassLoaderConfig.LAUNCHER) {
ClientClassLoader classLoader = new ClientClassLoader(classpath.toArray(new URL[0]), ClassLoader.getSystemClassLoader());
ClientLauncherEntryPoint.classLoader = classLoader;
Thread.currentThread().setContextClassLoader(classLoader);
classLoader.nativePath = clientDir.resolve("natives").toString();
LauncherEngine.modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(engine, classLoader, profile));
AuthService.username = params.playerProfile.username; AuthService.username = params.playerProfile.username;
AuthService.uuid = params.playerProfile.uuid; AuthService.uuid = params.playerProfile.uuid;
ClientService.classLoader = classLoader; ClientService.classLoader = classLoader;
ClientService.nativePath = classLoader.nativePath; ClientService.nativePath = classLoader.nativePath;
classLoader.addURL(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL()); classLoader.addURL(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL());
//classForName(classLoader, "com.google.common.collect.ForwardingMultimap");
ClientService.baseURLs = classLoader.getURLs(); ClientService.baseURLs = classLoader.getURLs();
}
else if(params.profile.classLoaderConfig == ClientProfile.ClassLoaderConfig.AGENT) {
ClientLauncherEntryPoint.classLoader = ClassLoader.getSystemClassLoader();
classpath.add(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL());
for(URL url : classpath) {
LauncherAgent.addJVMClassPath(Paths.get(url.toURI()));
}
ClientService.instrumentation = LauncherAgent.inst;
ClientService.nativePath = clientDir.resolve("natives").toString();
LauncherEngine.modulesManager.invokeEvent(new ClientProcessClassLoaderEvent(engine, classLoader, profile));
AuthService.username = params.playerProfile.username;
AuthService.uuid = params.playerProfile.uuid;
ClientService.classLoader = classLoader;
ClientService.baseURLs = classpath.toArray(new URL[0]);
}
LauncherEngine.modulesManager.invokeEvent(new ClientProcessReadyEvent(engine, params)); LauncherEngine.modulesManager.invokeEvent(new ClientProcessReadyEvent(engine, params));
LogHelper.debug("Starting JVM and client WatchService"); LogHelper.debug("Starting JVM and client WatchService");
FileNameMatcher assetMatcher = profile.getAssetUpdateMatcher(); FileNameMatcher assetMatcher = profile.getAssetUpdateMatcher();
@ -179,35 +198,42 @@ public static void verifyHDir(Path dir, HashedDir hdir, FileNameMatcher matcher,
HashedDir currentHDir = new HashedDir(dir, matcher, true, digest); HashedDir currentHDir = new HashedDir(dir, matcher, true, digest);
HashedDir.Diff diff = hdir.diff(currentHDir, matcher); HashedDir.Diff diff = hdir.diff(currentHDir, matcher);
if (!diff.isSame()) { if (!diff.isSame()) {
/*AtomicBoolean isFoundFile = new AtomicBoolean(false); if(LogHelper.isDebugEnabled()) {
diff.extra.walk(File.separator, (e,k,v) -> { diff.extra.walk(File.separator, (e, k, v) -> {
if(v.getType().equals(HashedEntry.Type.FILE)) { LogHelper.error("Extra file %s", e); isFoundFile.set(true); } if(v.getType().equals(HashedEntry.Type.FILE)) { LogHelper.error("Extra file %s", e); }
else LogHelper.error("Extra %s", e); else LogHelper.error("Extra %s", e);
return HashedDir.WalkAction.CONTINUE;
}); });
diff.mismatch.walk(File.separator, (e,k,v) -> { diff.mismatch.walk(File.separator, (e,k,v) -> {
if(v.getType().equals(HashedEntry.Type.FILE)) { LogHelper.error("Mismatch file %s", e); isFoundFile.set(true); } if(v.getType().equals(HashedEntry.Type.FILE)) { LogHelper.error("Mismatch file %s", e); }
else LogHelper.error("Mismatch %s", e); else LogHelper.error("Mismatch %s", e);
return HashedDir.WalkAction.CONTINUE;
}); });
if(isFoundFile.get())*/ }
throw new SecurityException(String.format("Forbidden modification: '%s'", IOHelper.getFileName(dir))); throw new SecurityException(String.format("Forbidden modification: '%s'", IOHelper.getFileName(dir)));
} }
} }
public static void checkJVMBitsAndVersion() { public static boolean checkJVMBitsAndVersion(int minVersion, int recommendVersion, int maxVersion, boolean showMessage) {
boolean ok = true;
if (JVMHelper.JVM_BITS != JVMHelper.OS_BITS) { if (JVMHelper.JVM_BITS != JVMHelper.OS_BITS) {
String error = String.format("У Вас установлена Java %d, но Ваша система определена как %d. Установите Java правильной разрядности", JVMHelper.JVM_BITS, JVMHelper.OS_BITS); String error = String.format("У Вас установлена Java %d, но Ваша система определена как %d. Установите Java правильной разрядности", JVMHelper.JVM_BITS, JVMHelper.OS_BITS);
LogHelper.error(error); LogHelper.error(error);
if (Launcher.getConfig().isWarningMissArchJava) if (showMessage)
JOptionPane.showMessageDialog(null, error); JOptionPane.showMessageDialog(null, error);
ok = false;
} }
String jvmVersion = JVMHelper.RUNTIME_MXBEAN.getVmVersion(); String jvmVersion = JVMHelper.RUNTIME_MXBEAN.getVmVersion();
LogHelper.info(jvmVersion); LogHelper.info(jvmVersion);
if (jvmVersion.startsWith("10.") || jvmVersion.startsWith("9.") || jvmVersion.startsWith("11.")) { int version = JVMHelper.getVersion();
String error = String.format("У Вас установлена Java %s. Для правильной работы необходима Java 8", JVMHelper.RUNTIME_MXBEAN.getVmVersion()); if (version < minVersion || version > maxVersion) {
String error = String.format("У Вас установлена Java %s. Для правильной работы необходима Java %d", JVMHelper.RUNTIME_MXBEAN.getVmVersion(), recommendVersion);
LogHelper.error(error); LogHelper.error(error);
if (Launcher.getConfig().isWarningMissArchJava) if (showMessage)
JOptionPane.showMessageDialog(null, error); JOptionPane.showMessageDialog(null, error);
ok = false;
} }
return ok;
} }
private static LinkedList<Path> resolveClassPathList(Path clientDir, String... classPath) throws IOException { private static LinkedList<Path> resolveClassPathList(Path clientDir, String... classPath) throws IOException {
@ -253,11 +279,21 @@ private static void launch(ClientProfile profile, ClientLauncherProcess.ClientPa
LogHelper.debug("Args: " + copy); LogHelper.debug("Args: " + copy);
// Resolve main class and method // Resolve main class and method
Class<?> mainClass = classLoader.loadClass(profile.getMainClass()); Class<?> mainClass = classLoader.loadClass(profile.getMainClass());
for (URL u : classLoader.getURLs()) { if(LogHelper.isDevEnabled() && classLoader instanceof URLClassLoader) {
LogHelper.info("ClassLoader URL: %s", u.toString()); for (URL u : ((URLClassLoader)classLoader).getURLs()) {
LogHelper.dev("ClassLoader URL: %s", u.toString());
}
} }
FMLPatcher.apply(); FMLPatcher.apply();
LauncherEngine.modulesManager.invokeEvent(new ClientProcessPreInvokeMainClassEvent(params, profile, args)); LauncherEngine.modulesManager.invokeEvent(new ClientProcessPreInvokeMainClassEvent(params, profile, args));
{
List<String> compatClasses = profile.getCompatClasses();
for(String e : compatClasses) {
Class<?> clazz = classLoader.loadClass(e);
MethodHandle runMethod = MethodHandles.publicLookup().findStatic(clazz, "run", MethodType.methodType(void.class));
runMethod.invoke();
}
}
MethodHandle mainMethod = MethodHandles.publicLookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)).asFixedArity(); MethodHandle mainMethod = MethodHandles.publicLookup().findStatic(mainClass, "main", MethodType.methodType(void.class, String[].class)).asFixedArity();
Launcher.LAUNCHED.set(true); Launcher.LAUNCHED.set(true);
JVMHelper.fullGC(); JVMHelper.fullGC();

View file

@ -117,6 +117,9 @@ public void start(boolean pipeOutput) throws IOException, InterruptedException {
processArgs.add(executeFile.toString()); processArgs.add(executeFile.toString());
processArgs.addAll(jvmArgs); processArgs.addAll(jvmArgs);
//ADD CLASSPATH //ADD CLASSPATH
if(params.profile.classLoaderConfig == ClientProfile.ClassLoaderConfig.AGENT) {
processArgs.add("-javaagent:".concat(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toAbsolutePath().toString()));
}
if (useLegacyJavaClassPathProperty) { if (useLegacyJavaClassPathProperty) {
processArgs.add("-Djava.class.path=".concat(String.join(getPathSeparator(), systemClassPath))); processArgs.add("-Djava.class.path=".concat(String.join(getPathSeparator(), systemClassPath)));
} else { } else {
@ -160,7 +163,7 @@ public void runWriteParams(SocketAddress address) throws IOException {
output.writeByteArray(serializedMainParams, 0); output.writeByteArray(serializedMainParams, 0);
params.clientHDir.write(output); params.clientHDir.write(output);
params.assetHDir.write(output); params.assetHDir.write(output);
if (params.javaHDir == null || params.javaHDir == params.assetHDir) { //TODO: OLD RUNTIME USE params.assetHDir AS NULL IN java.javaHDir if (params.javaHDir == null || params.javaHDir == params.assetHDir) { //OLD RUNTIME USE params.assetHDir AS NULL IN java.javaHDir
output.writeBoolean(false); output.writeBoolean(false);
} else { } else {
output.writeBoolean(true); output.writeBoolean(true);

View file

@ -30,7 +30,7 @@ public final class LauncherConfig extends StreamObject {
public final LauncherTrustManager trustManager; public final LauncherTrustManager trustManager;
public final ECPublicKey publicKey; public final ECPublicKey publicKey;
public final Map<String, byte[]> runtime; public final Map<String, byte[]> runtime;
@LauncherInject("launcher.isWarningMissArchJava") @Deprecated
public final boolean isWarningMissArchJava; public final boolean isWarningMissArchJava;
@LauncherInject("launcher.guardType") @LauncherInject("launcher.guardType")
public final String guardType; public final String guardType;

View file

@ -42,6 +42,8 @@ public final class ClientProfile implements Comparable<ClientProfile> {
public SecurityManagerConfig securityManagerConfig = SecurityManagerConfig.CLIENT; public SecurityManagerConfig securityManagerConfig = SecurityManagerConfig.CLIENT;
@LauncherNetworkAPI @LauncherNetworkAPI
public ClassLoaderConfig classLoaderConfig = ClassLoaderConfig.LAUNCHER; public ClassLoaderConfig classLoaderConfig = ClassLoaderConfig.LAUNCHER;
@LauncherNetworkAPI
public SignedClientConfig signedClientConfig = SignedClientConfig.NONE;
// Version // Version
@LauncherNetworkAPI @LauncherNetworkAPI
private String version; private String version;
@ -51,6 +53,14 @@ public final class ClientProfile implements Comparable<ClientProfile> {
private String dir; private String dir;
@LauncherNetworkAPI @LauncherNetworkAPI
private String assetDir; private String assetDir;
@LauncherNetworkAPI
private int recommendJavaVersion = 8;
@LauncherNetworkAPI
private int minJavaVersion = 8;
@LauncherNetworkAPI
private int maxJavaVersion = 17;
@LauncherNetworkAPI
private boolean warnMissJavaVersion = true;
// Client // Client
@LauncherNetworkAPI @LauncherNetworkAPI
private int sortIndex; private int sortIndex;
@ -72,6 +82,8 @@ public final class ClientProfile implements Comparable<ClientProfile> {
@LauncherNetworkAPI @LauncherNetworkAPI
private String mainClass; private String mainClass;
@LauncherNetworkAPI @LauncherNetworkAPI
private final List<String> compatClasses = new ArrayList<>();
@LauncherNetworkAPI
private final Map<String, String> properties = new HashMap<>(); private final Map<String, String> properties = new HashMap<>();
public static class ServerProfile { public static class ServerProfile {
@ -173,6 +185,38 @@ public Set<OptionalFile> getOptional() {
return updateOptional; return updateOptional;
} }
public int getRecommendJavaVersion() {
return recommendJavaVersion;
}
public void setRecommendJavaVersion(int recommendJavaVersion) {
this.recommendJavaVersion = recommendJavaVersion;
}
public int getMinJavaVersion() {
return minJavaVersion;
}
public void setMinJavaVersion(int minJavaVersion) {
this.minJavaVersion = minJavaVersion;
}
public int getMaxJavaVersion() {
return maxJavaVersion;
}
public void setMaxJavaVersion(int maxJavaVersion) {
this.maxJavaVersion = maxJavaVersion;
}
public boolean isWarnMissJavaVersion() {
return warnMissJavaVersion;
}
public void setWarnMissJavaVersion(boolean warnMissJavaVersion) {
this.warnMissJavaVersion = warnMissJavaVersion;
}
public void updateOptionalGraph() { public void updateOptionalGraph() {
for (OptionalFile file : updateOptional) { for (OptionalFile file : updateOptional) {
if (file.dependenciesFile != null) { if (file.dependenciesFile != null) {
@ -376,6 +420,9 @@ public void verify() {
for (String s : clientArgs) { for (String s : clientArgs) {
if (s == null) throw new IllegalArgumentException("Found null entry in clientArgs"); if (s == null) throw new IllegalArgumentException("Found null entry in clientArgs");
} }
for(String s : compatClasses) {
if (s == null) throw new IllegalArgumentException("Found null entry in compatClasses");
}
for (OptionalFile f : updateOptional) { for (OptionalFile f : updateOptional) {
if (f == null) throw new IllegalArgumentException("Found null entry in updateOptional"); if (f == null) throw new IllegalArgumentException("Found null entry in updateOptional");
if (f.name == null) throw new IllegalArgumentException("Optional: name must not be null"); if (f.name == null) throw new IllegalArgumentException("Optional: name must not be null");
@ -418,6 +465,10 @@ public Map<String, String> getProperties() {
return Collections.unmodifiableMap(properties); return Collections.unmodifiableMap(properties);
} }
public List<String> getCompatClasses() {
return Collections.unmodifiableList(compatClasses);
}
public enum Version { public enum Version {
MC125("1.2.5", 29), MC125("1.2.5", 29),
MC147("1.4.7", 51), MC147("1.4.7", 51),
@ -486,6 +537,9 @@ public enum ClassLoaderConfig {
AGENT, LAUNCHER AGENT, LAUNCHER
} }
public enum SignedClientConfig {
NONE, SIGNED
}
@FunctionalInterface @FunctionalInterface
public interface pushOptionalClassPathCallback { public interface pushOptionalClassPathCallback {

View file

@ -14,10 +14,18 @@
import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.netty.handler.codec.http.websocketx.WebSocketVersion;
import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.SslContextBuilder;
import pro.gravit.launcher.CertificatePinningTrustManager;
import pro.gravit.launcher.LauncherInject;
import pro.gravit.launcher.LauncherNetworkAPI;
import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.LogHelper;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import java.io.IOException;
import java.net.URI; import java.net.URI;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
public abstract class ClientJSONPoint { public abstract class ClientJSONPoint {
@ -29,6 +37,8 @@ public abstract class ClientJSONPoint {
protected WebSocketClientHandler webSocketClientHandler; protected WebSocketClientHandler webSocketClientHandler;
protected boolean ssl = false; protected boolean ssl = false;
protected int port; protected int port;
@LauncherInject("launcher.certificatePinning")
private static boolean isCertificatePinning;
public ClientJSONPoint(final String uri) throws SSLException { public ClientJSONPoint(final String uri) throws SSLException {
this(URI.create(uri)); this(URI.create(uri));
@ -49,7 +59,16 @@ public ClientJSONPoint(URI uri) throws SSLException {
} else port = uri.getPort(); } else port = uri.getPort();
final SslContext sslCtx; final SslContext sslCtx;
if (ssl) { if (ssl) {
sslCtx = SslContextBuilder.forClient().build(); SslContextBuilder sslContextBuilder = SslContextBuilder.forClient();
if(isCertificatePinning) {
try {
sslContextBuilder.trustManager(CertificatePinningTrustManager.getTrustManager());
} catch (KeyStoreException | NoSuchAlgorithmException | IOException | CertificateException e) {
LogHelper.error(e);
sslContextBuilder.trustManager();
}
}
sslCtx = sslContextBuilder.build();
} else sslCtx = null; } else sslCtx = null;
bootstrap.group(group) bootstrap.group(group)
.channel(NioSocketChannel.class) .channel(NioSocketChannel.class)

View file

@ -1,7 +1,11 @@
package pro.gravit.launcher; package pro.gravit.launcher;
import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.LogHelper;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import java.io.EOFException; import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -11,6 +15,11 @@
import java.net.URL; import java.net.URL;
import java.net.URLConnection; import java.net.URLConnection;
import java.nio.file.Path; import java.nio.file.Path;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.util.*; import java.util.*;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
@ -20,6 +29,8 @@ public class AsyncDownloader {
public static final Callback IGNORE = (ignored) -> { public static final Callback IGNORE = (ignored) -> {
}; };
public final Callback callback; public final Callback callback;
@LauncherInject("launcher.certificatePinning")
private static boolean isCertificatePinning;
public AsyncDownloader(Callback callback) { public AsyncDownloader(Callback callback) {
this.callback = callback; this.callback = callback;
@ -31,6 +42,14 @@ public AsyncDownloader() {
public void downloadFile(URL url, Path target, long size) throws IOException { public void downloadFile(URL url, Path target, long size) throws IOException {
URLConnection connection = url.openConnection(); URLConnection connection = url.openConnection();
if(isCertificatePinning) {
HttpsURLConnection connection1 = (HttpsURLConnection) connection;
try {
connection1.setSSLSocketFactory(makeSSLSocketFactory());
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException e) {
throw new IOException(e);
}
}
try (InputStream input = connection.getInputStream()) { try (InputStream input = connection.getInputStream()) {
transfer(input, target, size); transfer(input, target, size);
} }
@ -38,11 +57,25 @@ public void downloadFile(URL url, Path target, long size) throws IOException {
public void downloadFile(URL url, Path target) throws IOException { public void downloadFile(URL url, Path target) throws IOException {
URLConnection connection = url.openConnection(); URLConnection connection = url.openConnection();
if(isCertificatePinning) {
HttpsURLConnection connection1 = (HttpsURLConnection) connection;
try {
connection1.setSSLSocketFactory(makeSSLSocketFactory());
} catch (NoSuchAlgorithmException | CertificateException | KeyStoreException | KeyManagementException e) {
throw new IOException(e);
}
}
try (InputStream input = connection.getInputStream()) { try (InputStream input = connection.getInputStream()) {
IOHelper.transfer(input, target); IOHelper.transfer(input, target);
} }
} }
public SSLSocketFactory makeSSLSocketFactory() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, KeyManagementException {
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, CertificatePinningTrustManager.getTrustManager().getTrustManagers(), new SecureRandom());
return sslContext.getSocketFactory();
}
public void downloadListInOneThread(List<SizedFile> files, String baseURL, Path targetDir) throws URISyntaxException, IOException { public void downloadListInOneThread(List<SizedFile> files, String baseURL, Path targetDir) throws URISyntaxException, IOException {
URI baseUri = new URI(baseURL); URI baseUri = new URI(baseURL);
String scheme = baseUri.getScheme(); String scheme = baseUri.getScheme();

View file

@ -0,0 +1,62 @@
package pro.gravit.launcher;
import pro.gravit.utils.helper.LogHelper;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509TrustManager;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
public final class CertificatePinningTrustManager {
@LauncherInject("launchercore.certificates")
private static List<byte[]> secureConfigCertificates;
private static X509Certificate[] certs = null;
private static X509Certificate[] getInternalCertificates() {
CertificateFactory certFactory = null;
try {
certFactory = CertificateFactory.getInstance("X.509");
} catch (CertificateException e) {
return new X509Certificate[0];
}
CertificateFactory finalCertFactory = certFactory;
return secureConfigCertificates.stream().map((cert) -> {
try (InputStream input = new ByteArrayInputStream(cert)) {
return (X509Certificate) finalCertFactory.generateCertificate(input);
} catch (IOException | CertificateException e) {
LogHelper.error(e);
return null;
}
}).toArray(X509Certificate[]::new);
}
public static X509Certificate[] getCertificates() {
if(certs == null) certs = getInternalCertificates();
return Arrays.copyOf(certs, certs.length);
}
public static TrustManagerFactory getTrustManager() throws KeyStoreException, NoSuchAlgorithmException, IOException, CertificateException {
if(certs == null) certs = getInternalCertificates();
TrustManagerFactory factory = TrustManagerFactory.getInstance("X.509");
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
keystore.load(null, null);
int i = 1;
for (X509Certificate cert: certs) {
String alias = Integer.toString(i);
keystore.setCertificateEntry(alias, cert);
i++;
}
factory.init(keystore);
return factory;
}
}

View file

@ -6,7 +6,7 @@ public final class Version {
public static final int MAJOR = 5; public static final int MAJOR = 5;
public static final int MINOR = 1; public static final int MINOR = 1;
public static final int PATCH = 9; public static final int PATCH = 10;
public static final int BUILD = 1; public static final int BUILD = 1;
public static final Version.Type RELEASE = Type.DEV; public static final Version.Type RELEASE = Type.DEV;
public final int major; public final int major;