diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java index 8c49e94b..0c2079d9 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java @@ -72,7 +72,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/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/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/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/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/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/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 }