Files
FastLogin/bukkit/pom.xml

389 lines
14 KiB
XML
Raw Normal View History

<!--
SPDX-License-Identifier: MIT
The MIT License (MIT)
2024-05-06 16:40:24 +02:00
Copyright (c) 2015-2024 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.
-->
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"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<properties>
2024-05-03 10:43:57 +02:00
<maven.compiler.release>8</maven.compiler.release>
</properties>
<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>
<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-->
<artifactId>fastlogin.bukkit</artifactId>
<packaging>jar</packaging>
<name>FastLoginBukkit</name>
<build>
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
Bump the production-dependencies group across 1 directory with 6 updates Bumps the production-dependencies group with 6 updates in the / directory: | Package | From | To | | --- | --- | --- | | [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) | `10.12.7` | `10.16.0` | | [org.projectlombok:lombok](https://github.com/projectlombok/lombok) | `1.18.30` | `1.18.32` | | [org.apache.maven.plugins:maven-jar-plugin](https://github.com/apache/maven-jar-plugin) | `3.3.0` | `3.4.1` | | org.slf4j:slf4j-jdk14 | `2.0.11` | `2.0.13` | | [org.apache.maven.plugins:maven-shade-plugin](https://github.com/apache/maven-shade-plugin) | `3.5.1` | `3.5.3` | | [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) | `3.3.2` | `3.3.3` | Updates `com.puppycrawl.tools:checkstyle` from 10.12.7 to 10.16.0 - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-10.12.7...checkstyle-10.16.0) Updates `org.projectlombok:lombok` from 1.18.30 to 1.18.32 - [Changelog](https://github.com/projectlombok/lombok/blob/master/doc/changelog.markdown) - [Commits](https://github.com/projectlombok/lombok/compare/v1.18.30...v1.18.32) Updates `org.apache.maven.plugins:maven-jar-plugin` from 3.3.0 to 3.4.1 - [Release notes](https://github.com/apache/maven-jar-plugin/releases) - [Commits](https://github.com/apache/maven-jar-plugin/compare/maven-jar-plugin-3.3.0...maven-jar-plugin-3.4.1) Updates `org.slf4j:slf4j-jdk14` from 2.0.11 to 2.0.13 Updates `org.apache.maven.plugins:maven-shade-plugin` from 3.5.1 to 3.5.3 - [Release notes](https://github.com/apache/maven-shade-plugin/releases) - [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.5.1...maven-shade-plugin-3.5.3) Updates `org.mariadb.jdbc:mariadb-java-client` from 3.3.2 to 3.3.3 - [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases) - [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md) - [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.3.2...3.3.3) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: org.projectlombok:lombok dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: org.apache.maven.plugins:maven-jar-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: org.slf4j:slf4j-jdk14 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: org.apache.maven.plugins:maven-shade-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: org.mariadb.jdbc:mariadb-java-client dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] <support@github.com>
2024-04-30 13:59:35 +00:00
<version>3.5.3</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
<relocations>
<relocation>
<pattern>org.yaml.snakeyaml</pattern>
<shadedPattern>fastlogin.yaml</shadedPattern>
</relocation>
<relocation>
<pattern>com.zaxxer.hikari</pattern>
<shadedPattern>fastlogin.hikari</shadedPattern>
</relocation>
<relocation>
<pattern>org.slf4j</pattern>
<shadedPattern>fastlogin.slf4j</shadedPattern>
</relocation>
<relocation>
<pattern>net.md_5.bungee.config</pattern>
<shadedPattern>fastlogin.config</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.gson</pattern>
<shadedPattern>fastlogin.gson</shadedPattern>
</relocation>
<relocation>
<pattern>com.google.common</pattern>
<shadedPattern>fastlogin.guava</shadedPattern>
<excludes>
<exclude>com.google.common.collect.Multimap</exclude>
</excludes>
</relocation>
<relocation>
<pattern>io.papermc.lib</pattern>
<shadedPattern>fastlogin.paperlib</shadedPattern>
</relocation>
</relocations>
<!-- 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"/>
</transformers>
<minimizeJar>true</minimizeJar>
2022-03-25 08:48:20 +01:00
<filters>
<filter>
<artifact>*:*</artifact>
<excludes>
<exclude>**/module-info.class</exclude>
</excludes>
</filter>
</filters>
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<!-- PaperSpigot API and PaperLib -->
<repository>
<id>papermc</id>
<url>https://papermc.io/repo/repository/maven-public/</url>
2024-05-10 10:09:24 +02:00
<releases>
<enabled>false</enabled>
</releases>
</repository>
2024-05-06 11:17:16 +02:00
<repository>
<id>sonatype-snapshots</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots/</url>
<releases>
<enabled>false</enabled>
</releases>
</repository>
2019-05-06 17:39:21 +02:00
<!-- ProtocolLib -->
<repository>
<id>dmulloy2-repo</id>
2022-06-09 18:05:24 +02:00
<url>https://repo.dmulloy2.net/repository/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
2019-05-06 17:39:21 +02:00
<!-- AuthMe Reloaded, xAuth and LoginSecurity -->
<repository>
<id>codemc-releases</id>
<url>https://repo.codemc.io/repository/maven-public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
2019-05-06 17:39:21 +02:00
<!-- PlaceholderAPI -->
2017-01-06 12:54:02 +01:00
<repository>
<id>placeholderapi</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
2017-01-06 12:54:02 +01:00
</repository>
<!-- GitHub automatic maven builds -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<!--Common plugin component-->
2016-05-26 11:03:56 +02:00
<dependency>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin.core</artifactId>
<version>${project.version}</version>
2016-05-26 11:03:56 +02:00
</dependency>
2022-01-14 12:12:05 +01:00
<!-- PaperSpigot API for correcting user cache usage -->
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
2024-05-06 11:17:16 +02:00
<version>1.20.6-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<!-- Use our own newer api version -->
<exclusions>
<exclusion>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</exclusion>
</exclusions>
</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>
<!--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>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Changing onlinemode on login process-->
2017-01-06 12:54:02 +01:00
<dependency>
<groupId>com.github.ProtocolSupport</groupId>
<artifactId>ProtocolSupport</artifactId>
<!--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>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
2017-01-06 12:54:02 +01:00
</dependency>
<!--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>
<scope>provided</scope>
<exclusions>
<exclusion>
2022-12-18 14:03:49 +01:00
<groupId>*</groupId>
<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>
<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>
<!--Provide premium placeholders-->
2017-01-06 12:54:02 +01:00
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
Bump the production-dependencies group with 6 updates Bumps the production-dependencies group with 6 updates: | Package | From | To | | --- | --- | --- | | [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) | `10.12.4` | `10.12.5` | | [org.apache.maven.plugins:maven-checkstyle-plugin](https://github.com/apache/maven-checkstyle-plugin) | `3.3.0` | `3.3.1` | | [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) | `3.1.2` | `3.2.2` | | me.clip:placeholderapi | `2.11.4` | `2.11.5` | | [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) | `3.2.0` | `3.3.1` | | [org.codehaus.mojo:templating-maven-plugin](https://github.com/mojohaus/templating-maven-plugin) | `1.0.0` | `3.0.0` | Updates `com.puppycrawl.tools:checkstyle` from 10.12.4 to 10.12.5 - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-10.12.4...checkstyle-10.12.5) Updates `org.apache.maven.plugins:maven-checkstyle-plugin` from 3.3.0 to 3.3.1 - [Commits](https://github.com/apache/maven-checkstyle-plugin/compare/maven-checkstyle-plugin-3.3.0...maven-checkstyle-plugin-3.3.1) Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.1.2 to 3.2.2 - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.1.2...surefire-3.2.2) Updates `me.clip:placeholderapi` from 2.11.4 to 2.11.5 Updates `org.mariadb.jdbc:mariadb-java-client` from 3.2.0 to 3.3.1 - [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases) - [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md) - [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.2.0...3.3.1) Updates `org.codehaus.mojo:templating-maven-plugin` from 1.0.0 to 3.0.0 - [Release notes](https://github.com/mojohaus/templating-maven-plugin/releases) - [Commits](https://github.com/mojohaus/templating-maven-plugin/compare/templating-maven-plugin-1.0.0...3.0.0) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: org.apache.maven.plugins:maven-checkstyle-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: me.clip:placeholderapi dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: org.mariadb.jdbc:mariadb-java-client dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: org.codehaus.mojo:templating-maven-plugin dependency-type: direct:production update-type: version-update:semver-major dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] <support@github.com>
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
<!--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>
<optional>true</optional>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
2016-06-29 19:03:52 +02:00
<groupId>com.lenis0012.bukkit</groupId>
<artifactId>loginsecurity</artifactId>
Bump the production-dependencies group with 5 updates Bumps the production-dependencies group with 5 updates: | Package | From | To | | --- | --- | --- | | [com.puppycrawl.tools:checkstyle](https://github.com/checkstyle/checkstyle) | `10.12.5` | `10.12.7` | | [org.apache.maven.plugins:maven-surefire-plugin](https://github.com/apache/maven-surefire) | `3.2.2` | `3.2.5` | | org.slf4j:slf4j-jdk14 | `2.0.9` | `2.0.11` | | com.lenis0012.bukkit:loginsecurity | `3.2.1` | `3.3.0` | | [org.mariadb.jdbc:mariadb-java-client](https://github.com/mariadb-corporation/mariadb-connector-j) | `3.3.1` | `3.3.2` | Updates `com.puppycrawl.tools:checkstyle` from 10.12.5 to 10.12.7 - [Release notes](https://github.com/checkstyle/checkstyle/releases) - [Commits](https://github.com/checkstyle/checkstyle/compare/checkstyle-10.12.5...checkstyle-10.12.7) Updates `org.apache.maven.plugins:maven-surefire-plugin` from 3.2.2 to 3.2.5 - [Release notes](https://github.com/apache/maven-surefire/releases) - [Commits](https://github.com/apache/maven-surefire/compare/surefire-3.2.2...surefire-3.2.5) Updates `org.slf4j:slf4j-jdk14` from 2.0.9 to 2.0.11 Updates `com.lenis0012.bukkit:loginsecurity` from 3.2.1 to 3.3.0 Updates `org.mariadb.jdbc:mariadb-java-client` from 3.3.1 to 3.3.2 - [Release notes](https://github.com/mariadb-corporation/mariadb-connector-j/releases) - [Changelog](https://github.com/mariadb-corporation/mariadb-connector-j/blob/master/CHANGELOG.md) - [Commits](https://github.com/mariadb-corporation/mariadb-connector-j/compare/3.3.1...3.3.2) --- updated-dependencies: - dependency-name: com.puppycrawl.tools:checkstyle dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: org.apache.maven.plugins:maven-surefire-plugin dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: org.slf4j:slf4j-jdk14 dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies - dependency-name: com.lenis0012.bukkit:loginsecurity dependency-type: direct:production update-type: version-update:semver-minor dependency-group: production-dependencies - dependency-name: org.mariadb.jdbc:mariadb-java-client dependency-type: direct:production update-type: version-update:semver-patch dependency-group: production-dependencies ... Signed-off-by: dependabot[bot] <support@github.com>
2024-01-15 07:40:02 +00:00
<version>3.3.0</version>
2018-02-05 10:26:40 +01:00
<scope>provided</scope>
<optional>true</optional>
<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>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
2017-01-06 12:54:02 +01:00
</dependency>
<dependency>
<groupId>de.luricos.bukkit</groupId>
<artifactId>xAuth</artifactId>
<version>2.6</version>
2018-02-05 10:26:40 +01:00
<scope>provided</scope>
<optional>true</optional>
<!--These artifacts produce conflicts on downloading-->
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</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>
<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>
<artifactId>bcprov-jdk18on</artifactId>
Bump the development-dependencies group with 3 updates Bumps the development-dependencies group with 3 updates: [org.junit.jupiter:junit-jupiter](https://github.com/junit-team/junit5), [org.mockito:mockito-core](https://github.com/mockito/mockito) and [org.bouncycastle:bcprov-jdk18on](https://github.com/bcgit/bc-java). Updates `org.junit.jupiter:junit-jupiter` from 5.10.1 to 5.10.2 - [Release notes](https://github.com/junit-team/junit5/releases) - [Commits](https://github.com/junit-team/junit5/compare/r5.10.1...r5.10.2) Updates `org.mockito:mockito-core` from 5.9.0 to 5.11.0 - [Release notes](https://github.com/mockito/mockito/releases) - [Commits](https://github.com/mockito/mockito/compare/v5.9.0...v5.11.0) Updates `org.bouncycastle:bcprov-jdk18on` from 1.77 to 1.78.1 - [Changelog](https://github.com/bcgit/bc-java/blob/main/docs/releasenotes.html) - [Commits](https://github.com/bcgit/bc-java/commits) --- updated-dependencies: - dependency-name: org.junit.jupiter:junit-jupiter dependency-type: direct:development update-type: version-update:semver-patch dependency-group: development-dependencies - dependency-name: org.mockito:mockito-core dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies - dependency-name: org.bouncycastle:bcprov-jdk18on dependency-type: direct:development update-type: version-update:semver-minor dependency-group: development-dependencies ... Signed-off-by: dependabot[bot] <support@github.com>
2024-04-22 07:11:16 +00:00
<version>1.78.1</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>
</dependencies>
</project>