geforkt von Mirrors/Velocity
Online-mode and encryption support
Dieser Commit ist enthalten in:
Ursprung
359d1ea17c
Commit
fc5b0d3577
@ -1,6 +1,10 @@
|
|||||||
package com.velocitypowered.proxy;
|
package com.velocitypowered.proxy;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
|
import com.velocitypowered.proxy.connection.http.NettyHttpClient;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils;
|
||||||
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
||||||
@ -11,16 +15,22 @@ import io.netty.channel.*;
|
|||||||
import io.netty.channel.nio.NioEventLoopGroup;
|
import io.netty.channel.nio.NioEventLoopGroup;
|
||||||
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
import io.netty.channel.socket.nio.NioServerSocketChannel;
|
||||||
import io.netty.channel.socket.nio.NioSocketChannel;
|
import io.netty.channel.socket.nio.NioSocketChannel;
|
||||||
|
import net.kyori.text.Component;
|
||||||
|
import net.kyori.text.serializer.GsonComponentSerializer;
|
||||||
|
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
public class VelocityServer {
|
public class VelocityServer {
|
||||||
|
public static final Gson GSON = new GsonBuilder()
|
||||||
|
.registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
|
||||||
|
.create();
|
||||||
private static VelocityServer server;
|
private static VelocityServer server;
|
||||||
|
|
||||||
private EventLoopGroup bossGroup;
|
private EventLoopGroup bossGroup;
|
||||||
private EventLoopGroup childGroup;
|
private EventLoopGroup childGroup;
|
||||||
|
private NettyHttpClient httpClient;
|
||||||
private KeyPair serverKeyPair;
|
private KeyPair serverKeyPair;
|
||||||
|
|
||||||
public VelocityServer() {
|
public VelocityServer() {
|
||||||
@ -46,12 +56,15 @@ public class VelocityServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Start the listener
|
// Start the listener
|
||||||
bossGroup = new NioEventLoopGroup();
|
bossGroup = new NioEventLoopGroup(0, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Netty Boss Thread").build());
|
||||||
childGroup = new NioEventLoopGroup();
|
childGroup = new NioEventLoopGroup(0, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Netty I/O Thread #%d").build());
|
||||||
|
httpClient = new NettyHttpClient(this);
|
||||||
server = this;
|
server = this;
|
||||||
new ServerBootstrap()
|
new ServerBootstrap()
|
||||||
.channel(NioServerSocketChannel.class)
|
.channel(NioServerSocketChannel.class)
|
||||||
.group(bossGroup, childGroup)
|
.group(bossGroup, childGroup)
|
||||||
|
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||||
|
.childOption(ChannelOption.IP_TOS, 0x18)
|
||||||
.childHandler(new ChannelInitializer<Channel>() {
|
.childHandler(new ChannelInitializer<Channel>() {
|
||||||
@Override
|
@Override
|
||||||
protected void initChannel(Channel ch) throws Exception {
|
protected void initChannel(Channel ch) throws Exception {
|
||||||
@ -82,4 +95,8 @@ public class VelocityServer {
|
|||||||
.channel(NioSocketChannel.class)
|
.channel(NioSocketChannel.class)
|
||||||
.group(childGroup);
|
.group(childGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public NettyHttpClient getHttpClient() {
|
||||||
|
return httpClient;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
package com.velocitypowered.proxy.connection;
|
package com.velocitypowered.proxy.connection;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.proxy.Velocity;
|
||||||
import com.velocitypowered.proxy.protocol.PacketWrapper;
|
import com.velocitypowered.proxy.protocol.PacketWrapper;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.compression.JavaVelocityCompressor;
|
import com.velocitypowered.proxy.protocol.compression.JavaVelocityCompressor;
|
||||||
|
import com.velocitypowered.proxy.protocol.encryption.JavaVelocityCipher;
|
||||||
|
import com.velocitypowered.proxy.protocol.encryption.VelocityCipher;
|
||||||
import com.velocitypowered.proxy.protocol.netty.*;
|
import com.velocitypowered.proxy.protocol.netty.*;
|
||||||
import com.velocitypowered.proxy.protocol.packets.SetCompression;
|
import com.velocitypowered.proxy.protocol.packets.SetCompression;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
@ -12,8 +15,12 @@ import io.netty.channel.ChannelHandlerContext;
|
|||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
|
||||||
import static com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils.MINECRAFT_DECODER;
|
import javax.crypto.SecretKey;
|
||||||
import static com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils.MINECRAFT_ENCODER;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
|
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
import static com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft
|
* A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft
|
||||||
@ -156,4 +163,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
|||||||
channel.pipeline().addBefore(MINECRAFT_DECODER, "compress-decoder", decoder);
|
channel.pipeline().addBefore(MINECRAFT_DECODER, "compress-decoder", decoder);
|
||||||
channel.pipeline().addBefore(MINECRAFT_ENCODER, "compress-encoder", encoder);
|
channel.pipeline().addBefore(MINECRAFT_ENCODER, "compress-encoder", encoder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void enableEncryption(byte[] secret) throws GeneralSecurityException {
|
||||||
|
SecretKey key = new SecretKeySpec(secret, "AES");
|
||||||
|
|
||||||
|
VelocityCipher decryptionCipher = new JavaVelocityCipher(false, key);
|
||||||
|
VelocityCipher encryptionCipher = new JavaVelocityCipher(true, key);
|
||||||
|
channel.pipeline().addBefore(FRAME_DECODER, "cipher-decoder", new MinecraftCipherDecoder(decryptionCipher));
|
||||||
|
channel.pipeline().addBefore(FRAME_ENCODER, "cipher-encoder", new MinecraftCipherEncoder(encryptionCipher));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
|||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
public interface MinecraftSessionHandler {
|
public interface MinecraftSessionHandler {
|
||||||
void handle(MinecraftPacket packet);
|
void handle(MinecraftPacket packet) throws Exception;
|
||||||
|
|
||||||
default void handleUnknown(ByteBuf buf) {
|
default void handleUnknown(ByteBuf buf) {
|
||||||
// No-op: we'll release the buffer later.
|
// No-op: we'll release the buffer later.
|
||||||
|
@ -8,7 +8,6 @@ import com.velocitypowered.proxy.data.ServerInfo;
|
|||||||
import com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils;
|
import com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.util.UuidUtils;
|
|
||||||
import io.netty.channel.*;
|
import io.netty.channel.*;
|
||||||
|
|
||||||
public class ServerConnection {
|
public class ServerConnection {
|
||||||
@ -56,12 +55,10 @@ public class ServerConnection {
|
|||||||
// BungeeCord IP forwarding is simply a special injection after the "address" in the handshake,
|
// BungeeCord IP forwarding is simply a special injection after the "address" in the handshake,
|
||||||
// separated by \0 (the null byte). In order, you send the original host, the player's IP, their
|
// separated by \0 (the null byte). In order, you send the original host, the player's IP, their
|
||||||
// UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang).
|
// UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang).
|
||||||
//
|
|
||||||
// Velocity doesn't yet support online-mode, unfortunately. That will come soon.
|
|
||||||
return serverInfo.getAddress().getHostString() + "\0" +
|
return serverInfo.getAddress().getHostString() + "\0" +
|
||||||
proxyPlayer.getRemoteAddress().getHostString() + "\0" +
|
proxyPlayer.getRemoteAddress().getHostString() + "\0" +
|
||||||
UuidUtils.toUndashed(proxyPlayer.getUniqueId()) + "\0" +
|
proxyPlayer.getProfile().getId() + "\0" +
|
||||||
"[]";
|
VelocityServer.GSON.toJson(proxyPlayer.getProfile().getProperties());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void startHandshake() {
|
private void startHandshake() {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.velocitypowered.proxy.connection.client;
|
package com.velocitypowered.proxy.connection.client;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.data.GameProfile;
|
||||||
import com.velocitypowered.proxy.protocol.packets.Chat;
|
import com.velocitypowered.proxy.protocol.packets.Chat;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.backend.ServerConnection;
|
import com.velocitypowered.proxy.connection.backend.ServerConnection;
|
||||||
@ -14,23 +15,25 @@ import java.net.InetSocketAddress;
|
|||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class ConnectedPlayer {
|
public class ConnectedPlayer {
|
||||||
private final String username;
|
private final GameProfile profile;
|
||||||
private final UUID uniqueId;
|
|
||||||
private final MinecraftConnection connection;
|
private final MinecraftConnection connection;
|
||||||
private ServerConnection connectedServer;
|
private ServerConnection connectedServer;
|
||||||
|
|
||||||
public ConnectedPlayer(String username, UUID uniqueId, MinecraftConnection connection) {
|
public ConnectedPlayer(GameProfile profile, MinecraftConnection connection) {
|
||||||
this.username = username;
|
this.profile = profile;
|
||||||
this.uniqueId = uniqueId;
|
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return profile.getName();
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID getUniqueId() {
|
public UUID getUniqueId() {
|
||||||
return uniqueId;
|
return profile.idAsUuid();
|
||||||
|
}
|
||||||
|
|
||||||
|
public GameProfile getProfile() {
|
||||||
|
return profile;
|
||||||
}
|
}
|
||||||
|
|
||||||
public MinecraftConnection getConnection() {
|
public MinecraftConnection getConnection() {
|
||||||
|
@ -1,9 +1,11 @@
|
|||||||
package com.velocitypowered.proxy.connection.client;
|
package com.velocitypowered.proxy.connection.client;
|
||||||
|
|
||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.proxy.data.GameProfile;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.packets.EncryptionRequest;
|
import com.velocitypowered.proxy.protocol.packets.EncryptionRequest;
|
||||||
|
import com.velocitypowered.proxy.protocol.packets.EncryptionResponse;
|
||||||
import com.velocitypowered.proxy.protocol.packets.ServerLogin;
|
import com.velocitypowered.proxy.protocol.packets.ServerLogin;
|
||||||
import com.velocitypowered.proxy.protocol.packets.ServerLoginSuccess;
|
import com.velocitypowered.proxy.protocol.packets.ServerLoginSuccess;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
@ -11,25 +13,72 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
|||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.backend.ServerConnection;
|
import com.velocitypowered.proxy.connection.backend.ServerConnection;
|
||||||
import com.velocitypowered.proxy.data.ServerInfo;
|
import com.velocitypowered.proxy.data.ServerInfo;
|
||||||
|
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||||
import com.velocitypowered.proxy.util.UuidUtils;
|
import com.velocitypowered.proxy.util.UuidUtils;
|
||||||
|
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
|
|
||||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||||
|
private static final String MOJANG_SERVER_AUTH_URL =
|
||||||
|
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s";
|
||||||
|
|
||||||
private final MinecraftConnection inbound;
|
private final MinecraftConnection inbound;
|
||||||
private ServerLogin login;
|
private ServerLogin login;
|
||||||
|
private byte[] verify;
|
||||||
|
|
||||||
public LoginSessionHandler(MinecraftConnection inbound) {
|
public LoginSessionHandler(MinecraftConnection inbound) {
|
||||||
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handle(MinecraftPacket packet) {
|
public void handle(MinecraftPacket packet) throws Exception {
|
||||||
if (packet instanceof ServerLogin) {
|
if (packet instanceof ServerLogin) {
|
||||||
this.login = (ServerLogin) packet;
|
this.login = (ServerLogin) packet;
|
||||||
|
|
||||||
|
// Request encryption.
|
||||||
|
EncryptionRequest request = generateRequest();
|
||||||
|
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||||
|
inbound.write(request);
|
||||||
|
|
||||||
// TODO: Online-mode checks
|
// TODO: Online-mode checks
|
||||||
handleSuccessfulLogin();
|
//handleSuccessfulLogin();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (packet instanceof EncryptionResponse) {
|
||||||
|
KeyPair serverKeyPair = VelocityServer.getServer().getServerKeyPair();
|
||||||
|
EncryptionResponse response = (EncryptionResponse) packet;
|
||||||
|
byte[] decryptedVerifyToken = EncryptionUtils.decryptRsa(serverKeyPair, response.getVerifyToken());
|
||||||
|
if (!Arrays.equals(verify, decryptedVerifyToken)) {
|
||||||
|
throw new IllegalStateException("Unable to successfully decrypt the verification token.");
|
||||||
|
}
|
||||||
|
|
||||||
|
byte[] decryptedSharedSecret = EncryptionUtils.decryptRsa(serverKeyPair, response.getSharedSecret());
|
||||||
|
String serverId = EncryptionUtils.generateServerId(decryptedSharedSecret, serverKeyPair.getPublic());
|
||||||
|
|
||||||
|
String playerIp = ((InetSocketAddress) inbound.getChannel().remoteAddress()).getHostString();
|
||||||
|
VelocityServer.getServer().getHttpClient()
|
||||||
|
.get(new URL(String.format(MOJANG_SERVER_AUTH_URL, login.getUsername(), serverId, playerIp)))
|
||||||
|
.thenAccept(profileResponse -> {
|
||||||
|
try {
|
||||||
|
inbound.enableEncryption(decryptedSharedSecret);
|
||||||
|
} catch (GeneralSecurityException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
GameProfile profile = VelocityServer.GSON.fromJson(profileResponse, GameProfile.class);
|
||||||
|
handleSuccessfulLogin(profile);
|
||||||
|
})
|
||||||
|
.exceptionally(exception -> {
|
||||||
|
System.out.println("Can't enable encryption");
|
||||||
|
exception.printStackTrace();
|
||||||
|
inbound.close();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,17 +92,16 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
|||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSuccessfulLogin() {
|
private void handleSuccessfulLogin(GameProfile profile) {
|
||||||
inbound.setCompressionThreshold(256);
|
inbound.setCompressionThreshold(256);
|
||||||
|
|
||||||
String username = login.getUsername();
|
|
||||||
ServerLoginSuccess success = new ServerLoginSuccess();
|
ServerLoginSuccess success = new ServerLoginSuccess();
|
||||||
success.setUsername(username);
|
success.setUsername(profile.getName());
|
||||||
success.setUuid(UuidUtils.generateOfflinePlayerUuid(username));
|
success.setUuid(profile.idAsUuid());
|
||||||
inbound.write(success);
|
inbound.write(success);
|
||||||
|
|
||||||
// Initiate a regular connection and move over to it.
|
// Initiate a regular connection and move over to it.
|
||||||
ConnectedPlayer player = new ConnectedPlayer(success.getUsername(), success.getUuid(), inbound);
|
ConnectedPlayer player = new ConnectedPlayer(profile, inbound);
|
||||||
ServerInfo info = new ServerInfo("test", new InetSocketAddress("localhost", 25565));
|
ServerInfo info = new ServerInfo("test", new InetSocketAddress("localhost", 25565));
|
||||||
ServerConnection connection = new ServerConnection(info, player, VelocityServer.getServer());
|
ServerConnection connection = new ServerConnection(info, player, VelocityServer.getServer());
|
||||||
|
|
||||||
|
@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection.client;
|
|||||||
import com.google.common.base.Preconditions;
|
import com.google.common.base.Preconditions;
|
||||||
import com.google.gson.Gson;
|
import com.google.gson.Gson;
|
||||||
import com.google.gson.GsonBuilder;
|
import com.google.gson.GsonBuilder;
|
||||||
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packets.Ping;
|
import com.velocitypowered.proxy.protocol.packets.Ping;
|
||||||
import com.velocitypowered.proxy.protocol.packets.StatusRequest;
|
import com.velocitypowered.proxy.protocol.packets.StatusRequest;
|
||||||
@ -15,9 +16,6 @@ import net.kyori.text.TextComponent;
|
|||||||
import net.kyori.text.serializer.GsonComponentSerializer;
|
import net.kyori.text.serializer.GsonComponentSerializer;
|
||||||
|
|
||||||
public class StatusSessionHandler implements MinecraftSessionHandler {
|
public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||||
private static final Gson GSON = new GsonBuilder()
|
|
||||||
.registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
|
|
||||||
.create();
|
|
||||||
private final MinecraftConnection connection;
|
private final MinecraftConnection connection;
|
||||||
|
|
||||||
public StatusSessionHandler(MinecraftConnection connection) {
|
public StatusSessionHandler(MinecraftConnection connection) {
|
||||||
@ -43,7 +41,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
|||||||
null
|
null
|
||||||
);
|
);
|
||||||
StatusResponse response = new StatusResponse();
|
StatusResponse response = new StatusResponse();
|
||||||
response.setStatus(GSON.toJson(ping));
|
response.setStatus(VelocityServer.GSON.toJson(ping));
|
||||||
connection.write(response);
|
connection.write(response);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,63 @@
|
|||||||
|
package com.velocitypowered.proxy.connection.http;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
|
import io.netty.channel.*;
|
||||||
|
import io.netty.handler.codec.http.*;
|
||||||
|
import io.netty.handler.ssl.SslContext;
|
||||||
|
import io.netty.handler.ssl.SslContextBuilder;
|
||||||
|
import io.netty.handler.ssl.SslHandler;
|
||||||
|
|
||||||
|
import javax.net.ssl.SSLEngine;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
public class NettyHttpClient {
|
||||||
|
private final VelocityServer server;
|
||||||
|
|
||||||
|
public NettyHttpClient(VelocityServer server) {
|
||||||
|
this.server = server;
|
||||||
|
}
|
||||||
|
|
||||||
|
public CompletableFuture<String> get(URL url) {
|
||||||
|
String host = url.getHost();
|
||||||
|
int port = url.getPort();
|
||||||
|
boolean ssl = url.getProtocol().equals("https");
|
||||||
|
if (port == -1) {
|
||||||
|
port = ssl ? 443 : 80;
|
||||||
|
}
|
||||||
|
|
||||||
|
CompletableFuture<String> reply = new CompletableFuture<>();
|
||||||
|
server.initializeGenericBootstrap()
|
||||||
|
.handler(new ChannelInitializer<Channel>() {
|
||||||
|
@Override
|
||||||
|
protected void initChannel(Channel ch) throws Exception {
|
||||||
|
if (ssl) {
|
||||||
|
SslContext context = SslContextBuilder.forClient().build();
|
||||||
|
SSLEngine engine = context.newEngine(ch.alloc());
|
||||||
|
ch.pipeline().addLast(new SslHandler(engine));
|
||||||
|
}
|
||||||
|
ch.pipeline().addLast(new HttpClientCodec());
|
||||||
|
ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
|
||||||
|
@Override
|
||||||
|
public void channelActive(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
DefaultFullHttpRequest request = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.GET, url.getPath() + "?" + url.getQuery());
|
||||||
|
request.headers().add(HttpHeaderNames.HOST, url.getHost());
|
||||||
|
request.headers().add(HttpHeaderNames.USER_AGENT, "Velocity");
|
||||||
|
ctx.writeAndFlush(request);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
ch.pipeline().addLast(new SimpleHttpResponseCollector(reply));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.connect(host, port)
|
||||||
|
.addListener(new ChannelFutureListener() {
|
||||||
|
@Override
|
||||||
|
public void operationComplete(ChannelFuture future) throws Exception {
|
||||||
|
if (!future.isSuccess()) {
|
||||||
|
reply.completeExceptionally(future.cause());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return reply;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,41 @@
|
|||||||
|
package com.velocitypowered.proxy.connection.http;
|
||||||
|
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import io.netty.handler.codec.http.HttpContent;
|
||||||
|
import io.netty.handler.codec.http.HttpResponse;
|
||||||
|
import io.netty.handler.codec.http.HttpResponseStatus;
|
||||||
|
import io.netty.handler.codec.http.LastHttpContent;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
class SimpleHttpResponseCollector extends ChannelInboundHandlerAdapter {
|
||||||
|
private final StringBuilder buffer = new StringBuilder(1024);
|
||||||
|
private final CompletableFuture<String> reply;
|
||||||
|
|
||||||
|
SimpleHttpResponseCollector(CompletableFuture<String> reply) {
|
||||||
|
this.reply = reply;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
|
||||||
|
if (msg instanceof HttpResponse) {
|
||||||
|
HttpResponseStatus status = ((HttpResponse) msg).status();
|
||||||
|
if (status != HttpResponseStatus.OK) {
|
||||||
|
ctx.close();
|
||||||
|
reply.completeExceptionally(new RuntimeException("Unexpected status code " + status.code()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (msg instanceof HttpContent) {
|
||||||
|
buffer.append(((HttpContent) msg).content().toString(StandardCharsets.UTF_8));
|
||||||
|
((HttpContent) msg).release();
|
||||||
|
|
||||||
|
if (msg instanceof LastHttpContent) {
|
||||||
|
ctx.close();
|
||||||
|
reply.complete(buffer.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
84
src/main/java/com/velocitypowered/proxy/data/GameProfile.java
Normale Datei
84
src/main/java/com/velocitypowered/proxy/data/GameProfile.java
Normale Datei
@ -0,0 +1,84 @@
|
|||||||
|
package com.velocitypowered.proxy.data;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.velocitypowered.proxy.util.UuidUtils;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class GameProfile {
|
||||||
|
private final String id;
|
||||||
|
private final String name;
|
||||||
|
private final List<Property> properties;
|
||||||
|
|
||||||
|
public GameProfile(String id, String name, List<Property> properties) {
|
||||||
|
this.id = id;
|
||||||
|
this.name = name;
|
||||||
|
this.properties = ImmutableList.copyOf(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public UUID idAsUuid() {
|
||||||
|
return UuidUtils.fromUndashed(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Property> getProperties() {
|
||||||
|
return ImmutableList.copyOf(properties);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static GameProfile forOfflinePlayer(String username) {
|
||||||
|
Preconditions.checkNotNull(username, "username");
|
||||||
|
String id = UuidUtils.toUndashed(UuidUtils.generateOfflinePlayerUuid(username));
|
||||||
|
return new GameProfile(id, username, ImmutableList.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "GameProfile{" +
|
||||||
|
"id='" + id + '\'' +
|
||||||
|
", name='" + name + '\'' +
|
||||||
|
", properties=" + properties +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Property {
|
||||||
|
private final String name;
|
||||||
|
private final String value;
|
||||||
|
private final String signature;
|
||||||
|
|
||||||
|
public Property(String name, String value, String signature) {
|
||||||
|
this.name = name;
|
||||||
|
this.value = value;
|
||||||
|
this.signature = signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getValue() {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getSignature() {
|
||||||
|
return signature;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "Property{" +
|
||||||
|
"name='" + name + '\'' +
|
||||||
|
", value='" + value + '\'' +
|
||||||
|
", signature='" + signature + '\'' +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
package com.velocitypowered.proxy.protocol.encryption;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import javax.crypto.ShortBufferException;
|
||||||
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
|
import java.security.GeneralSecurityException;
|
||||||
|
|
||||||
|
public class JavaVelocityCipher implements VelocityCipher {
|
||||||
|
private final Cipher cipher;
|
||||||
|
private boolean disposed = false;
|
||||||
|
|
||||||
|
public JavaVelocityCipher(boolean encrypt, SecretKey key) throws GeneralSecurityException {
|
||||||
|
this.cipher = Cipher.getInstance("AES/CFB8/NoPadding");
|
||||||
|
this.cipher.init(encrypt ? Cipher.ENCRYPT_MODE : Cipher.DECRYPT_MODE, key, new IvParameterSpec(key.getEncoded()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(ByteBuf source, ByteBuf destination) throws ShortBufferException {
|
||||||
|
ensureNotDisposed();
|
||||||
|
|
||||||
|
byte[] sourceAsBytes = new byte[source.readableBytes()];
|
||||||
|
source.readBytes(sourceAsBytes);
|
||||||
|
|
||||||
|
int outputSize = cipher.getOutputSize(sourceAsBytes.length);
|
||||||
|
byte[] destinationBytes = new byte[outputSize];
|
||||||
|
cipher.update(sourceAsBytes, 0, sourceAsBytes.length, destinationBytes);
|
||||||
|
destination.writeBytes(destinationBytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dispose() {
|
||||||
|
ensureNotDisposed();
|
||||||
|
disposed = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureNotDisposed() {
|
||||||
|
Preconditions.checkState(!disposed, "Object already disposed");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.velocitypowered.proxy.protocol.encryption;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.util.Disposable;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
import javax.crypto.ShortBufferException;
|
||||||
|
|
||||||
|
public interface VelocityCipher extends Disposable {
|
||||||
|
void process(ByteBuf source, ByteBuf destination) throws ShortBufferException;
|
||||||
|
}
|
@ -1,8 +0,0 @@
|
|||||||
package com.velocitypowered.proxy.protocol.encryption;
|
|
||||||
|
|
||||||
import com.velocitypowered.proxy.util.Disposable;
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
|
|
||||||
public interface VelocityEncryptor extends Disposable {
|
|
||||||
void process(ByteBuf source, ByteBuf destination);
|
|
||||||
}
|
|
@ -0,0 +1,29 @@
|
|||||||
|
package com.velocitypowered.proxy.protocol.netty;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.proxy.protocol.encryption.VelocityCipher;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class MinecraftCipherDecoder extends ByteToMessageDecoder {
|
||||||
|
private final VelocityCipher cipher;
|
||||||
|
|
||||||
|
public MinecraftCipherDecoder(VelocityCipher cipher) {
|
||||||
|
this.cipher = Preconditions.checkNotNull(cipher, "cipher");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||||
|
ByteBuf decrypted = ctx.alloc().buffer();
|
||||||
|
try {
|
||||||
|
cipher.process(in, decrypted);
|
||||||
|
out.add(decrypted);
|
||||||
|
} catch (Exception e) {
|
||||||
|
decrypted.release();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package com.velocitypowered.proxy.protocol.netty;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.velocitypowered.proxy.protocol.encryption.VelocityCipher;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.MessageToByteEncoder;
|
||||||
|
|
||||||
|
public class MinecraftCipherEncoder extends MessageToByteEncoder<ByteBuf> {
|
||||||
|
private final VelocityCipher cipher;
|
||||||
|
|
||||||
|
public MinecraftCipherEncoder(VelocityCipher cipher) {
|
||||||
|
this.cipher = Preconditions.checkNotNull(cipher, "cipher");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, ByteBuf msg, ByteBuf out) throws Exception {
|
||||||
|
cipher.process(msg, out);
|
||||||
|
}
|
||||||
|
}
|
@ -5,14 +5,40 @@ import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
|||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
public class EncryptionResponse implements MinecraftPacket {
|
public class EncryptionResponse implements MinecraftPacket {
|
||||||
private byte[] sharedSecret;
|
private byte[] sharedSecret;
|
||||||
private byte[] verifyToken;
|
private byte[] verifyToken;
|
||||||
|
|
||||||
|
public byte[] getSharedSecret() {
|
||||||
|
return sharedSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSharedSecret(byte[] sharedSecret) {
|
||||||
|
this.sharedSecret = sharedSecret;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getVerifyToken() {
|
||||||
|
return verifyToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setVerifyToken(byte[] verifyToken) {
|
||||||
|
this.verifyToken = verifyToken;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "EncryptionResponse{" +
|
||||||
|
"sharedSecret=" + Arrays.toString(sharedSecret) +
|
||||||
|
", verifyToken=" + Arrays.toString(verifyToken) +
|
||||||
|
'}';
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||||
this.sharedSecret = ProtocolUtils.readByteArray(buf, 256);
|
this.sharedSecret = ProtocolUtils.readByteArray(buf, 256);
|
||||||
this.verifyToken = ProtocolUtils.readByteArray(buf, 4);
|
this.verifyToken = ProtocolUtils.readByteArray(buf, 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,9 +1,28 @@
|
|||||||
package com.velocitypowered.proxy.util;
|
package com.velocitypowered.proxy.util;
|
||||||
|
|
||||||
|
import javax.crypto.Cipher;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
|
import java.security.*;
|
||||||
|
|
||||||
public enum EncryptionUtils { ;
|
public enum EncryptionUtils { ;
|
||||||
public static String twosComplementSha1Digest(byte[] digest) {
|
public static String twosComplementSha1Digest(byte[] digest) {
|
||||||
return new BigInteger(digest).toString(16);
|
return new BigInteger(digest).toString(16);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static byte[] decryptRsa(KeyPair keyPair, byte[] bytes) throws GeneralSecurityException {
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA");
|
||||||
|
cipher.init(Cipher.DECRYPT_MODE, keyPair.getPrivate());
|
||||||
|
return cipher.doFinal(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String generateServerId(byte[] sharedSecret, PublicKey key) {
|
||||||
|
try {
|
||||||
|
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||||
|
digest.update(sharedSecret);
|
||||||
|
digest.update(key.getEncoded());
|
||||||
|
return twosComplementSha1Digest(digest.digest());
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new AssertionError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren