Generate a public key only for ProtocolLib listener

This commit is contained in:
games647
2018-03-09 13:57:51 +01:00
parent f250f8071f
commit 3f9eba69ba
26 changed files with 112 additions and 149 deletions
@@ -40,19 +40,6 @@ public class BukkitLoginSession extends LoginSession {
this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY, false, profile);
}
/**
* Gets the random generated server id. This makes sure the request sent from the client is just for this server.
*
* See this for details https://www.sk89q.com/2011/09/Minecraft-name-spoofing-exploit/
*
* Empty if it's a BungeeCord connection
*
* @return random generated server id
*/
public String getServerId() {
return serverId;
}
/**
* Gets the verify token the server sent to the client.
*
@@ -64,7 +51,6 @@ public class BukkitLoginSession extends LoginSession {
return ArrayUtils.clone(verifyToken);
}
//todo: this should be optional for players without a skin at all
public synchronized Optional<SkinProperties> getSkin() {
return Optional.ofNullable(skinProperty);
}
@@ -19,13 +19,13 @@ import com.google.common.io.ByteStreams;
import com.google.common.net.HostAndPort;
import java.nio.file.Path;
import java.security.KeyPair;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageRecipient;
import org.slf4j.Logger;
@@ -35,8 +35,6 @@ import org.slf4j.Logger;
*/
public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<CommandSender> {
//provide a immutable key pair to be thread safe | used for encrypting and decrypting traffic
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
private final Logger logger = CommonUtil.createLoggerFromJDK(getLogger());
private boolean bungeeCord;
@@ -65,6 +63,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return;
}
PluginManager pluginManager = getServer().getPluginManager();
if (bungeeCord) {
setServerStarted();
@@ -77,14 +76,11 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return;
}
if (getServer().getPluginManager().isPluginEnabled("ProtocolSupport")) {
getServer().getPluginManager().registerEvents(new ProtocolSupportListener(this), this);
} else if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
//they will be created with a static builder, because otherwise it will throw a
//NoClassDefFoundError: com/comphenix/protocol/events/PacketListener if only ProtocolSupport was found
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
pluginManager.registerEvents(new ProtocolSupportListener(this), this);
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
ProtocolLibListener.register(this);
getServer().getPluginManager().registerEvents(new SkinApplyListener(this), this);
pluginManager.registerEvents(new SkinApplyListener(this), this);
} else {
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use BungeeCord");
}
@@ -93,13 +89,13 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
//delay dependency setup because we load the plugin very early where plugins are initialized yet
getServer().getScheduler().runTaskLater(this, new DelayedAuthHook(this), 5L);
getServer().getPluginManager().registerEvents(new JoinListener(this), this);
pluginManager.registerEvents(new JoinListener(this), this);
//register commands using a unique name
getCommand("premium").setExecutor(new PremiumCommand(this));
getCommand("cracked").setExecutor(new CrackedCommand(this));
if (getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) {
if (pluginManager.isPluginEnabled("PlaceholderAPI")) {
//prevents NoClassDef errors if it's not available
PremiumPlaceholder.register(this);
}
@@ -148,15 +144,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return loginSession;
}
/**
* Gets the server KeyPair. This is used to encrypt or decrypt traffic between the client and server
*
* @return the server KeyPair
*/
public KeyPair getServerKey() {
return keyPair;
}
public boolean isBungeeCord() {
return bungeeCord;
}
@@ -24,10 +24,10 @@ public class PremiumPlaceholder extends PlaceholderHook {
return "unknown";
}
if (!metadata.isEmpty()) {
return "premium";
} else {
if (metadata.isEmpty()) {
return "cracked";
} else {
return "premium";
}
}
@@ -12,7 +12,6 @@ import de.st_ddt.crazylogin.metadata.Authenticated;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.apache.commons.lang.reflect.FieldUtils;
import org.bukkit.Bukkit;
@@ -29,11 +28,14 @@ public class CrazyLoginHook implements AuthPlugin<Player> {
private final FastLoginBukkit plugin;
private final CrazyLogin crazyLoginPlugin = CrazyLogin.getPlugin();
private final PlayerListener playerListener = getListener();
private final CrazyLogin crazyLoginPlugin;
private final PlayerListener playerListener;
public CrazyLoginHook(FastLoginBukkit plugin) {
this.plugin = plugin;
crazyLoginPlugin = CrazyLogin.getPlugin();
playerListener = getListener();
}
@Override
@@ -78,7 +80,7 @@ public class CrazyLoginHook implements AuthPlugin<Player> {
return true;
}
} catch (InterruptedException | ExecutionException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
plugin.getLog().error("Failed to forceLogin player: {}", player, ex);
return false;
}
@@ -112,7 +114,7 @@ public class CrazyLoginHook implements AuthPlugin<Player> {
try {
listener = (PlayerListener) FieldUtils.readField(crazyLoginPlugin, "playerListener", true);
} catch (IllegalAccessException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to get the listener instance for auto login", ex);
plugin.getLog().error("Failed to get the listener instance for auto login", ex);
listener = null;
}
@@ -5,7 +5,6 @@ import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -45,7 +44,7 @@ public class UltraAuthHook implements AuthPlugin<Player> {
try {
return future.get();
} catch (InterruptedException | ExecutionException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
plugin.getLog().error("Failed to forceLogin player: {}", player, ex);
return false;
}
}
@@ -8,7 +8,6 @@ import de.luricos.bukkit.xAuth.xAuthPlayer;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -52,7 +51,7 @@ public class xAuthHook implements AuthPlugin<Player> {
try {
return future.get();
} catch (InterruptedException | ExecutionException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
plugin.getLog().error("Failed to forceLogin player: {}", player, ex);
return false;
}
}
@@ -80,7 +79,7 @@ public class xAuthHook implements AuthPlugin<Player> {
//login in the player after registration
return future.get() && forceLogin(player);
} catch (InterruptedException | ExecutionException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
plugin.getLog().error("Failed to forceRegister player: {}", player, ex);
return false;
}
}
@@ -99,7 +99,7 @@ public class BungeeListener implements PluginMessageListener {
new ForceLoginTask(plugin.getCore(), player).run();
}
} catch (Exception ex) {
plugin.getLog().error("Failed to query isRegistered", ex);
plugin.getLog().error("Failed to query isRegistered for player: {}", player, ex);
}
}, 20L);
}
@@ -7,6 +7,7 @@ import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.PlayerProfile;
import com.github.games647.fastlogin.core.shared.JoinManagement;
import java.security.PublicKey;
import java.util.Random;
import org.bukkit.command.CommandSender;
@@ -17,6 +18,7 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
private final FastLoginBukkit plugin;
private final PacketEvent packetEvent;
private final PublicKey publicKey;
private final Random random;
@@ -24,11 +26,12 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
private final String username;
public NameCheckTask(FastLoginBukkit plugin, PacketEvent packetEvent, Random random,
Player player, String username) {
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;
@@ -37,7 +40,7 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
@Override
public void run() {
try {
super.onLogin(username, new ProtocolLibLoginSource(plugin, packetEvent, player, random));
super.onLogin(username, new ProtocolLibLoginSource(packetEvent, player, random, publicKey));
} finally {
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
}
@@ -46,12 +49,12 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
//Minecraft server implementation
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
@Override
public void requestPremiumLogin(ProtocolLibLoginSource source, PlayerProfile profile
, String username, boolean registered) {
public void requestPremiumLogin(ProtocolLibLoginSource source, PlayerProfile profile,
String username, boolean registered) {
try {
source.setOnlineMode();
} catch (Exception ex) {
plugin.getLog().error("Cannot send encryption packet. Falling back to cracked login", ex);
plugin.getLog().error("Cannot send encryption packet. Falling back to cracked login for: {}", profile, ex);
return;
}
@@ -5,8 +5,10 @@ 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;
@@ -20,8 +22,10 @@ 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
@@ -34,6 +38,7 @@ public class ProtocolLibListener extends PacketAdapter {
}
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))
@@ -61,7 +66,7 @@ public class ProtocolLibListener extends PacketAdapter {
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
packetEvent.getAsyncMarker().incrementProcessingDelay();
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret);
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair);
Bukkit.getScheduler().runTaskAsynchronously(plugin, verifyTask);
}
@@ -79,7 +84,7 @@ public class ProtocolLibListener extends PacketAdapter {
plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username);
packetEvent.getAsyncMarker().incrementProcessingDelay();
Runnable nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username);
Runnable nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username, keyPair.getPublic());
Bukkit.getScheduler().runTaskAsynchronously(plugin, nameCheckTask);
}
}
@@ -6,7 +6,6 @@ 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.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.shared.LoginSource;
import java.lang.reflect.InvocationTargetException;
@@ -23,31 +22,40 @@ import static com.comphenix.protocol.PacketType.Login.Server.ENCRYPTION_BEGIN;
public class ProtocolLibLoginSource implements LoginSource {
private final FastLoginBukkit plugin;
private final PacketEvent packetEvent;
private final Player player;
private final Random random;
private final PublicKey publicKey;
private String serverId;
private final String serverId = "";
private byte[] verifyToken;
public ProtocolLibLoginSource(FastLoginBukkit plugin, PacketEvent packetEvent, Player player, Random random) {
this.plugin = plugin;
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 {
//randomized server id to make sure the request is for our server
//this could be relevant https://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
serverId = Long.toString(random.nextLong(), 16);
verifyToken = EncryptionUtil.generateVerifyToken(random);
sentEncryptionRequest();
/*
* 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
@@ -72,24 +80,6 @@ public class ProtocolLibLoginSource implements LoginSource {
return packetEvent.getPlayer().getAddress();
}
private void sentEncryptionRequest() throws InvocationTargetException {
/*
* 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);
PublicKey publicKey = plugin.getServerKey().getPublic();
newPacket.getSpecificModifier(PublicKey.class).write(0, publicKey);
newPacket.getByteArrays().write(0, verifyToken);
//serverId is a empty string
ProtocolLibrary.getProtocolManager().sendServerPacket(player, newPacket);
}
public String getServerId() {
return serverId;
}
@@ -63,7 +63,7 @@ public class SkinApplyListener implements Listener {
try {
MethodUtils.invokeMethod(map, "put", new Object[]{SkinProperties.TEXTURE_KEY, skin.getHandle()});
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
plugin.getLog().error("Error setting premium skin", ex);
plugin.getLog().error("Error setting premium skin of: {}", player, ex);
}
}
}
@@ -15,8 +15,8 @@ import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.UUID;
@@ -32,16 +32,19 @@ 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) {
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
@@ -65,8 +68,7 @@ public class VerifyResponseTask implements Runnable {
}
private void verifyResponse(BukkitLoginSession session) {
PublicKey publicKey = plugin.getServerKey().getPublic();
PrivateKey privateKey = plugin.getServerKey().getPrivate();
PrivateKey privateKey = serverKey.getPrivate();
Cipher cipher;
SecretKey loginKey;
@@ -88,10 +90,7 @@ public class VerifyResponseTask implements Runnable {
return;
}
//this makes sure the request from the client is for us
//this might be relevant https://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
String generatedId = session.getServerId();
String serverId = EncryptionUtil.getServerIdHashString(generatedId, loginKey, publicKey);
String serverId = EncryptionUtil.getServerIdHashString("", loginKey, serverKey.getPublic());
String username = session.getUsername();
if (plugin.getCore().getApiConnector().hasJoinedServer(session, serverId, player.getAddress())) {
@@ -115,7 +114,7 @@ public class VerifyResponseTask implements Runnable {
//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", exc);
plugin.getLog().error("Error setting premium uuid of {}", player, exc);
}
}
}
@@ -188,7 +187,7 @@ public class VerifyResponseTask implements Runnable {
//tell the server that we want to close the connection
player.kickPlayer("Disconnect");
} catch (InvocationTargetException ex) {
plugin.getLog().error("Error sending kick packet", ex);
plugin.getLog().error("Error sending kick packet for: {}", player, ex);
}
}
@@ -204,7 +203,7 @@ public class VerifyResponseTask implements Runnable {
//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", 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"));
}
@@ -77,7 +77,7 @@ public class DelayedAuthHook implements Runnable {
}
}
} catch (ReflectiveOperationException ex) {
plugin.getLog().error("Couldn't load the integration class", ex);
plugin.getLog().error("Couldn't load the auth hook class", ex);
}
return null;
@@ -55,7 +55,7 @@ public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender,
//the player-list isn't thread-safe
return Bukkit.getScheduler().callSyncMethod(core.getPlugin(), player::isOnline).get();
} catch (InterruptedException | ExecutionException ex) {
core.getPlugin().getLog().error("Failed to perform thread-safe online check", ex);
core.getPlugin().getLog().error("Failed to perform thread-safe online check for {}", player, ex);
return false;
}
}