2021-04-05 11:04:42 +02:00
|
|
|
<!--
|
|
|
|
|
|
|
|
SPDX-License-Identifier: MIT
|
|
|
|
|
|
|
|
The MIT License (MIT)
|
|
|
|
|
2023-04-13 12:00:07 +02:00
|
|
|
Copyright (c) 2015-2023 games647 and contributors
|
2021-04-05 11:04:42 +02:00
|
|
|
|
|
|
|
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.
|
|
|
|
|
|
|
|
-->
|
2022-08-12 09:09:13 +02:00
|
|
|
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
|
2020-03-03 10:50:48 +01:00
|
|
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
2015-11-14 20:03:24 +01:00
|
|
|
<modelVersion>4.0.0</modelVersion>
|
|
|
|
|
2022-07-27 10:06:46 +02:00
|
|
|
<properties>
|
2022-07-27 15:25:36 +02:00
|
|
|
<nettyVersion>4.1.79.Final</nettyVersion>
|
2022-07-27 10:06:46 +02:00
|
|
|
</properties>
|
|
|
|
|
2015-11-14 20:03:24 +01:00
|
|
|
<parent>
|
|
|
|
<groupId>com.github.games647</groupId>
|
2016-05-03 16:55:08 +02:00
|
|
|
<artifactId>fastlogin</artifactId>
|
2022-06-09 18:05:24 +02:00
|
|
|
<version>1.12-SNAPSHOT</version>
|
2015-11-14 20:03:24 +01:00
|
|
|
<relativePath>../pom.xml</relativePath>
|
|
|
|
</parent>
|
|
|
|
|
2022-01-14 12:12:05 +01:00
|
|
|
<!--This has to be in lowercase because it's used by plugin.yml-->
|
2015-11-14 20:03:24 +01:00
|
|
|
<artifactId>fastlogin.bukkit</artifactId>
|
|
|
|
<packaging>jar</packaging>
|
|
|
|
|
|
|
|
<name>FastLoginBukkit</name>
|
|
|
|
|
2018-02-16 12:08:29 +01:00
|
|
|
<build>
|
|
|
|
<plugins>
|
|
|
|
<plugin>
|
|
|
|
<artifactId>maven-shade-plugin</artifactId>
|
2023-10-02 07:56:41 +00:00
|
|
|
<version>3.5.1</version>
|
2018-02-16 12:08:29 +01:00
|
|
|
<configuration>
|
|
|
|
<createDependencyReducedPom>false</createDependencyReducedPom>
|
|
|
|
<shadedArtifactAttached>false</shadedArtifactAttached>
|
|
|
|
<relocations>
|
2023-05-22 15:08:12 +02:00
|
|
|
<relocation>
|
|
|
|
<pattern>org.yaml.snakeyaml</pattern>
|
|
|
|
<shadedPattern>fastlogin.yaml</shadedPattern>
|
|
|
|
</relocation>
|
2018-02-16 12:08:29 +01:00
|
|
|
<relocation>
|
|
|
|
<pattern>com.zaxxer.hikari</pattern>
|
2018-02-19 09:03:53 +01:00
|
|
|
<shadedPattern>fastlogin.hikari</shadedPattern>
|
2018-02-16 12:08:29 +01:00
|
|
|
</relocation>
|
|
|
|
<relocation>
|
|
|
|
<pattern>org.slf4j</pattern>
|
2018-02-19 09:03:53 +01:00
|
|
|
<shadedPattern>fastlogin.slf4j</shadedPattern>
|
2018-02-16 12:08:29 +01:00
|
|
|
</relocation>
|
|
|
|
<relocation>
|
|
|
|
<pattern>net.md_5.bungee.config</pattern>
|
2018-02-19 09:03:53 +01:00
|
|
|
<shadedPattern>fastlogin.config</shadedPattern>
|
2018-02-16 12:08:29 +01:00
|
|
|
</relocation>
|
|
|
|
<relocation>
|
|
|
|
<pattern>com.google.gson</pattern>
|
2018-02-19 09:03:53 +01:00
|
|
|
<shadedPattern>fastlogin.gson</shadedPattern>
|
2018-02-16 12:08:29 +01:00
|
|
|
</relocation>
|
2021-12-01 16:29:37 +01:00
|
|
|
<relocation>
|
|
|
|
<pattern>com.google.common</pattern>
|
|
|
|
<shadedPattern>fastlogin.guava</shadedPattern>
|
2022-01-20 11:50:32 +01:00
|
|
|
<excludes>
|
|
|
|
<exclude>com.google.common.collect.Multimap</exclude>
|
|
|
|
</excludes>
|
2021-12-01 16:29:37 +01:00
|
|
|
</relocation>
|
2021-02-09 00:28:05 +01:00
|
|
|
<relocation>
|
|
|
|
<pattern>io.papermc.lib</pattern>
|
|
|
|
<shadedPattern>fastlogin.paperlib</shadedPattern>
|
|
|
|
</relocation>
|
2018-02-16 12:08:29 +01:00
|
|
|
</relocations>
|
2021-12-02 14:15:57 +01:00
|
|
|
<!-- Rename the service file too to let SLF4J api find our own relocated jdk logger -->
|
|
|
|
<!-- Located in META-INF/services -->
|
|
|
|
<transformers>
|
2022-08-12 09:09:13 +02:00
|
|
|
<transformer
|
|
|
|
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
|
2021-12-02 14:15:57 +01:00
|
|
|
</transformers>
|
2022-01-14 12:14:22 +01:00
|
|
|
<minimizeJar>true</minimizeJar>
|
2022-03-25 08:48:20 +01:00
|
|
|
<filters>
|
|
|
|
<filter>
|
|
|
|
<artifact>*:*</artifact>
|
|
|
|
<excludes>
|
|
|
|
<exclude>META-INF/MANIFEST.MF</exclude>
|
|
|
|
<exclude>**/module-info.class</exclude>
|
|
|
|
</excludes>
|
|
|
|
</filter>
|
|
|
|
</filters>
|
2018-02-16 12:08:29 +01:00
|
|
|
</configuration>
|
|
|
|
<executions>
|
|
|
|
<execution>
|
|
|
|
<phase>package</phase>
|
|
|
|
<goals>
|
|
|
|
<goal>shade</goal>
|
|
|
|
</goals>
|
|
|
|
</execution>
|
|
|
|
</executions>
|
|
|
|
</plugin>
|
|
|
|
</plugins>
|
|
|
|
</build>
|
|
|
|
|
2015-11-14 20:03:24 +01:00
|
|
|
<repositories>
|
2021-02-09 00:28:05 +01:00
|
|
|
<!-- PaperSpigot API and PaperLib -->
|
2015-11-14 20:03:24 +01:00
|
|
|
<repository>
|
2021-02-09 00:28:05 +01:00
|
|
|
<id>papermc</id>
|
|
|
|
<url>https://papermc.io/repo/repository/maven-public/</url>
|
2015-11-14 20:03:24 +01:00
|
|
|
</repository>
|
|
|
|
|
2019-05-06 17:39:21 +02:00
|
|
|
<!-- ProtocolLib -->
|
2015-11-14 20:03:24 +01:00
|
|
|
<repository>
|
|
|
|
<id>dmulloy2-repo</id>
|
2022-06-09 18:05:24 +02:00
|
|
|
<url>https://repo.dmulloy2.net/repository/public/</url>
|
2023-05-22 15:08:12 +02:00
|
|
|
<snapshots>
|
|
|
|
<enabled>false</enabled>
|
|
|
|
</snapshots>
|
2015-11-14 20:03:24 +01:00
|
|
|
</repository>
|
|
|
|
|
2019-05-06 17:39:21 +02:00
|
|
|
<!-- AuthMe Reloaded, xAuth and LoginSecurity -->
|
2015-11-14 20:03:24 +01:00
|
|
|
<repository>
|
2020-03-03 10:50:48 +01:00
|
|
|
<id>codemc-releases</id>
|
|
|
|
<url>https://repo.codemc.io/repository/maven-public/</url>
|
|
|
|
<snapshots>
|
|
|
|
<enabled>false</enabled>
|
|
|
|
</snapshots>
|
2016-02-07 14:39:59 +01:00
|
|
|
</repository>
|
2018-02-16 12:08:29 +01:00
|
|
|
|
2019-05-06 17:39:21 +02:00
|
|
|
<!-- PlaceholderAPI -->
|
2017-01-06 12:54:02 +01:00
|
|
|
<repository>
|
|
|
|
<id>placeholderapi</id>
|
2020-03-03 10:50:48 +01:00
|
|
|
<url>https://repo.extendedclip.com/content/repositories/placeholderapi</url>
|
|
|
|
<snapshots>
|
|
|
|
<enabled>false</enabled>
|
|
|
|
</snapshots>
|
2017-01-06 12:54:02 +01:00
|
|
|
</repository>
|
2022-02-22 10:43:04 +01:00
|
|
|
|
|
|
|
<!-- GitHub automatic maven builds -->
|
|
|
|
<repository>
|
|
|
|
<id>jitpack.io</id>
|
|
|
|
<url>https://jitpack.io</url>
|
|
|
|
<snapshots>
|
|
|
|
<enabled>false</enabled>
|
|
|
|
</snapshots>
|
|
|
|
</repository>
|
2015-11-14 20:03:24 +01:00
|
|
|
</repositories>
|
|
|
|
|
|
|
|
<dependencies>
|
2018-03-31 10:34:26 +02:00
|
|
|
<!--Common plugin component-->
|
2016-05-26 11:03:56 +02:00
|
|
|
<dependency>
|
|
|
|
<groupId>com.github.games647</groupId>
|
|
|
|
<artifactId>fastlogin.core</artifactId>
|
2018-03-13 14:21:24 +01:00
|
|
|
<version>${project.version}</version>
|
2016-05-26 11:03:56 +02:00
|
|
|
</dependency>
|
|
|
|
|
2023-05-22 15:08:12 +02:00
|
|
|
<dependency>
|
|
|
|
<groupId>org.yaml</groupId>
|
|
|
|
<artifactId>snakeyaml</artifactId>
|
|
|
|
<version>1.33</version>
|
|
|
|
<scope>compile</scope>
|
|
|
|
</dependency>
|
|
|
|
|
2022-01-14 12:12:05 +01:00
|
|
|
<!-- PaperSpigot API for correcting user cache usage -->
|
2015-11-14 20:03:24 +01:00
|
|
|
<dependency>
|
2021-12-02 14:15:57 +01:00
|
|
|
<groupId>io.papermc.paper</groupId>
|
2021-02-09 00:28:05 +01:00
|
|
|
<artifactId>paper-api</artifactId>
|
2022-07-02 12:38:14 +02:00
|
|
|
<version>1.19-R0.1-SNAPSHOT</version>
|
2015-11-14 20:03:24 +01:00
|
|
|
<scope>provided</scope>
|
2021-12-03 09:40:04 +01:00
|
|
|
<!-- Use our own newer api version -->
|
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
|
|
|
<groupId>org.slf4j</groupId>
|
|
|
|
<artifactId>slf4j-api</artifactId>
|
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
2015-11-14 20:03:24 +01:00
|
|
|
</dependency>
|
|
|
|
|
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-18 15:49:45 +02:00
|
|
|
<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>
|
|
|
|
|
2015-11-14 20:03:24 +01:00
|
|
|
<!--Library for listening and sending Minecraft packets-->
|
|
|
|
<dependency>
|
|
|
|
<groupId>com.comphenix.protocol</groupId>
|
|
|
|
<artifactId>ProtocolLib</artifactId>
|
2023-08-31 13:19:17 +02:00
|
|
|
<version>5.1.0</version>
|
2018-02-05 10:26:40 +01:00
|
|
|
<scope>provided</scope>
|
2021-06-17 15:02:24 +02:00
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
|
|
|
<groupId>*</groupId>
|
|
|
|
<artifactId>*</artifactId>
|
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
2015-11-14 20:03:24 +01:00
|
|
|
</dependency>
|
|
|
|
|
2018-03-31 10:34:26 +02:00
|
|
|
<!--Changing onlinemode on login process-->
|
2017-01-06 12:54:02 +01:00
|
|
|
<dependency>
|
|
|
|
<groupId>com.github.ProtocolSupport</groupId>
|
|
|
|
<artifactId>ProtocolSupport</artifactId>
|
2019-11-09 13:57:12 +01:00
|
|
|
<!--4.29.dev after commit about API improvements-->
|
2022-07-02 12:38:14 +02:00
|
|
|
<version>master-66b494a8dd-1</version>
|
2018-02-05 10:26:40 +01:00
|
|
|
<scope>provided</scope>
|
2021-06-17 15:02:24 +02:00
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
|
|
|
<groupId>*</groupId>
|
|
|
|
<artifactId>*</artifactId>
|
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
2017-01-06 12:54:02 +01:00
|
|
|
</dependency>
|
|
|
|
|
2021-12-02 14:39:08 +01:00
|
|
|
<!--Floodgate for Xbox Live Authentication-->
|
|
|
|
<dependency>
|
|
|
|
<groupId>org.geysermc.floodgate</groupId>
|
|
|
|
<artifactId>api</artifactId>
|
2023-03-24 17:03:37 +01:00
|
|
|
<version>${floodgate.version}</version>
|
2021-12-02 14:39:08 +01:00
|
|
|
<scope>provided</scope>
|
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
2022-12-18 14:03:49 +01:00
|
|
|
<groupId>*</groupId>
|
2021-12-02 14:39:08 +01:00
|
|
|
<artifactId>*</artifactId>
|
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
|
|
|
</dependency>
|
|
|
|
|
|
|
|
<!-- Bedrock player bridge -->
|
|
|
|
<dependency>
|
2022-09-20 06:48:15 +02:00
|
|
|
<groupId>org.geysermc.geyser</groupId>
|
2021-12-06 18:23:11 +01:00
|
|
|
<artifactId>core</artifactId>
|
2021-12-02 14:39:08 +01:00
|
|
|
<version>${geyser.version}</version>
|
|
|
|
<scope>provided</scope>
|
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
|
|
|
<groupId>*</groupId>
|
|
|
|
<artifactId>*</artifactId>
|
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
|
|
|
</dependency>
|
|
|
|
|
2021-12-06 18:23:11 +01:00
|
|
|
<!-- We need the API, but it was excluded above -->
|
|
|
|
<dependency>
|
2022-09-20 06:48:15 +02:00
|
|
|
<groupId>org.geysermc.geyser</groupId>
|
|
|
|
<artifactId>api</artifactId>
|
2021-12-06 18:23:11 +01:00
|
|
|
<version>${geyser.version}</version>
|
|
|
|
<scope>provided</scope>
|
2022-12-18 14:03:49 +01:00
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
|
|
|
<groupId>*</groupId>
|
|
|
|
<artifactId>*</artifactId>
|
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
2021-12-06 18:23:11 +01:00
|
|
|
</dependency>
|
|
|
|
|
2018-03-31 10:34:26 +02:00
|
|
|
<!--Provide premium placeholders-->
|
2017-01-06 12:54:02 +01:00
|
|
|
<dependency>
|
|
|
|
<groupId>me.clip</groupId>
|
|
|
|
<artifactId>placeholderapi</artifactId>
|
2023-12-04 07:49:44 +00:00
|
|
|
<version>2.11.5</version>
|
2017-01-06 12:54:02 +01:00
|
|
|
<scope>provided</scope>
|
|
|
|
<optional>true</optional>
|
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
|
|
|
<groupId>*</groupId>
|
|
|
|
<artifactId>*</artifactId>
|
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
|
|
|
</dependency>
|
2016-01-24 11:50:49 +01:00
|
|
|
|
2015-11-14 20:03:24 +01:00
|
|
|
<!--Login Plugins-->
|
|
|
|
<dependency>
|
|
|
|
<groupId>fr.xephi</groupId>
|
|
|
|
<artifactId>authme</artifactId>
|
2022-07-02 12:38:14 +02:00
|
|
|
<version>5.6.0-beta2</version>
|
2018-02-05 10:26:40 +01:00
|
|
|
<scope>provided</scope>
|
2015-11-14 20:03:24 +01:00
|
|
|
<optional>true</optional>
|
2016-03-04 13:34:02 +01:00
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
|
|
|
<groupId>*</groupId>
|
|
|
|
<artifactId>*</artifactId>
|
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
2015-11-14 20:03:24 +01:00
|
|
|
</dependency>
|
|
|
|
|
2016-06-18 14:39:47 +02:00
|
|
|
<dependency>
|
2016-06-29 19:03:52 +02:00
|
|
|
<groupId>com.lenis0012.bukkit</groupId>
|
|
|
|
<artifactId>loginsecurity</artifactId>
|
2024-01-15 07:40:02 +00:00
|
|
|
<version>3.3.0</version>
|
2018-02-05 10:26:40 +01:00
|
|
|
<scope>provided</scope>
|
2016-09-11 21:26:03 +02:00
|
|
|
<optional>true</optional>
|
2016-06-18 14:39:47 +02:00
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
|
|
|
<groupId>*</groupId>
|
|
|
|
<artifactId>*</artifactId>
|
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
|
|
|
</dependency>
|
|
|
|
|
2017-01-06 12:54:02 +01:00
|
|
|
<dependency>
|
|
|
|
<groupId>com.github.games647</groupId>
|
|
|
|
<artifactId>LogIt</artifactId>
|
|
|
|
<version>9e3581db27</version>
|
2016-09-08 11:48:13 +02:00
|
|
|
<scope>provided</scope>
|
2018-02-05 10:26:40 +01:00
|
|
|
<optional>true</optional>
|
2016-05-22 13:59:41 +02:00
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
|
|
|
<groupId>*</groupId>
|
2016-03-04 13:34:02 +01:00
|
|
|
<artifactId>*</artifactId>
|
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
2017-01-06 12:54:02 +01:00
|
|
|
</dependency>
|
2016-02-07 14:39:59 +01:00
|
|
|
|
2015-11-14 20:03:24 +01:00
|
|
|
<dependency>
|
|
|
|
<groupId>de.luricos.bukkit</groupId>
|
|
|
|
<artifactId>xAuth</artifactId>
|
|
|
|
<version>2.6</version>
|
2018-02-05 10:26:40 +01:00
|
|
|
<scope>provided</scope>
|
2015-11-14 20:03:24 +01:00
|
|
|
<optional>true</optional>
|
|
|
|
<!--These artifacts produce conflicts on downloading-->
|
|
|
|
<exclusions>
|
|
|
|
<exclusion>
|
2016-03-04 13:34:02 +01:00
|
|
|
<groupId>*</groupId>
|
|
|
|
<artifactId>*</artifactId>
|
2015-11-14 20:03:24 +01:00
|
|
|
</exclusion>
|
|
|
|
</exclusions>
|
|
|
|
</dependency>
|
|
|
|
|
|
|
|
<!--No maven repository :(-->
|
|
|
|
<dependency>
|
|
|
|
<groupId>de.st_ddt.crazy</groupId>
|
|
|
|
<artifactId>CrazyCore</artifactId>
|
|
|
|
<version>10.7.7</version>
|
|
|
|
<optional>true</optional>
|
|
|
|
<scope>system</scope>
|
|
|
|
<systemPath>${project.basedir}/lib/CrazyCore v10.7.7.jar</systemPath>
|
|
|
|
</dependency>
|
|
|
|
|
|
|
|
<dependency>
|
|
|
|
<groupId>de.st_ddt.crazy</groupId>
|
|
|
|
<artifactId>CrazyLogin</artifactId>
|
|
|
|
<version>7.23</version>
|
|
|
|
<optional>true</optional>
|
|
|
|
<scope>system</scope>
|
|
|
|
<systemPath>${project.basedir}/lib/CrazyLogin v7.23.2.jar</systemPath>
|
|
|
|
</dependency>
|
2016-03-01 17:26:25 +01:00
|
|
|
|
|
|
|
<dependency>
|
|
|
|
<groupId>ultraauth</groupId>
|
|
|
|
<artifactId>ultraauth</artifactId>
|
|
|
|
<version>2.0.2</version>
|
|
|
|
<optional>true</optional>
|
|
|
|
<scope>system</scope>
|
2016-09-18 10:38:05 +02:00
|
|
|
<systemPath>${project.basedir}/lib/UltraAuth v2.1.2.jar</systemPath>
|
2016-03-01 17:26:25 +01:00
|
|
|
</dependency>
|
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-11 13:17:36 +02:00
|
|
|
|
|
|
|
<dependency>
|
|
|
|
<groupId>org.bouncycastle</groupId>
|
2022-07-08 16:29:25 +02:00
|
|
|
<artifactId>bcprov-jdk18on</artifactId>
|
2023-12-04 07:48:07 +00:00
|
|
|
<version>1.77</version>
|
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-11 13:17:36 +02:00
|
|
|
<scope>test</scope>
|
|
|
|
</dependency>
|
2022-07-25 13:02:30 +02:00
|
|
|
|
|
|
|
<dependency>
|
|
|
|
<groupId>io.netty</groupId>
|
|
|
|
<artifactId>netty-transport</artifactId>
|
2022-07-27 10:06:46 +02:00
|
|
|
<version>${nettyVersion}</version>
|
2022-08-04 19:55:04 +02:00
|
|
|
<scope>provided</scope>
|
|
|
|
</dependency>
|
2022-08-12 09:09:13 +02:00
|
|
|
|
2022-07-25 13:02:30 +02:00
|
|
|
<dependency>
|
|
|
|
<groupId>io.netty</groupId>
|
|
|
|
<artifactId>netty-codec</artifactId>
|
2022-07-27 10:06:46 +02:00
|
|
|
<version>${nettyVersion}</version>
|
|
|
|
<scope>test</scope>
|
|
|
|
</dependency>
|
|
|
|
|
|
|
|
<!-- Provided by the spigot, required for testing ProtocolLib -->
|
|
|
|
<dependency>
|
|
|
|
<groupId>commons-lang</groupId>
|
|
|
|
<artifactId>commons-lang</artifactId>
|
|
|
|
<version>2.6</version>
|
2022-07-25 13:02:30 +02:00
|
|
|
<scope>test</scope>
|
|
|
|
</dependency>
|
2015-11-14 20:03:24 +01:00
|
|
|
</dependencies>
|
|
|
|
</project>
|