forked from TuxCoding/FastLogin
Make client public key verification optional
This commit is contained in:
@ -32,6 +32,8 @@ import com.github.games647.fastlogin.core.shared.LoginSession;
|
|||||||
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a client connecting to the server.
|
* Represents a client connecting to the server.
|
||||||
*
|
*
|
||||||
@ -83,6 +85,7 @@ public class BukkitLoginSession extends LoginSession {
|
|||||||
return verifyToken.clone();
|
return verifyToken.clone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
public ClientPublicKey getClientPublicKey() {
|
public ClientPublicKey getClientPublicKey() {
|
||||||
return clientPublicKey;
|
return clientPublicKey;
|
||||||
}
|
}
|
||||||
|
@ -117,7 +117,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
|
|||||||
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
|
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
|
||||||
pluginManager.registerEvents(new ProtocolSupportListener(this, core.getAntiBot()), this);
|
pluginManager.registerEvents(new ProtocolSupportListener(this, core.getAntiBot()), this);
|
||||||
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
|
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
|
||||||
ProtocolLibListener.register(this, core.getAntiBot());
|
ProtocolLibListener.register(this, core.getAntiBot(), core.getConfig().getBoolean("verifyClientKeys"));
|
||||||
|
|
||||||
if (isPluginInstalled("floodgate")) {
|
if (isPluginInstalled("floodgate")) {
|
||||||
if (getConfig().getBoolean("floodgatePrefixWorkaround")){
|
if (getConfig().getBoolean("floodgatePrefixWorkaround")){
|
||||||
|
@ -27,8 +27,6 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
|||||||
|
|
||||||
import com.comphenix.protocol.ProtocolLibrary;
|
import com.comphenix.protocol.ProtocolLibrary;
|
||||||
import com.comphenix.protocol.events.PacketEvent;
|
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.BukkitLoginSession;
|
||||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||||
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
|
||||||
@ -38,7 +36,6 @@ import com.github.games647.fastlogin.core.shared.JoinManagement;
|
|||||||
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
|
||||||
|
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.bukkit.command.CommandSender;
|
import org.bukkit.command.CommandSender;
|
||||||
@ -49,6 +46,8 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
|
|||||||
|
|
||||||
private final FastLoginBukkit plugin;
|
private final FastLoginBukkit plugin;
|
||||||
private final PacketEvent packetEvent;
|
private final PacketEvent packetEvent;
|
||||||
|
|
||||||
|
private final ClientPublicKey clientKey;
|
||||||
private final PublicKey serverKey;
|
private final PublicKey serverKey;
|
||||||
|
|
||||||
private final Random random;
|
private final Random random;
|
||||||
@ -57,11 +56,12 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
|
|||||||
private final String username;
|
private final String username;
|
||||||
|
|
||||||
public NameCheckTask(FastLoginBukkit plugin, Random random, Player player, PacketEvent packetEvent,
|
public NameCheckTask(FastLoginBukkit plugin, Random random, Player player, PacketEvent packetEvent,
|
||||||
String username, PublicKey serverKey) {
|
String username, ClientPublicKey clientKey, PublicKey serverKey) {
|
||||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook(), plugin.getBedrockService());
|
super(plugin.getCore(), plugin.getCore().getAuthPluginHook(), plugin.getBedrockService());
|
||||||
|
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.packetEvent = packetEvent;
|
this.packetEvent = packetEvent;
|
||||||
|
this.clientKey = clientKey;
|
||||||
this.serverKey = serverKey;
|
this.serverKey = serverKey;
|
||||||
this.random = random;
|
this.random = random;
|
||||||
this.player = player;
|
this.player = player;
|
||||||
@ -71,10 +71,7 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
try {
|
try {
|
||||||
Optional<WrappedProfileKeyData> clientKey = packetEvent.getPacket()
|
super.onLogin(username, new ProtocolLibLoginSource(player, random, serverKey, clientKey));
|
||||||
.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter()).read(0);
|
|
||||||
|
|
||||||
super.onLogin(username, new ProtocolLibLoginSource(player, random, serverKey, clientKey.orElse(null)));
|
|
||||||
} finally {
|
} finally {
|
||||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||||
}
|
}
|
||||||
@ -104,8 +101,7 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
|
|||||||
core.getPendingLogin().put(ip + username, new Object());
|
core.getPendingLogin().put(ip + username, new Object());
|
||||||
|
|
||||||
byte[] verify = source.getVerifyToken();
|
byte[] verify = source.getVerifyToken();
|
||||||
WrappedProfileKeyData key = source.getClientPublicKey();
|
ClientPublicKey clientKey = source.getClientKey();
|
||||||
ClientPublicKey clientKey = new ClientPublicKey(key.getExpireTime(), key.getKey(), key.getSignature());
|
|
||||||
|
|
||||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, verify, clientKey, registered, profile);
|
BukkitLoginSession playerSession = new BukkitLoginSession(username, verify, clientKey, registered, profile);
|
||||||
plugin.putSession(player.getAddress(), playerSession);
|
plugin.putSession(player.getAddress(), playerSession);
|
||||||
|
@ -49,6 +49,7 @@ import java.security.PublicKey;
|
|||||||
import java.security.SecureRandom;
|
import java.security.SecureRandom;
|
||||||
import java.security.SignatureException;
|
import java.security.SignatureException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
@ -66,7 +67,9 @@ public class ProtocolLibListener extends PacketAdapter {
|
|||||||
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||||
private final AntiBotService antiBotService;
|
private final AntiBotService antiBotService;
|
||||||
|
|
||||||
public ProtocolLibListener(FastLoginBukkit plugin, AntiBotService antiBotService) {
|
private final boolean verifyClientKeys;
|
||||||
|
|
||||||
|
public ProtocolLibListener(FastLoginBukkit plugin, AntiBotService antiBotService, boolean verifyClientKeys) {
|
||||||
//run async in order to not block the server, because we are making api calls to Mojang
|
//run async in order to not block the server, because we are making api calls to Mojang
|
||||||
super(params()
|
super(params()
|
||||||
.plugin(plugin)
|
.plugin(plugin)
|
||||||
@ -75,14 +78,15 @@ public class ProtocolLibListener extends PacketAdapter {
|
|||||||
|
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.antiBotService = antiBotService;
|
this.antiBotService = antiBotService;
|
||||||
|
this.verifyClientKeys = verifyClientKeys;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void register(FastLoginBukkit plugin, AntiBotService antiBotService) {
|
public static void register(FastLoginBukkit plugin, AntiBotService antiBotService, boolean verifyClientKeys) {
|
||||||
// they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
|
// they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
|
||||||
// TODO: make synchronous processing, but do web or database requests async
|
// TODO: make synchronous processing, but do web or database requests async
|
||||||
ProtocolLibrary.getProtocolManager()
|
ProtocolLibrary.getProtocolManager()
|
||||||
.getAsynchronousManager()
|
.getAsynchronousManager()
|
||||||
.registerAsyncHandler(new ProtocolLibListener(plugin, antiBotService))
|
.registerAsyncHandler(new ProtocolLibListener(plugin, antiBotService, verifyClientKeys))
|
||||||
.start();
|
.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,6 +145,13 @@ public class ProtocolLibListener extends PacketAdapter {
|
|||||||
plugin.getLog().warn("GameProfile {} tried to send encryption response at invalid state", sender.getAddress());
|
plugin.getLog().warn("GameProfile {} tried to send encryption response at invalid state", sender.getAddress());
|
||||||
sender.kickPlayer(plugin.getCore().getMessage("invalid-request"));
|
sender.kickPlayer(plugin.getCore().getMessage("invalid-request"));
|
||||||
} else {
|
} 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);
|
||||||
|
sender.kickPlayer(plugin.getCore().getMessage("invalid-verify-token"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Either<byte[], ?> either = packetEvent.getPacket().getSpecificModifier(Either.class).read(0);
|
Either<byte[], ?> either = packetEvent.getPacket().getSpecificModifier(Either.class).read(0);
|
||||||
Object signatureData = either.right().get();
|
Object signatureData = either.right().get();
|
||||||
long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true);
|
long salt = FuzzyReflection.getFieldValue(signatureData, Long.TYPE, true);
|
||||||
@ -176,9 +187,13 @@ public class ProtocolLibListener extends PacketAdapter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
PacketContainer packet = packetEvent.getPacket();
|
PacketContainer packet = packetEvent.getPacket();
|
||||||
WrappedProfileKeyData profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter())
|
var profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter())
|
||||||
.read(0).orElse(null);
|
.read(0);
|
||||||
if (profileKey != null && !verifyPublicKey(profileKey)) {
|
|
||||||
|
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"));
|
player.kickPlayer(plugin.getCore().getMessage("invalid-public-key"));
|
||||||
plugin.getLog().warn("Invalid public key from player {}", username);
|
plugin.getLog().warn("Invalid public key from player {}", username);
|
||||||
return;
|
return;
|
||||||
@ -187,20 +202,24 @@ public class ProtocolLibListener extends PacketAdapter {
|
|||||||
plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username);
|
plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username);
|
||||||
|
|
||||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
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);
|
plugin.getScheduler().runAsync(nameCheckTask);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean verifyPublicKey(WrappedProfileKeyData profileKey) {
|
private Optional<ClientPublicKey> verifyPublicKey(WrappedProfileKeyData profileKey) {
|
||||||
Instant expires = profileKey.getExpireTime();
|
Instant expires = profileKey.getExpireTime();
|
||||||
PublicKey key = profileKey.getKey();
|
PublicKey key = profileKey.getKey();
|
||||||
byte[] signature = profileKey.getSignature();
|
byte[] signature = profileKey.getSignature();
|
||||||
ClientPublicKey clientKey = new ClientPublicKey(expires, key, signature);
|
ClientPublicKey clientKey = new ClientPublicKey(expires, key, signature);
|
||||||
try {
|
try {
|
||||||
return EncryptionUtil.verifyClientKey(clientKey, Instant.now());
|
if (EncryptionUtil.verifyClientKey(clientKey, Instant.now())) {
|
||||||
|
return Optional.of(clientKey);
|
||||||
|
}
|
||||||
} catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) {
|
} catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) {
|
||||||
return false;
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getUsername(PacketContainer packet) {
|
private String getUsername(PacketContainer packet) {
|
||||||
|
@ -30,7 +30,7 @@ import com.comphenix.protocol.ProtocolManager;
|
|||||||
import com.comphenix.protocol.events.PacketContainer;
|
import com.comphenix.protocol.events.PacketContainer;
|
||||||
import com.comphenix.protocol.reflect.StructureModifier;
|
import com.comphenix.protocol.reflect.StructureModifier;
|
||||||
import com.comphenix.protocol.wrappers.WrappedChatComponent;
|
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 com.github.games647.fastlogin.core.shared.LoginSource;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
@ -49,17 +49,17 @@ class ProtocolLibLoginSource implements LoginSource {
|
|||||||
|
|
||||||
private final Random random;
|
private final Random random;
|
||||||
|
|
||||||
private final WrappedProfileKeyData clientPublicKey;
|
private final ClientPublicKey clientKey;
|
||||||
private final PublicKey publicKey;
|
private final PublicKey publicKey;
|
||||||
|
|
||||||
private final String serverId = "";
|
private final String serverId = "";
|
||||||
private byte[] verifyToken;
|
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.player = player;
|
||||||
this.random = random;
|
this.random = random;
|
||||||
this.publicKey = serverPublicKey;
|
this.publicKey = serverPublicKey;
|
||||||
this.clientPublicKey = clientPublicKey;
|
this.clientKey = clientKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -112,8 +112,8 @@ class ProtocolLibLoginSource implements LoginSource {
|
|||||||
return player.getAddress();
|
return player.getAddress();
|
||||||
}
|
}
|
||||||
|
|
||||||
public WrappedProfileKeyData getClientPublicKey() {
|
public ClientPublicKey getClientKey() {
|
||||||
return clientPublicKey;
|
return clientKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getServerId() {
|
public String getServerId() {
|
||||||
|
@ -263,7 +263,7 @@ public class VerifyResponseTask implements Runnable {
|
|||||||
|
|
||||||
EquivalentConverter<WrappedProfileKeyData> converter = BukkitConverters.getWrappedPublicKeyDataConverter();
|
EquivalentConverter<WrappedProfileKeyData> converter = BukkitConverters.getWrappedPublicKeyDataConverter();
|
||||||
var key = new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), sharedSecret);
|
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 {
|
} else {
|
||||||
//uuid is ignored by the packet definition
|
//uuid is ignored by the packet definition
|
||||||
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
|
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
|
||||||
|
@ -272,6 +272,15 @@ autoRegisterFloodgate: false
|
|||||||
# Enabling this might lead to people gaining unauthorized access to other's accounts!
|
# Enabling this might lead to people gaining unauthorized access to other's accounts!
|
||||||
floodgatePrefixWorkaround: false
|
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
|
# Database configuration
|
||||||
# Recommended is the use of MariaDB (a better version of MySQL)
|
# Recommended is the use of MariaDB (a better version of MySQL)
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user