All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.utils.ClientChainData Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
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>>() { }.getType()); if (map.isEmpty() || !map.containsKey("chain") || map.get("chain").isEmpty()) return; List chains = map.get("chain"); // Validate keys try { xboxAuthed = verifyChain(chains); } catch (Exception e) { xboxAuthed = false; } for (String c : chains) { JsonObject chainMap = decodeToken(c); if (chainMap == null) continue; if (chainMap.has("extraData")) { JsonObject extra = chainMap.get("extraData").getAsJsonObject(); if (extra.has("displayName")) this.username = extra.get("displayName").getAsString(); if (extra.has("identity")) this.clientUUID = UUID.fromString(extra.get("identity").getAsString()); if (extra.has("XUID")) this.xuid = extra.get("XUID").getAsString(); } if (chainMap.has("identityPublicKey")) this.identityPublicKey = chainMap.get("identityPublicKey").getAsString(); } if (!xboxAuthed) { xuid = null; } } private boolean verifyChain(List chains) throws Exception { ECPublicKey lastKey = null; boolean mojangKeyVerified = false; Iterator iterator = chains.iterator(); while (iterator.hasNext()) { JWSObject jws = JWSObject.parse(iterator.next()); URI x5u = jws.getHeader().getX509CertURL(); if (x5u == null) { return false; } ECPublicKey expectedKey = generateKey(x5u.toString()); // First key is self-signed if (lastKey == null) { lastKey = expectedKey; } else if (!lastKey.equals(expectedKey)) { return false; } if (!verify(lastKey, jws)) { return false; } if (mojangKeyVerified) { return !iterator.hasNext(); } if (lastKey.equals(MOJANG_PUBLIC_KEY)) { mojangKeyVerified = true; } Map payload = jws.getPayload().toJSONObject(); Object base64key = payload.get("identityPublicKey"); if (!(base64key instanceof String)) { throw new RuntimeException("No key found"); } lastKey = generateKey((String) base64key); } return mojangKeyVerified; } private boolean verify(ECPublicKey key, JWSObject object) throws JOSEException { return object.verify(new ECDSAVerifier(key)); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy