Add StructureModifier for extracting the signature data in chat and login packets (#1742)

This commit is contained in:
games647 2022-07-26 19:29:34 +02:00 committed by GitHub
parent c3dc00de05
commit 11a8184c3e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 483 additions and 4 deletions

View File

@ -929,6 +929,40 @@ public abstract class AbstractStructure {
BukkitConverters.getWrappedPublicKeyDataConverter());
}
/**
* @return read/write structure for login encryption packets
*/
public StructureModifier<Either<byte[], WrappedSaltedSignature>> getLoginSignatures() {
return getEithers(Converters.passthrough(byte[].class), BukkitConverters.getWrappedSignatureConverter());
}
/**
* @return read/writer structure direct access to signature data like chat messages
*/
public StructureModifier<WrappedSaltedSignature> getSignatures() {
return structureModifier.withType(
MinecraftReflection.getSaltedSignatureClass(),
BukkitConverters.getWrappedSignatureConverter()
);
}
/**
* @param leftConverter converter for left values
* @param rightConverter converter for right values
* @return ProtocolLib's read/write structure for Mojang either structures
* @param <L> left data type after converting from NMS
* @param <R> right data type after converting from NMS
*/
public <L, R> StructureModifier<Either<L, R>> getEithers(EquivalentConverter<L> leftConverter,
EquivalentConverter<R> rightConverter) {
return structureModifier.withType(
com.mojang.datafixers.util.Either.class,
BukkitConverters.getEitherConverter(
leftConverter, rightConverter
)
);
}
/**
* Retrieve a read/write structure for the Map class.
* @param keyConverter Converter for map keys

View File

@ -34,10 +34,7 @@ import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.AbstractFuzzyMatcher;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.reflect.fuzzy.*;
import com.comphenix.protocol.wrappers.EnumWrappers;
import org.bukkit.Bukkit;
@ -1499,6 +1496,31 @@ public final class MinecraftReflection {
return getMinecraftClass("world.entity.player.ProfilePublicKey");
}
public static Class<?> getSaltedSignatureClass() {
try {
return getMinecraftClass("SaltedSignature");
} catch (RuntimeException runtimeException) {
Class<?> messageSigClass = getMinecraftClass("network.chat.MessageSignature", "MessageSignature");
FuzzyClassContract signatureContract = FuzzyClassContract.newBuilder().
constructor(FuzzyMethodContract.newBuilder().
parameterCount(2).
parameterSuperOf(Long.TYPE, 0).
parameterSuperOf(byte[].class, 1).
build()
).build();
FuzzyFieldContract fuzzyFieldContract = FuzzyFieldContract.newBuilder().
typeMatches(getMinecraftObjectMatcher().and(signatureContract)).
build();
Class<?> signatureClass = FuzzyReflection.fromClass(messageSigClass, true)
.getField(fuzzyFieldContract)
.getType();
return setMinecraftClass("SaltedSignature", signatureClass);
}
}
public static Class<?> getProfilePublicKeyDataClass() {
return getProfilePublicKeyClass().getClasses()[0];
}

View File

@ -16,6 +16,8 @@
*/
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.wrappers.Either.Left;
import com.comphenix.protocol.wrappers.Either.Right;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
import java.lang.ref.WeakReference;
import java.lang.reflect.*;
@ -407,6 +409,43 @@ public class BukkitConverters {
});
}
/**
* @param leftConverter convert the left value if available
* @param rightConverter convert the right value if available
* @return converter for Mojang either class
* @param <A> converted left type
* @param <B> converted right type
*/
public static <A, B> EquivalentConverter<Either<A, B>> getEitherConverter(EquivalentConverter<A> leftConverter,
EquivalentConverter<B> rightConverter) {
return ignoreNull(new EquivalentConverter<Either<A, B>>() {
@Override
public Object getGeneric(Either<A, B> specific) {
return specific.map(
left -> com.mojang.datafixers.util.Either.left(leftConverter.getGeneric(left)),
right -> com.mojang.datafixers.util.Either.right(rightConverter.getGeneric(right))
);
}
@Override
public Either<A, B> getSpecific(Object generic) {
com.mojang.datafixers.util.Either<A, B> mjEither = (com.mojang.datafixers.util.Either<A, B>) generic;
return mjEither.map(
left -> new Left<>(leftConverter.getSpecific(left)),
right -> new Right<>(rightConverter.getSpecific(right))
);
}
@Override
public Class<Either<A, B>> getSpecificType() {
Class<?> dummy = Either.class;
return (Class<Either<A, B>>) dummy;
}
});
}
/**
* Retrieve an equivalent converter for a set of generic items.
* @param <T> Element type
@ -564,6 +603,13 @@ public class BukkitConverters {
return ignoreNull(handle(WrappedProfileKeyData::getHandle, WrappedProfileKeyData::new, WrappedProfileKeyData.class));
}
/**
* @return converter for cryptographic signature data that are used in login and chat packets
*/
public static EquivalentConverter<WrappedSaltedSignature> getWrappedSignatureConverter() {
return ignoreNull(handle(WrappedSaltedSignature::getHandle, WrappedSaltedSignature::new, WrappedSaltedSignature.class));
}
/**
* Retrieve a converter for watchable objects and the respective wrapper.
* @return A watchable object converter.

View File

@ -0,0 +1,100 @@
package com.comphenix.protocol.wrappers;
import java.util.Optional;
import java.util.function.Function;
/**
* Represents a datatype where either left or right is present. The values are available with a xor semantic. So at
* most and at least one value will be available.
*
* @param <L> left data type
* @param <R> right data type
*/
public abstract class Either<L, R> {
public static class Left<L, R> extends Either<L, R> {
private final L value;
protected Left(L value) {
this.value = value;
}
@Override
public <T> T map(Function<L, T> leftConsumer, Function<R, T> rightConsumer) {
return leftConsumer.apply(value);
}
@Override
public Optional<L> left() {
return Optional.ofNullable(value);
}
@Override
public Optional<R> right() {
return Optional.empty();
}
}
public static class Right<L, R> extends Either<L, R> {
private final R value;
protected Right(R value) {
this.value = value;
}
@Override
public <T> T map(Function<L, T> leftConsumer, Function<R, T> rightConsumer) {
return rightConsumer.apply(value);
}
@Override
public Optional<L> left() {
return Optional.empty();
}
@Override
public Optional<R> right() {
return Optional.ofNullable(value);
}
}
/**
* @param leftConsumer transformer if the left value is present
* @param rightConsumer transformer if the right value is present
* @return result of applying the given functions to the left or right side
* @param <T> result data type of both transformers
*/
public abstract <T> T map(Function<L, T> leftConsumer, Function<R, T> rightConsumer);
/**
* @return left value if present
*/
public abstract Optional<L> left();
/**
* @return right value if present
*/
public abstract Optional<R> right();
/**
* @param value containing value
* @return either containing a left value
* @param <L> data type of the containing value
* @param <R> right data type
*/
public static <L, R> Either<L, R> left(L value) {
return new Left<>(value);
}
/**
* @param value containing value
* @return either containing a right value
* @param <L> left data type
* @param <R> data type of the containing value
*/
public static <L, R> Either<L, R> right(R value) {
return new Right<>(value);
}
}

View File

@ -0,0 +1,90 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.primitives.Longs;
/**
* Wrapper representing the signature data associated to signed data by the player. This includes signed chat messages
* and login encryption acknowledgments.
*/
public class WrappedSaltedSignature extends AbstractWrapper {
private static ConstructorAccessor CONSTRUCTOR;
private final StructureModifier<Object> modifier;
/**
* Construct a wrapper from a NMS handle
*
* @param handle NMS Signature object
*/
public WrappedSaltedSignature(Object handle) {
super(MinecraftReflection.getSaltedSignatureClass());
this.setHandle(handle);
this.modifier = new StructureModifier<>(MinecraftReflection.getSaltedSignatureClass()).withTarget(handle);
}
/**
* Construct a wrapper and NMS handle containing the given values
* @param salt salt/nonce for this signature
* @param signature binary cryptographic signature
*/
public WrappedSaltedSignature(long salt, byte[] signature) {
super(MinecraftReflection.getSaltedSignatureClass());
if (CONSTRUCTOR == null) {
CONSTRUCTOR = Accessors.getConstructorAccessor(
this.getHandleType(),
Long.TYPE, byte[].class);
}
this.setHandle(CONSTRUCTOR.invoke(salt, signature));
this.modifier = new StructureModifier<>(MinecraftReflection.getSaltedSignatureClass()).withTarget(this.handle);
}
/**
* @return if a cryptographic signature data is present
*/
public boolean isSigned() {
return getSignature().length > 0;
}
/**
* @return cryptographic salt/nonce
*/
public long getSalt() {
return (long) modifier.withType(Long.TYPE).read(0);
}
/**
* @param salt cryptographic salt/nonce
*/
public void setSalt(long salt) {
modifier.withType(Long.TYPE).write(0, salt);
}
/**
* @return binary signature data associated to the salt and message
*/
public byte[] getSignature() {
return modifier.<byte[]>withType(byte[].class).read(0);
}
/**
* @param signature binary signature data associated to the salt and message
*/
public void setSignature(byte[] signature) {
modifier.<byte[]>withType(byte[].class).write(0, signature);
}
/**
* @return the long salt represented in 8 bytes
*/
public byte[] getSaltBytes() {
return Longs.toByteArray(getSalt());
}
}

View File

@ -39,6 +39,7 @@ import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ComponentConverter;
import com.comphenix.protocol.wrappers.Either;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.EnumWrappers.EntityUseAction;
import com.comphenix.protocol.wrappers.EnumWrappers.Hand;
@ -52,6 +53,7 @@ import com.comphenix.protocol.wrappers.WrappedDataWatcher.Registry;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject;
import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedSaltedSignature;
import com.comphenix.protocol.wrappers.WrappedRegistry;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
@ -707,6 +709,46 @@ public class PacketContainerTest {
assertArrayEquals(components, back);
}
@Test
public void testLoginSignatureNonce() {
PacketContainer encryptionStart = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN);
encryptionStart.getByteArrays().write(0, new byte[]{1, 2, 3});
byte[] nonce = {4, 5, 6};
encryptionStart.getLoginSignatures().write(0, Either.left(nonce));
byte[] read = encryptionStart.getLoginSignatures().read(0).left().get();
assertArrayEquals(nonce, read);
}
@Test
public void testLoginSignatureSigned() {
PacketContainer encryptionStart = new PacketContainer(PacketType.Login.Client.ENCRYPTION_BEGIN);
encryptionStart.getByteArrays().write(0, new byte[]{1, 2, 3});
byte[] signature = new byte[512];
long salt = 124L;
encryptionStart.getLoginSignatures().write(0, Either.right(new WrappedSaltedSignature(salt, signature)));
WrappedSaltedSignature read = encryptionStart.getLoginSignatures().read(0).right().get();
assertEquals(salt, read.getSalt());
assertArrayEquals(signature, read.getSignature());
}
@Test
public void testSignedChatMessage() {
PacketContainer chatPacket = new PacketContainer(PacketType.Play.Client.CHAT);
byte[] signature = new byte[512];
long salt = 124L;
WrappedSaltedSignature wrappedSignature = new WrappedSaltedSignature(salt, signature);
chatPacket.getSignatures().write(0, wrappedSignature);
WrappedSaltedSignature read = chatPacket.getSignatures().read(0);
assertEquals(salt, read.getSalt());
assertArrayEquals(signature, read.getSignature());
}
private void assertPacketsEqual(PacketContainer constructed, PacketContainer cloned) {
StructureModifier<Object> firstMod = constructed.getModifier(), secondMod = cloned.getModifier();
assertEquals(firstMod.size(), secondMod.size());

View File

@ -17,6 +17,7 @@ import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
import net.minecraft.network.protocol.status.ServerPing;
import net.minecraft.network.syncher.DataWatcher;
import net.minecraft.server.network.PlayerConnection;
import net.minecraft.util.MinecraftEncryption;
import net.minecraft.world.level.ChunkCoordIntPair;
import net.minecraft.world.level.block.state.IBlockData;
import org.bukkit.Material;
@ -119,6 +120,11 @@ public class MinecraftReflectionTest {
assertEquals(DataWatcher.Item.class, MinecraftReflection.getDataWatcherItemClass());
}
@Test
public void testLoginSignature() {
assertEquals(MinecraftEncryption.b.class, MinecraftReflection.getSaltedSignatureClass());
}
@Test
public void testItemStacks() {
ItemStack stack = new ItemStack(Material.GOLDEN_SWORD);

View File

@ -5,6 +5,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.wrappers.Either.Left;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
@ -38,4 +40,19 @@ public class BukkitConvertersTest {
assertEquals(item.hasItemMeta(), back.hasItemMeta());
assertTrue(Bukkit.getItemFactory().equals(item.getItemMeta(), back.getItemMeta()));
}
@Test
public void testEither() {
Either<String, String> test = new Left<>("bla");
EquivalentConverter<Either<String, String>> converter = BukkitConverters.getEitherConverter(
Converters.passthrough(String.class), Converters.passthrough(String.class)
);
com.mojang.datafixers.util.Either<String, String> nmsEither = (com.mojang.datafixers.util.Either<String, String>) converter.getGeneric(test);
Either<String, String> wrapped = converter.getSpecific(nmsEither);
assertEquals(wrapped.left(), nmsEither.left());
assertEquals(wrapped.right(), nmsEither.right());
}
}

View File

@ -0,0 +1,35 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.wrappers.Either.Left;
import com.comphenix.protocol.wrappers.Either.Right;
import java.util.Optional;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
class EitherTest {
@Test
void testLeft() {
Left<String, ?> left = new Left<>("left");
assertEquals(left.left(), Optional.of("left"));
assertEquals(left.right(), Optional.empty());
String map = left.map(l -> l + "left", r -> r + "right");
assertEquals("leftleft", map);
}
@Test
void testRight() {
Right<?, String> right = new Right<>("right");
assertEquals(right.left(), Optional.empty());
assertEquals(right.right(), Optional.of("right"));
String map = right.map(l -> l + "left", r -> r + "right");
assertEquals("rightright", map);
}
}

View File

@ -0,0 +1,87 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.BukkitInitialization;
import java.util.concurrent.ThreadLocalRandom;
import net.minecraft.util.MinecraftEncryption;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class WrappedSaltedSignatureTest {
@BeforeAll
static void initializeBukkit() {
BukkitInitialization.initializeAll();
}
@Test
void testLoginSignature() {
long salt = ThreadLocalRandom.current().nextLong();
byte[] signature = new byte[512];
ThreadLocalRandom.current().nextBytes(signature);
// test key data conversion
WrappedSaltedSignature loginSignature = new WrappedSaltedSignature(salt, signature);
Object handle = loginSignature.getHandle();
MinecraftEncryption.b data = assertInstanceOf(MinecraftEncryption.b.class, handle);
assertTrue(data.a());
assertArrayEquals(signature, data.d());
assertEquals(salt, data.c());
// test key data unwrapping
WrappedSaltedSignature unwrapped = BukkitConverters.getWrappedSignatureConverter().getSpecific(data);
assertNotNull(unwrapped);
assertTrue(unwrapped.isSigned());
assertEquals(loginSignature.getSalt(), unwrapped.getSalt());
assertArrayEquals(loginSignature.getSignature(), unwrapped.getSignature());
assertArrayEquals(loginSignature.getSaltBytes(), unwrapped.getSaltBytes());
// test key data wrapping
Object wrappedData = BukkitConverters.getWrappedSignatureConverter().getGeneric(loginSignature);
MinecraftEncryption.b wrapped = assertInstanceOf(MinecraftEncryption.b.class, wrappedData);
assertTrue(wrapped.a());
assertEquals(loginSignature.getSalt(), wrapped.c());
assertArrayEquals(loginSignature.getSignature(), wrapped.d());
assertArrayEquals(loginSignature.getSaltBytes(), wrapped.b());
}
@Test
void testSignedMessageWithoutSignature() {
long salt = ThreadLocalRandom.current().nextLong();
byte[] signature = {};
// test key data conversion
WrappedSaltedSignature loginSignature = new WrappedSaltedSignature(salt, signature);
Object handle = loginSignature.getHandle();
MinecraftEncryption.b data = assertInstanceOf(MinecraftEncryption.b.class, handle);
assertFalse(data.a());
assertArrayEquals(signature, data.d());
assertEquals(salt, data.c());
// test key data unwrapping
WrappedSaltedSignature unwrapped = BukkitConverters.getWrappedSignatureConverter().getSpecific(data);
assertNotNull(unwrapped);
assertFalse(unwrapped.isSigned());
assertEquals(loginSignature.getSalt(), unwrapped.getSalt());
assertArrayEquals(loginSignature.getSignature(), unwrapped.getSignature());
assertArrayEquals(loginSignature.getSaltBytes(), unwrapped.getSaltBytes());
// test key data wrapping
Object wrappedData = BukkitConverters.getWrappedSignatureConverter().getGeneric(loginSignature);
MinecraftEncryption.b wrapped = assertInstanceOf(MinecraftEncryption.b.class, wrappedData);
assertFalse(wrapped.a());
assertEquals(loginSignature.getSalt(), wrapped.c());
assertArrayEquals(loginSignature.getSignature(), wrapped.d());
assertArrayEquals(loginSignature.getSaltBytes(), wrapped.b());
}
}