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="
+}
FastLogin
Checks if a Minecraft player has a paid account (premium). If so, they can skip offline authentication (auth plugins). So they don't need to enter passwords. This is also called auto login (auto-login).
Features
- Detect paid accounts from others
- Automatically login paid accounts (premium)
- Support various of auth plugins
- Cauldron support
- Forge/Sponge message support
- Premium UUID support
- Forward skins
- Detect username changed and will update the existing database record
- BungeeCord support
- Auto register new premium players
- Plugin: ProtocolSupport is supported and can be used as an alternative to ProtocolLib
- No client modifications needed
- Good performance by using async operations
- Locale messages
- Support for Bedrock players proxied through FloodGate
Issues
Please use issues for bug reports, suggestions, questions and more. Please check for existing issues. Existing issues can be voted up by adding up vote to the original post. Closing issues means that they are marked as resolved. Comments are still allowed and it could be re-opened.
Development builds
Development builds contain the latest changes from the Source-Code. They are bleeding edge and could introduce new bugs,
but also include features, enhancements and bug fixes that are not yet in a released version. If you click on the left
side on Changes, you can see iterative change sets leading to a specific build.
You can download them from here: https://ci.codemc.org/job/Games647/job/FastLogin/
Commands
/premium [player] Label the invoker or the argument as paid account
/cracked [player] Label the invoker or the argument as cracked account
Permissions
fastlogin.bukkit.command.premium
fastlogin.bukkit.command.cracked
fastlogin.command.premium.other
fastlogin.command.cracked.other
Placeholder
This plugin supports PlaceholderAPI on Spigot. It exports the following variable
%fastlogin_status%. In BungeeCord environments, the status of a player will be delivered with a delay after the player
already successful joined the server. This takes about a couple of milliseconds. In this case the value
will be Unknown.
Possible values: Premium, Cracked, Unknown
Requirements
- Java 17+
- Server software in offlinemode:
- Spigot (or a fork e.g. Paper) 1.8.8+
- Protocol plugin:
- Latest BungeeCord (or a fork e.g. Waterfall)
- Spigot (or a fork e.g. Paper) 1.8.8+
- An auth plugin.
Supported auth plugins
Spigot/Paper
- AdvancedLogin (Paid)
- AuthMe (5.X)
- CrazyLogin
- LoginSecurity
- LogIt
- SodionAuth (2.0+)
- UltraAuth
- UserLogin
- xAuth
BungeeCord/Waterfall
Network requests
This plugin performs network requests to:
- https://api.mojang.com - retrieving uuid data to decide if we should activate premium login
- https://sessionserver.mojang.com - verify if the player is the owner of that account
How to install
Spigot/Paper
- Download and install ProtocolLib/ProtocolSupport
- Download and install FastLogin (or
FastLoginBukkitfor newer versions) - Set your server in offline mode by setting the value
onlinemodein your server.properties to false
BungeeCord/Waterfall or Velocity
Install the plugin on both platforms, that is proxy (BungeeCord or Velocity) and backend server (Spigot).
- Activate proxy support in the server configuration
- This is often found in
spigot.ymlorpaper.yml
- This is often found in
- Restart the backend server
- Now there is
allowed-proxies.txtfile in the FastLogin folder of the restarted server- BungeeCord: Put your
stats-idfrom the BungeeCord config into this file - Velocity: On plugin startup the plugin generates a
proxyId.txtinside the plugins folder of the proxy
- BungeeCord: Put your
- Activate ip forwarding in your proxy config
- Check your database settings in the config of FastLogin on your proxy
- The proxies only ship with a limited set of drivers where Spigot supports more. Therefore, these are supported:
- BungeeCord:
com.mysql.jdbc.Driverfor MySQL/MariaDB - Velocity:
fastlogin.mariadb.jdbc.Driverfor MySQL/MariaDB - Note the embedded file storage SQLite is not available
- MySQL/MariaDB requires an external database server running. Check your server provider if there is one available or install one.
- Set proxy and Spigot in offline mode by setting the value
onlinemodein yourconfig.ymlto false - You should always configure the firewall for your Spigot server so that it's only accessible through your proxy
- This is also the case without this plugin
- https://www.spigotmc.org/wiki/bungeecord-installation/#post-installation