cn.nukkit.utils.ClientChainData Maven / Gradle / Ivy
package cn.nukkit.utils;
import cn.nukkit.Server;
import cn.nukkit.network.protocol.LoginPacket;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.reflect.TypeToken;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JWSObject;
import com.nimbusds.jose.crypto.ECDSAVerifier;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.interfaces.ECPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.X509EncodedKeySpec;
import java.util.*;
/**
* ClientChainData is a container of chain data sent from clients.
*
* Device information such as client UUID, xuid and serverAddress, can be
* read from instances of this object.
*
* To get chain data, you can use player.getLoginChainData() or read(loginPacket)
*
* ===============
*
* @author boybook (Nukkit Project)
* ===============
*/
public final class ClientChainData implements LoginChainData {
private static final String MOJANG_PUBLIC_KEY_BASE64 =
"MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE8ELkixyLcwlZryUQcu1TvPOmI2B7vX83ndnWRUaXm74wFfa5f/lwQNTfrLVHa2PmenpGI6JhIMUJaWZrjmMj90NoKNFSNBuKdm8rYiXsfaz3K36x/1U26HpG0ZxK/V1V";
private static final PublicKey MOJANG_PUBLIC_KEY;
static {
try {
MOJANG_PUBLIC_KEY = generateKey(MOJANG_PUBLIC_KEY_BASE64);
} catch (InvalidKeySpecException | NoSuchAlgorithmException e) {
throw new AssertionError(e);
}
}
public static ClientChainData of(byte[] buffer) {
return new ClientChainData(buffer);
}
public static ClientChainData read(LoginPacket pk) {
return of(pk.getBuffer());
}
@Override
public String getUsername() {
return username;
}
@Override
public UUID getClientUUID() {
return clientUUID;
}
@Override
public String getIdentityPublicKey() {
return identityPublicKey;
}
@Override
public long getClientId() {
return clientId;
}
@Override
public String getServerAddress() {
return serverAddress;
}
@Override
public String getDeviceModel() {
return deviceModel;
}
@Override
public int getDeviceOS() {
return deviceOS;
}
@Override
public String getDeviceId() {
return deviceId;
}
@Override
public String getGameVersion() {
return gameVersion;
}
@Override
public int getGuiScale() {
return guiScale;
}
@Override
public String getLanguageCode() {
return languageCode;
}
@Override
public String getXUID() {
if (this.isWaterdog()) {
return waterdogXUID;
} else {
return xuid;
}
}
private boolean xboxAuthed;
@Override
public int getCurrentInputMode() {
return currentInputMode;
}
@Override
public int getDefaultInputMode() {
return defaultInputMode;
}
@Override
public String getCapeData() {
return capeData;
}
public final static int UI_PROFILE_CLASSIC = 0;
public final static int UI_PROFILE_POCKET = 1;
@Override
public int getUIProfile() {
return UIProfile;
}
@Override
public String getWaterdogXUID() {
return waterdogXUID;
}
@Override
public String getWaterdogIP() {
return waterdogIP;
}
@Override
public JsonObject getRawData() {
return rawData;
}
private boolean isWaterdog() {
if (waterdogXUID == null || Server.getInstance() == null) {
return false;
}
return Server.getInstance().isWaterdogCapable();
}
///////////////////////////////////////////////////////////////////////////
// Override
///////////////////////////////////////////////////////////////////////////
@Override
public boolean equals(Object obj) {
return obj instanceof ClientChainData && Objects.equals(bs, ((ClientChainData) obj).bs);
}
@Override
public int hashCode() {
return bs.hashCode();
}
///////////////////////////////////////////////////////////////////////////
// Internal
///////////////////////////////////////////////////////////////////////////
private String username;
private UUID clientUUID;
private String xuid;
private static ECPublicKey generateKey(String base64) throws NoSuchAlgorithmException, InvalidKeySpecException {
return (ECPublicKey) KeyFactory.getInstance("EC").generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(base64)));
}
private String identityPublicKey;
private long clientId;
private String serverAddress;
private String deviceModel;
private int deviceOS;
private String deviceId;
private String gameVersion;
private int guiScale;
private String languageCode;
private int currentInputMode;
private int defaultInputMode;
private String waterdogIP;
private String waterdogXUID;
private int UIProfile;
private String capeData;
private JsonObject rawData;
private BinaryStream bs = new BinaryStream();
private ClientChainData(byte[] buffer) {
bs.setBuffer(buffer, 0);
decodeChainData();
decodeSkinData();
}
@Override
public boolean isXboxAuthed() {
return xboxAuthed;
}
private void decodeSkinData() {
JsonObject skinToken = decodeToken(new String(bs.get(bs.getLInt())));
if (skinToken == null) return;
if (skinToken.has("ClientRandomId")) this.clientId = skinToken.get("ClientRandomId").getAsLong();
if (skinToken.has("ServerAddress")) this.serverAddress = skinToken.get("ServerAddress").getAsString();
if (skinToken.has("DeviceModel")) this.deviceModel = skinToken.get("DeviceModel").getAsString();
if (skinToken.has("DeviceOS")) this.deviceOS = skinToken.get("DeviceOS").getAsInt();
if (skinToken.has("DeviceId")) this.deviceId = skinToken.get("DeviceId").getAsString();
if (skinToken.has("GameVersion")) this.gameVersion = skinToken.get("GameVersion").getAsString();
if (skinToken.has("GuiScale")) this.guiScale = skinToken.get("GuiScale").getAsInt();
if (skinToken.has("LanguageCode")) this.languageCode = skinToken.get("LanguageCode").getAsString();
if (skinToken.has("CurrentInputMode")) this.currentInputMode = skinToken.get("CurrentInputMode").getAsInt();
if (skinToken.has("DefaultInputMode")) this.defaultInputMode = skinToken.get("DefaultInputMode").getAsInt();
if (skinToken.has("UIProfile")) this.UIProfile = skinToken.get("UIProfile").getAsInt();
if (skinToken.has("CapeData")) this.capeData = skinToken.get("CapeData").getAsString();
if (skinToken.has("Waterdog_IP")) this.waterdogIP = skinToken.get("Waterdog_IP").getAsString();
if (skinToken.has("Waterdog_XUID")) this.waterdogXUID = skinToken.get("Waterdog_XUID").getAsString();
if (this.isWaterdog()) {
xboxAuthed = true;
}
this.rawData = skinToken;
}
private JsonObject decodeToken(String token) {
String[] base = token.split("\\.");
if (base.length < 2) return null;
String json = new String(Base64.getDecoder().decode(base[1]), StandardCharsets.UTF_8);
//Server.getInstance().getLogger().debug(json);
return new Gson().fromJson(json, JsonObject.class);
}
private void decodeChainData() {
Map> map = new Gson().fromJson(new String(bs.get(bs.getLInt()), StandardCharsets.UTF_8),
new TypeToken