From e8bb3ec7a9e37c170876effab90ea96e8405d7e0 Mon Sep 17 00:00:00 2001 From: games647 Date: Thu, 23 Jun 2022 19:28:54 +0200 Subject: [PATCH] Validate other encryption methods --- .../fastlogin/bukkit/PremiumPlaceholder.java | 12 +++ .../protocollib/EncryptionUtilTest.java | 96 ++++++++++++++++++- 2 files changed, 104 insertions(+), 4 deletions(-) diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/PremiumPlaceholder.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/PremiumPlaceholder.java index d2acea0c..b839afee 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/PremiumPlaceholder.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/PremiumPlaceholder.java @@ -25,6 +25,8 @@ */ package com.github.games647.fastlogin.bukkit; +import java.util.List; + import me.clip.placeholderapi.expansion.PlaceholderExpansion; import org.bukkit.OfflinePlayer; @@ -40,6 +42,16 @@ public class PremiumPlaceholder extends PlaceholderExpansion { this.plugin = plugin; } + @Override + public boolean persist() { + return true; + } + + @Override + public @NotNull List getPlaceholders() { + return List.of(PLACEHOLDER_VARIABLE); + } + @Override public String onRequest(OfflinePlayer player, @NotNull String identifier) { // player is null if offline 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 582496d3..34579b30 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 @@ -27,6 +27,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.hash.Hashing; import com.google.common.io.Resources; import com.google.gson.Gson; import com.google.gson.JsonObject; @@ -34,13 +35,14 @@ import com.google.gson.JsonObject; import java.io.IOException; import java.io.Reader; import java.io.StringReader; +import java.math.BigInteger; import java.nio.charset.StandardCharsets; +import java.security.InvalidKeyException; +import java.security.Key; import java.security.KeyFactory; import java.security.KeyPair; import java.security.NoSuchAlgorithmException; -import java.security.PrivateKey; import java.security.PublicKey; -import java.security.SecureRandom; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; import java.security.spec.InvalidKeySpecException; @@ -49,12 +51,22 @@ import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Base64; +import java.util.concurrent.ThreadLocalRandom; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.SecretKeySpec; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemReader; +import org.jetbrains.annotations.NotNull; import org.junit.Test; import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -95,7 +107,6 @@ public class EncryptionUtilTest { 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 @@ -106,7 +117,6 @@ public class EncryptionUtilTest { assertThat(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp), is(false)); } - // @Test(expected = Exception.class) @Test public void testInvalidChangedKey() throws Exception { // changed public key no longer corresponding to the signature @@ -133,6 +143,84 @@ public class EncryptionUtilTest { assertThat(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp), is(true)); } + @Test + public void testDecryptSharedSecret() throws Exception { + KeyPair serverPair = EncryptionUtil.generateKeyPair(); + var serverPK = serverPair.getPublic(); + + SecretKey secretKey = generateSharedKey(); + byte[] encryptedSecret = encrypt(serverPK, secretKey.getEncoded()); + + SecretKey decryptSharedKey = EncryptionUtil.decryptSharedKey(serverPair.getPrivate(), encryptedSecret); + assertThat(decryptSharedKey, is(secretKey)); + } + + private byte[] encrypt(PublicKey receiverKey, byte[] message) + throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, + IllegalBlockSizeException, BadPaddingException { + var encryptCipher = Cipher.getInstance(receiverKey.getAlgorithm()); + encryptCipher.init(Cipher.ENCRYPT_MODE, receiverKey); + return encryptCipher.doFinal(message); + } + + private SecretKeySpec generateSharedKey() { + // according to wiki.vg 16 bytes long + byte[] sharedKey = new byte[16]; + ThreadLocalRandom.current().nextBytes(sharedKey); + // shared key is to be used for the AES/CFB8 stream cipher to encrypt the traffic + // therefore the encryption/decryption has to be AES + return new SecretKeySpec(sharedKey, "AES"); + } + + @Test + public void testServerIdHash() throws Exception { + var serverId = ""; + var sharedSecret = generateSharedKey(); + var serverPK = loadClientKey("client_keys/valid_public_key.json").key(); + + String sessionHash = getServerHash(serverId, sharedSecret, serverPK); + assertThat(EncryptionUtil.getServerIdHashString(serverId, sharedSecret, serverPK), is(sessionHash)); + } + + @NotNull + private String getServerHash(String serverId, SecretKey sharedSecret, PublicKey serverPK) { + // https://wiki.vg/Protocol_Encryption#Client + // sha1 := Sha1() + // sha1.update(ASCII encoding of the server id string from Encryption Request) + // sha1.update(shared secret) + // sha1.update(server's encoded public key from Encryption Request) + // hash := sha1.hexdigest() # String of hex characters + var hasher = Hashing.sha1().newHasher(); + hasher.putString(serverId, StandardCharsets.US_ASCII); + hasher.putBytes(sharedSecret.getEncoded()); + hasher.putBytes(serverPK.getEncoded()); + // It works by treating the sha1 output bytes as one large integer in two's complement and then printing the + // integer in base 16, placing a minus sign if the interpreted number is negative. + // reference: https://github.com/SpigotMC/BungeeCord/blob/ff5727c5ef9c0b56ad35f9816ae6bd660b622cf0/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java#L456 + return new BigInteger(hasher.hash().asBytes()).toString(16); + } + + @Test + public void testServerIdHashWrongSecret() throws Exception { + var serverId = ""; + var sharedSecret = generateSharedKey(); + var serverPK = loadClientKey("client_keys/valid_public_key.json").key(); + + String sessionHash = getServerHash(serverId, sharedSecret, serverPK); + assertThat(EncryptionUtil.getServerIdHashString("", generateSharedKey(), serverPK), not(sessionHash)); + } + + @Test + public void testServerIdHashWrongServerKey() throws Exception { + var serverId = ""; + var sharedSecret = generateSharedKey(); + var serverPK = EncryptionUtil.generateKeyPair().getPublic(); + + String sessionHash = getServerHash(serverId, sharedSecret, serverPK); + var wrongPK = EncryptionUtil.generateKeyPair().getPublic(); + assertThat(EncryptionUtil.getServerIdHashString("", sharedSecret, wrongPK), not(sessionHash)); + } + @Test public void testValidSignedNonce() throws Exception { ClientPublicKey clientKey = loadClientKey("client_keys/valid_public_key.json");