Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions Freesia-Common/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
plugins {
id("java-library")
}

repositories {
mavenCentral()
}
Expand All @@ -7,6 +11,7 @@ dependencies {
compileOnly("org.slf4j:slf4j-api:2.0.13")
compileOnly("org.jetbrains:annotations:24.1.0")
compileOnly("ca.spottedleaf:concurrentutil:0.0.3")
compileOnly("org.bouncycastle:bcpkix-jdk18on:1.78.1")
compileOnly("org.bouncycastle:bcprov-jdk18on:1.78.1")
api("org.bouncycastle:bcpkix-jdk18on:1.78.1")
api("org.bouncycastle:bcprov-jdk18on:1.78.1")
api("org.bouncycastle:bcutil-jdk18on:1.78.1")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package com.nguyendevs.freesia.common;

import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemWriter;

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.security.*;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Date;

public class CertificatePersistence {
static {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
}
}

public static void generateAndSaveSelfSigned(File certFile, File keyFile, String cn) throws Exception {
KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
kpGen.initialize(2048, new SecureRandom());
KeyPair kp = kpGen.generateKeyPair();

X500Name issuer = new X500Name("CN=" + cn);
BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
Date notBefore = new Date(System.currentTimeMillis() - 86400000L);
Date notAfter = new Date(System.currentTimeMillis() + 86400000L * 3650); // 10 years

X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
issuer, serial, notBefore, notAfter, issuer, kp.getPublic()
);
ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider("BC").build(kp.getPrivate());
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certGen.build(sigGen));

saveToPem(certFile, "CERTIFICATE", cert.getEncoded());
saveToPem(keyFile, "PRIVATE KEY", kp.getPrivate().getEncoded());

EntryPoint.LOGGER_INST.info("\u001B[32m[Security] Successfully generated and saved " + cn + " certificate to " + certFile.getName() + "\u001B[0m");
}

private static void saveToPem(File file, String type, byte[] content) throws IOException {
File parent = file.getParentFile();
if (parent != null && !parent.exists()) {
parent.mkdirs();
}
try (PemWriter writer = new PemWriter(new FileWriter(file))) {
writer.writeObject(new PemObject(type, content));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,51 +1,37 @@
package com.nguyendevs.freesia.common;

import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;

import java.io.File;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.security.cert.X509Certificate;
import java.util.Date;

public class ServerSslUtils {
public static SslContext createServerContext(boolean useSelfSigned, String certPath, String keyPath) throws Exception {
public static SslContext createServerContext(boolean useSelfSigned, String certPath, String keyPath, String trustPath) throws Exception {
File certFile = new File(certPath);
File keyFile = new File(keyPath);

if (useSelfSigned) {
if (Security.getProvider("BC") == null) {
Security.addProvider(new BouncyCastleProvider());
if (!certFile.exists() || !keyFile.exists()) {
EntryPoint.LOGGER_INST.info("\u001B[33m[Security] Certificate files not found. Generating new self-signed pair...\u001B[0m");
CertificatePersistence.generateAndSaveSelfSigned(certFile, keyFile, "FreesiaMaster");
}
}

KeyPairGenerator kpGen = KeyPairGenerator.getInstance("RSA", "BC");
kpGen.initialize(2048, new SecureRandom());
KeyPair kp = kpGen.generateKeyPair();

X500Name issuer = new X500Name("CN=Freesia");
BigInteger serial = BigInteger.valueOf(System.currentTimeMillis());
Date notBefore = new Date(System.currentTimeMillis() - 86400000L);
Date notAfter = new Date(System.currentTimeMillis() + 86400000L * 365);

X509v3CertificateBuilder certGen = new JcaX509v3CertificateBuilder(
issuer, serial, notBefore, notAfter, issuer, kp.getPublic()
);
ContentSigner sigGen = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider("BC").build(kp.getPrivate());
X509Certificate cert = new JcaX509CertificateConverter().setProvider("BC").getCertificate(certGen.build(sigGen));
SslContextBuilder builder = SslContextBuilder.forServer(certFile, keyFile);

EntryPoint.LOGGER_INST.info("\u001B[32m[Security] Generated Self-Signed Certificate for Master Server\u001B[0m");
return SslContextBuilder.forServer(kp.getPrivate(), cert).build();
} else {
EntryPoint.LOGGER_INST.info("\u001B[32m[Security] Loaded Certificate from " + certPath + " for Master Server\u001B[0m");
return SslContextBuilder.forServer(new File(certPath), new File(keyPath)).build();
if (trustPath != null) {
File trustFile = new File(trustPath);
if (trustFile.exists()) {
EntryPoint.LOGGER_INST.info("\u001B[32m[Security] mTLS enabled: Loading trust store from " + trustPath + " (Verifying Workers)\u001B[0m");
builder.trustManager(trustFile);
builder.clientAuth(ClientAuth.REQUIRE);
} else {
EntryPoint.LOGGER_INST.warn("\u001B[33m[Security] Trust store file not found at " + trustPath + ". mTLS will be disabled (Proxy authentication only).\u001B[0m");
}
}

EntryPoint.LOGGER_INST.info("\u001B[32m[Security] Initialized SSL context for Master Server (TLS " + (trustPath != null ? "2-way" : "1-way") + ")\u001B[0m");
return builder.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,33 @@

public class SslUtils {

public static SslContext createClientContext(boolean trustAll, String trustCertPath) throws Exception {
public static SslContext createClientContext(boolean trustAll, String trustCertPath, String workerCertPath, String workerKeyPath) throws Exception {
SslContextBuilder builder = SslContextBuilder.forClient();

if (trustAll) {
EntryPoint.LOGGER_INST.info("\u001B[33m[Security] Client trusting all certificates (Insecure TrustManager)\u001B[0m");
return SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
builder.trustManager(InsecureTrustManagerFactory.INSTANCE);
} else {
EntryPoint.LOGGER_INST.info("\u001B[32m[Security] Client loading TrustStore from " + trustCertPath + "\u001B[0m");
return SslContextBuilder.forClient().trustManager(new File(trustCertPath)).build();
File trustFile = new File(trustCertPath);
if (trustFile.exists()) {
EntryPoint.LOGGER_INST.info("\u001B[32m[Security] Client loading TrustStore from " + trustCertPath + "\u001B[0m");
builder.trustManager(trustFile);
}
}

if (workerCertPath != null && workerKeyPath != null) {
File certFile = new File(workerCertPath);
File keyFile = new File(workerKeyPath);

if (!certFile.exists() || !keyFile.exists()) {
EntryPoint.LOGGER_INST.info("\u001B[33m[Security] Worker identity files not found. Generating new self-signed pair...\u001B[0m");
CertificatePersistence.generateAndSaveSelfSigned(certFile, keyFile, "FreesiaWorker");
}

EntryPoint.LOGGER_INST.info("\u001B[32m[Security] mTLS: Providing worker identity from " + workerCertPath + "\u001B[0m");
builder.keyManager(certFile, keyFile);
}

return builder.build();
}
}
1 change: 1 addition & 0 deletions Freesia-Velocity/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ dependencies {
implementation("ca.spottedleaf:concurrentutil:0.0.3")
implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1")
implementation("org.bouncycastle:bcprov-jdk18on:1.78.1")
implementation("org.bouncycastle:bcutil-jdk18on:1.78.1")
annotationProcessor("com.velocitypowered:velocity-api:3.4.0-SNAPSHOT")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,8 @@ public void onProxyStart(ProxyInitializeEvent event) {
sslContext = com.nguyendevs.freesia.common.ServerSslUtils.createServerContext(
FreesiaSecurityConfig.useSelfSigned,
java.nio.file.Paths.get(FreesiaConstants.FileConstants.PLUGIN_DIR.getAbsolutePath()).resolve(FreesiaSecurityConfig.certPath).toFile().getAbsolutePath(),
java.nio.file.Paths.get(FreesiaConstants.FileConstants.PLUGIN_DIR.getAbsolutePath()).resolve(FreesiaSecurityConfig.keyPath).toFile().getAbsolutePath()
java.nio.file.Paths.get(FreesiaConstants.FileConstants.PLUGIN_DIR.getAbsolutePath()).resolve(FreesiaSecurityConfig.keyPath).toFile().getAbsolutePath(),
java.nio.file.Paths.get(FreesiaConstants.FileConstants.PLUGIN_DIR.getAbsolutePath()).resolve(FreesiaSecurityConfig.trustWorkerCertPath).toFile().getAbsolutePath()
);
}
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
public class FreesiaSecurityConfig {
public static boolean enableTls = true;
public static boolean useSelfSigned = true;
public static String certPath = "cert.pem";
public static String keyPath = "key.pem";
public static String certPath = "security/proxy_cert.pem";
public static String keyPath = "security/proxy_key.pem";
public static String trustWorkerCertPath = "security/worker_cert.pem";
public static boolean enableIpFilter = true;
public static java.util.List<String> allowedWorkerIps = java.util.Arrays.asList("127.0.0.1", "192.168.1.5");

Expand All @@ -19,6 +20,7 @@ private static void loadOrDefaultValues() {
useSelfSigned = get("security.use_self_signed", useSelfSigned);
certPath = get("security.cert_path", certPath);
keyPath = get("security.key_path", keyPath);
trustWorkerCertPath = get("security.trust_worker_cert_path", trustWorkerCertPath);
enableIpFilter = get("firewall.enable_ip_filter", enableIpFilter);
allowedWorkerIps = get("firewall.allowed_worker_ips", allowedWorkerIps);
}
Expand Down
1 change: 1 addition & 0 deletions Freesia-Waterfall/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ dependencies {
implementation("net.kyori:adventure-text-serializer-legacy:4.14.0")
implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1")
implementation("org.bouncycastle:bcprov-jdk18on:1.78.1")
implementation("org.bouncycastle:bcutil-jdk18on:1.78.1")
}

val targetJavaVersion = 21
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,8 @@ public void onEnable() {
sslContext = com.nguyendevs.freesia.common.ServerSslUtils.createServerContext(
FreesiaSecurityConfig.useSelfSigned,
java.nio.file.Paths.get(FreesiaConstants.FileConstants.PLUGIN_DIR.getAbsolutePath()).resolve(FreesiaSecurityConfig.certPath).toFile().getAbsolutePath(),
java.nio.file.Paths.get(FreesiaConstants.FileConstants.PLUGIN_DIR.getAbsolutePath()).resolve(FreesiaSecurityConfig.keyPath).toFile().getAbsolutePath()
java.nio.file.Paths.get(FreesiaConstants.FileConstants.PLUGIN_DIR.getAbsolutePath()).resolve(FreesiaSecurityConfig.keyPath).toFile().getAbsolutePath(),
java.nio.file.Paths.get(FreesiaConstants.FileConstants.PLUGIN_DIR.getAbsolutePath()).resolve(FreesiaSecurityConfig.trustWorkerCertPath).toFile().getAbsolutePath()
);
}
} catch (Exception e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@
public class FreesiaSecurityConfig {
public static boolean enableTls = true;
public static boolean useSelfSigned = true;
public static String certPath = "cert.pem";
public static String keyPath = "key.pem";
public static String certPath = "security/proxy_cert.pem";
public static String keyPath = "security/proxy_key.pem";
public static String trustWorkerCertPath = "security/worker_cert.pem";
public static boolean enableIpFilter = true;
public static java.util.List<String> allowedWorkerIps = java.util.Arrays.asList("127.0.0.1", "192.168.1.5");

Expand All @@ -19,6 +20,7 @@ private static void loadOrDefaultValues() {
useSelfSigned = get("security.use_self_signed", useSelfSigned);
certPath = get("security.cert_path", certPath);
keyPath = get("security.key_path", keyPath);
trustWorkerCertPath = get("security.trust_worker_cert_path", trustWorkerCertPath);
enableIpFilter = get("firewall.enable_ip_filter", enableIpFilter);
allowedWorkerIps = get("firewall.allowed_worker_ips", allowedWorkerIps);
}
Expand Down
6 changes: 6 additions & 0 deletions Freesia-Worker/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ dependencies {
include("com.electronwill.night-config:core:${rootProject.extra["night_config_version"]}")
include("ca.spottedleaf:concurrentutil:0.0.3")

implementation("org.bouncycastle:bcpkix-jdk18on:1.78.1")
include("org.bouncycastle:bcpkix-jdk18on:1.78.1")
implementation("org.bouncycastle:bcprov-jdk18on:1.78.1")
include("org.bouncycastle:bcprov-jdk18on:1.78.1")
implementation("org.bouncycastle:bcutil-jdk18on:1.78.1")
include("org.bouncycastle:bcutil-jdk18on:1.78.1")

implementation("com.electronwill.night-config:toml:${rootProject.extra["night_config_version"]}")
include("com.electronwill.night-config:toml:${rootProject.extra["night_config_version"]}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,10 @@ public class FreesiaWorkerConfig {
public static int reconnectInterval = 1;
public static int playerDataCacheInvalidateIntervalSeconds = 30;
public static boolean enableTls = true;
public static boolean trustAll = true;
public static String trustCertPath = "truststore.pem";
public static boolean trustAll = false;
public static String trustCertPath = "security/proxy_cert.pem";
public static String workerCertPath = "security/worker_cert.pem";
public static String workerKeyPath = "security/worker_key.pem";
private static CommentedFileConfig CONFIG_INSTANCE;

static {
Expand All @@ -33,7 +35,9 @@ private static void loadOrDefaultValues() {
playerDataCacheInvalidateIntervalSeconds = get("worker.player_data_cache_invalidate_interval_seconds", playerDataCacheInvalidateIntervalSeconds);
enableTls = get("security.enable_tls", enableTls);
trustAll = get("security.trust_all", trustAll);
trustCertPath = get("security.trust_cert_path", trustCertPath);
trustCertPath = get("security.trust_proxy_cert_path", trustCertPath);
workerCertPath = get("security.worker_cert_path", workerCertPath);
workerKeyPath = get("security.worker_key_path", workerKeyPath);
}

private static <T> T get(String key, T def) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,9 @@ public void onInitializeServer() {
if (FreesiaWorkerConfig.enableTls) {
sslContext = com.nguyendevs.freesia.common.SslUtils.createClientContext(
FreesiaWorkerConfig.trustAll,
new File("config", FreesiaWorkerConfig.trustCertPath).getAbsolutePath()
new File("config", FreesiaWorkerConfig.trustCertPath).getAbsolutePath(),
new File("config", FreesiaWorkerConfig.workerCertPath).getAbsolutePath(),
new File("config", FreesiaWorkerConfig.workerKeyPath).getAbsolutePath()
);
}
} catch (Exception e) {
Expand Down
Loading
Loading