mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-07-29 18:27:36 +02:00
Verify public keys from players
diff --git a/bukkit/pom.xml b/bukkit/pom.xml index 2e1ccc4..c4caae8 100644 --- a/bukkit/pom.xml +++ b/bukkit/pom.xml @@ -119,9 +119,6 @@ <repository> <id>dmulloy2-repo</id> <url>https://repo.dmulloy2.net/repository/public/</url> - <snapshots> - <enabled>false</enabled> - </snapshots> </repository> <!-- AuthMe Reloaded, xAuth and LoginSecurity --> @@ -186,7 +183,7 @@ <dependency> <groupId>com.comphenix.protocol</groupId> <artifactId>ProtocolLib</artifactId> - <version>5.0.0-SNAPSHOT</version> + <version>5.0.0-20220603.031413-2</version> <scope>provided</scope> <exclusions> <exclusion> @@ -351,5 +348,12 @@ <scope>system</scope> <systemPath>${project.basedir}/lib/UltraAuth v2.1.2.jar</systemPath> </dependency> + + <dependency> + <groupId>org.bouncycastle</groupId> + <artifactId>bcpkix-jdk18on</artifactId> + <version>1.71</version> + <scope>test</scope> + </dependency> </dependencies> </project> diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtil.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtil.java index 1197514..143dda9 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtil.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtil.java @@ -25,15 +25,28 @@ */ package com.github.games647.fastlogin.bukkit.listener.protocollib; +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; +import com.google.common.io.Resources; + +import java.io.IOException; import java.math.BigInteger; import java.nio.charset.StandardCharsets; import java.security.GeneralSecurityException; +import java.security.InvalidKeyException; +import java.security.KeyFactory; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; +import java.security.Signature; +import java.security.SignatureException; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; +import java.util.Base64; +import java.util.Base64.Encoder; import java.util.Random; import javax.crypto.Cipher; @@ -46,11 +59,25 @@ import javax.crypto.spec.SecretKeySpec; * * @see net.minecraft.server.MinecraftEncryption */ -public class EncryptionUtil { +class EncryptionUtil { public static final int VERIFY_TOKEN_LENGTH = 4; public static final String KEY_PAIR_ALGORITHM = "RSA"; + private static final int RSA_LENGTH = 1_024; + + private static final PublicKey mojangSessionKey; + private static final int LINE_LENGTH = 76; + private static final Encoder KEY_ENCODER = Base64.getMimeEncoder(LINE_LENGTH, "\n".getBytes(StandardCharsets.UTF_8)); + + static { + try { + mojangSessionKey = loadMojangSessionKey(); + } catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException ex) { + throw new RuntimeException("Failed to load Mojang session key", ex); + } + } + private EncryptionUtil() { // utility } @@ -65,7 +92,7 @@ public class EncryptionUtil { try { KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM); - keyPairGenerator.initialize(1_024); + keyPairGenerator.initialize(RSA_LENGTH); return keyPairGenerator.generateKeyPair(); } catch (NoSuchAlgorithmException nosuchalgorithmexception) { // Should be existing in every vm @@ -120,6 +147,33 @@ public class EncryptionUtil { return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES"); } + public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimstamp) + throws SignatureException, NoSuchAlgorithmException, InvalidKeyException { + if (!verifyTimstamp.isBefore(clientKey.getExpiry())) { + return false; + } + + Signature signature = Signature.getInstance("SHA1withRSA"); + signature.initVerify(mojangSessionKey); + signature.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII)); + return signature.verify(clientKey.getSignature()); + } + + private static PublicKey loadMojangSessionKey() + throws IOException, NoSuchAlgorithmException, InvalidKeySpecException { + var keyUrl = Resources.getResource("yggdrasil_session_pubkey.der"); + var keyData = Resources.toByteArray(keyUrl); + var keySpec = new X509EncodedKeySpec(keyData); + + return KeyFactory.getInstance("RSA").generatePublic(keySpec); + } + + private static String toSignable(ClientPublicKey clientPublicKey) { + long expiry = clientPublicKey.getExpiry().toEpochMilli(); + String encoded = KEY_ENCODER.encodeToString(clientPublicKey.getKey()); + return expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n"; + } + public static byte[] decrypt(PrivateKey key, byte[] data) throws GeneralSecurityException { // b(Key var0, byte[] var1) Cipher cipher = Cipher.getInstance(key.getAlgorithm()); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java index f8b8de2..c9e72fc 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/ProtocolLibListener.java @@ -27,18 +27,27 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib; import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.events.InternalStructure; import com.comphenix.protocol.events.PacketAdapter; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; -import com.github.games647.fastlogin.bukkit.BukkitLoginSession; +import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.wrappers.WrappedGameProfile; +import com.github.games647.fastlogin.bukkit.BukkitLoginSession; import com.github.games647.fastlogin.bukkit.FastLoginBukkit; +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; import com.github.games647.fastlogin.core.antibot.AntiBotService; import com.github.games647.fastlogin.core.antibot.AntiBotService.Action; import java.net.InetSocketAddress; +import java.security.InvalidKeyException; import java.security.KeyPair; +import java.security.NoSuchAlgorithmException; +import java.security.PublicKey; import java.security.SecureRandom; +import java.security.SignatureException; +import java.time.Instant; +import java.util.Optional; import org.bukkit.entity.Player; @@ -149,6 +158,11 @@ public class ProtocolLibListener extends PacketAdapter { username = (String) packetEvent.getPacket().getMeta("original_name").get(); } + if (!verifyPublicKey(packet)) { + plugin.getLog().warn("Invalid public key from player {}", username); + return; + } + plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username); packetEvent.getAsyncMarker().incrementProcessingDelay(); @@ -156,6 +170,24 @@ public class ProtocolLibListener extends PacketAdapter { plugin.getScheduler().runAsync(nameCheckTask); } + private boolean verifyPublicKey(PacketContainer packet) { + Optional<InternalStructure> internalStructure = packet.getOptionalStructures().readSafely(0); + if (internalStructure == null) { + return true; + } + + Object instance = internalStructure.get().getHandle(); + Instant expires = FuzzyReflection.getFieldValue(instance, Instant.class, true); + PublicKey key = FuzzyReflection.getFieldValue(instance, PublicKey.class, true); + byte[] signature = FuzzyReflection.getFieldValue(instance, byte[].class, true); + ClientPublicKey clientKey = new ClientPublicKey(expires, key.getEncoded(), signature); + try { + return EncryptionUtil.verifyClientKey(clientKey, Instant.now()); + } catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) { + return false; + } + } + private String getUsername(PacketContainer packet) { WrappedGameProfile profile = packet.getGameProfiles().readSafely(0); if (profile == null) { diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/packet/ClientPublicKey.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/packet/ClientPublicKey.java new file mode 100644 index 0000000..495adb1 --- /dev/null +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/protocollib/packet/ClientPublicKey.java @@ -0,0 +1,53 @@ +/* + * SPDX-License-Identifier: MIT + * + * The MIT License (MIT) + * + * Copyright (c) 2015-2022 games647 and contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package com.github.games647.fastlogin.bukkit.listener.protocollib.packet; + +import java.time.Instant; + +public class ClientPublicKey { + + private final Instant expiry; + private final byte[] key; + private final byte[] signature; + + public ClientPublicKey(Instant expiry, byte[] key, byte[] signature) { + this.expiry = expiry; + this.key = key; + this.signature = signature; + } + + public Instant getExpiry() { + return expiry; + } + + public byte[] getKey() { + return key; + } + + public byte[] getSignature() { + return signature; + } +} diff --git a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtilTest.java b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtilTest.java index 7a097f1..11a52f7 100644 --- a/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtilTest.java +++ b/bukkit/src/test/java/com/github/games647/fastlogin/bukkit/listener/protocollib/EncryptionUtilTest.java @@ -25,8 +25,27 @@ */ package com.github.games647.fastlogin.bukkit.listener.protocollib; +import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey; +import com.google.common.io.Resources; +import com.google.gson.Gson; +import com.google.gson.JsonObject; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; +import java.time.temporal.ChronoUnit; +import java.util.Base64; +import org.bouncycastle.util.io.pem.PemObject; +import org.bouncycastle.util.io.pem.PemReader; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; @@ -43,4 +62,77 @@ public class EncryptionUtilTest { assertThat(token, notNullValue()); assertThat(token.length, is(4)); } + + @Test + public void testExpiredClientKey() throws Exception { + var clientKey = loadClientKey("client_keys/valid.json"); + + // Client expires at the exact second mentioned, so use it for verification + var expiredTimestamp = clientKey.getExpiry(); + assertThat(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp), is(false)); + } + + // @Test(expected = Exception.class) + @Test + public void testInvalidChangedExpiration() throws Exception { + // expiration date changed should make the signature invalid + // expiration should still be valid + var clientKey = loadClientKey("client_keys/invalid_wrong_expiration.json"); + assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + } + + // @Test(expected = Exception.class) + @Test + public void testInvalidChangedKey() throws Exception { + // changed public key no longer corresponding to the signature + var clientKey = loadClientKey("client_keys/invalid_wrong_key.json"); + assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + } + + @Test + public void testInvalidChangedSignature() throws Exception { + // signature modified no longer corresponding to key and expiration date + var clientKey = loadClientKey("client_keys/invalid_wrong_signature.json"); + assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false)); + } + + @Test + public void testValidClientKey() throws Exception { + var clientKey = loadClientKey("client_keys/valid.json"); + + var verificationTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS); + assertThat(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp), is(true)); + } + + private ClientPublicKey loadClientKey(String path) + throws NoSuchAlgorithmException, IOException, InvalidKeySpecException { + var keyUrl = Resources.getResource(path); + var gson = new Gson(); + + var lines = Resources.toString(keyUrl, StandardCharsets.US_ASCII); + var object = gson.fromJson(lines, JsonObject.class); + + Instant expires = Instant.parse(object.getAsJsonPrimitive("expires_at").getAsString()); + String key = object.getAsJsonPrimitive("key").getAsString(); + RSAPublicKey publicKey = parsePublicKey(key); + + byte[] signature = Base64.getDecoder().decode(object.getAsJsonPrimitive("signature").getAsString()); + return new ClientPublicKey(expires, publicKey.getEncoded(), signature); + } + + private RSAPublicKey parsePublicKey(String lines) + throws IOException, InvalidKeySpecException, NoSuchAlgorithmException { + try ( + Reader reader = new StringReader(lines); + PemReader pemReader = new PemReader(reader) + ) { + + PemObject pemObject = pemReader.readPemObject(); + byte[] content = pemObject.getContent(); + var pubKeySpec = new X509EncodedKeySpec(content); + + var factory = KeyFactory.getInstance("RSA"); + return (RSAPublicKey) factory.generatePublic(pubKeySpec); + } + } } diff --git a/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json b/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json new file mode 100644 index 0000000..7ecc12e --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_expiration.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T09:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} diff --git a/bukkit/src/test/resources/client_keys/invalid_wrong_key.json b/bukkit/src/test/resources/client_keys/invalid_wrong_key.json new file mode 100644 index 0000000..37bb3ad --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_key.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T10:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU3I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} diff --git a/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json b/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json new file mode 100644 index 0000000..cbca4b1 --- /dev/null +++ b/bukkit/src/test/resources/client_keys/invalid_wrong_signature.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T10:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "bYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +} diff --git a/bukkit/src/test/resources/client_keys/valid.json b/bukkit/src/test/resources/client_keys/valid.json new file mode 100644 index 0000000..a2d6a41 --- /dev/null +++ b/bukkit/src/test/resources/client_keys/valid.json @@ -0,0 +1,5 @@ +{ + "expires_at": "2022-06-12T10:46:26.421156927Z", + "key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n", + "signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA=" +}
This commit is contained in:
@ -119,9 +119,6 @@
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>https://repo.dmulloy2.net/repository/public/</url>
|
||||
<snapshots>
|
||||
<enabled>false</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
|
||||
<!-- AuthMe Reloaded, xAuth and LoginSecurity -->
|
||||
@ -186,7 +183,7 @@
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>5.0.0-SNAPSHOT</version>
|
||||
<version>5.0.0-20220603.031413-2</version>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@ -351,5 +348,12 @@
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/lib/UltraAuth v2.1.2.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.bouncycastle</groupId>
|
||||
<artifactId>bcpkix-jdk18on</artifactId>
|
||||
<version>1.71</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
@ -25,15 +25,28 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.google.common.io.Resources;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.GeneralSecurityException;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.SignatureException;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Base64.Encoder;
|
||||
import java.util.Random;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
@ -46,11 +59,25 @@ import javax.crypto.spec.SecretKeySpec;
|
||||
*
|
||||
* @see net.minecraft.server.MinecraftEncryption
|
||||
*/
|
||||
public class EncryptionUtil {
|
||||
class EncryptionUtil {
|
||||
|
||||
public static final int VERIFY_TOKEN_LENGTH = 4;
|
||||
public static final String KEY_PAIR_ALGORITHM = "RSA";
|
||||
|
||||
private static final int RSA_LENGTH = 1_024;
|
||||
|
||||
private static final PublicKey mojangSessionKey;
|
||||
private static final int LINE_LENGTH = 76;
|
||||
private static final Encoder KEY_ENCODER = Base64.getMimeEncoder(LINE_LENGTH, "\n".getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
static {
|
||||
try {
|
||||
mojangSessionKey = loadMojangSessionKey();
|
||||
} catch (IOException | NoSuchAlgorithmException | InvalidKeySpecException ex) {
|
||||
throw new RuntimeException("Failed to load Mojang session key", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private EncryptionUtil() {
|
||||
// utility
|
||||
}
|
||||
@ -65,7 +92,7 @@ public class EncryptionUtil {
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
|
||||
|
||||
keyPairGenerator.initialize(1_024);
|
||||
keyPairGenerator.initialize(RSA_LENGTH);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
||||
// Should be existing in every vm
|
||||
@ -120,6 +147,33 @@ public class EncryptionUtil {
|
||||
return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES");
|
||||
}
|
||||
|
||||
public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimstamp)
|
||||
throws SignatureException, NoSuchAlgorithmException, InvalidKeyException {
|
||||
if (!verifyTimstamp.isBefore(clientKey.getExpiry())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Signature signature = Signature.getInstance("SHA1withRSA");
|
||||
signature.initVerify(mojangSessionKey);
|
||||
signature.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII));
|
||||
return signature.verify(clientKey.getSignature());
|
||||
}
|
||||
|
||||
private static PublicKey loadMojangSessionKey()
|
||||
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
|
||||
var keyUrl = Resources.getResource("yggdrasil_session_pubkey.der");
|
||||
var keyData = Resources.toByteArray(keyUrl);
|
||||
var keySpec = new X509EncodedKeySpec(keyData);
|
||||
|
||||
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
|
||||
}
|
||||
|
||||
private static String toSignable(ClientPublicKey clientPublicKey) {
|
||||
long expiry = clientPublicKey.getExpiry().toEpochMilli();
|
||||
String encoded = KEY_ENCODER.encodeToString(clientPublicKey.getKey());
|
||||
return expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n";
|
||||
}
|
||||
|
||||
public static byte[] decrypt(PrivateKey key, byte[] data) throws GeneralSecurityException {
|
||||
// b(Key var0, byte[] var1)
|
||||
Cipher cipher = Cipher.getInstance(key.getAlgorithm());
|
||||
|
@ -27,18 +27,27 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.events.InternalStructure;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService;
|
||||
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.SignatureException;
|
||||
import java.time.Instant;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
@ -149,6 +158,11 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
username = (String) packetEvent.getPacket().getMeta("original_name").get();
|
||||
}
|
||||
|
||||
if (!verifyPublicKey(packet)) {
|
||||
plugin.getLog().warn("Invalid public key from player {}", username);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username);
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
@ -156,6 +170,24 @@ public class ProtocolLibListener extends PacketAdapter {
|
||||
plugin.getScheduler().runAsync(nameCheckTask);
|
||||
}
|
||||
|
||||
private boolean verifyPublicKey(PacketContainer packet) {
|
||||
Optional<InternalStructure> internalStructure = packet.getOptionalStructures().readSafely(0);
|
||||
if (internalStructure == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Object instance = internalStructure.get().getHandle();
|
||||
Instant expires = FuzzyReflection.getFieldValue(instance, Instant.class, true);
|
||||
PublicKey key = FuzzyReflection.getFieldValue(instance, PublicKey.class, true);
|
||||
byte[] signature = FuzzyReflection.getFieldValue(instance, byte[].class, true);
|
||||
ClientPublicKey clientKey = new ClientPublicKey(expires, key.getEncoded(), signature);
|
||||
try {
|
||||
return EncryptionUtil.verifyClientKey(clientKey, Instant.now());
|
||||
} catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private String getUsername(PacketContainer packet) {
|
||||
WrappedGameProfile profile = packet.getGameProfiles().readSafely(0);
|
||||
if (profile == null) {
|
||||
|
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* SPDX-License-Identifier: MIT
|
||||
*
|
||||
* The MIT License (MIT)
|
||||
*
|
||||
* Copyright (c) 2015-2022 games647 and contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib.packet;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
public class ClientPublicKey {
|
||||
|
||||
private final Instant expiry;
|
||||
private final byte[] key;
|
||||
private final byte[] signature;
|
||||
|
||||
public ClientPublicKey(Instant expiry, byte[] key, byte[] signature) {
|
||||
this.expiry = expiry;
|
||||
this.key = key;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
public Instant getExpiry() {
|
||||
return expiry;
|
||||
}
|
||||
|
||||
public byte[] getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public byte[] getSignature() {
|
||||
return signature;
|
||||
}
|
||||
}
|
@ -25,8 +25,27 @@
|
||||
*/
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import java.security.SecureRandom;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
|
||||
import com.google.common.io.Resources;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.interfaces.RSAPublicKey;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.time.Instant;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.bouncycastle.util.io.pem.PemObject;
|
||||
import org.bouncycastle.util.io.pem.PemReader;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
@ -43,4 +62,77 @@ public class EncryptionUtilTest {
|
||||
assertThat(token, notNullValue());
|
||||
assertThat(token.length, is(4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpiredClientKey() throws Exception {
|
||||
var clientKey = loadClientKey("client_keys/valid.json");
|
||||
|
||||
// Client expires at the exact second mentioned, so use it for verification
|
||||
var expiredTimestamp = clientKey.getExpiry();
|
||||
assertThat(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp), is(false));
|
||||
}
|
||||
|
||||
// @Test(expected = Exception.class)
|
||||
@Test
|
||||
public void testInvalidChangedExpiration() throws Exception {
|
||||
// expiration date changed should make the signature invalid
|
||||
// expiration should still be valid
|
||||
var clientKey = loadClientKey("client_keys/invalid_wrong_expiration.json");
|
||||
assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false));
|
||||
}
|
||||
|
||||
// @Test(expected = Exception.class)
|
||||
@Test
|
||||
public void testInvalidChangedKey() throws Exception {
|
||||
// changed public key no longer corresponding to the signature
|
||||
var clientKey = loadClientKey("client_keys/invalid_wrong_key.json");
|
||||
assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInvalidChangedSignature() throws Exception {
|
||||
// signature modified no longer corresponding to key and expiration date
|
||||
var clientKey = loadClientKey("client_keys/invalid_wrong_signature.json");
|
||||
assertThat(EncryptionUtil.verifyClientKey(clientKey, clientKey.getExpiry().minus(5, ChronoUnit.HOURS)), is(false));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testValidClientKey() throws Exception {
|
||||
var clientKey = loadClientKey("client_keys/valid.json");
|
||||
|
||||
var verificationTimestamp = clientKey.getExpiry().minus(5, ChronoUnit.HOURS);
|
||||
assertThat(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp), is(true));
|
||||
}
|
||||
|
||||
private ClientPublicKey loadClientKey(String path)
|
||||
throws NoSuchAlgorithmException, IOException, InvalidKeySpecException {
|
||||
var keyUrl = Resources.getResource(path);
|
||||
var gson = new Gson();
|
||||
|
||||
var lines = Resources.toString(keyUrl, StandardCharsets.US_ASCII);
|
||||
var object = gson.fromJson(lines, JsonObject.class);
|
||||
|
||||
Instant expires = Instant.parse(object.getAsJsonPrimitive("expires_at").getAsString());
|
||||
String key = object.getAsJsonPrimitive("key").getAsString();
|
||||
RSAPublicKey publicKey = parsePublicKey(key);
|
||||
|
||||
byte[] signature = Base64.getDecoder().decode(object.getAsJsonPrimitive("signature").getAsString());
|
||||
return new ClientPublicKey(expires, publicKey.getEncoded(), signature);
|
||||
}
|
||||
|
||||
private RSAPublicKey parsePublicKey(String lines)
|
||||
throws IOException, InvalidKeySpecException, NoSuchAlgorithmException {
|
||||
try (
|
||||
Reader reader = new StringReader(lines);
|
||||
PemReader pemReader = new PemReader(reader)
|
||||
) {
|
||||
|
||||
PemObject pemObject = pemReader.readPemObject();
|
||||
byte[] content = pemObject.getContent();
|
||||
var pubKeySpec = new X509EncodedKeySpec(content);
|
||||
|
||||
var factory = KeyFactory.getInstance("RSA");
|
||||
return (RSAPublicKey) factory.generatePublic(pubKeySpec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"expires_at": "2022-06-12T09:46:26.421156927Z",
|
||||
"key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||
"signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA="
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"expires_at": "2022-06-12T10:46:26.421156927Z",
|
||||
"key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU3I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||
"signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA="
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
{
|
||||
"expires_at": "2022-06-12T10:46:26.421156927Z",
|
||||
"key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xm\nDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/\n33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZ\nOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF\n4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWc\nPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||
"signature": "bYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA="
|
||||
}
|
5
bukkit/src/test/resources/client_keys/valid.json
Normal file
5
bukkit/src/test/resources/client_keys/valid.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"expires_at": "2022-06-12T10:46:26.421156927Z",
|
||||
"key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoOv23jt2QPyab6bPRBwH2ggmzQU4I+xmDpi3X5ZB5Em/4uzyZqNVLJc0gShpk0XsdoB28Nq1bPxczOTBxuXi3rg5ax5gL+iymDSU27DLM8s/33lOofzGPJUEQGKlFm0QelDKZ/q5Y/9inHE3hEJKf7h0tnmGahXFmZSF/nRz9GHnfSYpjtDr9bsZOzQuLhHXT5E4ksNRTFW41h0MlZ1qOhO+NiiVgk7LmgVYiV7RRbgO8U6RaCEqg5n28Ewo6QtzB+DF4NTDeu3E9BLH5G0npdUrVNhdRUWCFDmH6n9hqSIz2J7o6GvWqEvp0h9e/3qtLsoS60hnQXunrcWcPaEIYQIDAQAB\n-----END RSA PUBLIC KEY-----\n",
|
||||
"signature": "BYv2mKJvz5O3Wo5V5sbJI0L6zAjfzQSkTNd7ykd/MB7KPPDg4zoTuOqphmh042xz1EYbMcfqEZvP04NTaoZDx+IxGieBB+LuxqnmYKIgtpcR2SEpzbSKznSHkupr1hKwF7kCVWLlwSbxc/XRlWPPyT6FE9m628A/jFb/obgfzLLQWfTFWp6kq2oBoUUQV5om2ihdrJ8oLCsw10SGdcFtK4+UuLzz+wjwv3JpvIX93IKdjFnw0KNd110HOPWZgp2n8+f6GsecysorqvwaE1rJC0m9Qa/wFsK2TY7twSMreCrbXPaBiWrkRovtm9gnaBwD+iuZYlnLvO0ld8qW928LL2vFBTi3TsPUWC3i/xYnAR2m8YP2hiCLHuPfSJmgfxHsM2iRdrR8qdOUkiC9h34STEA7Q2+rWkNWJ+YKYVTIkyqHEuXqU87txhVTaRJi6UDGDn49cMKmZwQnn+23JQf1chcn1YFkrivDaJPhm17GhoEldQHSLQfxb0ifja5WBNDbkKBF/h9JqvG3Ya9anxlyxY6g7/m2zP73xfkvUnejoX4GKjffEqezQmTe9RIeuWyz94nfZNLr0Ps363kAfP4KSW+f4zkTU/UVg19ccAY0ZhiwDetKyksU5WqLO8xMPZ6PNFYhNeBb2yhGdT8PidkRYkC4XBn1k7F7apiNUuZU8aA="
|
||||
}
|
Reference in New Issue
Block a user