diff --git a/example/src/main/java/org/geysermc/mcprotocollib/auth/example/MinecraftAuthTest.java b/example/src/main/java/org/geysermc/mcprotocollib/auth/example/MinecraftAuthTest.java new file mode 100644 index 000000000..e4e0aca56 --- /dev/null +++ b/example/src/main/java/org/geysermc/mcprotocollib/auth/example/MinecraftAuthTest.java @@ -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); + } + } +} diff --git a/example/src/main/java/org/geysermc/mcprotocollib/protocol/example/MinecraftProtocolTest.java b/example/src/main/java/org/geysermc/mcprotocollib/protocol/example/MinecraftProtocolTest.java index 508720fb9..1dd504cf8 100644 --- a/example/src/main/java/org/geysermc/mcprotocollib/protocol/example/MinecraftProtocolTest.java +++ b/example/src/main/java/org/geysermc/mcprotocollib/protocol/example/MinecraftProtocolTest.java @@ -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.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 @@ private static void status() { 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); } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ca5d16059..d0f9a87d5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" } diff --git a/protocol/build.gradle.kts b/protocol/build.gradle.kts index 4a619d087..dcb335b4e 100644 --- a/protocol/build.gradle.kts +++ b/protocol/build.gradle.kts @@ -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) diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/auth/GameProfile.java b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/GameProfile.java new file mode 100644 index 000000000..b481a437d --- /dev/null +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/GameProfile.java @@ -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 properties; + private Map 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 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 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 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 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 metadata; + + /** + * Creates a new Texture instance. + * + * @param url URL of the texture. + * @param metadata Metadata of the texture. + */ + public Texture(String url, Map 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 textures; + } +} diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/auth/SessionService.java b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/SessionService.java new file mode 100644 index 000000000..eb8489038 --- /dev/null +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/SessionService.java @@ -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 properties; + } + + private static class MinecraftProfileResponse { + public UUID id; + public String name; + public List properties; + } +} diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/HTTPUtils.java b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/HTTPUtils.java new file mode 100644 index 000000000..75f0046fc --- /dev/null +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/HTTPUtils.java @@ -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 makeRequest(@Nullable ProxyInfo proxy, URI uri, Object input, Class 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; + } +} diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/TextureUrlChecker.java b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/TextureUrlChecker.java new file mode 100644 index 000000000..343956e1d --- /dev/null +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/TextureUrlChecker.java @@ -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 ALLOWED_SCHEMES = Set.of( + "http", + "https" + ); + + private static final List ALLOWED_DOMAINS = List.of( + ".minecraft.net", + ".mojang.com" + ); + + private static final List 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 list) { + for (final String entry : list) { + if (domain.endsWith(entry)) { + return true; + } + } + + return false; + } +} diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/UUIDUtils.java b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/UUIDUtils.java new file mode 100644 index 000000000..d6a1493a9 --- /dev/null +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/UUIDUtils.java @@ -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("-", ""); + } +} diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/UndashedUUIDAdapter.java b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/UndashedUUIDAdapter.java new file mode 100644 index 000000000..7e7e2fc06 --- /dev/null +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/auth/util/UndashedUUIDAdapter.java @@ -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 { + @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()); + } +} diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/network/ProxyInfo.java b/protocol/src/main/java/org/geysermc/mcprotocollib/network/ProxyInfo.java index c09605a74..ddf303850 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/network/ProxyInfo.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/network/ProxyInfo.java @@ -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. - * @param address Network address of the proxy. */ - public ProxyInfo(Type type, SocketAddress address) { - this.type = type; - this.address = address; - this.authenticated = false; + public ProxyInfo(Type type, String host, int port, String username, String password) { + this(type, new InetSocketAddress(host, port), username, password); } /** - * 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; + public ProxyInfo(Type type, SocketAddress address) { + this(type, address, null, null); } /** - * Gets the proxy's authentication password. + * Creates a new unauthenticated proxy info. * - * @return The password to authenticate with. + * @param type Type of proxy. */ - public String getPassword() { - return this.password; + public ProxyInfo(Type type, String host, int port) { + this(type, new InetSocketAddress(host, port)); } /** diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpClientSession.java b/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpClientSession.java index 60816d861..6c1019c72 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpClientSession.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/network/tcp/TcpClientSession.java @@ -28,8 +28,8 @@ 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 @@ private InetSocketAddress resolveAddress() { 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()); } } } diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ClientListener.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ClientListener.java index c65dbe196..31f40b3e4 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ClientListener.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ClientListener.java @@ -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 javax.crypto.KeyGenerator; import javax.crypto.SecretKey; +import java.io.IOException; import java.security.NoSuchAlgorithmException; import java.util.Collections; @@ -78,16 +76,10 @@ public void packetReceived(Session session, Packet packet) { } 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; } diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/MinecraftConstants.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/MinecraftConstants.java index 935054786..19e59e3f2 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/MinecraftConstants.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/MinecraftConstants.java @@ -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; diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/MinecraftProtocol.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/MinecraftProtocol.java index ec2d53af3..781704e69 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/MinecraftProtocol.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/MinecraftProtocol.java @@ -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 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; diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ServerListener.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ServerListener.java index 261e21955..84949fde2 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ServerListener.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/ServerListener.java @@ -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.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 void run() { 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; } diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/codec/MinecraftCodecHelper.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/codec/MinecraftCodecHelper.java index cbf9a7af2..362806074 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/codec/MinecraftCodecHelper.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/codec/MinecraftCodecHelper.java @@ -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.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; diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/PlayerListEntry.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/PlayerListEntry.java index 7c330dcb3..029ce513e 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/PlayerListEntry.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/PlayerListEntry.java @@ -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; diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/entity/type/EntityType.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/entity/type/EntityType.java index 3b1a2c364..46bc7fb56 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/entity/type/EntityType.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/entity/type/EntityType.java @@ -136,7 +136,7 @@ public enum EntityType { @Getter private final boolean projectile; - + EntityType() { this.projectile = false; } diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/item/component/DataComponentType.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/item/component/DataComponentType.java index 7b0cbadaf..ef8e442f3 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/item/component/DataComponentType.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/item/component/DataComponentType.java @@ -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; diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/item/component/ItemCodecHelper.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/item/component/ItemCodecHelper.java index 02b499d20..66fa0f6b0 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/item/component/ItemCodecHelper.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/game/item/component/ItemCodecHelper.java @@ -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.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; diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/status/PlayerInfo.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/status/PlayerInfo.java index ed0853073..4de324c09 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/status/PlayerInfo.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/data/status/PlayerInfo.java @@ -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; diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/ingame/clientbound/ClientboundPlayerInfoUpdatePacket.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/ingame/clientbound/ClientboundPlayerInfoUpdatePacket.java index 117b6ada3..07a0b638f 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/ingame/clientbound/ClientboundPlayerInfoUpdatePacket.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/ingame/clientbound/ClientboundPlayerInfoUpdatePacket.java @@ -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; diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/login/clientbound/ClientboundGameProfilePacket.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/login/clientbound/ClientboundGameProfilePacket.java index 3d3e528e0..561abcda8 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/login/clientbound/ClientboundGameProfilePacket.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/login/clientbound/ClientboundGameProfilePacket.java @@ -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; diff --git a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/status/clientbound/ClientboundStatusResponsePacket.java b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/status/clientbound/ClientboundStatusResponsePacket.java index 831c019e8..ad642e8bd 100644 --- a/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/status/clientbound/ClientboundStatusResponsePacket.java +++ b/protocol/src/main/java/org/geysermc/mcprotocollib/protocol/packet/status/clientbound/ClientboundStatusResponsePacket.java @@ -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.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 java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.Base64; import java.util.List; @Data @@ -117,10 +117,10 @@ public static byte[] stringToIcon(String str) { 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); } } diff --git a/protocol/src/main/resources/yggdrasil_session_pubkey.der b/protocol/src/main/resources/yggdrasil_session_pubkey.der new file mode 100644 index 000000000..9c79a3aa4 Binary files /dev/null and b/protocol/src/main/resources/yggdrasil_session_pubkey.der differ diff --git a/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/packet/login/clientbound/ClientboundGameProfilePacketTest.java b/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/packet/login/clientbound/ClientboundGameProfilePacketTest.java index 52a2a6e0a..3afae9384 100644 --- a/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/packet/login/clientbound/ClientboundGameProfilePacketTest.java +++ b/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/packet/login/clientbound/ClientboundGameProfilePacketTest.java @@ -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; diff --git a/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/packet/status/clientbound/ClientboundStatusResponsePacketTest.java b/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/packet/status/clientbound/ClientboundStatusResponsePacketTest.java index 004d78299..8059af6ae 100644 --- a/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/packet/status/clientbound/ClientboundStatusResponsePacketTest.java +++ b/protocol/src/test/java/org/geysermc/mcprotocollib/protocol/packet/status/clientbound/ClientboundStatusResponsePacketTest.java @@ -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; diff --git a/settings.gradle.kts b/settings.gradle.kts index b1b2e7605..649eee1e5 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -17,4 +17,7 @@ dependencyResolutionManagement { rootProject.name = "mcprotocollib" -include("protocol", "example") +include( + "protocol", + "example" +)