mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-11-15 11:39:11 +03:00
Merge branch 'release/5.1.10'
This commit is contained in:
commit
610593b0f0
31 changed files with 609 additions and 85 deletions
9
.github/workflows/push.yml
vendored
9
.github/workflows/push.yml
vendored
|
@ -1,9 +1,6 @@
|
||||||
name: push
|
name: push
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
create:
|
|
||||||
tags:
|
|
||||||
- v*
|
|
||||||
jobs:
|
jobs:
|
||||||
launcher:
|
launcher:
|
||||||
name: Launcher
|
name: Launcher
|
||||||
|
@ -55,7 +52,7 @@ jobs:
|
||||||
- name: Create release
|
- name: Create release
|
||||||
id: create_release
|
id: create_release
|
||||||
uses: actions/create-release@v1
|
uses: actions/create-release@v1
|
||||||
if: github.event_name == 'create'
|
if: github.event.ref == 'refs/tags/*'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
@ -65,13 +62,13 @@ jobs:
|
||||||
prerelease: false
|
prerelease: false
|
||||||
|
|
||||||
- name: Pack release
|
- name: Pack release
|
||||||
if: github.event_name == 'create'
|
if: github.event.ref == 'refs/tags/*'
|
||||||
run: |
|
run: |
|
||||||
cd artifacts/
|
cd artifacts/
|
||||||
zip -r -9 ../Release.zip *
|
zip -r -9 ../Release.zip *
|
||||||
|
|
||||||
- name: Upload release
|
- name: Upload release
|
||||||
if: github.event_name == 'create'
|
if: github.event.ref == 'refs/tags/*'
|
||||||
uses: actions/upload-release-asset@v1
|
uses: actions/upload-release-asset@v1
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
|
@ -293,6 +293,15 @@ public void invoke(String... args) throws Exception {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
commands.put("reload", reload);
|
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;
|
return commands;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -319,7 +328,8 @@ public void buildLauncherBinaries() throws IOException {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void close() throws Exception {
|
public void close() throws Exception {
|
||||||
|
LogHelper.info("Close server socket");
|
||||||
|
nettyServerSocketHandler.close();
|
||||||
// Close handlers & providers
|
// Close handlers & providers
|
||||||
config.close(ReloadType.FULL);
|
config.close(ReloadType.FULL);
|
||||||
modulesManager.invokeEvent(new ClosePhase());
|
modulesManager.invokeEvent(new ClosePhase());
|
||||||
|
@ -368,8 +378,14 @@ public void run() {
|
||||||
}
|
}
|
||||||
if (config.netty != null)
|
if (config.netty != null)
|
||||||
rebindNettyServerSocket();
|
rebindNettyServerSocket();
|
||||||
|
try {
|
||||||
modulesManager.fullInitializedLaunchServer(this);
|
modulesManager.fullInitializedLaunchServer(this);
|
||||||
modulesManager.invokeEvent(new LaunchServerFullInitEvent(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 {
|
public void syncLauncherBinaries() throws IOException {
|
||||||
|
|
|
@ -61,7 +61,13 @@ public static void main(String[] args) throws Exception {
|
||||||
Path privateKeyFile = dir.resolve("private.key");
|
Path privateKeyFile = dir.resolve("private.key");
|
||||||
ECPublicKey publicKey;
|
ECPublicKey publicKey;
|
||||||
ECPrivateKey privateKey;
|
ECPrivateKey privateKey;
|
||||||
|
try {
|
||||||
|
Class.forName("org.bouncycastle.jce.provider.BouncyCastleProvider");
|
||||||
Security.addProvider(new BouncyCastleProvider());
|
Security.addProvider(new BouncyCastleProvider());
|
||||||
|
} catch (ClassNotFoundException ex) {
|
||||||
|
LogHelper.error("Library BouncyCastle not found! Is directory 'libraries' empty?");
|
||||||
|
return;
|
||||||
|
}
|
||||||
CertificateManager certificateManager = new CertificateManager();
|
CertificateManager certificateManager = new CertificateManager();
|
||||||
try {
|
try {
|
||||||
certificateManager.readTrustStore(dir.resolve("truststore"));
|
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) ?
|
//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.WARN_IN_NOT_SIGNED : LauncherTrustManager.CheckMode.EXCEPTION_IN_NOT_SIGNED) :
|
||||||
// (allowUnsigned ? LauncherTrustManager.CheckMode.NONE_IN_NOT_SIGNED : LauncherTrustManager.CheckMode.WARN_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;
|
LaunchServerRuntimeConfig runtimeConfig;
|
||||||
|
|
|
@ -1,16 +1,37 @@
|
||||||
package pro.gravit.launchserver.auth.session;
|
package pro.gravit.launchserver.auth.session;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.Launcher;
|
||||||
import pro.gravit.launcher.NeedGarbageCollection;
|
import pro.gravit.launcher.NeedGarbageCollection;
|
||||||
|
import pro.gravit.launchserver.LaunchServer;
|
||||||
import pro.gravit.launchserver.manangers.SessionManager;
|
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.*;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
public class MemorySessionStorage extends SessionStorage implements NeedGarbageCollection {
|
public class MemorySessionStorage extends SessionStorage implements NeedGarbageCollection, AutoCloseable {
|
||||||
|
|
||||||
private final Map<UUID, Entry> clientSet = new ConcurrentHashMap<>(128);
|
private transient final Map<UUID, Entry> clientSet = new ConcurrentHashMap<>(128);
|
||||||
private final Map<UUID, Set<Entry>> uuidIndex = new ConcurrentHashMap<>(32);
|
private transient final Map<UUID, Set<Entry>> 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
|
@Override
|
||||||
public byte[] getSessionData(UUID session) {
|
public byte[] getSessionData(UUID session) {
|
||||||
|
@ -71,6 +92,28 @@ public void clear() {
|
||||||
uuidIndex.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
|
@Override
|
||||||
public void lockSession(UUID sessionUUID) {
|
public void lockSession(UUID sessionUUID) {
|
||||||
|
|
||||||
|
@ -114,6 +157,14 @@ public void garbageCollection() {
|
||||||
to_delete.clear();
|
to_delete.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws Exception {
|
||||||
|
if(autoDump) {
|
||||||
|
garbageCollection();
|
||||||
|
dumpSessionsData();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class Entry {
|
private static class Entry {
|
||||||
public byte[] data;
|
public byte[] data;
|
||||||
public UUID sessionUuid;
|
public UUID sessionUuid;
|
||||||
|
@ -125,4 +176,14 @@ public Entry(byte[] data, UUID sessionUuid) {
|
||||||
this.timestamp = System.currentTimeMillis();
|
this.timestamp = System.currentTimeMillis();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static class DumpedData {
|
||||||
|
private final Map<UUID, Entry> clientSet;
|
||||||
|
private final Map<UUID, Set<Entry>> uuidIndex;
|
||||||
|
|
||||||
|
private DumpedData(Map<UUID, Entry> clientSet, Map<UUID, Set<Entry>> uuidIndex) {
|
||||||
|
this.clientSet = clientSet;
|
||||||
|
this.uuidIndex = uuidIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -236,6 +235,13 @@ public void close(LaunchServer.ReloadType type) {
|
||||||
}
|
}
|
||||||
if(sessions != null) {
|
if(sessions != null) {
|
||||||
server.unregisterObject("sessions", sessions);
|
server.unregisterObject("sessions", sessions);
|
||||||
|
if (sessions instanceof AutoCloseable) {
|
||||||
|
try {
|
||||||
|
((AutoCloseable) sessions).close();
|
||||||
|
} catch (Exception e) {
|
||||||
|
LogHelper.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (dao != null) {
|
if (dao != null) {
|
||||||
server.unregisterObject("dao", dao);
|
server.unregisterObject("dao", dao);
|
||||||
|
@ -289,11 +295,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 {
|
||||||
|
|
|
@ -193,6 +193,7 @@ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IO
|
||||||
trustManager = new LauncherTrustManager(certificates.toArray(new X509Certificate[0]));
|
trustManager = new LauncherTrustManager(certificates.toArray(new X509Certificate[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void checkClass(Class<?> clazz, LauncherTrustManager.CheckMode mode) throws SecurityException {
|
public void checkClass(Class<?> clazz, LauncherTrustManager.CheckMode mode) throws SecurityException {
|
||||||
if (trustManager == null) return;
|
if (trustManager == null) return;
|
||||||
X509Certificate[] certificates = JVMHelper.getCertificates(clazz);
|
X509Certificate[] certificates = JVMHelper.getCertificates(clazz);
|
||||||
|
@ -204,9 +205,13 @@ else if (mode == LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
trustManager.checkCertificate(certificates, trustManager::stdCertificateChecker);
|
trustManager.checkCertificatesSuccess(certificates, trustManager::stdCertificateChecker);
|
||||||
} catch (CertificateException | NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
|
} catch (Exception e) {
|
||||||
throw new SecurityException(e);
|
throw new SecurityException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
public LauncherTrustManager.CheckClassResult checkClass(Class<?> clazz) {
|
||||||
|
X509Certificate[] certificates = JVMHelper.getCertificates(clazz);
|
||||||
|
return trustManager.checkCertificates(certificates, trustManager::stdCertificateChecker);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ public FeaturesManager(LaunchServer server) {
|
||||||
this.server = server;
|
this.server = server;
|
||||||
map = new HashMap<>();
|
map = new HashMap<>();
|
||||||
addFeatureInfo("version", Version.getVersion().getVersionString());
|
addFeatureInfo("version", Version.getVersion().getVersionString());
|
||||||
|
addFeatureInfo("projectName", server.config.projectName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Map<String, String> getMap() {
|
public Map<String, String> getMap() {
|
||||||
|
|
|
@ -96,17 +96,16 @@ public void removeClient(UUID session) {
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void updateClient(UUID session) {
|
public void updateClient(UUID session) {
|
||||||
LogHelper.warning("Using deprecated method: sessionManager.updateClient");
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Set<Client> getSessions() {
|
public Set<Client> getSessions() {
|
||||||
LogHelper.warning("Using deprecated method: sessionManager.getSession");
|
throw new UnsupportedOperationException();
|
||||||
return new HashSet<>();
|
|
||||||
}
|
}
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public void loadSessions(Set<Client> set) {
|
public void loadSessions(Set<Client> set) {
|
||||||
LogHelper.warning("Using deprecated method: sessionManager.loadSessions");
|
throw new UnsupportedOperationException();
|
||||||
//clientSet.putAll(set.stream().collect(Collectors.toMap(c -> c.session, Function.identity())));
|
//clientSet.putAll(set.stream().collect(Collectors.toMap(c -> c.session, Function.identity())));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.security.cert.X509Certificate;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class LaunchServerModulesManager extends SimpleModuleManager {
|
public class LaunchServerModulesManager extends SimpleModuleManager {
|
||||||
|
@ -16,7 +17,6 @@ public class LaunchServerModulesManager extends SimpleModuleManager {
|
||||||
public LaunchServerModulesManager(Path modulesDir, Path configDir, LauncherTrustManager trustManager) {
|
public LaunchServerModulesManager(Path modulesDir, Path configDir, LauncherTrustManager trustManager) {
|
||||||
super(modulesDir, configDir, trustManager);
|
super(modulesDir, configDir, trustManager);
|
||||||
coreModule = new LaunchServerCoreModule();
|
coreModule = new LaunchServerCoreModule();
|
||||||
checkMode = LauncherTrustManager.CheckMode.NONE_IN_NOT_SIGNED;
|
|
||||||
loadModule(coreModule);
|
loadModule(coreModule);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,22 @@ public void fullInitializedLaunchServer(LaunchServer server) {
|
||||||
public void printModulesInfo() {
|
public void printModulesInfo() {
|
||||||
for (LauncherModule module : modules) {
|
for (LauncherModule module : modules) {
|
||||||
LauncherModuleInfo info = module.getModuleInfo();
|
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
|
@Override
|
||||||
|
|
|
@ -32,6 +32,8 @@ public class Client {
|
||||||
|
|
||||||
public transient Map<String, Object> properties;
|
public transient Map<String, Object> properties;
|
||||||
|
|
||||||
|
public Map<String, String> serializableProperties;
|
||||||
|
|
||||||
public transient AtomicInteger refCount = new AtomicInteger(1);
|
public transient AtomicInteger refCount = new AtomicInteger(1);
|
||||||
|
|
||||||
public Client(UUID session) {
|
public Client(UUID session) {
|
||||||
|
@ -55,6 +57,7 @@ public void updateAuth(LaunchServer server) {
|
||||||
else auth = server.config.getAuthProviderPair(auth_id);
|
else auth = server.config.getAuthProviderPair(auth_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public enum Type {
|
public enum Type {
|
||||||
SERVER,
|
SERVER,
|
||||||
USER
|
USER
|
||||||
|
@ -65,6 +68,9 @@ public static class TrustLevel {
|
||||||
public boolean keyChecked;
|
public boolean keyChecked;
|
||||||
public byte[] publicKey;
|
public byte[] publicKey;
|
||||||
public HardwareReportRequest.HardwareInfo hardwareInfo;
|
public HardwareReportRequest.HardwareInfo hardwareInfo;
|
||||||
|
// May be used later
|
||||||
|
public double rating;
|
||||||
|
public long latestMillis;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
|
@ -77,4 +83,14 @@ public <T> void setProperty(String name, T object) {
|
||||||
if (properties == null) properties = new HashMap<>();
|
if (properties == null) properties = new HashMap<>();
|
||||||
properties.put(name, object);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,7 @@
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public class LauncherNettyServer implements AutoCloseable {
|
public class LauncherNettyServer implements AutoCloseable {
|
||||||
private static final String WEBSOCKET_PATH = "/api";
|
private static final String WEBSOCKET_PATH = "/api";
|
||||||
|
@ -77,7 +78,9 @@ public ChannelFuture bind(InetSocketAddress address) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void close() {
|
public void close() {
|
||||||
workerGroup.shutdownGracefully();
|
workerGroup.shutdownGracefully(2, 5, TimeUnit.SECONDS);
|
||||||
bossGroup.shutdownGracefully();
|
bossGroup.shutdownGracefully(2, 5, TimeUnit.SECONDS);
|
||||||
|
//workerGroup.shutdownGracefully();
|
||||||
|
//bossGroup.shutdownGracefully();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -64,8 +64,8 @@ public static void checkClass(Class<?> clazz) throws SecurityException {
|
||||||
throw new SecurityException(String.format("Class %s not signed", clazz.getName()));
|
throw new SecurityException(String.format("Class %s not signed", clazz.getName()));
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
trustManager.checkCertificate(certificates, trustManager::stdCertificateChecker);
|
trustManager.checkCertificatesSuccess(certificates, trustManager::stdCertificateChecker);
|
||||||
} catch (CertificateException | NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
|
} catch (Exception e) {
|
||||||
throw new SecurityException(e);
|
throw new SecurityException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -11,7 +11,6 @@
|
||||||
public class ClientModuleManager extends SimpleModuleManager {
|
public class ClientModuleManager extends SimpleModuleManager {
|
||||||
public ClientModuleManager() {
|
public ClientModuleManager() {
|
||||||
super(null, null, Launcher.getConfig().trustManager);
|
super(null, null, Launcher.getConfig().trustManager);
|
||||||
checkMode = LauncherTrustManager.CheckMode.EXCEPTION_IN_NOT_SIGNED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -31,10 +30,14 @@ public LauncherModule loadModule(Path file) {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LauncherModule loadModule(LauncherModule module) {
|
public LauncherModule loadModule(LauncherModule module) {
|
||||||
checkModuleClass(module.getClass(), LauncherTrustManager.CheckMode.EXCEPTION_IN_NOT_SIGNED);
|
|
||||||
return super.loadModule(module);
|
return super.loadModule(module);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean verifyClassCheckResult(LauncherTrustManager.CheckClassResult result) {
|
||||||
|
return result.type == LauncherTrustManager.CheckClassResultType.SUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
public void callWrapper(ProcessBuilder processBuilder, Collection<String> jvmArgs) {
|
public void callWrapper(ProcessBuilder processBuilder, Collection<String> jvmArgs) {
|
||||||
for (LauncherModule module : modules) {
|
for (LauncherModule module : modules) {
|
||||||
if (module instanceof ClientWrapperModule) {
|
if (module instanceof ClientWrapperModule) {
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package pro.gravit.launcher.modules;
|
package pro.gravit.launcher.modules;
|
||||||
|
|
||||||
|
import pro.gravit.launcher.LauncherTrustManager;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -11,6 +13,7 @@ public abstract class LauncherModule {
|
||||||
protected ModulesConfigManager modulesConfigManager;
|
protected ModulesConfigManager modulesConfigManager;
|
||||||
protected InitStatus initStatus = InitStatus.CREATED;
|
protected InitStatus initStatus = InitStatus.CREATED;
|
||||||
private LauncherModulesContext context;
|
private LauncherModulesContext context;
|
||||||
|
private LauncherTrustManager.CheckClassResult checkResult;
|
||||||
|
|
||||||
protected LauncherModule() {
|
protected LauncherModule() {
|
||||||
moduleInfo = new LauncherModuleInfo("UnknownModule");
|
moduleInfo = new LauncherModuleInfo("UnknownModule");
|
||||||
|
@ -39,13 +42,33 @@ public LauncherModule setInitStatus(InitStatus initStatus) {
|
||||||
*
|
*
|
||||||
* @param context Private context
|
* @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");
|
if (this.context != null) throw new IllegalStateException("Module already set context");
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.modulesManager = context.getModulesManager();
|
this.modulesManager = context.getModulesManager();
|
||||||
this.modulesConfigManager = context.getModulesConfigManager();
|
this.modulesConfigManager = context.getModulesConfigManager();
|
||||||
this.setInitStatus(InitStatus.PRE_INIT_WAIT);
|
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.
|
* This method is called before initializing all modules and resolving dependencies.
|
||||||
|
|
|
@ -40,6 +40,7 @@ public class SimpleModuleManager implements LauncherModulesManager {
|
||||||
protected final LauncherTrustManager trustManager;
|
protected final LauncherTrustManager trustManager;
|
||||||
protected final PublicURLClassLoader classLoader = new PublicURLClassLoader(new URL[]{});
|
protected final PublicURLClassLoader classLoader = new PublicURLClassLoader(new URL[]{});
|
||||||
protected LauncherInitContext initContext;
|
protected LauncherInitContext initContext;
|
||||||
|
@Deprecated
|
||||||
protected LauncherTrustManager.CheckMode checkMode = LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED;
|
protected LauncherTrustManager.CheckMode checkMode = LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED;
|
||||||
|
|
||||||
public SimpleModuleManager(Path modulesDir, Path configDir) {
|
public SimpleModuleManager(Path modulesDir, Path configDir) {
|
||||||
|
@ -121,6 +122,11 @@ private boolean checkDepend(LauncherModule module) {
|
||||||
@Override
|
@Override
|
||||||
public LauncherModule loadModule(LauncherModule module) {
|
public LauncherModule loadModule(LauncherModule module) {
|
||||||
if (modules.contains(module)) return 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);
|
modules.add(module);
|
||||||
LauncherModuleInfo info = module.getModuleInfo();
|
LauncherModuleInfo info = module.getModuleInfo();
|
||||||
moduleNames.add(info.name);
|
moduleNames.add(info.name);
|
||||||
|
@ -145,12 +151,20 @@ public LauncherModule loadModule(Path file) throws IOException {
|
||||||
classLoader.addURL(file.toUri().toURL());
|
classLoader.addURL(file.toUri().toURL());
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
Class<? extends LauncherModule> clazz = (Class<? extends LauncherModule>) Class.forName(moduleClass, false, classLoader);
|
Class<? extends LauncherModule> clazz = (Class<? extends LauncherModule>) 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))
|
if (!LauncherModule.class.isAssignableFrom(clazz))
|
||||||
throw new ClassNotFoundException("Invalid module class... Not contains LauncherModule in hierarchy.");
|
throw new ClassNotFoundException("Invalid module class... Not contains LauncherModule in hierarchy.");
|
||||||
LauncherModule module;
|
LauncherModule module;
|
||||||
try {
|
try {
|
||||||
module = (LauncherModule) MethodHandles.publicLookup().findConstructor(clazz, VOID_TYPE).invokeWithArguments(Collections.emptyList());
|
module = (LauncherModule) MethodHandles.publicLookup().findConstructor(clazz, VOID_TYPE).invokeWithArguments(Collections.emptyList());
|
||||||
|
module.setCheckResult(result);
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
throw (InstantiationException) new InstantiationException("Error on instancing...").initCause(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<? extends LauncherModule> clazz, LauncherTrustManager.CheckMode mode) throws SecurityException {
|
public void checkModuleClass(Class<? extends LauncherModule> clazz, LauncherTrustManager.CheckMode mode) throws SecurityException {
|
||||||
if (trustManager == null) return;
|
if (trustManager == null) return;
|
||||||
X509Certificate[] certificates = getCertificates(clazz);
|
X509Certificate[] certificates = getCertificates(clazz);
|
||||||
|
@ -174,12 +189,29 @@ else if (mode == LauncherTrustManager.CheckMode.WARN_IN_NOT_SIGNED)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
trustManager.checkCertificate(certificates, trustManager::stdCertificateChecker);
|
trustManager.checkCertificatesSuccess(certificates, trustManager::stdCertificateChecker);
|
||||||
} catch (CertificateException | NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
|
} catch (Exception e) {
|
||||||
throw new SecurityException(e);
|
throw new SecurityException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public LauncherTrustManager.CheckClassResult checkModuleClass(Class<? extends LauncherModule> 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
|
@Override
|
||||||
public LauncherModule getModule(String name) {
|
public LauncherModule getModule(String name) {
|
||||||
for (LauncherModule module : modules) {
|
for (LauncherModule module : modules) {
|
||||||
|
|
|
@ -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;
|
||||||
|
@ -71,6 +81,10 @@ public final class ClientProfile implements Comparable<ClientProfile> {
|
||||||
// Client launcher
|
// Client launcher
|
||||||
@LauncherNetworkAPI
|
@LauncherNetworkAPI
|
||||||
private String mainClass;
|
private String mainClass;
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
private final List<String> compatClasses = new ArrayList<>();
|
||||||
|
@LauncherNetworkAPI
|
||||||
|
private final Map<String, String> properties = new HashMap<>();
|
||||||
|
|
||||||
public static class ServerProfile {
|
public static class ServerProfile {
|
||||||
public String name;
|
public String name;
|
||||||
|
@ -171,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) {
|
||||||
|
@ -374,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");
|
||||||
|
@ -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<String, String> getProperties() {
|
||||||
|
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),
|
||||||
|
@ -427,7 +500,8 @@ public enum Version {
|
||||||
MC1161("1.16.1", 736),
|
MC1161("1.16.1", 736),
|
||||||
MC1162("1.16.2", 751),
|
MC1162("1.16.2", 751),
|
||||||
MC1163("1.16.3", 753),
|
MC1163("1.16.3", 753),
|
||||||
MC1164("1.16.4", 754);
|
MC1164("1.16.4", 754),
|
||||||
|
MC1165("1.16.5", 754);
|
||||||
private static final Map<String, Version> VERSIONS;
|
private static final Map<String, Version> VERSIONS;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
|
@ -463,6 +537,9 @@ public enum ClassLoaderConfig {
|
||||||
AGENT, LAUNCHER
|
AGENT, LAUNCHER
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public enum SignedClientConfig {
|
||||||
|
NONE, SIGNED
|
||||||
|
}
|
||||||
|
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface pushOptionalClassPathCallback {
|
public interface pushOptionalClassPathCallback {
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
public class OptionalFile {
|
public class OptionalFile {
|
||||||
@LauncherNetworkAPI
|
@LauncherNetworkAPI
|
||||||
public final long permissions = 0L;
|
public long permissions = 0L;
|
||||||
@LauncherNetworkAPI
|
@LauncherNetworkAPI
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public String[] list;
|
public String[] list;
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -35,6 +35,11 @@ public YggdrasilMinecraftSessionService(AuthenticationService service) {
|
||||||
LogHelper.debug("Patched MinecraftSessionService created");
|
LogHelper.debug("Patched MinecraftSessionService created");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public YggdrasilMinecraftSessionService(YggdrasilAuthenticationService service) {
|
||||||
|
super(service);
|
||||||
|
LogHelper.debug("Patched MinecraftSessionService created");
|
||||||
|
}
|
||||||
|
|
||||||
public static void fillTextureProperties(GameProfile profile, PlayerProfile pp) {
|
public static void fillTextureProperties(GameProfile profile, PlayerProfile pp) {
|
||||||
boolean debug = LogHelper.isDebugEnabled();
|
boolean debug = LogHelper.isDebugEnabled();
|
||||||
if (debug) {
|
if (debug) {
|
||||||
|
|
|
@ -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,9 @@ 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;
|
||||||
|
private static volatile SSLSocketFactory sslSocketFactory;
|
||||||
|
|
||||||
public AsyncDownloader(Callback callback) {
|
public AsyncDownloader(Callback callback) {
|
||||||
this.callback = callback;
|
this.callback = callback;
|
||||||
|
@ -31,6 +43,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 +58,27 @@ 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 {
|
||||||
|
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<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();
|
||||||
|
|
|
@ -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<byte[]> 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,6 @@
|
||||||
package pro.gravit.launcher;
|
package pro.gravit.launcher;
|
||||||
|
|
||||||
|
import pro.gravit.utils.helper.JVMHelper;
|
||||||
import pro.gravit.utils.helper.LogHelper;
|
import pro.gravit.utils.helper.LogHelper;
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
|
@ -19,6 +20,92 @@ public class LauncherTrustManager {
|
||||||
private final X509Certificate[] trustSigners;
|
private final X509Certificate[] trustSigners;
|
||||||
private final List<X509Certificate> trustCache = new ArrayList<>();
|
private final List<X509Certificate> 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) {
|
public LauncherTrustManager(X509Certificate[] trustSigners) {
|
||||||
this.trustSigners = trustSigners;
|
this.trustSigners = trustSigners;
|
||||||
}
|
}
|
||||||
|
@ -35,6 +122,7 @@ public LauncherTrustManager(List<byte[]> encodedCertificate) throws CertificateE
|
||||||
}).toArray(X509Certificate[]::new);
|
}).toArray(X509Certificate[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Deprecated
|
||||||
public void checkCertificate(X509Certificate[] certs, CertificateChecker checker) throws CertificateException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
public void checkCertificate(X509Certificate[] certs, CertificateChecker checker) throws CertificateException, NoSuchProviderException, NoSuchAlgorithmException, InvalidKeyException, SignatureException {
|
||||||
if (certs == null) throw new SecurityException("Object not signed");
|
if (certs == null) throw new SecurityException("Object not signed");
|
||||||
for (int i = 0; i < certs.length; ++i) {
|
for (int i = 0; i < certs.length; ++i) {
|
||||||
|
@ -105,7 +193,7 @@ public void stdCertificateChecker(X509Certificate cert, X509Certificate signer,
|
||||||
else
|
else
|
||||||
isCertificateCA(cert);
|
isCertificateCA(cert);
|
||||||
}
|
}
|
||||||
|
@Deprecated
|
||||||
public enum CheckMode {
|
public enum CheckMode {
|
||||||
EXCEPTION_IN_NOT_SIGNED, WARN_IN_NOT_SIGNED, NONE_IN_NOT_SIGNED
|
EXCEPTION_IN_NOT_SIGNED, WARN_IN_NOT_SIGNED, NONE_IN_NOT_SIGNED
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,8 @@ 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 = 2;
|
||||||
public static final Version.Type RELEASE = Type.STABLE;
|
public static final Version.Type RELEASE = Type.STABLE;
|
||||||
public final int major;
|
public final int major;
|
||||||
public final int minor;
|
public final int minor;
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
id 'org.openjfx.javafxplugin' version '0.0.8' apply false
|
id 'org.openjfx.javafxplugin' version '0.0.8' apply false
|
||||||
}
|
}
|
||||||
group = 'pro.gravit.launcher'
|
group = 'pro.gravit.launcher'
|
||||||
version = '5.1.9'
|
version = '5.1.10'
|
||||||
|
|
||||||
apply from: 'props.gradle'
|
apply from: 'props.gradle'
|
||||||
|
|
||||||
|
|
2
modules
2
modules
|
@ -1 +1 @@
|
||||||
Subproject commit 5c4f6850bd4feeee0caff5561564b7e54bb94774
|
Subproject commit 4a2718644a38c263da18e303767901b018ed520e
|
Loading…
Reference in a new issue