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 dc458373..7383972b 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 @@ -6,8 +6,6 @@ import com.github.games647.fastlogin.core.shared.LoginSession; import java.util.Optional; -import org.apache.commons.lang.ArrayUtils; - /** * Represents a client connecting to the server. * @@ -15,40 +13,21 @@ import org.apache.commons.lang.ArrayUtils; */ public class BukkitLoginSession extends LoginSession { - private final String serverId; - private final byte[] verifyToken; - + private SkinProperty skinProperty; private boolean verified; - private SkinProperty skinProperty; - - public BukkitLoginSession(String username, String serverId, byte[] verifyToken, boolean registered - , StoredProfile profile) { + public BukkitLoginSession(String username, boolean registered, StoredProfile profile) { super(username, registered, profile); - - this.serverId = serverId; - this.verifyToken = ArrayUtils.clone(verifyToken); } //available for BungeeCord public BukkitLoginSession(String username, boolean registered) { - this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY, registered, null); + this(username, registered, null); } //cracked player public BukkitLoginSession(String username, StoredProfile profile) { - this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY, false, profile); - } - - /** - * Gets the verify token the server sent to the client. - * - * Empty if it's a BungeeCord connection - * - * @return the verify token from the server - */ - public byte[] getVerifyToken() { - return ArrayUtils.clone(verifyToken); + this(username, false, profile); } /** diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/EncryptionUtil.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/EncryptionUtil.java deleted file mode 100644 index 2a29a628..00000000 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/EncryptionUtil.java +++ /dev/null @@ -1,121 +0,0 @@ -package com.github.games647.fastlogin.bukkit; - -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.Key; -import java.security.KeyPair; -import java.security.KeyPairGenerator; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; -import java.security.PublicKey; -import java.util.Random; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; -import javax.crypto.spec.SecretKeySpec; - -/** - * Encryption and decryption minecraft util for connection between servers - * and paid Minecraft account clients. - * - * @see net.minecraft.server.MinecraftEncryption - */ -public class EncryptionUtil { - - public static final int VERIFY_TOKEN_LENGTH = 4; - public static final String KEY_PAIR_ALGORITHM = "RSA"; - - private EncryptionUtil() { - //utility - } - - /** - * Generate a RSA key pair - * - * @return The RSA key pair. - */ - public static KeyPair generateKeyPair() { - try { - KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM); - - keyPairGenerator.initialize(1_024); - return keyPairGenerator.generateKeyPair(); - } catch (NoSuchAlgorithmException nosuchalgorithmexception) { - //Should be existing in every vm - throw new ExceptionInInitializerError(nosuchalgorithmexception); - } - } - - /** - * Generate a random token. This is used to verify that we are communicating with the same player - * in a login session. - * - * @param random random generator - * @return an error with 4 bytes long - */ - public static byte[] generateVerifyToken(Random random) { - byte[] token = new byte[VERIFY_TOKEN_LENGTH]; - random.nextBytes(token); - return token; - } - - /** - * Generate the server id based on client and server data. - * - * @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 - * @return the server id formatted as a hexadecimal string. - */ - public static String getServerIdHashString(String sessionId, Key sharedSecret, PublicKey publicKey) { - try { - byte[] serverHash = getServerIdHash(sessionId, sharedSecret, publicKey); - return (new BigInteger(serverHash)).toString(16); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } - - return ""; - } - - /** - * Decrypts the content and extracts the key spec. - * - * @param cipher decryption cipher - * @param privateKey private key of the server - * @param sharedKey the encrypted shared key - * @return shared secret key - * @throws GeneralSecurityException - */ - public static SecretKey decryptSharedKey(Cipher cipher, PrivateKey privateKey, byte[] sharedKey) - throws GeneralSecurityException { - return new SecretKeySpec(decrypt(cipher, privateKey, sharedKey), "AES"); - } - - /** - * Decrypted the given data using the cipher. - * - * @param cipher decryption cypher - * @param key server private key - * @param data the encrypted data - * @return clear text data - * @throws GeneralSecurityException if it fails to initialize and decrypt the data - */ - public static byte[] decrypt(Cipher cipher, PrivateKey key, byte[] data) throws GeneralSecurityException { - cipher.init(Cipher.DECRYPT_MODE, key); - return cipher.doFinal(data); - } - - private static byte[] getServerIdHash(String sessionId, Key sharedSecret, PublicKey publicKey) - throws NoSuchAlgorithmException { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - - digest.update(sessionId.getBytes(StandardCharsets.ISO_8859_1)); - digest.update(sharedSecret.getEncoded()); - digest.update(publicKey.getEncoded()); - - return digest.digest(); - } -} 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 e0fe7a83..a95924ac 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 @@ -4,8 +4,6 @@ import com.github.games647.fastlogin.bukkit.commands.CrackedCommand; import com.github.games647.fastlogin.bukkit.commands.PremiumCommand; import com.github.games647.fastlogin.bukkit.listener.BungeeListener; import com.github.games647.fastlogin.bukkit.listener.ConnectionListener; -import com.github.games647.fastlogin.bukkit.listener.protocollib.ProtocolLibListener; -import com.github.games647.fastlogin.bukkit.listener.protocollib.SkinApplyListener; import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener; import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook; import com.github.games647.fastlogin.core.CommonUtil; @@ -35,7 +33,7 @@ import org.slf4j.Logger; public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin { //1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang) - private final ConcurrentMap loginSession = CommonUtil.buildCache(1, -1); + private final ConcurrentMap loginSession = new ConcurrentHashMap<>(); private final Logger logger = CommonUtil.createLoggerFromJDK(getLogger()); private final Map premiumPlayers = new ConcurrentHashMap<>(); @@ -64,8 +62,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin metadata = player.getMetadata(plugin.getName()); - if (metadata == null) { - return "unknown"; - } - - if (metadata.isEmpty()) { - return "cracked"; - } else { - return "premium"; - } + return plugin.getStatus(player.getUniqueId()).name(); } return ""; diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ConnectionListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ConnectionListener.java index fec78d49..53a4a306 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ConnectionListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ConnectionListener.java @@ -6,11 +6,8 @@ import com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask; import org.bukkit.Bukkit; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; -import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; -import org.bukkit.event.player.PlayerLoginEvent; -import org.bukkit.event.player.PlayerLoginEvent.Result; import org.bukkit.event.player.PlayerQuitEvent; /** @@ -27,13 +24,6 @@ public class ConnectionListener implements Listener { this.plugin = plugin; } - @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerLogin(PlayerLoginEvent loginEvent) { - if (loginEvent.getResult() == Result.ALLOWED && !plugin.isServerFullyStarted()) { - loginEvent.disallow(Result.KICK_OTHER, plugin.getCore().getMessage("not-started")); - } - } - @EventHandler(ignoreCancelled = true) public void onPlayerJoin(PlayerJoinEvent joinEvent) { Player player = joinEvent.getPlayer(); 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 deleted file mode 100644 index 622f4727..00000000 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/NameCheckTask.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.github.games647.fastlogin.bukkit.listener.protocollib; - -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.events.PacketEvent; -import com.github.games647.fastlogin.bukkit.BukkitLoginSession; -import com.github.games647.fastlogin.bukkit.FastLoginBukkit; -import com.github.games647.fastlogin.core.StoredProfile; -import com.github.games647.fastlogin.core.shared.JoinManagement; - -import java.security.PublicKey; -import java.util.Random; - -import org.bukkit.command.CommandSender; -import org.bukkit.entity.Player; - -public class NameCheckTask extends JoinManagement - implements Runnable { - - private final FastLoginBukkit plugin; - private final PacketEvent packetEvent; - private final PublicKey publicKey; - - private final Random random; - - private final Player player; - private final String username; - - public NameCheckTask(FastLoginBukkit plugin, PacketEvent packetEvent, Random random, - Player player, String username, PublicKey publicKey) { - super(plugin.getCore(), plugin.getCore().getAuthPluginHook()); - - this.plugin = plugin; - this.packetEvent = packetEvent; - this.publicKey = publicKey; - this.random = random; - this.player = player; - this.username = username; - } - - @Override - public void run() { - try { - super.onLogin(username, new ProtocolLibLoginSource(packetEvent, player, random, publicKey)); - } finally { - ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent); - } - } - - //Minecraft server implementation - //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161 - @Override - public void requestPremiumLogin(ProtocolLibLoginSource source, StoredProfile profile - , String username, boolean registered) { - try { - source.setOnlineMode(); - } catch (Exception ex) { - plugin.getLog().error("Cannot send encryption packet. Falling back to cracked login for: {}", profile, ex); - return; - } - - String ip = player.getAddress().getAddress().getHostAddress(); - core.getPendingLogin().put(ip + username, new Object()); - - String serverId = source.getServerId(); - byte[] verify = source.getVerifyToken(); - - BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, registered, profile); - plugin.getLoginSessions().put(player.getAddress().toString(), playerSession); - //cancel only if the player has a paid account otherwise login as normal offline player - synchronized (packetEvent.getAsyncMarker().getProcessingLock()) { - packetEvent.setCancelled(true); - } - } - - @Override - public void startCrackedSession(ProtocolLibLoginSource source, StoredProfile profile, String username) { - BukkitLoginSession loginSession = new BukkitLoginSession(username, profile); - plugin.getLoginSessions().put(player.getAddress().toString(), loginSession); - } -} 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 deleted file mode 100644 index 327b8989..00000000 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.github.games647.fastlogin.bukkit.listener.protocollib; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.events.PacketAdapter; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.events.PacketEvent; -import com.github.games647.fastlogin.bukkit.EncryptionUtil; -import com.github.games647.fastlogin.bukkit.FastLoginBukkit; - -import java.security.KeyPair; -import java.security.SecureRandom; - -import org.bukkit.Bukkit; -import org.bukkit.entity.Player; - -import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN; -import static com.comphenix.protocol.PacketType.Login.Client.START; - -public class ProtocolLibListener extends PacketAdapter { - - private static final int WORKER_THREADS = 3; - - private final FastLoginBukkit plugin; - - //just create a new once on plugin enable. This used for verify token generation - private final SecureRandom random = new SecureRandom(); - private final KeyPair keyPair = EncryptionUtil.generateKeyPair(); - - public ProtocolLibListener(FastLoginBukkit plugin) { - //run async in order to not block the server, because we are making api calls to Mojang - super(params() - .plugin(plugin) - .types(START, ENCRYPTION_BEGIN) - .optionAsync()); - - this.plugin = plugin; - } - - public static void register(FastLoginBukkit plugin) { - //they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError - ProtocolLibrary.getProtocolManager() - .getAsynchronousManager() - .registerAsyncHandler(new ProtocolLibListener(plugin)) - .start(WORKER_THREADS); - } - - @Override - public void onPacketReceiving(PacketEvent packetEvent) { - if (packetEvent.isCancelled() - || plugin.getCore().getAuthPluginHook()== null - || !plugin.isServerFullyStarted()) { - return; - } - - Player sender = packetEvent.getPlayer(); - PacketType packetType = packetEvent.getPacketType(); - if (packetType == START) { - onLogin(packetEvent, sender); - } else { - onEncryptionBegin(packetEvent, sender); - } - } - - private void onEncryptionBegin(PacketEvent packetEvent, Player sender) { - byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0); - - packetEvent.getAsyncMarker().incrementProcessingDelay(); - Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair); - Bukkit.getScheduler().runTaskAsynchronously(plugin, verifyTask); - } - - private void onLogin(PacketEvent packetEvent, Player player) { - //this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes - String sessionKey = player.getAddress().toString(); - - //remove old data every time on a new login in order to keep the session only for one person - plugin.getLoginSessions().remove(sessionKey); - - //player.getName() won't work at this state - PacketContainer packet = packetEvent.getPacket(); - - String username = packet.getGameProfiles().read(0).getName(); - plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username); - - packetEvent.getAsyncMarker().incrementProcessingDelay(); - Runnable nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username, keyPair.getPublic()); - Bukkit.getScheduler().runTaskAsynchronously(plugin, nameCheckTask); - } -} 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 deleted file mode 100644 index 5a5aba3b..00000000 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibLoginSource.java +++ /dev/null @@ -1,101 +0,0 @@ -package com.github.games647.fastlogin.bukkit.listener.protocollib; - -import com.comphenix.protocol.ProtocolLibrary; -import com.comphenix.protocol.ProtocolManager; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.wrappers.WrappedChatComponent; -import com.github.games647.fastlogin.bukkit.EncryptionUtil; -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; -import java.util.Random; - -import org.apache.commons.lang.ArrayUtils; -import org.bukkit.entity.Player; - -import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT; -import static com.comphenix.protocol.PacketType.Login.Server.ENCRYPTION_BEGIN; - -public class ProtocolLibLoginSource implements LoginSource { - - private final PacketEvent packetEvent; - private final Player player; - - private final Random random; - private final PublicKey publicKey; - - private final String serverId = ""; - private byte[] verifyToken; - - public ProtocolLibLoginSource(PacketEvent packetEvent, Player player, Random random, PublicKey publicKey) { - this.packetEvent = packetEvent; - this.player = player; - this.random = random; - this.publicKey = publicKey; - } - - @Override - public void setOnlineMode() throws Exception { - verifyToken = EncryptionUtil.generateVerifyToken(random); - - /* - * Packet Information: http://wiki.vg/Protocol#Encryption_Request - * - * ServerID="" (String) key=public server key verifyToken=random 4 byte array - */ - PacketContainer newPacket = new PacketContainer(ENCRYPTION_BEGIN); - - newPacket.getStrings().write(0, serverId); - newPacket.getSpecificModifier(PublicKey.class).write(0, publicKey); - - newPacket.getByteArrays().write(0, verifyToken); - - //serverId is a empty string - ProtocolLibrary.getProtocolManager().sendServerPacket(player, newPacket); - } - - @Override - public void kick(String message) throws InvocationTargetException { - ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager(); - - PacketContainer kickPacket = new PacketContainer(DISCONNECT); - kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(message)); - - try { - //send kick packet at login state - //the normal event.getPlayer.kickPlayer(String) method does only work at play state - protocolManager.sendServerPacket(player, kickPacket); - } finally { - //tell the server that we want to close the connection - player.kickPlayer("Disconnect"); - } - } - - @Override - public InetSocketAddress getAddress() { - return packetEvent.getPlayer().getAddress(); - } - - public String getServerId() { - return serverId; - } - - public byte[] getVerifyToken() { - return ArrayUtils.clone(verifyToken); - } - - @Override - public String toString() { - return this.getClass().getSimpleName() + '{' + - "packetEvent=" + packetEvent + - ", player=" + player + - ", random=" + random + - ", serverId='" + serverId + '\'' + - ", verifyToken=" + Arrays.toString(verifyToken) + - '}'; - } -} 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 deleted file mode 100644 index fa146d04..00000000 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/SkinApplyListener.java +++ /dev/null @@ -1,70 +0,0 @@ -package com.github.games647.fastlogin.bukkit.listener.protocollib; - -import com.comphenix.protocol.reflect.MethodUtils; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.MethodAccessor; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.wrappers.WrappedGameProfile; -import com.comphenix.protocol.wrappers.WrappedSignedProperty; -import com.github.games647.craftapi.model.skin.SkinProperty; -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; -import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerLoginEvent; -import org.bukkit.event.player.PlayerLoginEvent.Result; - -public class SkinApplyListener implements Listener { - - private static final Class GAME_PROFILE = MinecraftReflection.getGameProfileClass(); - private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessor(GAME_PROFILE, "getProperties"); - - private final FastLoginBukkit plugin; - - public SkinApplyListener(FastLoginBukkit plugin) { - this.plugin = plugin; - } - - @EventHandler(priority = EventPriority.LOW) - //run this on the loginEvent to let skins plugins see the skin like in normal Minecraft behaviour - public void onPlayerLogin(PlayerLoginEvent loginEvent) { - if (loginEvent.getResult() != Result.ALLOWED) { - return; - } - - Player player = loginEvent.getPlayer(); - - if (plugin.getConfig().getBoolean("forwardSkin")) { - //go through every session, because player.getAddress is null - //loginEvent.getAddress is just a InetAddress not InetSocketAddress, so not unique enough - for (BukkitLoginSession session : plugin.getLoginSessions().values()) { - if (session.getUsername().equals(player.getName())) { - session.getSkin().ifPresent(skin -> applySkin(player, skin.getValue(), skin.getSignature())); - break; - } - } - } - } - - private void applySkin(Player player, String skinData, String signature) { - WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player); - - WrappedSignedProperty skin = WrappedSignedProperty.fromValues(SkinProperty.TEXTURE_KEY, skinData, signature); - try { - gameProfile.getProperties().put(SkinProperty.TEXTURE_KEY, skin); - } catch (ClassCastException castException) { - //Cauldron, MCPC, Thermos, ... - Object map = GET_PROPERTIES.invoke(gameProfile.getHandle()); - try { - MethodUtils.invokeMethod(map, "put", new Object[]{SkinProperty.TEXTURE_KEY, skin.getHandle()}); - } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) { - plugin.getLog().error("Error setting premium skin of: {}", player, ex); - } - } - } -} 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 deleted file mode 100644 index 784fdc4c..00000000 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/VerifyResponseTask.java +++ /dev/null @@ -1,232 +0,0 @@ -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.reflect.FieldUtils; -import com.comphenix.protocol.reflect.FuzzyReflection; -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.MojangResolver; -import com.github.games647.fastlogin.bukkit.BukkitLoginSession; -import com.github.games647.fastlogin.bukkit.EncryptionUtil; -import com.github.games647.fastlogin.bukkit.FastLoginBukkit; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.security.GeneralSecurityException; -import java.security.KeyPair; -import java.security.PrivateKey; -import java.util.Arrays; -import java.util.Optional; -import java.util.UUID; - -import javax.crypto.Cipher; -import javax.crypto.SecretKey; - -import org.bukkit.entity.Player; - -import static com.comphenix.protocol.PacketType.Login.Client.START; -import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT; - -public class VerifyResponseTask implements Runnable { - - private final FastLoginBukkit plugin; - private final PacketEvent packetEvent; - private final KeyPair serverKey; - - private final Player player; - - private final byte[] sharedSecret; - - public VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player player, - byte[] sharedSecret, KeyPair keyPair) { - this.plugin = plugin; - this.packetEvent = packetEvent; - this.player = player; - this.sharedSecret = Arrays.copyOf(sharedSecret, sharedSecret.length); - this.serverKey = keyPair; - } - - @Override - public void run() { - try { - BukkitLoginSession session = plugin.getLoginSessions().get(player.getAddress().toString()); - if (session == null) { - disconnect(plugin.getCore().getMessage("invalid-request"), true - , "GameProfile {0} tried to send encryption response at invalid state", player.getAddress()); - } else { - verifyResponse(session); - } - } finally { - //this is a fake packet; it shouldn't be send to the server - synchronized (packetEvent.getAsyncMarker().getProcessingLock()) { - packetEvent.setCancelled(true); - } - - ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent); - } - } - - private void verifyResponse(BukkitLoginSession session) { - PrivateKey privateKey = serverKey.getPrivate(); - - Cipher cipher; - SecretKey loginKey; - try { - cipher = Cipher.getInstance(privateKey.getAlgorithm()); - - loginKey = EncryptionUtil.decryptSharedKey(cipher, privateKey, sharedSecret); - } catch (GeneralSecurityException securityEx) { - disconnect("error-kick", false, "Cannot decrypt received contents", securityEx); - return; - } - - try { - if (!checkVerifyToken(session, cipher, privateKey) || !encryptConnection(loginKey)) { - return; - } - } catch (Exception ex) { - disconnect("error-kick", false, "Cannot decrypt received contents", ex); - return; - } - - String serverId = EncryptionUtil.getServerIdHashString("", loginKey, serverKey.getPublic()); - - String username = session.getUsername(); - InetSocketAddress socketAddress = player.getAddress(); - try { - MojangResolver resolver = plugin.getCore().getResolver(); - InetAddress address = socketAddress.getAddress(); - Optional response = resolver.hasJoined(username, serverId, address); - if (response.isPresent()) { - plugin.getLog().info("GameProfile {} has a verified premium account", username); - - SkinProperty[] properties = response.get().getProperties(); - if (properties.length > 0) { - session.setSkinProperty(properties[0]); - } - - session.setVerified(true); - setPremiumUUID(session.getUuid()); - - receiveFakeStartPacket(username); - } else { - //user tried to fake a authentication - disconnect(plugin.getCore().getMessage("invalid-session"), true - , "GameProfile {0} ({1}) tried to log in with an invalid session ServerId: {2}" - , session.getUsername(), socketAddress, serverId); - } - } catch (IOException ioEx) { - disconnect("error-kick", false, "Failed to connect to sessionserver", ioEx); - } - } - - private void setPremiumUUID(UUID premiumUUID) { - if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) { - try { - Object networkManager = getNetworkManager(); - //https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69 - FieldUtils.writeField(networkManager, "spoofedUUID", premiumUUID, true); - } catch (Exception exc) { - plugin.getLog().error("Error setting premium uuid of {}", player, exc); - } - } - } - - private boolean checkVerifyToken(BukkitLoginSession session, Cipher cipher, PrivateKey privateKey) - 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(cipher, privateKey, responseVerify))) { - //check if the verify token are equal to the server sent one - disconnect(plugin.getCore().getMessage("invalid-verify-token"), true - , "GameProfile {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}" - , session.getUsername(), 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); - - //ChannelInjector - Class injectorClass = Class.forName("com.comphenix.protocol.injector.netty.Injector"); - Object rawInjector = FuzzyReflection.getFieldValue(injectorContainer, injectorClass, true); - return FieldUtils.readField(rawInjector, "networkManager", true); - } - - private boolean encryptConnection(SecretKey loginKey) throws IllegalArgumentException { - try { - //get the NMS connection handle of this player - Object networkManager = getNetworkManager(); - - //try to detect the method by parameters - Method encryptMethod = FuzzyReflection - .fromObject(networkManager).getMethodByParameters("a", SecretKey.class); - - //encrypt/decrypt following packets - //the client expects this behaviour - encryptMethod.invoke(networkManager, loginKey); - } catch (Exception ex) { - disconnect("error-kick", false, "Couldn't enable encryption", ex); - return false; - } - - return true; - } - - private void disconnect(String kickReason, boolean debug, String logMessage, Object... arguments) { - if (debug) { - plugin.getLog().debug(logMessage, arguments); - } else { - plugin.getLog().error(logMessage, arguments); - } - - kickPlayer(plugin.getCore().getMessage(kickReason)); - } - - 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); - } - } - - //fake a new login packet in order to let the server handle all the other stuff - private void receiveFakeStartPacket(String username) { - //see StartPacketListener for packet information - PacketContainer startPacket = new PacketContainer(START); - - //uuid is ignored by the packet definition - WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username); - startPacket.getGameProfiles().write(0, fakeProfile); - try { - //we don't want to handle our own packets so ignore filters - ProtocolLibrary.getProtocolManager().recieveClientPacket(player, startPacket, false); - } 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")); - } - } -} diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java index fe1234b4..cb626988 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocolsupport/ProtocolSupportListener.java @@ -67,8 +67,7 @@ public class ProtocolSupportListener extends JoinManagement