package com.comphenix.protocol.wrappers; import com.comphenix.protocol.injector.BukkitUnwrapper; import com.comphenix.protocol.reflect.StructureModifier; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; import com.comphenix.protocol.reflect.accessors.FieldAccessor; import com.comphenix.protocol.utility.MinecraftReflection; import java.nio.charset.StandardCharsets; import java.security.PublicKey; import java.time.Instant; import java.util.Base64; import java.util.Base64.Encoder; import com.comphenix.protocol.utility.MinecraftVersion; import org.bukkit.entity.Player; import javax.annotation.Nullable; /** * A wrapper around the profile public key. * * @since 1.19 */ public class WrappedProfilePublicKey extends AbstractWrapper { private static ConstructorAccessor CONSTRUCTOR; private static FieldAccessor DATA_ACCESSOR; // lazy initialized when needed private static FieldAccessor PROFILE_KEY_ACCESSOR; /** * Constructs a new profile public key wrapper directly from a nms ProfilePublicKey object. * * @param handle the handle to create the wrapper from. */ public WrappedProfilePublicKey(Object handle) { super(MinecraftReflection.getProfilePublicKeyClass()); this.setHandle(handle); } /** * Constructs a new profile public key wrapper holding the given key data. * * @param keyData the data of the key. */ public WrappedProfilePublicKey(WrappedProfileKeyData keyData) { super(MinecraftReflection.getProfilePublicKeyClass()); if (CONSTRUCTOR == null) { CONSTRUCTOR = Accessors.getConstructorAccessor(this.getHandleType(), MinecraftReflection.getProfilePublicKeyDataClass()); } this.setHandle(CONSTRUCTOR.invoke(keyData.getHandle())); } /** * Retrieves the profile public key from the given player instance. * * @param player the player to get the key of. * @return a wrapper around the public key of the given player. */ @Nullable public static WrappedProfilePublicKey ofPlayer(Player player) { if(MinecraftVersion.FEATURE_PREVIEW_2.atOrAbove()) { WrappedRemoteChatSessionData data = WrappedRemoteChatSessionData.fromPlayer(player); return data == null ? null : new WrappedProfilePublicKey(data.getProfilePublicKey()); } FieldAccessor accessor = PROFILE_KEY_ACCESSOR; if (accessor == null) { accessor = Accessors.getFieldAccessor(MinecraftReflection.getEntityHumanClass(), MinecraftReflection.getProfilePublicKeyClass(), true); PROFILE_KEY_ACCESSOR = accessor; } Object nmsPlayer = BukkitUnwrapper.getInstance().unwrapItem(player); return new WrappedProfilePublicKey(accessor.get(nmsPlayer)); } /** * Get a wrapper around the key data stored in this public key. * * @return the data of this key. */ public WrappedProfileKeyData getKeyData() { if (DATA_ACCESSOR == null) { DATA_ACCESSOR = Accessors.getFieldAccessor(this.getHandleType(), MinecraftReflection.getProfilePublicKeyDataClass(), true); } return new WrappedProfileKeyData(DATA_ACCESSOR.get(this.getHandle())); } /** * Sets the data of this key. Note that changing the key data might lead to unexpected issues with the server, for * example chat message validation to fail. * * @param keyData the new data of this key. */ public void setKeyData(WrappedProfileKeyData keyData) { if (DATA_ACCESSOR == null) { DATA_ACCESSOR = Accessors.getFieldAccessor(this.getHandleType(), MinecraftReflection.getProfilePublicKeyDataClass(), true); } DATA_ACCESSOR.set(this.getHandle(), keyData.getHandle()); } /** * A wrapper around the data stored in a profile key. * * @since 1.19 */ public static class WrappedProfileKeyData extends AbstractWrapper { private static ConstructorAccessor CONSTRUCTOR; private static final Encoder MIME_ENCODER = Base64.getMimeEncoder(76, "\n".getBytes(StandardCharsets.UTF_8)); private final StructureModifier modifier; /** * Constructs a new key data instance directly from the given nms KeyData object. * * @param handle the handle to create the wrapper from. */ public WrappedProfileKeyData(Object handle) { super(MinecraftReflection.getProfilePublicKeyDataClass()); this.setHandle(handle); this.modifier = new StructureModifier<>(MinecraftReflection.getProfilePublicKeyDataClass()).withTarget(handle); } /** * Constructs a new data wrapper instance using the given parameters. * * @param expireTime the instant when the key data expires. * @param key the public key used to verify incoming data. * @param signature the signature of the public key. */ public WrappedProfileKeyData(Instant expireTime, PublicKey key, byte[] signature) { super(MinecraftReflection.getProfilePublicKeyDataClass()); if (CONSTRUCTOR == null) { CONSTRUCTOR = Accessors.getConstructorAccessor( this.getHandleType(), Instant.class, PublicKey.class, byte[].class); } this.setHandle(CONSTRUCTOR.invoke(expireTime, key, signature)); this.modifier = new StructureModifier<>(MinecraftReflection.getProfilePublicKeyDataClass()).withTarget(this.handle); } /** * Get the time instant when this key data expires. * * @return the time instant when this key data expires. */ public Instant getExpireTime() { return this.modifier.withType(Instant.class).read(0); } /** * Sets the time when this key data expires. * * @param expireTime the new time when this key data expires. */ public void setExpireTime(Instant expireTime) { this.modifier.withType(Instant.class).write(0, expireTime); } /** * Checks if this key data is expired. * * @return true if this key data is expired, false otherwise. */ public boolean isExpired() { return this.getExpireTime().isBefore(Instant.now()); } /** * Get the signed payload of this key data. The signature of this data must verify against the payload data of * the key. If it does not, the key data is considered unsigned. *

* Note that this method takes the expiry time of the key into accountability, if this key is expired it will no * longer match the key data signature. * * @return the signed payload version of this profile key data. */ public String getSignedPayload() { String rsaString = "-----BEGIN RSA PUBLIC KEY-----\n" + MIME_ENCODER.encodeToString(this.getKey().getEncoded()) + "\n-----END RSA PUBLIC KEY-----\n"; return this.getExpireTime().toEpochMilli() + rsaString; } /** * Get the public key of this key data. * * @return the public key of this key data. */ public PublicKey getKey() { return this.modifier.withType(PublicKey.class).read(0); } /** * Sets the public key of this key data. * * @param key the new public key of this key data. */ public void setKey(PublicKey key) { this.modifier.withType(PublicKey.class).write(0, key); } /** * Get the signature of this key data. * * @return the key data of this signature. * @see #getSignedPayload() */ public byte[] getSignature() { return this.modifier.withType(byte[].class).read(0); } /** * Sets the signature of this key data. * * @param signature the new signature of this key data. * @see #getSignedPayload() */ public void setSignature(byte[] signature) { this.modifier.withType(byte[].class).write(0, signature); } } }