diff --git a/bukkit/pom.xml b/bukkit/pom.xml
index 9b3c5c5d..c1f63ba1 100644
--- a/bukkit/pom.xml
+++ b/bukkit/pom.xml
@@ -179,6 +179,19 @@
1.0.7
+
+ com.mojang
+ datafixerupper
+ 5.0.28
+ provided
+
+
+ *
+ *
+
+
+
+
com.comphenix.protocol
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 556def1b..4f4af4dd 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 956a60f7..713fa68a 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 f0a40830..485c065d 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
- implements Runnable {
+ implements Runnable {
private final FastLoginBukkit plugin;
private final PacketEvent packetEvent;
@@ -67,7 +71,9 @@ public class NameCheckTask extends JoinManagement 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 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, session, 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 dd191f1c..d87b602d 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 e974b09e..d13a5c9c 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 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 00000000..c2a2d1a4
--- /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 {
+
+ @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 904405ae..63a24bac 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 00000000..8ea85f6e
--- /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/java/integration/README.md b/bukkit/src/test/java/integration/README.md
new file mode 100644
index 00000000..d4efc537
--- /dev/null
+++ b/bukkit/src/test/java/integration/README.md
@@ -0,0 +1,53 @@
+# Integration tests for authentication
+
+## Description
+
+Projects require integration tests in order to check against errors that could only occur if connected to other
+components. However, they are heavier in terms of performance and require a more complex setup. Unit tests often make
+use of fake, mock, stubs, etc. implementations to test the unit in isolation and thus could hide issues across
+boundaries of a unit. Nevertheless, both are not replacement for each other.
+
+## Usage in this project
+
+The authentication system is a core component, so it requires some kind of testing. Here we are going to
+spin up a Spigot server and test with the supported authentication schemes against the implementation of MCProtocolLib.
+
+### Goals
+
+* OS platform independent
+* Reproducible, but not fixed to a specific image hash
+ * This is a dev container, so fixing it to feature/major version is enough instead of a version fixed by hash
+* Improve container spin up
+ * E.g. Remove/Reduce world generation
+
+### Note on automation
+
+The simplest solution it to use the official Mojang session and authentication servers. However, this would require
+a spare Minecraft account. Mocking the auth servers would be a solution to avoid this.
+
+## Related
+
+Interest blog article about integration tests and why they are necessary.
+https://software.rajivprab.com/2019/04/28/rethinking-software-testing-perspectives-from-the-world-of-hardware/
+
+## Issues
+
+### Slow startup
+
+Tried a lot of optimizations like only loading a single world without the nether or the end. However, there the startup
+is still slow. If you have any ideas on how to tune the startup parameters of the Minecraft server or the JVM
+itself to reduce the startup time, please suggest it.
+
+### Checkpoint
+
+An idea to optimize the time is to use CRIU (checkpoint and restore). So to save the process at a certain stage and
+restore all data multiple times. This could cause a lot of issues like open files have to be present. However, the
+impact is significant and since it runs inside the container all files, pids (pid=1) should be matching. Potential
+checkpoint locations are:
+
+* Direct before loading the plugins
+ * Likely before binding the port to prevent issues
+* After loading the libraries
+
+Nevertheless, the current state requires to run it with root and the Java support is currently still in progress.
+
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 00000000..1165e23d
--- /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 7ecc12e4..ea509fec 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 37bb3ad0..98ea33f5 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 cbca4b16..2b80c2c9 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 a2d6a41a..00000000
--- 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 00000000..8943e87b
--- /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 00000000..cc603bcc
--- /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 00000000..b88c0f5d
--- /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 00000000..8edffb62
--- /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 00000000..ba6bac53
--- /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 00000000..7f4f4ad5
--- /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
+ }
+}