mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-07-29 18:27:36 +02:00
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 + } +}
This commit is contained in:
@ -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>
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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");
|
||||
|
||||
|
@ -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()) {
|
||||
|
@ -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, session, sharedSecret, keyPair);
|
||||
plugin.getScheduler().runAsync(verifyTask);
|
||||
} else {
|
||||
sender.kickPlayer("Invalid signature");
|
||||
}
|
||||
} catch (NoSuchAlgorithmException | InvalidKeyException | SignatureException e) {
|
||||
sender.kickPlayer("Invalid signature");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
53
bukkit/src/test/java/integration/README.md
Normal file
53
bukkit/src/test/java/integration/README.md
Normal file
@ -0,0 +1,53 @@
|
||||
# Integration tests for authentication
|
||||
|
||||
## Description
|
||||
|
||||
Projects require integration tests in order to check against errors that could only occur if connected to other
|
||||
components. However, they are heavier in terms of performance and require a more complex setup. Unit tests often make
|
||||
use of fake, mock, stubs, etc. implementations to test the unit in isolation and thus could hide issues across
|
||||
boundaries of a unit. Nevertheless, both are not replacement for each other.
|
||||
|
||||
## Usage in this project
|
||||
|
||||
The authentication system is a core component, so it requires some kind of testing. Here we are going to
|
||||
spin up a Spigot server and test with the supported authentication schemes against the implementation of MCProtocolLib.
|
||||
|
||||
### Goals
|
||||
|
||||
* OS platform independent
|
||||
* Reproducible, but not fixed to a specific image hash
|
||||
* This is a dev container, so fixing it to feature/major version is enough instead of a version fixed by hash
|
||||
* Improve container spin up
|
||||
* E.g. Remove/Reduce world generation
|
||||
|
||||
### Note on automation
|
||||
|
||||
The simplest solution it to use the official Mojang session and authentication servers. However, this would require
|
||||
a spare Minecraft account. Mocking the auth servers would be a solution to avoid this.
|
||||
|
||||
## Related
|
||||
|
||||
Interest blog article about integration tests and why they are necessary.
|
||||
https://software.rajivprab.com/2019/04/28/rethinking-software-testing-perspectives-from-the-world-of-hardware/
|
||||
|
||||
## Issues
|
||||
|
||||
### Slow startup
|
||||
|
||||
Tried a lot of optimizations like only loading a single world without the nether or the end. However, there the startup
|
||||
is still slow. If you have any ideas on how to tune the startup parameters of the Minecraft server or the JVM
|
||||
itself to reduce the startup time, please suggest it.
|
||||
|
||||
### Checkpoint
|
||||
|
||||
An idea to optimize the time is to use CRIU (checkpoint and restore). So to save the process at a certain stage and
|
||||
restore all data multiple times. This could cause a lot of issues like open files have to be present. However, the
|
||||
impact is significant and since it runs inside the container all files, pids (pid=1) should be matching. Potential
|
||||
checkpoint locations are:
|
||||
|
||||
* Direct before loading the plugins
|
||||
* Likely before binding the port to prevent issues
|
||||
* After loading the libraries
|
||||
|
||||
Nevertheless, the current state requires to run it with root and the Java support is currently still in progress.
|
||||
|
27
bukkit/src/test/resources/client_keys/README.md
Normal file
27
bukkit/src/test/resources/client_keys/README.md
Normal file
@ -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
|
@ -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="
|
||||
}
|
||||
|
@ -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="
|
||||
}
|
||||
|
@ -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="
|
||||
}
|
||||
|
@ -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="
|
||||
}
|
@ -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="
|
||||
}
|
16
bukkit/src/test/resources/signature/README.md
Normal file
16
bukkit/src/test/resources/signature/README.md
Normal file
@ -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`
|
7
bukkit/src/test/resources/signature/incorrect_nonce.json
Normal file
7
bukkit/src/test/resources/signature/incorrect_nonce.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"nonce": "galNig\u003d\u003d",
|
||||
"signature": {
|
||||
"signature": "JlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d",
|
||||
"salt": -2985008842905108412
|
||||
}
|
||||
}
|
7
bukkit/src/test/resources/signature/incorrect_salt.json
Normal file
7
bukkit/src/test/resources/signature/incorrect_salt.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"nonce": "GalNig\u003d\u003d",
|
||||
"signature": {
|
||||
"signature": "JlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d",
|
||||
"salt": -1985008842905108412
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
{
|
||||
"nonce": "GalNig\u003d\u003d",
|
||||
"signature": {
|
||||
"signature": "jlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d",
|
||||
"salt": -2985008842905108412
|
||||
}
|
||||
}
|
7
bukkit/src/test/resources/signature/valid_signature.json
Normal file
7
bukkit/src/test/resources/signature/valid_signature.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"nonce": "GalNig\u003d\u003d",
|
||||
"signature": {
|
||||
"signature": "JlXAUtIGDjxUOnF5vkg/NUEN2wlzXcqADyYIw2WRTb5hgKwIgxyUPO5v/2M7xU3hxz2Zf0iYHM97h8qNMGQ43cLgfVH9VWZ1kGMuOby2LNSb6nDaMzm0b02ftThaWOWj9kJXbR8fN7qdpB+28t2CTW5ILT+2AZYI/Sn8gFFR+LvJJt1ENMfEj2ZIIkHecpNBuKyLz1aDCZ5BEASSLfAqHEAA3dpHV1DIgzfpO6xwo7bVFDtcBEeusl/Nc3KyGyT8sDFTsZWgitgz53xNKrZUK8Q2BaJfP0zrGAX36rpYURJSVD0AtI1ic9s5aG+OFUC1YhLXb/1cDv37ZjHcdV2ppw\u003d\u003d",
|
||||
"salt": -2985008842905108412
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user