Added optional Mojang auth and UUIDs support (MojangAuth.init()), added support for skin layers, and made everywhere have max lighting.

This commit is contained in:
Eoghanmc22 2020-06-21 18:04:19 -04:00
parent ac758e4acb
commit 42276efc49
19 changed files with 2536 additions and 2100 deletions

View File

@ -11,6 +11,7 @@ sourceCompatibility = 1.11
repositories {
mavenCentral()
maven { url 'https://jitpack.io' }
maven { url 'https://libraries.minecraft.net' }
}
@ -47,5 +48,9 @@ dependencies {
api 'net.kyori:text-serializer-legacy:3.0.3'
api 'net.kyori:text-serializer-gson:3.0.3'
api 'net.kyori:text-serializer-plain:3.0.3'
api 'com.mojang:authlib:1.5.21'
api 'net.sf.jopt-simple:jopt-simple:5.0.4'
api 'org.projectlombok:lombok:1.18.12'
annotationProcessor 'org.projectlombok:lombok:1.18.12'
}

View File

@ -1,10 +1,16 @@
package net.minestom.server;
import com.mojang.authlib.AuthenticationService;
import com.mojang.authlib.minecraft.MinecraftSessionService;
import com.mojang.authlib.yggdrasil.YggdrasilAuthenticationService;
import lombok.Getter;
import lombok.Setter;
import net.minestom.server.benchmark.BenchmarkManager;
import net.minestom.server.command.CommandManager;
import net.minestom.server.data.DataManager;
import net.minestom.server.entity.EntityManager;
import net.minestom.server.entity.Player;
import net.minestom.server.extras.mojangAuth.MojangCrypt;
import net.minestom.server.gamedata.loottables.LootTableManager;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.instance.block.BlockManager;
@ -26,8 +32,11 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.Proxy;
import java.security.KeyPair;
public class MinecraftServer {
@Getter
private final static Logger LOGGER = LoggerFactory.getLogger(MinecraftServer.class);
public static final int PROTOCOL_VERSION = 578;
@ -66,6 +75,11 @@ public class MinecraftServer {
public static final int TICK_MS = MS_TO_SEC / 20;
public static final int TICK_PER_SECOND = MS_TO_SEC / TICK_MS;
//Extras
@Getter
@Setter
private static boolean fixLighting = true;
// Networking
private static PacketProcessor packetProcessor;
private static PacketListenerManager packetListenerManager;
@ -92,6 +106,14 @@ public class MinecraftServer {
private static Difficulty difficulty = Difficulty.NORMAL;
private static LootTableManager lootTableManager;
//Mojang Auth
@Getter
private static KeyPair keyPair = MojangCrypt.generateKeyPair();
@Getter
private static AuthenticationService authService = new YggdrasilAuthenticationService(Proxy.NO_PROXY, "");
@Getter
private static MinecraftSessionService sessionService = authService.createMinecraftSessionService();
public static MinecraftServer init() {
connectionManager = new ConnectionManager();
packetProcessor = new PacketProcessor();

View File

@ -21,4 +21,17 @@ public class ByteArrayData extends DataType<byte[]> {
}
return array;
}
public static void encodeByteArray(PacketWriter packetWriter, byte[] value) {
packetWriter.writeVarInt(value.length);
for (byte val : value) {
packetWriter.writeByte(val);
}
}
public static byte[] decodeByteArray(PacketReader packetReader) {
byte[] array = new byte[packetReader.readVarInt()];
for (int i = 0; i < array.length; i++) {
array[i] = packetReader.readByte();
}
return array;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,19 @@
package net.minestom.server.extras;
import lombok.Getter;
import net.minestom.server.MinecraftServer;
public class MojangAuth {
@Getter
private static boolean usingMojangAuth = false;
public static void init() {
if (MinecraftServer.getNettyServer().getAddress() == null) {
System.out.println("Using Mojang Auth");
usingMojangAuth = true;
} else {
throw new IllegalStateException("The server has already been started");
}
}
}

View File

@ -0,0 +1,52 @@
package net.minestom.server.extras.mojangAuth;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import javax.crypto.Cipher;
import javax.crypto.ShortBufferException;
public class CipherBase {
private final Cipher cipher;
private byte[] inTempArray = new byte[0];
private byte[] outTempArray = new byte[0];
protected CipherBase(Cipher cipher) {
this.cipher = cipher;
}
private byte[] bufToByte(ByteBuf buffer) {
int remainingBytes = buffer.readableBytes();
// Need to resize temp array
if (inTempArray.length < remainingBytes) {
inTempArray = new byte[remainingBytes];
}
buffer.readBytes(inTempArray, 0, remainingBytes);
return inTempArray;
}
protected ByteBuf decrypt(ChannelHandlerContext channelHandlerContext, ByteBuf byteBufIn) throws ShortBufferException {
int remainingBytes = byteBufIn.readableBytes();
byte[] bytes = bufToByte(byteBufIn);
ByteBuf outputBuffer = channelHandlerContext.alloc().heapBuffer(cipher.getOutputSize(remainingBytes));
outputBuffer.writerIndex(cipher.update(bytes, 0, remainingBytes, outputBuffer.array(), outputBuffer.arrayOffset()));
return outputBuffer;
}
protected void encrypt(ByteBuf byteBufIn, ByteBuf byteBufOut) throws ShortBufferException {
int remainingBytes = byteBufIn.readableBytes();
byte[] bytes = bufToByte(byteBufIn);
int newSize = cipher.getOutputSize(remainingBytes);
// Need to resize temp array
if (outTempArray.length < newSize) {
outTempArray = new byte[newSize];
}
byteBufOut.writeBytes(outTempArray, 0, cipher.update(bytes, 0, remainingBytes, outTempArray));
}
}

View File

@ -0,0 +1,20 @@
package net.minestom.server.extras.mojangAuth;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageDecoder;
import javax.crypto.Cipher;
import java.util.List;
public class Decrypter extends MessageToMessageDecoder<ByteBuf> {
private final CipherBase cipher;
public Decrypter(Cipher cipher) {
this.cipher = new CipherBase(cipher);
}
protected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {
list.add(this.cipher.decrypt(channelHandlerContext, byteBuf));
}
}

View File

@ -0,0 +1,19 @@
package net.minestom.server.extras.mojangAuth;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToByteEncoder;
import javax.crypto.Cipher;
public class Encrypter extends MessageToByteEncoder<ByteBuf> {
private final CipherBase cipher;
public Encrypter(Cipher cipher) {
this.cipher = new CipherBase(cipher);
}
protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBufIn, ByteBuf byteBufOut) throws Exception {
this.cipher.encrypt(byteBufIn, byteBufOut);
}
}

View File

@ -0,0 +1,98 @@
package net.minestom.server.extras.mojangAuth;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.*;
public class MojangCrypt {
private static final Logger LOGGER = LogManager.getLogger();
public static KeyPair generateKeyPair() {
try {
KeyPairGenerator keyPairGenerator1 = KeyPairGenerator.getInstance("RSA");
keyPairGenerator1.initialize(1024);
return keyPairGenerator1.generateKeyPair();
} catch (NoSuchAlgorithmException var1) {
var1.printStackTrace();
LOGGER.error("Key pair generation failed!");
return null;
}
}
public static byte[] digestData(String string, PublicKey publicKey, SecretKey secretKey) {
try {
return digestData("SHA-1", string.getBytes("ISO_8859_1"), secretKey.getEncoded(), publicKey.getEncoded());
} catch (UnsupportedEncodingException var4) {
var4.printStackTrace();
return null;
}
}
private static byte[] digestData(String string, byte[]... arr) {
try {
MessageDigest messageDigest3 = MessageDigest.getInstance(string);
for(byte[] arr7 : arr) {
messageDigest3.update(arr7);
}
return messageDigest3.digest();
} catch (NoSuchAlgorithmException var7) {
var7.printStackTrace();
return null;
}
}
public static SecretKey decryptByteToSecretKey(PrivateKey privateKey, byte[] arr) {
return new SecretKeySpec(decryptUsingKey(privateKey, arr), "AES");
}
public static byte[] decryptUsingKey(Key key, byte[] arr) {
return cipherData(2, key, arr);
}
private static byte[] cipherData(int integer, Key key, byte[] arr) {
try {
return setupCipher(integer, key.getAlgorithm(), key).doFinal(arr);
} catch (IllegalBlockSizeException var4) {
var4.printStackTrace();
} catch (BadPaddingException var5) {
var5.printStackTrace();
}
LOGGER.error("Cipher data failed!");
return null;
}
private static Cipher setupCipher(int integer, String string, Key key) {
try {
Cipher cipher4 = Cipher.getInstance(string);
cipher4.init(integer, key);
return cipher4;
} catch (InvalidKeyException var4) {
var4.printStackTrace();
} catch (NoSuchAlgorithmException var5) {
var5.printStackTrace();
} catch (NoSuchPaddingException var6) {
var6.printStackTrace();
}
LOGGER.error("Cipher creation failed!");
return null;
}
public static Cipher getCipher(int integer, Key key) {
try {
Cipher cipher3 = Cipher.getInstance("AES/CFB8/NoPadding");
cipher3.init(integer, key, new IvParameterSpec(key.getEncoded()));
return cipher3;
} catch (GeneralSecurityException var3) {
throw new RuntimeException(var3);
}
}
}

View File

@ -12,9 +12,7 @@ import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.CustomBlock;
import net.minestom.server.instance.block.rule.BlockPlacementRule;
import net.minestom.server.network.PacketWriterUtils;
import net.minestom.server.network.packet.server.play.BlockChangePacket;
import net.minestom.server.network.packet.server.play.ParticlePacket;
import net.minestom.server.network.packet.server.play.UnloadChunkPacket;
import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.particle.Particle;
import net.minestom.server.particle.ParticleCreator;
import net.minestom.server.storage.StorageFolder;
@ -448,6 +446,29 @@ public class InstanceContainer extends Instance {
} else {
sendChunkUpdate(player, chunk);
}
if (MinecraftServer.isFixLighting()) {
UpdateLightPacket updateLightPacket = new UpdateLightPacket();
updateLightPacket.chunkX = chunk.getChunkX();
updateLightPacket.chunkZ = chunk.getChunkZ();
updateLightPacket.skyLightMask = 0x3FFF0;
updateLightPacket.blockLightMask = 0x3F;
updateLightPacket.emptySkyLightMask = 0x0F;
updateLightPacket.emptyBlockLightMask = 0x3FFC0;
byte[] bytes = new byte[2048];
Arrays.fill(bytes, (byte) 0xFF);
List<byte[]> temp = new ArrayList<>();
List<byte[]> temp2 = new ArrayList<>();
for (int i = 0; i < 14; ++i) {
temp.add(bytes);
}
for (int i = 0; i < 6; ++i) {
temp2.add(bytes);
}
updateLightPacket.skyLight = temp;
updateLightPacket.blockLight = temp2;
PacketWriterUtils.writeAndSend(player, updateLightPacket);
}
}
@Override

View File

@ -39,8 +39,8 @@ public class NettyServer {
serverBootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(new NettyDecoder());
socketChannel.pipeline().addLast(new ClientChannel(packetProcessor));
socketChannel.pipeline().addLast("decoder", new NettyDecoder());
socketChannel.pipeline().addLast("encoder", new ClientChannel(packetProcessor));
}
});
ChannelFuture channelFuture = serverBootstrap.bind().sync();

View File

@ -1,11 +1,13 @@
package net.minestom.server.network.packet.client.handler;
import net.minestom.server.network.packet.client.login.EncryptionResponsePacket;
import net.minestom.server.network.packet.client.login.LoginStartPacket;
public class ClientLoginPacketsHandler extends ClientPacketsHandler {
public ClientLoginPacketsHandler() {
register(0, LoginStartPacket::new);
register(1, EncryptionResponsePacket::new);
}
}

View File

@ -0,0 +1,68 @@
package net.minestom.server.network.packet.client.login;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.exceptions.AuthenticationUnavailableException;
import net.minestom.server.MinecraftServer;
import net.minestom.server.data.type.array.ByteArrayData;
import net.minestom.server.extras.mojangAuth.MojangCrypt;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.packet.PacketReader;
import net.minestom.server.network.packet.client.ClientPreplayPacket;
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
import net.minestom.server.network.player.NettyPlayerConnection;
import net.minestom.server.network.player.PlayerConnection;
import javax.crypto.SecretKey;
import java.math.BigInteger;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
public class EncryptionResponsePacket implements ClientPreplayPacket {
private final static String THREAD_NAME = "Mojang Auth Thread";
private static AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0);
private byte[] sharedSecret;
private byte[] verifyToken;
@Override
public void process(PlayerConnection connection, ConnectionManager connectionManager) {
new Thread(THREAD_NAME + " #" + UNIQUE_THREAD_ID.incrementAndGet()) {
public void run() {
try {
if (!Arrays.equals(connection.getNonce(), getNonce())) {
System.out.println(connection.getLoginUsername() + " tried to login with an invalid nonce!");
return;
}
if (!connection.getLoginUsername().isEmpty()) {
String string3 = new BigInteger(MojangCrypt.digestData("", MinecraftServer.getKeyPair().getPublic(), getSecretKey())).toString(16);
GameProfile gameProfile = MinecraftServer.getSessionService().hasJoinedServer(new GameProfile(null, connection.getLoginUsername()), string3);
((NettyPlayerConnection) connection).setEncryptionKey(getSecretKey());
LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(gameProfile.getId(), gameProfile.getName());
connection.sendPacket(loginSuccessPacket);
MinecraftServer.getLOGGER().info("UUID of player {} is {}", connection.getLoginUsername(), gameProfile.getId());
connection.setConnectionState(ConnectionState.PLAY);
connectionManager.createPlayer(gameProfile.getId(), gameProfile.getName(), connection);
}
} catch (AuthenticationUnavailableException e) {
e.printStackTrace();
}
}
}.start();
}
@Override
public void read(PacketReader reader) {
sharedSecret = ByteArrayData.decodeByteArray(reader);
verifyToken = ByteArrayData.decodeByteArray(reader);
}
public SecretKey getSecretKey() {
return MojangCrypt.decryptByteToSecretKey(MinecraftServer.getKeyPair().getPrivate(), sharedSecret);
}
public byte[] getNonce() {
return MinecraftServer.getKeyPair().getPrivate() == null ? this.verifyToken : MojangCrypt.decryptUsingKey(MinecraftServer.getKeyPair().getPrivate(), this.verifyToken);
}
}

View File

@ -1,9 +1,11 @@
package net.minestom.server.network.packet.client.login;
import net.minestom.server.extras.MojangAuth;
import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.packet.PacketReader;
import net.minestom.server.network.packet.client.ClientPreplayPacket;
import net.minestom.server.network.packet.server.login.EncryptionRequestPacket;
import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
import net.minestom.server.network.player.PlayerConnection;
@ -15,15 +17,21 @@ public class LoginStartPacket implements ClientPreplayPacket {
@Override
public void process(PlayerConnection connection, ConnectionManager connectionManager) {
// TODO send encryption request OR directly login success
if (MojangAuth.isUsingMojangAuth()) {
connection.setConnectionState(ConnectionState.LOGIN);
UUID playerUuid = connectionManager.getPlayerConnectionUuid(connection);
connection.setLoginUsername(username);
EncryptionRequestPacket encryptionRequestPacket = new EncryptionRequestPacket(connection);
connection.sendPacket(encryptionRequestPacket);
} else {
UUID playerUuid = connectionManager.getPlayerConnectionUuid(connection);
LoginSuccessPacket successPacket = new LoginSuccessPacket(playerUuid, username);
connection.sendPacket(successPacket);
LoginSuccessPacket successPacket = new LoginSuccessPacket(playerUuid, username);
connection.sendPacket(successPacket);
connection.setConnectionState(ConnectionState.PLAY);
connectionManager.createPlayer(playerUuid, username, connection);
connection.setConnectionState(ConnectionState.PLAY);
connectionManager.createPlayer(playerUuid, username, connection);
}
}
@Override

View File

@ -0,0 +1,32 @@
package net.minestom.server.network.packet.server.login;
import net.minestom.server.MinecraftServer;
import net.minestom.server.data.type.array.ByteArrayData;
import net.minestom.server.network.packet.PacketWriter;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.player.PlayerConnection;
import java.util.concurrent.ThreadLocalRandom;
public class EncryptionRequestPacket implements ServerPacket {
private byte[] nonce = new byte[4];
public EncryptionRequestPacket(PlayerConnection connection) {
ThreadLocalRandom.current().nextBytes(nonce);
connection.setNonce(nonce);
}
@Override
public void write(PacketWriter writer) {
writer.writeSizedString("");
byte[] publicKey = MinecraftServer.getKeyPair().getPublic().getEncoded();
ByteArrayData.encodeByteArray(writer, publicKey);
ByteArrayData.encodeByteArray(writer, nonce);
}
@Override
public int getId() {
return 0x01;
}
}

View File

@ -17,7 +17,7 @@ public class LoginSuccessPacket implements ServerPacket {
@Override
public void write(PacketWriter writer) {
writer.writeSizedString(uuid.toString()); // TODO mojang auth
writer.writeSizedString(uuid.toString());
writer.writeSizedString(username);
}

View File

@ -4,6 +4,8 @@ import net.minestom.server.network.packet.PacketWriter;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import java.util.List;
public class UpdateLightPacket implements ServerPacket {
public int chunkX;
@ -15,8 +17,8 @@ public class UpdateLightPacket implements ServerPacket {
public int emptySkyLightMask;
public int emptyBlockLightMask;
public byte[] skyLight;
public byte[] blockLight;
public List<byte[]> skyLight;
public List<byte[]> blockLight;
@Override
public void write(PacketWriter writer) {
@ -29,10 +31,17 @@ public class UpdateLightPacket implements ServerPacket {
writer.writeVarInt(emptySkyLightMask);
writer.writeVarInt(emptyBlockLightMask);
writer.writeVarInt(2048); // Always 2048 length
writer.writeBytes(skyLight);
writer.writeVarInt(2048); // Always 2048 length
writer.writeBytes(blockLight);
//writer.writeVarInt(skyLight.size());
for (byte[] bytes : skyLight) {
writer.writeVarInt(2048); // Always 2048 length
writer.writeBytes(bytes);
}
//writer.writeVarInt(blockLight.size());
for (byte[] bytes : blockLight) {
writer.writeVarInt(2048); // Always 2048 length
writer.writeBytes(bytes);
}
}
@Override

View File

@ -2,9 +2,14 @@ package net.minestom.server.network.player;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import lombok.Getter;
import net.minestom.server.extras.mojangAuth.Decrypter;
import net.minestom.server.extras.mojangAuth.Encrypter;
import net.minestom.server.extras.mojangAuth.MojangCrypt;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.utils.PacketUtils;
import javax.crypto.SecretKey;
import java.net.SocketAddress;
/**
@ -13,49 +18,72 @@ import java.net.SocketAddress;
*/
public class NettyPlayerConnection extends PlayerConnection {
private ChannelHandlerContext channel;
private ChannelHandlerContext channel;
@Getter
private boolean encrypted = false;
public NettyPlayerConnection(ChannelHandlerContext channel) {
super();
this.channel = channel;
}
@Override
public void sendPacket(ByteBuf buffer) {
buffer.retain();
getChannel().writeAndFlush(buffer);
}
public NettyPlayerConnection(ChannelHandlerContext channel) {
super();
this.channel = channel;
}
@Override
public void writePacket(ByteBuf buffer) {
buffer.retain();
getChannel().write(buffer);
}
public void setEncryptionKey(SecretKey secretKey) {
this.encrypted = true;
getChannel().pipeline().addBefore("decoder", "decrypt", new Decrypter(MojangCrypt.getCipher(2, secretKey)));
getChannel().pipeline().addBefore("encoder", "encrypt", new Encrypter(MojangCrypt.getCipher(1, secretKey)));
}
@Override
public void sendPacket(ServerPacket serverPacket) {
ByteBuf buffer = PacketUtils.writePacket(serverPacket);
sendPacket(buffer);
buffer.release();
}
@Override
public void sendPacket(ByteBuf buffer) {
if (encrypted) {
buffer = buffer.copy();
buffer.retain();
getChannel().writeAndFlush(buffer);
buffer.release();
} else {
buffer.retain();
getChannel().writeAndFlush(buffer);
}
}
@Override
public void flush() {
getChannel().flush();
}
@Override
public void writePacket(ByteBuf buffer) {
if (encrypted) {
buffer = buffer.copy();
buffer.retain();
getChannel().write(buffer);
buffer.release();
} else {
buffer.retain();
getChannel().write(buffer);
}
}
@Override
public SocketAddress getRemoteAddress() {
return getChannel().channel().remoteAddress();
}
@Override
public void sendPacket(ServerPacket serverPacket) {
ByteBuf buffer = PacketUtils.writePacket(serverPacket);
sendPacket(buffer);
buffer.release();
}
@Override
public void disconnect() {
getChannel().close();
}
@Override
public void flush() {
getChannel().flush();
}
public ChannelHandlerContext getChannel() {
return channel;
}
@Override
public SocketAddress getRemoteAddress() {
return getChannel().channel().remoteAddress();
}
@Override
public void disconnect() {
getChannel().close();
}
public ChannelHandlerContext getChannel() {
return channel;
}
}

View File

@ -1,6 +1,8 @@
package net.minestom.server.network.player;
import io.netty.buffer.ByteBuf;
import lombok.Getter;
import lombok.Setter;
import net.minestom.server.entity.Player;
import net.minestom.server.network.ConnectionState;
import net.minestom.server.network.packet.server.ServerPacket;
@ -14,6 +16,10 @@ import java.net.SocketAddress;
public abstract class PlayerConnection {
private Player player;
//Could be null. Only used for Mojang Auth
@Getter @Setter private String loginUsername;
//Could be null. Only used for Mojang Auth
@Getter @Setter private byte[] nonce = new byte[4];
private ConnectionState connectionState;
private boolean online;