ProtocolLib/src/main/java/com/comphenix/protocol/wrappers/WrappedProfilePublicKey.java

233 lines
8.4 KiB
Java

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<Object> 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.<Instant>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.<Instant>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.
* <p>
* 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.<PublicKey>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.<PublicKey>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.<byte[]>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.<byte[]>withType(byte[].class).write(0, signature);
}
}
}