mirror of
https://github.com/GeyserMC/MCProtocolLib.git
synced 2024-11-24 08:08:20 -05:00
Replace MCAuth with RK_01 MinecraftAuth (#795)
* Initial work on moving over mcauth * Initial work on importing MinecraftAuth * Make compile * Remove extra headers code * Switch to different http utils * Merge changes * Cleanup * Remove unused exceptions and constructors * Implement proxies * Fixup proxy stuff * Cleanup * Remove SR license header * Remove custom exceptions * Move auth into main module Auth has become so small that it's not worth keeping separate * Make ProxyInfo be part of network again * Fix indent * Allow null id and name in GameProfile * Fix remaining logs * Make texture checker more accurate * Fix spaces * Update dependencies * Remove usage of var * Use faster approach for reading raw uuids.
This commit is contained in:
parent
5624d7729a
commit
471e92ec6a
29 changed files with 908 additions and 135 deletions
|
@ -0,0 +1,53 @@
|
|||
package org.geysermc.mcprotocollib.auth.example;
|
||||
|
||||
import net.raphimc.minecraftauth.MinecraftAuth;
|
||||
import net.raphimc.minecraftauth.step.java.StepMCProfile;
|
||||
import net.raphimc.minecraftauth.step.java.StepMCToken;
|
||||
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
|
||||
import net.raphimc.minecraftauth.step.msa.StepCredentialsMsaCode;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.auth.SessionService;
|
||||
import org.geysermc.mcprotocollib.network.ProxyInfo;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
public class MinecraftAuthTest {
|
||||
private static final Logger log = LoggerFactory.getLogger(MinecraftAuthTest.class);
|
||||
private static final String EMAIL = "Username@mail.com";
|
||||
private static final String PASSWORD = "Password";
|
||||
private static final boolean REQUIRE_SECURE_TEXTURES = true;
|
||||
|
||||
private static final ProxyInfo PROXY = null;
|
||||
|
||||
public static void main(String[] args) {
|
||||
auth();
|
||||
}
|
||||
|
||||
private static void auth() {
|
||||
SessionService service = new SessionService();
|
||||
service.setProxy(PROXY);
|
||||
|
||||
StepFullJavaSession.FullJavaSession fullJavaSession;
|
||||
try {
|
||||
fullJavaSession = MinecraftAuth.JAVA_CREDENTIALS_LOGIN.getFromInput(
|
||||
MinecraftAuth.createHttpClient(),
|
||||
new StepCredentialsMsaCode.MsaCredentials(EMAIL, PASSWORD));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
StepMCProfile.MCProfile mcProfile = fullJavaSession.getMcProfile();
|
||||
StepMCToken.MCToken mcToken = mcProfile.getMcToken();
|
||||
GameProfile profile = new GameProfile(mcProfile.getId(), mcProfile.getName());
|
||||
try {
|
||||
service.fillProfileProperties(profile);
|
||||
|
||||
log.info("Selected Profile: {}", profile);
|
||||
log.info("Selected Profile Textures: {}", profile.getTextures(REQUIRE_SECURE_TEXTURES));
|
||||
log.info("Access Token: {}", mcToken.getAccessToken());
|
||||
log.info("Expire Time: {}", mcToken.getExpireTimeMs());
|
||||
} catch (Exception e) {
|
||||
log.error("Failed to get properties and textures of selected profile {}.", profile, e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,14 +1,16 @@
|
|||
package org.geysermc.mcprotocollib.protocol.example;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
||||
import com.github.steveice10.mc.auth.service.AuthenticationService;
|
||||
import com.github.steveice10.mc.auth.service.MojangAuthenticationService;
|
||||
import com.github.steveice10.mc.auth.service.SessionService;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import net.kyori.adventure.text.format.TextDecoration;
|
||||
import net.raphimc.minecraftauth.MinecraftAuth;
|
||||
import net.raphimc.minecraftauth.step.java.StepMCProfile;
|
||||
import net.raphimc.minecraftauth.step.java.StepMCToken;
|
||||
import net.raphimc.minecraftauth.step.java.session.StepFullJavaSession;
|
||||
import net.raphimc.minecraftauth.step.msa.StepCredentialsMsaCode;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.auth.SessionService;
|
||||
import org.geysermc.mcprotocollib.network.ProxyInfo;
|
||||
import org.geysermc.mcprotocollib.network.Server;
|
||||
import org.geysermc.mcprotocollib.network.Session;
|
||||
|
@ -36,7 +38,6 @@ import org.geysermc.mcprotocollib.protocol.packet.ingame.serverbound.Serverbound
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.net.Proxy;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.Instant;
|
||||
import java.util.ArrayList;
|
||||
|
@ -51,7 +52,7 @@ public class MinecraftProtocolTest {
|
|||
private static final String HOST = "127.0.0.1";
|
||||
private static final int PORT = 25565;
|
||||
private static final ProxyInfo PROXY = null;
|
||||
private static final Proxy AUTH_PROXY = Proxy.NO_PROXY;
|
||||
private static final ProxyInfo AUTH_PROXY = null;
|
||||
private static final String USERNAME = "Username";
|
||||
private static final String PASSWORD = "Password";
|
||||
|
||||
|
@ -177,19 +178,21 @@ public class MinecraftProtocolTest {
|
|||
private static void login() {
|
||||
MinecraftProtocol protocol;
|
||||
if (VERIFY_USERS) {
|
||||
StepFullJavaSession.FullJavaSession fullJavaSession;
|
||||
try {
|
||||
AuthenticationService authService = new MojangAuthenticationService();
|
||||
authService.setUsername(USERNAME);
|
||||
authService.setPassword(PASSWORD);
|
||||
authService.setProxy(AUTH_PROXY);
|
||||
authService.login();
|
||||
|
||||
protocol = new MinecraftProtocol(authService.getSelectedProfile(), authService.getAccessToken());
|
||||
log.info("Successfully authenticated user.");
|
||||
} catch (RequestException e) {
|
||||
log.error("Failed to authenticate user.", e);
|
||||
return;
|
||||
fullJavaSession = MinecraftAuth.JAVA_CREDENTIALS_LOGIN.getFromInput(
|
||||
MinecraftAuth.createHttpClient(),
|
||||
new StepCredentialsMsaCode.MsaCredentials(USERNAME, PASSWORD));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
StepMCProfile.MCProfile mcProfile = fullJavaSession.getMcProfile();
|
||||
StepMCToken.MCToken mcToken = mcProfile.getMcToken();
|
||||
protocol = new MinecraftProtocol(
|
||||
new GameProfile(mcProfile.getId(), mcProfile.getName()),
|
||||
mcToken.getAccessToken());
|
||||
log.info("Successfully authenticated user.");
|
||||
} else {
|
||||
protocol = new MinecraftProtocol(USERNAME);
|
||||
}
|
||||
|
|
|
@ -4,12 +4,13 @@ metadata.format.version = "1.1"
|
|||
|
||||
adventure = "4.15.0"
|
||||
cloudburstnbt = "3.0.0.Final"
|
||||
mcauthlib = "e5b0bcc"
|
||||
slf4j = "2.0.9"
|
||||
math = "2.0"
|
||||
fastutil-maps = "8.5.3"
|
||||
netty = "4.1.103.Final"
|
||||
netty-io_uring = "0.0.24.Final"
|
||||
gson = "2.11.0"
|
||||
minecraftauth = "4.0.2"
|
||||
checkerframework = "3.42.0"
|
||||
junit = "5.8.2"
|
||||
|
||||
|
@ -24,7 +25,6 @@ adventure-text-serializer-gson = { module = "net.kyori:adventure-text-serializer
|
|||
adventure-text-serializer-json-legacy-impl = { module = "net.kyori:adventure-text-serializer-json-legacy-impl", version.ref = "adventure" }
|
||||
|
||||
cloudburstnbt = { module = "org.cloudburstmc:nbt", version.ref = "cloudburstnbt" }
|
||||
mcauthlib = { module = "com.github.GeyserMC:mcauthlib", version.ref = "mcauthlib" }
|
||||
|
||||
slf4j-api = { module = "org.slf4j:slf4j-api", version.ref = "slf4j" }
|
||||
slf4j-simple = { module = "org.slf4j:slf4j-simple", version.ref = "slf4j" }
|
||||
|
@ -39,6 +39,10 @@ fastutil-int2int-maps = { module = "com.nukkitx.fastutil:fastutil-int-int-maps",
|
|||
netty-all = { module = "io.netty:netty-all", version.ref = "netty" }
|
||||
netty-incubator-transport-native-io_uring = { module = "io.netty.incubator:netty-incubator-transport-native-io_uring", version.ref = "netty-io_uring" }
|
||||
|
||||
gson = { module = "com.google.code.gson:gson", version.ref = "gson" }
|
||||
|
||||
minecraftauth = { module = "net.raphimc:MinecraftAuth", version.ref = "minecraftauth" }
|
||||
|
||||
checkerframework-qual = { module = "org.checkerframework:checker-qual", version.ref = "checkerframework" }
|
||||
|
||||
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit" }
|
||||
|
|
|
@ -9,7 +9,12 @@ description = "MCProtocolLib is a simple library for communicating with Minecraf
|
|||
dependencies {
|
||||
// Minecraft related libraries
|
||||
api(libs.cloudburstnbt)
|
||||
api(libs.mcauthlib)
|
||||
|
||||
// Gson
|
||||
api(libs.gson)
|
||||
|
||||
// MinecraftAuth for authentication
|
||||
api(libs.minecraftauth)
|
||||
|
||||
// Slf4j
|
||||
api(libs.slf4j.api)
|
||||
|
|
|
@ -0,0 +1,453 @@
|
|||
package org.geysermc.mcprotocollib.auth;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.geysermc.mcprotocollib.auth.util.TextureUrlChecker;
|
||||
import org.geysermc.mcprotocollib.auth.util.UndashedUUIDAdapter;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.PublicKey;
|
||||
import java.security.Signature;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Information about a user profile.
|
||||
*/
|
||||
public class GameProfile {
|
||||
private static final PublicKey SIGNATURE_KEY = loadSignatureKey();
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(UUID.class, new UndashedUUIDAdapter())
|
||||
.create();
|
||||
|
||||
private static PublicKey loadSignatureKey() {
|
||||
try (InputStream in = Objects.requireNonNull(SessionService.class.getResourceAsStream("/yggdrasil_session_pubkey.der"))) {
|
||||
return KeyFactory.getInstance("RSA")
|
||||
.generatePublic(new X509EncodedKeySpec(in.readAllBytes()));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Missing/invalid yggdrasil public key.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private final UUID id;
|
||||
private final String name;
|
||||
|
||||
private List<Property> properties;
|
||||
private Map<TextureType, Texture> textures;
|
||||
private boolean texturesVerified;
|
||||
|
||||
/**
|
||||
* Creates a new GameProfile instance.
|
||||
*
|
||||
* @param id ID of the profile.
|
||||
* @param name Name of the profile.
|
||||
*/
|
||||
public GameProfile(String id, String name) {
|
||||
this(id == null || id.isEmpty() ? null : UUID.fromString(id), name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new GameProfile instance.
|
||||
*
|
||||
* @param id ID of the profile.
|
||||
* @param name Name of the profile.
|
||||
*/
|
||||
public GameProfile(UUID id, String name) {
|
||||
this.id = id;
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the profile is complete.
|
||||
*
|
||||
* @return Whether the profile is complete.
|
||||
*/
|
||||
public boolean isComplete() {
|
||||
return this.id != null && this.name != null && !this.name.isEmpty();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the profile.
|
||||
*
|
||||
* @return The profile's ID.
|
||||
*/
|
||||
public UUID getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the ID of the profile as a String.
|
||||
*
|
||||
* @return The profile's ID as a string.
|
||||
*/
|
||||
public String getIdAsString() {
|
||||
return this.id != null ? this.id.toString() : "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the profile.
|
||||
*
|
||||
* @return The profile's name.
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an immutable list of properties contained in the profile.
|
||||
*
|
||||
* @return The profile's properties.
|
||||
*/
|
||||
public List<Property> getProperties() {
|
||||
if (this.properties == null) {
|
||||
this.properties = new ArrayList<>();
|
||||
}
|
||||
|
||||
return Collections.unmodifiableList(this.properties);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the properties of this profile.
|
||||
*
|
||||
* @param properties Properties belonging to this profile.
|
||||
*/
|
||||
public void setProperties(List<Property> properties) {
|
||||
if (this.properties == null) {
|
||||
this.properties = new ArrayList<>();
|
||||
} else {
|
||||
this.properties.clear();
|
||||
}
|
||||
|
||||
if (properties != null) {
|
||||
this.properties.addAll(properties);
|
||||
}
|
||||
|
||||
// Invalidate cached decoded textures.
|
||||
this.textures = null;
|
||||
this.texturesVerified = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a property contained in the profile.
|
||||
*
|
||||
* @param name Name of the property.
|
||||
* @return The property with the specified name.
|
||||
*/
|
||||
public Property getProperty(String name) {
|
||||
for (Property property : this.getProperties()) {
|
||||
if (property.getName().equals(name)) {
|
||||
return property;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an immutable map of texture types to textures contained in the profile.
|
||||
*
|
||||
* @return The profile's textures.
|
||||
* @throws IllegalStateException If an error occurs decoding the profile's texture property.
|
||||
*/
|
||||
public Map<TextureType, Texture> getTextures() throws IllegalStateException {
|
||||
return this.getTextures(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets an immutable map of texture types to textures contained in the profile.
|
||||
*
|
||||
* @param requireSecure Whether to require the profile's texture payload to be securely signed.
|
||||
* @return The profile's textures.
|
||||
* @throws IllegalStateException If an error occurs decoding the profile's texture property.
|
||||
*/
|
||||
public Map<TextureType, Texture> getTextures(boolean requireSecure) throws IllegalStateException {
|
||||
if (this.textures == null || (requireSecure && !this.texturesVerified)) {
|
||||
GameProfile.Property textures = this.getProperty("textures");
|
||||
if (textures != null) {
|
||||
if (requireSecure) {
|
||||
if (!textures.hasSignature()) {
|
||||
throw new IllegalStateException("Signature is missing from textures payload.");
|
||||
}
|
||||
|
||||
if (!textures.isSignatureValid(SIGNATURE_KEY)) {
|
||||
throw new IllegalStateException("Textures payload has been tampered with. (signature invalid)");
|
||||
}
|
||||
}
|
||||
|
||||
MinecraftTexturesPayload result;
|
||||
try {
|
||||
String json = new String(Base64.getDecoder().decode(textures.getValue().getBytes(StandardCharsets.UTF_8)));
|
||||
result = GSON.fromJson(json, MinecraftTexturesPayload.class);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Could not decode texture payload.", e);
|
||||
}
|
||||
|
||||
if (result != null && result.textures != null) {
|
||||
if (requireSecure) {
|
||||
for (GameProfile.Texture texture : result.textures.values()) {
|
||||
if (TextureUrlChecker.isAllowedTextureDomain(texture.getURL())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Textures payload has been tampered with. (non-whitelisted domain)");
|
||||
}
|
||||
}
|
||||
|
||||
this.textures = result.textures;
|
||||
} else {
|
||||
this.textures = Collections.emptyMap();
|
||||
}
|
||||
|
||||
this.texturesVerified = requireSecure;
|
||||
} else {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.unmodifiableMap(this.textures);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a texture contained in the profile.
|
||||
*
|
||||
* @param type Type of texture to get.
|
||||
* @return The texture of the specified type.
|
||||
* @throws IllegalStateException If an error occurs decoding the profile's texture property.
|
||||
*/
|
||||
public Texture getTexture(TextureType type) throws IllegalStateException {
|
||||
return this.getTextures().get(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a texture contained in the profile.
|
||||
*
|
||||
* @param type Type of texture to get.
|
||||
* @param requireSecure Whether to require the profile's texture payload to be securely signed.
|
||||
* @return The texture of the specified type.
|
||||
* @throws IllegalStateException If an error occurs decoding the profile's texture property.
|
||||
*/
|
||||
public Texture getTexture(TextureType type, boolean requireSecure) throws IllegalStateException {
|
||||
return this.getTextures(requireSecure).get(type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
} else if (o != null && this.getClass() == o.getClass()) {
|
||||
GameProfile that = (GameProfile) o;
|
||||
return Objects.equals(this.id, that.id) && Objects.equals(this.name, that.name);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = this.id != null ? this.id.hashCode() : 0;
|
||||
result = 31 * result + (this.name != null ? this.name.hashCode() : 0);
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "GameProfile{id=" + this.id + ", name=" + this.name + ", properties=" + this.getProperties() + "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* A property belonging to a profile.
|
||||
*/
|
||||
public static class Property {
|
||||
private final String name;
|
||||
private final String value;
|
||||
private final String signature;
|
||||
|
||||
/**
|
||||
* Creates a new Property instance.
|
||||
*
|
||||
* @param name Name of the property.
|
||||
* @param value Value of the property.
|
||||
*/
|
||||
public Property(String name, String value) {
|
||||
this(name, value, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new Property instance.
|
||||
*
|
||||
* @param name Name of the property.
|
||||
* @param value Value of the property.
|
||||
* @param signature Signature used to verify the property.
|
||||
*/
|
||||
public Property(String name, String value, String signature) {
|
||||
this.name = name;
|
||||
this.value = value;
|
||||
this.signature = signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the name of the property.
|
||||
*
|
||||
* @return The property's name.
|
||||
*/
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value of the property.
|
||||
*
|
||||
* @return The property's value.
|
||||
*/
|
||||
public String getValue() {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this property has a signature to verify it.
|
||||
*
|
||||
* @return Whether this property is signed.
|
||||
*/
|
||||
public boolean hasSignature() {
|
||||
return this.signature != null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the signature used to verify the property.
|
||||
*
|
||||
* @return The property's signature.
|
||||
*/
|
||||
public String getSignature() {
|
||||
return this.signature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether this property's signature is valid.
|
||||
*
|
||||
* @param key Public key to validate the signature against.
|
||||
* @return Whether the signature is valid.
|
||||
* @throws IllegalStateException If the signature could not be validated.
|
||||
*/
|
||||
public boolean isSignatureValid(PublicKey key) throws IllegalStateException {
|
||||
if (!this.hasSignature()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
Signature sig = Signature.getInstance("SHA1withRSA");
|
||||
sig.initVerify(key);
|
||||
sig.update(this.value.getBytes());
|
||||
return sig.verify(Base64.getDecoder().decode(this.signature.getBytes(StandardCharsets.UTF_8)));
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Could not validate property signature.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Property{name=" + this.name + ", value=" + this.value + ", signature=" + this.signature + "}";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type of a profile texture.
|
||||
*/
|
||||
public enum TextureType {
|
||||
SKIN,
|
||||
CAPE,
|
||||
ELYTRA;
|
||||
}
|
||||
|
||||
/**
|
||||
* The model used for a profile texture.
|
||||
*/
|
||||
public enum TextureModel {
|
||||
NORMAL,
|
||||
SLIM;
|
||||
}
|
||||
|
||||
/**
|
||||
* A texture contained within a profile.
|
||||
*/
|
||||
public static class Texture {
|
||||
private final String url;
|
||||
private final Map<String, String> metadata;
|
||||
|
||||
/**
|
||||
* Creates a new Texture instance.
|
||||
*
|
||||
* @param url URL of the texture.
|
||||
* @param metadata Metadata of the texture.
|
||||
*/
|
||||
public Texture(String url, Map<String, String> metadata) {
|
||||
this.url = url;
|
||||
this.metadata = new HashMap<>(metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the URL of the texture.
|
||||
*
|
||||
* @return The texture's URL.
|
||||
*/
|
||||
public String getURL() {
|
||||
return this.url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a metadata string from the texture.
|
||||
*
|
||||
* @return The metadata value corresponding to the given key.
|
||||
*/
|
||||
public String getMetadata(String key) {
|
||||
return this.metadata.get(key);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the model of the texture.
|
||||
*
|
||||
* @return The texture's model.
|
||||
*/
|
||||
public TextureModel getModel() {
|
||||
String model = this.getMetadata("model");
|
||||
return model != null && model.equals("slim") ? TextureModel.SLIM : TextureModel.NORMAL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the hash of the texture.
|
||||
*
|
||||
* @return The texture's hash.
|
||||
*/
|
||||
public String getHash() {
|
||||
String url = this.url.endsWith("/") ? this.url.substring(0, this.url.length() - 1) : this.url;
|
||||
int slash = url.lastIndexOf("/");
|
||||
int dot = url.lastIndexOf(".");
|
||||
if (dot < slash) {
|
||||
dot = url.length();
|
||||
}
|
||||
|
||||
return url.substring(slash + 1, dot != -1 ? dot : url.length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Texture{url=" + this.url + ", model=" + this.getModel() + ", hash=" + this.getHash() + "}";
|
||||
}
|
||||
}
|
||||
|
||||
private static class MinecraftTexturesPayload {
|
||||
public long timestamp;
|
||||
public UUID profileId;
|
||||
public String profileName;
|
||||
public boolean isPublic;
|
||||
public Map<GameProfile.TextureType, GameProfile.Texture> textures;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,126 @@
|
|||
package org.geysermc.mcprotocollib.auth;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.mcprotocollib.auth.util.HTTPUtils;
|
||||
import org.geysermc.mcprotocollib.network.ProxyInfo;
|
||||
import org.geysermc.mcprotocollib.auth.util.UUIDUtils;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.IOException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.URI;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Service used for session-related queries.
|
||||
*/
|
||||
@Setter
|
||||
@Getter
|
||||
public class SessionService {
|
||||
private static final URI JOIN_ENDPOINT = URI.create("https://sessionserver.mojang.com/session/minecraft/join");
|
||||
private static final String HAS_JOINED_ENDPOINT = "https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s";
|
||||
private static final String PROFILE_ENDPOINT = "https://sessionserver.mojang.com/session/minecraft/profile/%s?unsigned=false";
|
||||
private ProxyInfo proxy;
|
||||
|
||||
/**
|
||||
* Calculates the server ID from a base string, public key, and secret key.
|
||||
*
|
||||
* @param base Base server ID to use.
|
||||
* @param publicKey Public key to use.
|
||||
* @param secretKey Secret key to use.
|
||||
* @return The calculated server ID.
|
||||
* @throws IllegalStateException If the server ID hash algorithm is unavailable.
|
||||
*/
|
||||
public static String getServerId(String base, PublicKey publicKey, SecretKey secretKey) {
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-1");
|
||||
digest.update(base.getBytes(StandardCharsets.ISO_8859_1));
|
||||
digest.update(secretKey.getEncoded());
|
||||
digest.update(publicKey.getEncoded());
|
||||
return new BigInteger(digest.digest()).toString(16);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new IllegalStateException("Server ID hash algorithm unavailable.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Joins a server.
|
||||
*
|
||||
* @param profile Profile to join the server with.
|
||||
* @param authenticationToken Authentication token to join the server with.
|
||||
* @param serverId ID of the server to join.
|
||||
* @throws IOException If an error occurs while making the request.
|
||||
*/
|
||||
public void joinServer(GameProfile profile, String authenticationToken, String serverId) throws IOException {
|
||||
JoinServerRequest request = new JoinServerRequest(authenticationToken, profile.getId(), serverId);
|
||||
HTTPUtils.makeRequest(this.getProxy(), JOIN_ENDPOINT, request, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the profile of the given user if they are currently logged in to the given server.
|
||||
*
|
||||
* @param name Name of the user to get the profile of.
|
||||
* @param serverId ID of the server to check if they're logged in to.
|
||||
* @return The profile of the given user, or null if they are not logged in to the given server.
|
||||
* @throws IOException If an error occurs while making the request.
|
||||
*/
|
||||
public GameProfile getProfileByServer(String name, String serverId) throws IOException {
|
||||
HasJoinedResponse response = HTTPUtils.makeRequest(this.getProxy(),
|
||||
URI.create(String.format(HAS_JOINED_ENDPOINT,
|
||||
URLEncoder.encode(name, StandardCharsets.UTF_8),
|
||||
URLEncoder.encode(serverId, StandardCharsets.UTF_8))),
|
||||
null, HasJoinedResponse.class);
|
||||
if (response != null && response.id != null) {
|
||||
GameProfile result = new GameProfile(response.id, name);
|
||||
result.setProperties(response.properties);
|
||||
return result;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fills in the properties of a profile.
|
||||
*
|
||||
* @param profile Profile to fill in the properties of.
|
||||
* @throws IOException If the property lookup fails.
|
||||
*/
|
||||
public void fillProfileProperties(GameProfile profile) throws IOException {
|
||||
if (profile.getId() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MinecraftProfileResponse response = HTTPUtils.makeRequest(this.getProxy(), URI.create(String.format(PROFILE_ENDPOINT, UUIDUtils.convertToNoDashes(profile.getId()))), null, MinecraftProfileResponse.class);
|
||||
if (response == null) {
|
||||
throw new IllegalStateException("Couldn't fetch profile properties for " + profile + " as the profile does not exist.");
|
||||
}
|
||||
|
||||
profile.setProperties(response.properties);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SessionService{}";
|
||||
}
|
||||
|
||||
private record JoinServerRequest(String accessToken, UUID selectedProfile, String serverId) {
|
||||
}
|
||||
|
||||
private static class HasJoinedResponse {
|
||||
public UUID id;
|
||||
public List<GameProfile.Property> properties;
|
||||
}
|
||||
|
||||
private static class MinecraftProfileResponse {
|
||||
public UUID id;
|
||||
public String name;
|
||||
public List<GameProfile.Property> properties;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package org.geysermc.mcprotocollib.auth.util;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import net.lenni0451.commons.httpclient.HttpClient;
|
||||
import net.lenni0451.commons.httpclient.HttpResponse;
|
||||
import net.lenni0451.commons.httpclient.constants.ContentTypes;
|
||||
import net.lenni0451.commons.httpclient.constants.Headers;
|
||||
import net.lenni0451.commons.httpclient.content.HttpContent;
|
||||
import net.lenni0451.commons.httpclient.proxy.ProxyHandler;
|
||||
import net.lenni0451.commons.httpclient.proxy.ProxyType;
|
||||
import net.lenni0451.commons.httpclient.requests.HttpContentRequest;
|
||||
import net.lenni0451.commons.httpclient.requests.HttpRequest;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.mcprotocollib.network.ProxyInfo;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Utilities for making HTTP requests.
|
||||
*/
|
||||
public class HTTPUtils {
|
||||
private static final Gson GSON = new GsonBuilder()
|
||||
.registerTypeAdapter(UUID.class, new UndashedUUIDAdapter())
|
||||
.create();
|
||||
|
||||
private HTTPUtils() {
|
||||
}
|
||||
|
||||
public static <T> T makeRequest(@Nullable ProxyInfo proxy, URI uri, Object input, Class<T> responseType) throws IOException {
|
||||
if (proxy == null) {
|
||||
throw new IllegalArgumentException("Proxy cannot be null.");
|
||||
} else if (uri == null) {
|
||||
throw new IllegalArgumentException("URI cannot be null.");
|
||||
}
|
||||
|
||||
HttpResponse response = createHttpClient(proxy).execute(input == null ? new HttpRequest("GET", uri.toURL()) :
|
||||
new HttpContentRequest("POST", uri.toURL()).setContent(HttpContent.string(GSON.toJson(input))));
|
||||
|
||||
if (responseType == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return GSON.fromJson(new InputStreamReader(new ByteArrayInputStream(response.getContent())), responseType);
|
||||
}
|
||||
|
||||
public static HttpClient createHttpClient(@Nullable ProxyInfo proxy) {
|
||||
final int timeout = 5000;
|
||||
|
||||
HttpClient client = new HttpClient()
|
||||
.setConnectTimeout(timeout)
|
||||
.setReadTimeout(timeout * 2)
|
||||
.setCookieManager(null)
|
||||
.setFollowRedirects(false)
|
||||
.setHeader(Headers.ACCEPT, ContentTypes.APPLICATION_JSON.toString())
|
||||
.setHeader(Headers.ACCEPT_LANGUAGE, "en-US,en");
|
||||
|
||||
if (proxy != null) {
|
||||
client.setProxyHandler(new ProxyHandler(switch (proxy.type()) {
|
||||
case HTTP -> ProxyType.HTTP;
|
||||
case SOCKS4 -> ProxyType.SOCKS4;
|
||||
case SOCKS5 -> ProxyType.SOCKS5;
|
||||
}, proxy.address(), proxy.username(), proxy.password()));
|
||||
}
|
||||
|
||||
return client;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,64 @@
|
|||
package org.geysermc.mcprotocollib.auth.util;
|
||||
|
||||
import java.net.IDN;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
public class TextureUrlChecker {
|
||||
private static final Set<String> ALLOWED_SCHEMES = Set.of(
|
||||
"http",
|
||||
"https"
|
||||
);
|
||||
|
||||
private static final List<String> ALLOWED_DOMAINS = List.of(
|
||||
".minecraft.net",
|
||||
".mojang.com"
|
||||
);
|
||||
|
||||
private static final List<String> BLOCKED_DOMAINS = List.of(
|
||||
"bugs.mojang.com",
|
||||
"education.minecraft.net",
|
||||
"feedback.minecraft.net"
|
||||
);
|
||||
|
||||
public static boolean isAllowedTextureDomain(final String url) {
|
||||
final URI uri;
|
||||
|
||||
try {
|
||||
uri = new URI(url).normalize();
|
||||
} catch (final URISyntaxException ignored) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String scheme = uri.getScheme();
|
||||
if (scheme == null || !ALLOWED_SCHEMES.contains(scheme)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String domain = uri.getHost();
|
||||
if (domain == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final String decodedDomain = IDN.toUnicode(domain);
|
||||
final String lowerCaseDomain = decodedDomain.toLowerCase(Locale.ROOT);
|
||||
if (!lowerCaseDomain.equals(decodedDomain)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return isDomainOnList(decodedDomain, ALLOWED_DOMAINS) && !isDomainOnList(decodedDomain, BLOCKED_DOMAINS);
|
||||
}
|
||||
|
||||
private static boolean isDomainOnList(final String domain, final List<String> list) {
|
||||
for (final String entry : list) {
|
||||
if (domain.endsWith(entry)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
package org.geysermc.mcprotocollib.auth.util;
|
||||
|
||||
import java.util.HexFormat;
|
||||
import java.util.UUID;
|
||||
|
||||
public class UUIDUtils {
|
||||
public static UUID convertToDashed(String noDashes) {
|
||||
if (noDashes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new UUID(
|
||||
HexFormat.fromHexDigitsToLong(noDashes, 0, 16),
|
||||
HexFormat.fromHexDigitsToLong(noDashes, 16, 32)
|
||||
);
|
||||
}
|
||||
|
||||
public static String convertToNoDashes(UUID uuid) {
|
||||
if (uuid == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return uuid.toString().replace("-", "");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
package org.geysermc.mcprotocollib.auth.util;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Utility class for serializing and deserializing undashed UUIDs.
|
||||
*/
|
||||
public class UndashedUUIDAdapter extends TypeAdapter<UUID> {
|
||||
@Override
|
||||
public void write(JsonWriter out, UUID value) throws IOException {
|
||||
out.value(UUIDUtils.convertToNoDashes(value));
|
||||
}
|
||||
|
||||
@Override
|
||||
public UUID read(JsonReader in) throws IOException {
|
||||
return UUIDUtils.convertToDashed(in.nextString());
|
||||
}
|
||||
}
|
|
@ -1,87 +1,37 @@
|
|||
package org.geysermc.mcprotocollib.network;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.net.SocketAddress;
|
||||
|
||||
/**
|
||||
* Information describing a network proxy.
|
||||
*/
|
||||
public class ProxyInfo {
|
||||
private final Type type;
|
||||
private final SocketAddress address;
|
||||
private boolean authenticated;
|
||||
private String username;
|
||||
private String password;
|
||||
|
||||
public record ProxyInfo(Type type, SocketAddress address, String username, String password) {
|
||||
/**
|
||||
* Creates a new unauthenticated ProxyInfo instance.
|
||||
* Creates a new unauthenticated proxy info.
|
||||
*
|
||||
* @param type Type of proxy.
|
||||
*/
|
||||
public ProxyInfo(Type type, String host, int port, String username, String password) {
|
||||
this(type, new InetSocketAddress(host, port), username, password);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new unauthenticated proxy info.
|
||||
*
|
||||
* @param type Type of proxy.
|
||||
* @param address Network address of the proxy.
|
||||
*/
|
||||
public ProxyInfo(Type type, SocketAddress address) {
|
||||
this.type = type;
|
||||
this.address = address;
|
||||
this.authenticated = false;
|
||||
this(type, address, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new authenticated ProxyInfo instance.
|
||||
* Creates a new unauthenticated proxy info.
|
||||
*
|
||||
* @param type Type of proxy.
|
||||
* @param address Network address of the proxy.
|
||||
* @param username Username to authenticate with.
|
||||
* @param password Password to authenticate with.
|
||||
*/
|
||||
public ProxyInfo(Type type, SocketAddress address, String username, String password) {
|
||||
this(type, address);
|
||||
this.authenticated = true;
|
||||
this.username = username;
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the proxy's type.
|
||||
*
|
||||
* @return The proxy's type.
|
||||
*/
|
||||
public Type getType() {
|
||||
return this.type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the proxy's network address.
|
||||
*
|
||||
* @return The proxy's network address.
|
||||
*/
|
||||
public SocketAddress getAddress() {
|
||||
return this.address;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets whether the proxy is authenticated with.
|
||||
*
|
||||
* @return Whether to authenticate with the proxy.
|
||||
*/
|
||||
public boolean isAuthenticated() {
|
||||
return this.authenticated;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the proxy's authentication username.
|
||||
*
|
||||
* @return The username to authenticate with.
|
||||
*/
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the proxy's authentication password.
|
||||
*
|
||||
* @return The password to authenticate with.
|
||||
*/
|
||||
public String getPassword() {
|
||||
return this.password;
|
||||
public ProxyInfo(Type type, String host, int port) {
|
||||
this(type, new InetSocketAddress(host, port));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -28,8 +28,8 @@ import io.netty.handler.proxy.Socks5ProxyHandler;
|
|||
import io.netty.resolver.dns.DnsNameResolver;
|
||||
import io.netty.resolver.dns.DnsNameResolverBuilder;
|
||||
import io.netty.util.concurrent.DefaultThreadFactory;
|
||||
import org.geysermc.mcprotocollib.network.BuiltinFlags;
|
||||
import org.geysermc.mcprotocollib.network.ProxyInfo;
|
||||
import org.geysermc.mcprotocollib.network.BuiltinFlags;
|
||||
import org.geysermc.mcprotocollib.network.codec.PacketCodecHelper;
|
||||
import org.geysermc.mcprotocollib.network.helper.TransportHelper;
|
||||
import org.geysermc.mcprotocollib.network.packet.PacketProtocol;
|
||||
|
@ -207,29 +207,29 @@ public class TcpClientSession extends TcpSession {
|
|||
|
||||
private void addProxy(ChannelPipeline pipeline) {
|
||||
if (proxy != null) {
|
||||
switch (proxy.getType()) {
|
||||
switch (proxy.type()) {
|
||||
case HTTP -> {
|
||||
if (proxy.isAuthenticated()) {
|
||||
pipeline.addFirst("proxy", new HttpProxyHandler(proxy.getAddress(), proxy.getUsername(), proxy.getPassword()));
|
||||
if (proxy.username() != null && proxy.password() != null) {
|
||||
pipeline.addFirst("proxy", new HttpProxyHandler(proxy.address(), proxy.username(), proxy.password()));
|
||||
} else {
|
||||
pipeline.addFirst("proxy", new HttpProxyHandler(proxy.getAddress()));
|
||||
pipeline.addFirst("proxy", new HttpProxyHandler(proxy.address()));
|
||||
}
|
||||
}
|
||||
case SOCKS4 -> {
|
||||
if (proxy.isAuthenticated()) {
|
||||
pipeline.addFirst("proxy", new Socks4ProxyHandler(proxy.getAddress(), proxy.getUsername()));
|
||||
if (proxy.username() != null) {
|
||||
pipeline.addFirst("proxy", new Socks4ProxyHandler(proxy.address(), proxy.username()));
|
||||
} else {
|
||||
pipeline.addFirst("proxy", new Socks4ProxyHandler(proxy.getAddress()));
|
||||
pipeline.addFirst("proxy", new Socks4ProxyHandler(proxy.address()));
|
||||
}
|
||||
}
|
||||
case SOCKS5 -> {
|
||||
if (proxy.isAuthenticated()) {
|
||||
pipeline.addFirst("proxy", new Socks5ProxyHandler(proxy.getAddress(), proxy.getUsername(), proxy.getPassword()));
|
||||
if (proxy.username() != null && proxy.password() != null) {
|
||||
pipeline.addFirst("proxy", new Socks5ProxyHandler(proxy.address(), proxy.username(), proxy.password()));
|
||||
} else {
|
||||
pipeline.addFirst("proxy", new Socks5ProxyHandler(proxy.getAddress()));
|
||||
pipeline.addFirst("proxy", new Socks5ProxyHandler(proxy.address()));
|
||||
}
|
||||
}
|
||||
default -> throw new UnsupportedOperationException("Unsupported proxy type: " + proxy.getType());
|
||||
default -> throw new UnsupportedOperationException("Unsupported proxy type: " + proxy.type());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
package org.geysermc.mcprotocollib.protocol;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.github.steveice10.mc.auth.exception.request.InvalidCredentialsException;
|
||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
||||
import com.github.steveice10.mc.auth.exception.request.ServiceUnavailableException;
|
||||
import com.github.steveice10.mc.auth.service.SessionService;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.NonNull;
|
||||
import lombok.SneakyThrows;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.auth.SessionService;
|
||||
import org.geysermc.mcprotocollib.network.Session;
|
||||
import org.geysermc.mcprotocollib.network.event.session.ConnectedEvent;
|
||||
import org.geysermc.mcprotocollib.network.event.session.SessionAdapter;
|
||||
|
@ -44,6 +41,7 @@ import org.geysermc.mcprotocollib.protocol.packet.status.serverbound.Serverbound
|
|||
|
||||
import javax.crypto.KeyGenerator;
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.IOException;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.Collections;
|
||||
|
||||
|
@ -78,16 +76,10 @@ public class ClientListener extends SessionAdapter {
|
|||
}
|
||||
|
||||
SessionService sessionService = session.getFlag(MinecraftConstants.SESSION_SERVICE_KEY, new SessionService());
|
||||
String serverId = sessionService.getServerId(helloPacket.getServerId(), helloPacket.getPublicKey(), key);
|
||||
String serverId = SessionService.getServerId(helloPacket.getServerId(), helloPacket.getPublicKey(), key);
|
||||
try {
|
||||
sessionService.joinServer(profile, accessToken, serverId);
|
||||
} catch (ServiceUnavailableException e) {
|
||||
session.disconnect("Login failed: Authentication service unavailable.", e);
|
||||
return;
|
||||
} catch (InvalidCredentialsException e) {
|
||||
session.disconnect("Login failed: Invalid login session.", e);
|
||||
return;
|
||||
} catch (RequestException e) {
|
||||
} catch (IOException e) {
|
||||
session.disconnect("Login failed: Authentication error: " + e.getMessage(), e);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.geysermc.mcprotocollib.protocol;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.github.steveice10.mc.auth.service.SessionService;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.auth.SessionService;
|
||||
import org.geysermc.mcprotocollib.network.Flag;
|
||||
import org.geysermc.mcprotocollib.network.packet.DefaultPacketHeader;
|
||||
import org.geysermc.mcprotocollib.network.packet.PacketHeader;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.geysermc.mcprotocollib.protocol;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
|
@ -8,6 +7,7 @@ import lombok.Setter;
|
|||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtUtils;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.network.Server;
|
||||
import org.geysermc.mcprotocollib.network.Session;
|
||||
import org.geysermc.mcprotocollib.network.codec.PacketCodecHelper;
|
||||
|
|
|
@ -1,13 +1,12 @@
|
|||
package org.geysermc.mcprotocollib.protocol;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.github.steveice10.mc.auth.exception.request.RequestException;
|
||||
import com.github.steveice10.mc.auth.service.SessionService;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.auth.SessionService;
|
||||
import org.geysermc.mcprotocollib.network.Session;
|
||||
import org.geysermc.mcprotocollib.network.event.session.ConnectedEvent;
|
||||
import org.geysermc.mcprotocollib.network.event.session.DisconnectingEvent;
|
||||
|
@ -40,6 +39,7 @@ import org.geysermc.mcprotocollib.protocol.packet.status.serverbound.Serverbound
|
|||
import org.geysermc.mcprotocollib.protocol.packet.status.serverbound.ServerboundStatusRequestPacket;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.io.IOException;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
|
@ -228,8 +228,8 @@ public class ServerListener extends SessionAdapter {
|
|||
if (this.key != null) {
|
||||
SessionService sessionService = this.session.getFlag(MinecraftConstants.SESSION_SERVICE_KEY, new SessionService());
|
||||
try {
|
||||
profile = sessionService.getProfileByServer(username, sessionService.getServerId(SERVER_ID, KEY_PAIR.getPublic(), this.key));
|
||||
} catch (RequestException e) {
|
||||
profile = sessionService.getProfileByServer(username, SessionService.getServerId(SERVER_ID, KEY_PAIR.getPublic(), this.key));
|
||||
} catch (IOException e) {
|
||||
this.session.disconnect("Failed to make session service request.", e);
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.geysermc.mcprotocollib.protocol.codec;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.google.gson.JsonElement;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufInputStream;
|
||||
|
@ -18,6 +17,7 @@ import org.cloudburstmc.nbt.NBTInputStream;
|
|||
import org.cloudburstmc.nbt.NBTOutputStream;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.network.codec.BasePacketCodecHelper;
|
||||
import org.geysermc.mcprotocollib.protocol.data.DefaultComponentSerializer;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package org.geysermc.mcprotocollib.protocol.data.game;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.player.GameMode;
|
||||
|
||||
import java.security.PublicKey;
|
||||
|
|
|
@ -136,7 +136,7 @@ public enum EntityType {
|
|||
|
||||
@Getter
|
||||
private final boolean projectile;
|
||||
|
||||
|
||||
EntityType() {
|
||||
this.projectile = false;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
package org.geysermc.mcprotocollib.protocol.data.game.item.component;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.key.Key;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.cloudburstmc.nbt.NbtList;
|
||||
import org.cloudburstmc.nbt.NbtMap;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.item.ItemStack;
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
package org.geysermc.mcprotocollib.protocol.data.game.item.component;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
|
||||
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
|
||||
|
@ -9,6 +8,7 @@ import net.kyori.adventure.key.Key;
|
|||
import net.kyori.adventure.text.Component;
|
||||
import org.cloudburstmc.nbt.NbtList;
|
||||
import org.cloudburstmc.nbt.NbtType;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.Holder;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.entity.Effect;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package org.geysermc.mcprotocollib.protocol.data.status;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.With;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.data.game.PlayerListEntry;
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
package org.geysermc.mcprotocollib.protocol.packet.login.clientbound;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NonNull;
|
||||
import lombok.With;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftPacket;
|
||||
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
package org.geysermc.mcprotocollib.protocol.packet.status.clientbound;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import com.github.steveice10.mc.auth.util.Base64;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonArray;
|
||||
import com.google.gson.JsonElement;
|
||||
|
@ -12,6 +10,7 @@ import lombok.Data;
|
|||
import lombok.NonNull;
|
||||
import lombok.With;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodecHelper;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftPacket;
|
||||
import org.geysermc.mcprotocollib.protocol.data.DefaultComponentSerializer;
|
||||
|
@ -21,6 +20,7 @@ import org.geysermc.mcprotocollib.protocol.data.status.VersionInfo;
|
|||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Base64;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
|
@ -117,10 +117,10 @@ public class ClientboundStatusResponsePacket implements MinecraftPacket {
|
|||
str = str.substring("data:image/png;base64,".length());
|
||||
}
|
||||
|
||||
return Base64.decode(str.getBytes(StandardCharsets.UTF_8));
|
||||
return Base64.getDecoder().decode(str.getBytes(StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
public static String iconToString(byte[] icon) {
|
||||
return "data:image/png;base64," + new String(Base64.encode(icon), StandardCharsets.UTF_8);
|
||||
return "data:image/png;base64," + new String(Base64.getEncoder().encode(icon), StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
|
BIN
protocol/src/main/resources/yggdrasil_session_pubkey.der
Normal file
BIN
protocol/src/main/resources/yggdrasil_session_pubkey.der
Normal file
Binary file not shown.
|
@ -1,6 +1,6 @@
|
|||
package org.geysermc.mcprotocollib.protocol.packet.login.clientbound;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.protocol.packet.PacketTest;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package org.geysermc.mcprotocollib.protocol.packet.status.clientbound;
|
||||
|
||||
import com.github.steveice10.mc.auth.data.GameProfile;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.geysermc.mcprotocollib.auth.GameProfile;
|
||||
import org.geysermc.mcprotocollib.protocol.codec.MinecraftCodec;
|
||||
import org.geysermc.mcprotocollib.protocol.data.status.PlayerInfo;
|
||||
import org.geysermc.mcprotocollib.protocol.data.status.ServerStatusInfo;
|
||||
|
|
|
@ -17,4 +17,7 @@ dependencyResolutionManagement {
|
|||
|
||||
rootProject.name = "mcprotocollib"
|
||||
|
||||
include("protocol", "example")
|
||||
include(
|
||||
"protocol",
|
||||
"example"
|
||||
)
|
||||
|
|
Loading…
Reference in a new issue