Update description

This commit is contained in:
games647
2015-11-03 17:44:57 +01:00
parent 53af09ae34
commit fdc2772f38
6 changed files with 196 additions and 77 deletions

View File

@@ -31,23 +31,23 @@ import javax.crypto.spec.SecretKeySpec;
*
* Remapped by: https://github.com/Techcable/MinecraftMappings/tree/master/1.8
*/
public class Encryption {
public class EncryptionUtil {
public static KeyPair generateKeyPair() {
try {
KeyPairGenerator keypairgenerator = KeyPairGenerator.getInstance("RSA");
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keypairgenerator.initialize(1024);
return keypairgenerator.generateKeyPair();
keyPairGenerator.initialize(1024);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
//Should be existing in every vm
return null;
throw new ExceptionInInitializerError(nosuchalgorithmexception);
}
}
public static byte[] getServerIdHash(String serverId, PublicKey publickey, SecretKey secretkey) {
public static byte[] getServerIdHash(String serverId, PublicKey publicKey, SecretKey secretKey) {
return digestOperation("SHA-1"
, new byte[][]{serverId.getBytes(Charsets.ISO_8859_1), secretkey.getEncoded(), publickey.getEncoded()});
, new byte[][]{serverId.getBytes(Charsets.ISO_8859_1), secretKey.getEncoded(), publicKey.getEncoded()});
}
private static byte[] digestOperation(String algo, byte[]... content) {
@@ -66,24 +66,24 @@ public class Encryption {
public static PublicKey decodePublicKey(byte[] encodedKey) {
try {
X509EncodedKeySpec x509encodedkeyspec = new X509EncodedKeySpec(encodedKey);
KeyFactory keyfactory = KeyFactory.getInstance("RSA");
X509EncodedKeySpec x509encodedkeyspec = new X509EncodedKeySpec(encodedKey);
return keyfactory.generatePublic(x509encodedkeyspec);
} catch (NoSuchAlgorithmException | InvalidKeySpecException nosuchalgorithmexception) {
;
//ignore
}
System.err.println("Public key reconstitute failed!");
return null;
}
public static SecretKey decryptSharedKey(PrivateKey privatekey, byte[] encryptedSharedKey) {
return new SecretKeySpec(decryptData(privatekey, encryptedSharedKey), "AES");
public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] encryptedSharedKey) {
return new SecretKeySpec(decryptData(privateKey, encryptedSharedKey), "AES");
}
public static byte[] decryptData(Key key, byte[] abyte) {
return cipherOperation(Cipher.DECRYPT_MODE, key, abyte);
public static byte[] decryptData(Key key, byte[] data) {
return cipherOperation(Cipher.DECRYPT_MODE, key, data);
}
private static byte[] cipherOperation(int operationMode, Key key, byte[] data) {
@@ -122,7 +122,7 @@ public class Encryption {
}
}
private Encryption() {
private EncryptionUtil() {
//utility
}
}

View File

@@ -33,7 +33,7 @@ public class FastLogin extends JavaPlugin {
private static final String USER_AGENT = "Premium-Checker";
//provide a immutable key pair to be thread safe | used for encrypting and decrypting traffic
private final KeyPair keyPair = Encryption.generateKeyPair();
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
//we need a thread-safe set because we access it async in the packet listener
private final Set<String> enabledPremium = Sets.newConcurrentHashSet();
@@ -102,7 +102,7 @@ public class FastLogin extends JavaPlugin {
*
* @return the server KeyPair
*/
public KeyPair getKeyPair() {
public KeyPair getServerKey() {
return keyPair;
}

View File

@@ -9,7 +9,7 @@ import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.github.games647.fastlogin.Encryption;
import com.github.games647.fastlogin.EncryptionUtil;
import com.github.games647.fastlogin.FastLogin;
import com.github.games647.fastlogin.PlayerSession;
@@ -80,7 +80,6 @@ public class EncryptionPacketListener extends PacketAdapter {
*/
@Override
public void onPacketReceiving(PacketEvent packetEvent) {
PacketContainer packet = packetEvent.getPacket();
Player player = packetEvent.getPlayer();
//the player name is unknown to ProtocolLib (so getName() doesn't work) - now uses ip:port as key
@@ -88,48 +87,23 @@ public class EncryptionPacketListener extends PacketAdapter {
PlayerSession session = plugin.getSessions().get(uniqueSessionKey);
if (session == null) {
disconnect(packetEvent, "Invalid request", Level.FINE
, "Player {0} tried to send encryption response on an invalid connection state"
, "Player {0} tried to send encryption response at invalid state"
, player.getAddress());
return;
}
byte[] sharedSecret = packet.getByteArrays().read(0);
//encrypted verify token
byte[] clientVerify = packet.getByteArrays().read(1);
PrivateKey privateKey = plugin.getServerKey().getPrivate();
PrivateKey privateKey = plugin.getKeyPair().getPrivate();
byte[] serverVerify = session.getVerifyToken();
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
if (!Arrays.equals(serverVerify, Encryption.decryptData(privateKey, clientVerify))) {
//check if the verify token are equal to the server sent one
disconnect(packetEvent, "Invalid token", Level.FINE
, "Player {0} ({1}) tried to login with an invalid verify token. "
+ "Server: {2} Client: {3}"
, session.getUsername(), player.getAddress(), serverVerify, clientVerify);
return;
}
SecretKey loginKey = Encryption.decryptSharedKey(privateKey, sharedSecret);
try {
//get the NMS connection handle of this player
Object networkManager = getNetworkManager(player);
//try to detect the method by parameters
Method encryptConnectionMethod = FuzzyReflection.fromObject(networkManager)
.getMethodByParameters("a", SecretKey.class);
//encrypt/decrypt following packets
//the client expects this behaviour
encryptConnectionMethod.invoke(networkManager, loginKey);
} catch (ReflectiveOperationException ex) {
disconnect(packetEvent, "Error occurred", Level.SEVERE, "Couldn't enable encryption", ex);
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
SecretKey loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
if (!checkVerifyToken(session, privateKey, packetEvent) || !encryptConnection(player, loginKey, packetEvent)) {
return;
}
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L193
//generate the server id based on client and server data
String serverId = (new BigInteger(Encryption.getServerIdHash("", plugin.getKeyPair().getPublic(), loginKey)))
.toString(16);
byte[] serverIdHash = EncryptionUtil.getServerIdHash("", plugin.getServerKey().getPublic(), loginKey);
String serverId = (new BigInteger(serverIdHash)).toString(16);
String username = session.getUsername();
if (hasJoinedServer(username, serverId)) {
@@ -144,9 +118,63 @@ public class EncryptionPacketListener extends PacketAdapter {
, session.getUsername(), player.getAddress(), serverId);
}
//this is a fake packet; it shouldn't be send to the server
packetEvent.setCancelled(true);
}
private boolean checkVerifyToken(PlayerSession session, PrivateKey privateKey, PacketEvent packetEvent) {
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.decryptData(privateKey, responseVerify))) {
//check if the verify token are equal to the server sent one
disconnect(packetEvent, "Invalid token", Level.FINE
, "Player {0} ({1}) tried to login with an invalid verify token. "
+ "Server: {2} Client: {3}"
, session.getUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
return false;
}
return true;
}
//try to get the networkManager from ProtocolLib
private Object getNetworkManager(Player player)
throws IllegalAccessException, NoSuchFieldException {
Object injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Field injectorField = injector.getClass().getDeclaredField("injector");
injectorField.setAccessible(true);
Object rawInjector = injectorField.get(injector);
injectorField = rawInjector.getClass().getDeclaredField("networkManager");
injectorField.setAccessible(true);
return injectorField.get(rawInjector);
}
private boolean encryptConnection(Player player, SecretKey loginKey, PacketEvent packetEvent)
throws IllegalArgumentException {
try {
//get the NMS connection handle of this player
Object networkManager = getNetworkManager(player);
//try to detect the method by parameters
Method encryptConnectionMethod = FuzzyReflection.fromObject(networkManager)
.getMethodByParameters("a", SecretKey.class);
//encrypt/decrypt following packets
//the client expects this behaviour
encryptConnectionMethod.invoke(networkManager, loginKey);
} catch (ReflectiveOperationException ex) {
disconnect(packetEvent, "Error occurred", Level.SEVERE, "Couldn't enable encryption", ex);
return false;
}
return true;
}
private void disconnect(PacketEvent packetEvent, String kickReason, Level logLevel, String logMessage
, Object... arguments) {
plugin.getLogger().log(logLevel, logMessage, arguments);
@@ -170,20 +198,6 @@ public class EncryptionPacketListener extends PacketAdapter {
}
}
//try to get the networkManager from ProtocolLib
private Object getNetworkManager(Player player)
throws SecurityException, IllegalAccessException, NoSuchFieldException {
Object injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
Field injectorField = injector.getClass().getDeclaredField("injector");
injectorField.setAccessible(true);
Object rawInjector = injectorField.get(injector);
injectorField = rawInjector.getClass().getDeclaredField("networkManager");
injectorField.setAccessible(true);
return injectorField.get(rawInjector);
}
private boolean hasJoinedServer(String username, String serverId) {
try {
String url = HAS_JOINED_URL + "username=" + username + "&serverId=" + serverId;
@@ -192,7 +206,7 @@ public class EncryptionPacketListener extends PacketAdapter {
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
String line = reader.readLine();
if (!line.equals("null")) {
if (!"null".equals(line)) {
//validate parsing
//http://wiki.vg/Protocol_Encryption#Server
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
@@ -220,7 +234,7 @@ public class EncryptionPacketListener extends PacketAdapter {
//see StartPacketListener for packet information
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START, true);
//uuid is ignored
//uuid is ignored by the packet definition
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
startPacket.getGameProfiles().write(0, fakeProfile);
try {

View File

@@ -68,7 +68,6 @@ public class StartPacketListener extends PacketAdapter {
*/
@Override
public void onPacketReceiving(PacketEvent packetEvent) {
PacketContainer packet = packetEvent.getPacket();
Player player = packetEvent.getPlayer();
//this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes
@@ -78,17 +77,18 @@ public class StartPacketListener extends PacketAdapter {
plugin.getSessions().remove(sessionKey);
//player.getName() won't work at this state
PacketContainer packet = packetEvent.getPacket();
String username = packet.getGameProfiles().read(0).getName();
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting to the server"
, new Object[]{sessionKey, username});
if (plugin.getEnabledPremium().contains(username) && isPremium(username)) {
if (plugin.getEnabledPremium().contains(username) && isPremiumName(username)) {
//minecraft server implementation
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
sentEncryptionRequest(sessionKey, username, player, packetEvent);
}
}
private boolean isPremium(String playerName) {
private boolean isPremiumName(String playerName) {
//check if it's a valid playername
if (playernameMatcher.matcher(playerName).matches()) {
//only make a API call if the name is valid existing mojang account
@@ -120,12 +120,13 @@ public class StartPacketListener extends PacketAdapter {
PacketContainer newPacket = protocolManager
.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN, true);
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getKeyPair().getPublic());
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getServerKey().getPublic());
//generate a random token which should be the same when we receive it from the client
byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
random.nextBytes(verifyToken);
newPacket.getByteArrays().write(0, verifyToken);
//serverId is a empty string
protocolManager.sendServerPacket(player, newPacket);
//cancel only if the player has a paid account otherwise login as normal offline player