diff --git a/Freesia-Common/build.gradle.kts b/Freesia-Common/build.gradle.kts index c1b9d11..762fcf2 100644 --- a/Freesia-Common/build.gradle.kts +++ b/Freesia-Common/build.gradle.kts @@ -1,3 +1,7 @@ +plugins { + id("java-library") +} + repositories { mavenCentral() } @@ -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") } diff --git a/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/CertificatePersistence.java b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/CertificatePersistence.java new file mode 100644 index 0000000..3eef951 --- /dev/null +++ b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/CertificatePersistence.java @@ -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)); + } + } +} diff --git a/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/ServerSslUtils.java b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/ServerSslUtils.java index 3d77087..122b8fa 100644 --- a/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/ServerSslUtils.java +++ b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/ServerSslUtils.java @@ -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(); } } diff --git a/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/SslUtils.java b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/SslUtils.java index b787328..65adff0 100644 --- a/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/SslUtils.java +++ b/Freesia-Common/src/main/java/com/nguyendevs/freesia/common/SslUtils.java @@ -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(); } } diff --git a/Freesia-Velocity/build.gradle.kts b/Freesia-Velocity/build.gradle.kts index 75dec7d..24029b9 100644 --- a/Freesia-Velocity/build.gradle.kts +++ b/Freesia-Velocity/build.gradle.kts @@ -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") } diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/Freesia.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/Freesia.java index bd2b321..d31664e 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/Freesia.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/Freesia.java @@ -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) { diff --git a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/FreesiaSecurityConfig.java b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/FreesiaSecurityConfig.java index cb8f1ec..05b297c 100644 --- a/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/FreesiaSecurityConfig.java +++ b/Freesia-Velocity/src/main/java/com/nguyendevs/freesia/velocity/FreesiaSecurityConfig.java @@ -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 allowedWorkerIps = java.util.Arrays.asList("127.0.0.1", "192.168.1.5"); @@ -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); } diff --git a/Freesia-Waterfall/build.gradle.kts b/Freesia-Waterfall/build.gradle.kts index 9b56029..339d75d 100644 --- a/Freesia-Waterfall/build.gradle.kts +++ b/Freesia-Waterfall/build.gradle.kts @@ -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 diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/Freesia.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/Freesia.java index e07b3a8..ff55671 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/Freesia.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/Freesia.java @@ -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) { diff --git a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/FreesiaSecurityConfig.java b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/FreesiaSecurityConfig.java index fdcd191..454438c 100644 --- a/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/FreesiaSecurityConfig.java +++ b/Freesia-Waterfall/src/main/java/com/nguyendevs/freesia/waterfall/FreesiaSecurityConfig.java @@ -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 allowedWorkerIps = java.util.Arrays.asList("127.0.0.1", "192.168.1.5"); @@ -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); } diff --git a/Freesia-Worker/build.gradle.kts b/Freesia-Worker/build.gradle.kts index 74bc978..88bab24 100644 --- a/Freesia-Worker/build.gradle.kts +++ b/Freesia-Worker/build.gradle.kts @@ -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"]}") diff --git a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/FreesiaWorkerConfig.java b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/FreesiaWorkerConfig.java index 82d3d6c..9033474 100644 --- a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/FreesiaWorkerConfig.java +++ b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/FreesiaWorkerConfig.java @@ -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 { @@ -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 get(String key, T def) { diff --git a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/ServerLoader.java b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/ServerLoader.java index 693ef19..c6ab89c 100644 --- a/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/ServerLoader.java +++ b/Freesia-Worker/src/main/java/com/nguyendevs/freesia/worker/ServerLoader.java @@ -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) { diff --git a/README.md b/README.md index e5d730b..7497547 100644 --- a/README.md +++ b/README.md @@ -61,7 +61,7 @@ Freesia is a hybrid between **MultiPaper** and **Geyser**. It uses MultiPaper's ## Configuration -> 🔒 **Security Warning:** Never expose ports `19200` (master) or `19199` (msession) to the public internet! They are designed strictly for internal communication. +> 🛡️ **Network Policy:** While mTLS provides robust authentication, it is highly recommended to restrict ports `19199` and `19200` to your internal network via firewall to minimize attack surface and optimize resource handling. ### Proxy — `plugins/Freesia/freesia_config.toml` @@ -81,55 +81,55 @@ worker_msession_ip = "localhost" worker_msession_port = 19199 ``` -### Proxy Security — `plugins/Freesia/freesia_security.toml` -*Freesia comes with a hardened IP filtering and TLS encapsulation system to strictly authorize traffic between Master and Worker Nodes.* +## Security & mTLS Setup +Freesia uses **Mutual TLS (mTLS)** 2-way authentication to secure traffic between the Proxy and Worker nodes. + +### 1. Certificate Generation +Start both the Proxy and Worker once. They will automatically generate persistent identity files in their respective `security/` directories. + +### 2. Trust Exchange +Exchange the public certificates to establish a mutual trust chain: +* Copy `proxy_cert.pem` from Proxy to Worker's `security/` folder. +* Copy `worker_cert.pem` from Worker to Proxy's `security/` folder. + +### 3. Verification +Ensure the following configurations are set: + +**Proxy — `freesia_security.toml`** ```toml [security] -enable_tls = true # Set to false to disable network encryption -use_self_signed = true # Automatically generates a TLS certificate on boot -cert_path = "cert.pem" # Required if use_self_signed = false -key_path = "key.pem" +enable_tls = true +use_self_signed = true +cert_path = "security/proxy_cert.pem" +key_path = "security/proxy_key.pem" +trust_worker_cert_path = "security/worker_cert.pem" [firewall] -enable_ip_filter = true # Enables strict IP Whitelisting -allowed_worker_ips = ["127.0.0.1", "192.168.1.5"] # Only IPs listed here are allowed to connect +enable_ip_filter = true +allowed_worker_ips = ["127.0.0.1"] ``` -### Worker — `config/freesia_config.toml` - +**Worker — `config/freesia_config.toml`** ```toml -[worker] -worker_master_ip = "localhost" -worker_master_port = 19200 -controller_reconnect_interval = 1 -player_data_cache_invalidate_interval_seconds = 30 - [security] -enable_tls = true # Must match proxy's security configuration -trust_all = true # Trusts any master certificates (useful for self-signed proxy setups) -trust_cert_path = "truststore.pem" -``` - -### Worker — `server.properties` - -```properties -server-ip=127.0.0.1 -server-port=19199 # Must match worker_msession_port in Velocity config +enable_tls = true +trust_all = false +trust_proxy_cert_path = "security/proxy_cert.pem" +worker_cert_path = "security/worker_cert.pem" +worker_key_path = "security/worker_key.pem" ``` --- ## Features -- Full Yes Steve Model support on Velocity/Waterfall / plugin networks -- **Native TLS Encryption:** Data passing between Proxy and Workers is actively encrypted natively on Java 21+ environments. -- **Zero-Latency Processing:** Internal socket buffering is disabled (Nagle's Algorithm bypassed) ensuring rapid-fire YSM sync processing without latency spikes. -- Asynchronous model synchronization and data generation -- Worker nodes optimized exclusively for YSM packet handling -- Optional kick for players without YSM mod installed -- Multi-language kick messages (`zh_CN` / `en_US` / `vi_VN`) -- Secure design — Configurable Built-in **IP Whitelist Firewall** against external intrusions. +- **Mutual TLS (mTLS):** Full 2-way authenticated and encrypted communication. +- **Persistent PKI:** Automatic generation and storage of self-signed RSA-2048 certificates. +- **Zero-Latency Sockets:** Bypassed Nagle's Algorithm for immediate packet dispatch. +- **Multi-Backend Support:** Compatible with both Velocity and Waterfall (BungeeCord) proxies. +- **Optimized Worker:** Dedicated Fabric node for async YSM processing and caching. +- **Security Firewall:** Built-in IP whitelisting for additional network-level protection. --- diff --git a/gradle.properties b/gradle.properties index 38a0fa8..4131173 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,4 +4,5 @@ minecraft_version=1.21.1 parchment_version=2024.11.17 fabric_loader_version=0.16.13 fabric_version=0.115.4+1.21.1 -night_config_version=3.6.6 \ No newline at end of file +night_config_version=3.6.6 +org.gradle.jvmargs=-Xmx2048m \ No newline at end of file