diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index fd5ef96f..a2a04688 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -1,9 +1,6 @@ name: push on: push: - create: - tags: - - v* jobs: launcher: name: Launcher @@ -55,7 +52,7 @@ jobs: - name: Create release id: create_release uses: actions/create-release@v1 - if: github.event_name == 'create' + if: github.event.ref == 'refs/tags/*' env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: @@ -65,13 +62,13 @@ jobs: prerelease: false - name: Pack release - if: github.event_name == 'create' + if: github.event.ref == 'refs/tags/*' run: | cd artifacts/ zip -r -9 ../Release.zip * - name: Upload release - if: github.event_name == 'create' + if: github.event.ref == 'refs/tags/*' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java index b74d8de7..724d8a31 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java @@ -293,6 +293,15 @@ public void invoke(String... args) throws Exception { } }; commands.put("reload", reload); + SubCommand save = new SubCommand() { + @Override + public void invoke(String... args) throws Exception { + launchServerConfigManager.writeConfig(config); + launchServerConfigManager.writeRuntimeConfig(runtime); + LogHelper.info("LaunchServerConfig saved"); + } + }; + commands.put("save", save); return commands; } @@ -319,7 +328,8 @@ public void buildLauncherBinaries() throws IOException { } public void close() throws Exception { - + LogHelper.info("Close server socket"); + nettyServerSocketHandler.close(); // Close handlers & providers config.close(ReloadType.FULL); modulesManager.invokeEvent(new ClosePhase()); @@ -368,8 +378,14 @@ public void run() { } if (config.netty != null) rebindNettyServerSocket(); - modulesManager.fullInitializedLaunchServer(this); - modulesManager.invokeEvent(new LaunchServerFullInitEvent(this)); + try { + modulesManager.fullInitializedLaunchServer(this); + modulesManager.invokeEvent(new LaunchServerFullInitEvent(this)); + LogHelper.info("LaunchServer started"); + } catch (Throwable e) { + LogHelper.error(e); + JVMHelper.RUNTIME.exit(-1); + } } public void syncLauncherBinaries() throws IOException { diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java index 8c49e94b..a8e858b4 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java @@ -61,7 +61,13 @@ public static void main(String[] args) throws Exception { Path privateKeyFile = dir.resolve("private.key"); ECPublicKey publicKey; ECPrivateKey privateKey; - Security.addProvider(new BouncyCastleProvider()); + try { + Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider"); + Security.addProvider(new BouncyCastleProvider()); + } catch (ClassNotFoundException ex) { + LogHelper.error("Library BouncyCastle not found! Is directory 'libraries' empty?"); + return; + } CertificateManager certificateManager = new CertificateManager(); try { certificateManager.readTrustStore(dir.resolve("truststore")); @@ -72,7 +78,19 @@ public static void main(String[] args) throws Exception { //LauncherTrustManager.CheckMode mode = (Version.RELEASE == Version.Type.LTS || Version.RELEASE == Version.Type.STABLE) ? // (allowUnsigned ? LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED : LauncherTrustManager.CheckMode.EXCEPTION_IN_NOT_SIGNED) : // (allowUnsigned ? LauncherTrustManager.CheckMode.NONE_IN_NOT_SIGNED : LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED); - certificateManager.checkClass(LaunchServer.class, LauncherTrustManager.CheckMode.NONE_IN_NOT_SIGNED); + LauncherTrustManager.CheckClassResult result = certificateManager.checkClass(LaunchServer.class); + if(result.type == LauncherTrustManager.CheckClassResultType.SUCCESS) { + LogHelper.info("LaunchServer signed by %s", result.endCertificate.getSubjectDN().getName()); + } + else if(result.type == LauncherTrustManager.CheckClassResultType.NOT_SIGNED) { + // None + } + else { + if(result.exception != null) { + LogHelper.error(result.exception); + } + LogHelper.warning("LaunchServer signed incorrectly. Status: %s", result.type.name()); + } } LaunchServerRuntimeConfig runtimeConfig; diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/session/MemorySessionStorage.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/session/MemorySessionStorage.java index 7676bdab..0dc45db7 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/session/MemorySessionStorage.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/session/MemorySessionStorage.java @@ -1,16 +1,37 @@ package pro.gravit.launchserver.auth.session; +import pro.gravit.launcher.Launcher; import pro.gravit.launcher.NeedGarbageCollection; +import pro.gravit.launchserver.LaunchServer; import pro.gravit.launchserver.manangers.SessionManager; +import pro.gravit.utils.helper.IOHelper; +import pro.gravit.utils.helper.LogHelper; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; -public class MemorySessionStorage extends SessionStorage implements NeedGarbageCollection { +public class MemorySessionStorage extends SessionStorage implements NeedGarbageCollection, AutoCloseable { - private final Map clientSet = new ConcurrentHashMap<>(128); - private final Map> uuidIndex = new ConcurrentHashMap<>(32); + private transient final Map clientSet = new ConcurrentHashMap<>(128); + private transient final Map> uuidIndex = new ConcurrentHashMap<>(32); + public boolean autoDump = false; + public String dumpFile = "sessions.json"; + + @Override + public void init(LaunchServer server) { + super.init(server); + if(autoDump) { + loadSessionsData(); + garbageCollection(); + } + } @Override public byte[] getSessionData(UUID session) { @@ -71,6 +92,28 @@ public void clear() { uuidIndex.clear(); } + public void dumpSessionsData() { + DumpedData dumpedData = new DumpedData(clientSet, uuidIndex); + Path path = Paths.get(dumpFile); + try(Writer writer = IOHelper.newWriter(path)) { + Launcher.gsonManager.gson.toJson(dumpedData, writer); + } catch (IOException e) { + LogHelper.error(e); + } + } + + public void loadSessionsData() { + Path path = Paths.get(dumpFile); + if(!Files.exists(path)) return; + try(Reader reader = IOHelper.newReader(path)) { + DumpedData data = Launcher.gsonManager.gson.fromJson(reader, DumpedData.class); + clientSet.putAll(data.clientSet); + uuidIndex.putAll(data.uuidIndex); + } catch (IOException e) { + LogHelper.error(e); + } + } + @Override public void lockSession(UUID sessionUUID) { @@ -114,6 +157,14 @@ public void garbageCollection() { to_delete.clear(); } + @Override + public void close() throws Exception { + if(autoDump) { + garbageCollection(); + dumpSessionsData(); + } + } + private static class Entry { public byte[] data; public UUID sessionUuid; @@ -125,4 +176,14 @@ public Entry(byte[] data, UUID sessionUuid) { this.timestamp = System.currentTimeMillis(); } } + + private static class DumpedData { + private final Map clientSet; + private final Map> uuidIndex; + + private DumpedData(Map clientSet, Map> uuidIndex) { + this.clientSet = clientSet; + this.uuidIndex = uuidIndex; + } + } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/tasks/MainBuildTask.java b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/tasks/MainBuildTask.java index d6f768e1..19c1775b 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/binary/tasks/MainBuildTask.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/binary/tasks/MainBuildTask.java @@ -107,8 +107,9 @@ protected void initProps() { properties.put("runtimeconfig.secretKeyClient", SecurityHelper.randomStringAESKey()); properties.put("launcher.port", 32148 + SecurityHelper.newRandom().nextInt(512)); 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("launcher.memory", server.config.launcher.memoryLimit); + properties.put("launcher.certificatePinning", server.config.launcher.certificatePinning); properties.put("runtimeconfig.passwordEncryptKey", server.runtime.passwordEncryptKey); String launcherSalt = SecurityHelper.randomStringToken(); byte[] launcherSecureHash = SecurityHelper.digest(SecurityHelper.DigestAlgorithm.SHA256, diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java b/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java index 170cc4c4..107153c5 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java @@ -93,7 +93,6 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) { newConfig.launcher = new LauncherConf(); newConfig.launcher.guardType = "no"; newConfig.launcher.compress = true; - newConfig.launcher.warningMissArchJava = true; newConfig.launcher.attachLibraryBeforeProGuard = false; newConfig.launcher.deleteTempFiles = true; newConfig.launcher.enabledProGuard = true; @@ -236,6 +235,13 @@ public void close(LaunchServer.ReloadType type) { } if(sessions != null) { server.unregisterObject("sessions", sessions); + if (sessions instanceof AutoCloseable) { + try { + ((AutoCloseable) sessions).close(); + } catch (Exception e) { + LogHelper.error(e); + } + } } if (dao != null) { server.unregisterObject("dao", dao); @@ -289,11 +295,14 @@ public static class LauncherConf { public String guardType; public boolean attachLibraryBeforeProGuard; public boolean compress; + @Deprecated public boolean warningMissArchJava; public boolean enabledProGuard; public boolean stripLineNumbers; public boolean deleteTempFiles; public boolean proguardGenMappings; + public boolean certificatePinning; + public int memoryLimit = 256; } public static class NettyConfig { diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/CertificateManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/CertificateManager.java index c20ef7ba..cf5409cc 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/CertificateManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/CertificateManager.java @@ -193,6 +193,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO trustManager = new LauncherTrustManager(certificates.toArray(new X509Certificate[0])); } + @Deprecated public void checkClass(Class clazz, LauncherTrustManager.CheckMode mode) throws SecurityException { if (trustManager == null) return; X509Certificate[] certificates = JVMHelper.getCertificates(clazz); @@ -204,9 +205,13 @@ else if (mode == LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED) return; } try { - trustManager.checkCertificate(certificates, trustManager::stdCertificateChecker); - } catch (CertificateException | NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { + trustManager.checkCertificatesSuccess(certificates, trustManager::stdCertificateChecker); + } catch (Exception e) { throw new SecurityException(e); } } + public LauncherTrustManager.CheckClassResult checkClass(Class clazz) { + X509Certificate[] certificates = JVMHelper.getCertificates(clazz); + return trustManager.checkCertificates(certificates, trustManager::stdCertificateChecker); + } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/FeaturesManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/FeaturesManager.java index da1d15c8..99201fbe 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/FeaturesManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/FeaturesManager.java @@ -14,7 +14,7 @@ public FeaturesManager(LaunchServer server) { this.server = server; map = new HashMap<>(); addFeatureInfo("version", Version.getVersion().getVersionString()); - + addFeatureInfo("projectName", server.config.projectName); } public Map getMap() { diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/SessionManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/SessionManager.java index 92c2a7d2..3dd5bf48 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/SessionManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/SessionManager.java @@ -96,17 +96,16 @@ public void removeClient(UUID session) { @Deprecated public void updateClient(UUID session) { - LogHelper.warning("Using deprecated method: sessionManager.updateClient"); + throw new UnsupportedOperationException(); } @Deprecated public Set getSessions() { - LogHelper.warning("Using deprecated method: sessionManager.getSession"); - return new HashSet<>(); + throw new UnsupportedOperationException(); } @Deprecated public void loadSessions(Set set) { - LogHelper.warning("Using deprecated method: sessionManager.loadSessions"); + throw new UnsupportedOperationException(); //clientSet.putAll(set.stream().collect(Collectors.toMap(c -> c.session, Function.identity()))); } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/modules/impl/LaunchServerModulesManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/modules/impl/LaunchServerModulesManager.java index 89433a37..04b6b241 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/modules/impl/LaunchServerModulesManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/modules/impl/LaunchServerModulesManager.java @@ -8,6 +8,7 @@ import pro.gravit.utils.helper.LogHelper; import java.nio.file.Path; +import java.security.cert.X509Certificate; import java.util.Arrays; public class LaunchServerModulesManager extends SimpleModuleManager { @@ -16,7 +17,6 @@ public class LaunchServerModulesManager extends SimpleModuleManager { public LaunchServerModulesManager(Path modulesDir, Path configDir, LauncherTrustManager trustManager) { super(modulesDir, configDir, trustManager); coreModule = new LaunchServerCoreModule(); - checkMode = LauncherTrustManager.CheckMode.NONE_IN_NOT_SIGNED; loadModule(coreModule); } @@ -27,10 +27,24 @@ public void fullInitializedLaunchServer(LaunchServer server) { public void printModulesInfo() { for (LauncherModule module : modules) { LauncherModuleInfo info = module.getModuleInfo(); - LogHelper.info("[MODULE] %s v: %s p: %d deps: %s", info.name, info.version.getVersionString(), info.priority, Arrays.toString(info.dependencies)); + LauncherTrustManager.CheckClassResult checkStatus = module.getCheckResult(); + LogHelper.info("[MODULE] %s v: %s p: %d deps: %s sig: %s", info.name, info.version.getVersionString(), info.priority, Arrays.toString(info.dependencies), checkStatus == null ? "null": checkStatus.type); + if(checkStatus != null && checkStatus.endCertificate != null) { + X509Certificate cert = checkStatus.endCertificate; + LogHelper.info("[MODULE CERT] Module signer: %s", cert.getSubjectDN().getName()); + } + if(checkStatus != null && checkStatus.rootCertificate != null) { + X509Certificate cert = checkStatus.rootCertificate; + LogHelper.info("[MODULE CERT] Module signer CA: %s", cert.getSubjectDN().getName()); + } } } + @Override + public final boolean verifyClassCheckResult(LauncherTrustManager.CheckClassResult result) { + return true; + } + @Override public LauncherModule getCoreModule() { return coreModule; diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java index 57113f14..5998aca9 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java @@ -32,6 +32,8 @@ public class Client { public transient Map properties; + public Map serializableProperties; + public transient AtomicInteger refCount = new AtomicInteger(1); public Client(UUID session) { @@ -55,6 +57,7 @@ public void updateAuth(LaunchServer server) { else auth = server.config.getAuthProviderPair(auth_id); } + @Deprecated public enum Type { SERVER, USER @@ -65,6 +68,9 @@ public static class TrustLevel { public boolean keyChecked; public byte[] publicKey; public HardwareReportRequest.HardwareInfo hardwareInfo; + // May be used later + public double rating; + public long latestMillis; } @SuppressWarnings("unchecked") @@ -77,4 +83,14 @@ public void setProperty(String name, T object) { if (properties == null) properties = new HashMap<>(); properties.put(name, object); } + + public String getSerializableProperty(String name) { + if (serializableProperties == null) serializableProperties = new HashMap<>(); + return serializableProperties.get(name); + } + + public void setSerializableProperty(String name, String value) { + if (serializableProperties == null) serializableProperties = new HashMap<>(); + properties.put(name, value); + } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/LauncherNettyServer.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/LauncherNettyServer.java index 6042d2d2..a68169df 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/LauncherNettyServer.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/LauncherNettyServer.java @@ -24,6 +24,7 @@ import pro.gravit.utils.helper.LogHelper; import java.net.InetSocketAddress; +import java.util.concurrent.TimeUnit; public class LauncherNettyServer implements AutoCloseable { private static final String WEBSOCKET_PATH = "/api"; @@ -77,7 +78,9 @@ public ChannelFuture bind(InetSocketAddress address) { @Override public void close() { - workerGroup.shutdownGracefully(); - bossGroup.shutdownGracefully(); + workerGroup.shutdownGracefully(2, 5, TimeUnit.SECONDS); + bossGroup.shutdownGracefully(2, 5, TimeUnit.SECONDS); + //workerGroup.shutdownGracefully(); + //bossGroup.shutdownGracefully(); } } diff --git a/Launcher/build.gradle b/Launcher/build.gradle index ca8c804a..9ecd01cc 100644 --- a/Launcher/build.gradle +++ b/Launcher/build.gradle @@ -26,11 +26,7 @@ classifier = 'clean' manifest.attributes("Main-Class": mainClassName, "Premain-Class": mainAgentName, - "Can-Redefine-Classes": "true", - "Multi-Release": "true", - "Can-Retransform-Classes": "true", - "Can-Set-Native-Method-Prefix": "true", - "Multi-Release-Jar": "true") + "Multi-Release": "true") } task sourcesJar(type: Jar) { diff --git a/Launcher/src/main/java/pro/gravit/launcher/ClientLauncherWrapper.java b/Launcher/src/main/java/pro/gravit/launcher/ClientLauncherWrapper.java index 94edcf82..317fab67 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/ClientLauncherWrapper.java +++ b/Launcher/src/main/java/pro/gravit/launcher/ClientLauncherWrapper.java @@ -22,6 +22,8 @@ public class ClientLauncherWrapper { public static final String NO_JAVA_CHECK_PROPERTY = "launcher.noJavaCheck"; public static boolean noJavaCheck = Boolean.getBoolean(NO_JAVA_CHECK_PROPERTY); public static boolean waitProcess = Boolean.getBoolean(WAIT_PROCESS_PROPERTY); + @LauncherInject("launcher.memory") + public static int launcherMemoryLimit; public static class JavaVersion { public final Path jvmDir; diff --git a/Launcher/src/main/java/pro/gravit/launcher/LauncherEngine.java b/Launcher/src/main/java/pro/gravit/launcher/LauncherEngine.java index 3d30f34d..bf67b5b6 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/LauncherEngine.java +++ b/Launcher/src/main/java/pro/gravit/launcher/LauncherEngine.java @@ -64,8 +64,8 @@ public static void checkClass(Class clazz) throws SecurityException { throw new SecurityException(String.format("Class %s not signed", clazz.getName())); } try { - trustManager.checkCertificate(certificates, trustManager::stdCertificateChecker); - } catch (CertificateException | NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { + trustManager.checkCertificatesSuccess(certificates, trustManager::stdCertificateChecker); + } catch (Exception e) { throw new SecurityException(e); } } diff --git a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java index 00001648..337f8453 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java +++ b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java @@ -10,6 +10,7 @@ import pro.gravit.launcher.guard.LauncherGuardManager; import pro.gravit.launcher.hasher.FileNameMatcher; import pro.gravit.launcher.hasher.HashedDir; +import pro.gravit.launcher.hasher.HashedEntry; import pro.gravit.launcher.managers.ClientGsonManager; import pro.gravit.launcher.modules.events.PreConfigPhase; import pro.gravit.launcher.patches.FMLPatcher; @@ -26,25 +27,24 @@ import pro.gravit.utils.helper.*; import javax.swing.*; +import java.io.File; import java.io.IOException; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; -import java.net.InetSocketAddress; -import java.net.Socket; -import java.net.SocketAddress; -import java.net.URL; +import java.net.*; import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.*; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.stream.Collectors; import java.util.stream.Stream; public class ClientLauncherEntryPoint { - private static ClientClassLoader classLoader; + private static ClassLoader classLoader; private static ClientLauncherProcess.ClientParams readParams(SocketAddress address) throws IOException { try (Socket socket = IOHelper.newSocket()) { @@ -77,18 +77,20 @@ public static void main(String[] args) throws Throwable { LauncherConfig.initModules(LauncherEngine.modulesManager); //INIT LauncherEngine.modulesManager.initModules(null); initGson(LauncherEngine.modulesManager); - LauncherEngine.verifyNoAgent(); LauncherEngine.modulesManager.invokeEvent(new PreConfigPhase()); engine.readKeys(); LauncherGuardManager.initGuard(true); LogHelper.debug("Reading ClientLauncher params"); 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; Launcher.profile = profile; AuthService.profile = profile; LauncherEngine.clientParams = params; 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)); Path clientDir = Paths.get(params.clientDir); @@ -103,10 +105,6 @@ public static void main(String[] args) throws Throwable { if (a instanceof OptionalActionClassPath) 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 boolean digest = !profile.isUpdateFastCheck(); LogHelper.debug("Restore sessions"); @@ -129,13 +127,34 @@ public static void main(String[] args) throws Throwable { LogHelper.error(e); } }; - AuthService.username = params.playerProfile.username; - AuthService.uuid = params.playerProfile.uuid; - ClientService.classLoader = classLoader; - ClientService.nativePath = classLoader.nativePath; - classLoader.addURL(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL()); - //classForName(classLoader, "com.google.common.collect.ForwardingMultimap"); - ClientService.baseURLs = classLoader.getURLs(); + 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.uuid = params.playerProfile.uuid; + ClientService.classLoader = classLoader; + ClientService.nativePath = classLoader.nativePath; + classLoader.addURL(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toUri().toURL()); + 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)); LogHelper.debug("Starting JVM and client WatchService"); 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.Diff diff = hdir.diff(currentHDir, matcher); if (!diff.isSame()) { - /*AtomicBoolean isFoundFile = new AtomicBoolean(false); - diff.extra.walk(File.separator, (e,k,v) -> { - if(v.getType().equals(HashedEntry.Type.FILE)) { LogHelper.error("Extra file %s", e); isFoundFile.set(true); } - else LogHelper.error("Extra %s", e); - }); - diff.mismatch.walk(File.separator, (e,k,v) -> { - if(v.getType().equals(HashedEntry.Type.FILE)) { LogHelper.error("Mismatch file %s", e); isFoundFile.set(true); } - else LogHelper.error("Mismatch %s", e); - }); - if(isFoundFile.get())*/ + if(LogHelper.isDebugEnabled()) { + diff.extra.walk(File.separator, (e, k, v) -> { + if(v.getType().equals(HashedEntry.Type.FILE)) { LogHelper.error("Extra file %s", e); } + else LogHelper.error("Extra %s", e); + return HashedDir.WalkAction.CONTINUE; + }); + diff.mismatch.walk(File.separator, (e,k,v) -> { + if(v.getType().equals(HashedEntry.Type.FILE)) { LogHelper.error("Mismatch file %s", e); } + else LogHelper.error("Mismatch %s", e); + return HashedDir.WalkAction.CONTINUE; + }); + } 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) { String error = String.format("У Вас установлена Java %d, но Ваша система определена как %d. Установите Java правильной разрядности", JVMHelper.JVM_BITS, JVMHelper.OS_BITS); LogHelper.error(error); - if (Launcher.getConfig().isWarningMissArchJava) + if (showMessage) JOptionPane.showMessageDialog(null, error); + ok = false; } String jvmVersion = JVMHelper.RUNTIME_MXBEAN.getVmVersion(); LogHelper.info(jvmVersion); - if (jvmVersion.startsWith("10.") || jvmVersion.startsWith("9.") || jvmVersion.startsWith("11.")) { - String error = String.format("У Вас установлена Java %s. Для правильной работы необходима Java 8", JVMHelper.RUNTIME_MXBEAN.getVmVersion()); + int version = JVMHelper.getVersion(); + if (version < minVersion || version > maxVersion) { + String error = String.format("У Вас установлена Java %s. Для правильной работы необходима Java %d", JVMHelper.RUNTIME_MXBEAN.getVmVersion(), recommendVersion); LogHelper.error(error); - if (Launcher.getConfig().isWarningMissArchJava) + if (showMessage) JOptionPane.showMessageDialog(null, error); + ok = false; } + return ok; } private static LinkedList resolveClassPathList(Path clientDir, String... classPath) throws IOException { @@ -253,11 +279,21 @@ private static void launch(ClientProfile profile, ClientLauncherProcess.ClientPa LogHelper.debug("Args: " + copy); // Resolve main class and method Class mainClass = classLoader.loadClass(profile.getMainClass()); - for (URL u : classLoader.getURLs()) { - LogHelper.info("ClassLoader URL: %s", u.toString()); + if(LogHelper.isDevEnabled() && classLoader instanceof URLClassLoader) { + for (URL u : ((URLClassLoader)classLoader).getURLs()) { + LogHelper.dev("ClassLoader URL: %s", u.toString()); + } } FMLPatcher.apply(); LauncherEngine.modulesManager.invokeEvent(new ClientProcessPreInvokeMainClassEvent(params, profile, args)); + { + List 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(); Launcher.LAUNCHED.set(true); JVMHelper.fullGC(); diff --git a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java index 8b05806a..cb064f92 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java +++ b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java @@ -117,6 +117,9 @@ public void start(boolean pipeOutput) throws IOException, InterruptedException { processArgs.add(executeFile.toString()); processArgs.addAll(jvmArgs); //ADD CLASSPATH + if(params.profile.classLoaderConfig == ClientProfile.ClassLoaderConfig.AGENT) { + processArgs.add("-javaagent:".concat(IOHelper.getCodeSource(ClientLauncherEntryPoint.class).toAbsolutePath().toString())); + } if (useLegacyJavaClassPathProperty) { processArgs.add("-Djava.class.path=".concat(String.join(getPathSeparator(), systemClassPath))); } else { @@ -160,7 +163,7 @@ public void runWriteParams(SocketAddress address) throws IOException { output.writeByteArray(serializedMainParams, 0); params.clientHDir.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); } else { output.writeBoolean(true); diff --git a/Launcher/src/main/java/pro/gravit/launcher/client/ClientModuleManager.java b/Launcher/src/main/java/pro/gravit/launcher/client/ClientModuleManager.java index 428fe7d1..882e3555 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/client/ClientModuleManager.java +++ b/Launcher/src/main/java/pro/gravit/launcher/client/ClientModuleManager.java @@ -11,7 +11,6 @@ public class ClientModuleManager extends SimpleModuleManager { public ClientModuleManager() { super(null, null, Launcher.getConfig().trustManager); - checkMode = LauncherTrustManager.CheckMode.EXCEPTION_IN_NOT_SIGNED; } @Override @@ -31,10 +30,14 @@ public LauncherModule loadModule(Path file) { @Override public LauncherModule loadModule(LauncherModule module) { - checkModuleClass(module.getClass(), LauncherTrustManager.CheckMode.EXCEPTION_IN_NOT_SIGNED); return super.loadModule(module); } + @Override + public final boolean verifyClassCheckResult(LauncherTrustManager.CheckClassResult result) { + return result.type == LauncherTrustManager.CheckClassResultType.SUCCESS; + } + public void callWrapper(ProcessBuilder processBuilder, Collection jvmArgs) { for (LauncherModule module : modules) { if (module instanceof ClientWrapperModule) { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/LauncherConfig.java b/LauncherAPI/src/main/java/pro/gravit/launcher/LauncherConfig.java index 3ad49fcb..77276ea4 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/LauncherConfig.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/LauncherConfig.java @@ -30,7 +30,7 @@ public final class LauncherConfig extends StreamObject { public final LauncherTrustManager trustManager; public final ECPublicKey publicKey; public final Map runtime; - @LauncherInject("launcher.isWarningMissArchJava") + @Deprecated public final boolean isWarningMissArchJava; @LauncherInject("launcher.guardType") public final String guardType; diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/modules/LauncherModule.java b/LauncherAPI/src/main/java/pro/gravit/launcher/modules/LauncherModule.java index 95ecf024..afe37722 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/modules/LauncherModule.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/modules/LauncherModule.java @@ -1,5 +1,7 @@ package pro.gravit.launcher.modules; +import pro.gravit.launcher.LauncherTrustManager; + import java.util.HashMap; import java.util.Map; @@ -11,6 +13,7 @@ public abstract class LauncherModule { protected ModulesConfigManager modulesConfigManager; protected InitStatus initStatus = InitStatus.CREATED; private LauncherModulesContext context; + private LauncherTrustManager.CheckClassResult checkResult; protected LauncherModule() { moduleInfo = new LauncherModuleInfo("UnknownModule"); @@ -39,13 +42,33 @@ public LauncherModule setInitStatus(InitStatus initStatus) { * * @param context Private context */ - public void setContext(LauncherModulesContext context) { + public final void setContext(LauncherModulesContext context) { if (this.context != null) throw new IllegalStateException("Module already set context"); this.context = context; this.modulesManager = context.getModulesManager(); this.modulesConfigManager = context.getModulesConfigManager(); this.setInitStatus(InitStatus.PRE_INIT_WAIT); } + /** + * The internal method used by the ModuleManager + * DO NOT TOUCH + * + * @param result Check result + */ + public final void setCheckResult(LauncherTrustManager.CheckClassResult result) { + if(this.checkResult != null) throw new IllegalStateException("Module already set check result"); + this.checkResult = result; + } + + public final LauncherTrustManager.CheckClassResultType getCheckStatus() { + if(this.checkResult == null) return null; + return this.checkResult.type; + } + + public final LauncherTrustManager.CheckClassResult getCheckResult() { + if(this.checkResult == null) return null; + return new LauncherTrustManager.CheckClassResult(this.checkResult); + } /** * This method is called before initializing all modules and resolving dependencies. diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/modules/impl/SimpleModuleManager.java b/LauncherAPI/src/main/java/pro/gravit/launcher/modules/impl/SimpleModuleManager.java index dbaaccd1..9e944b79 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/modules/impl/SimpleModuleManager.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/modules/impl/SimpleModuleManager.java @@ -40,6 +40,7 @@ public class SimpleModuleManager implements LauncherModulesManager { protected final LauncherTrustManager trustManager; protected final PublicURLClassLoader classLoader = new PublicURLClassLoader(new URL[]{}); protected LauncherInitContext initContext; + @Deprecated protected LauncherTrustManager.CheckMode checkMode = LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED; public SimpleModuleManager(Path modulesDir, Path configDir) { @@ -121,6 +122,11 @@ private boolean checkDepend(LauncherModule module) { @Override public LauncherModule loadModule(LauncherModule module) { if (modules.contains(module)) return module; + if(module.getCheckStatus() == null) { + LauncherTrustManager.CheckClassResult result = checkModuleClass(module.getClass()); + verifyClassCheckResult(result); + module.setCheckResult(result); + } modules.add(module); LauncherModuleInfo info = module.getModuleInfo(); moduleNames.add(info.name); @@ -145,12 +151,20 @@ public LauncherModule loadModule(Path file) throws IOException { classLoader.addURL(file.toUri().toURL()); @SuppressWarnings("unchecked") Class clazz = (Class) Class.forName(moduleClass, false, classLoader); - checkModuleClass(clazz, checkMode); + LauncherTrustManager.CheckClassResult result = checkModuleClass(clazz); + try { + verifyClassCheckResultExceptional(result); + } catch (Exception e) { + LogHelper.error(e); + LogHelper.error("In module %s signature check failed", file.toString()); + return null; + } if (!LauncherModule.class.isAssignableFrom(clazz)) throw new ClassNotFoundException("Invalid module class... Not contains LauncherModule in hierarchy."); LauncherModule module; try { module = (LauncherModule) MethodHandles.publicLookup().findConstructor(clazz, VOID_TYPE).invokeWithArguments(Collections.emptyList()); + module.setCheckResult(result); } catch (Throwable e) { throw (InstantiationException) new InstantiationException("Error on instancing...").initCause(e); } @@ -163,6 +177,7 @@ public LauncherModule loadModule(Path file) throws IOException { } } + @Deprecated public void checkModuleClass(Class clazz, LauncherTrustManager.CheckMode mode) throws SecurityException { if (trustManager == null) return; X509Certificate[] certificates = getCertificates(clazz); @@ -174,12 +189,29 @@ else if (mode == LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED) return; } try { - trustManager.checkCertificate(certificates, trustManager::stdCertificateChecker); - } catch (CertificateException | NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { + trustManager.checkCertificatesSuccess(certificates, trustManager::stdCertificateChecker); + } catch (Exception e) { throw new SecurityException(e); } } + public LauncherTrustManager.CheckClassResult checkModuleClass(Class clazz) { + if (trustManager == null) return null; + X509Certificate[] certificates = getCertificates(clazz); + return trustManager.checkCertificates(certificates,trustManager::stdCertificateChecker); + } + + public boolean verifyClassCheckResult(LauncherTrustManager.CheckClassResult result) { + if(result == null) return false; + return result.type == LauncherTrustManager.CheckClassResultType.SUCCESS; + } + + public void verifyClassCheckResultExceptional(LauncherTrustManager.CheckClassResult result) throws Exception { + if(verifyClassCheckResult(result)) return; + if(result.exception != null) throw result.exception; + throw new SecurityException(result.type.name()); + } + @Override public LauncherModule getModule(String name) { for (LauncherModule module : modules) { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java index 1c0f8e99..9338aae9 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java @@ -42,6 +42,8 @@ public final class ClientProfile implements Comparable { public SecurityManagerConfig securityManagerConfig = SecurityManagerConfig.CLIENT; @LauncherNetworkAPI public ClassLoaderConfig classLoaderConfig = ClassLoaderConfig.LAUNCHER; + @LauncherNetworkAPI + public SignedClientConfig signedClientConfig = SignedClientConfig.NONE; // Version @LauncherNetworkAPI private String version; @@ -51,6 +53,14 @@ public final class ClientProfile implements Comparable { private String dir; @LauncherNetworkAPI private String assetDir; + @LauncherNetworkAPI + private int recommendJavaVersion = 8; + @LauncherNetworkAPI + private int minJavaVersion = 8; + @LauncherNetworkAPI + private int maxJavaVersion = 17; + @LauncherNetworkAPI + private boolean warnMissJavaVersion = true; // Client @LauncherNetworkAPI private int sortIndex; @@ -71,6 +81,10 @@ public final class ClientProfile implements Comparable { // Client launcher @LauncherNetworkAPI private String mainClass; + @LauncherNetworkAPI + private final List compatClasses = new ArrayList<>(); + @LauncherNetworkAPI + private final Map properties = new HashMap<>(); public static class ServerProfile { public String name; @@ -171,6 +185,38 @@ public Set getOptional() { 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() { for (OptionalFile file : updateOptional) { if (file.dependenciesFile != null) { @@ -374,6 +420,9 @@ public void verify() { for (String s : 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) { if (f == null) throw new IllegalArgumentException("Found null entry in updateOptional"); if (f.name == null) throw new IllegalArgumentException("Optional: name must not be null"); @@ -396,6 +445,30 @@ public void verify() { } } + public String getProperty(String name) { + return properties.get(name); + } + + public void putProperty(String name, String value) { + properties.put(name, value); + } + + public boolean containsProperty(String name) { + return properties.containsKey(name); + } + + public void clearProperties() { + properties.clear(); + } + + public Map getProperties() { + return Collections.unmodifiableMap(properties); + } + + public List getCompatClasses() { + return Collections.unmodifiableList(compatClasses); + } + public enum Version { MC125("1.2.5", 29), MC147("1.4.7", 51), @@ -427,7 +500,8 @@ public enum Version { MC1161("1.16.1", 736), MC1162("1.16.2", 751), MC1163("1.16.3", 753), - MC1164("1.16.4", 754); + MC1164("1.16.4", 754), + MC1165("1.16.5", 754); private static final Map VERSIONS; static { @@ -463,6 +537,9 @@ public enum ClassLoaderConfig { AGENT, LAUNCHER } + public enum SignedClientConfig { + NONE, SIGNED + } @FunctionalInterface public interface pushOptionalClassPathCallback { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/OptionalFile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/OptionalFile.java index 11bfb3be..373032a8 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/OptionalFile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/optional/OptionalFile.java @@ -16,7 +16,7 @@ public class OptionalFile { @LauncherNetworkAPI - public final long permissions = 0L; + public long permissions = 0L; @LauncherNetworkAPI @Deprecated public String[] list; diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/request/websockets/ClientJSONPoint.java b/LauncherAPI/src/main/java/pro/gravit/launcher/request/websockets/ClientJSONPoint.java index 8d64bc64..5c4d4974 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/request/websockets/ClientJSONPoint.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/request/websockets/ClientJSONPoint.java @@ -14,10 +14,18 @@ import io.netty.handler.codec.http.websocketx.WebSocketVersion; import io.netty.handler.ssl.SslContext; 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 javax.net.ssl.SSLException; +import java.io.IOException; 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 { @@ -29,6 +37,8 @@ public abstract class ClientJSONPoint { protected WebSocketClientHandler webSocketClientHandler; protected boolean ssl = false; protected int port; + @LauncherInject("launcher.certificatePinning") + private static boolean isCertificatePinning; public ClientJSONPoint(final String uri) throws SSLException { this(URI.create(uri)); @@ -49,7 +59,16 @@ public ClientJSONPoint(URI uri) throws SSLException { } else port = uri.getPort(); final SslContext sslCtx; 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; bootstrap.group(group) .channel(NioSocketChannel.class) diff --git a/LauncherAuthlib/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilMinecraftSessionService.java b/LauncherAuthlib/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilMinecraftSessionService.java index 2d95d0ff..434626ed 100644 --- a/LauncherAuthlib/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilMinecraftSessionService.java +++ b/LauncherAuthlib/src/main/java/com/mojang/authlib/yggdrasil/YggdrasilMinecraftSessionService.java @@ -35,6 +35,11 @@ public YggdrasilMinecraftSessionService(AuthenticationService service) { LogHelper.debug("Patched MinecraftSessionService created"); } + public YggdrasilMinecraftSessionService(YggdrasilAuthenticationService service) { + super(service); + LogHelper.debug("Patched MinecraftSessionService created"); + } + public static void fillTextureProperties(GameProfile profile, PlayerProfile pp) { boolean debug = LogHelper.isDebugEnabled(); if (debug) { diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/AsyncDownloader.java b/LauncherCore/src/main/java/pro/gravit/launcher/AsyncDownloader.java index 6e38d59b..97689567 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/AsyncDownloader.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/AsyncDownloader.java @@ -1,7 +1,11 @@ package pro.gravit.launcher; 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.IOException; import java.io.InputStream; @@ -11,6 +15,11 @@ import java.net.URL; import java.net.URLConnection; 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.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -20,6 +29,9 @@ public class AsyncDownloader { public static final Callback IGNORE = (ignored) -> { }; public final Callback callback; + @LauncherInject("launcher.certificatePinning") + private static boolean isCertificatePinning; + private static volatile SSLSocketFactory sslSocketFactory; public AsyncDownloader(Callback callback) { this.callback = callback; @@ -31,6 +43,14 @@ public AsyncDownloader() { public void downloadFile(URL url, Path target, long size) throws IOException { 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()) { transfer(input, target, size); } @@ -38,11 +58,27 @@ public void downloadFile(URL url, Path target, long size) throws IOException { public void downloadFile(URL url, Path target) throws IOException { 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()) { IOHelper.transfer(input, target); } } + public SSLSocketFactory makeSSLSocketFactory() throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException, KeyManagementException { + if(sslSocketFactory != null) return sslSocketFactory; + SSLContext sslContext = SSLContext.getInstance("TLS"); + sslContext.init(null, CertificatePinningTrustManager.getTrustManager().getTrustManagers(), new SecureRandom()); + sslSocketFactory = sslContext.getSocketFactory(); + return sslSocketFactory; + } + public void downloadListInOneThread(List files, String baseURL, Path targetDir) throws URISyntaxException, IOException { URI baseUri = new URI(baseURL); String scheme = baseUri.getScheme(); diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/CertificatePinningTrustManager.java b/LauncherCore/src/main/java/pro/gravit/launcher/CertificatePinningTrustManager.java new file mode 100644 index 00000000..a8183fbc --- /dev/null +++ b/LauncherCore/src/main/java/pro/gravit/launcher/CertificatePinningTrustManager.java @@ -0,0 +1,65 @@ +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 secureConfigCertificates; + private static X509Certificate[] certs = null; + private volatile static TrustManagerFactory INSTANCE; + 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(INSTANCE != null) return INSTANCE; + 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); + INSTANCE = factory; + return factory; + } +} diff --git a/LauncherCore/src/main/java/pro/gravit/launcher/LauncherTrustManager.java b/LauncherCore/src/main/java/pro/gravit/launcher/LauncherTrustManager.java index 9af0ed90..6756e070 100644 --- a/LauncherCore/src/main/java/pro/gravit/launcher/LauncherTrustManager.java +++ b/LauncherCore/src/main/java/pro/gravit/launcher/LauncherTrustManager.java @@ -1,5 +1,6 @@ package pro.gravit.launcher; +import pro.gravit.utils.helper.JVMHelper; import pro.gravit.utils.helper.LogHelper; import java.io.ByteArrayInputStream; @@ -19,6 +20,92 @@ public class LauncherTrustManager { private final X509Certificate[] trustSigners; private final List trustCache = new ArrayList<>(); + public enum CheckClassResultType { + NOT_SIGNED, + SUCCESS, + UNTRUSTED, + UNVERIFED, + UNCOMPAT + } + public static class CheckClassResult { + public final CheckClassResultType type; + public final X509Certificate endCertificate; + public final X509Certificate rootCertificate; + public final Exception exception; + + public CheckClassResult(CheckClassResultType type, X509Certificate endCertificate, X509Certificate rootCertificate) { + this.type = type; + this.endCertificate = endCertificate; + this.rootCertificate = rootCertificate; + exception = null; + } + + public CheckClassResult(CheckClassResultType type, X509Certificate endCertificate, X509Certificate rootCertificate, Exception exception) { + this.type = type; + this.endCertificate = endCertificate; + this.rootCertificate = rootCertificate; + this.exception = exception; + } + + public CheckClassResult(CheckClassResult orig) { + this.type = orig.type; + this.exception = orig.exception; + this.rootCertificate = orig.rootCertificate; + this.endCertificate = orig.endCertificate; + } + } + public CheckClassResult checkCertificates( X509Certificate[] certs, CertificateChecker checker) { + if(certs == null) return new CheckClassResult(CheckClassResultType.NOT_SIGNED, null, null); + X509Certificate rootCert = certs[certs.length-1]; + X509Certificate endCert = certs[0]; + for (int i = 0; i < certs.length; ++i) { + X509Certificate cert = certs[i]; + if (trustCache.contains(cert)) { + //Добавляем в кеш все проверенные сертификаты + trustCache.addAll(Arrays.asList(certs).subList(0, i)); + return new CheckClassResult(CheckClassResultType.SUCCESS, endCert, rootCert); + } + X509Certificate signer = (i + 1 < certs.length) ? certs[i + 1] : null; + try { + cert.checkValidity(); + } catch (Exception e) { + return new CheckClassResult(CheckClassResultType.UNVERIFED, endCert, rootCert, e); + } + + if (signer != null) { + try { + cert.verify(signer.getPublicKey()); + } catch (Exception e) { + return new CheckClassResult(CheckClassResultType.UNVERIFED, endCert, rootCert, e); + } + } else { + try { + if(isTrusted(cert)) { + continue; + } else { + return new CheckClassResult(CheckClassResultType.UNTRUSTED, endCert, rootCert); + } + } catch (CertificateEncodingException e) { + return new CheckClassResult(CheckClassResultType.UNVERIFED, endCert, rootCert, e); + } + } + try { + checker.check(cert, signer, i); + } catch (Exception e) { + return new CheckClassResult(CheckClassResultType.UNCOMPAT, endCert, rootCert, e); + } + } + Collections.addAll(trustCache, certs); + return new CheckClassResult(CheckClassResultType.SUCCESS, endCert, rootCert); + } + + public void checkCertificatesSuccess( X509Certificate[] certs, CertificateChecker checker) throws Exception { + CheckClassResult result = checkCertificates(certs, checker); + if(result.type == CheckClassResultType.SUCCESS) return; + if(result.exception != null) throw result.exception; + throw new SecurityException(result.type.name()); + } + public LauncherTrustManager(X509Certificate[] trustSigners) { this.trustSigners = trustSigners; } @@ -35,6 +122,7 @@ public LauncherTrustManager(List encodedCertificate) throws CertificateE }).toArray(X509Certificate[]::new); } + @Deprecated public void checkCertificate(X509Certificate[] certs, CertificateChecker checker) throws CertificateException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, SignatureException { if (certs == null) throw new SecurityException("Object not signed"); for (int i = 0; i < certs.length; ++i) { @@ -105,7 +193,7 @@ public void stdCertificateChecker(X509Certificate cert, X509Certificate signer, else isCertificateCA(cert); } - + @Deprecated public enum CheckMode { EXCEPTION_IN_NOT_SIGNED, WARN_IN_NOT_SIGNED, NONE_IN_NOT_SIGNED } diff --git a/LauncherCore/src/main/java/pro/gravit/utils/Version.java b/LauncherCore/src/main/java/pro/gravit/utils/Version.java index f94eaf4f..1686b8bc 100644 --- a/LauncherCore/src/main/java/pro/gravit/utils/Version.java +++ b/LauncherCore/src/main/java/pro/gravit/utils/Version.java @@ -6,8 +6,8 @@ public final class Version { public static final int MAJOR = 5; public static final int MINOR = 1; - public static final int PATCH = 9; - public static final int BUILD = 1; + public static final int PATCH = 10; + public static final int BUILD = 2; public static final Version.Type RELEASE = Type.STABLE; public final int major; public final int minor; diff --git a/build.gradle b/build.gradle index 915ab739..b53c73e8 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ id 'org.openjfx.javafxplugin' version '0.0.8' apply false } group = 'pro.gravit.launcher' -version = '5.1.9' +version = '5.1.10' apply from: 'props.gradle' diff --git a/modules b/modules index 5c4f6850..4a271864 160000 --- a/modules +++ b/modules @@ -1 +1 @@ -Subproject commit 5c4f6850bd4feeee0caff5561564b7e54bb94774 +Subproject commit 4a2718644a38c263da18e303767901b018ed520e