From 0b0a46a18a0bb709bbf37319e996c6bcc0528551 Mon Sep 17 00:00:00 2001 From: games647 Date: Wed, 6 Jul 2022 16:49:26 +0200 Subject: [PATCH] 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)