Commit Graph
31 Commits
Author SHA1 Message Date
games647 a182976207 Update license date 2023-04-13 12:00:07 +02:00
games647 e5d61101ae Typo fixes 2023-04-11 10:36:42 +02:00
games647 ed45fada59 Update 1.19.1 public key verification 2022-07-28 17:06:06 +02:00
games647 1e732d62cb Replace Field- and MethodUtils from breaking changes in ProtocolLib 2022-07-27 10:06:46 +02:00
games647 05708a256c Perform check for available reflection during compile time 2022-07-25 13:02:30 +02:00
games647 9fdbdeaf9a Use Lombok to create a record-like struct 2022-07-22 13:26:39 +02:00
games647 dd2fca2ea1 Replace var with val to prevent conflicts with newer Java version
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 81abad1..d148bc1 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
@@ -59,7 +59,7 @@ import javax.crypto.NoSuchPaddingException;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;

-import lombok.var;
+import lombok.val;

 /**
  * Encryption and decryption minecraft util for connection between servers
@@ -179,9 +179,9 @@ final class EncryptionUtil {

     private static PublicKey loadMojangSessionKey()
             throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
-        var keyUrl = FastLoginBukkit.class.getClassLoader().getResource("yggdrasil_session_pubkey.der");
-        var keyData = Resources.toByteArray(keyUrl);
-        var keySpec = new X509EncodedKeySpec(keyData);
+        val keyUrl = FastLoginBukkit.class.getClassLoader().getResource("yggdrasil_session_pubkey.der");
+        val keyData = Resources.toByteArray(keyUrl);
+        val keySpec = new X509EncodedKeySpec(keyData);

         return KeyFactory.getInstance("RSA").generatePublic(keySpec);
     }
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 dc208ff..1363cb3 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
@@ -60,7 +60,7 @@ import java.util.UUID;
 import javax.crypto.Cipher;
 import javax.crypto.SecretKey;

-import lombok.var;
+import lombok.val;
 import org.bukkit.entity.Player;

 import static com.comphenix.protocol.PacketType.Login.Client.START;
@@ -269,7 +269,7 @@ public class VerifyResponseTask implements Runnable {
             startPacket.getStrings().write(0, username);

             EquivalentConverter<WrappedProfileKeyData> converter = BukkitConverters.getWrappedPublicKeyDataConverter();
-            var wrappedKey = Optional.ofNullable(clientKey).map(key ->
+            val wrappedKey = Optional.ofNullable(clientKey).map(key ->
                 new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), clientKey.signature())
             );

diff --git a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/FastLoginBukkitTest.java b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/FastLoginBukkitTest.java
index e4ce55a..9d6832e 100644
--- a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/FastLoginBukkitTest.java
+++ b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/FastLoginBukkitTest.java
@@ -30,7 +30,7 @@ import com.github.games647.fastlogin.core.CommonUtil;
 import net.md_5.bungee.api.chat.TextComponent;
 import net.md_5.bungee.chat.ComponentSerializer;

-import lombok.var;
+import lombok.val;
 import org.junit.jupiter.api.Test;

 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -39,12 +39,12 @@ class FastLoginBukkitTest {

     @Test
     void testRGB() {
-        var message = "&x00002a00002b&lText";
-        var msg = CommonUtil.translateColorCodes(message);
+        val message = "&x00002a00002b&lText";
+        val msg = CommonUtil.translateColorCodes(message);
         assertEquals(msg, "§x00002a00002b§lText");

-        var components = TextComponent.fromLegacyText(msg);
-        var expected = "{\"bold\":true,\"color\":\"#00a00b\",\"text\":\"Text\"}";
+        val components = TextComponent.fromLegacyText(msg);
+        val expected = "{\"bold\":true,\"color\":\"#00a00b\",\"text\":\"Text\"}";
         assertEquals(ComponentSerializer.toString(components), expected);
     }
 }
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
index 29b2f3d..c2fb34d 100644
--- 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
@@ -32,13 +32,13 @@ import com.google.gson.stream.JsonWriter;
 import java.io.IOException;
 import java.util.Base64;

-import lombok.var;
+import lombok.val;

 public class Base64Adapter extends TypeAdapter<byte[]> {

     @Override
     public void write(JsonWriter out, byte[] value) throws IOException {
-        var encoded = Base64.getEncoder().encodeToString(value);
+        val encoded = Base64.getEncoder().encodeToString(value);
         out.value(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 df3888e..1bbe864 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
@@ -50,7 +50,7 @@ import javax.crypto.NoSuchPaddingException;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;

-import lombok.var;
+import lombok.val;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
@@ -61,7 +61,7 @@ class EncryptionUtilTest {

     @Test
     void testVerifyToken() {
-        var random = ThreadLocalRandom.current();
+        val random = ThreadLocalRandom.current();
         byte[] token = EncryptionUtil.generateVerifyToken(random);

         assertAll(
@@ -89,10 +89,10 @@ class EncryptionUtilTest {

     @Test
     void testExpiredClientKey() throws Exception {
-        var clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
+        val 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();
+        val expiredTimestamp = clientKey.expiry();
         assertFalse(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp));
     }

@@ -106,7 +106,7 @@ class EncryptionUtilTest {
             "client_keys/invalid_wrong_signature.json"
     })
     void testInvalidClientKey(String clientKeySource) throws Exception {
-        var clientKey = ResourceLoader.loadClientKey(clientKeySource);
+        val clientKey = ResourceLoader.loadClientKey(clientKeySource);
         Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);

         assertFalse(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp));
@@ -114,8 +114,8 @@ class EncryptionUtilTest {

     @Test
     void testValidClientKey() throws Exception {
-        var clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
-        var verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
+        val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
+        val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);

         assertTrue(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp));
     }
@@ -123,7 +123,7 @@ class EncryptionUtilTest {
     @Test
     void testDecryptSharedSecret() throws Exception {
         KeyPair serverPair = EncryptionUtil.generateKeyPair();
-        var serverPK = serverPair.getPublic();
+        val serverPK = serverPair.getPublic();

         SecretKey secretKey = generateSharedKey();
         byte[] encryptedSecret = encrypt(serverPK, secretKey.getEncoded());
@@ -135,7 +135,7 @@ class EncryptionUtilTest {
     private static byte[] encrypt(PublicKey receiverKey, byte... message)
             throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException,
             IllegalBlockSizeException, BadPaddingException {
-        var encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm());
+        val encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm());
         encryptCipher.init(Cipher.ENCRYPT_MODE, receiverKey);
         return encryptCipher.doFinal(message);
     }
@@ -151,9 +151,9 @@ class EncryptionUtilTest {

     @Test
     void testServerIdHash() throws Exception {
-        var serverId = "";
-        var sharedSecret = generateSharedKey();
-        var serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
+        val serverId = "";
+        val sharedSecret = generateSharedKey();
+        val serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();

         String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
         assertEquals(EncryptionUtil.getServerIdHashString(serverId, sharedSecret, serverPK), sessionHash);
@@ -167,7 +167,7 @@ class EncryptionUtilTest {
         // sha1.update(server's encoded public key from Encryption Request)
         // hash := sha1.hexdigest() # String of hex characters
         @SuppressWarnings("deprecation")
-        var hasher = Hashing.sha1().newHasher();
+        val hasher = Hashing.sha1().newHasher();
         hasher.putString(serverId, StandardCharsets.US_ASCII);
         hasher.putBytes(sharedSecret.getEncoded());
         hasher.putBytes(serverPK.getEncoded());
@@ -180,9 +180,9 @@ class EncryptionUtilTest {

     @Test
     void testServerIdHashWrongSecret() throws Exception {
-        var serverId = "";
-        var sharedSecret = generateSharedKey();
-        var serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
+        val serverId = "";
+        val sharedSecret = generateSharedKey();
+        val serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();

         String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
         assertNotEquals(EncryptionUtil.getServerIdHashString("", generateSharedKey(), serverPK), sessionHash);
@@ -190,12 +190,12 @@ class EncryptionUtilTest {

     @Test
     void testServerIdHashWrongServerKey() {
-        var serverId = "";
-        var sharedSecret = generateSharedKey();
-        var serverPK = EncryptionUtil.generateKeyPair().getPublic();
+        val serverId = "";
+        val sharedSecret = generateSharedKey();
+        val serverPK = EncryptionUtil.generateKeyPair().getPublic();

         String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
-        var wrongPK = EncryptionUtil.generateKeyPair().getPublic();
+        val wrongPK = EncryptionUtil.generateKeyPair().getPublic();
         assertNotEquals(EncryptionUtil.getServerIdHashString("", sharedSecret, wrongPK), sessionHash);
     }

@@ -239,8 +239,8 @@ class EncryptionUtilTest {
     @Test
     void testNonce() throws Exception {
         byte[] expected = {1, 2, 3, 4};
-        var serverKey = EncryptionUtil.generateKeyPair();
-        var encryptedNonce = encrypt(serverKey.getPublic(), expected);
+        val serverKey = EncryptionUtil.generateKeyPair();
+        val encryptedNonce = encrypt(serverKey.getPublic(), expected);

         assertTrue(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce));
     }
@@ -248,19 +248,19 @@ class EncryptionUtilTest {
     @Test
     void testNonceIncorrect() throws Exception {
         byte[] expected = {1, 2, 3, 4};
-        var serverKey = EncryptionUtil.generateKeyPair();
+        val serverKey = EncryptionUtil.generateKeyPair();

         // flipped first character
-        var encryptedNonce = encrypt(serverKey.getPublic(), new byte[]{0, 2, 3, 4});
+        val encryptedNonce = encrypt(serverKey.getPublic(), new byte[]{0, 2, 3, 4});
         assertFalse(EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce));
     }

     @Test
     void testNonceFailedDecryption() throws Exception {
         byte[] expected = {1, 2, 3, 4};
-        var serverKey = EncryptionUtil.generateKeyPair();
+        val serverKey = EncryptionUtil.generateKeyPair();
         // generate a new keypair that is different
-        var encryptedNonce = encrypt(EncryptionUtil.generateKeyPair().getPublic(), expected);
+        val encryptedNonce = encrypt(EncryptionUtil.generateKeyPair().getPublic(), expected);

         assertThrows(GeneralSecurityException.class,
                 () -> EncryptionUtil.verifyNonce(expected, serverKey.getPrivate(), encryptedNonce)
@@ -270,7 +270,7 @@ class EncryptionUtilTest {
     @Test
     void testNonceIncorrectEmpty() {
         byte[] expected = {1, 2, 3, 4};
-        var serverKey = EncryptionUtil.generateKeyPair();
+        val serverKey = EncryptionUtil.generateKeyPair();
         byte[] encryptedNonce = {};

         assertThrows(GeneralSecurityException.class,
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 9a75fc5..01b734e 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
@@ -32,13 +32,13 @@ import com.google.gson.annotations.JsonAdapter;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;

-import lombok.var;
+import lombok.val;

 public class SignatureTestData {

     public static SignatureTestData fromResource(String resourceName) throws IOException {
-        var keyUrl = Resources.getResource(resourceName);
-        var encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII);
+        val keyUrl = Resources.getResource(resourceName);
+        val encodedSignature = Resources.toString(keyUrl, StandardCharsets.US_ASCII);

         return new Gson().fromJson(encodedSignature, SignatureTestData.class);
     }
diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java
index 148282c..c83c14d 100644
--- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java
+++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java
@@ -57,6 +57,7 @@ import net.md_5.bungee.api.chat.TextComponent;
 import net.md_5.bungee.api.connection.PendingConnection;
 import net.md_5.bungee.api.connection.ProxiedPlayer;
 import net.md_5.bungee.api.connection.Server;
+import net.md_5.bungee.api.plugin.Listener;
 import net.md_5.bungee.api.plugin.Plugin;
 import net.md_5.bungee.api.plugin.PluginManager;
 import net.md_5.bungee.api.scheduler.GroupedThreadFactory;
@@ -100,7 +101,7 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
         //events
         PluginManager pluginManager = getProxy().getPluginManager();

-        ConnectListener connectListener = new ConnectListener(this, core.getAntiBot());
+        Listener connectListener = new ConnectListener(this, core.getAntiBot());
         pluginManager.registerListener(this, connectListener);
         pluginManager.registerListener(this, new PluginMessageListener(this));
2022-07-22 13:26:38 +02:00
games647 7e9da9fd7c Migrate to Java 8 (sponsored contribution)
Java >8 includes many helpful features and API additions. Current
phoronix benchmarks indicated a very similar performance. Furthermore,
the fact that Java 18 even works with 1.8.8 contributed to the
decision to move to Java 17 like vanilla Java in 1.19.

This also helps us to learn about the newer features added for our
personal interests. As this is a free project, this motivated us to make
this step.

Nevertheless, many server owners were frustrated about this decision.
Thanks to financial contribution, we revised this decision until Java 8
is end of life or no longer used actively used according to bstats.org

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 68095f6..81abad1 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
@@ -50,7 +50,7 @@ import java.time.Instant;
 import java.util.Arrays;
 import java.util.Base64;
 import java.util.Base64.Encoder;
-import java.util.random.RandomGenerator;
+import java.util.Random;

 import javax.crypto.BadPaddingException;
 import javax.crypto.Cipher;
@@ -59,6 +59,8 @@ import javax.crypto.NoSuchPaddingException;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;

+import lombok.var;
+
 /**
  * Encryption and decryption minecraft util for connection between servers
  * and paid Minecraft account clients.
@@ -112,7 +114,7 @@ final class EncryptionUtil {
      * @param random random generator
      * @return a token with 4 bytes long
      */
-    public static byte[] generateVerifyToken(RandomGenerator random) {
+    public static byte[] generateVerifyToken(Random random) {
         byte[] token = new byte[VERIFY_TOKEN_LENGTH];
         random.nextBytes(token);
         return token;
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 2e21ccb..4ce14ff 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
@@ -56,6 +56,7 @@ import javax.crypto.BadPaddingException;
 import javax.crypto.IllegalBlockSizeException;
 import javax.crypto.NoSuchPaddingException;

+import lombok.var;
 import org.bukkit.entity.Player;

 import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN;
@@ -171,7 +172,7 @@ public class ProtocolLibListener extends PacketAdapter {
                 Either<byte[], ?> either = packet.getSpecificModifier(Either.class).read(0);
                 if (clientPublicKey == null) {
                     Optional<byte[]> left = either.left();
-                    if (left.isEmpty()) {
+                    if (!left.isPresent()) {
                         plugin.getLog().error("No verify token sent if requested without player signed key {}", sender);
                         return false;
                     }
@@ -179,7 +180,7 @@ public class ProtocolLibListener extends PacketAdapter {
                     return EncryptionUtil.verifyNonce(expectedToken, keyPair.getPrivate(), left.get());
                 } else {
                     Optional<?> optSignatureData = either.right();
-                    if (optSignatureData.isEmpty()) {
+                    if (!optSignatureData.isPresent()) {
                         plugin.getLog().error("No signature given to sent player signing key {}", sender);
                         return false;
                     }
@@ -219,7 +220,7 @@ public class ProtocolLibListener extends PacketAdapter {
             .optionRead(0);

         var clientKey = profileKey.flatMap(opt -> opt).flatMap(this::verifyPublicKey);
-        if (verifyClientKeys && clientKey.isEmpty()) {
+        if (verifyClientKeys && !clientKey.isPresent()) {
             // missing or incorrect
             // expired always not allowed
             player.kickPlayer(plugin.getCore().getMessage("invalid-public-key"));
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 829d764..dc208ff 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
@@ -60,6 +60,7 @@ import java.util.UUID;
 import javax.crypto.Cipher;
 import javax.crypto.SecretKey;

+import lombok.var;
 import org.bukkit.entity.Player;

 import static com.comphenix.protocol.PacketType.Login.Client.START;
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 e375e29..619d41d 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
@@ -27,8 +27,62 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib.packet;

 import java.security.PublicKey;
 import java.time.Instant;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.StringJoiner;

-public record ClientPublicKey(Instant expiry, PublicKey key, byte[] signature) {
+public final class ClientPublicKey {
+    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 expiry() {
+        return expiry;
+    }
+
+    public PublicKey key() {
+        return key;
+    }
+
+    public byte[] signature() {
+        return signature;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (obj == this) {
+            return true;
+        }
+
+        if (obj == null || obj.getClass() != this.getClass()) {
+            return false;
+        }
+
+        ClientPublicKey that = (ClientPublicKey) obj;
+        return Objects.equals(this.expiry, that.expiry)
+                && Objects.equals(this.key, that.key)
+                && Arrays.equals(this.signature, that.signature);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(expiry, key, signature);
+    }
+
+    @Override
+    public String toString() {
+        return new StringJoiner(", ", ClientPublicKey.class.getSimpleName() + "[", "]")
+                .add("expiry=" + expiry)
+                .add("key=" + key)
+                .add("signature=" + Arrays.toString(signature))
+                .toString();
+    }

     public boolean isExpired(Instant verifyTimestamp) {
         return !verifyTimestamp.isBefore(expiry);
diff --git a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/FastLoginBukkitTest.java b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/FastLoginBukkitTest.java
index 07ff623..e4ce55a 100644
--- a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/FastLoginBukkitTest.java
+++ b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/FastLoginBukkitTest.java
@@ -30,6 +30,7 @@ import com.github.games647.fastlogin.core.CommonUtil;
 import net.md_5.bungee.api.chat.TextComponent;
 import net.md_5.bungee.chat.ComponentSerializer;

+import lombok.var;
 import org.junit.jupiter.api.Test;

 import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -43,8 +44,7 @@ class FastLoginBukkitTest {
         assertEquals(msg, "§x00002a00002b§lText");

         var components = TextComponent.fromLegacyText(msg);
-        var expected = """
-            {"bold":true,"color":"#00a00b","text":"Text"}""";
+        var expected = "{\"bold\":true,\"color\":\"#00a00b\",\"text\":\"Text\"}";
         assertEquals(ComponentSerializer.toString(components), expected);
     }
 }
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
index c2a2d1a..29b2f3d 100644
--- 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
@@ -32,6 +32,8 @@ import com.google.gson.stream.JsonWriter;
 import java.io.IOException;
 import java.util.Base64;

+import lombok.var;
+
 public class Base64Adapter extends TypeAdapter<byte[]> {

     @Override
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 e1b8bea..df3888e 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
@@ -50,6 +50,7 @@ import javax.crypto.NoSuchPaddingException;
 import javax.crypto.SecretKey;
 import javax.crypto.spec.SecretKeySpec;

+import lombok.var;
 import org.junit.jupiter.api.Test;
 import org.junit.jupiter.params.ParameterizedTest;
 import org.junit.jupiter.params.provider.ValueSource;
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
index 4361fe9..ac0991e 100644
--- 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
@@ -33,6 +33,7 @@ import com.google.gson.JsonObject;
 import java.io.IOException;
 import java.io.Reader;
 import java.io.StringReader;
+import java.net.URL;
 import java.nio.charset.StandardCharsets;
 import java.security.KeyFactory;
 import java.security.NoSuchAlgorithmException;
@@ -57,19 +58,19 @@ public class ResourceLoader {
         ) {
             PemObject pemObject = pemReader.readPemObject();
             byte[] content = pemObject.getContent();
-            var privateKeySpec = new PKCS8EncodedKeySpec(content);
+            PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(content);

-            var factory = KeyFactory.getInstance("RSA");
+            KeyFactory factory = KeyFactory.getInstance("RSA");
             return (RSAPrivateKey) factory.generatePrivate(privateKeySpec);
         }
     }

     protected static ClientPublicKey loadClientKey(String path)
         throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
-        var keyUrl = Resources.getResource(path);
+        URL keyUrl = Resources.getResource(path);

-        var lines = Resources.toString(keyUrl, StandardCharsets.US_ASCII);
-        var object = new Gson().fromJson(lines, JsonObject.class);
+        String lines = Resources.toString(keyUrl, StandardCharsets.US_ASCII);
+        JsonObject object = new Gson().fromJson(lines, JsonObject.class);

         Instant expires = Instant.parse(object.getAsJsonPrimitive("expires_at").getAsString());
         String key = object.getAsJsonPrimitive("key").getAsString();
@@ -87,9 +88,9 @@ public class ResourceLoader {
         ) {
             PemObject pemObject = pemReader.readPemObject();
             byte[] content = pemObject.getContent();
-            var pubKeySpec = new X509EncodedKeySpec(content);
+            X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content);

-            var factory = KeyFactory.getInstance("RSA");
+            KeyFactory 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 ee62e24..9a75fc5 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
@@ -32,6 +32,8 @@ import com.google.gson.annotations.JsonAdapter;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;

+import lombok.var;
+
 public class SignatureTestData {

     public static SignatureTestData fromResource(String resourceName) throws IOException {
diff --git a/core/src/main/java/com/github/games647/fastlogin/core/CommonUtil.java b/core/src/main/java/com/github/games647/fastlogin/core/CommonUtil.java
index 324e240..89dc7ee 100644
--- a/core/src/main/java/com/github/games647/fastlogin/core/CommonUtil.java
+++ b/core/src/main/java/com/github/games647/fastlogin/core/CommonUtil.java
@@ -25,8 +25,7 @@
  */
 package com.github.games647.fastlogin.core;

-import com.github.games647.craftapi.cache.SafeCacheBuilder;
-import com.google.common.cache.CacheLoader;
+import com.google.common.cache.CacheBuilder;

 import java.lang.reflect.Constructor;
 import java.util.concurrent.ConcurrentMap;
@@ -43,7 +42,7 @@ public final class CommonUtil {
     private static final char TRANSLATED_CHAR = '§';

     public static <K, V> ConcurrentMap<K, V> buildCache(int expireAfterWrite, int maxSize) {
-        SafeCacheBuilder<Object, Object> builder = SafeCacheBuilder.newBuilder();
+        CacheBuilder<Object, Object> builder = CacheBuilder.newBuilder();

         if (expireAfterWrite > 0) {
             builder.expireAfterWrite(expireAfterWrite, TimeUnit.MINUTES);
@@ -53,9 +52,7 @@ public final class CommonUtil {
             builder.maximumSize(maxSize);
         }

-        return builder.build(CacheLoader.from(() -> {
-            throw new UnsupportedOperationException();
-        }));
+        return (ConcurrentMap<K, V>) builder.build().asMap();
     }

     public static String translateColorCodes(String rawMessage) {
diff --git a/core/src/main/java/com/github/games647/fastlogin/core/StoredProfile.java b/core/src/main/java/com/github/games647/fastlogin/core/StoredProfile.java
index ee58bde..26492d3 100644
--- a/core/src/main/java/com/github/games647/fastlogin/core/StoredProfile.java
+++ b/core/src/main/java/com/github/games647/fastlogin/core/StoredProfile.java
@@ -118,10 +118,11 @@ public class StoredProfile extends Profile {
             return true;
         }

-        if (!(o instanceof StoredProfile that)) {
+        if (!(o instanceof StoredProfile)) {
             return false;
         }

+        StoredProfile that = (StoredProfile) o;
         if (!super.equals(o)) {
             return false;
         }
diff --git a/pom.xml b/pom.xml
index f2f3950..e1b26b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -48,7 +48,7 @@
         <!-- Set default for non-git clones -->
         <git.commit.id>Unknown</git.commit.id>

-        <java.version>17</java.version>
+        <java.version>8</java.version>
         <maven.compiler.source>${java.version}</maven.compiler.source>
         <maven.compiler.target>${java.version}</maven.compiler.target>

@@ -189,5 +189,12 @@
             <version>5.8.2</version>
             <scope>test</scope>
         </dependency>
+
+        <dependency>
+            <groupId>org.projectlombok</groupId>
+            <artifactId>lombok</artifactId>
+            <version>1.18.24</version>
+            <scope>provided</scope>
+        </dependency>
     </dependencies>
 </project>
2022-07-22 13:26:38 +02:00
games647 f513cffbaf Migrate tests to use Junit5 2022-07-22 13:24:57 +02:00
games647 adfae507ac Flip velocity check to scan for newer Paper configurations first
The PaperConfig class file still exists in newer versions
2022-07-08 16:43:02 +02:00
games647 eb47dd3254 Restore compatibility with older Minecraft versions 2022-07-08 16:28:56 +02:00
games647 7c8de84a34 Bump dependencies 2022-07-06 17:08:57 +02:00
games647 e8bb3ec7a9 Validate other encryption methods 2022-06-28 18:34:24 +02:00
games647 1d46640b42 Limit length of server keys 2022-06-28 18:34:24 +02:00
games647 a0fddd69aa Decrease necessary entropy for running tests 2022-06-28 18:34:24 +02:00
games647 11077a002d Migrate public key to record 2022-06-28 18:34:24 +02:00
games647 d9bf7267a6 Test valid server key pairs 2022-06-28 18:34:23 +02:00
games647 b041a89209 Typo fixes 2022-06-28 18:34:23 +02:00
games647 dac5cd7639 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 @@
             <version>1.0.7</version>
         </dependency>

+        <dependency>
+            <groupId>com.mojang</groupId>
+            <artifactId>datafixerupper</artifactId>
+            <version>5.0.28</version>
+            <scope>provided</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>*</groupId>
+                    <artifactId>*</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+
         <!--Library for listening and sending Minecraft packets-->
         <dependency>
             <groupId>com.comphenix.protocol</groupId>
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<Player, CommandSender, ProtocolLibLoginSource>
-        implements Runnable {
+    implements Runnable {

     private final FastLoginBukkit plugin;
     private final PacketEvent packetEvent;
@@ -67,7 +71,9 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
     @Override
     public void run() {
         try {
-            super.onLogin(username, new ProtocolLibLoginSource(player, random, publicKey));
+            Optional<WrappedProfileKeyData> 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<Player, CommandSender, Protoco
     //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) {
+        , String username, boolean registered) {
         try {
             source.enableOnlinemode();
         } catch (Exception ex) {
@@ -97,8 +103,10 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
         core.getPendingLogin().put(ip + username, new Object());

         byte[] verify = source.getVerifyToken();
+        WrappedProfileKeyData key = source.getClientPublicKey();
+        ClientPublicKey clientKey = new ClientPublicKey(key.getExpireTime(), key.getKey(), key.getSignature());

-        BukkitLoginSession playerSession = new BukkitLoginSession(username, verify, registered, profile);
+        BukkitLoginSession playerSession = new BukkitLoginSession(username, verify, clientKey, registered, profile);
         plugin.putSession(player.getAddress(), playerSession);
         //cancel only if the player has a paid account otherwise login as normal offline player
         synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
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 27c0335..a3bb3d0 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
@@ -30,6 +30,7 @@ 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.comphenix.protocol.reflect.FuzzyReflection;
 import com.comphenix.protocol.wrappers.WrappedGameProfile;
 import com.comphenix.protocol.wrappers.WrappedProfilePublicKey;
 import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
@@ -38,6 +39,7 @@ 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 com.mojang.datafixers.util.Either;

 import java.net.InetSocketAddress;
 import java.security.InvalidKeyException;
@@ -139,9 +141,24 @@ 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 {
-            packetEvent.getAsyncMarker().incrementProcessingDelay();
-            Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, session, sharedSecret, keyPair);
-            plugin.getScheduler().runAsync(verifyTask);
+            Either<byte[], ?> 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<WrappedProfileKeyData> 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<byte[]> {
+
+    @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
+    }
+}
2022-06-28 18:34:22 +02:00
games647 bc7c4eea60 Use PublicKey instead of byte representation 2022-06-28 18:34:22 +02:00
games647 d353ac66ab 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 @@
         <repository>
             <id>dmulloy2-repo</id>
             <url>https://repo.dmulloy2.net/repository/public/</url>
-            <snapshots>
-                <enabled>false</enabled>
-            </snapshots>
         </repository>

         <!-- AuthMe Reloaded, xAuth and LoginSecurity -->
@@ -186,7 +183,7 @@
         <dependency>
             <groupId>com.comphenix.protocol</groupId>
             <artifactId>ProtocolLib</artifactId>
-            <version>5.0.0-SNAPSHOT</version>
+            <version>5.0.0-20220603.031413-2</version>
             <scope>provided</scope>
             <exclusions>
                 <exclusion>
@@ -351,5 +348,12 @@
             <scope>system</scope>
             <systemPath>${project.basedir}/lib/UltraAuth v2.1.2.jar</systemPath>
         </dependency>
+
+        <dependency>
+            <groupId>org.bouncycastle</groupId>
+            <artifactId>bcpkix-jdk18on</artifactId>
+            <version>1.71</version>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
 </project>
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> 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="
+}
2022-06-28 18:34:21 +02:00
games647 9ba6e9ba90 Add missing license header 2022-06-24 16:28:18 +02:00
games647 f182adc3bc Add support for RGB colors using the color char
Examples include:
`&x00002a00002b&lText`
2022-06-24 16:21:53 +02:00
Smart123s a856356c49 Update license headers to 02db104
Done using `mvn license:format`
2022-03-13 10:08:57 +01:00
games647 52d778afb1 Clean up 2022-01-14 12:52:46 +01:00
games647 d6a02aa538 Apply license headers to every file
(Related #494)
2021-04-05 11:04:42 +02:00
games647 f40e787b55 Delay force action until PlayerJoinEvent is fired
Fixes Paper async chunk loading -> delayed PlayerJoinEvent
(Related #331)
2020-05-09 18:49:43 +02:00
games647 6f99b6e9b4 Add explicit Junit dependency 2020-03-20 16:13:45 +01:00
games647 3beb8beaeb Migrate tests to assertThat 2018-01-27 18:47:01 +01:00
games647 bb80521ab6 Thermos supports GSON so we could share the json parsing 2017-09-22 18:17:35 +02:00
games647 bb240d3aa0 Refactor encryption implementation
* Simplify utility class and make it more independent from the vendor code
* Create only one cipher object for verification
2017-08-28 12:17:47 +02:00