mirror of
https://github.com/GravitLauncher/Launcher
synced 2024-12-22 16:41:46 +03:00
[FEATURE] Certificate verification refactoring
This commit is contained in:
parent
a4927dae4d
commit
5d39e168cd
8 changed files with 191 additions and 14 deletions
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String> jvmArgs) {
|
||||
for (LauncherModule module : modules) {
|
||||
if (module instanceof ClientWrapperModule) {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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<? 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))
|
||||
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<? extends LauncherModule> 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<? 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
|
||||
public LauncherModule getModule(String name) {
|
||||
for (LauncherModule module : modules) {
|
||||
|
|
|
@ -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<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) {
|
||||
this.trustSigners = trustSigners;
|
||||
}
|
||||
|
@ -35,6 +122,7 @@ public LauncherTrustManager(List<byte[]> 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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue