package net.glowstone.net.handler.login; import com.flowpowered.network.MessageHandler; import lombok.AllArgsConstructor; import net.glowstone.EventFactory; import net.glowstone.GlowServer; import net.glowstone.entity.meta.profile.PlayerProfile; import net.glowstone.entity.meta.profile.PlayerProperty; import net.glowstone.net.GlowSession; import net.glowstone.net.http.HttpCallback; import net.glowstone.net.http.HttpClient; import net.glowstone.net.message.login.EncryptionKeyResponseMessage; import net.glowstone.util.UuidUtils; import org.bukkit.event.player.AsyncPlayerPreLoginEvent; import org.bukkit.event.player.AsyncPlayerPreLoginEvent.Result; import org.json.simple.JSONArray; import org.json.simple.JSONObject; import org.json.simple.parser.JSONParser; import org.json.simple.parser.ParseException; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.math.BigInteger; import java.security.GeneralSecurityException; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.UUID; import java.util.logging.Level; public final class EncryptionKeyResponseHandler implements MessageHandler<GlowSession, EncryptionKeyResponseMessage> { private static final String BASE_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined"; private static final JSONParser PARSER = new JSONParser(); @Override public void handle(GlowSession session, EncryptionKeyResponseMessage message) { PrivateKey privateKey = session.getServer().getKeyPair().getPrivate(); // create rsaCipher Cipher rsaCipher; try { rsaCipher = Cipher.getInstance("RSA"); } catch (GeneralSecurityException ex) { GlowServer.logger.log(Level.SEVERE, "Could not initialize RSA cipher", ex); session.disconnect("Unable to initialize RSA cipher."); return; } // decrypt shared secret SecretKey sharedSecret; try { rsaCipher.init(Cipher.DECRYPT_MODE, privateKey); sharedSecret = new SecretKeySpec(rsaCipher.doFinal(message.getSharedSecret()), "AES"); } catch (Exception ex) { GlowServer.logger.log(Level.WARNING, "Could not decrypt shared secret", ex); session.disconnect("Unable to decrypt shared secret."); return; } // decrypt verify token byte[] verifyToken; try { rsaCipher.init(Cipher.DECRYPT_MODE, privateKey); verifyToken = rsaCipher.doFinal(message.getVerifyToken()); } catch (Exception ex) { GlowServer.logger.log(Level.WARNING, "Could not decrypt verify token", ex); session.disconnect("Unable to decrypt verify token."); return; } // check verify token if (!Arrays.equals(verifyToken, session.getVerifyToken())) { session.disconnect("Invalid verify token."); return; } // initialize stream encryption session.enableEncryption(sharedSecret); // create hash for auth String hash; try { MessageDigest digest = MessageDigest.getInstance("SHA-1"); digest.update(session.getSessionId().getBytes()); digest.update(sharedSecret.getEncoded()); digest.update(session.getServer().getKeyPair().getPublic().getEncoded()); // BigInteger takes care of sign and leading zeroes hash = new BigInteger(digest.digest()).toString(16); } catch (NoSuchAlgorithmException ex) { GlowServer.logger.log(Level.SEVERE, "Unable to generate SHA-1 digest", ex); session.disconnect("Failed to hash login data."); return; } String url = BASE_URL + "?username=" + session.getVerifyUsername() + "&serverId=" + hash; HttpClient.connect(url, session.getChannel().eventLoop(), new ClientAuthCallback(session)); } @AllArgsConstructor private static class ClientAuthCallback implements HttpCallback { private final GlowSession session; @Override public void done(String response) { JSONObject json; try { json = (JSONObject) PARSER.parse(response); // TODO gson here } catch (ParseException e) { GlowServer.logger.warning("Username \"" + session.getVerifyUsername() + "\" failed to authenticate!"); session.disconnect("Failed to verify username!"); return; } String name = (String) json.get("name"); String id = (String) json.get("id"); // parse UUID UUID uuid; try { uuid = UuidUtils.fromFlatString(id); } catch (IllegalArgumentException ex) { GlowServer.logger.log(Level.SEVERE, "Returned authentication UUID invalid: " + id, ex); session.disconnect("Invalid UUID."); return; } JSONArray propsArray = (JSONArray) json.get("properties"); // parse properties List<PlayerProperty> properties = new ArrayList<>(propsArray.size()); for (Object obj : propsArray) { JSONObject propJson = (JSONObject) obj; String propName = (String) propJson.get("name"); String value = (String) propJson.get("value"); String signature = (String) propJson.get("signature"); properties.add(new PlayerProperty(propName, value, signature)); } AsyncPlayerPreLoginEvent event = EventFactory.onPlayerPreLogin(name, session.getAddress(), uuid); if (event.getLoginResult() != Result.ALLOWED) { session.disconnect(event.getKickMessage(), true); return; } // spawn player session.getServer().getScheduler().runTask(null, () -> session.setPlayer(new PlayerProfile(name, uuid, properties))); } @Override public void error(Throwable t) { GlowServer.logger.log(Level.SEVERE, "Error in authentication thread", t); session.disconnect("Internal error during authentication.", true); } } }