diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e211a2cb..b525c628 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -40,8 +40,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'temurin' - # Use Java 16+, because it's minimum required version by Geyser - java-version: 17 + java-version: 18 cache: 'maven' # Initializes the CodeQL tools for scanning. diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index bb1603e7..790c641e 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -31,8 +31,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'temurin' - # Use Java 16+, because it's minimum required version by Geyser - java-version: 17 + java-version: 18 cache: 'maven' # Build and test (included in package) @@ -40,4 +39,4 @@ jobs: # Run non-interactive, package (with compile+test), # ignore snapshot updates, because they are likely to have breaking changes, enforce checksums to validate # possible errors in dependencies - run: mvn test --batch-mode --no-snapshot-updates --strict-checksums --file pom.xml + run: mvn test --batch-mode --threads 2.0C --no-snapshot-updates --strict-checksums --file pom.xml diff --git a/CHANGELOG.md b/CHANGELOG.md index d1ac62cd..af685534 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,7 +35,7 @@ * Automatically register accounts if they are not in the auth plugin database but in the FastLogin database * Update BungeeAuth dependency and use the new API. Please update your plugin if you still use the old one. * Remove deprecated API methods from the last version -* Finally update the IP column on every login +* Finally, update the IP column on every login * No duplicate session login * Fix timestamp parsing in newer versions of SQLite * Fix Spigot console command invocation sends result to in game players @@ -82,7 +82,7 @@ * Fix player entry is not saved if namechangecheck is enabled * Fix skin applies for third-party plugins * Switch to mcapi.ca for uuid lookups -* Fix BungeeCord not setting an premium uuid +* Fix BungeeCord not setting a premium uuid * Fix setting skin on Cauldron * Fix saving on name change @@ -148,7 +148,7 @@ ### 1.2 * Fix race condition in BungeeCord -* Fix dead lock in xAuth +* Fix deadlock in xAuth * Added API methods for plugins to set their own password generator * Added API methods for plugins to set their own auth plugin hook => Added support for AdvancedLogin @@ -182,7 +182,7 @@ * Added a forwardSkin config option * Added premium UUID support * Updated to the newest changes of Spigot -* Removes the need of an Bukkit auth plugin if you use a bungeecord one +* Removes the need of a Bukkit auth plugin if you use a bungeecord one * Optimize performance and thread-safety * Fixed BungeeCord support * Changed config option auto-login to auto-register to clarify the usage diff --git a/README.md b/README.md index d5847c54..64a28e09 100644 --- a/README.md +++ b/README.md @@ -64,7 +64,7 @@ Possible values: `Premium`, `Cracked`, `Unknown` * Server software in offlinemode: * Spigot (or a fork e.g. Paper) 1.8.8+ * Protocol plugin: - * [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) or + * [ProtocolLib 5.0+](https://www.spigotmc.org/resources/protocollib.1997/) or * [ProtocolSupport](https://www.spigotmc.org/resources/protocolsupport.7201/) * Latest BungeeCord (or a fork e.g. Waterfall) * An auth plugin. diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 91ca39ac..ae285d64 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -32,7 +32,7 @@ com.github.games647 fastlogin - 1.11-SNAPSHOT + 1.12-SNAPSHOT ../pom.xml @@ -118,10 +118,7 @@ dmulloy2-repo - https://repo.dmulloy2.net/nexus/repository/public/ - - false - + https://repo.dmulloy2.net/repository/public/ @@ -164,7 +161,7 @@ io.papermc.paper paper-api - 1.18-R0.1-SNAPSHOT + 1.19-R0.1-SNAPSHOT provided @@ -182,11 +179,24 @@ 1.0.7 + + com.mojang + datafixerupper + 5.0.28 + provided + + + * + * + + + + com.comphenix.protocol ProtocolLib - 4.8.0 + 5.0.0-SNAPSHOT provided @@ -201,7 +211,7 @@ com.github.ProtocolSupport ProtocolSupport - 3a80c661fe + master-66b494a8dd-1 provided @@ -255,7 +265,7 @@ me.clip placeholderapi - 2.11.1 + 2.11.2 provided true @@ -270,7 +280,7 @@ fr.xephi authme - 5.4.0 + 5.6.0-beta2 provided true @@ -351,5 +361,12 @@ system ${project.basedir}/lib/UltraAuth v2.1.2.jar + + + org.bouncycastle + bcprov-jdk18on + 1.71 + test + diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BukkitLoginSession.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BukkitLoginSession.java index 556def1b..7eb39878 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BukkitLoginSession.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BukkitLoginSession.java @@ -26,11 +26,14 @@ package com.github.games647.fastlogin.bukkit; import com.github.games647.craftapi.model.skin.SkinProperty; +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.shared.LoginSession; import java.util.Optional; +import javax.annotation.Nullable; + /** * Represents a client connecting to the server. * @@ -42,30 +45,33 @@ public class BukkitLoginSession extends LoginSession { private final byte[] verifyToken; + private final ClientPublicKey clientPublicKey; + private boolean verified; private SkinProperty skinProperty; - public BukkitLoginSession(String username, byte[] verifyToken, boolean registered + public BukkitLoginSession(String username, byte[] verifyToken, ClientPublicKey publicKey, boolean registered , StoredProfile profile) { super(username, registered, profile); + this.clientPublicKey = publicKey; this.verifyToken = verifyToken.clone(); } //available for BungeeCord public BukkitLoginSession(String username, boolean registered) { - this(username, EMPTY_ARRAY, registered, null); + this(username, EMPTY_ARRAY, null, registered, null); } //cracked player public BukkitLoginSession(String username, StoredProfile profile) { - this(username, EMPTY_ARRAY, false, profile); + this(username, EMPTY_ARRAY, null, false, profile); } //ProtocolSupport public BukkitLoginSession(String username, boolean registered, StoredProfile profile) { - this(username, EMPTY_ARRAY, registered, profile); + this(username, EMPTY_ARRAY, null, registered, profile); } /** @@ -79,6 +85,11 @@ public class BukkitLoginSession extends LoginSession { return verifyToken.clone(); } + @Nullable + public ClientPublicKey getClientPublicKey() { + return clientPublicKey; + } + /** * @return premium skin if available */ diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BungeeManager.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BungeeManager.java index a47715aa..e0c4385a 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BungeeManager.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/BungeeManager.java @@ -33,8 +33,10 @@ import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import java.io.IOException; +import java.lang.reflect.InvocationTargetException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -61,7 +63,7 @@ public class BungeeManager { private final FastLoginBukkit plugin; private boolean enabled; - private final Set firedJoinEvents = new HashSet<>(); + private final Collection firedJoinEvents = new HashSet<>(); public BungeeManager(FastLoginBukkit plugin) { this.plugin = plugin; @@ -87,33 +89,62 @@ public class BungeeManager { } public void initialize() { - try { - enabled = detectProxy(); - } catch (Exception ex) { - plugin.getLog().warn("Cannot check proxy support. Fallback to non-proxy mode", ex); - } + enabled = detectProxy(); if (enabled) { proxyIds = loadBungeeCordIds(); registerPluginChannels(); + plugin.getLog().info("Found enabled proxy configuration"); + plugin.getLog().info("Remember to follow the proxy guide to complete your setup"); + } else { + plugin.getLog().warn("Disabling Minecraft proxy configuration. Assuming direct connections from now on."); } } - private boolean isProxySupported(String className, String fieldName) { + private boolean isProxySupported(String className, String fieldName) + throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException { + return Class.forName(className).getDeclaredField(fieldName).getBoolean(null); + } + + private boolean isVelocityEnabled() + throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, ClassNotFoundException, + NoSuchMethodException, InvocationTargetException { try { - return Class.forName(className).getDeclaredField(fieldName).getBoolean(null); - } catch (ClassNotFoundException notFoundEx) { - //ignore server has no proxy support - } catch (NoSuchFieldException | IllegalAccessException noSuchFieldException) { - plugin.getLog().warn("Cannot access proxy field", noSuchFieldException); + Class globalConfig = Class.forName("io.papermc.paper.configuration.GlobalConfiguration"); + Object global = globalConfig.getDeclaredMethod("get").invoke(null); + Object proxiesConfiguration = global.getClass().getDeclaredField("proxies").get(global); + Object velocityConfig = proxiesConfiguration.getClass().getDeclaredField("velocity").get(proxiesConfiguration); + + return velocityConfig.getClass().getDeclaredField("enabled").getBoolean(velocityConfig); + } catch (ClassNotFoundException classNotFoundException) { + // try again using the older Paper configuration, because the old class file still exists in newer versions + if (isProxySupported("com.destroystokyo.paper.PaperConfig", "velocitySupport")) { + return true; + } } return false; } private boolean detectProxy() { - return isProxySupported("org.spigotmc.SpigotConfig", "bungee") - || isProxySupported("com.destroystokyo.paper.PaperConfig", "velocitySupport"); + try { + if (isProxySupported("org.spigotmc.SpigotConfig", "bungee")) return true; + } catch (ClassNotFoundException classNotFoundException) { + // leave stacktrace for class not found out + plugin.getLog().warn("Cannot check for BungeeCord support: {}", classNotFoundException.getMessage()); + } catch (NoSuchFieldException | IllegalAccessException ex) { + plugin.getLog().warn("Cannot check for BungeeCord support", ex); + } + + try { + return isVelocityEnabled(); + } catch (ClassNotFoundException classNotFoundException) { + plugin.getLog().warn("Cannot check for Velocity support in Paper: {}", classNotFoundException.getMessage()); + } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) { + plugin.getLog().warn("Cannot check for Velocity support in Paper", ex); + } + + return false; } private void registerPluginChannels() { @@ -147,9 +178,7 @@ public class BungeeManager { Files.deleteIfExists(legacyFile); try (Stream lines = Files.lines(proxiesFile)) { - return lines.map(String::trim) - .map(UUID::fromString) - .collect(toSet()); + return lines.map(String::trim).map(UUID::fromString).collect(toSet()); } } catch (IOException ex) { plugin.getLog().error("Failed to read proxies", ex); @@ -176,7 +205,7 @@ public class BungeeManager { /** * Check if the event fired including with the task delay. This necessary to restore the order of processing the * BungeeCord messages after the PlayerJoinEvent fires including the delay. - * + *

* If the join event fired, the delay exceeded, but it ran earlier and couldn't find the recently started login * session. If not fired, we can start a new force login task. This will still match the requirement that we wait * a certain time after the player join event fired. diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java index 3121cbca..8b5dad60 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java @@ -117,7 +117,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin getPlaceholders() { + return List.of(PLACEHOLDER_VARIABLE); + } + @Override public String onRequest(OfflinePlayer player, @NotNull String identifier) { // player is null if offline diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/CrackedCommand.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/CrackedCommand.java index a6ac9b7b..61533387 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/CrackedCommand.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/CrackedCommand.java @@ -44,7 +44,7 @@ public class CrackedCommand extends ToggleCommand { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { if (args.length == 0) { - onCrackedSelf(sender, command, args); + onCrackedSelf(sender); } else { onCrackedOther(sender, command, args); } @@ -52,7 +52,7 @@ public class CrackedCommand extends ToggleCommand { return true; } - private void onCrackedSelf(CommandSender sender, Command cmd, String[] args) { + private void onCrackedSelf(CommandSender sender) { if (isConsole(sender)) { return; } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/PremiumCommand.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/PremiumCommand.java index 26b6d312..9f1ef982 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/PremiumCommand.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/command/PremiumCommand.java @@ -51,7 +51,7 @@ public class PremiumCommand extends ToggleCommand { @Override public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, String[] args) { if (args.length == 0) { - onPremiumSelf(sender, command, args); + onPremiumSelf(sender); } else { onPremiumOther(sender, command, args); } @@ -59,7 +59,7 @@ public class PremiumCommand extends ToggleCommand { return true; } - private void onPremiumSelf(CommandSender sender, Command cmd, String[] args) { + private void onPremiumSelf(CommandSender sender) { if (isConsole(sender)) { return; } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginAutoLoginEvent.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginAutoLoginEvent.java index 8c61330b..6412c6d8 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginAutoLoginEvent.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginAutoLoginEvent.java @@ -28,6 +28,7 @@ package com.github.games647.fastlogin.bukkit.event; import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.shared.LoginSession; import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent; + import org.bukkit.event.Cancellable; import org.bukkit.event.Event; import org.bukkit.event.HandlerList; diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginPreLoginEvent.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginPreLoginEvent.java index 6b2edfca..5bf6df99 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginPreLoginEvent.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginPreLoginEvent.java @@ -28,6 +28,7 @@ package com.github.games647.fastlogin.bukkit.event; import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.shared.LoginSource; import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent; + import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginPremiumToggleEvent.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginPremiumToggleEvent.java index 146efb07..0904826d 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginPremiumToggleEvent.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/event/BukkitFastLoginPremiumToggleEvent.java @@ -27,6 +27,7 @@ package com.github.games647.fastlogin.bukkit.event; import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent; + import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import org.jetbrains.annotations.NotNull; diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/AuthMeHook.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/AuthMeHook.java index 6003c02c..45d5a5f4 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/AuthMeHook.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/AuthMeHook.java @@ -28,26 +28,28 @@ package com.github.games647.fastlogin.bukkit.hook; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.core.hooks.AuthPlugin; + import fr.xephi.authme.api.v3.AuthMeApi; import fr.xephi.authme.events.RestoreSessionEvent; import fr.xephi.authme.process.Management; import fr.xephi.authme.process.register.executors.ApiPasswordRegisterParams; import fr.xephi.authme.process.register.executors.RegistrationMethod; + +import java.lang.reflect.Field; + import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; -import java.lang.reflect.Field; - /** - * GitHub: https://github.com/Xephi/AuthMeReloaded/ + * GitHub: ... *

* Project page: *

- * Bukkit: https://dev.bukkit.org/bukkit-plugins/authme-reloaded/ + * Bukkit: ... *

- * Spigot: https://www.spigotmc.org/resources/authme-reloaded.6269/ + * Spigot: ... */ public class AuthMeHook implements AuthPlugin, Listener { diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/CrazyLoginHook.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/CrazyLoginHook.java index bb4d1804..f0680293 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/CrazyLoginHook.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/CrazyLoginHook.java @@ -43,11 +43,11 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; /** - * GitHub: https://github.com/ST-DDT/CrazyLogin + * GitHub: ... *

* Project page: *

- * Bukkit: https://dev.bukkit.org/server-mods/crazylogin/ + * Bukkit: ... */ public class CrazyLoginHook implements AuthPlugin { diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/LogItHook.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/LogItHook.java index cccfffd3..3868a62a 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/LogItHook.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/LogItHook.java @@ -38,7 +38,7 @@ import java.time.Instant; import org.bukkit.entity.Player; /** - * GitHub: https://github.com/XziomekX/LogIt + * GitHub: ... *

* Project page: *

diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/LoginSecurityHook.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/LoginSecurityHook.java index 5467e434..f4b134a9 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/LoginSecurityHook.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/LoginSecurityHook.java @@ -36,13 +36,13 @@ import com.lenis0012.bukkit.loginsecurity.session.action.RegisterAction; import org.bukkit.entity.Player; /** - * GitHub: https://github.com/lenis0012/LoginSecurity-2 + * GitHub: ... *

* Project page: *

- * Bukkit: https://dev.bukkit.org/bukkit-plugins/loginsecurity/ + * Bukkit: ... *

- * Spigot: https://www.spigotmc.org/resources/loginsecurity.19362/ + * Spigot: ... */ public class LoginSecurityHook implements AuthPlugin { diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/UltraAuthHook.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/UltraAuthHook.java index 08a23d99..9b687a24 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/UltraAuthHook.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/UltraAuthHook.java @@ -39,9 +39,9 @@ import ultraauth.managers.PlayerManager; /** * Project page: *

- * Bukkit: https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/ + * Bukkit: ... *

- * Spigot: https://www.spigotmc.org/resources/ultraauth.17044/ + * Spigot: ... */ public class UltraAuthHook implements AuthPlugin { diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/xAuthHook.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/xAuthHook.java index 58280bb4..9f6cab6c 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/xAuthHook.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/hook/xAuthHook.java @@ -38,11 +38,11 @@ import org.bukkit.Bukkit; import org.bukkit.entity.Player; /** - * GitHub: https://github.com/LycanDevelopment/xAuth/ + * GitHub: ... *

* Project page: *

- * Bukkit: https://dev.bukkit.org/bukkit-plugins/xauth/ + * Bukkit: ... */ public class xAuthHook implements AuthPlugin { diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/PaperCacheListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/PaperCacheListener.java index e444ed92..44ff6ea0 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/PaperCacheListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/PaperCacheListener.java @@ -29,6 +29,7 @@ import com.destroystokyo.paper.profile.ProfileProperty; import com.github.games647.craftapi.model.skin.Textures; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; + import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtil.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtil.java index 1197514b..844b92c6 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtil.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtil.java @@ -25,32 +25,63 @@ */ package com.github.games647.fastlogin.bukkit.listener.protocollib; +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; +import com.google.common.io.Resources; +import com.google.common.primitives.Longs; + +import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; -import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; -import java.util.Random; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; +import java.util.Arrays; +import java.util.Base64; +import java.util.Base64.Encoder; +import java.util.random.RandomGenerator; +import javax.crypto.BadPaddingException; import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; /** * Encryption and decryption minecraft util for connection between servers * and paid Minecraft account clients. - * - * @see net.minecraft.server.MinecraftEncryption */ -public class EncryptionUtil { +class EncryptionUtil { public static final int VERIFY_TOKEN_LENGTH = 4; public static final String KEY_PAIR_ALGORITHM = "RSA"; + private static final int RSA_LENGTH = 1_024; + + private static final PublicKey mojangSessionKey; + private static final int LINE_LENGTH = 76; + private static final Encoder KEY_ENCODER = Base64.getMimeEncoder(LINE_LENGTH, "\n".getBytes(StandardCharsets.UTF_8)); + + static { + try { + mojangSessionKey = loadMojangSessionKey(); + } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException ex) { + throw new RuntimeException("Failed to load Mojang session key", ex); + } + } + private EncryptionUtil() { // utility } @@ -61,11 +92,10 @@ public class EncryptionUtil { * @return The RSA key pair. */ public static KeyPair generateKeyPair() { - // KeyPair b() try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM); - keyPairGenerator.initialize(1_024); + keyPairGenerator.initialize(RSA_LENGTH); return keyPairGenerator.generateKeyPair(); } catch (NoSuchAlgorithmException nosuchalgorithmexception) { // Should be existing in every vm @@ -80,8 +110,7 @@ public class EncryptionUtil { * @param random random generator * @return an error with 4 bytes long */ - public static byte[] generateVerifyToken(Random random) { - // extracted from LoginListener + public static byte[] generateVerifyToken(RandomGenerator random) { byte[] token = new byte[VERIFY_TOKEN_LENGTH]; random.nextBytes(token); return token; @@ -90,68 +119,91 @@ public class EncryptionUtil { /** * Generate the server id based on client and server data. * - * @param sessionId session for the current login attempt + * @param serverId session for the current login attempt * @param sharedSecret shared secret between the client and the server - * @param publicKey public key of the server + * @param publicKey public key of the server * @return the server id formatted as a hexadecimal string. */ - public static String getServerIdHashString(String sessionId, SecretKey sharedSecret, PublicKey publicKey) { - // found in LoginListener - try { - byte[] serverHash = getServerIdHash(sessionId, publicKey, sharedSecret); - return (new BigInteger(serverHash)).toString(16); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - - return ""; + public static String getServerIdHashString(String serverId, SecretKey sharedSecret, PublicKey publicKey) { + byte[] serverHash = getServerIdHash(serverId, publicKey, sharedSecret); + return (new BigInteger(serverHash)).toString(16); } /** * Decrypts the content and extracts the key spec. * * @param privateKey private server key - * @param sharedKey the encrypted shared key + * @param sharedKey the encrypted shared key * @return shared secret key * @throws GeneralSecurityException if it fails to decrypt the data */ - public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] sharedKey) throws GeneralSecurityException { - // SecretKey a(PrivateKey var0, byte[] var1) + public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] sharedKey) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + BadPaddingException, InvalidKeyException { return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES"); } - public static byte[] decrypt(PrivateKey key, byte[] data) throws GeneralSecurityException { - // b(Key var0, byte[] var1) - Cipher cipher = Cipher.getInstance(key.getAlgorithm()); - cipher.init(Cipher.DECRYPT_MODE, key); - return decrypt(cipher, data); + public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimestamp) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + if (clientKey.isExpired(verifyTimestamp)) { + return false; + } + + Signature verifier = Signature.getInstance("SHA1withRSA"); + // key of the signer + verifier.initVerify(mojangSessionKey); + verifier.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII)); + return verifier.verify(clientKey.signature()); } - /** - * Decrypted the given data using the cipher. - * - * @param cipher decryption cypher initialized with the private key - * @param data the encrypted data - * @return clear text data - * @throws GeneralSecurityException if it fails to decrypt the data - */ - private static byte[] decrypt(Cipher cipher, byte[] data) throws GeneralSecurityException { - // inlined: byte[] a(int var0, Key var1, byte[] var2), Cipher a(int var0, String var1, Key - // var2) + public static boolean verifyNonce(byte[] exptected, PrivateKey decryptionKey, byte[] encryptedNonce) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + BadPaddingException, InvalidKeyException { + byte[] decryptedNonce = decrypt(decryptionKey, encryptedNonce); + return Arrays.equals(exptected, decryptedNonce); + } + + public static boolean verifySignedNonce(byte[] nonce, PublicKey clientKey, long signatureSalt, byte[] signature) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Signature verifier = Signature.getInstance("SHA256withRSA"); + // key of the signer + verifier.initVerify(clientKey); + + verifier.update(nonce); + verifier.update(Longs.toByteArray(signatureSalt)); + return verifier.verify(signature); + } + + private static PublicKey loadMojangSessionKey() + throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + var keyUrl = Resources.getResource("yggdrasil_session_pubkey.der"); + var keyData = Resources.toByteArray(keyUrl); + var keySpec = new X509EncodedKeySpec(keyData); + + return KeyFactory.getInstance("RSA").generatePublic(keySpec); + } + + private static String toSignable(ClientPublicKey clientPublicKey) { + long expiry = clientPublicKey.expiry().toEpochMilli(); + String encoded = KEY_ENCODER.encodeToString(clientPublicKey.key().getEncoded()); + return expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n"; + } + + private static byte[] decrypt(PrivateKey key, byte[] data) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, + IllegalBlockSizeException, BadPaddingException { + Cipher cipher = Cipher.getInstance(key.getAlgorithm()); + cipher.init(Cipher.DECRYPT_MODE, key); return cipher.doFinal(data); } - private static byte[] getServerIdHash( - String sessionId, PublicKey publicKey, SecretKey sharedSecret) - throws NoSuchAlgorithmException { - // byte[] a(String var0, PublicKey var1, SecretKey var2) - MessageDigest digest = MessageDigest.getInstance("SHA-1"); + private static byte[] getServerIdHash(String sessionId, PublicKey publicKey, SecretKey sharedSecret) { + Hasher hasher = Hashing.sha1().newHasher(); - // inlined from byte[] a(String var0, byte[]... var1) - digest.update(sessionId.getBytes(StandardCharsets.ISO_8859_1)); - digest.update(sharedSecret.getEncoded()); - digest.update(publicKey.getEncoded()); + hasher.putBytes(sessionId.getBytes(StandardCharsets.ISO_8859_1)); + hasher.putBytes(sharedSecret.getEncoded()); + hasher.putBytes(publicKey.getEncoded()); - return digest.digest(); + return hasher.hash().asBytes(); } } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ManualNameChange.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ManualNameChange.java index 78a8c474..2a36e88a 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ManualNameChange.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ManualNameChange.java @@ -25,6 +25,7 @@ */ package com.github.games647.fastlogin.bukkit.listener.protocollib; +import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; @@ -36,15 +37,13 @@ import org.geysermc.floodgate.api.FloodgateApi; import static com.comphenix.protocol.PacketType.Login.Client.START; -import com.comphenix.protocol.ProtocolLibrary; - /** * Manually inject Floodgate player name prefixes. *
* This is used as a workaround, because Floodgate fails to inject * the prefixes when it's used together with ProtocolLib and FastLogin. *
- * For more information visit: https://github.com/games647/FastLogin/issues/493 + * For more information visit: ... */ public class ManualNameChange extends PacketAdapter { diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/NameCheckTask.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/NameCheckTask.java index f0a40830..a135116f 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/NameCheckTask.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/NameCheckTask.java @@ -30,6 +30,7 @@ import com.comphenix.protocol.events.PacketEvent; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent; +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.shared.JoinManagement; import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent; @@ -41,11 +42,13 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; public class NameCheckTask extends JoinManagement - implements Runnable { + implements Runnable { private final FastLoginBukkit plugin; private final PacketEvent packetEvent; - private final PublicKey publicKey; + + private final ClientPublicKey clientKey; + private final PublicKey serverKey; private final Random random; @@ -53,12 +56,13 @@ public class NameCheckTask extends JoinManagement val.equals(plugin.getName())) .orElse(false); @@ -130,13 +150,56 @@ public class ProtocolLibListener extends PacketAdapter { plugin.getLog().warn("GameProfile {} tried to send encryption response at invalid state", sender.getAddress()); sender.kickPlayer(plugin.getCore().getMessage("invalid-request")); } else { - packetEvent.getAsyncMarker().incrementProcessingDelay(); - Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, session, sharedSecret, keyPair); - plugin.getScheduler().runAsync(verifyTask); + byte[] expectedVerifyToken = session.getVerifyToken(); + if (verifyNonce(sender, packetEvent.getPacket(), session.getClientPublicKey(), expectedVerifyToken)) { + packetEvent.getAsyncMarker().incrementProcessingDelay(); + Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, session, sharedSecret, keyPair); + plugin.getScheduler().runAsync(verifyTask); + } else { + sender.kickPlayer(plugin.getCore().getMessage("invalid-verify-token")); + } } } - private void onLogin(PacketEvent packetEvent, Player player, String username) { + private boolean verifyNonce(Player sender, PacketContainer packet, + ClientPublicKey clientPublicKey, byte[] expectedToken) { + try { + if (MinecraftVersion.atOrAbove(new MinecraftVersion(1, 19, 0))) { + Either either = packet.getSpecificModifier(Either.class).read(0); + if (clientPublicKey == null) { + Optional left = either.left(); + if (left.isEmpty()) { + plugin.getLog().error("No verify token sent if requested without player signed key {}", sender); + return false; + } + + return EncryptionUtil.verifyNonce(expectedToken, keyPair.getPrivate(), left.get()); + } else { + Optional optSignatureData = either.right(); + if (optSignatureData.isEmpty()) { + plugin.getLog().error("No signature given to sent player signing key {}", sender); + return false; + } + + Object signatureData = optSignatureData.get(); + long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true); + byte[] signature = FuzzyReflection.getFieldValue(signatureData, byte[].class, true); + + PublicKey publicKey = clientPublicKey.key(); + return EncryptionUtil.verifySignedNonce(expectedToken, publicKey, salt, signature); + } + } else { + byte[] nonce = packet.getByteArrays().read(1); + return EncryptionUtil.verifyNonce(expectedToken, keyPair.getPrivate(), nonce); + } + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException | NoSuchPaddingException | + IllegalBlockSizeException | BadPaddingException signatureEx) { + plugin.getLog().error("Invalid signature from player {}", sender, signatureEx); + return false; + } + } + + private void onLoginStart(PacketEvent packetEvent, Player player, String username) { //this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes String sessionKey = player.getAddress().toString(); @@ -148,10 +211,49 @@ public class ProtocolLibListener extends PacketAdapter { username = (String) packetEvent.getPacket().getMeta("original_name").get(); } + PacketContainer packet = packetEvent.getPacket(); + var profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()) + .optionRead(0); + + var clientKey = profileKey.flatMap(opt -> opt).flatMap(this::verifyPublicKey); + if (verifyClientKeys && clientKey.isEmpty()) { + // missing or incorrect + // expired always not allowed + player.kickPlayer(plugin.getCore().getMessage("invalid-public-key")); + plugin.getLog().warn("Invalid public key from player {}", username); + return; + } + plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username); packetEvent.getAsyncMarker().incrementProcessingDelay(); - Runnable nameCheckTask = new NameCheckTask(plugin, random, player, packetEvent, username, keyPair.getPublic()); + Runnable nameCheckTask = new NameCheckTask(plugin, random, player, packetEvent, username, clientKey.orElse(null), keyPair.getPublic()); plugin.getScheduler().runAsync(nameCheckTask); } + + private Optional verifyPublicKey(WrappedProfileKeyData profileKey) { + Instant expires = profileKey.getExpireTime(); + PublicKey key = profileKey.getKey(); + byte[] signature = profileKey.getSignature(); + ClientPublicKey clientKey = new ClientPublicKey(expires, key, signature); + try { + if (EncryptionUtil.verifyClientKey(clientKey, Instant.now())) { + return Optional.of(clientKey); + } + } catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) { + return Optional.empty(); + } + + return Optional.empty(); + } + + private String getUsername(PacketContainer packet) { + WrappedGameProfile profile = packet.getGameProfiles().readSafely(0); + if (profile == null) { + return packet.getStrings().read(0); + } + + //player.getName() won't work at this state + return profile.getName(); + } } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibLoginSource.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibLoginSource.java index dd191f1c..71b4c3ba 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibLoginSource.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibLoginSource.java @@ -30,9 +30,9 @@ import com.comphenix.protocol.ProtocolManager; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.wrappers.WrappedChatComponent; +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; import com.github.games647.fastlogin.core.shared.LoginSource; -import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import java.security.PublicKey; import java.util.Arrays; @@ -48,19 +48,22 @@ class ProtocolLibLoginSource implements LoginSource { private final Player player; private final Random random; + + private final ClientPublicKey clientKey; private final PublicKey publicKey; private final String serverId = ""; private byte[] verifyToken; - public ProtocolLibLoginSource(Player player, Random random, PublicKey publicKey) { + public ProtocolLibLoginSource(Player player, Random random, PublicKey serverPublicKey, ClientPublicKey clientKey) { this.player = player; this.random = random; - this.publicKey = publicKey; + this.publicKey = serverPublicKey; + this.clientKey = clientKey; } @Override - public void enableOnlinemode() throws InvocationTargetException { + public void enableOnlinemode() { verifyToken = EncryptionUtil.generateVerifyToken(random); /* @@ -88,7 +91,7 @@ class ProtocolLibLoginSource implements LoginSource { } @Override - public void kick(String message) throws InvocationTargetException { + public void kick(String message) { ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); PacketContainer kickPacket = new PacketContainer(DISCONNECT); @@ -109,6 +112,10 @@ class ProtocolLibLoginSource implements LoginSource { return player.getAddress(); } + public ClientPublicKey getClientKey() { + return clientKey; + } + public String getServerId() { return serverId; } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SkinApplyListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SkinApplyListener.java index 7d43835d..3d4a807b 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SkinApplyListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SkinApplyListener.java @@ -34,6 +34,9 @@ import com.comphenix.protocol.wrappers.WrappedSignedProperty; import com.github.games647.craftapi.model.skin.Textures; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; + +import java.lang.reflect.InvocationTargetException; + import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -41,8 +44,6 @@ import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerLoginEvent; import org.bukkit.event.player.PlayerLoginEvent.Result; -import java.lang.reflect.InvocationTargetException; - public class SkinApplyListener implements Listener { private static final Class GAME_PROFILE = MinecraftReflection.getGameProfileClass(); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/VerifyResponseTask.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/VerifyResponseTask.java index 54d525c2..c99a3cdf 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/VerifyResponseTask.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/VerifyResponseTask.java @@ -28,26 +28,27 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; +import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory; +import com.comphenix.protocol.reflect.EquivalentConverter; import com.comphenix.protocol.reflect.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; +import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedGameProfile; +import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; import com.github.games647.craftapi.model.auth.Verification; import com.github.games647.craftapi.model.skin.SkinProperty; -import com.github.games647.craftapi.resolver.AbstractResolver; import com.github.games647.craftapi.resolver.MojangResolver; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.net.*; -import java.nio.charset.StandardCharsets; +import java.net.InetAddress; +import java.net.InetSocketAddress; import java.security.GeneralSecurityException; import java.security.Key; import java.security.KeyPair; @@ -123,7 +124,7 @@ public class VerifyResponseTask implements Runnable { } try { - if (!checkVerifyToken(session) || !enableEncryption(loginKey)) { + if (!enableEncryption(loginKey)) { return; } } catch (Exception ex) { @@ -168,7 +169,7 @@ public class VerifyResponseTask implements Runnable { session.setVerified(true); setPremiumUUID(session.getUuid()); - receiveFakeStartPacket(realUsername); + receiveFakeStartPacket(realUsername, session.getClientPublicKey()); } private void setPremiumUUID(UUID premiumUUID) { @@ -183,23 +184,6 @@ public class VerifyResponseTask implements Runnable { } } - private boolean checkVerifyToken(BukkitLoginSession session) throws GeneralSecurityException { - byte[] requestVerify = session.getVerifyToken(); - //encrypted verify token - byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1); - - //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182 - if (!Arrays.equals(requestVerify, EncryptionUtil.decrypt(serverKey.getPrivate(), responseVerify))) { - //check if the verify-token are equal to the server sent one - disconnect("invalid-verify-token", - "GameProfile {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}", - session.getRequestUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify); - return false; - } - - return true; - } - //try to get the networkManager from ProtocolLib private Object getNetworkManager() throws IllegalAccessException, ClassNotFoundException { Object injectorContainer = TemporaryPlayerFactory.getInjectorFromPlayer(player); @@ -262,33 +246,32 @@ public class VerifyResponseTask implements Runnable { private void kickPlayer(String reason) { PacketContainer kickPacket = new PacketContainer(DISCONNECT); kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason)); - try { - //send kick packet at login state - //the normal event.getPlayer.kickPlayer(String) method does only work at play state - ProtocolLibrary.getProtocolManager().sendServerPacket(player, kickPacket); - //tell the server that we want to close the connection - player.kickPlayer("Disconnect"); - } catch (InvocationTargetException ex) { - plugin.getLog().error("Error sending kick packet for: {}", player, ex); - } + //send kick packet at login state + //the normal event.getPlayer.kickPlayer(String) method does only work at play state + ProtocolLibrary.getProtocolManager().sendServerPacket(player, kickPacket); + //tell the server that we want to close the connection + player.kickPlayer("Disconnect"); } //fake a new login packet in order to let the server handle all the other stuff - private void receiveFakeStartPacket(String username) { + private void receiveFakeStartPacket(String username, ClientPublicKey clientKey) { //see StartPacketListener for packet information PacketContainer startPacket = new PacketContainer(START); - //uuid is ignored by the packet definition - WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username); - startPacket.getGameProfiles().write(0, fakeProfile); - try { - //we don't want to handle our own packets so ignore filters - startPacket.setMeta(ProtocolLibListener.SOURCE_META_KEY, plugin.getName()); - ProtocolLibrary.getProtocolManager().recieveClientPacket(player, startPacket, true); - } catch (InvocationTargetException | IllegalAccessException ex) { - plugin.getLog().warn("Failed to fake a new start packet for: {}", username, ex); - //cancel the event in order to prevent the server receiving an invalid packet - kickPlayer(plugin.getCore().getMessage("error-kick")); + if (MinecraftVersion.atOrAbove(new MinecraftVersion(1, 19, 0))) { + startPacket.getStrings().write(0, username); + + EquivalentConverter converter = BukkitConverters.getWrappedPublicKeyDataConverter(); + var key = new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), sharedSecret); + startPacket.getOptionals(converter).write(0, Optional.ofNullable(key)); + } else { + //uuid is ignored by the packet definition + WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username); + startPacket.getGameProfiles().write(0, fakeProfile); } + + //we don't want to handle our own packets so ignore filters + startPacket.setMeta(ProtocolLibListener.SOURCE_META_KEY, plugin.getName()); + ProtocolLibrary.getProtocolManager().receiveClientPacket(player, startPacket, true); } } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/packet/ClientPublicKey.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/packet/ClientPublicKey.java new file mode 100644 index 00000000..e375e294 --- /dev/null +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/packet/ClientPublicKey.java @@ -0,0 +1,36 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2022 games647 and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.bukkit.listener.protocollib.packet; + +import java.security.PublicKey; +import java.time.Instant; + +public record ClientPublicKey(Instant expiry, PublicKey key, byte[] signature) { + + public boolean isExpired(Instant verifyTimestamp) { + return !verifyTimestamp.isBefore(expiry); + } +} diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/task/FloodgateAuthTask.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/task/FloodgateAuthTask.java index acb35976..ef9fed0a 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/task/FloodgateAuthTask.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/task/FloodgateAuthTask.java @@ -25,6 +25,11 @@ */ package com.github.games647.fastlogin.bukkit.task; +import com.github.games647.fastlogin.bukkit.BukkitLoginSession; +import com.github.games647.fastlogin.bukkit.FastLoginBukkit; +import com.github.games647.fastlogin.core.shared.FastLoginCore; +import com.github.games647.fastlogin.core.shared.FloodgateManagement; + import java.net.InetSocketAddress; import java.util.UUID; @@ -33,11 +38,6 @@ import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.geysermc.floodgate.api.player.FloodgatePlayer; -import com.github.games647.fastlogin.bukkit.BukkitLoginSession; -import com.github.games647.fastlogin.bukkit.FastLoginBukkit; -import com.github.games647.fastlogin.core.shared.FastLoginCore; -import com.github.games647.fastlogin.core.shared.FloodgateManagement; - public class FloodgateAuthTask extends FloodgateManagement { public FloodgateAuthTask(FastLoginCore core, Player player, FloodgatePlayer floodgatePlayer) { diff --git a/bukkit/src/main/resources/yggdrasil_session_pubkey.der b/bukkit/src/main/resources/yggdrasil_session_pubkey.der new file mode 100644 index 00000000..9c79a3aa Binary files /dev/null and b/bukkit/src/main/resources/yggdrasil_session_pubkey.der differ diff --git a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/Base64Adapter.java b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/Base64Adapter.java new file mode 100644 index 00000000..c2a2d1a4 --- /dev/null +++ b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/Base64Adapter.java @@ -0,0 +1,48 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2022 games647 and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.bukkit.listener.protocollib; + +import com.google.gson.TypeAdapter; +import com.google.gson.stream.JsonReader; +import com.google.gson.stream.JsonWriter; + +import java.io.IOException; +import java.util.Base64; + +public class Base64Adapter extends TypeAdapter { + + @Override + public void write(JsonWriter out, byte[] value) throws IOException { + var encoded = Base64.getEncoder().encodeToString(value); + out.value(encoded); + } + + @Override + public byte[] read(JsonReader in) throws IOException { + String encoded = in.nextString(); + return Base64.getDecoder().decode(encoded); + } +} diff --git a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtilTest.java b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtilTest.java index 7a097f1d..3a8adbfb 100644 --- a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtilTest.java +++ b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtilTest.java @@ -25,22 +25,273 @@ */ package com.github.games647.fastlogin.bukkit.listener.protocollib; -import java.security.SecureRandom; +import com.github.games647.fastlogin.bukkit.listener.protocollib.SignatureTestData.SignatureData; +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; +import com.google.common.hash.Hashing; + +import java.math.BigInteger; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; +import java.security.SignatureException; +import java.security.interfaces.RSAPublicKey; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.concurrent.ThreadLocalRandom; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; + public class EncryptionUtilTest { @Test public void testVerifyToken() { - SecureRandom random = new SecureRandom(); + var random = ThreadLocalRandom.current(); byte[] token = EncryptionUtil.generateVerifyToken(random); assertThat(token, notNullValue()); assertThat(token.length, is(4)); } + + @Test + public void testServerKey() { + KeyPair keyPair = EncryptionUtil.generateKeyPair(); + + Key privateKey = keyPair.getPrivate(); + assertThat(privateKey.getAlgorithm(), is("RSA")); + + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + assertThat(publicKey.getAlgorithm(), is("RSA")); + + // clients accept larger values than the standard vanilla server, but we shouldn't crash them + assertTrue(publicKey.getModulus().bitLength() >= 1024); + assertTrue(publicKey.getModulus().bitLength() < 8192); + } + + @Test + public void testExpiredClientKey() throws Exception { + var clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json"); + + // Client expires at the exact second mentioned, so use it for verification + var expiredTimestamp = clientKey.expiry(); + assertThat(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp), is(false)); + } + + @Test + public void testInvalidChangedExpiration() throws Exception { + // expiration date changed should make the signature invalid + // expiration should still be valid + var clientKey = ResourceLoader.loadClientKey("client_keys/invalid_wrong_expiration.json"); + Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS); + + assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); + } + + @Test + public void testInvalidChangedKey() throws Exception { + // changed public key no longer corresponding to the signature + var clientKey = ResourceLoader.loadClientKey("client_keys/invalid_wrong_key.json"); + Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS); + + assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); + } + + @Test + public void testInvalidChangedSignature() throws Exception { + // signature modified no longer corresponding to key and expiration date + var clientKey = ResourceLoader.loadClientKey("client_keys/invalid_wrong_signature.json"); + Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS); + + assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); + } + + @Test + public void testValidClientKey() throws Exception { + var clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json"); + var verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS); + + assertThat(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp), is(true)); + } + + @Test + public void testDecryptSharedSecret() throws Exception { + KeyPair serverPair = EncryptionUtil.generateKeyPair(); + var serverPK = serverPair.getPublic(); + + SecretKey secretKey = generateSharedKey(); + byte[] encryptedSecret = encrypt(serverPK, secretKey.getEncoded()); + + SecretKey decryptSharedKey = EncryptionUtil.decryptSharedKey(serverPair.getPrivate(), encryptedSecret); + assertThat(decryptSharedKey, is(secretKey)); + } + + private static byte[] encrypt(PublicKey receiverKey, byte... message) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, + IllegalBlockSizeException, BadPaddingException { + var encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm()); + encryptCipher.init(Cipher.ENCRYPT_MODE, receiverKey); + return encryptCipher.doFinal(message); + } + + private static SecretKeySpec generateSharedKey() { + // according to wiki.vg 16 bytes long + byte[] sharedKey = new byte[16]; + ThreadLocalRandom.current().nextBytes(sharedKey); + // shared key is to be used for the AES/CFB8 stream cipher to encrypt the traffic + // therefore the encryption/decryption has to be AES + return new SecretKeySpec(sharedKey, "AES"); + } + + @Test + public void testServerIdHash() throws Exception { + var serverId = ""; + var sharedSecret = generateSharedKey(); + var serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key(); + + String sessionHash = getServerHash(serverId, sharedSecret, serverPK); + assertThat(EncryptionUtil.getServerIdHashString(serverId, sharedSecret, serverPK), is(sessionHash)); + } + + private static String getServerHash(CharSequence serverId, SecretKey sharedSecret, PublicKey serverPK) { + // https://wiki.vg/Protocol_Encryption#Client + // sha1 := Sha1() + // sha1.update(ASCII encoding of the server id string from Encryption Request) + // sha1.update(shared secret) + // sha1.update(server's encoded public key from Encryption Request) + // hash := sha1.hexdigest() # String of hex characters + @SuppressWarnings("deprecation") + var hasher = Hashing.sha1().newHasher(); + hasher.putString(serverId, StandardCharsets.US_ASCII); + hasher.putBytes(sharedSecret.getEncoded()); + hasher.putBytes(serverPK.getEncoded()); + // It works by treating the sha1 output bytes as one large integer in two's complement and then printing the + // integer in base 16, placing a minus sign if the interpreted number is negative. + // reference: https://github.com/SpigotMC/BungeeCord/blob/ff5727c5ef9c0b56ad35f9816ae6bd660b622cf0/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java#L456 + return new BigInteger(hasher.hash().asBytes()).toString(16); + } + + @Test + public void testServerIdHashWrongSecret() throws Exception { + var serverId = ""; + var sharedSecret = generateSharedKey(); + var serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key(); + + String sessionHash = getServerHash(serverId, sharedSecret, serverPK); + assertThat(EncryptionUtil.getServerIdHashString("", generateSharedKey(), serverPK), not(sessionHash)); + } + + @Test + public void testServerIdHashWrongServerKey() { + var serverId = ""; + var sharedSecret = generateSharedKey(); + var serverPK = EncryptionUtil.generateKeyPair().getPublic(); + + String sessionHash = getServerHash(serverId, sharedSecret, serverPK); + var wrongPK = EncryptionUtil.generateKeyPair().getPublic(); + assertThat(EncryptionUtil.getServerIdHashString("", sharedSecret, wrongPK), not(sessionHash)); + } + + @Test + public void testValidSignedNonce() throws Exception { + ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json"); + SignatureTestData testData = SignatureTestData.fromResource("signature/valid_signature.json"); + assertThat(verifySignedNonce(testData, clientKey), is(true)); + } + + @Test + public void testIncorrectNonce() throws Exception { + ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json"); + SignatureTestData testData = SignatureTestData.fromResource("signature/incorrect_nonce.json"); + assertThat(verifySignedNonce(testData, clientKey), is(false)); + } + + @Test + public void testIncorrectSalt() throws Exception { + // client generated + ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json"); + SignatureTestData testData = SignatureTestData.fromResource("signature/incorrect_salt.json"); + assertThat(verifySignedNonce(testData, clientKey), is(false)); + } + + @Test + public void testIncorrectSignature() throws Exception { + // client generated + ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json"); + SignatureTestData testData = SignatureTestData.fromResource("signature/incorrect_signature.json"); + assertThat(verifySignedNonce(testData, clientKey), is(false)); + } + + @Test + public void testWrongPublicKeySigned() throws Exception { + // load a different public key + ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/invalid_wrong_key.json"); + SignatureTestData testData = SignatureTestData.fromResource("signature/valid_signature.json"); + assertThat(verifySignedNonce(testData, clientKey), is(false)); + } + + private static boolean verifySignedNonce(SignatureTestData testData, ClientPublicKey clientKey) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + PublicKey clientPublicKey = clientKey.key(); + + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + return EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()); + } + + @Test + public void testNonce() throws Exception { + byte[] expected = {1, 2, 3, 4}; + var serverKey = EncryptionUtil.generateKeyPair(); + var encryptedNonce = encrypt(serverKey.getPublic(), expected); + + assertThat(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce), is(true)); + } + + @Test + public void testNonceIncorrect() throws Exception { + byte[] expected = {1, 2, 3, 4}; + var serverKey = EncryptionUtil.generateKeyPair(); + + // flipped first character + var encryptedNonce = encrypt(serverKey.getPublic(), new byte[]{0, 2, 3 , 4}); + + assertThat(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce), is(false)); + } + + @Test(expected = GeneralSecurityException.class) + public void testNonceFailedDecryption() throws Exception { + byte[] expected = {1, 2, 3, 4}; + var serverKey = EncryptionUtil.generateKeyPair(); + // generate a new keypair that iss different + var encryptedNonce = encrypt(EncryptionUtil.generateKeyPair().getPublic(), expected); + + EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce); + } + + @Test(expected = GeneralSecurityException.class) + public void testNonceIncorrectEmpty() throws Exception { + byte[] expected = {1, 2, 3, 4}; + var serverKey = EncryptionUtil.generateKeyPair(); + byte[] encryptedNonce = {}; + + assertThat(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce), is(false)); + } } diff --git a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ResourceLoader.java b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ResourceLoader.java new file mode 100644 index 00000000..4361fe9b --- /dev/null +++ b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ResourceLoader.java @@ -0,0 +1,96 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2022 games647 and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.bukkit.listener.protocollib; + +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; +import com.google.common.io.Resources; +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; +import java.util.Base64; + +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; + +public class ResourceLoader { + + public static RSAPrivateKey parsePrivateKey(String keySpec) + throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + try ( + Reader reader = new StringReader(keySpec); + PemReader pemReader = new PemReader(reader) + ) { + PemObject pemObject = pemReader.readPemObject(); + byte[] content = pemObject.getContent(); + var privateKeySpec = new PKCS8EncodedKeySpec(content); + + var factory = KeyFactory.getInstance("RSA"); + return (RSAPrivateKey) factory.generatePrivate(privateKeySpec); + } + } + + protected static ClientPublicKey loadClientKey(String path) + throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { + var keyUrl = Resources.getResource(path); + + var lines = Resources.toString(keyUrl, StandardCharsets.US_ASCII); + var object = new Gson().fromJson(lines, JsonObject.class); + + Instant expires = Instant.parse(object.getAsJsonPrimitive("expires_at").getAsString()); + String key = object.getAsJsonPrimitive("key").getAsString(); + RSAPublicKey publicKey = parsePublicKey(key); + + byte[] signature = Base64.getDecoder().decode(object.getAsJsonPrimitive("signature").getAsString()); + return new ClientPublicKey(expires, publicKey, signature); + } + + private static RSAPublicKey parsePublicKey(String keySpec) + throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { + try ( + Reader reader = new StringReader(keySpec); + PemReader pemReader = new PemReader(reader) + ) { + PemObject pemObject = pemReader.readPemObject(); + byte[] content = pemObject.getContent(); + var pubKeySpec = new X509EncodedKeySpec(content); + + var factory = KeyFactory.getInstance("RSA"); + return (RSAPublicKey) factory.generatePublic(pubKeySpec); + } + } +} diff --git a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SignatureTestData.java b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SignatureTestData.java new file mode 100644 index 00000000..ee62e242 --- /dev/null +++ b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SignatureTestData.java @@ -0,0 +1,72 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2022 games647 and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.bukkit.listener.protocollib; + +import com.google.common.io.Resources; +import com.google.gson.Gson; +import com.google.gson.annotations.JsonAdapter; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; + +public class SignatureTestData { + + public static SignatureTestData fromResource(String resourceName) throws IOException { + var keyUrl = Resources.getResource(resourceName); + var encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII); + + return new Gson().fromJson(encodedSignature, SignatureTestData.class); + } + + @JsonAdapter(Base64Adapter.class) + private byte[] nonce; + + private SignatureData signature; + + public byte[] getNonce() { + return nonce; + } + + public SignatureData getSignature() { + return signature; + } + + public static class SignatureData { + + private long salt; + + @JsonAdapter(Base64Adapter.class) + private byte[] signature; + + public long getSalt() { + return salt; + } + + public byte[] getSignature() { + return signature; + } + } +} diff --git a/bukkit/src/test/java/integration/README.md b/bukkit/src/test/java/integration/README.md new file mode 100644 index 00000000..d4efc537 --- /dev/null +++ b/bukkit/src/test/java/integration/README.md @@ -0,0 +1,53 @@ +# Integration tests for authentication + +## Description + +Projects require integration tests in order to check against errors that could only occur if connected to other +components. However, they are heavier in terms of performance and require a more complex setup. Unit tests often make +use of fake, mock, stubs, etc. implementations to test the unit in isolation and thus could hide issues across +boundaries of a unit. Nevertheless, both are not replacement for each other. + +## Usage in this project + +The authentication system is a core component, so it requires some kind of testing. Here we are going to +spin up a Spigot server and test with the supported authentication schemes against the implementation of MCProtocolLib. + +### Goals + +* OS platform independent +* Reproducible, but not fixed to a specific image hash + * This is a dev container, so fixing it to feature/major version is enough instead of a version fixed by hash +* Improve container spin up + * E.g. Remove/Reduce world generation + +### Note on automation + +The simplest solution it to use the official Mojang session and authentication servers. However, this would require +a spare Minecraft account. Mocking the auth servers would be a solution to avoid this. + +## Related + +Interest blog article about integration tests and why they are necessary. +https://software.rajivprab.com/2019/04/28/rethinking-software-testing-perspectives-from-the-world-of-hardware/ + +## Issues + +### Slow startup + +Tried a lot of optimizations like only loading a single world without the nether or the end. However, there the startup +is still slow. If you have any ideas on how to tune the startup parameters of the Minecraft server or the JVM +itself to reduce the startup time, please suggest it. + +### Checkpoint + +An idea to optimize the time is to use CRIU (checkpoint and restore). So to save the process at a certain stage and +restore all data multiple times. This could cause a lot of issues like open files have to be present. However, the +impact is significant and since it runs inside the container all files, pids (pid=1) should be matching. Potential +checkpoint locations are: + +* Direct before loading the plugins + * Likely before binding the port to prevent issues +* After loading the libraries + +Nevertheless, the current state requires to run it with root and the Java support is currently still in progress. + diff --git a/bukkit/src/test/resources/client_keys/README.md b/bukkit/src/test/resources/client_keys/README.md new file mode 100644 index 00000000..78882233 --- /dev/null +++ b/bukkit/src/test/resources/client_keys/README.md @@ -0,0 +1,27 @@ +# About + +This contains test resources for the unit tests. The files are extracted from the Minecraft directory with slight +modifications. The files are found in `$MINECRAFT_HOME$/profilekeys/`, where `$MINECRAFT_HOME$` represents the +OS-dependent minecraft folder. + +**Notable the files in this folder do not contain the private key information. It should be explicitly +stripped before including it.** + +## Minecraft folder + +* Windows: `%appdata%\.minecraft` +* Linux: `/home/username/.minecraft` +* Mac: `~/Library/Application Support/minecraft` + +## Directory structure + +* `valid_public_key.json`: Extracted from the actual file +* `invalid_wrong_expiration.json`: Changed the expiration date +* `invalid_wrong_key.json`: Modified public key while keeping the RSA structure valid +* `invalid_wrong_signature.json`: Changed a character in the public key signature + +## File content + +* `expires_at`: Expiration date +* `key`: Public key from the original file out of `public_key.key` +* `signature`: Mojang signed signature of this public key diff --git a/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json b/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json new file mode 100644 index 00000000..ea509fec --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-20T07:31:47.318722344Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApd3ZxDhcRWWru1XEBke6uYqmbnS2Oxyk\nOMj+QDKrkwUqhVJYciyXGsMx46Mgr/KIoGCcokP5OtIxc6+69/ZLqJ9PvM81kLIxAqyvfBMKMGjP\n376LgxTF1FeDpbe5zXaNRxfmnvQhS5YTLbzgk36qWVjqxJMG4VLVmh7RV5zWsb7XlckZb2zRHM2Y\nMHbEC+ggX+l6zQyfG1KK0MH5k+O6b0xD0rv1wm24sLOesTXH6RZG8cNE3ofdnavxjFodTOnra6w1\naiVcoUTdEPSS86wQwq9j0YCcAKOwMXsqbk9NhpujrdyJ94dev+ELwkNS7P0pPrcfiyFTQeJCZTXz\nJB36MwIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "lfRXK4zL213wBKg760eiPV7yvnLZ6a6v9Iohmw78yxIzqXO3tfrC5Z+P2LGiO1BdI4xckx8yz4ktn82zX97+r2zktBw0As7g71H/FjInpoZ76j3gMUaiFNrQJ0vKCCI7xsjonemroWAVDCAqlvdyqwUu/Fnz85+WoR2kCQ721vwy6IjWA3xhq8XrWjkI/AlBmoS/kVqnvjjjc9vocdddJXbUYzCse/hWWIbsFeBXyiGCd3v7apgtXwQfM++tt87fq7444zQskiYb14oQP8/uNwqZWQ9jAs00i1BZ0MNM6+TZYGHOfS6rbHZ1bcX34VZdcCwpapK/Z2HBRIgDN4QOcgJkyq1GcjvlM2wjfhN8gXTsmbF9Ee+5Y6a4ONRkxRZK2sT8oAXdm0OlTEGB0P0+WRRFOQ/PnRqbI7lvANao2METT2EUHHrtqFMe53kqCHdzy5qyuHxdCEa6l/gSR08fybx9DdRRmhOlhSPGxhgwqyi1fEMrN4CsSKNrv5u+sMqhspA05b3DQJeLDX+UV5ujRHwm0A49NF+h1ZYlrcefz5IMUUisOOw6HiLc/YGLD2jHwSePGdfMwMnrIxbxjCta7/7A91aaN7eYm16KW9erCOwAfJmBSQC6Pbmg5f7rd7rAKVOPxglq7nayXmd+BK53Mal5tltMSgd/0iY6SEtGSEU=" +} diff --git a/bukkit/src/test/resources/client_keys/invalid_wrong_key.json b/bukkit/src/test/resources/client_keys/invalid_wrong_key.json new file mode 100644 index 00000000..98ea33f5 --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_key.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-20T08:31:47.318722344Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU3I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "lfRXK4zL213wBKg760eiPV7yvnLZ6a6v9Iohmw78yxIzqXO3tfrC5Z+P2LGiO1BdI4xckx8yz4ktn82zX97+r2zktBw0As7g71H/FjInpoZ76j3gMUaiFNrQJ0vKCCI7xsjonemroWAVDCAqlvdyqwUu/Fnz85+WoR2kCQ721vwy6IjWA3xhq8XrWjkI/AlBmoS/kVqnvjjjc9vocdddJXbUYzCse/hWWIbsFeBXyiGCd3v7apgtXwQfM++tt87fq7444zQskiYb14oQP8/uNwqZWQ9jAs00i1BZ0MNM6+TZYGHOfS6rbHZ1bcX34VZdcCwpapK/Z2HBRIgDN4QOcgJkyq1GcjvlM2wjfhN8gXTsmbF9Ee+5Y6a4ONRkxRZK2sT8oAXdm0OlTEGB0P0+WRRFOQ/PnRqbI7lvANao2METT2EUHHrtqFMe53kqCHdzy5qyuHxdCEa6l/gSR08fybx9DdRRmhOlhSPGxhgwqyi1fEMrN4CsSKNrv5u+sMqhspA05b3DQJeLDX+UV5ujRHwm0A49NF+h1ZYlrcefz5IMUUisOOw6HiLc/YGLD2jHwSePGdfMwMnrIxbxjCta7/7A91aaN7eYm16KW9erCOwAfJmBSQC6Pbmg5f7rd7rAKVOPxglq7nayXmd+BK53Mal5tltMSgd/0iY6SEtGSEU=" +} diff --git a/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json b/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json new file mode 100644 index 00000000..2b80c2c9 --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-20T08:31:47.318722344Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApd3ZxDhcRWWru1XEBke6uYqmbnS2Oxyk\nOMj+QDKrkwUqhVJYciyXGsMx46Mgr/KIoGCcokP5OtIxc6+69/ZLqJ9PvM81kLIxAqyvfBMKMGjP\n376LgxTF1FeDpbe5zXaNRxfmnvQhS5YTLbzgk36qWVjqxJMG4VLVmh7RV5zWsb7XlckZb2zRHM2Y\nMHbEC+ggX+l6zQyfG1KK0MH5k+O6b0xD0rv1wm24sLOesTXH6RZG8cNE3ofdnavxjFodTOnra6w1\naiVcoUTdEPSS86wQwq9j0YCcAKOwMXsqbk9NhpujrdyJ94dev+ELwkNS7P0pPrcfiyFTQeJCZTXz\nJB36MwIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "lfRxK4zL213wBKg760eiPV7yvnLZ6a6v9Iohmw78yxIzqXO3tfrC5Z+P2LGiO1BdI4xckx8yz4ktn82zX97+r2zktBw0As7g71H/FjInpoZ76j3gMUaiFNrQJ0vKCCI7xsjonemroWAVDCAqlvdyqwUu/Fnz85+WoR2kCQ721vwy6IjWA3xhq8XrWjkI/AlBmoS/kVqnvjjjc9vocdddJXbUYzCse/hWWIbsFeBXyiGCd3v7apgtXwQfM++tt87fq7444zQskiYb14oQP8/uNwqZWQ9jAs00i1BZ0MNM6+TZYGHOfS6rbHZ1bcX34VZdcCwpapK/Z2HBRIgDN4QOcgJkyq1GcjvlM2wjfhN8gXTsmbF9Ee+5Y6a4ONRkxRZK2sT8oAXdm0OlTEGB0P0+WRRFOQ/PnRqbI7lvANao2METT2EUHHrtqFMe53kqCHdzy5qyuHxdCEa6l/gSR08fybx9DdRRmhOlhSPGxhgwqyi1fEMrN4CsSKNrv5u+sMqhspA05b3DQJeLDX+UV5ujRHwm0A49NF+h1ZYlrcefz5IMUUisOOw6HiLc/YGLD2jHwSePGdfMwMnrIxbxjCta7/7A91aaN7eYm16KW9erCOwAfJmBSQC6Pbmg5f7rd7rAKVOPxglq7nayXmd+BK53Mal5tltMSgd/0iY6SEtGSEU=" +} diff --git a/bukkit/src/test/resources/client_keys/valid_public_key.json b/bukkit/src/test/resources/client_keys/valid_public_key.json new file mode 100644 index 00000000..8943e87b --- /dev/null +++ b/bukkit/src/test/resources/client_keys/valid_public_key.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-20T08:31:47.318722344Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEApd3ZxDhcRWWru1XEBke6uYqmbnS2Oxyk\nOMj+QDKrkwUqhVJYciyXGsMx46Mgr/KIoGCcokP5OtIxc6+69/ZLqJ9PvM81kLIxAqyvfBMKMGjP\n376LgxTF1FeDpbe5zXaNRxfmnvQhS5YTLbzgk36qWVjqxJMG4VLVmh7RV5zWsb7XlckZb2zRHM2Y\nMHbEC+ggX+l6zQyfG1KK0MH5k+O6b0xD0rv1wm24sLOesTXH6RZG8cNE3ofdnavxjFodTOnra6w1\naiVcoUTdEPSS86wQwq9j0YCcAKOwMXsqbk9NhpujrdyJ94dev+ELwkNS7P0pPrcfiyFTQeJCZTXz\nJB36MwIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "lfRXK4zL213wBKg760eiPV7yvnLZ6a6v9Iohmw78yxIzqXO3tfrC5Z+P2LGiO1BdI4xckx8yz4ktn82zX97+r2zktBw0As7g71H/FjInpoZ76j3gMUaiFNrQJ0vKCCI7xsjonemroWAVDCAqlvdyqwUu/Fnz85+WoR2kCQ721vwy6IjWA3xhq8XrWjkI/AlBmoS/kVqnvjjjc9vocdddJXbUYzCse/hWWIbsFeBXyiGCd3v7apgtXwQfM++tt87fq7444zQskiYb14oQP8/uNwqZWQ9jAs00i1BZ0MNM6+TZYGHOfS6rbHZ1bcX34VZdcCwpapK/Z2HBRIgDN4QOcgJkyq1GcjvlM2wjfhN8gXTsmbF9Ee+5Y6a4ONRkxRZK2sT8oAXdm0OlTEGB0P0+WRRFOQ/PnRqbI7lvANao2METT2EUHHrtqFMe53kqCHdzy5qyuHxdCEa6l/gSR08fybx9DdRRmhOlhSPGxhgwqyi1fEMrN4CsSKNrv5u+sMqhspA05b3DQJeLDX+UV5ujRHwm0A49NF+h1ZYlrcefz5IMUUisOOw6HiLc/YGLD2jHwSePGdfMwMnrIxbxjCta7/7A91aaN7eYm16KW9erCOwAfJmBSQC6Pbmg5f7rd7rAKVOPxglq7nayXmd+BK53Mal5tltMSgd/0iY6SEtGSEU=" +} diff --git a/bukkit/src/test/resources/signature/README.md b/bukkit/src/test/resources/signature/README.md new file mode 100644 index 00000000..401142fa --- /dev/null +++ b/bukkit/src/test/resources/signature/README.md @@ -0,0 +1,16 @@ +# About + +This contains test resources for the unit tests. Files in this folder include pre-made cryptographic signatures. + +## Directory structure + +* `valid_signature.json`: Extracted using packet extract from an actual authentication +* `incorrect_nonce.json`: Different nonce token simulating that the server expected a different token than signed +* `incorrect_salt.json`: Salt sent is different to the content signed by the signature (changed salt field) +* `incorrect_signature.json`: Changed signature + +## File content + +* `nonce`: Server generated nonce token +* `salt`: Client generated random token that will be signed +* `signature`: Nonce and salt signed using the client key from `valid_public_key.json` diff --git a/bukkit/src/test/resources/signature/incorrect_nonce.json b/bukkit/src/test/resources/signature/incorrect_nonce.json new file mode 100644 index 00000000..b88c0f5d --- /dev/null +++ b/bukkit/src/test/resources/signature/incorrect_nonce.json @@ -0,0 +1,7 @@ +{ + "nonce": "galNig\u003d\u003d", + "signature": { + "signature": "JlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d", + "salt": -2985008842905108412 + } +} diff --git a/bukkit/src/test/resources/signature/incorrect_salt.json b/bukkit/src/test/resources/signature/incorrect_salt.json new file mode 100644 index 00000000..8edffb62 --- /dev/null +++ b/bukkit/src/test/resources/signature/incorrect_salt.json @@ -0,0 +1,7 @@ +{ + "nonce": "GalNig\u003d\u003d", + "signature": { + "signature": "JlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d", + "salt": -1985008842905108412 + } +} diff --git a/bukkit/src/test/resources/signature/incorrect_signature.json b/bukkit/src/test/resources/signature/incorrect_signature.json new file mode 100644 index 00000000..ba6bac53 --- /dev/null +++ b/bukkit/src/test/resources/signature/incorrect_signature.json @@ -0,0 +1,7 @@ +{ + "nonce": "GalNig\u003d\u003d", + "signature": { + "signature": "jlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d", + "salt": -2985008842905108412 + } +} diff --git a/bukkit/src/test/resources/signature/valid_signature.json b/bukkit/src/test/resources/signature/valid_signature.json new file mode 100644 index 00000000..7f4f4ad5 --- /dev/null +++ b/bukkit/src/test/resources/signature/valid_signature.json @@ -0,0 +1,7 @@ +{ + "nonce": "GalNig\u003d\u003d", + "signature": { + "signature": "JlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d", + "salt": -2985008842905108412 + } +} diff --git a/bungee/pom.xml b/bungee/pom.xml index ee0d6d89..b7645231 100644 --- a/bungee/pom.xml +++ b/bungee/pom.xml @@ -32,7 +32,7 @@ com.github.games647 fastlogin - 1.11-SNAPSHOT + 1.12-SNAPSHOT ../pom.xml @@ -137,6 +137,42 @@ org.slf4j slf4j-api + + io.netty + * + + + net.sf.jopt-simple + * + + + mysql + * + + + net.md-5 + bungeecord-log + + + net.md-5 + bungeecord-native + + + net.md-5 + bungeecord-query + + + net.md-5 + bungeecord-slf4j + + + org.apache.maven + * + + + org.apache.maven.resolver + * + @@ -192,7 +228,7 @@ de.xxschrandxx.bca BungeeCordAuthenticator - 0.0.2 + 0.0.3 provided diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/event/BungeeFastLoginAutoLoginEvent.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/event/BungeeFastLoginAutoLoginEvent.java index 365a1980..6503ff92 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/event/BungeeFastLoginAutoLoginEvent.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/event/BungeeFastLoginAutoLoginEvent.java @@ -28,6 +28,7 @@ package com.github.games647.fastlogin.bungee.event; import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.shared.LoginSession; import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent; + import net.md_5.bungee.api.plugin.Cancellable; import net.md_5.bungee.api.plugin.Event; diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/event/BungeeFastLoginPreLoginEvent.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/event/BungeeFastLoginPreLoginEvent.java index b3507633..2128caba 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/event/BungeeFastLoginPreLoginEvent.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/event/BungeeFastLoginPreLoginEvent.java @@ -28,6 +28,7 @@ package com.github.games647.fastlogin.bungee.event; import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.shared.LoginSource; import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent; + import net.md_5.bungee.api.plugin.Event; public class BungeeFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent { diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/hook/BungeeAuthHook.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/hook/BungeeAuthHook.java index e39645ce..fe04eff8 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/hook/BungeeAuthHook.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/hook/BungeeAuthHook.java @@ -34,11 +34,13 @@ import me.vik1395.BungeeAuthAPI.RequestHandler; import net.md_5.bungee.api.connection.ProxiedPlayer; /** - * GitHub: https://github.com/vik1395/BungeeAuth-Minecraft + * GitHub: + * ... * * Project page: * - * Spigot: https://www.spigotmc.org/resources/bungeeauth.493/ + * Spigot: + * ... */ public class BungeeAuthHook implements AuthPlugin { diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/hook/BungeeCordAuthenticatorBungeeHook.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/hook/BungeeCordAuthenticatorBungeeHook.java index 4430abce..98443a63 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/hook/BungeeCordAuthenticatorBungeeHook.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/hook/BungeeCordAuthenticatorBungeeHook.java @@ -38,11 +38,11 @@ import net.md_5.bungee.api.connection.ProxiedPlayer; /** * GitHub: - * https://github.com/xXSchrandXx/SpigotPlugins/tree/master/BungeeCordAuthenticator + * ... *

* Project page: *

- * Spigot: https://www.spigotmc.org/resources/bungeecordauthenticator.87669/ + * Spigot: ... */ public class BungeeCordAuthenticatorBungeeHook implements AuthPlugin { diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/task/AsyncToggleMessage.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/task/AsyncToggleMessage.java index 98c384c0..29658553 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/task/AsyncToggleMessage.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/task/AsyncToggleMessage.java @@ -29,8 +29,8 @@ import com.github.games647.fastlogin.bungee.FastLoginBungee; import com.github.games647.fastlogin.bungee.event.BungeeFastLoginPremiumToggleEvent; import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.shared.FastLoginCore; - import com.github.games647.fastlogin.core.shared.event.FastLoginPremiumToggleEvent.PremiumToggleReason; + import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.chat.TextComponent; diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/task/FloodgateAuthTask.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/task/FloodgateAuthTask.java index 72719cfc..51331984 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/task/FloodgateAuthTask.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/task/FloodgateAuthTask.java @@ -25,19 +25,19 @@ */ package com.github.games647.fastlogin.bungee.task; +import com.github.games647.fastlogin.bungee.BungeeLoginSession; +import com.github.games647.fastlogin.bungee.FastLoginBungee; +import com.github.games647.fastlogin.core.shared.FastLoginCore; +import com.github.games647.fastlogin.core.shared.FloodgateManagement; + import java.net.InetSocketAddress; import java.util.UUID; -import org.geysermc.floodgate.api.player.FloodgatePlayer; - import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.Server; -import com.github.games647.fastlogin.bungee.BungeeLoginSession; -import com.github.games647.fastlogin.bungee.FastLoginBungee; -import com.github.games647.fastlogin.core.shared.FastLoginCore; -import com.github.games647.fastlogin.core.shared.FloodgateManagement; +import org.geysermc.floodgate.api.player.FloodgatePlayer; public class FloodgateAuthTask extends FloodgateManagement { diff --git a/core/pom.xml b/core/pom.xml index 430b754f..8cd8469c 100644 --- a/core/pom.xml +++ b/core/pom.xml @@ -32,7 +32,7 @@ com.github.games647 fastlogin - 1.11-SNAPSHOT + 1.12-SNAPSHOT ../pom.xml @@ -52,9 +52,6 @@ codemc-repo https://repo.codemc.io/repository/maven-public/ - - false - @@ -73,7 +70,7 @@ com.zaxxer HikariCP - 4.0.3 + 5.0.1 @@ -95,7 +92,7 @@ net.md-5 bungeecord-config - 1.16-R0.4 + 1.19-R0.1-20220702.004052-16 * diff --git a/core/src/main/java/com/github/games647/fastlogin/core/AsyncScheduler.java b/core/src/main/java/com/github/games647/fastlogin/core/AsyncScheduler.java index d5ebe1a0..4c01bc19 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/AsyncScheduler.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/AsyncScheduler.java @@ -58,16 +58,18 @@ public class AsyncScheduler { } public CompletableFuture runAsync(Runnable task) { - return CompletableFuture.runAsync(() -> { - currentlyRunning.incrementAndGet(); - try { - task.run(); - } finally { - currentlyRunning.getAndDecrement(); - } - }, processingPool).exceptionally(error -> { + return CompletableFuture.runAsync(() -> process(task), processingPool).exceptionally(error -> { logger.warn("Error occurred on thread pool", error); return null; }); } + + private void process(Runnable task) { + currentlyRunning.incrementAndGet(); + try { + task.run(); + } finally { + currentlyRunning.getAndDecrement(); + } + } } diff --git a/core/src/main/java/com/github/games647/fastlogin/core/antibot/ProxyAgnosticMojangResolver.java b/core/src/main/java/com/github/games647/fastlogin/core/antibot/ProxyAgnosticMojangResolver.java index aabba993..ce10bb20 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/antibot/ProxyAgnosticMojangResolver.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/antibot/ProxyAgnosticMojangResolver.java @@ -43,7 +43,7 @@ import java.util.Optional; */ public class ProxyAgnosticMojangResolver extends MojangResolver { /** - * A formatting string containing an URL used to call the {@code hasJoined} method on mojang session servers. + * A formatting string containing a URL used to call the {@code hasJoined} method on mojang session servers. * * Formatting parameters: * 1. The username of the player in question diff --git a/core/src/main/java/com/github/games647/fastlogin/core/hooks/DefaultPasswordGenerator.java b/core/src/main/java/com/github/games647/fastlogin/core/hooks/DefaultPasswordGenerator.java index b6802a69..9caadef5 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/hooks/DefaultPasswordGenerator.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/hooks/DefaultPasswordGenerator.java @@ -26,7 +26,7 @@ package com.github.games647.fastlogin.core.hooks; import java.security.SecureRandom; -import java.util.Random; +import java.util.random.RandomGenerator; import java.util.stream.IntStream; public class DefaultPasswordGenerator

implements PasswordGenerator

{ @@ -35,7 +35,7 @@ public class DefaultPasswordGenerator

implements PasswordGenerator

{ private static final char[] PASSWORD_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" .toCharArray(); - private final Random random = new SecureRandom(); + private final RandomGenerator random = new SecureRandom(); @Override public String getRandomPassword(P player) { diff --git a/core/src/main/java/com/github/games647/fastlogin/core/hooks/bedrock/FloodgateService.java b/core/src/main/java/com/github/games647/fastlogin/core/hooks/bedrock/FloodgateService.java index 7e6a1c24..1b555f18 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/hooks/bedrock/FloodgateService.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/hooks/bedrock/FloodgateService.java @@ -54,14 +54,13 @@ public class FloodgateService extends BedrockService { *

  • autoLoginFloodgate *
  • autoRegisterFloodgate * - *

    * * @param key the key of the entry in config.yml * @return true if the entry's value is "true", "false", or "linked" */ public boolean isValidFloodgateConfigString(String key) { String value = core.getConfig().get(key).toString().toLowerCase(Locale.ENGLISH); - if (!value.equals("true") && !value.equals("linked") && !value.equals("false") && !value.equals("no-conflict")) { + if (!"true".equals(value) && !"linked".equals(value) && !"false".equals(value) && !"no-conflict".equals(value)) { core.getPlugin().getLog().error("Invalid value detected for {} in FastLogin/config.yml.", key); return false; } @@ -87,7 +86,7 @@ public class FloodgateService extends BedrockService { } else { core.getPlugin().getLog().info("Skipping name conflict checking for player {}", username); } - + //Floodgate users don't need Java specific checks return true; } @@ -98,7 +97,7 @@ public class FloodgateService extends BedrockService { * username can be found *
    * Falls back to non-prefixed name checks, if ProtocolLib is installed - * + * * @param prefixedUsername the name of the player with the prefix appended * @return FloodgatePlayer if found, null otherwise */ diff --git a/core/src/main/java/com/github/games647/fastlogin/core/shared/ForceLoginManagement.java b/core/src/main/java/com/github/games647/fastlogin/core/shared/ForceLoginManagement.java index c8c7bea8..873a8552 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/shared/ForceLoginManagement.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/shared/ForceLoginManagement.java @@ -25,10 +25,10 @@ */ package com.github.games647.fastlogin.core.shared; -import com.github.games647.fastlogin.core.storage.SQLStorage; import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.hooks.AuthPlugin; import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent; +import com.github.games647.fastlogin.core.storage.SQLStorage; public abstract class ForceLoginManagement

    > implements Runnable { diff --git a/core/src/main/java/com/github/games647/fastlogin/core/shared/LoginSession.java b/core/src/main/java/com/github/games647/fastlogin/core/shared/LoginSession.java index 33b5ff5c..3935ada4 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/shared/LoginSession.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/shared/LoginSession.java @@ -52,7 +52,7 @@ public abstract class LoginSession { return requestUsername; } - public String getUsername() { + public synchronized String getUsername() { return username; } diff --git a/core/src/main/java/com/github/games647/fastlogin/core/shared/PlatformPlugin.java b/core/src/main/java/com/github/games647/fastlogin/core/shared/PlatformPlugin.java index 4c436ed6..fc86c8ce 100644 --- a/core/src/main/java/com/github/games647/fastlogin/core/shared/PlatformPlugin.java +++ b/core/src/main/java/com/github/games647/fastlogin/core/shared/PlatformPlugin.java @@ -59,7 +59,7 @@ public interface PlatformPlugin { default ThreadFactory getThreadFactory() { return new ThreadFactoryBuilder() .setNameFormat(getName() + " Pool Thread #%1$d") - // Hikari create daemons by default. We could daemon threads for our own scheduler too + // Hikari create daemons by default. We could use daemon threads for our own scheduler too // because we safely shut down .setDaemon(true) .build(); diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 3a2ab7a9..1db567dd 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -272,6 +272,15 @@ autoRegisterFloodgate: false # Enabling this might lead to people gaining unauthorized access to other's accounts! floodgatePrefixWorkaround: false +# This option resembles the vanilla configuration option 'enforce-secure-profile' in the 'server.properties' file. +# It verifies if the incoming cryptographic key in the login request from the player is signed by Mojang. This key +# is necessary for servers where you or other in-game players want to verify that a chat message sent and signed by +# this player is not modified by any third-party. Modifications by your server would also invalidate the message. +# +# This feature is only relevant if you use the plugin in ProtocolLib mode and use 1.19+. +# This also the case if you don't have any proxies in use. +verifyClientKeys: false + # Database configuration # Recommended is the use of MariaDB (a better version of MySQL) diff --git a/core/src/main/resources/messages.yml b/core/src/main/resources/messages.yml index 33cadaad..e8c8bae5 100644 --- a/core/src/main/resources/messages.yml +++ b/core/src/main/resources/messages.yml @@ -80,7 +80,7 @@ error-kick: '&4Error occurred' # The server sends a verify-token within the premium authentication request. If this doesn't match on response, # it could be another client sending malicious packets -invalid-verify-token: '&4Invalid token' +invalid-verify-token: '&4Invalid nonce token. Please verify you are using the correct server address' # The client sent no request join server request to the mojang servers which would proof that it's owner of that # account. Only modified clients would do this. @@ -96,6 +96,9 @@ not-started: '&cServer is not fully started yet. Please retry' premium-warning: '&c&lWARNING: &6This command should&l only&6 be invoked if you are the owner of this paid Minecraft account Type &a/premium&6 again to confirm' +# Invalid client public key that will be used in the future to send authenticated chat messages from clients +invalid-public-key: '&cInvalid client public key. Please try to restart your game.' + # ========= Bungee/Waterfall only ================================ diff --git a/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java b/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java index 3f753cd4..cedb61b5 100644 --- a/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java +++ b/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java @@ -32,18 +32,16 @@ import java.util.concurrent.TimeUnit; import org.junit.Test; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertTrue; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; public class TickingRateLimiterTest { - private static final long THRESHOLD_MILLI = 10; - /** * Always expired */ @Test - public void allowExpire() throws InterruptedException { + public void allowExpire() { int size = 3; FakeTicker ticker = new FakeTicker(5_000_000L); @@ -51,17 +49,17 @@ public class TickingRateLimiterTest { // run twice the size to fill it first and then test it TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, 0); for (int i = 0; i < size; i++) { - assertTrue("Filling up", rateLimiter.tryAcquire()); + assertThat("Filling up", rateLimiter.tryAcquire(), is(true)); } for (int i = 0; i < size; i++) { ticker.add(Duration.ofSeconds(1)); - assertTrue("Should be expired", rateLimiter.tryAcquire()); + assertThat("Should be expired", rateLimiter.tryAcquire(), is(true)); } } @Test - public void allowExpireNegative() throws InterruptedException { + public void allowExpireNegative() { int size = 3; FakeTicker ticker = new FakeTicker(-5_000_000L); @@ -69,12 +67,12 @@ public class TickingRateLimiterTest { // run twice the size to fill it first and then test it TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, 0); for (int i = 0; i < size; i++) { - assertTrue("Filling up", rateLimiter.tryAcquire()); + assertThat("Filling up", rateLimiter.tryAcquire(), is(true)); } for (int i = 0; i < size; i++) { ticker.add(Duration.ofSeconds(1)); - assertTrue("Should be expired", rateLimiter.tryAcquire()); + assertThat("Should be expired", rateLimiter.tryAcquire(), is(true)); } } @@ -90,10 +88,10 @@ public class TickingRateLimiterTest { // fill the size TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, TimeUnit.SECONDS.toMillis(30)); for (int i = 0; i < size; i++) { - assertTrue("Filling up", rateLimiter.tryAcquire()); + assertThat("Filling up", rateLimiter.tryAcquire(), is(true)); } - assertFalse("Should be full and no entry should be expired", rateLimiter.tryAcquire()); + assertThat("Should be full and no entry should be expired", rateLimiter.tryAcquire(), is(false)); } /** @@ -108,51 +106,51 @@ public class TickingRateLimiterTest { // fill the size TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, size, TimeUnit.SECONDS.toMillis(30)); for (int i = 0; i < size; i++) { - assertTrue("Filling up", rateLimiter.tryAcquire()); + assertThat("Filling up", rateLimiter.tryAcquire(), is(true)); } - assertFalse("Should be full and no entry should be expired", rateLimiter.tryAcquire()); + assertThat("Should be full and no entry should be expired", rateLimiter.tryAcquire(), is(false)); } /** * Blocked attempts shouldn't replace existing ones. */ @Test - public void blockedNotAdded() throws InterruptedException { + public void blockedNotAdded() { FakeTicker ticker = new FakeTicker(5_000_000L); // fill the size - 100ms should be reasonable high TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, 1, 100); - assertTrue("Filling up", rateLimiter.tryAcquire()); + assertThat("Filling up", rateLimiter.tryAcquire(), is(true)); ticker.add(Duration.ofMillis(50)); // still is full - should fail - assertFalse("Expired too early", rateLimiter.tryAcquire()); + assertThat("Expired too early", rateLimiter.tryAcquire(), is(false)); // wait the remaining time and add a threshold, because ticker.add(Duration.ofMillis(50)); - assertTrue("Request not released", rateLimiter.tryAcquire()); + assertThat("Request not released", rateLimiter.tryAcquire(), is(true)); } /** * Blocked attempts shouldn't replace existing ones. */ @Test - public void blockedNotAddedNegative() throws InterruptedException { + public void blockedNotAddedNegative() { FakeTicker ticker = new FakeTicker(-5_000_000L); // fill the size - 100ms should be reasonable high TickingRateLimiter rateLimiter = new TickingRateLimiter(ticker, 1, 100); - assertTrue("Filling up", rateLimiter.tryAcquire()); + assertThat("Filling up", rateLimiter.tryAcquire(), is(true)); ticker.add(Duration.ofMillis(50)); // still is full - should fail - assertFalse("Expired too early", rateLimiter.tryAcquire()); + assertThat("Expired too early", rateLimiter.tryAcquire(), is(false)); // wait the remaining time and add a threshold, because ticker.add(Duration.ofMillis(50)); - assertTrue("Request not released", rateLimiter.tryAcquire()); + assertThat("Request not released", rateLimiter.tryAcquire(), is(true)); } } diff --git a/pom.xml b/pom.xml index 690b7a49..517a10ac 100644 --- a/pom.xml +++ b/pom.xml @@ -35,7 +35,7 @@ pom FastLogin - 1.11-SNAPSHOT + 1.12-SNAPSHOT https://www.spigotmc.org/resources/fastlogin.14153/ @@ -129,7 +129,18 @@ + src/main/resources + + yggdrasil_session_pubkey.der + + false + + + src/main/resources + + yggdrasil_session_pubkey.der + true diff --git a/velocity/pom.xml b/velocity/pom.xml index 4bd22364..4eb541c6 100644 --- a/velocity/pom.xml +++ b/velocity/pom.xml @@ -32,7 +32,7 @@ com.github.games647 fastlogin - 1.11-SNAPSHOT + 1.12-SNAPSHOT ../pom.xml @@ -113,7 +113,7 @@ velocity - https://nexus.velocitypowered.com/repository/maven-public/ + https://repo.papermc.io/repository/maven-public/ @@ -129,7 +129,7 @@ com.velocitypowered velocity-api - 3.1.0 + 3.1.1 provided @@ -137,7 +137,7 @@ org.mariadb.jdbc mariadb-java-client - 3.0.5 + 3.0.6 diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java index 33c8c311..d0dcfd17 100644 --- a/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/listener/ConnectListener.java @@ -45,9 +45,11 @@ import com.velocitypowered.api.proxy.InboundConnection; import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.GameProfile; +import com.velocitypowered.api.util.GameProfile.Property; import java.net.InetSocketAddress; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.UUID; import java.util.concurrent.TimeUnit; @@ -116,13 +118,14 @@ public class ConnectListener { } if (!plugin.getCore().getConfig().get("forwardSkin", true)) { - event.setGameProfile(event.getGameProfile().withProperties(removeSkin(event.getGameProfile().getProperties()))); + List skinFreeProp = removeSkin(event.getGameProfile().getProperties()); + event.setGameProfile(event.getGameProfile().withProperties(skinFreeProp)); } } } - private List removeSkin(List oldProperties) { - List newProperties = new ArrayList<>(oldProperties.size() - 1); + private List removeSkin(Collection oldProperties) { + List newProperties = new ArrayList<>(oldProperties.size()); for (GameProfile.Property property : oldProperties) { if (!"textures".equals(property.getName())) newProperties.add(property); diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java index 12444fca..8dc7d1c0 100644 --- a/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/AsyncToggleMessage.java @@ -32,6 +32,7 @@ import com.github.games647.fastlogin.velocity.FastLoginVelocity; import com.github.games647.fastlogin.velocity.event.VelocityFastLoginPremiumToggleEvent; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.proxy.Player; + import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; public class AsyncToggleMessage implements Runnable { diff --git a/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java index df5c4338..849f53d4 100644 --- a/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java +++ b/velocity/src/main/java/com/github/games647/fastlogin/velocity/task/ForceLoginTask.java @@ -48,8 +48,7 @@ public class ForceLoginTask private final RegisteredServer server; - //treat player as if they had a premium account, even when they don't - //used to do auto login for Floodgate aut + //treat player as if they had a premium account, even when they don't used to do auto login for Floodgate private final boolean forcedOnlineMode; public ForceLoginTask(FastLoginCore core,