From 87281be8cfb9759efd0b3b809c7cefada964b60c Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 9 Jun 2022 12:56:42 +0200 Subject: [PATCH 01/37] Fix start packet containing username in 1.19 --- .../listener/protocollib/VerifyResponseTask.java | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) 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..1b3ba0ae 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 @@ -32,6 +32,7 @@ import com.comphenix.protocol.injector.server.TemporaryPlayerFactory; 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.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.github.games647.craftapi.model.auth.Verification; @@ -278,9 +279,14 @@ public class VerifyResponseTask implements Runnable { //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); + if (MinecraftVersion.atOrAbove(new MinecraftVersion(1, 19, 0))) { + startPacket.getStrings().write(0, username); + } else { + //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()); From 219ebb1248ba70c71b83117a29fca2e2c12283b9 Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 9 Jun 2022 18:05:24 +0200 Subject: [PATCH 02/37] Bump version --- bukkit/pom.xml | 4 ++-- bungee/pom.xml | 2 +- core/pom.xml | 2 +- pom.xml | 2 +- velocity/pom.xml | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 91ca39ac..cfc0cdf5 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,7 +118,7 @@ dmulloy2-repo - https://repo.dmulloy2.net/nexus/repository/public/ + https://repo.dmulloy2.net/repository/public/ false diff --git a/bungee/pom.xml b/bungee/pom.xml index ee0d6d89..4de703cf 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 diff --git a/core/pom.xml b/core/pom.xml index 430b754f..119ea35c 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 diff --git a/pom.xml b/pom.xml index 690b7a49..e8a886a3 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/ diff --git a/velocity/pom.xml b/velocity/pom.xml index 4bd22364..a6cd8016 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 From d8a6e2a6fa9c9c482aead2a8514be2766d2a151e Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 10 Jun 2022 17:34:16 +0200 Subject: [PATCH 03/37] Update ProtocolLib to 5.0 diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 13e466e..2494a2b 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -186,7 +186,7 @@ com.comphenix.protocol ProtocolLib - 4.8.0 + 5.0.0-SNAPSHOT provided 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 5a2794d..2e60cd1 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,7 +28,7 @@ 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.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; @@ -37,18 +37,14 @@ import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedGameProfile; 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 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; @@ -263,15 +259,11 @@ 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 @@ -287,14 +279,8 @@ public class VerifyResponseTask implements Runnable { 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")); - } + //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); } } --- bukkit/pom.xml | 2 +- .../protocollib/VerifyResponseTask.java | 36 ++++++------------- 2 files changed, 12 insertions(+), 26 deletions(-) diff --git a/bukkit/pom.xml b/bukkit/pom.xml index cfc0cdf5..2e1ccc4f 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -186,7 +186,7 @@ com.comphenix.protocol ProtocolLib - 4.8.0 + 5.0.0-SNAPSHOT provided 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 1b3ba0ae..e974b09e 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,7 +28,7 @@ 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.FieldUtils; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.utility.MinecraftReflection; @@ -37,18 +37,14 @@ import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedGameProfile; 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 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; @@ -263,15 +259,11 @@ 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 @@ -287,14 +279,8 @@ public class VerifyResponseTask implements Runnable { 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")); - } + //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); } } From 4fa28f2c692c4cbd971a26fcc5c03a9b8b842e4b Mon Sep 17 00:00:00 2001 From: games647 Date: Sat, 11 Jun 2022 13:04:45 +0200 Subject: [PATCH 04/37] Support username extraction in 1.19 diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index d1f83a6..f8b8de2 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -31,6 +31,7 @@ import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; +import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.core.antibot.AntiBotService; import com.github.games647.fastlogin.core.antibot.AntiBotService.Action; @@ -94,7 +95,7 @@ public class ProtocolLibListener extends PacketAdapter { PacketContainer packet = packetEvent.getPacket(); InetSocketAddress address = sender.getAddress(); - String username = packet.getGameProfiles().read(0).getName(); + String username = getUsername(packet); Action action = antiBotService.onIncomingConnection(address, username); switch (action) { @@ -154,4 +155,14 @@ public class ProtocolLibListener extends PacketAdapter { Runnable nameCheckTask = new NameCheckTask(plugin, random, player, packetEvent, username, keyPair.getPublic()); plugin.getScheduler().runAsync(nameCheckTask); } + + 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(); + } } --- .../listener/protocollib/ProtocolLibListener.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index d1f83a61..f8b8de2b 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -31,6 +31,7 @@ import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; +import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.core.antibot.AntiBotService; import com.github.games647.fastlogin.core.antibot.AntiBotService.Action; @@ -94,7 +95,7 @@ public class ProtocolLibListener extends PacketAdapter { PacketContainer packet = packetEvent.getPacket(); InetSocketAddress address = sender.getAddress(); - String username = packet.getGameProfiles().read(0).getName(); + String username = getUsername(packet); Action action = antiBotService.onIncomingConnection(address, username); switch (action) { @@ -154,4 +155,14 @@ public class ProtocolLibListener extends PacketAdapter { Runnable nameCheckTask = new NameCheckTask(plugin, random, player, packetEvent, username, keyPair.getPublic()); plugin.getScheduler().runAsync(nameCheckTask); } + + 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(); + } } From d353ac66ab7ec0e7bb725bf0b2c27633c40aa2fc Mon Sep 17 00:00:00 2001 From: games647 Date: Sat, 11 Jun 2022 13:17:36 +0200 Subject: [PATCH 05/37] Verify public keys from players diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 2e1ccc4..c4caae8 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -119,9 +119,6 @@ dmulloy2-repo https://repo.dmulloy2.net/repository/public/ - - false - @@ -186,7 +183,7 @@ com.comphenix.protocol ProtocolLib - 5.0.0-SNAPSHOT + 5.0.0-20220603.031413-2 provided @@ -351,5 +348,12 @@ system ${project.basedir}/lib/UltraAuth v2.1.2.jar + + + org.bouncycastle + bcpkix-jdk18on + 1.71 + test + 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 1197514..143dda9 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,15 +25,28 @@ */ 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 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.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; +import java.util.Base64; +import java.util.Base64.Encoder; import java.util.Random; import javax.crypto.Cipher; @@ -46,11 +59,25 @@ import javax.crypto.spec.SecretKeySpec; * * @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 } @@ -65,7 +92,7 @@ public class EncryptionUtil { 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 @@ -120,6 +147,33 @@ public class EncryptionUtil { return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES"); } + public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimstamp) + throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { + if (!verifyTimstamp.isBefore(clientKey.getExpiry())) { + return false; + } + + Signature signature = Signature.getInstance("SHA1withRSA"); + signature.initVerify(mojangSessionKey); + signature.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII)); + return signature.verify(clientKey.getSignature()); + } + + 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.getExpiry().toEpochMilli(); + String encoded = KEY_ENCODER.encodeToString(clientPublicKey.getKey()); + return expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n"; + } + public static byte[] decrypt(PrivateKey key, byte[] data) throws GeneralSecurityException { // b(Key var0, byte[] var1) Cipher cipher = Cipher.getInstance(key.getAlgorithm()); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index f8b8de2..c9e72fc 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -27,18 +27,27 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.InternalStructure; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; -import com.github.games647.fastlogin.bukkit.BukkitLoginSession; +import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.wrappers.WrappedGameProfile; +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 com.github.games647.fastlogin.core.antibot.AntiBotService; import com.github.games647.fastlogin.core.antibot.AntiBotService.Action; import java.net.InetSocketAddress; +import java.security.InvalidKeyException; import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; import java.security.SecureRandom; +import java.security.SignatureException; +import java.time.Instant; +import java.util.Optional; import org.bukkit.entity.Player; @@ -149,6 +158,11 @@ public class ProtocolLibListener extends PacketAdapter { username = (String) packetEvent.getPacket().getMeta("original_name").get(); } + if (!verifyPublicKey(packet)) { + plugin.getLog().warn("Invalid public key from player {}", username); + return; + } + plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username); packetEvent.getAsyncMarker().incrementProcessingDelay(); @@ -156,6 +170,24 @@ public class ProtocolLibListener extends PacketAdapter { plugin.getScheduler().runAsync(nameCheckTask); } + private boolean verifyPublicKey(PacketContainer packet) { + Optional internalStructure = packet.getOptionalStructures().readSafely(0); + if (internalStructure == null) { + return true; + } + + Object instance = internalStructure.get().getHandle(); + Instant expires = FuzzyReflection.getFieldValue(instance, Instant.class, true); + PublicKey key = FuzzyReflection.getFieldValue(instance, PublicKey.class, true); + byte[] signature = FuzzyReflection.getFieldValue(instance, byte[].class, true); + ClientPublicKey clientKey = new ClientPublicKey(expires, key.getEncoded(), signature); + try { + return EncryptionUtil.verifyClientKey(clientKey, Instant.now()); + } catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) { + return false; + } + } + private String getUsername(PacketContainer packet) { WrappedGameProfile profile = packet.getGameProfiles().readSafely(0); if (profile == null) { 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 0000000..495adb1 --- /dev/null +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/packet/ClientPublicKey.java @@ -0,0 +1,53 @@ +/* + * 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.time.Instant; + +public class ClientPublicKey { + + private final Instant expiry; + private final byte[] key; + private final byte[] signature; + + public ClientPublicKey(Instant expiry, byte[] key, byte[] signature) { + this.expiry = expiry; + this.key = key; + this.signature = signature; + } + + public Instant getExpiry() { + return expiry; + } + + public byte[] getKey() { + return key; + } + + public byte[] getSignature() { + return signature; + } +} 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 7a097f1..11a52f7 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,8 +25,27 @@ */ 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.SecureRandom; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Base64; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; @@ -43,4 +62,77 @@ public class EncryptionUtilTest { assertThat(token, notNullValue()); assertThat(token.length, is(4)); } + + @Test + public void testExpiredClientKey() throws Exception { + var clientKey = loadClientKey("client_keys/valid.json"); + + // Client expires at the exact second mentioned, so use it for verification + var expiredTimestamp = clientKey.getExpiry(); + assertThat(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp), is(false)); + } + + // @Test(expected = Exception.class) + @Test + public void testInvalidChangedExpiration() throws Exception { + // expiration date changed should make the signature invalid + // expiration should still be valid + var clientKey = loadClientKey("client_keys/invalid_wrong_expiration.json"); + assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + } + + // @Test(expected = Exception.class) + @Test + public void testInvalidChangedKey() throws Exception { + // changed public key no longer corresponding to the signature + var clientKey = loadClientKey("client_keys/invalid_wrong_key.json"); + assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + } + + @Test + public void testInvalidChangedSignature() throws Exception { + // signature modified no longer corresponding to key and expiration date + var clientKey = loadClientKey("client_keys/invalid_wrong_signature.json"); + assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + } + + @Test + public void testValidClientKey() throws Exception { + var clientKey = loadClientKey("client_keys/valid.json"); + + var verificationTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + assertThat(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp), is(true)); + } + + private ClientPublicKey loadClientKey(String path) + throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { + var keyUrl = Resources.getResource(path); + var gson = new Gson(); + + var lines = Resources.toString(keyUrl, StandardCharsets.US_ASCII); + var object = 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.getEncoded(), signature); + } + + private RSAPublicKey parsePublicKey(String lines) + throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { + try ( + Reader reader = new StringReader(lines); + 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/resources/client_keys/invalid_wrong_expiration.json b/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json new file mode 100644 index 0000000..7ecc12e --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T09:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} 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 0000000..37bb3ad --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_key.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T10:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU3I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} 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 0000000..cbca4b1 --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T10:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "bYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} diff --git a/bukkit/src/test/resources/client_keys/valid.json b/bukkit/src/test/resources/client_keys/valid.json new file mode 100644 index 0000000..a2d6a41 --- /dev/null +++ b/bukkit/src/test/resources/client_keys/valid.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T10:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} --- bukkit/pom.xml | 12 ++- .../listener/protocollib/EncryptionUtil.java | 58 +++++++++++- .../protocollib/ProtocolLibListener.java | 34 ++++++- .../protocollib/packet/ClientPublicKey.java | 53 +++++++++++ .../protocollib/EncryptionUtilTest.java | 94 ++++++++++++++++++- .../client_keys/invalid_wrong_expiration.json | 5 + .../client_keys/invalid_wrong_key.json | 5 + .../client_keys/invalid_wrong_signature.json | 5 + .../src/test/resources/client_keys/valid.json | 5 + 9 files changed, 263 insertions(+), 8 deletions(-) create mode 100644 bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/packet/ClientPublicKey.java create mode 100644 bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json create mode 100644 bukkit/src/test/resources/client_keys/invalid_wrong_key.json create mode 100644 bukkit/src/test/resources/client_keys/invalid_wrong_signature.json create mode 100644 bukkit/src/test/resources/client_keys/valid.json diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 2e1ccc4f..c4caae89 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -119,9 +119,6 @@ dmulloy2-repo https://repo.dmulloy2.net/repository/public/ - - false - @@ -186,7 +183,7 @@ com.comphenix.protocol ProtocolLib - 5.0.0-SNAPSHOT + 5.0.0-20220603.031413-2 provided @@ -351,5 +348,12 @@ system ${project.basedir}/lib/UltraAuth v2.1.2.jar + + + org.bouncycastle + bcpkix-jdk18on + 1.71 + test + 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..143dda91 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,15 +25,28 @@ */ 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 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.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; +import java.util.Base64; +import java.util.Base64.Encoder; import java.util.Random; import javax.crypto.Cipher; @@ -46,11 +59,25 @@ import javax.crypto.spec.SecretKeySpec; * * @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 } @@ -65,7 +92,7 @@ public class EncryptionUtil { 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 @@ -120,6 +147,33 @@ public class EncryptionUtil { return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES"); } + public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimstamp) + throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { + if (!verifyTimstamp.isBefore(clientKey.getExpiry())) { + return false; + } + + Signature signature = Signature.getInstance("SHA1withRSA"); + signature.initVerify(mojangSessionKey); + signature.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII)); + return signature.verify(clientKey.getSignature()); + } + + 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.getExpiry().toEpochMilli(); + String encoded = KEY_ENCODER.encodeToString(clientPublicKey.getKey()); + return expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n"; + } + public static byte[] decrypt(PrivateKey key, byte[] data) throws GeneralSecurityException { // b(Key var0, byte[] var1) Cipher cipher = Cipher.getInstance(key.getAlgorithm()); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index f8b8de2b..c9e72fc1 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -27,18 +27,27 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.InternalStructure; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; -import com.github.games647.fastlogin.bukkit.BukkitLoginSession; +import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.wrappers.WrappedGameProfile; +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 com.github.games647.fastlogin.core.antibot.AntiBotService; import com.github.games647.fastlogin.core.antibot.AntiBotService.Action; import java.net.InetSocketAddress; +import java.security.InvalidKeyException; import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; import java.security.SecureRandom; +import java.security.SignatureException; +import java.time.Instant; +import java.util.Optional; import org.bukkit.entity.Player; @@ -149,6 +158,11 @@ public class ProtocolLibListener extends PacketAdapter { username = (String) packetEvent.getPacket().getMeta("original_name").get(); } + if (!verifyPublicKey(packet)) { + plugin.getLog().warn("Invalid public key from player {}", username); + return; + } + plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username); packetEvent.getAsyncMarker().incrementProcessingDelay(); @@ -156,6 +170,24 @@ public class ProtocolLibListener extends PacketAdapter { plugin.getScheduler().runAsync(nameCheckTask); } + private boolean verifyPublicKey(PacketContainer packet) { + Optional internalStructure = packet.getOptionalStructures().readSafely(0); + if (internalStructure == null) { + return true; + } + + Object instance = internalStructure.get().getHandle(); + Instant expires = FuzzyReflection.getFieldValue(instance, Instant.class, true); + PublicKey key = FuzzyReflection.getFieldValue(instance, PublicKey.class, true); + byte[] signature = FuzzyReflection.getFieldValue(instance, byte[].class, true); + ClientPublicKey clientKey = new ClientPublicKey(expires, key.getEncoded(), signature); + try { + return EncryptionUtil.verifyClientKey(clientKey, Instant.now()); + } catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) { + return false; + } + } + private String getUsername(PacketContainer packet) { WrappedGameProfile profile = packet.getGameProfiles().readSafely(0); if (profile == null) { 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..495adb10 --- /dev/null +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/packet/ClientPublicKey.java @@ -0,0 +1,53 @@ +/* + * 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.time.Instant; + +public class ClientPublicKey { + + private final Instant expiry; + private final byte[] key; + private final byte[] signature; + + public ClientPublicKey(Instant expiry, byte[] key, byte[] signature) { + this.expiry = expiry; + this.key = key; + this.signature = signature; + } + + public Instant getExpiry() { + return expiry; + } + + public byte[] getKey() { + return key; + } + + public byte[] getSignature() { + return signature; + } +} 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..11a52f75 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,8 +25,27 @@ */ package com.github.games647.fastlogin.bukkit.listener.protocollib; -import java.security.SecureRandom; +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.SecureRandom; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Base64; + +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; @@ -43,4 +62,77 @@ public class EncryptionUtilTest { assertThat(token, notNullValue()); assertThat(token.length, is(4)); } + + @Test + public void testExpiredClientKey() throws Exception { + var clientKey = loadClientKey("client_keys/valid.json"); + + // Client expires at the exact second mentioned, so use it for verification + var expiredTimestamp = clientKey.getExpiry(); + assertThat(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp), is(false)); + } + + // @Test(expected = Exception.class) + @Test + public void testInvalidChangedExpiration() throws Exception { + // expiration date changed should make the signature invalid + // expiration should still be valid + var clientKey = loadClientKey("client_keys/invalid_wrong_expiration.json"); + assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + } + + // @Test(expected = Exception.class) + @Test + public void testInvalidChangedKey() throws Exception { + // changed public key no longer corresponding to the signature + var clientKey = loadClientKey("client_keys/invalid_wrong_key.json"); + assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + } + + @Test + public void testInvalidChangedSignature() throws Exception { + // signature modified no longer corresponding to key and expiration date + var clientKey = loadClientKey("client_keys/invalid_wrong_signature.json"); + assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + } + + @Test + public void testValidClientKey() throws Exception { + var clientKey = loadClientKey("client_keys/valid.json"); + + var verificationTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + assertThat(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp), is(true)); + } + + private ClientPublicKey loadClientKey(String path) + throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { + var keyUrl = Resources.getResource(path); + var gson = new Gson(); + + var lines = Resources.toString(keyUrl, StandardCharsets.US_ASCII); + var object = 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.getEncoded(), signature); + } + + private RSAPublicKey parsePublicKey(String lines) + throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { + try ( + Reader reader = new StringReader(lines); + 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/resources/client_keys/invalid_wrong_expiration.json b/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json new file mode 100644 index 00000000..7ecc12e4 --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T09:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} 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..37bb3ad0 --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_key.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T10:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU3I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} 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..cbca4b16 --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T10:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "bYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} diff --git a/bukkit/src/test/resources/client_keys/valid.json b/bukkit/src/test/resources/client_keys/valid.json new file mode 100644 index 00000000..a2d6a41a --- /dev/null +++ b/bukkit/src/test/resources/client_keys/valid.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T10:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} From d6c6108a7e16613d58f6e924a35f9c833cf26707 Mon Sep 17 00:00:00 2001 From: games647 Date: Sat, 11 Jun 2022 13:22:35 +0200 Subject: [PATCH 06/37] Do not modify certificate file during packaging phase --- pom.xml | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pom.xml b/pom.xml index e8a886a3..517a10ac 100644 --- a/pom.xml +++ b/pom.xml @@ -129,7 +129,18 @@ + src/main/resources + + yggdrasil_session_pubkey.der + + false + + + src/main/resources + + yggdrasil_session_pubkey.der + true From e85d0d3f0e4f5f4b69e7dad9751274ba5de0d100 Mon Sep 17 00:00:00 2001 From: games647 Date: Mon, 13 Jun 2022 13:10:49 +0200 Subject: [PATCH 07/37] Add session key to plugin --- .../main/resources/yggdrasil_session_pubkey.der | Bin 0 -> 550 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 bukkit/src/main/resources/yggdrasil_session_pubkey.der 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 0000000000000000000000000000000000000000..9c79a3aa4771da1f15af37a2af0898f878ad816f GIT binary patch literal 550 zcmXqLVp1~TW#iOp^Jx3d%gD&c%D~*j#Lr;R#Kgta#Kg#ODxiXW<<9ALj}L8UJ-;ya z@6Y=|;`-C2g}v%h6xM&sNZiU@$#c3jxt+66e1U7A=KW*BIR}0`ygOs<1Q{2GFtcZG zo~VQr$g?_pOE2hMtr*kv)_w7+lC9T8W2V@&3hF$Wwj$Ya>s}jyd+|nvdpd$12uxx$ zmbPX4SiZUbXxqb2HY=w3&YYg?I^*G-8LNJAxj%iJY2t79A%}OJm~!^k^P*=eDl=F79!8$9PKGvA((J z!xgJdSJ(7@pQ7~fcdq}BGiovkuXaRjQje*c^l8;Qz9iK*|H5O^rSjff*mU{+KiMW1 zR!#fQPi4z_rhVVgmpJ9c@x$6LlWNs@lR3^Gm9m(#YQSW z%cX#0clR7@Kc%2>=)f*+h8nS7#+9qM7R~ZGApbW&xqYImTE(T`+m8I)<`V9;AzRi- zsQJ9g-grfhbKYkjUp}{>Z{kF*E1SMQyKHNI+5JoB&rrrExg8Zsx)UzGRIT0A)NwTZ z=1;MT-|pAaEzWm;s_E$Yy*=~s`>m(0Pgi;<|0Cb>6JO6CziCVNSx+(W2>xN=ZluGi zzvKn3XQiR*YNpQDCF!=CTEbpKKTJ?2iu4 O5H1S4&&15gzz6_ODhXZy literal 0 HcmV?d00001 From 456342c686482f3531396eff15a5ab1e172616d9 Mon Sep 17 00:00:00 2001 From: games647 Date: Mon, 13 Jun 2022 15:38:08 +0200 Subject: [PATCH 08/37] Migrate to ProtocolLib field extractors for better compatibility diff --git a/bukkit/pom.xml b/bukkit/pom.xml index c4caae8..9b3c5c5 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -183,7 +183,7 @@ com.comphenix.protocol ProtocolLib - 5.0.0-20220603.031413-2 + 5.0.0-SNAPSHOT provided diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index c9e72fc..3c66e7b 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -27,12 +27,12 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.events.InternalStructure; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.wrappers.WrappedGameProfile; +import com.comphenix.protocol.wrappers.WrappedProfilePublicKey; +import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; @@ -47,7 +47,6 @@ import java.security.PublicKey; import java.security.SecureRandom; import java.security.SignatureException; import java.time.Instant; -import java.util.Optional; import org.bukkit.entity.Player; @@ -171,15 +170,15 @@ public class ProtocolLibListener extends PacketAdapter { } private boolean verifyPublicKey(PacketContainer packet) { - Optional internalStructure = packet.getOptionalStructures().readSafely(0); - if (internalStructure == null) { + WrappedProfileKeyData profileKey = packet.getProfilePublicKeys().optionRead(0) + .map(WrappedProfilePublicKey::getKeyData).orElse(null); + if (profileKey == null) { return true; } - Object instance = internalStructure.get().getHandle(); - Instant expires = FuzzyReflection.getFieldValue(instance, Instant.class, true); - PublicKey key = FuzzyReflection.getFieldValue(instance, PublicKey.class, true); - byte[] signature = FuzzyReflection.getFieldValue(instance, byte[].class, true); + Instant expires = profileKey.getExpireTime(); + PublicKey key = profileKey.getKey(); + byte[] signature = profileKey.getSignature(); ClientPublicKey clientKey = new ClientPublicKey(expires, key.getEncoded(), signature); try { return EncryptionUtil.verifyClientKey(clientKey, Instant.now()); --- bukkit/pom.xml | 2 +- .../protocollib/ProtocolLibListener.java | 17 ++++++++--------- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/bukkit/pom.xml b/bukkit/pom.xml index c4caae89..9b3c5c5d 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -183,7 +183,7 @@ com.comphenix.protocol ProtocolLib - 5.0.0-20220603.031413-2 + 5.0.0-SNAPSHOT provided diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index c9e72fc1..3c66e7ba 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -27,12 +27,12 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.events.InternalStructure; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.wrappers.WrappedGameProfile; +import com.comphenix.protocol.wrappers.WrappedProfilePublicKey; +import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; @@ -47,7 +47,6 @@ import java.security.PublicKey; import java.security.SecureRandom; import java.security.SignatureException; import java.time.Instant; -import java.util.Optional; import org.bukkit.entity.Player; @@ -171,15 +170,15 @@ public class ProtocolLibListener extends PacketAdapter { } private boolean verifyPublicKey(PacketContainer packet) { - Optional internalStructure = packet.getOptionalStructures().readSafely(0); - if (internalStructure == null) { + WrappedProfileKeyData profileKey = packet.getProfilePublicKeys().optionRead(0) + .map(WrappedProfilePublicKey::getKeyData).orElse(null); + if (profileKey == null) { return true; } - Object instance = internalStructure.get().getHandle(); - Instant expires = FuzzyReflection.getFieldValue(instance, Instant.class, true); - PublicKey key = FuzzyReflection.getFieldValue(instance, PublicKey.class, true); - byte[] signature = FuzzyReflection.getFieldValue(instance, byte[].class, true); + Instant expires = profileKey.getExpireTime(); + PublicKey key = profileKey.getKey(); + byte[] signature = profileKey.getSignature(); ClientPublicKey clientKey = new ClientPublicKey(expires, key.getEncoded(), signature); try { return EncryptionUtil.verifyClientKey(clientKey, Instant.now()); From bc7c4eea606c0e4f7b118518f96df2e0f825ce55 Mon Sep 17 00:00:00 2001 From: games647 Date: Wed, 15 Jun 2022 15:48:02 +0200 Subject: [PATCH 09/37] Use PublicKey instead of byte representation --- .../bukkit/listener/protocollib/EncryptionUtil.java | 7 ++----- .../bukkit/listener/protocollib/ProtocolLibListener.java | 2 +- .../listener/protocollib/packet/ClientPublicKey.java | 7 ++++--- .../bukkit/listener/protocollib/EncryptionUtilTest.java | 3 +-- 4 files changed, 8 insertions(+), 11 deletions(-) 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 143dda91..956a60f7 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 @@ -56,8 +56,6 @@ import javax.crypto.spec.SecretKeySpec; /** * Encryption and decryption minecraft util for connection between servers * and paid Minecraft account clients. - * - * @see net.minecraft.server.MinecraftEncryption */ class EncryptionUtil { @@ -170,7 +168,7 @@ class EncryptionUtil { private static String toSignable(ClientPublicKey clientPublicKey) { long expiry = clientPublicKey.getExpiry().toEpochMilli(); - String encoded = KEY_ENCODER.encodeToString(clientPublicKey.getKey()); + String encoded = KEY_ENCODER.encodeToString(clientPublicKey.getKey().getEncoded()); return expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n"; } @@ -195,8 +193,7 @@ class EncryptionUtil { return cipher.doFinal(data); } - private static byte[] getServerIdHash( - String sessionId, PublicKey publicKey, SecretKey sharedSecret) + 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"); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index 3c66e7ba..27c03352 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -179,7 +179,7 @@ public class ProtocolLibListener extends PacketAdapter { Instant expires = profileKey.getExpireTime(); PublicKey key = profileKey.getKey(); byte[] signature = profileKey.getSignature(); - ClientPublicKey clientKey = new ClientPublicKey(expires, key.getEncoded(), signature); + ClientPublicKey clientKey = new ClientPublicKey(expires, key, signature); try { return EncryptionUtil.verifyClientKey(clientKey, Instant.now()); } catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) { 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 index 495adb10..f14b16f1 100644 --- 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 @@ -25,15 +25,16 @@ */ package com.github.games647.fastlogin.bukkit.listener.protocollib.packet; +import java.security.PublicKey; import java.time.Instant; public class ClientPublicKey { private final Instant expiry; - private final byte[] key; + private final PublicKey key; private final byte[] signature; - public ClientPublicKey(Instant expiry, byte[] key, byte[] signature) { + public ClientPublicKey(Instant expiry, PublicKey key, byte[] signature) { this.expiry = expiry; this.key = key; this.signature = signature; @@ -43,7 +44,7 @@ public class ClientPublicKey { return expiry; } - public byte[] getKey() { + public PublicKey getKey() { return key; } 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 11a52f75..904405ae 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 @@ -117,7 +117,7 @@ public class EncryptionUtilTest { RSAPublicKey publicKey = parsePublicKey(key); byte[] signature = Base64.getDecoder().decode(object.getAsJsonPrimitive("signature").getAsString()); - return new ClientPublicKey(expires, publicKey.getEncoded(), signature); + return new ClientPublicKey(expires, publicKey, signature); } private RSAPublicKey parsePublicKey(String lines) @@ -126,7 +126,6 @@ public class EncryptionUtilTest { Reader reader = new StringReader(lines); PemReader pemReader = new PemReader(reader) ) { - PemObject pemObject = pemReader.readPemObject(); byte[] content = pemObject.getContent(); var pubKeySpec = new X509EncodedKeySpec(content); From dac5cd76393fd5172c931b55386e77970438554b Mon Sep 17 00:00:00 2001 From: games647 Date: Sat, 18 Jun 2022 15:49:45 +0200 Subject: [PATCH 10/37] Verify signed nonce diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 9b3c5c5..c1f63ba 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -179,6 +179,19 @@ 1.0.7 + + com.mojang + datafixerupper + 5.0.28 + provided + + + * + * + + + + com.comphenix.protocol 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 556def1..4f4af4d 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,6 +26,7 @@ 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; @@ -42,30 +43,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 +83,10 @@ public class BukkitLoginSession extends LoginSession { return verifyToken.clone(); } + public ClientPublicKey getClientPublicKey() { + return clientPublicKey; + } + /** * @return premium skin if available */ 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 956a60f..713fa68 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 @@ -27,6 +27,7 @@ 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.common.primitives.Longs; import java.io.IOException; import java.math.BigInteger; @@ -115,9 +116,9 @@ class EncryptionUtil { /** * Generate the server id based on client and server data. * - * @param sessionId session for the current login attempt + * @param sessionId 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) { @@ -136,7 +137,7 @@ class EncryptionUtil { * 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 */ @@ -151,10 +152,20 @@ class EncryptionUtil { return false; } - Signature signature = Signature.getInstance("SHA1withRSA"); - signature.initVerify(mojangSessionKey); - signature.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII)); - return signature.verify(clientKey.getSignature()); + Signature verifier = Signature.getInstance("SHA1withRSA"); + verifier.initVerify(mojangSessionKey); + verifier.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII)); + return verifier.verify(clientKey.getSignature()); + } + + public static boolean verifySignedNonce(byte[] nonce, PublicKey clientKey, long signatureSalt, byte[] signature) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Signature verifier = Signature.getInstance("SHA256withRSA"); + verifier.initVerify(clientKey); + + verifier.update(nonce); + verifier.update(Longs.toByteArray(signatureSalt)); + return verifier.verify(signature); } private static PublicKey loadMojangSessionKey() @@ -183,7 +194,7 @@ class EncryptionUtil { * Decrypted the given data using the cipher. * * @param cipher decryption cypher initialized with the private key - * @param data the encrypted data + * @param data the encrypted data * @return clear text data * @throws GeneralSecurityException if it fails to decrypt the data */ @@ -194,7 +205,7 @@ class EncryptionUtil { } private static byte[] getServerIdHash(String sessionId, PublicKey publicKey, SecretKey sharedSecret) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { // byte[] a(String var0, PublicKey var1, SecretKey var2) MessageDigest digest = MessageDigest.getInstance("SHA-1"); 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 f0a4083..485c065 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 @@ -27,21 +27,25 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.wrappers.BukkitConverters; +import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; 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; import java.security.PublicKey; +import java.util.Optional; import java.util.Random; 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; @@ -67,7 +71,9 @@ public class NameCheckTask extends JoinManagement publicKey = packetEvent.getPacket().getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()).read(0); + + super.onLogin(username, new ProtocolLibLoginSource(player, random, publicKey.get(), this.publicKey)); } finally { ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent); } @@ -85,7 +91,7 @@ public class NameCheckTask extends JoinManagement either = packetEvent.getPacket().getSpecificModifier(Either.class).read(0); + Object signatureData = either.right().get(); + long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true); + byte[] signature = FuzzyReflection.getFieldValue(signatureData, byte[].class, true); + + BukkitLoginSession session = plugin.getSession(sender.getAddress()); + PublicKey publicKey = session.getClientPublicKey().getKey(); + try { + if (EncryptionUtil.verifySignedNonce(session.getVerifyToken(), publicKey, salt, signature)) { + packetEvent.getAsyncMarker().incrementProcessingDelay(); + Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair); + plugin.getScheduler().runAsync(verifyTask); + } else { + sender.kickPlayer("Invalid signature"); + } + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { + sender.kickPlayer("Invalid signature"); + } } } 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 dd191f1..d87b602 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,6 +30,7 @@ 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.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; import com.github.games647.fastlogin.core.shared.LoginSource; import java.lang.reflect.InvocationTargetException; @@ -48,15 +49,18 @@ class ProtocolLibLoginSource implements LoginSource { private final Player player; private final Random random; + + private final WrappedProfileKeyData clientPublicKey; 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, WrappedProfileKeyData clientPublicKey, PublicKey serverPublicKey) { this.player = player; this.random = random; - this.publicKey = publicKey; + this.clientPublicKey = clientPublicKey; + this.publicKey = serverPublicKey; } @Override @@ -109,6 +113,10 @@ class ProtocolLibLoginSource implements LoginSource { return player.getAddress(); } + public WrappedProfileKeyData getClientPublicKey() { + return clientPublicKey; + } + public String getServerId() { return serverId; } 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 e974b09..d13a5c9 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 @@ -29,12 +29,15 @@ import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; 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.MojangResolver; @@ -120,7 +123,7 @@ public class VerifyResponseTask implements Runnable { } try { - if (!checkVerifyToken(session) || !enableEncryption(loginKey)) { + if (!enableEncryption(loginKey)) { return; } } catch (Exception ex) { @@ -180,23 +183,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); @@ -273,6 +259,9 @@ public class VerifyResponseTask implements Runnable { if (MinecraftVersion.atOrAbove(new MinecraftVersion(1, 19, 0))) { startPacket.getStrings().write(0, username); + + EquivalentConverter converter = BukkitConverters.getWrappedPublicKeyDataConverter(); + startPacket.getOptionals(converter).write(0, Optional.empty()); } else { //uuid is ignored by the packet definition WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username); 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 0000000..c2a2d1a --- /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 904405a..63a24ba 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,6 +25,7 @@ */ package com.github.games647.fastlogin.bukkit.listener.protocollib; +import com.github.games647.fastlogin.bukkit.listener.protocollib.SignatureTestData.SignatureData; import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; import com.google.common.io.Resources; import com.google.gson.Gson; @@ -36,9 +37,12 @@ import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; import java.security.SecureRandom; +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.time.temporal.ChronoUnit; @@ -65,7 +69,7 @@ public class EncryptionUtilTest { @Test public void testExpiredClientKey() throws Exception { - var clientKey = loadClientKey("client_keys/valid.json"); + var clientKey = loadClientKey("client_keys/valid_public_key.json"); // Client expires at the exact second mentioned, so use it for verification var expiredTimestamp = clientKey.getExpiry(); @@ -78,7 +82,9 @@ public class EncryptionUtilTest { // expiration date changed should make the signature invalid // expiration should still be valid var clientKey = loadClientKey("client_keys/invalid_wrong_expiration.json"); - assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + Instant expireTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + + assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); } // @Test(expected = Exception.class) @@ -86,31 +92,119 @@ public class EncryptionUtilTest { public void testInvalidChangedKey() throws Exception { // changed public key no longer corresponding to the signature var clientKey = loadClientKey("client_keys/invalid_wrong_key.json"); - assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + Instant expireTimestamp = clientKey.getExpiry().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 = loadClientKey("client_keys/invalid_wrong_signature.json"); - assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + Instant expireTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + + assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); } @Test public void testValidClientKey() throws Exception { - var clientKey = loadClientKey("client_keys/valid.json"); - + var clientKey = loadClientKey("client_keys/valid_public_key.json"); var verificationTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + assertThat(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp), is(true)); } + @Test + public void testValidSignedNonce() throws Exception { + ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); + PublicKey clientPublicKey = clientKey.getKey(); + + SignatureTestData testData = loadSignatureResource("signature/valid_signature.json"); + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(true)); + } + + @Test + public void testIncorrectNonce() throws Exception { + ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); + PublicKey clientPublicKey = clientKey.getKey(); + + SignatureTestData testData = loadSignatureResource("signature/incorrect_nonce.json"); + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + } + + @Test + public void testIncorrectSalt() throws Exception { + // client generated + ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); + PublicKey clientPublicKey = clientKey.getKey(); + + SignatureTestData testData = loadSignatureResource("signature/incorrect_salt.json"); + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + } + + @Test + public void testIncorrectSignature() throws Exception { + // client generated + ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); + PublicKey clientPublicKey = clientKey.getKey(); + + SignatureTestData testData = loadSignatureResource("signature/incorrect_signature.json"); + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + } + + @Test + public void testWrongPublicKeySigned() throws Exception { + // load a different public key + ClientPublicKey clientKey = loadClientKey("client_keys/invalid_wrong_key.json"); + PublicKey clientPublicKey = clientKey.getKey(); + + SignatureTestData testData = loadSignatureResource("signature/valid_signature.json"); + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + } + + private SignatureTestData loadSignatureResource(String resourceName) throws IOException { + var keyUrl = Resources.getResource(resourceName); + var encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII); + + return new Gson().fromJson(encodedSignature, SignatureTestData.class); + } + + private 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); + } + } + private ClientPublicKey loadClientKey(String path) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { var keyUrl = Resources.getResource(path); - var gson = new Gson(); var lines = Resources.toString(keyUrl, StandardCharsets.US_ASCII); - var object = gson.fromJson(lines, JsonObject.class); + var object = new Gson().fromJson(lines, JsonObject.class); Instant expires = Instant.parse(object.getAsJsonPrimitive("expires_at").getAsString()); String key = object.getAsJsonPrimitive("key").getAsString(); @@ -120,10 +214,10 @@ public class EncryptionUtilTest { return new ClientPublicKey(expires, publicKey, signature); } - private RSAPublicKey parsePublicKey(String lines) + private RSAPublicKey parsePublicKey(String keySpec) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { try ( - Reader reader = new StringReader(lines); + Reader reader = new StringReader(keySpec); PemReader pemReader = new PemReader(reader) ) { PemObject pemObject = pemReader.readPemObject(); 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 0000000..8ea85f6 --- /dev/null +++ b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SignatureTestData.java @@ -0,0 +1,60 @@ +/* + * 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.annotations.JsonAdapter; + +public class SignatureTestData { + + @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/resources/client_keys/README.md b/bukkit/src/test/resources/client_keys/README.md new file mode 100644 index 0000000..1165e23 --- /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 file 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 + +* `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 +* `valid_public_key.json`: Extracted from actual file + +## 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 index 7ecc12e..ea509fe 100644 --- a/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json @@ -1,5 +1,5 @@ { - "expires_at": "2022-06-12T09:46:26.421156927Z", - "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", - "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" + "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 index 37bb3ad..98ea33f 100644 --- a/bukkit/src/test/resources/client_keys/invalid_wrong_key.json +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_key.json @@ -1,5 +1,5 @@ { - "expires_at": "2022-06-12T10:46:26.421156927Z", + "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": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" + "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 index cbca4b1..2b80c2c 100644 --- a/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json @@ -1,5 +1,5 @@ { - "expires_at": "2022-06-12T10:46:26.421156927Z", - "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", - "signature": "bYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" + "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.json b/bukkit/src/test/resources/client_keys/valid.json deleted file mode 100644 index a2d6a41..0000000 --- a/bukkit/src/test/resources/client_keys/valid.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "expires_at": "2022-06-12T10:46:26.421156927Z", - "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", - "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" -} 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 0000000..8943e87 --- /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 0000000..cc603bc --- /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 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 +* `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 0000000..b88c0f5 --- /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 0000000..8edffb6 --- /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 0000000..ba6bac5 --- /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 0000000..7f4f4ad --- /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 + } +} --- bukkit/pom.xml | 13 ++ .../fastlogin/bukkit/BukkitLoginSession.java | 16 ++- .../listener/protocollib/EncryptionUtil.java | 29 +++-- .../listener/protocollib/NameCheckTask.java | 16 ++- .../protocollib/ProtocolLibListener.java | 23 +++- .../protocollib/ProtocolLibLoginSource.java | 12 +- .../protocollib/VerifyResponseTask.java | 25 ++-- .../listener/protocollib/Base64Adapter.java | 48 ++++++++ .../protocollib/EncryptionUtilTest.java | 114 ++++++++++++++++-- .../protocollib/SignatureTestData.java | 60 +++++++++ bukkit/src/test/java/integration/README.md | 53 ++++++++ .../src/test/resources/client_keys/README.md | 27 +++++ .../client_keys/invalid_wrong_expiration.json | 6 +- .../client_keys/invalid_wrong_key.json | 4 +- .../client_keys/invalid_wrong_signature.json | 6 +- .../src/test/resources/client_keys/valid.json | 5 - .../client_keys/valid_public_key.json | 5 + bukkit/src/test/resources/signature/README.md | 16 +++ .../resources/signature/incorrect_nonce.json | 7 ++ .../resources/signature/incorrect_salt.json | 7 ++ .../signature/incorrect_signature.json | 7 ++ .../resources/signature/valid_signature.json | 7 ++ 22 files changed, 443 insertions(+), 63 deletions(-) create mode 100644 bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/Base64Adapter.java create mode 100644 bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SignatureTestData.java create mode 100644 bukkit/src/test/java/integration/README.md create mode 100644 bukkit/src/test/resources/client_keys/README.md delete mode 100644 bukkit/src/test/resources/client_keys/valid.json create mode 100644 bukkit/src/test/resources/client_keys/valid_public_key.json create mode 100644 bukkit/src/test/resources/signature/README.md create mode 100644 bukkit/src/test/resources/signature/incorrect_nonce.json create mode 100644 bukkit/src/test/resources/signature/incorrect_salt.json create mode 100644 bukkit/src/test/resources/signature/incorrect_signature.json create mode 100644 bukkit/src/test/resources/signature/valid_signature.json diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 9b3c5c5d..c1f63ba1 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -179,6 +179,19 @@ 1.0.7 + + com.mojang + datafixerupper + 5.0.28 + provided + + + * + * + + + + com.comphenix.protocol 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..4f4af4dd 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,6 +26,7 @@ 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; @@ -42,30 +43,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 +83,10 @@ public class BukkitLoginSession extends LoginSession { return verifyToken.clone(); } + public ClientPublicKey getClientPublicKey() { + return clientPublicKey; + } + /** * @return premium skin if available */ 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 956a60f7..713fa68a 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 @@ -27,6 +27,7 @@ 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.common.primitives.Longs; import java.io.IOException; import java.math.BigInteger; @@ -115,9 +116,9 @@ class EncryptionUtil { /** * Generate the server id based on client and server data. * - * @param sessionId session for the current login attempt + * @param sessionId 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) { @@ -136,7 +137,7 @@ class EncryptionUtil { * 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 */ @@ -151,10 +152,20 @@ class EncryptionUtil { return false; } - Signature signature = Signature.getInstance("SHA1withRSA"); - signature.initVerify(mojangSessionKey); - signature.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII)); - return signature.verify(clientKey.getSignature()); + Signature verifier = Signature.getInstance("SHA1withRSA"); + verifier.initVerify(mojangSessionKey); + verifier.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII)); + return verifier.verify(clientKey.getSignature()); + } + + public static boolean verifySignedNonce(byte[] nonce, PublicKey clientKey, long signatureSalt, byte[] signature) + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + Signature verifier = Signature.getInstance("SHA256withRSA"); + verifier.initVerify(clientKey); + + verifier.update(nonce); + verifier.update(Longs.toByteArray(signatureSalt)); + return verifier.verify(signature); } private static PublicKey loadMojangSessionKey() @@ -183,7 +194,7 @@ class EncryptionUtil { * Decrypted the given data using the cipher. * * @param cipher decryption cypher initialized with the private key - * @param data the encrypted data + * @param data the encrypted data * @return clear text data * @throws GeneralSecurityException if it fails to decrypt the data */ @@ -194,7 +205,7 @@ class EncryptionUtil { } private static byte[] getServerIdHash(String sessionId, PublicKey publicKey, SecretKey sharedSecret) - throws NoSuchAlgorithmException { + throws NoSuchAlgorithmException { // byte[] a(String var0, PublicKey var1, SecretKey var2) MessageDigest digest = MessageDigest.getInstance("SHA-1"); 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..485c065d 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 @@ -27,21 +27,25 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib; import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.wrappers.BukkitConverters; +import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; 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; import java.security.PublicKey; +import java.util.Optional; import java.util.Random; 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; @@ -67,7 +71,9 @@ public class NameCheckTask extends JoinManagement publicKey = packetEvent.getPacket().getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()).read(0); + + super.onLogin(username, new ProtocolLibLoginSource(player, random, publicKey.get(), this.publicKey)); } finally { ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent); } @@ -85,7 +91,7 @@ public class NameCheckTask extends JoinManagement either = packetEvent.getPacket().getSpecificModifier(Either.class).read(0); + Object signatureData = either.right().get(); + long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true); + byte[] signature = FuzzyReflection.getFieldValue(signatureData, byte[].class, true); + + BukkitLoginSession session = plugin.getSession(sender.getAddress()); + PublicKey publicKey = session.getClientPublicKey().getKey(); + try { + if (EncryptionUtil.verifySignedNonce(session.getVerifyToken(), publicKey, salt, signature)) { + packetEvent.getAsyncMarker().incrementProcessingDelay(); + Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, session, sharedSecret, keyPair); + plugin.getScheduler().runAsync(verifyTask); + } else { + sender.kickPlayer("Invalid signature"); + } + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { + sender.kickPlayer("Invalid signature"); + } } } 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..d87b602d 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,6 +30,7 @@ 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.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; import com.github.games647.fastlogin.core.shared.LoginSource; import java.lang.reflect.InvocationTargetException; @@ -48,15 +49,18 @@ class ProtocolLibLoginSource implements LoginSource { private final Player player; private final Random random; + + private final WrappedProfileKeyData clientPublicKey; 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, WrappedProfileKeyData clientPublicKey, PublicKey serverPublicKey) { this.player = player; this.random = random; - this.publicKey = publicKey; + this.clientPublicKey = clientPublicKey; + this.publicKey = serverPublicKey; } @Override @@ -109,6 +113,10 @@ class ProtocolLibLoginSource implements LoginSource { return player.getAddress(); } + public WrappedProfileKeyData getClientPublicKey() { + return clientPublicKey; + } + public String getServerId() { return serverId; } 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 e974b09e..d13a5c9c 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 @@ -29,12 +29,15 @@ import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; 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.MojangResolver; @@ -120,7 +123,7 @@ public class VerifyResponseTask implements Runnable { } try { - if (!checkVerifyToken(session) || !enableEncryption(loginKey)) { + if (!enableEncryption(loginKey)) { return; } } catch (Exception ex) { @@ -180,23 +183,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); @@ -273,6 +259,9 @@ public class VerifyResponseTask implements Runnable { if (MinecraftVersion.atOrAbove(new MinecraftVersion(1, 19, 0))) { startPacket.getStrings().write(0, username); + + EquivalentConverter converter = BukkitConverters.getWrappedPublicKeyDataConverter(); + startPacket.getOptionals(converter).write(0, Optional.empty()); } else { //uuid is ignored by the packet definition WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username); 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 904405ae..63a24bac 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,6 +25,7 @@ */ package com.github.games647.fastlogin.bukkit.listener.protocollib; +import com.github.games647.fastlogin.bukkit.listener.protocollib.SignatureTestData.SignatureData; import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; import com.google.common.io.Resources; import com.google.gson.Gson; @@ -36,9 +37,12 @@ import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; import java.security.SecureRandom; +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.time.temporal.ChronoUnit; @@ -65,7 +69,7 @@ public class EncryptionUtilTest { @Test public void testExpiredClientKey() throws Exception { - var clientKey = loadClientKey("client_keys/valid.json"); + var clientKey = loadClientKey("client_keys/valid_public_key.json"); // Client expires at the exact second mentioned, so use it for verification var expiredTimestamp = clientKey.getExpiry(); @@ -78,7 +82,9 @@ public class EncryptionUtilTest { // expiration date changed should make the signature invalid // expiration should still be valid var clientKey = loadClientKey("client_keys/invalid_wrong_expiration.json"); - assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + Instant expireTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + + assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); } // @Test(expected = Exception.class) @@ -86,31 +92,119 @@ public class EncryptionUtilTest { public void testInvalidChangedKey() throws Exception { // changed public key no longer corresponding to the signature var clientKey = loadClientKey("client_keys/invalid_wrong_key.json"); - assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + Instant expireTimestamp = clientKey.getExpiry().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 = loadClientKey("client_keys/invalid_wrong_signature.json"); - assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + Instant expireTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + + assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); } @Test public void testValidClientKey() throws Exception { - var clientKey = loadClientKey("client_keys/valid.json"); - + var clientKey = loadClientKey("client_keys/valid_public_key.json"); var verificationTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + assertThat(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp), is(true)); } + @Test + public void testValidSignedNonce() throws Exception { + ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); + PublicKey clientPublicKey = clientKey.getKey(); + + SignatureTestData testData = loadSignatureResource("signature/valid_signature.json"); + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(true)); + } + + @Test + public void testIncorrectNonce() throws Exception { + ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); + PublicKey clientPublicKey = clientKey.getKey(); + + SignatureTestData testData = loadSignatureResource("signature/incorrect_nonce.json"); + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + } + + @Test + public void testIncorrectSalt() throws Exception { + // client generated + ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); + PublicKey clientPublicKey = clientKey.getKey(); + + SignatureTestData testData = loadSignatureResource("signature/incorrect_salt.json"); + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + } + + @Test + public void testIncorrectSignature() throws Exception { + // client generated + ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); + PublicKey clientPublicKey = clientKey.getKey(); + + SignatureTestData testData = loadSignatureResource("signature/incorrect_signature.json"); + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + } + + @Test + public void testWrongPublicKeySigned() throws Exception { + // load a different public key + ClientPublicKey clientKey = loadClientKey("client_keys/invalid_wrong_key.json"); + PublicKey clientPublicKey = clientKey.getKey(); + + SignatureTestData testData = loadSignatureResource("signature/valid_signature.json"); + byte[] nonce = testData.getNonce(); + SignatureData signature = testData.getSignature(); + long salt = signature.getSalt(); + assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + } + + private SignatureTestData loadSignatureResource(String resourceName) throws IOException { + var keyUrl = Resources.getResource(resourceName); + var encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII); + + return new Gson().fromJson(encodedSignature, SignatureTestData.class); + } + + private 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); + } + } + private ClientPublicKey loadClientKey(String path) throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { var keyUrl = Resources.getResource(path); - var gson = new Gson(); var lines = Resources.toString(keyUrl, StandardCharsets.US_ASCII); - var object = gson.fromJson(lines, JsonObject.class); + var object = new Gson().fromJson(lines, JsonObject.class); Instant expires = Instant.parse(object.getAsJsonPrimitive("expires_at").getAsString()); String key = object.getAsJsonPrimitive("key").getAsString(); @@ -120,10 +214,10 @@ public class EncryptionUtilTest { return new ClientPublicKey(expires, publicKey, signature); } - private RSAPublicKey parsePublicKey(String lines) + private RSAPublicKey parsePublicKey(String keySpec) throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { try ( - Reader reader = new StringReader(lines); + Reader reader = new StringReader(keySpec); PemReader pemReader = new PemReader(reader) ) { PemObject pemObject = pemReader.readPemObject(); 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..8ea85f6e --- /dev/null +++ b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SignatureTestData.java @@ -0,0 +1,60 @@ +/* + * 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.annotations.JsonAdapter; + +public class SignatureTestData { + + @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..1165e23d --- /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 file 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 + +* `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 +* `valid_public_key.json`: Extracted from actual file + +## 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 index 7ecc12e4..ea509fec 100644 --- a/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json @@ -1,5 +1,5 @@ { - "expires_at": "2022-06-12T09:46:26.421156927Z", - "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", - "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" + "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 index 37bb3ad0..98ea33f5 100644 --- a/bukkit/src/test/resources/client_keys/invalid_wrong_key.json +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_key.json @@ -1,5 +1,5 @@ { - "expires_at": "2022-06-12T10:46:26.421156927Z", + "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": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" + "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 index cbca4b16..2b80c2c9 100644 --- a/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json @@ -1,5 +1,5 @@ { - "expires_at": "2022-06-12T10:46:26.421156927Z", - "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", - "signature": "bYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" + "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.json b/bukkit/src/test/resources/client_keys/valid.json deleted file mode 100644 index a2d6a41a..00000000 --- a/bukkit/src/test/resources/client_keys/valid.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "expires_at": "2022-06-12T10:46:26.421156927Z", - "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", - "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" -} 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..cc603bcc --- /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 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 +* `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 + } +} From 0f17fe18f9f9e2f74758330aede9d47f2cc6ab3f Mon Sep 17 00:00:00 2001 From: games647 Date: Sat, 18 Jun 2022 15:55:45 +0200 Subject: [PATCH 11/37] Document origin of signing keys --- .../fastlogin/bukkit/listener/protocollib/EncryptionUtil.java | 2 ++ 1 file changed, 2 insertions(+) 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 713fa68a..ef210d78 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 @@ -153,6 +153,7 @@ class EncryptionUtil { } Signature verifier = Signature.getInstance("SHA1withRSA"); + // key of the signer verifier.initVerify(mojangSessionKey); verifier.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII)); return verifier.verify(clientKey.getSignature()); @@ -161,6 +162,7 @@ class EncryptionUtil { 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); From f44d7a6780729455d69cb4e8e8285ecb4cbd9d60 Mon Sep 17 00:00:00 2001 From: games647 Date: Wed, 22 Jun 2022 15:55:00 +0200 Subject: [PATCH 12/37] Forward client key to server diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index a3bb3d0..55a8f33 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -119,7 +119,7 @@ public class ProtocolLibListener extends PacketAdapter { case Continue: default: //player.getName() won't work at this state - onLogin(packetEvent, sender, username); + onLoginStart(packetEvent, sender, username); break; } } else { @@ -146,7 +146,6 @@ public class ProtocolLibListener extends PacketAdapter { long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true); byte[] signature = FuzzyReflection.getFieldValue(signatureData, byte[].class, true); - BukkitLoginSession session = plugin.getSession(sender.getAddress()); PublicKey publicKey = session.getClientPublicKey().getKey(); try { if (EncryptionUtil.verifySignedNonce(session.getVerifyToken(), publicKey, salt, signature)) { @@ -162,7 +161,7 @@ public class ProtocolLibListener extends PacketAdapter { } } - private void onLogin(PacketEvent packetEvent, Player player, String username) { + 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(); 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 d13a5c9..ed84298 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 @@ -43,6 +43,7 @@ import com.github.games647.craftapi.model.skin.SkinProperty; 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.lang.reflect.Method; @@ -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) { @@ -253,7 +254,7 @@ public class VerifyResponseTask implements Runnable { } //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); @@ -261,7 +262,8 @@ public class VerifyResponseTask implements Runnable { startPacket.getStrings().write(0, username); EquivalentConverter converter = BukkitConverters.getWrappedPublicKeyDataConverter(); - startPacket.getOptionals(converter).write(0, Optional.empty()); + var key = new WrappedProfileKeyData(clientKey.getExpiry(), clientKey.getKey(), sharedSecret); + startPacket.getOptionals(converter).write(0, Optional.of(key)); } else { //uuid is ignored by the packet definition WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username); --- .../bukkit/listener/protocollib/ProtocolLibListener.java | 5 ++--- .../bukkit/listener/protocollib/VerifyResponseTask.java | 8 +++++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index 425f1f5c..5bf9be76 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -119,7 +119,7 @@ public class ProtocolLibListener extends PacketAdapter { case Continue: default: //player.getName() won't work at this state - onLogin(packetEvent, sender, username); + onLoginStart(packetEvent, sender, username); break; } } else { @@ -146,7 +146,6 @@ public class ProtocolLibListener extends PacketAdapter { long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true); byte[] signature = FuzzyReflection.getFieldValue(signatureData, byte[].class, true); - BukkitLoginSession session = plugin.getSession(sender.getAddress()); PublicKey publicKey = session.getClientPublicKey().getKey(); try { if (EncryptionUtil.verifySignedNonce(session.getVerifyToken(), publicKey, salt, signature)) { @@ -162,7 +161,7 @@ public class ProtocolLibListener extends PacketAdapter { } } - private void onLogin(PacketEvent packetEvent, Player player, String username) { + 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(); 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 d13a5c9c..ed842980 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 @@ -43,6 +43,7 @@ import com.github.games647.craftapi.model.skin.SkinProperty; 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.lang.reflect.Method; @@ -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) { @@ -253,7 +254,7 @@ public class VerifyResponseTask implements Runnable { } //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); @@ -261,7 +262,8 @@ public class VerifyResponseTask implements Runnable { startPacket.getStrings().write(0, username); EquivalentConverter converter = BukkitConverters.getWrappedPublicKeyDataConverter(); - startPacket.getOptionals(converter).write(0, Optional.empty()); + var key = new WrappedProfileKeyData(clientKey.getExpiry(), clientKey.getKey(), sharedSecret); + startPacket.getOptionals(converter).write(0, Optional.of(key)); } else { //uuid is ignored by the packet definition WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username); From a5942cba74429193f2a0aa657cda0a6fd0907e91 Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 12:23:24 +0200 Subject: [PATCH 13/37] Use HTML links --- .../github/games647/fastlogin/bukkit/hook/AuthMeHook.java | 6 +++--- .../games647/fastlogin/bukkit/hook/CrazyLoginHook.java | 4 ++-- .../github/games647/fastlogin/bukkit/hook/LogItHook.java | 2 +- .../games647/fastlogin/bukkit/hook/LoginSecurityHook.java | 6 +++--- .../games647/fastlogin/bukkit/hook/UltraAuthHook.java | 4 ++-- .../github/games647/fastlogin/bukkit/hook/xAuthHook.java | 4 ++-- .../bukkit/listener/protocollib/ManualNameChange.java | 2 +- .../games647/fastlogin/bungee/hook/BungeeAuthHook.java | 6 ++++-- .../bungee/hook/BungeeCordAuthenticatorBungeeHook.java | 4 ++-- 9 files changed, 20 insertions(+), 18 deletions(-) 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..fecfb73b 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 @@ -41,13 +41,13 @@ 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/protocollib/ManualNameChange.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ManualNameChange.java index 78a8c474..cdf39997 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 @@ -44,7 +44,7 @@ import com.comphenix.protocol.ProtocolLibrary; * 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/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 { From b041a892095937c6f62b8cda47af29dbddd1b919 Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 12:32:22 +0200 Subject: [PATCH 14/37] Typo fixes --- CHANGELOG.md | 8 ++++---- bukkit/src/test/resources/client_keys/README.md | 2 +- .../core/antibot/ProxyAgnosticMojangResolver.java | 2 +- .../games647/fastlogin/core/shared/PlatformPlugin.java | 2 +- .../games647/fastlogin/velocity/task/ForceLoginTask.java | 3 +-- 5 files changed, 8 insertions(+), 9 deletions(-) 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/bukkit/src/test/resources/client_keys/README.md b/bukkit/src/test/resources/client_keys/README.md index 1165e23d..dd119ad5 100644 --- a/bukkit/src/test/resources/client_keys/README.md +++ b/bukkit/src/test/resources/client_keys/README.md @@ -1,6 +1,6 @@ # About -This contains test resources for the unit tests. The file are extracted from the Minecraft directory with slight +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. 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/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/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, From 1c528fb9cb135e6590ec12d356f390c01c04d084 Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 12:34:14 +0200 Subject: [PATCH 15/37] Clean up 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 49ff879..7149238 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 @@ -36,6 +36,7 @@ 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; @@ -62,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; 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 a6ac9b7..6153338 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 26b6d31..9f1ef98 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 8c61330..6412c6d 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 6b2edfc..5bf6df9 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 146efb0..0904826 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 fecfb73..45d5a5f 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,18 +28,20 @@ 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: ... *

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 e444ed9..44ff6ea 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/ManualNameChange.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ManualNameChange.java index cdf3999..2a36e88 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,8 +37,6 @@ 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. *
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 485c065..d3b38ea 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 @@ -49,7 +49,7 @@ public class NameCheckTask extends JoinManagement publicKey = packetEvent.getPacket().getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()).read(0); + Optional clientKey = packetEvent.getPacket().getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()).read(0); - super.onLogin(username, new ProtocolLibLoginSource(player, random, publicKey.get(), this.publicKey)); + super.onLogin(username, new ProtocolLibLoginSource(player, random, clientKey.get(), serverKey)); } finally { ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent); } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index 55a8f33..89d855d 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -127,7 +127,7 @@ public class ProtocolLibListener extends PacketAdapter { } } - private Boolean isFastLoginPacket(PacketEvent packetEvent) { + private boolean isFastLoginPacket(PacketEvent packetEvent) { return packetEvent.getPacket().getMeta(SOURCE_META_KEY) .map(val -> val.equals(plugin.getName())) .orElse(false); @@ -146,7 +146,7 @@ public class ProtocolLibListener extends PacketAdapter { long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true); byte[] signature = FuzzyReflection.getFieldValue(signatureData, byte[].class, true); - PublicKey publicKey = session.getClientPublicKey().getKey(); + PublicKey publicKey = session.getClientPublicKey().key(); try { if (EncryptionUtil.verifySignedNonce(session.getVerifyToken(), publicKey, salt, signature)) { packetEvent.getAsyncMarker().incrementProcessingDelay(); 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 d87b602..d4c1130 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 @@ -33,7 +33,6 @@ import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; 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; @@ -64,7 +63,7 @@ class ProtocolLibLoginSource implements LoginSource { } @Override - public void enableOnlinemode() throws InvocationTargetException { + public void enableOnlinemode() { verifyToken = EncryptionUtil.generateVerifyToken(random); /* @@ -92,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); 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 7d43835..3d4a807 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/task/FloodgateAuthTask.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/task/FloodgateAuthTask.java index acb3597..ef9fed0 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/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 365a198..6503ff9 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 b350763..2128cab 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/task/AsyncToggleMessage.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/task/AsyncToggleMessage.java index 98c384c..2965855 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 72719cf..5133198 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/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 b6802a6..9caadef 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 7e6a1c2..1b555f1 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 c8c7bea..873a855 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/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java b/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java index 3f753cd..34f196a 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,8 +32,8 @@ 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 { @@ -43,7 +43,7 @@ public class TickingRateLimiterTest { * Always expired */ @Test - public void allowExpire() throws InterruptedException { + public void allowExpire() { int size = 3; FakeTicker ticker = new FakeTicker(5_000_000L); @@ -51,17 +51,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 +69,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 +90,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 +108,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/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 33c8c31..902fb03 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; @@ -121,7 +123,7 @@ public class ConnectListener { } } - private List removeSkin(List oldProperties) { + private List removeSkin(Collection oldProperties) { List newProperties = new ArrayList<>(oldProperties.size() - 1); for (GameProfile.Property property : oldProperties) { if (!"textures".equals(property.getName())) 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 12444fc..8dc7d1c 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 { --- .../fastlogin/bukkit/BungeeManager.java | 3 +- .../bukkit/command/CrackedCommand.java | 4 +- .../bukkit/command/PremiumCommand.java | 4 +- .../event/BukkitFastLoginAutoLoginEvent.java | 1 + .../event/BukkitFastLoginPreLoginEvent.java | 1 + .../BukkitFastLoginPremiumToggleEvent.java | 1 + .../fastlogin/bukkit/hook/AuthMeHook.java | 6 ++- .../bukkit/listener/PaperCacheListener.java | 1 + .../protocollib/ManualNameChange.java | 3 +- .../listener/protocollib/NameCheckTask.java | 10 ++--- .../protocollib/ProtocolLibListener.java | 4 +- .../protocollib/ProtocolLibLoginSource.java | 5 +-- .../protocollib/SkinApplyListener.java | 5 ++- .../bukkit/task/FloodgateAuthTask.java | 10 ++--- .../event/BungeeFastLoginAutoLoginEvent.java | 1 + .../event/BungeeFastLoginPreLoginEvent.java | 1 + .../bungee/task/AsyncToggleMessage.java | 2 +- .../bungee/task/FloodgateAuthTask.java | 12 +++--- .../core/hooks/DefaultPasswordGenerator.java | 4 +- .../core/hooks/bedrock/FloodgateService.java | 7 ++-- .../core/shared/ForceLoginManagement.java | 2 +- .../core/TickingRateLimiterTest.java | 40 +++++++++---------- .../velocity/listener/ConnectListener.java | 4 +- .../velocity/task/AsyncToggleMessage.java | 1 + 24 files changed, 71 insertions(+), 61 deletions(-) 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..c6fb4f02 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 @@ -35,6 +35,7 @@ import com.google.common.io.ByteStreams; import java.io.IOException; 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 +62,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; 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 fecfb73b..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,18 +28,20 @@ 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: ... *

    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/ManualNameChange.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ManualNameChange.java index cdf39997..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,8 +37,6 @@ 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. *
    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 485c065d..d3b38eae 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 @@ -49,7 +49,7 @@ public class NameCheckTask extends JoinManagement publicKey = packetEvent.getPacket().getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()).read(0); + Optional clientKey = packetEvent.getPacket().getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()).read(0); - super.onLogin(username, new ProtocolLibLoginSource(player, random, publicKey.get(), this.publicKey)); + super.onLogin(username, new ProtocolLibLoginSource(player, random, clientKey.get(), serverKey)); } finally { ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent); } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index 5bf9be76..10377b04 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -127,7 +127,7 @@ public class ProtocolLibListener extends PacketAdapter { } } - private Boolean isFastLoginPacket(PacketEvent packetEvent) { + private boolean isFastLoginPacket(PacketEvent packetEvent) { return packetEvent.getPacket().getMeta(SOURCE_META_KEY) .map(val -> val.equals(plugin.getName())) .orElse(false); @@ -146,7 +146,7 @@ public class ProtocolLibListener extends PacketAdapter { long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true); byte[] signature = FuzzyReflection.getFieldValue(signatureData, byte[].class, true); - PublicKey publicKey = session.getClientPublicKey().getKey(); + PublicKey publicKey = session.getClientPublicKey().key(); try { if (EncryptionUtil.verifySignedNonce(session.getVerifyToken(), publicKey, salt, signature)) { packetEvent.getAsyncMarker().incrementProcessingDelay(); 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 d87b602d..d4c1130c 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 @@ -33,7 +33,6 @@ import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; 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; @@ -64,7 +63,7 @@ class ProtocolLibLoginSource implements LoginSource { } @Override - public void enableOnlinemode() throws InvocationTargetException { + public void enableOnlinemode() { verifyToken = EncryptionUtil.generateVerifyToken(random); /* @@ -92,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); 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/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/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/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/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/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java b/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java index 3f753cd4..34f196aa 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,8 +32,8 @@ 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 { @@ -43,7 +43,7 @@ public class TickingRateLimiterTest { * Always expired */ @Test - public void allowExpire() throws InterruptedException { + public void allowExpire() { int size = 3; FakeTicker ticker = new FakeTicker(5_000_000L); @@ -51,17 +51,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 +69,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 +90,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 +108,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/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..902fb039 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; @@ -121,7 +123,7 @@ public class ConnectListener { } } - private List removeSkin(List oldProperties) { + private List removeSkin(Collection oldProperties) { List newProperties = new ArrayList<>(oldProperties.size() - 1); for (GameProfile.Property property : oldProperties) { if (!"textures".equals(property.getName())) 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 { From 53e6fe6ddfda61c6c42e070a6bf28fa4e61ce2dc Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 12:35:25 +0200 Subject: [PATCH 16/37] Missing synchronization access to the username --- .../com/github/games647/fastlogin/core/shared/LoginSession.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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; } From d9bf7267a6754c1359882942917edcfa887f361b Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 12:36:02 +0200 Subject: [PATCH 17/37] Test valid server key pairs --- .../protocollib/EncryptionUtilTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) 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 63a24bac..781cca3a 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 @@ -36,7 +36,9 @@ import java.io.Reader; import java.io.StringReader; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; +import java.security.KeyPair; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; import java.security.PublicKey; import java.security.SecureRandom; import java.security.interfaces.RSAPrivateKey; @@ -56,6 +58,8 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertTrue; + public class EncryptionUtilTest { @Test @@ -67,6 +71,20 @@ public class EncryptionUtilTest { assertThat(token.length, is(4)); } + @Test + public void testServerKey() { + KeyPair keyPair = EncryptionUtil.generateKeyPair(); + + PrivateKey privateKey = keyPair.getPrivate(); + assertThat(privateKey.getAlgorithm(), is("RSA")); + + PublicKey publicKey = keyPair.getPublic(); + assertThat(publicKey.getAlgorithm(), is("RSA")); + + // clients accept larger values, but we shouldn't crash them + assertTrue(publicKey.getEncoded().length > (1024 / 8)); + } + @Test public void testExpiredClientKey() throws Exception { var clientKey = loadClientKey("client_keys/valid_public_key.json"); From 11077a002d61dda40709885bfce2a7e86d133051 Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 12:37:13 +0200 Subject: [PATCH 18/37] Migrate public key to record --- .../listener/protocollib/EncryptionUtil.java | 10 ++++---- .../protocollib/VerifyResponseTask.java | 2 +- .../protocollib/packet/ClientPublicKey.java | 23 +------------------ .../protocollib/EncryptionUtilTest.java | 20 ++++++++-------- 4 files changed, 17 insertions(+), 38 deletions(-) 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 ef210d78..d124eb07 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 @@ -147,8 +147,8 @@ class EncryptionUtil { } public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimstamp) - throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { - if (!verifyTimstamp.isBefore(clientKey.getExpiry())) { + throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { + if (!verifyTimstamp.isBefore(clientKey.expiry())) { return false; } @@ -156,7 +156,7 @@ class EncryptionUtil { // key of the signer verifier.initVerify(mojangSessionKey); verifier.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII)); - return verifier.verify(clientKey.getSignature()); + return verifier.verify(clientKey.signature()); } public static boolean verifySignedNonce(byte[] nonce, PublicKey clientKey, long signatureSalt, byte[] signature) @@ -180,8 +180,8 @@ class EncryptionUtil { } private static String toSignable(ClientPublicKey clientPublicKey) { - long expiry = clientPublicKey.getExpiry().toEpochMilli(); - String encoded = KEY_ENCODER.encodeToString(clientPublicKey.getKey().getEncoded()); + 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"; } 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 ed842980..5a156d64 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 @@ -262,7 +262,7 @@ public class VerifyResponseTask implements Runnable { startPacket.getStrings().write(0, username); EquivalentConverter converter = BukkitConverters.getWrappedPublicKeyDataConverter(); - var key = new WrappedProfileKeyData(clientKey.getExpiry(), clientKey.getKey(), sharedSecret); + var key = new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), sharedSecret); startPacket.getOptionals(converter).write(0, Optional.of(key)); } else { //uuid is ignored by the packet definition 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 index f14b16f1..24c4733f 100644 --- 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 @@ -28,27 +28,6 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib.packet; import java.security.PublicKey; import java.time.Instant; -public class ClientPublicKey { +public record ClientPublicKey(Instant expiry, PublicKey key, byte[] signature) { - private final Instant expiry; - private final PublicKey key; - private final byte[] signature; - - public ClientPublicKey(Instant expiry, PublicKey key, byte[] signature) { - this.expiry = expiry; - this.key = key; - this.signature = signature; - } - - public Instant getExpiry() { - return expiry; - } - - public PublicKey getKey() { - return key; - } - - public byte[] getSignature() { - return signature; - } } 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 781cca3a..dbe41ee3 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 @@ -90,7 +90,7 @@ public class EncryptionUtilTest { var clientKey = loadClientKey("client_keys/valid_public_key.json"); // Client expires at the exact second mentioned, so use it for verification - var expiredTimestamp = clientKey.getExpiry(); + var expiredTimestamp = clientKey.expiry(); assertThat(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp), is(false)); } @@ -100,7 +100,7 @@ public class EncryptionUtilTest { // expiration date changed should make the signature invalid // expiration should still be valid var clientKey = loadClientKey("client_keys/invalid_wrong_expiration.json"); - Instant expireTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS); assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); } @@ -110,7 +110,7 @@ public class EncryptionUtilTest { public void testInvalidChangedKey() throws Exception { // changed public key no longer corresponding to the signature var clientKey = loadClientKey("client_keys/invalid_wrong_key.json"); - Instant expireTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS); assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); } @@ -119,7 +119,7 @@ public class EncryptionUtilTest { public void testInvalidChangedSignature() throws Exception { // signature modified no longer corresponding to key and expiration date var clientKey = loadClientKey("client_keys/invalid_wrong_signature.json"); - Instant expireTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS); assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); } @@ -127,7 +127,7 @@ public class EncryptionUtilTest { @Test public void testValidClientKey() throws Exception { var clientKey = loadClientKey("client_keys/valid_public_key.json"); - var verificationTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + var verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS); assertThat(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp), is(true)); } @@ -135,7 +135,7 @@ public class EncryptionUtilTest { @Test public void testValidSignedNonce() throws Exception { ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); - PublicKey clientPublicKey = clientKey.getKey(); + PublicKey clientPublicKey = clientKey.key(); SignatureTestData testData = loadSignatureResource("signature/valid_signature.json"); byte[] nonce = testData.getNonce(); @@ -147,7 +147,7 @@ public class EncryptionUtilTest { @Test public void testIncorrectNonce() throws Exception { ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); - PublicKey clientPublicKey = clientKey.getKey(); + PublicKey clientPublicKey = clientKey.key(); SignatureTestData testData = loadSignatureResource("signature/incorrect_nonce.json"); byte[] nonce = testData.getNonce(); @@ -160,7 +160,7 @@ public class EncryptionUtilTest { public void testIncorrectSalt() throws Exception { // client generated ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); - PublicKey clientPublicKey = clientKey.getKey(); + PublicKey clientPublicKey = clientKey.key(); SignatureTestData testData = loadSignatureResource("signature/incorrect_salt.json"); byte[] nonce = testData.getNonce(); @@ -173,7 +173,7 @@ public class EncryptionUtilTest { public void testIncorrectSignature() throws Exception { // client generated ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); - PublicKey clientPublicKey = clientKey.getKey(); + PublicKey clientPublicKey = clientKey.key(); SignatureTestData testData = loadSignatureResource("signature/incorrect_signature.json"); byte[] nonce = testData.getNonce(); @@ -186,7 +186,7 @@ public class EncryptionUtilTest { public void testWrongPublicKeySigned() throws Exception { // load a different public key ClientPublicKey clientKey = loadClientKey("client_keys/invalid_wrong_key.json"); - PublicKey clientPublicKey = clientKey.getKey(); + PublicKey clientPublicKey = clientKey.key(); SignatureTestData testData = loadSignatureResource("signature/valid_signature.json"); byte[] nonce = testData.getNonce(); From 3767b022a9e2b2db3c707bda8df32ee8b22d8989 Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 12:39:28 +0200 Subject: [PATCH 19/37] Migrate to guava hashing to replace unneeded exceptions --- .../listener/protocollib/EncryptionUtil.java | 51 +++++++------------ 1 file changed, 19 insertions(+), 32 deletions(-) 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 d124eb07..40108196 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 @@ -26,6 +26,8 @@ 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; @@ -37,7 +39,6 @@ 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; @@ -50,7 +51,10 @@ import java.util.Base64; import java.util.Base64.Encoder; import java.util.Random; +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; @@ -123,14 +127,8 @@ class EncryptionUtil { */ 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 ""; + byte[] serverHash = getServerIdHash(sessionId, publicKey, sharedSecret); + return (new BigInteger(serverHash)).toString(16); } /** @@ -141,7 +139,9 @@ class EncryptionUtil { * @return shared secret key * @throws GeneralSecurityException if it fails to decrypt the data */ - public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] sharedKey) throws GeneralSecurityException { + public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] sharedKey) + throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, + BadPaddingException, InvalidKeyException { // SecretKey a(PrivateKey var0, byte[] var1) return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES"); } @@ -185,37 +185,24 @@ class EncryptionUtil { return expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n"; } - public static byte[] decrypt(PrivateKey key, byte[] data) throws GeneralSecurityException { + public static byte[] decrypt(PrivateKey key, byte[] data) + throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, + IllegalBlockSizeException, BadPaddingException { // b(Key var0, byte[] var1) Cipher cipher = Cipher.getInstance(key.getAlgorithm()); cipher.init(Cipher.DECRYPT_MODE, key); - return decrypt(cipher, data); - } - - /** - * 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) return cipher.doFinal(data); } - private static byte[] getServerIdHash(String sessionId, PublicKey publicKey, SecretKey sharedSecret) - throws NoSuchAlgorithmException { + private static byte[] getServerIdHash(String sessionId, PublicKey publicKey, SecretKey sharedSecret) { // byte[] a(String var0, PublicKey var1, SecretKey var2) - MessageDigest digest = MessageDigest.getInstance("SHA-1"); + 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(); } } From 700b889aa932e4bfd60300805d31fbd0659089b1 Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 19:25:12 +0200 Subject: [PATCH 20/37] Improve precision and flexibility of encrypt methods --- .../bukkit/listener/protocollib/EncryptionUtil.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) 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 40108196..8a0b9dca 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 @@ -49,7 +49,7 @@ import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.util.Base64; import java.util.Base64.Encoder; -import java.util.Random; +import java.util.random.RandomGenerator; import javax.crypto.BadPaddingException; import javax.crypto.Cipher; @@ -110,7 +110,7 @@ class EncryptionUtil { * @param random random generator * @return an error with 4 bytes long */ - public static byte[] generateVerifyToken(Random random) { + public static byte[] generateVerifyToken(RandomGenerator random) { // extracted from LoginListener byte[] token = new byte[VERIFY_TOKEN_LENGTH]; random.nextBytes(token); @@ -120,14 +120,14 @@ 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 * @return the server id formatted as a hexadecimal string. */ - public static String getServerIdHashString(String sessionId, SecretKey sharedSecret, PublicKey publicKey) { + public static String getServerIdHashString(String serverId, SecretKey sharedSecret, PublicKey publicKey) { // found in LoginListener - byte[] serverHash = getServerIdHash(sessionId, publicKey, sharedSecret); + byte[] serverHash = getServerIdHash(serverId, publicKey, sharedSecret); return (new BigInteger(serverHash)).toString(16); } @@ -185,7 +185,7 @@ class EncryptionUtil { return expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n"; } - public static byte[] decrypt(PrivateKey key, byte[] data) + private static byte[] decrypt(PrivateKey key, byte[] data) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { // b(Key var0, byte[] var1) From a0fddd69aabbf41679feb5876f73594c551a17fa Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 19:26:15 +0200 Subject: [PATCH 21/37] Decrease necessary entropy for running tests --- .../bukkit/listener/protocollib/EncryptionUtilTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 dbe41ee3..e50423a2 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 @@ -64,7 +64,7 @@ public class EncryptionUtilTest { @Test public void testVerifyToken() { - SecureRandom random = new SecureRandom(); + var random = ThreadLocalRandom.current(); byte[] token = EncryptionUtil.generateVerifyToken(random); assertThat(token, notNullValue()); From 1d46640b42e2cc1e2328e034b3033ddf3cc1cfb0 Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 19:28:37 +0200 Subject: [PATCH 22/37] Limit length of server keys --- .../bukkit/listener/protocollib/EncryptionUtilTest.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) 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 e50423a2..582496d3 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 @@ -75,14 +75,15 @@ public class EncryptionUtilTest { public void testServerKey() { KeyPair keyPair = EncryptionUtil.generateKeyPair(); - PrivateKey privateKey = keyPair.getPrivate(); + Key privateKey = keyPair.getPrivate(); assertThat(privateKey.getAlgorithm(), is("RSA")); - PublicKey publicKey = keyPair.getPublic(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); assertThat(publicKey.getAlgorithm(), is("RSA")); - // clients accept larger values, but we shouldn't crash them - assertTrue(publicKey.getEncoded().length > (1024 / 8)); + // 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 From e8bb3ec7a9e37c170876effab90ea96e8405d7e0 Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 19:28:54 +0200 Subject: [PATCH 23/37] Validate other encryption methods --- .../fastlogin/bukkit/PremiumPlaceholder.java | 12 +++ .../protocollib/EncryptionUtilTest.java | 96 ++++++++++++++++++- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/PremiumPlaceholder.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/PremiumPlaceholder.java index d2acea0c..b839afee 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/PremiumPlaceholder.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/PremiumPlaceholder.java @@ -25,6 +25,8 @@ */ package com.github.games647.fastlogin.bukkit; +import java.util.List; + import me.clip.placeholderapi.expansion.PlaceholderExpansion; import org.bukkit.OfflinePlayer; @@ -40,6 +42,16 @@ public class PremiumPlaceholder extends PlaceholderExpansion { this.plugin = plugin; } + @Override + public boolean persist() { + return true; + } + + @Override + public @NotNull List 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/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 582496d3..34579b30 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 @@ -27,6 +27,7 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib; 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 com.google.common.io.Resources; import com.google.gson.Gson; import com.google.gson.JsonObject; @@ -34,13 +35,14 @@ import com.google.gson.JsonObject; import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; @@ -49,12 +51,22 @@ import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Base64; +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.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; +import org.jetbrains.annotations.NotNull; 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; @@ -95,7 +107,6 @@ public class EncryptionUtilTest { assertThat(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp), is(false)); } - // @Test(expected = Exception.class) @Test public void testInvalidChangedExpiration() throws Exception { // expiration date changed should make the signature invalid @@ -106,7 +117,6 @@ public class EncryptionUtilTest { assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); } - // @Test(expected = Exception.class) @Test public void testInvalidChangedKey() throws Exception { // changed public key no longer corresponding to the signature @@ -133,6 +143,84 @@ public class EncryptionUtilTest { 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 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 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 = loadClientKey("client_keys/valid_public_key.json").key(); + + String sessionHash = getServerHash(serverId, sharedSecret, serverPK); + assertThat(EncryptionUtil.getServerIdHashString(serverId, sharedSecret, serverPK), is(sessionHash)); + } + + @NotNull + private String getServerHash(String 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 + 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 = 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() throws Exception { + 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 = loadClientKey("client_keys/valid_public_key.json"); From 34e63b7eae890d1f729d6f3b9a444c6361a442ec Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 24 Jun 2022 16:46:46 +0200 Subject: [PATCH 24/37] Make profile key optional --- .../listener/protocollib/EncryptionUtil.java | 2 +- .../listener/protocollib/NameCheckTask.java | 5 +++-- .../listener/protocollib/ProtocolLibListener.java | 15 ++++++--------- .../protocollib/ProtocolLibLoginSource.java | 4 ++-- .../protocollib/packet/ClientPublicKey.java | 3 +++ 5 files changed, 15 insertions(+), 14 deletions(-) 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 8a0b9dca..179cc45f 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 @@ -148,7 +148,7 @@ class EncryptionUtil { public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimstamp) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - if (!verifyTimstamp.isBefore(clientKey.expiry())) { + if (clientKey.isExpired(verifyTimstamp)) { return false; } 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 d3b38eae..e58aed61 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 @@ -71,9 +71,10 @@ public class NameCheckTask extends JoinManagement clientKey = packetEvent.getPacket().getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()).read(0); + Optional clientKey = packetEvent.getPacket() + .getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()).read(0); - super.onLogin(username, new ProtocolLibLoginSource(player, random, clientKey.get(), serverKey)); + super.onLogin(username, new ProtocolLibLoginSource(player, random, serverKey, clientKey.orElse(null))); } finally { ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent); } diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index 10377b04..ea50a75f 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -31,8 +31,8 @@ import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.WrappedGameProfile; -import com.comphenix.protocol.wrappers.WrappedProfilePublicKey; import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; @@ -173,7 +173,10 @@ public class ProtocolLibListener extends PacketAdapter { username = (String) packetEvent.getPacket().getMeta("original_name").get(); } - if (!verifyPublicKey(packet)) { + PacketContainer packet = packetEvent.getPacket(); + WrappedProfileKeyData profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()) + .read(0).orElse(null); + if (profileKey != null && !verifyPublicKey(profileKey)) { plugin.getLog().warn("Invalid public key from player {}", username); return; } @@ -185,13 +188,7 @@ public class ProtocolLibListener extends PacketAdapter { plugin.getScheduler().runAsync(nameCheckTask); } - private boolean verifyPublicKey(PacketContainer packet) { - WrappedProfileKeyData profileKey = packet.getProfilePublicKeys().optionRead(0) - .map(WrappedProfilePublicKey::getKeyData).orElse(null); - if (profileKey == null) { - return true; - } - + private boolean verifyPublicKey(WrappedProfileKeyData profileKey) { Instant expires = profileKey.getExpireTime(); PublicKey key = profileKey.getKey(); byte[] signature = profileKey.getSignature(); 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 d4c1130c..55701e40 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 @@ -55,11 +55,11 @@ class ProtocolLibLoginSource implements LoginSource { private final String serverId = ""; private byte[] verifyToken; - public ProtocolLibLoginSource(Player player, Random random, WrappedProfileKeyData clientPublicKey, PublicKey serverPublicKey) { + public ProtocolLibLoginSource(Player player, Random random, PublicKey serverPublicKey, WrappedProfileKeyData clientPublicKey) { this.player = player; this.random = random; - this.clientPublicKey = clientPublicKey; this.publicKey = serverPublicKey; + this.clientPublicKey = clientPublicKey; } @Override 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 index 24c4733f..510d77d1 100644 --- 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 @@ -30,4 +30,7 @@ import java.time.Instant; public record ClientPublicKey(Instant expiry, PublicKey key, byte[] signature) { + public boolean isExpired(Instant verifyTimestamp) { + return verifyTimestamp.isBefore(expiry); + } } From ce59172839ebc7f596ed543f9e353156d291aa27 Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 24 Jun 2022 16:58:14 +0200 Subject: [PATCH 25/37] Log signature verification errors to help administrators diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index c3e159a..da28f38 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -154,9 +154,11 @@ public class ProtocolLibListener extends PacketAdapter { plugin.getScheduler().runAsync(verifyTask); } else { sender.kickPlayer("Invalid signature"); + plugin.getLog().error("Invalid signature from player {}", sender); } - } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException signatureEx) { sender.kickPlayer("Invalid signature"); + plugin.getLog().error("Invalid signature from player {}", sender, signatureEx); } } } --- .../bukkit/listener/protocollib/ProtocolLibListener.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index ea50a75f..f8273292 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -154,9 +154,11 @@ public class ProtocolLibListener extends PacketAdapter { plugin.getScheduler().runAsync(verifyTask); } else { sender.kickPlayer("Invalid signature"); + plugin.getLog().error("Invalid signature from player {}", sender); } - } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) { + } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException signatureEx) { sender.kickPlayer("Invalid signature"); + plugin.getLog().error("Invalid signature from player {}", sender, signatureEx); } } } From c118430bf58b5fc96fc78e09b95da1d9b23034b1 Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 24 Jun 2022 17:08:27 +0200 Subject: [PATCH 26/37] Kick players using an invalid public key --- .../bukkit/listener/protocollib/ProtocolLibListener.java | 1 + core/src/main/resources/messages.yml | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index f8273292..5b4fb135 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -179,6 +179,7 @@ public class ProtocolLibListener extends PacketAdapter { WrappedProfileKeyData profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()) .read(0).orElse(null); if (profileKey != null && !verifyPublicKey(profileKey)) { + player.kickPlayer(plugin.getCore().getMessage("invalid-public-key")); plugin.getLog().warn("Invalid public key from player {}", username); return; } 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 ================================ From 91c01e34224e6c9f1c553d89df31eb5a23fa7593 Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 24 Jun 2022 17:09:54 +0200 Subject: [PATCH 27/37] Reuse verify token tick message for signature verification diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index 6c578ff..92e1dcd 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -153,11 +153,11 @@ public class ProtocolLibListener extends PacketAdapter { Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair); plugin.getScheduler().runAsync(verifyTask); } else { - sender.kickPlayer("Invalid signature"); + sender.kickPlayer(plugin.getCore().getMessage("invalid-verify-token")); plugin.getLog().error("Invalid signature from player {}", sender); } } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException signatureEx) { - sender.kickPlayer("Invalid signature"); + sender.kickPlayer(plugin.getCore().getMessage("invalid-verify-token")); plugin.getLog().error("Invalid signature from player {}", sender, signatureEx); } } --- .../bukkit/listener/protocollib/ProtocolLibListener.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index 5b4fb135..f973a901 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -153,11 +153,11 @@ public class ProtocolLibListener extends PacketAdapter { Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, session, sharedSecret, keyPair); plugin.getScheduler().runAsync(verifyTask); } else { - sender.kickPlayer("Invalid signature"); + sender.kickPlayer(plugin.getCore().getMessage("invalid-verify-token")); plugin.getLog().error("Invalid signature from player {}", sender); } } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException signatureEx) { - sender.kickPlayer("Invalid signature"); + sender.kickPlayer(plugin.getCore().getMessage("invalid-verify-token")); plugin.getLog().error("Invalid signature from player {}", sender, signatureEx); } } From cf356099a0adcc341cb7c60ae691e0789727b8b9 Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 24 Jun 2022 17:12:27 +0200 Subject: [PATCH 28/37] Fix public key timestamp verification. --- .../bukkit/listener/protocollib/packet/ClientPublicKey.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 510d77d1..e375e294 100644 --- 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 @@ -31,6 +31,6 @@ import java.time.Instant; public record ClientPublicKey(Instant expiry, PublicKey key, byte[] signature) { public boolean isExpired(Instant verifyTimestamp) { - return verifyTimestamp.isBefore(expiry); + return !verifyTimestamp.isBefore(expiry); } } From bb1cbb79f22c450a8aad71a67ad34dbcd085b2d6 Mon Sep 17 00:00:00 2001 From: games647 Date: Tue, 28 Jun 2022 18:40:51 +0200 Subject: [PATCH 29/37] Support newer Paper configuration with clearer error messages --- .../fastlogin/bukkit/BungeeManager.java | 62 ++++++++++++++----- 1 file changed, 45 insertions(+), 17 deletions(-) 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 c6fb4f02..5c030bb9 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,6 +33,7 @@ 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; @@ -88,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); + if (isProxySupported("com.destroystokyo.paper.PaperConfig", "velocitySupport")) { + return true; + } + } catch (ClassNotFoundException classNotFoundException) { + // try again using the newer Paper configuration + 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); } 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() { @@ -148,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); @@ -177,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. From 944db748e84998201d9f0abb13a0896363c5f630 Mon Sep 17 00:00:00 2001 From: games647 Date: Sat, 2 Jul 2022 12:36:58 +0200 Subject: [PATCH 30/37] Upgrade GitHub actions to use Java 18 --- .github/workflows/codeql-analysis.yml | 3 +-- .github/workflows/maven.yml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) 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..1046981b 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) From 339156be183d1341298cb55d8fb037bf14ff26e8 Mon Sep 17 00:00:00 2001 From: games647 Date: Sat, 2 Jul 2022 12:37:49 +0200 Subject: [PATCH 31/37] Use multiple threads to build the project --- .github/workflows/maven.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 1046981b..790c641e 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -39,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 From 7c8de84a3486c7902c7b7f6f77bbc788d7461e3b Mon Sep 17 00:00:00 2001 From: games647 Date: Sat, 2 Jul 2022 12:38:14 +0200 Subject: [PATCH 32/37] Bump dependencies --- bukkit/pom.xml | 8 ++++---- .../bukkit/listener/protocollib/EncryptionUtil.java | 7 ------- bukkit/src/test/resources/client_keys/README.md | 2 +- bukkit/src/test/resources/signature/README.md | 4 ++-- bungee/pom.xml | 2 +- core/pom.xml | 7 ++----- .../games647/fastlogin/core/TickingRateLimiterTest.java | 2 -- velocity/pom.xml | 6 +++--- .../fastlogin/velocity/listener/ConnectListener.java | 5 +++-- 9 files changed, 16 insertions(+), 27 deletions(-) diff --git a/bukkit/pom.xml b/bukkit/pom.xml index c1f63ba1..28c585a0 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -161,7 +161,7 @@ io.papermc.paper paper-api - 1.18-R0.1-SNAPSHOT + 1.19-R0.1-SNAPSHOT provided @@ -211,7 +211,7 @@ com.github.ProtocolSupport ProtocolSupport - 3a80c661fe + master-66b494a8dd-1 provided @@ -265,7 +265,7 @@ me.clip placeholderapi - 2.11.1 + 2.11.2 provided true @@ -280,7 +280,7 @@ fr.xephi authme - 5.4.0 + 5.6.0-beta2 provided true 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 179cc45f..53189ea8 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 @@ -91,7 +91,6 @@ class EncryptionUtil { * @return The RSA key pair. */ public static KeyPair generateKeyPair() { - // KeyPair b() try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM); @@ -111,7 +110,6 @@ class EncryptionUtil { * @return an error with 4 bytes long */ public static byte[] generateVerifyToken(RandomGenerator random) { - // extracted from LoginListener byte[] token = new byte[VERIFY_TOKEN_LENGTH]; random.nextBytes(token); return token; @@ -126,7 +124,6 @@ class EncryptionUtil { * @return the server id formatted as a hexadecimal string. */ public static String getServerIdHashString(String serverId, SecretKey sharedSecret, PublicKey publicKey) { - // found in LoginListener byte[] serverHash = getServerIdHash(serverId, publicKey, sharedSecret); return (new BigInteger(serverHash)).toString(16); } @@ -142,7 +139,6 @@ class EncryptionUtil { public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] sharedKey) throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException, BadPaddingException, InvalidKeyException { - // SecretKey a(PrivateKey var0, byte[] var1) return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES"); } @@ -188,17 +184,14 @@ class EncryptionUtil { private static byte[] decrypt(PrivateKey key, byte[] data) throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { - // b(Key var0, byte[] var1) 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) { - // byte[] a(String var0, PublicKey var1, SecretKey var2) Hasher hasher = Hashing.sha1().newHasher(); - // inlined from byte[] a(String var0, byte[]... var1) hasher.putBytes(sessionId.getBytes(StandardCharsets.ISO_8859_1)); hasher.putBytes(sharedSecret.getEncoded()); hasher.putBytes(publicKey.getEncoded()); diff --git a/bukkit/src/test/resources/client_keys/README.md b/bukkit/src/test/resources/client_keys/README.md index dd119ad5..78882233 100644 --- a/bukkit/src/test/resources/client_keys/README.md +++ b/bukkit/src/test/resources/client_keys/README.md @@ -15,10 +15,10 @@ stripped before including it.** ## 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 -* `valid_public_key.json`: Extracted from actual file ## File content diff --git a/bukkit/src/test/resources/signature/README.md b/bukkit/src/test/resources/signature/README.md index cc603bcc..401142fa 100644 --- a/bukkit/src/test/resources/signature/README.md +++ b/bukkit/src/test/resources/signature/README.md @@ -4,7 +4,7 @@ This contains test resources for the unit tests. Files in this folder include pr ## Directory structure -* `valid_signature.json`: Extracted using packet extract from actual authentication +* `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 @@ -12,5 +12,5 @@ This contains test resources for the unit tests. Files in this folder include pr ## File content * `nonce`: Server generated nonce token -* `salt`: Client generated random 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/bungee/pom.xml b/bungee/pom.xml index 4de703cf..07c50764 100644 --- a/bungee/pom.xml +++ b/bungee/pom.xml @@ -192,7 +192,7 @@ de.xxschrandxx.bca BungeeCordAuthenticator - 0.0.2 + 0.0.3 provided diff --git a/core/pom.xml b/core/pom.xml index 119ea35c..8cd8469c 100644 --- a/core/pom.xml +++ b/core/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/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java b/core/src/test/java/com/github/games647/fastlogin/core/TickingRateLimiterTest.java index 34f196aa..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 @@ -37,8 +37,6 @@ import static org.hamcrest.MatcherAssert.assertThat; public class TickingRateLimiterTest { - private static final long THRESHOLD_MILLI = 10; - /** * Always expired */ diff --git a/velocity/pom.xml b/velocity/pom.xml index a6cd8016..4eb541c6 100644 --- a/velocity/pom.xml +++ b/velocity/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 902fb039..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 @@ -118,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(Collection oldProperties) { - List newProperties = new ArrayList<>(oldProperties.size() - 1); + List newProperties = new ArrayList<>(oldProperties.size()); for (GameProfile.Property property : oldProperties) { if (!"textures".equals(property.getName())) newProperties.add(property); From 0b0a46a18a0bb709bbf37319e996c6bcc0528551 Mon Sep 17 00:00:00 2001 From: games647 Date: Wed, 6 Jul 2022 16:49:26 +0200 Subject: [PATCH 33/37] Make client public key verification optional --- .../fastlogin/bukkit/BukkitLoginSession.java | 3 ++ .../fastlogin/bukkit/FastLoginBukkit.java | 2 +- .../listener/protocollib/NameCheckTask.java | 16 +++----- .../protocollib/ProtocolLibListener.java | 39 ++++++++++++++----- .../protocollib/ProtocolLibLoginSource.java | 12 +++--- .../protocollib/VerifyResponseTask.java | 2 +- core/src/main/resources/config.yml | 9 +++++ 7 files changed, 55 insertions(+), 28 deletions(-) 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 4f4af4dd..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 @@ -32,6 +32,8 @@ import com.github.games647.fastlogin.core.shared.LoginSession; import java.util.Optional; +import javax.annotation.Nullable; + /** * Represents a client connecting to the server. * @@ -83,6 +85,7 @@ public class BukkitLoginSession extends LoginSession { return verifyToken.clone(); } + @Nullable public ClientPublicKey getClientPublicKey() { return clientPublicKey; } 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 clientKey = packetEvent.getPacket() - .getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()).read(0); - - super.onLogin(username, new ProtocolLibLoginSource(player, random, serverKey, clientKey.orElse(null))); + super.onLogin(username, new ProtocolLibLoginSource(player, random, serverKey, clientKey)); } finally { ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent); } @@ -104,8 +101,7 @@ public class NameCheckTask extends JoinManagement either = packetEvent.getPacket().getSpecificModifier(Either.class).read(0); Object signatureData = either.right().get(); long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true); @@ -176,9 +187,13 @@ public class ProtocolLibListener extends PacketAdapter { } PacketContainer packet = packetEvent.getPacket(); - WrappedProfileKeyData profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()) - .read(0).orElse(null); - if (profileKey != null && !verifyPublicKey(profileKey)) { + var profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()) + .read(0); + + var clientKey = profileKey.flatMap(this::verifyPublicKey); + if (verifyClientKeys && !clientKey.isPresent()) { + // 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; @@ -187,20 +202,24 @@ public class ProtocolLibListener extends PacketAdapter { 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 boolean verifyPublicKey(WrappedProfileKeyData profileKey) { + 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 { - return EncryptionUtil.verifyClientKey(clientKey, Instant.now()); + if (EncryptionUtil.verifyClientKey(clientKey, Instant.now())) { + return Optional.of(clientKey); + } } catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) { - return false; + return Optional.empty(); } + + return Optional.empty(); } private String getUsername(PacketContainer packet) { 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 55701e40..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,7 +30,7 @@ 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.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; import com.github.games647.fastlogin.core.shared.LoginSource; import java.net.InetSocketAddress; @@ -49,17 +49,17 @@ class ProtocolLibLoginSource implements LoginSource { private final Random random; - private final WrappedProfileKeyData clientPublicKey; + private final ClientPublicKey clientKey; private final PublicKey publicKey; private final String serverId = ""; private byte[] verifyToken; - public ProtocolLibLoginSource(Player player, Random random, PublicKey serverPublicKey, WrappedProfileKeyData clientPublicKey) { + public ProtocolLibLoginSource(Player player, Random random, PublicKey serverPublicKey, ClientPublicKey clientKey) { this.player = player; this.random = random; this.publicKey = serverPublicKey; - this.clientPublicKey = clientPublicKey; + this.clientKey = clientKey; } @Override @@ -112,8 +112,8 @@ class ProtocolLibLoginSource implements LoginSource { return player.getAddress(); } - public WrappedProfileKeyData getClientPublicKey() { - return clientPublicKey; + public ClientPublicKey getClientKey() { + return clientKey; } public String getServerId() { 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 5a156d64..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 @@ -263,7 +263,7 @@ public class VerifyResponseTask implements Runnable { EquivalentConverter converter = BukkitConverters.getWrappedPublicKeyDataConverter(); var key = new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), sharedSecret); - startPacket.getOptionals(converter).write(0, Optional.of(key)); + startPacket.getOptionals(converter).write(0, Optional.ofNullable(key)); } else { //uuid is ignored by the packet definition WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username); diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 3a2ab7a9..6c1fde38 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. This also the case if you don't have any +# proxies in use. +verifyClientKeys: true + # Database configuration # Recommended is the use of MariaDB (a better version of MySQL) From eb47dd32549af5edcd185fb5cb058f1d800d7369 Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 8 Jul 2022 16:28:56 +0200 Subject: [PATCH 34/37] Restore compatibility with older Minecraft versions --- README.md | 2 +- .../listener/protocollib/EncryptionUtil.java | 12 +- .../protocollib/ProtocolLibListener.java | 69 +++++--- .../protocollib/EncryptionUtilTest.java | 160 +++++++----------- .../listener/protocollib/ResourceLoader.java | 96 +++++++++++ .../protocollib/SignatureTestData.java | 12 ++ .../fastlogin/core/AsyncScheduler.java | 18 +- 7 files changed, 235 insertions(+), 134 deletions(-) create mode 100644 bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ResourceLoader.java 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/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 53189ea8..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 @@ -47,6 +47,7 @@ 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; @@ -142,9 +143,9 @@ class EncryptionUtil { return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES"); } - public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimstamp) + public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimestamp) throws NoSuchAlgorithmException, InvalidKeyException, SignatureException { - if (clientKey.isExpired(verifyTimstamp)) { + if (clientKey.isExpired(verifyTimestamp)) { return false; } @@ -155,6 +156,13 @@ class EncryptionUtil { return verifier.verify(clientKey.signature()); } + 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"); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index ebcbed6e..d3acfbe1 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -31,6 +31,7 @@ import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.utility.MinecraftVersion; import com.comphenix.protocol.wrappers.BukkitConverters; import com.comphenix.protocol.wrappers.WrappedGameProfile; import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; @@ -51,6 +52,10 @@ import java.security.SignatureException; import java.time.Instant; import java.util.Optional; +import javax.crypto.BadPaddingException; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; + import org.bukkit.entity.Player; import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN; @@ -145,32 +150,52 @@ 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 { - if (session.getClientPublicKey() == null) { - // we cannot verify the signature if no public was provided - plugin.getLog().error("No public key provided for signed nonce {}", sender); + 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")); - return; } + } + } - Either either = packetEvent.getPacket().getSpecificModifier(Either.class).read(0); - Object signatureData = either.right().get(); - long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true); - byte[] signature = FuzzyReflection.getFieldValue(signatureData, byte[].class, true); + 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; + } - PublicKey publicKey = session.getClientPublicKey().key(); - try { - if (EncryptionUtil.verifySignedNonce(session.getVerifyToken(), publicKey, salt, signature)) { - packetEvent.getAsyncMarker().incrementProcessingDelay(); - Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, session, sharedSecret, keyPair); - plugin.getScheduler().runAsync(verifyTask); + return EncryptionUtil.verifyNonce(expectedToken, keyPair.getPrivate(), left.get()); } else { - sender.kickPlayer(plugin.getCore().getMessage("invalid-verify-token")); - plugin.getLog().error("Invalid signature from player {}", sender); + 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); } - } catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException signatureEx) { - sender.kickPlayer(plugin.getCore().getMessage("invalid-verify-token")); - plugin.getLog().error("Invalid signature from player {}", sender, signatureEx); + } 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; } } @@ -188,10 +213,10 @@ public class ProtocolLibListener extends PacketAdapter { PacketContainer packet = packetEvent.getPacket(); var profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()) - .read(0); + .optionRead(0); - var clientKey = profileKey.flatMap(this::verifyPublicKey); - if (verifyClientKeys && !clientKey.isPresent()) { + 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")); 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 34579b30..d8a4f5b2 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 @@ -28,29 +28,19 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib; 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 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.math.BigInteger; import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; import java.security.InvalidKeyException; import java.security.Key; -import java.security.KeyFactory; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; import java.security.PublicKey; -import java.security.interfaces.RSAPrivateKey; +import java.security.SignatureException; 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.time.temporal.ChronoUnit; -import java.util.Base64; import java.util.concurrent.ThreadLocalRandom; import javax.crypto.BadPaddingException; @@ -60,9 +50,6 @@ import javax.crypto.NoSuchPaddingException; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; -import org.bouncycastle.util.io.pem.PemObject; -import org.bouncycastle.util.io.pem.PemReader; -import org.jetbrains.annotations.NotNull; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; @@ -100,7 +87,7 @@ public class EncryptionUtilTest { @Test public void testExpiredClientKey() throws Exception { - var clientKey = loadClientKey("client_keys/valid_public_key.json"); + 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(); @@ -111,7 +98,7 @@ public class EncryptionUtilTest { public void testInvalidChangedExpiration() throws Exception { // expiration date changed should make the signature invalid // expiration should still be valid - var clientKey = loadClientKey("client_keys/invalid_wrong_expiration.json"); + 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)); @@ -120,7 +107,7 @@ public class EncryptionUtilTest { @Test public void testInvalidChangedKey() throws Exception { // changed public key no longer corresponding to the signature - var clientKey = loadClientKey("client_keys/invalid_wrong_key.json"); + 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)); @@ -129,7 +116,7 @@ public class EncryptionUtilTest { @Test public void testInvalidChangedSignature() throws Exception { // signature modified no longer corresponding to key and expiration date - var clientKey = loadClientKey("client_keys/invalid_wrong_signature.json"); + 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)); @@ -137,7 +124,7 @@ public class EncryptionUtilTest { @Test public void testValidClientKey() throws Exception { - var clientKey = loadClientKey("client_keys/valid_public_key.json"); + 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)); @@ -155,7 +142,7 @@ public class EncryptionUtilTest { assertThat(decryptSharedKey, is(secretKey)); } - private byte[] encrypt(PublicKey receiverKey, byte[] message) + private static byte[] encrypt(PublicKey receiverKey, byte[] message) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { var encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm()); @@ -163,7 +150,7 @@ public class EncryptionUtilTest { return encryptCipher.doFinal(message); } - private SecretKeySpec generateSharedKey() { + private static SecretKeySpec generateSharedKey() { // according to wiki.vg 16 bytes long byte[] sharedKey = new byte[16]; ThreadLocalRandom.current().nextBytes(sharedKey); @@ -176,14 +163,13 @@ public class EncryptionUtilTest { public void testServerIdHash() throws Exception { var serverId = ""; var sharedSecret = generateSharedKey(); - var serverPK = loadClientKey("client_keys/valid_public_key.json").key(); + 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)); } - @NotNull - private String getServerHash(String serverId, SecretKey sharedSecret, PublicKey serverPK) { + private static String getServerHash(String 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) @@ -204,7 +190,7 @@ public class EncryptionUtilTest { public void testServerIdHashWrongSecret() throws Exception { var serverId = ""; var sharedSecret = generateSharedKey(); - var serverPK = loadClientKey("client_keys/valid_public_key.json").key(); + var serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key(); String sessionHash = getServerHash(serverId, sharedSecret, serverPK); assertThat(EncryptionUtil.getServerIdHashString("", generateSharedKey(), serverPK), not(sessionHash)); @@ -223,116 +209,88 @@ public class EncryptionUtilTest { @Test public void testValidSignedNonce() throws Exception { - ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json"); - PublicKey clientPublicKey = clientKey.key(); - - SignatureTestData testData = loadSignatureResource("signature/valid_signature.json"); - byte[] nonce = testData.getNonce(); - SignatureData signature = testData.getSignature(); - long salt = signature.getSalt(); - assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(true)); + 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 = loadClientKey("client_keys/valid_public_key.json"); - PublicKey clientPublicKey = clientKey.key(); - - SignatureTestData testData = loadSignatureResource("signature/incorrect_nonce.json"); - byte[] nonce = testData.getNonce(); - SignatureData signature = testData.getSignature(); - long salt = signature.getSalt(); - assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + 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 = loadClientKey("client_keys/valid_public_key.json"); - PublicKey clientPublicKey = clientKey.key(); - - SignatureTestData testData = loadSignatureResource("signature/incorrect_salt.json"); - byte[] nonce = testData.getNonce(); - SignatureData signature = testData.getSignature(); - long salt = signature.getSalt(); - assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + 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 = loadClientKey("client_keys/valid_public_key.json"); - PublicKey clientPublicKey = clientKey.key(); - - SignatureTestData testData = loadSignatureResource("signature/incorrect_signature.json"); - byte[] nonce = testData.getNonce(); - SignatureData signature = testData.getSignature(); - long salt = signature.getSalt(); - assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + 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 = loadClientKey("client_keys/invalid_wrong_key.json"); + 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(); - SignatureTestData testData = loadSignatureResource("signature/valid_signature.json"); byte[] nonce = testData.getNonce(); SignatureData signature = testData.getSignature(); long salt = signature.getSalt(); - assertThat(EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()), is(false)); + return EncryptionUtil.verifySignedNonce(nonce, clientPublicKey, salt, signature.getSignature()); } - private SignatureTestData loadSignatureResource(String resourceName) throws IOException { - var keyUrl = Resources.getResource(resourceName); - var encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII); + @Test + public void testNonce() throws Exception { + byte[] expected = {1, 2, 3, 4}; + var serverKey = EncryptionUtil.generateKeyPair(); + var encryptedNonce = encrypt(serverKey.getPublic(), expected); - return new Gson().fromJson(encodedSignature, SignatureTestData.class); + assertThat(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce), is(true)); } - private 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); + @Test + public void testNonceIncorrect() throws Exception { + byte[] expected = {1, 2, 3, 4}; + var serverKey = EncryptionUtil.generateKeyPair(); - var factory = KeyFactory.getInstance("RSA"); - return (RSAPrivateKey) factory.generatePrivate(privateKeySpec); - } + // flipped first character + var encryptedNonce = encrypt(serverKey.getPublic(), new byte[]{0, 2, 3 , 4}); + + assertThat(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce), is(false)); } - private ClientPublicKey loadClientKey(String path) - throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { - var keyUrl = Resources.getResource(path); + @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); - 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); + EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce); } - private 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); + @Test(expected = GeneralSecurityException.class) + public void testNonceIncorrectEmpty() throws Exception { + byte[] expected = {1, 2, 3, 4}; + var serverKey = EncryptionUtil.generateKeyPair(); + byte[] encryptedNonce = {}; - var factory = KeyFactory.getInstance("RSA"); - return (RSAPublicKey) factory.generatePublic(pubKeySpec); - } + 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 index 8ea85f6e..ee62e242 100644 --- 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 @@ -25,10 +25,22 @@ */ 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; 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(); + } + } } From b4ddf4fb19861e07ba655ba0627f295037fa5796 Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 8 Jul 2022 16:29:25 +0200 Subject: [PATCH 35/37] Reduce dependency list to improve on build time --- bukkit/pom.xml | 2 +- bungee/pom.xml | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 28c585a0..ae285d64 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -364,7 +364,7 @@ org.bouncycastle - bcpkix-jdk18on + bcprov-jdk18on 1.71 test diff --git a/bungee/pom.xml b/bungee/pom.xml index 07c50764..b7645231 100644 --- a/bungee/pom.xml +++ b/bungee/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 + * + From e89cb3293ae56fb2d35c657e54c8f87646c560f3 Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 8 Jul 2022 16:30:41 +0200 Subject: [PATCH 36/37] Disable verify client keys by default for older compatibility This also mimics the default vanilla configuration. --- core/src/main/resources/config.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/resources/config.yml b/core/src/main/resources/config.yml index 6c1fde38..1db567dd 100644 --- a/core/src/main/resources/config.yml +++ b/core/src/main/resources/config.yml @@ -277,9 +277,9 @@ floodgatePrefixWorkaround: false # 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. This also the case if you don't have any -# proxies in use. -verifyClientKeys: true +# 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) From adfae507ac73f1aef1284eb792d0ef6533822ef5 Mon Sep 17 00:00:00 2001 From: games647 Date: Fri, 8 Jul 2022 16:43:02 +0200 Subject: [PATCH 37/37] Flip velocity check to scan for newer Paper configurations first The PaperConfig class file still exists in newer versions --- .../games647/fastlogin/bukkit/BungeeManager.java | 10 +++++----- .../listener/protocollib/EncryptionUtilTest.java | 7 ++++--- 2 files changed, 9 insertions(+), 8 deletions(-) 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 5c030bb9..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 @@ -110,17 +110,17 @@ public class BungeeManager { throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException { try { - if (isProxySupported("com.destroystokyo.paper.PaperConfig", "velocitySupport")) { - return true; - } - } catch (ClassNotFoundException classNotFoundException) { - // try again using the newer Paper configuration 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; 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 d8a4f5b2..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 @@ -142,7 +142,7 @@ public class EncryptionUtilTest { assertThat(decryptSharedKey, is(secretKey)); } - private static byte[] encrypt(PublicKey receiverKey, byte[] message) + private static byte[] encrypt(PublicKey receiverKey, byte... message) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { var encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm()); @@ -169,13 +169,14 @@ public class EncryptionUtilTest { assertThat(EncryptionUtil.getServerIdHashString(serverId, sharedSecret, serverPK), is(sessionHash)); } - private static String getServerHash(String serverId, SecretKey sharedSecret, PublicKey serverPK) { + 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()); @@ -197,7 +198,7 @@ public class EncryptionUtilTest { } @Test - public void testServerIdHashWrongServerKey() throws Exception { + public void testServerIdHashWrongServerKey() { var serverId = ""; var sharedSecret = generateSharedKey(); var serverPK = EncryptionUtil.generateKeyPair().getPublic();