Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-12-24 23:30:26 +01:00
Online-mode and encryption support
Dieser Commit ist enthalten in:
Ursprung
359d1ea17c
Commit
fc5b0d3577
@ -1,6 +1,10 @@
|
||||
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.http.NettyHttpClient;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils;
|
||||
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.socket.nio.NioServerSocketChannel;
|
||||
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.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
||||
public class VelocityServer {
|
||||
public static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
|
||||
.create();
|
||||
private static VelocityServer server;
|
||||
|
||||
private EventLoopGroup bossGroup;
|
||||
private EventLoopGroup childGroup;
|
||||
private NettyHttpClient httpClient;
|
||||
private KeyPair serverKeyPair;
|
||||
|
||||
public VelocityServer() {
|
||||
@ -46,12 +56,15 @@ public class VelocityServer {
|
||||
}
|
||||
|
||||
// Start the listener
|
||||
bossGroup = new NioEventLoopGroup();
|
||||
childGroup = new NioEventLoopGroup();
|
||||
bossGroup = new NioEventLoopGroup(0, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Netty Boss Thread").build());
|
||||
childGroup = new NioEventLoopGroup(0, new ThreadFactoryBuilder().setDaemon(true).setNameFormat("Netty I/O Thread #%d").build());
|
||||
httpClient = new NettyHttpClient(this);
|
||||
server = this;
|
||||
new ServerBootstrap()
|
||||
.channel(NioServerSocketChannel.class)
|
||||
.group(bossGroup, childGroup)
|
||||
.childOption(ChannelOption.TCP_NODELAY, true)
|
||||
.childOption(ChannelOption.IP_TOS, 0x18)
|
||||
.childHandler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
@ -82,4 +95,8 @@ public class VelocityServer {
|
||||
.channel(NioSocketChannel.class)
|
||||
.group(childGroup);
|
||||
}
|
||||
|
||||
public NettyHttpClient getHttpClient() {
|
||||
return httpClient;
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,12 @@
|
||||
package com.velocitypowered.proxy.connection;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.Velocity;
|
||||
import com.velocitypowered.proxy.protocol.PacketWrapper;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
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.packets.SetCompression;
|
||||
import io.netty.channel.Channel;
|
||||
@ -12,8 +15,12 @@ import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.protocol.netty.MinecraftPipelineUtils.MINECRAFT_ENCODER;
|
||||
import javax.crypto.SecretKey;
|
||||
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
|
||||
@ -156,4 +163,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
channel.pipeline().addBefore(MINECRAFT_DECODER, "compress-decoder", decoder);
|
||||
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;
|
||||
|
||||
public interface MinecraftSessionHandler {
|
||||
void handle(MinecraftPacket packet);
|
||||
void handle(MinecraftPacket packet) throws Exception;
|
||||
|
||||
default void handleUnknown(ByteBuf buf) {
|
||||
// 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.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.util.UuidUtils;
|
||||
import io.netty.channel.*;
|
||||
|
||||
public class ServerConnection {
|
||||
@ -56,12 +55,10 @@ public class ServerConnection {
|
||||
// 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
|
||||
// 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" +
|
||||
proxyPlayer.getRemoteAddress().getHostString() + "\0" +
|
||||
UuidUtils.toUndashed(proxyPlayer.getUniqueId()) + "\0" +
|
||||
"[]";
|
||||
proxyPlayer.getProfile().getId() + "\0" +
|
||||
VelocityServer.GSON.toJson(proxyPlayer.getProfile().getProperties());
|
||||
}
|
||||
|
||||
private void startHandshake() {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.proxy.data.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.packets.Chat;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.backend.ServerConnection;
|
||||
@ -14,23 +15,25 @@ import java.net.InetSocketAddress;
|
||||
import java.util.UUID;
|
||||
|
||||
public class ConnectedPlayer {
|
||||
private final String username;
|
||||
private final UUID uniqueId;
|
||||
private final GameProfile profile;
|
||||
private final MinecraftConnection connection;
|
||||
private ServerConnection connectedServer;
|
||||
|
||||
public ConnectedPlayer(String username, UUID uniqueId, MinecraftConnection connection) {
|
||||
this.username = username;
|
||||
this.uniqueId = uniqueId;
|
||||
public ConnectedPlayer(GameProfile profile, MinecraftConnection connection) {
|
||||
this.profile = profile;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
return profile.getName();
|
||||
}
|
||||
|
||||
public UUID getUniqueId() {
|
||||
return uniqueId;
|
||||
return profile.idAsUuid();
|
||||
}
|
||||
|
||||
public GameProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
public MinecraftConnection getConnection() {
|
||||
|
@ -1,9 +1,11 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.data.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
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.ServerLoginSuccess;
|
||||
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.connection.backend.ServerConnection;
|
||||
import com.velocitypowered.proxy.data.ServerInfo;
|
||||
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||
import com.velocitypowered.proxy.util.UuidUtils;
|
||||
|
||||
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;
|
||||
|
||||
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 ServerLogin login;
|
||||
private byte[] verify;
|
||||
|
||||
public LoginSessionHandler(MinecraftConnection inbound) {
|
||||
this.inbound = Preconditions.checkNotNull(inbound, "inbound");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
public void handle(MinecraftPacket packet) throws Exception {
|
||||
if (packet instanceof ServerLogin) {
|
||||
this.login = (ServerLogin) packet;
|
||||
|
||||
// Request encryption.
|
||||
EncryptionRequest request = generateRequest();
|
||||
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||
inbound.write(request);
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
||||
private void handleSuccessfulLogin() {
|
||||
private void handleSuccessfulLogin(GameProfile profile) {
|
||||
inbound.setCompressionThreshold(256);
|
||||
|
||||
String username = login.getUsername();
|
||||
ServerLoginSuccess success = new ServerLoginSuccess();
|
||||
success.setUsername(username);
|
||||
success.setUuid(UuidUtils.generateOfflinePlayerUuid(username));
|
||||
success.setUsername(profile.getName());
|
||||
success.setUuid(profile.idAsUuid());
|
||||
inbound.write(success);
|
||||
|
||||
// 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));
|
||||
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.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.packets.Ping;
|
||||
import com.velocitypowered.proxy.protocol.packets.StatusRequest;
|
||||
@ -15,9 +16,6 @@ import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.serializer.GsonComponentSerializer;
|
||||
|
||||
public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeHierarchyAdapter(Component.class, new GsonComponentSerializer())
|
||||
.create();
|
||||
private final MinecraftConnection connection;
|
||||
|
||||
public StatusSessionHandler(MinecraftConnection connection) {
|
||||
@ -43,7 +41,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
null
|
||||
);
|
||||
StatusResponse response = new StatusResponse();
|
||||
response.setStatus(GSON.toJson(ping));
|
||||
response.setStatus(VelocityServer.GSON.toJson(ping));
|
||||
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 io.netty.buffer.ByteBuf;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
public class EncryptionResponse implements MinecraftPacket {
|
||||
private byte[] sharedSecret;
|
||||
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
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.sharedSecret = ProtocolUtils.readByteArray(buf, 256);
|
||||
this.verifyToken = ProtocolUtils.readByteArray(buf, 4);
|
||||
this.verifyToken = ProtocolUtils.readByteArray(buf, 128);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1,9 +1,28 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import java.math.BigInteger;
|
||||
import java.security.*;
|
||||
|
||||
public enum EncryptionUtils { ;
|
||||
public static String twosComplementSha1Digest(byte[] digest) {
|
||||
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