[FEATURE] LauncherTrustManager и базовые сертификаты

This commit is contained in:
Gravit 2019-10-17 20:22:24 +07:00
parent 8a52340b1a
commit 5a2aedbe06
No known key found for this signature in database
GPG key ID: 061981E1E85D3216
10 changed files with 233 additions and 18 deletions

View file

@ -13,6 +13,7 @@
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
@ -274,14 +275,16 @@ public static class LaunchServerDirectories
public Path updatesDir;
public Path profilesDir;
public Path dir;
public Path trustStore;
public void collect()
{
if(updatesDir == null) updatesDir = dir.resolve("updates");
if(profilesDir == null) profilesDir = dir.resolve("profiles");
if(trustStore == null) trustStore = dir.resolve("truststore");
}
}
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, ECPublicKey publicKey, ECPrivateKey privateKey, CommandHandler commandHandler) throws IOException, InvalidKeySpecException {
public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, LaunchServerConfig config, LaunchServerRuntimeConfig runtimeConfig, LaunchServerConfigManager launchServerConfigManager, LaunchServerModulesManager modulesManager, ECPublicKey publicKey, ECPrivateKey privateKey, CommandHandler commandHandler, CertificateManager certificateManager) throws IOException {
this.dir = directories.dir;
this.env = env;
this.config = config;
@ -293,6 +296,7 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
this.privateKey = privateKey;
this.commandHandler = commandHandler;
this.runtime = runtimeConfig;
this.certificateManager = certificateManager;
taskPool = new Timer("Timered task worker thread", true);
launcherLibraries = dir.resolve("launcher-libraries");
launcherLibrariesCompile = dir.resolve("launcher-libraries-compile");
@ -331,7 +335,6 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La
reconfigurableManager = new ReconfigurableManager();
authHookManager = new AuthHookManager();
configManager = new ConfigManager();
certificateManager = new CertificateManager();
//Generate or set new Certificate API
certificateManager.orgName = config.projectName;
if(config.certificate != null && config.certificate.enabled)

View file

@ -8,6 +8,7 @@
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.utils.command.CommandHandler;
@ -20,6 +21,7 @@ public class LaunchServerBuilder {
private LaunchServer.LaunchServerDirectories directories = new LaunchServer.LaunchServerDirectories();
private ECPublicKey publicKey;
private ECPrivateKey privateKey;
private CertificateManager certificateManager;
private LaunchServer.LaunchServerConfigManager launchServerConfigManager;
public LaunchServerBuilder setConfig(LaunchServerConfig config) {
@ -101,6 +103,11 @@ public void writeRuntimeConfig(LaunchServerRuntimeConfig config) {
}
};
}
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, publicKey, privateKey, commandHandler);
return new LaunchServer(directories, env, config, runtimeConfig, launchServerConfigManager, modulesManager, publicKey, privateKey, commandHandler, certificateManager);
}
public LaunchServerBuilder setCertificateManager(CertificateManager certificateManager) {
this.certificateManager = certificateManager;
return this;
}
}

View file

@ -9,6 +9,7 @@
import java.security.KeyPair;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.CertificateException;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
@ -27,6 +28,7 @@
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.dao.provider.DaoProvider;
import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.launchserver.socket.WebSocketService;
@ -37,12 +39,12 @@
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.helper.SecurityHelper;
import pro.gravit.utils.verify.LauncherTrustManager;
import javax.crypto.Cipher;
public class LaunchServerStarter {
public static void main(String[] args) throws Exception {
Security.addProvider(new BouncyCastleProvider());
JVMHelper.checkStackTrace(LaunchServerStarter.class);
JVMHelper.verifySystemProperties(LaunchServer.class, true);
LogHelper.addOutput(IOHelper.WORKING_DIR.resolve("LaunchServer.log"));
@ -52,18 +54,24 @@ public static void main(String[] args) throws Exception {
LogHelper.error("StarterAgent is not started!");
LogHelper.error("You should add to JVM options this option: `-javaagent:LaunchServer.jar`");
}
Path dir = IOHelper.WORKING_DIR;
Path configFile, runtimeConfigFile;
Path publicKeyFile =dir.resolve("public.key");
Path privateKeyFile = dir.resolve("private.key");
ECPublicKey publicKey;
ECPrivateKey privateKey;
Security.addProvider(new BouncyCastleProvider());
CertificateManager certificateManager = new CertificateManager();
try {
certificateManager.readTrustStore(dir.resolve("truststore"));
} catch (CertificateException e) {
throw new IOException(e);
}
LaunchServerRuntimeConfig runtimeConfig;
LaunchServerConfig config;
LaunchServer.LaunchServerEnv env = LaunchServer.LaunchServerEnv.PRODUCTION;
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(dir.resolve("modules"), dir.resolve("config"));
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(dir.resolve("modules"), dir.resolve("config"), certificateManager.trustManager);
modulesManager.autoload();
modulesManager.initModules(null);
registerAll();
@ -173,6 +181,7 @@ public void writeRuntimeConfig(LaunchServerRuntimeConfig config) throws IOExcept
.setConfig(config)
.setModulesManager(modulesManager)
.setLaunchServerConfigManager(launchServerConfigManager)
.setCertificateManager(certificateManager)
.build();
server.run();
}

View file

@ -1,25 +1,31 @@
package pro.gravit.launchserver.manangers;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.io.*;
import java.math.BigInteger;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.InvalidAlgorithmParameterException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.security.spec.ECGenParameterSpec;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x500.X500Name;
@ -43,6 +49,7 @@
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.SecurityHelper;
import pro.gravit.utils.verify.LauncherTrustManager;
public class CertificateManager {
public X509CertificateHolder ca;
@ -51,9 +58,7 @@ public class CertificateManager {
public X509CertificateHolder server;
public AsymmetricKeyParameter serverKey;
//public X509CertificateHolder server;
//public AsymmetricKeyParameter serverKey;
public LauncherTrustManager trustManager;
public int validDays = 60;
public int minusHours = 6;
@ -172,4 +177,34 @@ public X509CertificateHolder readCertificate(Reader reader) throws IOException {
}
return ret;
}
public void readTrustStore(Path dir) throws IOException, CertificateException {
if(!IOHelper.isDir(dir))
{
Files.createDirectories(dir);
try(OutputStream outputStream = IOHelper.newOutput(dir.resolve("GravitCentralRootCA.crt"));
InputStream inputStream = IOHelper.newInput(IOHelper.getResourceURL("pro/gravit/launchserver/defaults/GravitCentralRootCA.crt")))
{
IOHelper.transfer(inputStream, outputStream);
}
}
List<X509Certificate> certificates = new ArrayList<>();
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
IOHelper.walk(dir, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if(file.toFile().getName().endsWith(".crt"))
{
try(InputStream inputStream = IOHelper.newInput(file))
{
certificates.add((X509Certificate) certFactory.generateCertificate(inputStream));
} catch (CertificateException e) {
throw new IOException(e);
}
}
return super.visitFile(file, attrs);
}
}, false);
trustManager = new LauncherTrustManager(certificates.toArray(new X509Certificate[0]));
}
}

View file

@ -7,12 +7,14 @@
import pro.gravit.launcher.modules.LauncherModuleInfo;
import pro.gravit.launcher.modules.impl.SimpleModuleManager;
import pro.gravit.launchserver.LaunchServer;
import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.verify.LauncherTrustManager;
public class LaunchServerModulesManager extends SimpleModuleManager {
public LaunchServerCoreModule coreModule;
public LaunchServerModulesManager(Path modulesDir, Path configDir) {
super(modulesDir, configDir);
public LaunchServerModulesManager(Path modulesDir, Path configDir, LauncherTrustManager trustManager) {
super(modulesDir, configDir, trustManager);
coreModule = new LaunchServerCoreModule();
modules.add(coreModule);
}

View file

@ -0,0 +1,33 @@
-----BEGIN CERTIFICATE-----
MIIFyjCCA7KgAwIBAgIRALnsjNjfvOTXfla3fX1fNEUwDQYJKoZIhvcNAQELBQAw
WTELMAkGA1UEBhMCUlUxFzAVBgNVBAoTDkdyYXZpdFRydXN0IENBMRAwDgYDVQQL
EwdSb290IENBMR8wHQYDVQQDExZHcmF2aXQgQ2VudHJhbCBSb290IENBMCAXDTE5
MDYwOTAyNDIwMFoYDzIwNTEwNjA5MDI0MjAwWjBZMQswCQYDVQQGEwJSVTEXMBUG
A1UEChMOR3Jhdml0VHJ1c3QgQ0ExEDAOBgNVBAsTB1Jvb3QgQ0ExHzAdBgNVBAMT
FkdyYXZpdCBDZW50cmFsIFJvb3QgQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAw
ggIKAoICAQDA3Qm9OH8Xz3YM3bKkZuQI7T/aL3ulMOdY5GFADYgHrOVZXVSJi/4P
PruBsut4WXN6TGQdpJtNZ2kyWTYzENGTm/TMzBcIchor1M3JW5Uv/C0r5gSEU1uP
DPe7oEpeKtb3FXML/pGoGpLv/sonTKky4AKZnK7B15bZ+oVZNwh7UKANpNrVA8k5
0gb4BisFcegLidYL9Y00H1x5WzUxldQAA1IQuwdkL3NP0NPQrSVJ2Ka2EtebE2HP
fXHtbftvvnvSWyh4CXAxTfEmJgut0gSPQPm9wVt6pIWWd4O0hHwVmxkKQidgnP6A
+d05FnJGsBw0ztMCifIteqNiHF0D8E0GuSz6NtcuV47J3p43qkvKr2vPc8o6WMN8
PAb0eVHc/AX8qqOwYQyHlj4M0SDhCltHeeYRWmuZmRFIIelv6VAocaQLlPQrhJNp
feIzmXLy60a+84vpe/eQKQx+D8a1elarQkoHMxI7x/9AJvxcnJ4KuXc2rkiu3Zv9
KMhixtkLc+pA6jY023U211v+c20RjTqwKIZoMFc7BZipoinAOn1bdsTzXlhOMv1O
zj5WoW6DsQQONMZNyLQAkaX6SYZE/kQVJ9YMPhNdaXjxxzfrY05IrWAaWhtPbW8z
5nb4/JyO+bJq3v2rav9p03s8P/lQ4k/0af5vOkGkEO0+YKx97ZP8FQIDAQABo4GK
MIGHMA8GA1UdEwEB/wQFMAMBAf8wHQYDVR0OBBYEFFjMGCvHXAE/vGJih+Lfdo2s
YnzsMAsGA1UdDwQEAwIBBjA1BgNVHR8ELjAsMCqgKKAmhiRodHRwOi8vY2EuZ3Jh
dml0LnByby9jZW50cmFscm9vdC5jcmwwEQYJYIZIAYb4QgEBBAQDAgAHMA0GCSqG
SIb3DQEBCwUAA4ICAQAexCGpThx85skEllva1UskmdlRh3rud9u59AUiwNZF0b0I
+7eeyLNaLHarg2Zm30TSCF53ksyPTE5QNdmozs1fl3MddFqunkbUm4G6hwedZMSi
4IXIb2QK3z3gZG5ZNdHaDG2u00Jdkc39h3jQFp1rpn4+0DcnYJAe+lw5G+XHURY2
j15wcmUFp/Ywgw3pfCWmH5+rxq21e/LG8JiQrxekkFI2GUD+Qw7+Hq3o1Fgg3kfh
Lg4B5WEbEICQ1FC+dHYHasEI3q3c96Qpqu2k3pO0l1fr6Cys+AGjoI2WrgXkGlmA
F+Wi2ndoZbvspGAwxmrNMtLE3OYNuMXFF410QSPf4o9QqpGDC3a2mccTXb231a18
5vDJixeZpuzEm5ECXg8j6aj53X3rtm7C8yfOsg5UTKJJj+pSNz4YTp91IDHm0nTP
2KhrgS7jujgKdJn9xv07e/API3kLWkVmMwHBiaSCIaHOfAN0RJMQVV+YgnSp2sIa
OATWgSKH0qTkleE/v7k+USs0a+KV8wmC5wwliqH+uLO++yIP/9bjDctyLulQX5Ee
+EhD7tb1R/yyWY4uhkzlsr3N2Kl34aQAEBMn8Z1mHsyyu1FcbEaNLU8jcS3pHPVM
gQRn3m1iDnQlFciAMxW0pW6mW/4xKYzhXk5BTSolnqMVylxHgWXuBwdDDQQVnQ==
-----END CERTIFICATE-----

View file

@ -16,6 +16,7 @@
import pro.gravit.launcher.Launcher;
import pro.gravit.launchserver.config.LaunchServerConfig;
import pro.gravit.launchserver.config.LaunchServerRuntimeConfig;
import pro.gravit.launchserver.manangers.CertificateManager;
import pro.gravit.launchserver.manangers.LaunchServerGsonManager;
import pro.gravit.launchserver.modules.impl.LaunchServerModulesManager;
import pro.gravit.utils.command.StdCommandHandler;
@ -32,7 +33,7 @@ public class StartLaunchServerTest {
@BeforeAll
public static void prepare() throws Exception
{
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(modulesDir, configDir);
LaunchServerModulesManager modulesManager = new LaunchServerModulesManager(modulesDir, configDir, null);
LaunchServerConfig config = LaunchServerConfig.getDefault(LaunchServer.LaunchServerEnv.TEST);
Launcher.gsonManager = new LaunchServerGsonManager(modulesManager);
Launcher.gsonManager.initGson();
@ -47,6 +48,7 @@ public static void prepare() throws Exception
.setRuntimeConfig(runtimeConfig)
.setPublicKey(publicKey)
.setPrivateKey(privateKey)
.setCertificateManager(new CertificateManager())
.setLaunchServerConfigManager(new LaunchServer.LaunchServerConfigManager() {
@Override
public LaunchServerConfig readConfig() throws IOException {

View file

@ -7,6 +7,12 @@
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@ -22,7 +28,9 @@
import pro.gravit.utils.PublicURLClassLoader;
import pro.gravit.utils.Version;
import pro.gravit.utils.helper.IOHelper;
import pro.gravit.utils.helper.JVMHelper;
import pro.gravit.utils.helper.LogHelper;
import pro.gravit.utils.verify.LauncherTrustManager;
public class SimpleModuleManager implements LauncherModulesManager {
protected final List<LauncherModule> modules = new ArrayList<>();
@ -30,6 +38,7 @@ public class SimpleModuleManager implements LauncherModulesManager {
protected final SimpleModuleContext context;
protected final ModulesConfigManager modulesConfigManager;
protected final Path modulesDir;
protected final LauncherTrustManager trustManager;
protected LauncherInitContext initContext;
protected PublicURLClassLoader classLoader = new PublicURLClassLoader(new URL[]{});
@ -113,6 +122,13 @@ public SimpleModuleManager(Path modulesDir, Path configDir) {
modulesConfigManager = new SimpleModulesConfigManager(configDir);
context = new SimpleModuleContext(this, modulesConfigManager);
this.modulesDir = modulesDir;
this.trustManager = null;
}
public SimpleModuleManager(Path modulesDir, Path configDir, LauncherTrustManager trustManager) {
modulesConfigManager = new SimpleModulesConfigManager(configDir);
context = new SimpleModuleContext(this, modulesConfigManager);
this.modulesDir = modulesDir;
this.trustManager = trustManager;
}
@Override
@ -142,7 +158,10 @@ public LauncherModule loadModule(Path file) throws IOException {
return null;
}
classLoader.addURL(file.toUri().toURL());
LauncherModule module = (LauncherModule) Class.forName(moduleClass, true, classLoader).newInstance();
@SuppressWarnings("unchecked cast")
Class<? extends LauncherModule> clazz = (Class<? extends LauncherModule>) Class.forName(moduleClass, false, classLoader);
checkModuleClass(clazz);
LauncherModule module = clazz.newInstance();
loadModule(module);
return module;
} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {
@ -152,6 +171,23 @@ public LauncherModule loadModule(Path file) throws IOException {
}
}
protected void checkModuleClass(Class<? extends LauncherModule> clazz) throws SecurityException
{
if(trustManager == null) return;
X509Certificate[] certificates = JVMHelper.getCertificates(clazz);
if(certificates == null)
{
LogHelper.warning("Module class %s not signed", clazz.getName());
}
try {
trustManager.checkCertificate(certificates, (c,s) -> {
});
} catch (CertificateException | NoSuchProviderException | NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
throw new SecurityException(e);
}
}
@Override
public LauncherModule getModule(String name) {
for(LauncherModule module : modules)

View file

@ -114,7 +114,7 @@ public static URL[] getClassPathURL() {
public static X509Certificate[] getCertificates(Class<?> clazz)
{
Object[] signers = clazz.getSigners();
if(signers == null) return new X509Certificate[] {};
if(signers == null) return null;
return Arrays.stream(signers).filter((c) -> c instanceof X509Certificate).map((c) -> (X509Certificate) c).toArray(X509Certificate[]::new);
}

View file

@ -0,0 +1,88 @@
package pro.gravit.utils.verify;
import pro.gravit.utils.helper.LogHelper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.SignatureException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public class LauncherTrustManager {
private final X509Certificate[] trustSigners;
private final List<X509Certificate> trustCache = new ArrayList<>();
public LauncherTrustManager(X509Certificate[] trustSigners) {
this.trustSigners = trustSigners;
}
public LauncherTrustManager(byte[][] encodedCertificate) throws CertificateException {
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
trustSigners = Arrays.stream(encodedCertificate).map((cert) -> {
try(InputStream input = new ByteArrayInputStream(cert))
{
return (X509Certificate) certFactory.generateCertificate(input);
} catch (IOException | CertificateException e) {
LogHelper.error(e);
return null;
}
}).toArray(X509Certificate[]::new);
}
public interface CertificateChecker
{
void check(X509Certificate cert, X509Certificate signer) throws CertificateException, SecurityException;
}
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)
{
X509Certificate cert = certs[i];
if(trustCache.contains(cert))
{
//Добавляем в кеш все проверенные сертификаты
for(int j=0;j < i;++j)
trustCache.add(certs[j]);
return;
}
X509Certificate signer = (i+1 < certs.length) ? certs[i+1] : null;
cert.checkValidity();
if(signer != null)
{
cert.verify(signer.getPublicKey());
}
else
{
if(!isTrusted(cert))
{
throw new CertificateException(String.format("Certificate %s is not signed by a trusted signer", cert.getSubjectDN().getName()));
}
}
checker.check(cert, signer);
}
Collections.addAll(trustCache, certs);
}
public boolean isTrusted(X509Certificate certificate) throws CertificateEncodingException {
//Java API не дает возможности вызвать getFingerprint
//Oracle использует хак с кастом к sun.security.x509.X509CertImpl для проверки равенства сертификатов
//Мы пойдем более медленным путем
for(X509Certificate cert : trustSigners)
{
if(cert.getSerialNumber().equals(certificate.getSerialNumber()) //Проверка serialNumber (быстро)
&& Arrays.equals(cert.getEncoded(), certificate.getEncoded())) //Полная проверка (медленно)
{
return true;
}
}
return false;
}
}