[FEATURE] Certificate verification refactoring

This commit is contained in:
Gravita 2021-01-09 21:06:35 +07:00
parent a4927dae4d
commit 5d39e168cd
8 changed files with 191 additions and 14 deletions

View file

@ -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) ? //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;

View file

@ -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);
}
} }

View file

@ -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,10 +27,24 @@ 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
public LauncherModule getCoreModule() { public LauncherModule getCoreModule() {
return coreModule; return coreModule;

View file

@ -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);
} }
} }

View file

@ -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) {

View file

@ -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.

View file

@ -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) {

View file

@ -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
} }