Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public void packetSent(Session session, Packet packet) {

if (this.targetState == ProtocolState.LOGIN) {
GameProfile profile = session.getFlag(MinecraftConstants.PROFILE_KEY);
session.send(new ServerboundHelloPacket(profile.getName(), null, null, null, profile.getId()));
session.send(new ServerboundHelloPacket(profile.getName(), null, profile.getId()));
} else {
session.send(new ServerboundStatusRequestPacket());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@
import net.kyori.adventure.text.Component;

import javax.crypto.SecretKey;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.*;
import java.util.Arrays;
import java.util.Random;
import java.util.UUID;
Expand All @@ -58,8 +55,9 @@ public class ServerListener extends SessionAdapter {
}
}

private byte[] verifyToken = new byte[4];
private final byte[] verifyToken = new byte[4];
private String username = "";
private ServerboundHelloPacket.ProfilePublicKeyData profilePublicKeyData;

private long lastPingTime = 0;
private int lastPingId = 0;
Expand Down Expand Up @@ -101,6 +99,7 @@ public void packetReceived(Session session, Packet packet) {
if (protocol.getState() == ProtocolState.LOGIN) {
if (packet instanceof ServerboundHelloPacket) {
this.username = ((ServerboundHelloPacket) packet).getUsername();
this.profilePublicKeyData = ((ServerboundHelloPacket) packet).getPublicKey();

if (session.getFlag(MinecraftConstants.VERIFY_USERS_KEY, true)) {
session.send(new ClientboundHelloPacket(SERVER_ID, KEY_PAIR.getPublic(), this.verifyToken));
Expand All @@ -110,9 +109,17 @@ public void packetReceived(Session session, Packet packet) {
} else if (packet instanceof ServerboundKeyPacket) {
ServerboundKeyPacket keyPacket = (ServerboundKeyPacket) packet;
PrivateKey privateKey = KEY_PAIR.getPrivate();
if (!Arrays.equals(this.verifyToken, keyPacket.getVerifyToken(privateKey))) {
session.disconnect("Invalid nonce!");
return;

if (this.profilePublicKeyData != null) {
if (!keyPacket.verifyWithSaltSignature(this.verifyToken, this.profilePublicKeyData)) {
session.disconnect("Invalid profile key setup!");
return;
}
} else {
if (!Arrays.equals(this.verifyToken, keyPacket.getVerifyToken(privateKey))) {
session.disconnect("Invalid nonce!");
return;
}
}

SecretKey key = keyPacket.getSecretKey(privateKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,7 @@
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.function.ObjIntConsumer;
import java.util.function.ToIntFunction;
import java.util.function.*;

@RequiredArgsConstructor
public class MinecraftCodecHelper extends BasePacketCodecHelper {
Expand All @@ -64,6 +63,24 @@ public class MinecraftCodecHelper extends BasePacketCodecHelper {

protected CompoundTag registry;

@Nullable
public <T> T readNullable(ByteBuf buf, Function<ByteBuf, T> ifPresent) {
if (buf.readBoolean()) {
return ifPresent.apply(buf);
} else {
return null;
}
}

public <T> void writeNullable(ByteBuf buf, @Nullable T value, BiConsumer<ByteBuf, T> ifPresent) {
if (value != null) {
buf.writeBoolean(true);
ifPresent.accept(buf, value);
} else {
buf.writeBoolean(false);
}
}

public UUID readUUID(ByteBuf buf) {
return new UUID(buf.readLong(), buf.readLong());
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.github.steveice10.mc.protocol.data.game;

public enum ChatFilterType {
PASS_THROUGH,
FULLY_FILTERED,
PARTIALLY_FILTERED;

public static final ChatFilterType[] VALUES = values();

public static ChatFilterType from(int id) {
return VALUES[id];
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.github.steveice10.mc.protocol.codec.MinecraftCodecHelper;
import com.github.steveice10.mc.protocol.codec.MinecraftPacket;
import com.github.steveice10.mc.protocol.data.game.BuiltinChatType;
import com.github.steveice10.mc.protocol.data.game.ChatFilterType;
import com.github.steveice10.mc.protocol.data.game.LastSeenMessage;
import io.netty.buffer.ByteBuf;
import lombok.AllArgsConstructor;
Expand All @@ -13,6 +14,7 @@

import java.io.IOException;
import java.util.ArrayList;
import java.util.BitSet;
import java.util.List;
import java.util.UUID;

Expand All @@ -29,6 +31,8 @@ public class ClientboundPlayerChatPacket implements MinecraftPacket {
private final long salt;
private final List<LastSeenMessage> lastSeenMessages;
private final @Nullable Component unsignedContent;
private final BitSet filterMask;
private final ChatFilterType filterType;
/**
* Is {@link BuiltinChatType} defined in the order sent by the server in the login packet.
*/
Expand Down Expand Up @@ -66,6 +70,13 @@ public ClientboundPlayerChatPacket(ByteBuf in, MinecraftCodecHelper helper) {
this.unsignedContent = null;
}

this.filterType = ChatFilterType.from(helper.readVarInt(in));
if (filterType == ChatFilterType.PARTIALLY_FILTERED) {
this.filterMask = BitSet.valueOf(helper.readLongArray(in));
} else {
this.filterMask = new BitSet(0);
}

this.chatType = helper.readVarInt(in);
this.name = helper.readComponent(in);
if (in.readBoolean()) {
Expand Down Expand Up @@ -112,6 +123,11 @@ public void serialize(ByteBuf out, MinecraftCodecHelper helper) throws IOExcepti
out.writeBoolean(false);
}

helper.writeVarInt(out, this.filterType.ordinal());
if (this.filterType == ChatFilterType.PARTIALLY_FILTERED) {
helper.writeLongArray(out, this.filterMask.toLongArray());
}

helper.writeVarInt(out, this.chatType);
helper.writeComponent(out, this.name);
if (this.targetName != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,57 @@
import lombok.Data;
import lombok.NonNull;
import lombok.With;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.Nullable;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.time.Instant;
import java.util.UUID;

@Data
@With
@AllArgsConstructor
public class ServerboundHelloPacket implements MinecraftPacket {
private final @NonNull String username;
private final @Nullable Long expiresAt;
private final @Nullable PublicKey publicKey;
private final byte @Nullable[] keySignature;
private final @Nullable ProfilePublicKeyData publicKey;
private final @Nullable UUID profileId;

public ServerboundHelloPacket(ByteBuf in, MinecraftCodecHelper helper) throws IOException {
this.username = helper.readString(in);
if (in.readBoolean()) {
this.publicKey = new ProfilePublicKeyData(in, helper);
} else {
this.publicKey = null;
}
this.profileId = helper.readNullable(in, helper::readUUID);
}

@Override
public void serialize(ByteBuf out, MinecraftCodecHelper helper) {
helper.writeString(out, this.username);
out.writeBoolean(this.publicKey != null);
if (this.publicKey != null) {
this.publicKey.serialize(out, helper);
}
helper.writeNullable(out, this.profileId, helper::writeUUID);
}

@Override
public boolean isPriority() {
return true;
}

// Likely temporary; will be moved to AuthLib when full public key support is developed
public static class ProfilePublicKeyData {
private final long expiresAt;
private final PublicKey publicKey;
private final byte[] keySignature;

public ProfilePublicKeyData(ByteBuf in, MinecraftCodecHelper helper) throws IOException {
this.expiresAt = in.readLong();
byte[] publicKey = helper.readByteArray(in);
this.keySignature = helper.readByteArray(in);
Expand All @@ -38,36 +67,32 @@ public ServerboundHelloPacket(ByteBuf in, MinecraftCodecHelper helper) throws IO
} catch (GeneralSecurityException e) {
throw new IOException("Could not decode public key.", e);
}
} else {
this.expiresAt = null;
this.publicKey = null;
this.keySignature = null;
}
if (in.readBoolean()) {
this.profileId = helper.readUUID(in);
} else {
this.profileId = null;

public ProfilePublicKeyData(long expiresAt, PublicKey publicKey, byte[] keySignature) {
this.expiresAt = expiresAt;
this.publicKey = publicKey;
this.keySignature = keySignature;
}
}

@Override
public void serialize(ByteBuf out, MinecraftCodecHelper helper) throws IOException {
helper.writeString(out, this.username);
out.writeBoolean(this.publicKey != null);
if (this.publicKey != null) {
private void serialize(ByteBuf out, MinecraftCodecHelper helper) {
out.writeLong(this.expiresAt);
byte[] encoded = this.publicKey.getEncoded();
helper.writeByteArray(out, encoded);
helper.writeByteArray(out, this.keySignature);
}
out.writeBoolean(this.profileId != null);
if (this.profileId != null) {
helper.writeUUID(out, this.profileId);

@Contract("-> new")
public Instant getExpiresAt() {
return Instant.ofEpochMilli(this.expiresAt);
}
}

@Override
public boolean isPriority() {
return true;
public PublicKey getPublicKey() {
return publicKey;
}

public byte[] getKeySignature() {
return keySignature;
}
}
}
Loading