From a25c88a93def70223f79f3a8aba1638ad211f0fc Mon Sep 17 00:00:00 2001 From: RaphiMC <50594595+RaphiMC@users.noreply.github.com> Date: Sun, 22 Oct 2023 12:19:22 +0200 Subject: [PATCH] Huge code cleanup and refactor --- build.gradle | 25 ++ .../mcauth/step/java/StepGameOwnership.java | 124 ------ .../mcauth/step/java/StepMCProfile.java | 143 ------- .../raphimc/mcauth/step/java/StepMCToken.java | 156 ------- .../step/xbl/StepXblSisuAuthentication.java | 402 ------------------ .../step/xbl/session/StepFullXblSession.java | 104 ----- .../xbl/session/StepInitialXblSession.java | 104 ----- .../MinecraftAuth.java | 46 +- .../step/AbstractStep.java | 2 +- .../step/OptionalMergeStep.java | 2 +- .../step/SameInputOptionalMergeStep.java | 2 +- .../step/bedrock/PlayFabResponseHandler.java | 2 +- .../step/bedrock/StepMCChain.java | 147 ++----- .../step/bedrock/StepPlayFabToken.java | 109 ++--- .../step/java/StepMCProfile.java | 100 +++++ .../minecraftauth/step/java/StepMCToken.java | 113 +++++ .../step/java/StepPlayerCertificates.java | 117 ++--- .../step/msa/MsaCodeStep.java | 72 +--- .../msa/MsaCredentialsResponseHandler.java | 2 +- .../step/msa/MsaResponseHandler.java | 2 +- .../step/msa/StepCredentialsMsaCode.java | 27 +- .../step/msa/StepExternalBrowser.java | 73 +--- .../step/msa/StepExternalBrowserMsaCode.java | 10 +- .../step/msa/StepMsaDeviceCode.java | 93 +--- .../step/msa/StepMsaDeviceCodeMsaCode.java | 10 +- .../step/msa/StepMsaToken.java | 110 ++--- .../step/xbl/StepXblDeviceToken.java | 103 +---- .../step/xbl/StepXblSisuAuthentication.java | 239 +++++++++++ .../step/xbl/StepXblTitleToken.java | 103 ++--- .../step/xbl/StepXblUserToken.java | 101 ++--- .../step/xbl/StepXblXstsToken.java | 127 ++---- .../step/xbl/XblResponseHandler.java | 4 +- .../step/xbl/session/StepFullXblSession.java | 72 ++++ .../xbl/session/StepInitialXblSession.java | 72 ++++ .../util/CryptUtil.java | 23 +- .../util/MicrosoftConstants.java | 5 +- .../util/logging/ConsoleLogger.java | 2 +- .../util/logging/ILogger.java | 2 +- 38 files changed, 972 insertions(+), 1978 deletions(-) delete mode 100644 src/main/java/net/raphimc/mcauth/step/java/StepGameOwnership.java delete mode 100644 src/main/java/net/raphimc/mcauth/step/java/StepMCProfile.java delete mode 100644 src/main/java/net/raphimc/mcauth/step/java/StepMCToken.java delete mode 100644 src/main/java/net/raphimc/mcauth/step/xbl/StepXblSisuAuthentication.java delete mode 100644 src/main/java/net/raphimc/mcauth/step/xbl/session/StepFullXblSession.java delete mode 100644 src/main/java/net/raphimc/mcauth/step/xbl/session/StepInitialXblSession.java rename src/main/java/net/raphimc/{mcauth => minecraftauth}/MinecraftAuth.java (85%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/AbstractStep.java (98%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/OptionalMergeStep.java (98%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/SameInputOptionalMergeStep.java (98%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/bedrock/PlayFabResponseHandler.java (97%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/bedrock/StepMCChain.java (53%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/bedrock/StepPlayFabToken.java (60%) create mode 100644 src/main/java/net/raphimc/minecraftauth/step/java/StepMCProfile.java create mode 100644 src/main/java/net/raphimc/minecraftauth/step/java/StepMCToken.java rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/java/StepPlayerCertificates.java (53%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/msa/MsaCodeStep.java (52%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/msa/MsaCredentialsResponseHandler.java (98%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/msa/MsaResponseHandler.java (98%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/msa/StepCredentialsMsaCode.java (90%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/msa/StepExternalBrowser.java (65%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/msa/StepExternalBrowserMsaCode.java (94%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/msa/StepMsaDeviceCode.java (60%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/msa/StepMsaDeviceCodeMsaCode.java (93%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/msa/StepMsaToken.java (57%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/xbl/StepXblDeviceToken.java (60%) create mode 100644 src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblSisuAuthentication.java rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/xbl/StepXblTitleToken.java (51%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/xbl/StepXblUserToken.java (53%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/xbl/StepXblXstsToken.java (50%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/step/xbl/XblResponseHandler.java (95%) create mode 100644 src/main/java/net/raphimc/minecraftauth/step/xbl/session/StepFullXblSession.java create mode 100644 src/main/java/net/raphimc/minecraftauth/step/xbl/session/StepInitialXblSession.java rename src/main/java/net/raphimc/{mcauth => minecraftauth}/util/CryptUtil.java (86%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/util/MicrosoftConstants.java (97%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/util/logging/ConsoleLogger.java (96%) rename src/main/java/net/raphimc/{mcauth => minecraftauth}/util/logging/ILogger.java (95%) diff --git a/build.gradle b/build.gradle index cb8f387..322dc00 100644 --- a/build.gradle +++ b/build.gradle @@ -2,6 +2,8 @@ plugins { id "java-library" id "maven-publish" id "signing" + id "net.raphimc.class-token-replacer" version "1.0.0" + id "io.freefair.lombok" version "8.4" } base { @@ -27,6 +29,15 @@ dependencies { api "org.slf4j:slf4j-api:2.0.9" } +sourceSets { + main { + classTokenReplacer { + property("\${version}", project.version) + property("\${impl_version}", "git-${project.name}-${project.version}:${project.latestCommitHash()}") + } + } +} + java { withSourcesJar() withJavadocJar() @@ -38,6 +49,11 @@ jar { } } +sourcesJar { + from delombok + duplicatesStrategy = DuplicatesStrategy.INCLUDE +} + artifacts { archives javadocJar, sourcesJar } @@ -105,3 +121,12 @@ signing { project.tasks.withType(PublishToMavenRepository).forEach { it.dependsOn(project.tasks.withType(Sign)) } + +String latestCommitHash() { + def stdout = new ByteArrayOutputStream() + exec { + commandLine "git", "rev-parse", "--short", "HEAD" + standardOutput = stdout + } + return stdout.toString().trim() +} diff --git a/src/main/java/net/raphimc/mcauth/step/java/StepGameOwnership.java b/src/main/java/net/raphimc/mcauth/step/java/StepGameOwnership.java deleted file mode 100644 index 66518fe..0000000 --- a/src/main/java/net/raphimc/mcauth/step/java/StepGameOwnership.java +++ /dev/null @@ -1,124 +0,0 @@ -/* - * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth - * Copyright (C) 2023 RK_01/RaphiMC and contributors - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package net.raphimc.mcauth.step.java; - -import com.google.gson.*; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.BasicResponseHandler; - -import java.util.ArrayList; -import java.util.List; -import java.util.Objects; - -public class StepGameOwnership extends AbstractStep { - - public static final String MINECRAFT_OWNERSHIP_URL = "https://api.minecraftservices.com/entitlements/mcstore"; - - public StepGameOwnership(AbstractStep prevStep) { - super(prevStep); - } - - @Override - public GameOwnership applyStep(HttpClient httpClient, StepMCToken.MCToken prevResult) throws Exception { - MinecraftAuth.LOGGER.info("Getting game ownership..."); - - final HttpGet httpGet = new HttpGet(MINECRAFT_OWNERSHIP_URL); - httpGet.addHeader("Authorization", prevResult.token_type() + " " + prevResult.access_token()); - final String response = httpClient.execute(httpGet, new BasicResponseHandler()); - final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); - - final List items = new ArrayList<>(); - final JsonArray itemsArr = obj.getAsJsonArray("items"); - for (JsonElement item : itemsArr) { - items.add(item.getAsJsonObject().get("name").getAsString()); - } - - final GameOwnership result = new GameOwnership( - items, - prevResult - ); - MinecraftAuth.LOGGER.info("Got GameOwnership, games: " + result.items); - if (!result.items.contains("product_minecraft") || !result.items.contains("game_minecraft")) { - MinecraftAuth.LOGGER.warn("Microsoft account does not own minecraft!"); - } - return result; - } - - @Override - public GameOwnership fromJson(JsonObject json) throws Exception { - final StepMCToken.MCToken prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; - return new GameOwnership( - new Gson().>fromJson(json.get("items"), List.class), - prev - ); - } - - public static final class GameOwnership implements AbstractStep.StepResult { - - private final List items; - private final StepMCToken.MCToken prevResult; - - public GameOwnership(List items, StepMCToken.MCToken prevResult) { - this.items = items; - this.prevResult = prevResult; - } - - @Override - public JsonObject toJson() { - final JsonObject json = new JsonObject(); - json.add("items", new Gson().toJsonTree(items)); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); - return json; - } - - public List items() { - return items; - } - - @Override - public StepMCToken.MCToken prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - GameOwnership that = (GameOwnership) obj; - return Objects.equals(this.items, that.items) && - Objects.equals(this.prevResult, that.prevResult); - } - - @Override - public int hashCode() { - return Objects.hash(items, prevResult); - } - - @Override - public String toString() { - return "GameOwnership[" + - "items=" + items + ", " + - "prevResult=" + prevResult + ']'; - } - - } - -} diff --git a/src/main/java/net/raphimc/mcauth/step/java/StepMCProfile.java b/src/main/java/net/raphimc/mcauth/step/java/StepMCProfile.java deleted file mode 100644 index da27e47..0000000 --- a/src/main/java/net/raphimc/mcauth/step/java/StepMCProfile.java +++ /dev/null @@ -1,143 +0,0 @@ -/* - * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth - * Copyright (C) 2023 RK_01/RaphiMC and contributors - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package net.raphimc.mcauth.step.java; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.impl.client.BasicResponseHandler; - -import java.io.IOException; -import java.net.URL; -import java.util.Objects; -import java.util.UUID; - -public class StepMCProfile extends AbstractStep { - - public static final String MINECRAFT_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile"; - - public StepMCProfile(AbstractStep prevStep) { - super(prevStep); - } - - @Override - public MCProfile applyStep(HttpClient httpClient, StepGameOwnership.GameOwnership prevResult) throws Exception { - MinecraftAuth.LOGGER.info("Getting profile..."); - - final HttpGet httpGet = new HttpGet(MINECRAFT_PROFILE_URL); - httpGet.addHeader("Authorization", prevResult.prevResult().token_type() + " " + prevResult.prevResult().access_token()); - final String response = httpClient.execute(httpGet, new BasicResponseHandler()); - final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); - - if (obj.has("error")) { - throw new IOException("No valid minecraft profile found: " + obj); - } - - final MCProfile result = new MCProfile( - UUID.fromString(obj.get("id").getAsString().replaceFirst("(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5")), - obj.get("name").getAsString(), - new URL(obj.get("skins").getAsJsonArray().get(0).getAsJsonObject().get("url").getAsString()), - prevResult - ); - MinecraftAuth.LOGGER.info("Got MC Profile, name: " + result.name + ", uuid: " + result.id); - return result; - } - - @Override - public MCProfile fromJson(JsonObject json) throws Exception { - final StepGameOwnership.GameOwnership prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; - return new MCProfile( - UUID.fromString(json.get("id").getAsString()), - json.get("name").getAsString(), - new URL(json.get("skin_url").getAsString()), - prev - ); - } - - public static final class MCProfile implements AbstractStep.StepResult { - - private final UUID id; - private final String name; - private final URL skin_url; - private final StepGameOwnership.GameOwnership prevResult; - - public MCProfile(UUID id, String name, URL skin_url, StepGameOwnership.GameOwnership prevResult) { - this.id = id; - this.name = name; - this.skin_url = skin_url; - this.prevResult = prevResult; - } - - @Override - public JsonObject toJson() { - final JsonObject json = new JsonObject(); - json.addProperty("id", this.id.toString()); - json.addProperty("name", this.name); - json.addProperty("skin_url", this.skin_url.toString()); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); - return json; - } - - public UUID id() { - return id; - } - - public String name() { - return name; - } - - public URL skin_url() { - return skin_url; - } - - @Override - public StepGameOwnership.GameOwnership prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - MCProfile that = (MCProfile) obj; - return Objects.equals(this.id, that.id) && - Objects.equals(this.name, that.name) && - Objects.equals(this.skin_url, that.skin_url) && - Objects.equals(this.prevResult, that.prevResult); - } - - @Override - public int hashCode() { - return Objects.hash(id, name, skin_url, prevResult); - } - - @Override - public String toString() { - return "MCProfile[" + - "id=" + id + ", " + - "name=" + name + ", " + - "skin_url=" + skin_url + ", " + - "prevResult=" + prevResult + ']'; - } - - } - -} diff --git a/src/main/java/net/raphimc/mcauth/step/java/StepMCToken.java b/src/main/java/net/raphimc/mcauth/step/java/StepMCToken.java deleted file mode 100644 index 2161f08..0000000 --- a/src/main/java/net/raphimc/mcauth/step/java/StepMCToken.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth - * Copyright (C) 2023 RK_01/RaphiMC and contributors - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package net.raphimc.mcauth.step.java; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.step.xbl.StepXblXstsToken; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; -import org.apache.http.impl.client.BasicResponseHandler; - -import java.time.Instant; -import java.time.ZoneId; -import java.util.Objects; - -public class StepMCToken extends AbstractStep, StepMCToken.MCToken> { - - public static final String MINECRAFT_LOGIN_URL = "https://api.minecraftservices.com/authentication/login_with_xbox"; - - public StepMCToken(AbstractStep> prevStep) { - super(prevStep); - } - - @Override - public MCToken applyStep(HttpClient httpClient, StepXblXstsToken.XblXsts prevResult) throws Exception { - MinecraftAuth.LOGGER.info("Authenticating with Minecraft Services..."); - - final JsonObject postData = new JsonObject(); - postData.addProperty("identityToken", "XBL3.0 x=" + prevResult.userHash() + ";" + prevResult.token()); - - final HttpPost httpPost = new HttpPost(MINECRAFT_LOGIN_URL); - httpPost.setEntity(new StringEntity(postData.toString(), ContentType.APPLICATION_JSON)); - final String response = httpClient.execute(httpPost, new BasicResponseHandler()); - final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); - - final MCToken result = new MCToken( - obj.get("access_token").getAsString(), - obj.get("token_type").getAsString(), - System.currentTimeMillis() + obj.get("expires_in").getAsLong() * 1000, - prevResult - ); - MinecraftAuth.LOGGER.info("Got MC Token, expires: " + Instant.ofEpochMilli(result.expireTimeMs).atZone(ZoneId.systemDefault())); - return result; - } - - @Override - public MCToken refresh(HttpClient httpClient, MCToken result) throws Exception { - if (result == null || result.isExpired()) return super.refresh(httpClient, result); - - return result; - } - - @Override - public MCToken fromJson(JsonObject json) throws Exception { - final StepXblXstsToken.XblXsts prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; - return new MCToken( - json.get("access_token").getAsString(), - json.get("token_type").getAsString(), - json.get("expireTimeMs").getAsLong(), - prev - ); - } - - public static final class MCToken implements AbstractStep.StepResult> { - - private final String access_token; - private final String token_type; - private final long expireTimeMs; - private final StepXblXstsToken.XblXsts prevResult; - - public MCToken(String access_token, String token_type, long expireTimeMs, StepXblXstsToken.XblXsts prevResult) { - this.access_token = access_token; - this.token_type = token_type; - this.expireTimeMs = expireTimeMs; - this.prevResult = prevResult; - } - - @Override - public JsonObject toJson() { - final JsonObject json = new JsonObject(); - json.addProperty("access_token", this.access_token); - json.addProperty("token_type", this.token_type); - json.addProperty("expireTimeMs", this.expireTimeMs); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); - return json; - } - - @Override - public boolean isExpired() { - return this.expireTimeMs <= System.currentTimeMillis(); - } - - public String access_token() { - return access_token; - } - - public String token_type() { - return token_type; - } - - public long expireTimeMs() { - return expireTimeMs; - } - - @Override - public StepXblXstsToken.XblXsts prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - MCToken that = (MCToken) obj; - return Objects.equals(this.access_token, that.access_token) && - Objects.equals(this.token_type, that.token_type) && - this.expireTimeMs == that.expireTimeMs && - Objects.equals(this.prevResult, that.prevResult); - } - - @Override - public int hashCode() { - return Objects.hash(access_token, token_type, expireTimeMs, prevResult); - } - - @Override - public String toString() { - return "MCToken[" + - "access_token=" + access_token + ", " + - "token_type=" + token_type + ", " + - "expireTimeMs=" + expireTimeMs + ", " + - "prevResult=" + prevResult + ']'; - } - - } - -} diff --git a/src/main/java/net/raphimc/mcauth/step/xbl/StepXblSisuAuthentication.java b/src/main/java/net/raphimc/mcauth/step/xbl/StepXblSisuAuthentication.java deleted file mode 100644 index b3aad69..0000000 --- a/src/main/java/net/raphimc/mcauth/step/xbl/StepXblSisuAuthentication.java +++ /dev/null @@ -1,402 +0,0 @@ -/* - * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth - * Copyright (C) 2023 RK_01/RaphiMC and contributors - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package net.raphimc.mcauth.step.xbl; - -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.step.xbl.session.StepFullXblSession; -import net.raphimc.mcauth.step.xbl.session.StepInitialXblSession; -import net.raphimc.mcauth.util.CryptUtil; -import org.apache.http.client.HttpClient; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.entity.ContentType; -import org.apache.http.entity.StringEntity; - -import java.time.Instant; -import java.time.ZoneId; -import java.util.Objects; - -public class StepXblSisuAuthentication extends AbstractStep> { - - public static final String XBL_SISU_URL = "https://sisu.xboxlive.com/authorize"; - - private final String relyingParty; - - public StepXblSisuAuthentication(AbstractStep prevStep, final String relyingParty) { - super(prevStep); - - this.relyingParty = relyingParty; - } - - @Override - public StepXblSisuAuthentication.XblSisuTokens applyStep(HttpClient httpClient, StepInitialXblSession.InitialXblSession prevResult) throws Exception { - MinecraftAuth.LOGGER.info("Authenticating with Xbox Live using SISU..."); - - if (prevResult.prevResult2() == null) throw new IllegalStateException("A XBL Device Token is needed for SISU authentication"); - - final JsonObject postData = new JsonObject(); - postData.addProperty("AccessToken", "t=" + prevResult.prevResult().access_token()); - postData.addProperty("DeviceToken", prevResult.prevResult2().token()); - postData.addProperty("AppId", prevResult.prevResult().prevResult().clientId()); - postData.add("ProofKey", CryptUtil.getProofKey(prevResult.prevResult2().publicKey())); - postData.addProperty("SiteName", "user.auth.xboxlive.com"); - postData.addProperty("RelyingParty", this.relyingParty); - postData.addProperty("Sandbox", "RETAIL"); - postData.addProperty("UseModernGamertag", true); - - final HttpPost httpPost = new HttpPost(XBL_SISU_URL); - httpPost.setEntity(new StringEntity(postData.toString(), ContentType.APPLICATION_JSON)); - httpPost.addHeader(CryptUtil.getSignatureHeader(httpPost, prevResult.prevResult2().privateKey())); - final String response = httpClient.execute(httpPost, new XblResponseHandler()); - final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); - - final XblSisuTokens result = new XblSisuTokens( - new XblSisuTokens.SisuTitleToken( - Instant.parse(obj.getAsJsonObject("TitleToken").get("NotAfter").getAsString()).toEpochMilli(), - obj.getAsJsonObject("TitleToken").get("Token").getAsString(), - obj.getAsJsonObject("TitleToken").getAsJsonObject("DisplayClaims").getAsJsonObject("xti").get("tid").getAsString() - ), - new XblSisuTokens.SisuUserToken( - Instant.parse(obj.getAsJsonObject("UserToken").get("NotAfter").getAsString()).toEpochMilli(), - obj.getAsJsonObject("UserToken").get("Token").getAsString(), - obj.getAsJsonObject("UserToken").getAsJsonObject("DisplayClaims").getAsJsonArray("xui").get(0).getAsJsonObject().get("uhs").getAsString() - ), - new XblSisuTokens.SisuXstsToken( - Instant.parse(obj.getAsJsonObject("AuthorizationToken").get("NotAfter").getAsString()).toEpochMilli(), - obj.getAsJsonObject("AuthorizationToken").get("Token").getAsString(), - obj.getAsJsonObject("AuthorizationToken").getAsJsonObject("DisplayClaims").getAsJsonArray("xui").get(0).getAsJsonObject().get("uhs").getAsString() - ), - prevResult - ); - - MinecraftAuth.LOGGER.info("Got XBL Title+User+XSTS Token, expires: " + Instant.ofEpochMilli(result.expireTimeMs()).atZone(ZoneId.systemDefault())); - return result; - } - - @Override - public StepXblXstsToken.XblXsts refresh(HttpClient httpClient, StepXblXstsToken.XblXsts result) throws Exception { - if (result == null || result.isExpired()) return super.refresh(httpClient, result); - - return result; - } - - @Override - public StepXblSisuAuthentication.XblSisuTokens fromJson(JsonObject json) throws Exception { - final StepInitialXblSession.InitialXblSession prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; - return new StepXblSisuAuthentication.XblSisuTokens( - XblSisuTokens.SisuTitleToken.fromJson(json.getAsJsonObject("titleToken")), - XblSisuTokens.SisuUserToken.fromJson(json.getAsJsonObject("userToken")), - XblSisuTokens.SisuXstsToken.fromJson(json.getAsJsonObject("xstsToken")), - prev - ); - } - - public static final class XblSisuTokens implements AbstractStep.StepResult, StepXblXstsToken.XblXsts { - - private final SisuTitleToken titleToken; - private final SisuUserToken userToken; - private final SisuXstsToken xstsToken; - private final StepInitialXblSession.InitialXblSession prevResult; - - public XblSisuTokens(SisuTitleToken titleToken, SisuUserToken userToken, SisuXstsToken xstsToken, StepInitialXblSession.InitialXblSession prevResult) { - this.titleToken = titleToken; - this.userToken = userToken; - this.xstsToken = xstsToken; - this.prevResult = prevResult; - } - - @Override - public long expireTimeMs() { - return Math.min(Math.min(this.xstsToken.expireTimeMs, this.titleToken.expireTimeMs), this.userToken.expireTimeMs); - } - - @Override - public String token() { - return this.xstsToken.token; - } - - @Override - public String userHash() { - return this.xstsToken.userHash; - } - - @Override - public JsonObject toJson() { - final JsonObject json = new JsonObject(); - json.add("titleToken", titleToken.toJson()); - json.add("userToken", userToken.toJson()); - json.add("xstsToken", xstsToken.toJson()); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); - return json; - } - - @Override - public boolean isExpired() { - return this.expireTimeMs() <= System.currentTimeMillis(); - } - - @Override - public StepFullXblSession.FullXblSession fullXblSession() { - final StepXblUserToken.XblUserToken userToken = new StepXblUserToken.XblUserToken(this.userToken.expireTimeMs, this.userToken.token, this.userToken.userHash, this.prevResult); - final StepXblTitleToken.XblTitleToken titleToken = new StepXblTitleToken.XblTitleToken(this.titleToken.expireTimeMs, this.titleToken.token, this.titleToken.titleId, this.prevResult); - return new StepFullXblSession.FullXblSession(userToken, titleToken); - } - - public SisuTitleToken titleToken() { - return titleToken; - } - - public SisuUserToken userToken() { - return userToken; - } - - public SisuXstsToken xstsToken() { - return xstsToken; - } - - @Override - public StepInitialXblSession.InitialXblSession prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - XblSisuTokens that = (XblSisuTokens) obj; - return Objects.equals(this.titleToken, that.titleToken) && - Objects.equals(this.userToken, that.userToken) && - Objects.equals(this.xstsToken, that.xstsToken) && - Objects.equals(this.prevResult, that.prevResult); - } - - @Override - public int hashCode() { - return Objects.hash(titleToken, userToken, xstsToken, prevResult); - } - - @Override - public String toString() { - return "XblSisuTokens[" + - "titleToken=" + titleToken + ", " + - "userToken=" + userToken + ", " + - "xstsToken=" + xstsToken + ", " + - "prevResult=" + prevResult + ']'; - } - - - public static final class SisuTitleToken { - - private final long expireTimeMs; - private final String token; - private final String titleId; - - public SisuTitleToken(long expireTimeMs, String token, String titleId) { - this.expireTimeMs = expireTimeMs; - this.token = token; - this.titleId = titleId; - } - - public static SisuTitleToken fromJson(final JsonObject json) { - return new SisuTitleToken( - json.get("expireTimeMs").getAsLong(), - json.get("token").getAsString(), - json.get("titleId").getAsString() - ); - } - - public JsonObject toJson() { - final JsonObject json = new JsonObject(); - json.addProperty("expireTimeMs", this.expireTimeMs); - json.addProperty("token", this.token); - json.addProperty("titleId", this.titleId); - return json; - } - - public long expireTimeMs() { - return expireTimeMs; - } - - public String token() { - return token; - } - - public String titleId() { - return titleId; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - SisuTitleToken that = (SisuTitleToken) obj; - return this.expireTimeMs == that.expireTimeMs && - Objects.equals(this.token, that.token) && - Objects.equals(this.titleId, that.titleId); - } - - @Override - public int hashCode() { - return Objects.hash(expireTimeMs, token, titleId); - } - - @Override - public String toString() { - return "SisuTitleToken[" + - "expireTimeMs=" + expireTimeMs + ", " + - "token=" + token + ", " + - "titleId=" + titleId + ']'; - } - - } - - public static final class SisuUserToken { - - private final long expireTimeMs; - private final String token; - private final String userHash; - - public SisuUserToken(long expireTimeMs, String token, String userHash) { - this.expireTimeMs = expireTimeMs; - this.token = token; - this.userHash = userHash; - } - - public static SisuUserToken fromJson(final JsonObject json) { - return new SisuUserToken( - json.get("expireTimeMs").getAsLong(), - json.get("token").getAsString(), - json.get("userHash").getAsString() - ); - } - - public JsonObject toJson() { - final JsonObject json = new JsonObject(); - json.addProperty("expireTimeMs", this.expireTimeMs); - json.addProperty("token", this.token); - json.addProperty("userHash", this.userHash); - return json; - } - - public long expireTimeMs() { - return expireTimeMs; - } - - public String token() { - return token; - } - - public String userHash() { - return userHash; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - SisuUserToken that = (SisuUserToken) obj; - return this.expireTimeMs == that.expireTimeMs && - Objects.equals(this.token, that.token) && - Objects.equals(this.userHash, that.userHash); - } - - @Override - public int hashCode() { - return Objects.hash(expireTimeMs, token, userHash); - } - - @Override - public String toString() { - return "SisuUserToken[" + - "expireTimeMs=" + expireTimeMs + ", " + - "token=" + token + ", " + - "userHash=" + userHash + ']'; - } - - } - - public static final class SisuXstsToken { - - private final long expireTimeMs; - private final String token; - private final String userHash; - - public SisuXstsToken(long expireTimeMs, String token, String userHash) { - this.expireTimeMs = expireTimeMs; - this.token = token; - this.userHash = userHash; - } - - public static SisuXstsToken fromJson(final JsonObject json) { - return new SisuXstsToken( - json.get("expireTimeMs").getAsLong(), - json.get("token").getAsString(), - json.get("userHash").getAsString() - ); - } - - public JsonObject toJson() { - final JsonObject json = new JsonObject(); - json.addProperty("expireTimeMs", this.expireTimeMs); - json.addProperty("token", this.token); - json.addProperty("userHash", this.userHash); - return json; - } - - public long expireTimeMs() { - return expireTimeMs; - } - - public String token() { - return token; - } - - public String userHash() { - return userHash; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - SisuXstsToken that = (SisuXstsToken) obj; - return this.expireTimeMs == that.expireTimeMs && - Objects.equals(this.token, that.token) && - Objects.equals(this.userHash, that.userHash); - } - - @Override - public int hashCode() { - return Objects.hash(expireTimeMs, token, userHash); - } - - @Override - public String toString() { - return "SisuXstsToken[" + - "expireTimeMs=" + expireTimeMs + ", " + - "token=" + token + ", " + - "userHash=" + userHash + ']'; - } - - } - - } - -} diff --git a/src/main/java/net/raphimc/mcauth/step/xbl/session/StepFullXblSession.java b/src/main/java/net/raphimc/mcauth/step/xbl/session/StepFullXblSession.java deleted file mode 100644 index 0e4f348..0000000 --- a/src/main/java/net/raphimc/mcauth/step/xbl/session/StepFullXblSession.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth - * Copyright (C) 2023 RK_01/RaphiMC and contributors - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package net.raphimc.mcauth.step.xbl.session; - -import com.google.gson.JsonObject; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.step.SameInputOptionalMergeStep; -import net.raphimc.mcauth.step.xbl.StepXblTitleToken; -import net.raphimc.mcauth.step.xbl.StepXblUserToken; -import org.apache.http.client.HttpClient; - -import java.util.Objects; - -public class StepFullXblSession extends SameInputOptionalMergeStep { - - public StepFullXblSession(AbstractStep prevStep1, AbstractStep prevStep2) { - super(prevStep1, prevStep2); - } - - @Override - public FullXblSession applyStep(HttpClient httpClient, StepXblUserToken.XblUserToken prevResult1, StepXblTitleToken.XblTitleToken prevResult2) throws Exception { - return new FullXblSession( - prevResult1, - prevResult2 - ); - } - - @Override - public FullXblSession fromJson(JsonObject json) throws Exception { - final StepXblUserToken.XblUserToken prev1 = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; - final StepXblTitleToken.XblTitleToken prev2 = this.prevStep2 != null ? this.prevStep2.fromJson(json.getAsJsonObject("prev2")) : null; - return new FullXblSession( - prev1, - prev2 - ); - } - - public static final class FullXblSession implements SameInputOptionalMergeStep.StepResult { - - private final StepXblUserToken.XblUserToken prevResult; - private final StepXblTitleToken.XblTitleToken prevResult2; - - public FullXblSession(StepXblUserToken.XblUserToken prevResult, StepXblTitleToken.XblTitleToken prevResult2) { - this.prevResult = prevResult; - this.prevResult2 = prevResult2; - } - - @Override - public JsonObject toJson() { - final JsonObject json = new JsonObject(); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); - if (this.prevResult2 != null) json.add("prev2", this.prevResult2.toJson()); - return json; - } - - @Override - public StepXblUserToken.XblUserToken prevResult() { - return prevResult; - } - - @Override - public StepXblTitleToken.XblTitleToken prevResult2() { - return prevResult2; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - FullXblSession that = (FullXblSession) obj; - return Objects.equals(this.prevResult, that.prevResult) && - Objects.equals(this.prevResult2, that.prevResult2); - } - - @Override - public int hashCode() { - return Objects.hash(prevResult, prevResult2); - } - - @Override - public String toString() { - return "FullXblSession[" + - "prevResult=" + prevResult + ", " + - "prevResult2=" + prevResult2 + ']'; - } - - } - -} diff --git a/src/main/java/net/raphimc/mcauth/step/xbl/session/StepInitialXblSession.java b/src/main/java/net/raphimc/mcauth/step/xbl/session/StepInitialXblSession.java deleted file mode 100644 index 05216ef..0000000 --- a/src/main/java/net/raphimc/mcauth/step/xbl/session/StepInitialXblSession.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth - * Copyright (C) 2023 RK_01/RaphiMC and contributors - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ -package net.raphimc.mcauth.step.xbl.session; - -import com.google.gson.JsonObject; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.step.OptionalMergeStep; -import net.raphimc.mcauth.step.msa.StepMsaToken; -import net.raphimc.mcauth.step.xbl.StepXblDeviceToken; -import org.apache.http.client.HttpClient; - -import java.util.Objects; - -public class StepInitialXblSession extends OptionalMergeStep { - - public StepInitialXblSession(AbstractStep prevStep1, AbstractStep prevStep2) { - super(prevStep1, prevStep2); - } - - @Override - public InitialXblSession applyStep(HttpClient httpClient, StepMsaToken.MsaToken prevResult1, StepXblDeviceToken.XblDeviceToken prevResult2) throws Exception { - return new InitialXblSession( - prevResult1, - prevResult2 - ); - } - - @Override - public InitialXblSession fromJson(JsonObject json) throws Exception { - final StepMsaToken.MsaToken prev1 = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; - final StepXblDeviceToken.XblDeviceToken prev2 = this.prevStep2 != null ? this.prevStep2.fromJson(json.getAsJsonObject("prev2")) : null; - return new InitialXblSession( - prev1, - prev2 - ); - } - - public static final class InitialXblSession implements OptionalMergeStep.StepResult { - - private final StepMsaToken.MsaToken prevResult; - private final StepXblDeviceToken.XblDeviceToken prevResult2; - - public InitialXblSession(StepMsaToken.MsaToken prevResult, StepXblDeviceToken.XblDeviceToken prevResult2) { - this.prevResult = prevResult; - this.prevResult2 = prevResult2; - } - - @Override - public JsonObject toJson() { - final JsonObject json = new JsonObject(); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); - if (this.prevResult2 != null) json.add("prev2", this.prevResult2.toJson()); - return json; - } - - @Override - public StepMsaToken.MsaToken prevResult() { - return prevResult; - } - - @Override - public StepXblDeviceToken.XblDeviceToken prevResult2() { - return prevResult2; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - InitialXblSession that = (InitialXblSession) obj; - return Objects.equals(this.prevResult, that.prevResult) && - Objects.equals(this.prevResult2, that.prevResult2); - } - - @Override - public int hashCode() { - return Objects.hash(prevResult, prevResult2); - } - - @Override - public String toString() { - return "InitialXblSession[" + - "prevResult=" + prevResult + ", " + - "prevResult2=" + prevResult2 + ']'; - } - - } - -} diff --git a/src/main/java/net/raphimc/mcauth/MinecraftAuth.java b/src/main/java/net/raphimc/minecraftauth/MinecraftAuth.java similarity index 85% rename from src/main/java/net/raphimc/mcauth/MinecraftAuth.java rename to src/main/java/net/raphimc/minecraftauth/MinecraftAuth.java index 71f32d0..7984308 100644 --- a/src/main/java/net/raphimc/mcauth/MinecraftAuth.java +++ b/src/main/java/net/raphimc/minecraftauth/MinecraftAuth.java @@ -15,27 +15,29 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth; - -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.step.OptionalMergeStep; -import net.raphimc.mcauth.step.bedrock.StepMCChain; -import net.raphimc.mcauth.step.bedrock.StepPlayFabToken; -import net.raphimc.mcauth.step.java.StepGameOwnership; -import net.raphimc.mcauth.step.java.StepMCProfile; -import net.raphimc.mcauth.step.java.StepMCToken; -import net.raphimc.mcauth.step.java.StepPlayerCertificates; -import net.raphimc.mcauth.step.msa.*; -import net.raphimc.mcauth.step.xbl.*; -import net.raphimc.mcauth.step.xbl.session.StepFullXblSession; -import net.raphimc.mcauth.step.xbl.session.StepInitialXblSession; -import net.raphimc.mcauth.util.MicrosoftConstants; -import net.raphimc.mcauth.util.logging.ConsoleLogger; -import net.raphimc.mcauth.util.logging.ILogger; +package net.raphimc.minecraftauth; + +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.OptionalMergeStep; +import net.raphimc.minecraftauth.step.bedrock.StepMCChain; +import net.raphimc.minecraftauth.step.bedrock.StepPlayFabToken; +import net.raphimc.minecraftauth.step.java.StepMCProfile; +import net.raphimc.minecraftauth.step.java.StepMCToken; +import net.raphimc.minecraftauth.step.java.StepPlayerCertificates; +import net.raphimc.minecraftauth.step.msa.*; +import net.raphimc.minecraftauth.step.xbl.*; +import net.raphimc.minecraftauth.step.xbl.session.StepFullXblSession; +import net.raphimc.minecraftauth.step.xbl.session.StepInitialXblSession; +import net.raphimc.minecraftauth.util.MicrosoftConstants; +import net.raphimc.minecraftauth.util.logging.ConsoleLogger; +import net.raphimc.minecraftauth.util.logging.ILogger; import org.apache.http.client.HttpClient; public class MinecraftAuth { + public static final String VERSION = "${version}"; + public static final String IMPL_VERSION = "${impl_version}"; + public static ILogger LOGGER = new ConsoleLogger(); public static final AbstractStep JAVA_DEVICE_CODE_LOGIN = builder() @@ -147,7 +149,7 @@ public MsaTokenBuilder withRedirectUri(final String redirectUri) { /** * Uses the device code flow to get an MSA token. This is the recommended way to get an MSA token. - * Needs instance of {@link net.raphimc.mcauth.step.msa.StepMsaDeviceCode.MsaDeviceCodeCallback} as input when calling {@link AbstractStep#getFromInput(HttpClient, Object)}. + * Needs instance of {@link net.raphimc.minecraftauth.step.msa.StepMsaDeviceCode.MsaDeviceCodeCallback} as input when calling {@link AbstractStep#getFromInput(HttpClient, Object)}. * * @return The builder */ @@ -159,7 +161,7 @@ public InitialXblSessionBuilder deviceCode() { /** * Generates a URL to open in the browser to get an MSA token. The browser redirects to a localhost URL with the token as a parameter when the user logged in. - * Needs instance of {@link net.raphimc.mcauth.step.msa.StepExternalBrowser.ExternalBrowserCallback} as input when calling {@link AbstractStep#getFromInput(HttpClient, Object)}. + * Needs instance of {@link net.raphimc.minecraftauth.step.msa.StepExternalBrowser.ExternalBrowserCallback} as input when calling {@link AbstractStep#getFromInput(HttpClient, Object)}. * * @return The builder */ @@ -175,7 +177,7 @@ public InitialXblSessionBuilder externalBrowser() { /** * Logs in with a Microsoft account's credentials and gets an MSA token. - * Needs instance of {@link net.raphimc.mcauth.step.msa.StepCredentialsMsaCode.MsaCredentials} as input when calling {@link AbstractStep#getFromInput(HttpClient, Object)}. + * Needs instance of {@link net.raphimc.minecraftauth.step.msa.StepCredentialsMsaCode.MsaCredentials} as input when calling {@link AbstractStep#getFromInput(HttpClient, Object)}. * * @return The builder */ @@ -269,8 +271,8 @@ private MinecraftBuilder(final XblXstsTokenBuilder parent) { this.xblXstsTokenStep = parent.build(); } - public AbstractStep buildMinecraftJavaProfileStep() { - return new StepMCProfile(new StepGameOwnership(new StepMCToken(this.xblXstsTokenStep))); + public AbstractStep buildMinecraftJavaProfileStep() { + return new StepMCProfile(new StepMCToken(this.xblXstsTokenStep)); } public AbstractStep, StepMCChain.MCChain> buildMinecraftBedrockChainStep() { diff --git a/src/main/java/net/raphimc/mcauth/step/AbstractStep.java b/src/main/java/net/raphimc/minecraftauth/step/AbstractStep.java similarity index 98% rename from src/main/java/net/raphimc/mcauth/step/AbstractStep.java rename to src/main/java/net/raphimc/minecraftauth/step/AbstractStep.java index b9450a1..d847689 100644 --- a/src/main/java/net/raphimc/mcauth/step/AbstractStep.java +++ b/src/main/java/net/raphimc/minecraftauth/step/AbstractStep.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step; +package net.raphimc.minecraftauth.step; import com.google.gson.JsonObject; import org.apache.http.client.HttpClient; diff --git a/src/main/java/net/raphimc/mcauth/step/OptionalMergeStep.java b/src/main/java/net/raphimc/minecraftauth/step/OptionalMergeStep.java similarity index 98% rename from src/main/java/net/raphimc/mcauth/step/OptionalMergeStep.java rename to src/main/java/net/raphimc/minecraftauth/step/OptionalMergeStep.java index 335dcc9..78068c3 100644 --- a/src/main/java/net/raphimc/mcauth/step/OptionalMergeStep.java +++ b/src/main/java/net/raphimc/minecraftauth/step/OptionalMergeStep.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step; +package net.raphimc.minecraftauth.step; import org.apache.http.client.HttpClient; diff --git a/src/main/java/net/raphimc/mcauth/step/SameInputOptionalMergeStep.java b/src/main/java/net/raphimc/minecraftauth/step/SameInputOptionalMergeStep.java similarity index 98% rename from src/main/java/net/raphimc/mcauth/step/SameInputOptionalMergeStep.java rename to src/main/java/net/raphimc/minecraftauth/step/SameInputOptionalMergeStep.java index 9fd86d1..8ccae5f 100644 --- a/src/main/java/net/raphimc/mcauth/step/SameInputOptionalMergeStep.java +++ b/src/main/java/net/raphimc/minecraftauth/step/SameInputOptionalMergeStep.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step; +package net.raphimc.minecraftauth.step; import org.apache.http.client.HttpClient; diff --git a/src/main/java/net/raphimc/mcauth/step/bedrock/PlayFabResponseHandler.java b/src/main/java/net/raphimc/minecraftauth/step/bedrock/PlayFabResponseHandler.java similarity index 97% rename from src/main/java/net/raphimc/mcauth/step/bedrock/PlayFabResponseHandler.java rename to src/main/java/net/raphimc/minecraftauth/step/bedrock/PlayFabResponseHandler.java index 7b4a17a..670877d 100644 --- a/src/main/java/net/raphimc/mcauth/step/bedrock/PlayFabResponseHandler.java +++ b/src/main/java/net/raphimc/minecraftauth/step/bedrock/PlayFabResponseHandler.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.bedrock; +package net.raphimc.minecraftauth.step.bedrock; import com.google.gson.JsonObject; import com.google.gson.JsonParser; diff --git a/src/main/java/net/raphimc/mcauth/step/bedrock/StepMCChain.java b/src/main/java/net/raphimc/minecraftauth/step/bedrock/StepMCChain.java similarity index 53% rename from src/main/java/net/raphimc/mcauth/step/bedrock/StepMCChain.java rename to src/main/java/net/raphimc/minecraftauth/step/bedrock/StepMCChain.java index 4d187e7..d959166 100644 --- a/src/main/java/net/raphimc/mcauth/step/bedrock/StepMCChain.java +++ b/src/main/java/net/raphimc/minecraftauth/step/bedrock/StepMCChain.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.bedrock; +package net.raphimc.minecraftauth.step.bedrock; import com.google.gson.JsonArray; import com.google.gson.JsonObject; @@ -23,10 +23,11 @@ import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.step.xbl.StepXblXstsToken; -import net.raphimc.mcauth.util.CryptUtil; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.xbl.StepXblXstsToken; +import net.raphimc.minecraftauth.util.CryptUtil; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; @@ -38,12 +39,8 @@ import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; import java.util.Base64; import java.util.Map; -import java.util.Objects; import java.util.UUID; public class StepMCChain extends AbstractStep, StepMCChain.MCChain> { @@ -51,15 +48,15 @@ public class StepMCChain extends AbstractStep, StepM public static final String MINECRAFT_LOGIN_URL = "https://multiplayer.minecraft.net/authentication"; private static final String MOJANG_PUBLIC_KEY_BASE64 = "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAECRXueJeTDqNRRgJi/vlRufByu/2G0i2Ebt6YMar5QX/R0DIIyrJMcUpruK4QveTfJSTp3Shlq4Gk34cD/4GUWwkv0DVuzeuB+tXija7HBxii03NHDbPAD0AKnLr2wdAp"; - private static final ECPublicKey MOJANG_PUBLIC_KEY = publicKeyFromBase64(MOJANG_PUBLIC_KEY_BASE64); + private static final ECPublicKey MOJANG_PUBLIC_KEY = CryptUtil.publicKeyFromBase64(MOJANG_PUBLIC_KEY_BASE64); private static final int CLOCK_SKEW = 60; - public StepMCChain(AbstractStep> prevStep) { + public StepMCChain(final AbstractStep> prevStep) { super(prevStep); } @Override - public MCChain applyStep(HttpClient httpClient, StepXblXstsToken.XblXsts prevResult) throws Exception { + public MCChain applyStep(final HttpClient httpClient, final StepXblXstsToken.XblXsts prevResult) throws Exception { MinecraftAuth.LOGGER.info("Authenticating with Minecraft Services..."); final KeyPairGenerator secp384r1 = KeyPairGenerator.getInstance("EC"); @@ -73,14 +70,14 @@ public MCChain applyStep(HttpClient httpClient, StepXblXstsToken.XblXsts prev final HttpPost httpPost = new HttpPost(MINECRAFT_LOGIN_URL); httpPost.setEntity(new StringEntity(postData.toString(), ContentType.APPLICATION_JSON)); - httpPost.addHeader("Authorization", "XBL3.0 x=" + prevResult.userHash() + ";" + prevResult.token()); + httpPost.addHeader("Authorization", "XBL3.0 x=" + prevResult.getUserHash() + ";" + prevResult.getToken()); final String response = httpClient.execute(httpPost, new BasicResponseHandler()); final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); final JsonArray chain = obj.get("chain").getAsJsonArray(); if (chain.size() != 2) throw new IllegalStateException("Invalid chain size"); final Jws mojangJwt = Jwts.parser().clockSkewSeconds(CLOCK_SKEW).verifyWith(MOJANG_PUBLIC_KEY).build().parseSignedClaims(chain.get(0).getAsString()); - final ECPublicKey mojangJwtPublicKey = publicKeyFromBase64(mojangJwt.getPayload().get("identityPublicKey", String.class)); + final ECPublicKey mojangJwtPublicKey = CryptUtil.publicKeyFromBase64(mojangJwt.getPayload().get("identityPublicKey", String.class)); final Jws identityJwt = Jwts.parser().clockSkewSeconds(CLOCK_SKEW).verifyWith(mojangJwtPublicKey).build().parseSignedClaims(chain.get(1).getAsString()); final Map extraData = identityJwt.getPayload().get("extraData", Map.class); @@ -107,56 +104,42 @@ public MCChain applyStep(HttpClient httpClient, StepXblXstsToken.XblXsts prev } @Override - public MCChain refresh(HttpClient httpClient, MCChain result) throws Exception { + public MCChain refresh(final HttpClient httpClient, final MCChain result) throws Exception { if (result == null || result.isExpired()) return super.refresh(httpClient, result); return result; } @Override - public MCChain fromJson(JsonObject json) throws Exception { - final StepXblXstsToken.XblXsts prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; + public MCChain fromJson(final JsonObject json) throws Exception { + final StepXblXstsToken.XblXsts xblXsts = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("xblXsts")) : null; return new MCChain( - publicKeyFromBase64(json.get("publicKey").getAsString()), - (ECPrivateKey) CryptUtil.EC_KEYFACTORY.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(json.get("privateKey").getAsString()))), + CryptUtil.publicKeyFromBase64(json.get("publicKey").getAsString()), + CryptUtil.privateKeyFromBase64(json.get("privateKey").getAsString()), json.get("mojangJwt").getAsString(), json.get("identityJwt").getAsString(), json.get("xuid").getAsString(), UUID.fromString(json.get("id").getAsString()), json.get("displayName").getAsString(), - prev + xblXsts ); } - private static ECPublicKey publicKeyFromBase64(final String base64) { - try { - return (ECPublicKey) CryptUtil.EC_KEYFACTORY.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(base64))); - } catch (InvalidKeySpecException e) { - throw new RuntimeException("Could not decode base64 public key", e); - } - } + @Value + public static class MCChain implements AbstractStep.StepResult> { - public static final class MCChain implements AbstractStep.StepResult> { - - private final ECPublicKey publicKey; - private final ECPrivateKey privateKey; - private final String mojangJwt; - private final String identityJwt; - private final String xuid; - private final UUID id; - private final String displayName; - private final StepXblXstsToken.XblXsts prevResult; - - public MCChain(ECPublicKey publicKey, ECPrivateKey privateKey, String mojangJwt, String identityJwt, String xuid, UUID id, String displayName, - StepXblXstsToken.XblXsts prevResult) { - this.publicKey = publicKey; - this.privateKey = privateKey; - this.mojangJwt = mojangJwt; - this.identityJwt = identityJwt; - this.xuid = xuid; - this.id = id; - this.displayName = displayName; - this.prevResult = prevResult; + ECPublicKey publicKey; + ECPrivateKey privateKey; + String mojangJwt; + String identityJwt; + String xuid; + UUID id; + String displayName; + StepXblXstsToken.XblXsts xblXsts; + + @Override + public StepXblXstsToken.XblXsts prevResult() { + return this.xblXsts; } @Override @@ -169,76 +152,10 @@ public JsonObject toJson() { json.addProperty("xuid", this.xuid); json.addProperty("id", this.id.toString()); json.addProperty("displayName", this.displayName); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); + if (this.xblXsts != null) json.add("xblXsts", this.xblXsts.toJson()); return json; } - public ECPublicKey publicKey() { - return publicKey; - } - - public ECPrivateKey privateKey() { - return privateKey; - } - - public String mojangJwt() { - return mojangJwt; - } - - public String identityJwt() { - return identityJwt; - } - - public String xuid() { - return xuid; - } - - public UUID id() { - return id; - } - - public String displayName() { - return displayName; - } - - @Override - public StepXblXstsToken.XblXsts prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - MCChain that = (MCChain) obj; - return Objects.equals(this.publicKey, that.publicKey) && - Objects.equals(this.privateKey, that.privateKey) && - Objects.equals(this.mojangJwt, that.mojangJwt) && - Objects.equals(this.identityJwt, that.identityJwt) && - Objects.equals(this.xuid, that.xuid) && - Objects.equals(this.id, that.id) && - Objects.equals(this.displayName, that.displayName) && - Objects.equals(this.prevResult, that.prevResult); - } - - @Override - public int hashCode() { - return Objects.hash(publicKey, privateKey, mojangJwt, identityJwt, xuid, id, displayName, prevResult); - } - - @Override - public String toString() { - return "MCChain[" + - "publicKey=" + publicKey + ", " + - "privateKey=" + privateKey + ", " + - "mojangJwt=" + mojangJwt + ", " + - "identityJwt=" + identityJwt + ", " + - "xuid=" + xuid + ", " + - "id=" + id + ", " + - "displayName=" + displayName + ", " + - "prevResult=" + prevResult + ']'; - } - } } diff --git a/src/main/java/net/raphimc/mcauth/step/bedrock/StepPlayFabToken.java b/src/main/java/net/raphimc/minecraftauth/step/bedrock/StepPlayFabToken.java similarity index 60% rename from src/main/java/net/raphimc/mcauth/step/bedrock/StepPlayFabToken.java rename to src/main/java/net/raphimc/minecraftauth/step/bedrock/StepPlayFabToken.java index 8a362e3..f146f2c 100644 --- a/src/main/java/net/raphimc/mcauth/step/bedrock/StepPlayFabToken.java +++ b/src/main/java/net/raphimc/minecraftauth/step/bedrock/StepPlayFabToken.java @@ -15,14 +15,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.bedrock; +package net.raphimc.minecraftauth.step.bedrock; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.step.xbl.StepXblXstsToken; -import net.raphimc.mcauth.util.MicrosoftConstants; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.xbl.StepXblXstsToken; +import net.raphimc.minecraftauth.util.MicrosoftConstants; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; @@ -30,18 +31,17 @@ import java.time.Instant; import java.time.ZoneId; -import java.util.Objects; public class StepPlayFabToken extends AbstractStep, StepPlayFabToken.PlayFabToken> { public static final String PLAY_FAB_URL = "https://" + MicrosoftConstants.BEDROCK_PLAY_FAB_TITLE_ID.toLowerCase() + ".playfabapi.com/Client/LoginWithXbox"; - public StepPlayFabToken(AbstractStep> prevStep) { + public StepPlayFabToken(final AbstractStep> prevStep) { super(prevStep); } @Override - public PlayFabToken applyStep(HttpClient httpClient, StepXblXstsToken.XblXsts prevResult) throws Exception { + public PlayFabToken applyStep(final HttpClient httpClient, final StepXblXstsToken.XblXsts prevResult) throws Exception { MinecraftAuth.LOGGER.info("Authenticating with PlayFab..."); final JsonObject postData = new JsonObject(); @@ -66,7 +66,7 @@ public PlayFabToken applyStep(HttpClient httpClient, StepXblXstsToken.XblXsts postData.add("InfoRequestParameters", infoRequestParameters); postData.add("PlayerSecret", null); postData.addProperty("TitleId", MicrosoftConstants.BEDROCK_PLAY_FAB_TITLE_ID); - postData.addProperty("XboxToken", "XBL3.0 x=" + prevResult.userHash() + ";" + prevResult.token()); + postData.addProperty("XboxToken", "XBL3.0 x=" + prevResult.getUserHash() + ";" + prevResult.getToken()); final HttpPost httpPost = new HttpPost(PLAY_FAB_URL); httpPost.setEntity(new StringEntity(postData.toString(), ContentType.APPLICATION_JSON)); @@ -83,46 +83,43 @@ public PlayFabToken applyStep(HttpClient httpClient, StepXblXstsToken.XblXsts data.get("PlayFabId").getAsString(), prevResult ); - MinecraftAuth.LOGGER.info("Got PlayFab Token, expires: " + Instant.ofEpochMilli(result.expireTimeMs).atZone(ZoneId.systemDefault())); + MinecraftAuth.LOGGER.info("Got PlayFab Token, expires: " + Instant.ofEpochMilli(result.getExpireTimeMs()).atZone(ZoneId.systemDefault())); return result; } @Override - public PlayFabToken refresh(HttpClient httpClient, PlayFabToken result) throws Exception { + public PlayFabToken refresh(final HttpClient httpClient, final PlayFabToken result) throws Exception { if (result == null || result.isExpired()) return super.refresh(httpClient, result); return result; } @Override - public PlayFabToken fromJson(JsonObject json) throws Exception { - final StepXblXstsToken.XblXsts prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; + public PlayFabToken fromJson(final JsonObject json) throws Exception { + final StepXblXstsToken.XblXsts xblXsts = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("xblXsts")) : null; return new PlayFabToken( json.get("expireTimeMs").getAsLong(), json.get("entityToken").getAsString(), json.get("entityId").getAsString(), json.get("sessionTicket").getAsString(), json.get("playFabId").getAsString(), - prev + xblXsts ); } - public static final class PlayFabToken implements AbstractStep.StepResult> { - - private final long expireTimeMs; - private final String entityToken; - private final String entityId; - private final String sessionTicket; - private final String playFabId; - private final StepXblXstsToken.XblXsts prevResult; - - public PlayFabToken(long expireTimeMs, String entityToken, String entityId, String sessionTicket, String playFabId, StepXblXstsToken.XblXsts prevResult) { - this.expireTimeMs = expireTimeMs; - this.entityToken = entityToken; - this.entityId = entityId; - this.sessionTicket = sessionTicket; - this.playFabId = playFabId; - this.prevResult = prevResult; + @Value + public static class PlayFabToken implements AbstractStep.StepResult> { + + long expireTimeMs; + String entityToken; + String entityId; + String sessionTicket; + String playFabId; + StepXblXstsToken.XblXsts xblXsts; + + @Override + public StepXblXstsToken.XblXsts prevResult() { + return this.xblXsts; } @Override @@ -133,7 +130,7 @@ public JsonObject toJson() { json.addProperty("entityId", this.entityId); json.addProperty("sessionTicket", this.sessionTicket); json.addProperty("playFabId", this.playFabId); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); + if (this.xblXsts != null) json.add("xblXsts", this.xblXsts.toJson()); return json; } @@ -142,56 +139,6 @@ public boolean isExpired() { return this.expireTimeMs <= System.currentTimeMillis(); } - public long expireTimeMs() { - return expireTimeMs; - } - - public String entityToken() { - return entityToken; - } - - public String entityId() { - return entityId; - } - - public String sessionTicket() { - return sessionTicket; - } - - public String playFabId() { - return playFabId; - } - - @Override - public StepXblXstsToken.XblXsts prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PlayFabToken that = (PlayFabToken) o; - return expireTimeMs == that.expireTimeMs && Objects.equals(entityToken, that.entityToken) && Objects.equals(entityId, that.entityId) && Objects.equals(sessionTicket, that.sessionTicket) && Objects.equals(playFabId, that.playFabId) && Objects.equals(prevResult, that.prevResult); - } - - @Override - public int hashCode() { - return Objects.hash(expireTimeMs, entityToken, entityId, sessionTicket, playFabId, prevResult); - } - - @Override - public String toString() { - return "PlayFabToken{" + - "expireTimeMs=" + expireTimeMs + - ", entityToken='" + entityToken + '\'' + - ", entityId='" + entityId + '\'' + - ", sessionTicket='" + sessionTicket + '\'' + - ", playFabId='" + playFabId + '\'' + - ", prevResult=" + prevResult + - '}'; - } - } } diff --git a/src/main/java/net/raphimc/minecraftauth/step/java/StepMCProfile.java b/src/main/java/net/raphimc/minecraftauth/step/java/StepMCProfile.java new file mode 100644 index 0000000..e0cb199 --- /dev/null +++ b/src/main/java/net/raphimc/minecraftauth/step/java/StepMCProfile.java @@ -0,0 +1,100 @@ +/* + * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.minecraftauth.step.java; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.BasicResponseHandler; + +import java.io.IOException; +import java.net.URL; +import java.util.UUID; + +public class StepMCProfile extends AbstractStep { + + public static final String MINECRAFT_PROFILE_URL = "https://api.minecraftservices.com/minecraft/profile"; + + public StepMCProfile(final AbstractStep prevStep) { + super(prevStep); + } + + @Override + public MCProfile applyStep(final HttpClient httpClient, final StepMCToken.MCToken mcToken) throws Exception { + MinecraftAuth.LOGGER.info("Getting profile..."); + + final HttpGet httpGet = new HttpGet(MINECRAFT_PROFILE_URL); + httpGet.addHeader("Authorization", mcToken.getTokenType() + " " + mcToken.getAccessToken()); + final String response = httpClient.execute(httpGet, new BasicResponseHandler()); + final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); + + if (obj.has("error")) { + throw new IOException("No valid minecraft profile found: " + obj); + } + + final MCProfile result = new MCProfile( + UUID.fromString(obj.get("id").getAsString().replaceFirst("(\\p{XDigit}{8})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}{4})(\\p{XDigit}+)", "$1-$2-$3-$4-$5")), + obj.get("name").getAsString(), + new URL(obj.get("skins").getAsJsonArray().get(0).getAsJsonObject().get("url").getAsString()), + mcToken + ); + MinecraftAuth.LOGGER.info("Got MC Profile, name: " + result.name + ", uuid: " + result.id); + return result; + } + + @Override + public MCProfile fromJson(final JsonObject json) throws Exception { + final StepMCToken.MCToken mcToken = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("mcToken")) : null; + return new MCProfile( + UUID.fromString(json.get("id").getAsString()), + json.get("name").getAsString(), + new URL(json.get("skinUrl").getAsString()), + mcToken + ); + } + + @Value + public static class MCProfile implements AbstractStep.StepResult { + + UUID id; + String name; + URL skinUrl; + StepMCToken.MCToken mcToken; + + @Override + public StepMCToken.MCToken prevResult() { + return this.mcToken; + } + + @Override + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + json.addProperty("id", this.id.toString()); + json.addProperty("name", this.name); + json.addProperty("skinUrl", this.skinUrl.toString()); + if (this.mcToken != null) json.add("mcToken", this.mcToken.toJson()); + return json; + } + + } + +} diff --git a/src/main/java/net/raphimc/minecraftauth/step/java/StepMCToken.java b/src/main/java/net/raphimc/minecraftauth/step/java/StepMCToken.java new file mode 100644 index 0000000..90beaf4 --- /dev/null +++ b/src/main/java/net/raphimc/minecraftauth/step/java/StepMCToken.java @@ -0,0 +1,113 @@ +/* + * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.minecraftauth.step.java; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.xbl.StepXblXstsToken; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicResponseHandler; + +import java.time.Instant; +import java.time.ZoneId; + +public class StepMCToken extends AbstractStep, StepMCToken.MCToken> { + + public static final String MINECRAFT_LOGIN_URL = "https://api.minecraftservices.com/authentication/login_with_xbox"; + + public StepMCToken(final AbstractStep> prevStep) { + super(prevStep); + } + + @Override + public MCToken applyStep(final HttpClient httpClient, final StepXblXstsToken.XblXsts xblXsts) throws Exception { + MinecraftAuth.LOGGER.info("Authenticating with Minecraft Services..."); + + final JsonObject postData = new JsonObject(); + postData.addProperty("identityToken", "XBL3.0 x=" + xblXsts.getUserHash() + ";" + xblXsts.getToken()); + + final HttpPost httpPost = new HttpPost(MINECRAFT_LOGIN_URL); + httpPost.setEntity(new StringEntity(postData.toString(), ContentType.APPLICATION_JSON)); + final String response = httpClient.execute(httpPost, new BasicResponseHandler()); + final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); + + final MCToken result = new MCToken( + obj.get("access_token").getAsString(), + obj.get("token_type").getAsString(), + System.currentTimeMillis() + obj.get("expires_in").getAsLong() * 1000, + xblXsts + ); + MinecraftAuth.LOGGER.info("Got MC Token, expires: " + Instant.ofEpochMilli(result.getExpireTimeMs()).atZone(ZoneId.systemDefault())); + return result; + } + + @Override + public MCToken refresh(final HttpClient httpClient, final MCToken result) throws Exception { + if (result == null || result.isExpired()) return super.refresh(httpClient, result); + + return result; + } + + @Override + public MCToken fromJson(final JsonObject json) throws Exception { + final StepXblXstsToken.XblXsts xblXsts = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("xblXsts")) : null; + return new MCToken( + json.get("accessToken").getAsString(), + json.get("tokenType").getAsString(), + json.get("expireTimeMs").getAsLong(), + xblXsts + ); + } + + @Value + public static class MCToken implements AbstractStep.StepResult> { + + String accessToken; + String tokenType; + long expireTimeMs; + StepXblXstsToken.XblXsts xblXsts; + + @Override + public StepXblXstsToken.XblXsts prevResult() { + return this.xblXsts; + } + + @Override + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + json.addProperty("accessToken", this.accessToken); + json.addProperty("tokenType", this.tokenType); + json.addProperty("expireTimeMs", this.expireTimeMs); + if (this.xblXsts != null) json.add("xblXsts", this.xblXsts.toJson()); + return json; + } + + @Override + public boolean isExpired() { + return this.expireTimeMs <= System.currentTimeMillis(); + } + + } + +} diff --git a/src/main/java/net/raphimc/mcauth/step/java/StepPlayerCertificates.java b/src/main/java/net/raphimc/minecraftauth/step/java/StepPlayerCertificates.java similarity index 53% rename from src/main/java/net/raphimc/mcauth/step/java/StepPlayerCertificates.java rename to src/main/java/net/raphimc/minecraftauth/step/java/StepPlayerCertificates.java index f7e0e40..a9b6d7e 100644 --- a/src/main/java/net/raphimc/mcauth/step/java/StepPlayerCertificates.java +++ b/src/main/java/net/raphimc/minecraftauth/step/java/StepPlayerCertificates.java @@ -15,13 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.java; +package net.raphimc.minecraftauth.step.java; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.util.CryptUtil; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.util.CryptUtil; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; @@ -34,25 +35,23 @@ import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.time.ZoneId; -import java.util.Arrays; import java.util.Base64; -import java.util.Objects; public class StepPlayerCertificates extends AbstractStep { public static final String PLAYER_CERTIFICATES_URL = "https://api.minecraftservices.com/player/certificates"; - public StepPlayerCertificates(AbstractStep prevStep) { + public StepPlayerCertificates(final AbstractStep prevStep) { super(prevStep); } @Override - public PlayerCertificates applyStep(HttpClient httpClient, StepMCToken.MCToken prevResult) throws Exception { + public PlayerCertificates applyStep(final HttpClient httpClient, final StepMCToken.MCToken mcToken) throws Exception { MinecraftAuth.LOGGER.info("Getting player certificates..."); final HttpPost httpPost = new HttpPost(PLAYER_CERTIFICATES_URL); httpPost.setEntity(new StringEntity("", ContentType.APPLICATION_JSON)); - httpPost.addHeader("Authorization", "Bearer " + prevResult.access_token()); + httpPost.addHeader("Authorization", "Bearer " + mcToken.getAccessToken()); final String response = httpClient.execute(httpPost, new BasicResponseHandler()); final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); final JsonObject keyPair = obj.getAsJsonObject("keyPair"); @@ -75,48 +74,45 @@ public PlayerCertificates applyStep(HttpClient httpClient, StepMCToken.MCToken p privateKey, Base64.getMimeDecoder().decode(obj.get("publicKeySignatureV2").getAsString()), obj.has("publicKeySignature") ? Base64.getMimeDecoder().decode(obj.get("publicKeySignature").getAsString()) : new byte[0], - prevResult + mcToken ); - MinecraftAuth.LOGGER.info("Got player certificates, expires: " + Instant.ofEpochMilli(result.expireTimeMs).atZone(ZoneId.systemDefault())); + MinecraftAuth.LOGGER.info("Got player certificates, expires: " + Instant.ofEpochMilli(result.getExpireTimeMs()).atZone(ZoneId.systemDefault())); return result; } @Override - public PlayerCertificates refresh(HttpClient httpClient, PlayerCertificates result) throws Exception { + public PlayerCertificates refresh(final HttpClient httpClient, final PlayerCertificates result) throws Exception { if (result == null || result.isExpired()) return super.refresh(httpClient, result); return result; } @Override - public PlayerCertificates fromJson(JsonObject json) throws Exception { - final StepMCToken.MCToken prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; + public PlayerCertificates fromJson(final JsonObject json) throws Exception { + final StepMCToken.MCToken mcToken = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("mcToken")) : null; return new PlayerCertificates( json.get("expireTimeMs").getAsLong(), - (RSAPublicKey) CryptUtil.RSA_KEYFACTORY.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(json.get("publicKey").getAsString()))), - (RSAPrivateKey) CryptUtil.RSA_KEYFACTORY.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(json.get("privateKey").getAsString()))), + CryptUtil.publicKeyFromBase64(json.get("publicKey").getAsString()), + CryptUtil.privateKeyFromBase64(json.get("privateKey").getAsString()), Base64.getDecoder().decode(json.get("publicKeySignature").getAsString()), Base64.getDecoder().decode(json.get("legacyPublicKeySignature").getAsString()), - prev + mcToken ); } - public static final class PlayerCertificates implements AbstractStep.StepResult { - - private final long expireTimeMs; - private final RSAPublicKey publicKey; - private final RSAPrivateKey privateKey; - private final byte[] publicKeySignature; - private final byte[] legacyPublicKeySignature; - private final StepMCToken.MCToken prevResult; - - public PlayerCertificates(long expireTimeMs, RSAPublicKey publicKey, RSAPrivateKey privateKey, byte[] publicKeySignature, byte[] legacyPublicKeySignature, StepMCToken.MCToken prevResult) { - this.expireTimeMs = expireTimeMs; - this.publicKey = publicKey; - this.privateKey = privateKey; - this.publicKeySignature = publicKeySignature; - this.legacyPublicKeySignature = legacyPublicKeySignature; - this.prevResult = prevResult; + @Value + public static class PlayerCertificates implements AbstractStep.StepResult { + + long expireTimeMs; + RSAPublicKey publicKey; + RSAPrivateKey privateKey; + byte[] publicKeySignature; + byte[] legacyPublicKeySignature; + StepMCToken.MCToken mcToken; + + @Override + public StepMCToken.MCToken prevResult() { + return this.mcToken; } @Override @@ -127,7 +123,7 @@ public JsonObject toJson() { json.addProperty("privateKey", Base64.getEncoder().encodeToString(this.privateKey.getEncoded())); json.addProperty("publicKeySignature", Base64.getEncoder().encodeToString(this.publicKeySignature)); json.addProperty("legacyPublicKeySignature", Base64.getEncoder().encodeToString(this.legacyPublicKeySignature)); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); + if (this.mcToken != null) json.add("mcToken", this.mcToken.toJson()); return json; } @@ -136,59 +132,6 @@ public boolean isExpired() { return this.expireTimeMs <= System.currentTimeMillis(); } - public long expireTimeMs() { - return expireTimeMs; - } - - public RSAPublicKey publicKey() { - return publicKey; - } - - public RSAPrivateKey privateKey() { - return privateKey; - } - - public byte[] publicKeySignature() { - return publicKeySignature; - } - - public byte[] legacyPublicKeySignature() { - return legacyPublicKeySignature; - } - - @Override - public StepMCToken.MCToken prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - PlayerCertificates that = (PlayerCertificates) o; - return expireTimeMs == that.expireTimeMs && Objects.equals(publicKey, that.publicKey) && Objects.equals(privateKey, that.privateKey) && Arrays.equals(publicKeySignature, that.publicKeySignature) && Arrays.equals(legacyPublicKeySignature, that.legacyPublicKeySignature) && Objects.equals(prevResult, that.prevResult); - } - - @Override - public int hashCode() { - int result = Objects.hash(expireTimeMs, publicKey, privateKey, prevResult); - result = 31 * result + Arrays.hashCode(publicKeySignature); - result = 31 * result + Arrays.hashCode(legacyPublicKeySignature); - return result; - } - - @Override - public String toString() { - return "PlayerCertificates{" + - "expireTimeMs=" + expireTimeMs + - ", publicKey=" + publicKey + - ", privateKey=" + privateKey + - ", publicKeySignature=" + Arrays.toString(publicKeySignature) + - ", legacyPublicKeySignature=" + Arrays.toString(legacyPublicKeySignature) + - ", prevResult=" + prevResult + - '}'; - } - } } diff --git a/src/main/java/net/raphimc/mcauth/step/msa/MsaCodeStep.java b/src/main/java/net/raphimc/minecraftauth/step/msa/MsaCodeStep.java similarity index 52% rename from src/main/java/net/raphimc/mcauth/step/msa/MsaCodeStep.java rename to src/main/java/net/raphimc/minecraftauth/step/msa/MsaCodeStep.java index 43f81b9..b81b83b 100644 --- a/src/main/java/net/raphimc/mcauth/step/msa/MsaCodeStep.java +++ b/src/main/java/net/raphimc/minecraftauth/step/msa/MsaCodeStep.java @@ -15,14 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.msa; +package net.raphimc.minecraftauth.step.msa; import com.google.gson.JsonObject; -import net.raphimc.mcauth.step.AbstractStep; +import lombok.Value; +import net.raphimc.minecraftauth.step.AbstractStep; import org.apache.http.client.HttpClient; -import java.util.Objects; - public class MsaCodeStep> extends AbstractStep { protected final String clientId; @@ -47,21 +46,14 @@ public MsaCode fromJson(JsonObject json) throws Exception { return new MsaCode(json.get("code").getAsString(), json.get("clientId").getAsString(), json.get("scope").getAsString(), json.get("clientSecret") != null && !json.get("clientSecret").isJsonNull() ? json.get("clientSecret").getAsString() : null, null); } - public static final class MsaCode implements AbstractStep.StepResult> { - - private final String code; - private final String clientId; - private final String scope; - private final String clientSecret; - private final String redirectUri; + @Value + public static class MsaCode implements AbstractStep.StepResult> { - public MsaCode(String code, String clientId, String scope, String clientSecret, String redirectUri) { - this.code = code; - this.clientId = clientId; - this.scope = scope; - this.clientSecret = clientSecret; - this.redirectUri = redirectUri; - } + String code; + String clientId; + String scope; + String clientSecret; + String redirectUri; @Override public StepResult prevResult() { @@ -78,50 +70,6 @@ public JsonObject toJson() { return json; } - public String code() { - return code; - } - - public String clientId() { - return clientId; - } - - public String scope() { - return scope; - } - - public String clientSecret() { - return clientSecret; - } - - public String redirectUri() { - return redirectUri; - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - MsaCode msaCode = (MsaCode) o; - return Objects.equals(code, msaCode.code) && Objects.equals(clientId, msaCode.clientId) && Objects.equals(scope, msaCode.scope) && Objects.equals(clientSecret, msaCode.clientSecret) && Objects.equals(redirectUri, msaCode.redirectUri); - } - - @Override - public int hashCode() { - return Objects.hash(code, clientId, scope, clientSecret, redirectUri); - } - - @Override - public String toString() { - return "MsaCode{" + - "code='" + code + '\'' + - ", clientId='" + clientId + '\'' + - ", scope='" + scope + '\'' + - ", clientSecret='" + clientSecret + '\'' + - ", redirectUri='" + redirectUri + '\'' + - '}'; - } - } } diff --git a/src/main/java/net/raphimc/mcauth/step/msa/MsaCredentialsResponseHandler.java b/src/main/java/net/raphimc/minecraftauth/step/msa/MsaCredentialsResponseHandler.java similarity index 98% rename from src/main/java/net/raphimc/mcauth/step/msa/MsaCredentialsResponseHandler.java rename to src/main/java/net/raphimc/minecraftauth/step/msa/MsaCredentialsResponseHandler.java index cf02bca..24ead5a 100644 --- a/src/main/java/net/raphimc/mcauth/step/msa/MsaCredentialsResponseHandler.java +++ b/src/main/java/net/raphimc/minecraftauth/step/msa/MsaCredentialsResponseHandler.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.msa; +package net.raphimc.minecraftauth.step.msa; import org.apache.http.*; import org.apache.http.client.HttpResponseException; diff --git a/src/main/java/net/raphimc/mcauth/step/msa/MsaResponseHandler.java b/src/main/java/net/raphimc/minecraftauth/step/msa/MsaResponseHandler.java similarity index 98% rename from src/main/java/net/raphimc/mcauth/step/msa/MsaResponseHandler.java rename to src/main/java/net/raphimc/minecraftauth/step/msa/MsaResponseHandler.java index 466583f..ca4c600 100644 --- a/src/main/java/net/raphimc/mcauth/step/msa/MsaResponseHandler.java +++ b/src/main/java/net/raphimc/minecraftauth/step/msa/MsaResponseHandler.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.msa; +package net.raphimc.minecraftauth.step.msa; import com.google.gson.JsonObject; import com.google.gson.JsonParser; diff --git a/src/main/java/net/raphimc/mcauth/step/msa/StepCredentialsMsaCode.java b/src/main/java/net/raphimc/minecraftauth/step/msa/StepCredentialsMsaCode.java similarity index 90% rename from src/main/java/net/raphimc/mcauth/step/msa/StepCredentialsMsaCode.java rename to src/main/java/net/raphimc/minecraftauth/step/msa/StepCredentialsMsaCode.java index c3945f3..d1a3359 100644 --- a/src/main/java/net/raphimc/mcauth/step/msa/StepCredentialsMsaCode.java +++ b/src/main/java/net/raphimc/minecraftauth/step/msa/StepCredentialsMsaCode.java @@ -15,11 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.msa; +package net.raphimc.minecraftauth.step.msa; import com.google.gson.JsonObject; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; import org.apache.http.HttpHeaders; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; @@ -102,15 +103,11 @@ private URI getAuthenticationUrl() throws URISyntaxException { .build(); } - public static final class MsaCredentials implements AbstractStep.InitialInput { + @Value + public static class MsaCredentials implements AbstractStep.InitialInput { - private final String email; - private final String password; - - public MsaCredentials(String email, String password) { - this.email = email; - this.password = password; - } + String email; + String password; public static MsaCredentials fromJson(final JsonObject json) { return new MsaCredentials(json.get("email").getAsString(), json.get("password").getAsString()); @@ -124,14 +121,6 @@ public JsonObject toJson() { return json; } - public String email() { - return email; - } - - public String password() { - return password; - } - } } diff --git a/src/main/java/net/raphimc/mcauth/step/msa/StepExternalBrowser.java b/src/main/java/net/raphimc/minecraftauth/step/msa/StepExternalBrowser.java similarity index 65% rename from src/main/java/net/raphimc/mcauth/step/msa/StepExternalBrowser.java rename to src/main/java/net/raphimc/minecraftauth/step/msa/StepExternalBrowser.java index 43fabd8..26ce244 100644 --- a/src/main/java/net/raphimc/mcauth/step/msa/StepExternalBrowser.java +++ b/src/main/java/net/raphimc/minecraftauth/step/msa/StepExternalBrowser.java @@ -15,17 +15,17 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.msa; +package net.raphimc.minecraftauth.step.msa; import com.google.gson.JsonObject; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; import org.apache.http.client.HttpClient; import org.apache.http.client.utils.URIBuilder; import java.net.ServerSocket; import java.net.URISyntaxException; -import java.util.Objects; import java.util.function.Consumer; public class StepExternalBrowser extends AbstractStep { @@ -87,17 +87,12 @@ private String getAuthenticationUrl(final int localPort) throws URISyntaxExcepti .build().toString(); } - public static final class ExternalBrowser implements AbstractStep.StepResult> { + @Value + public static class ExternalBrowser implements AbstractStep.StepResult> { - private final String authenticationUrl; - private final String redirectUri; - private final int port; - - public ExternalBrowser(String authenticationUrl, String redirectUri, int port) { - this.authenticationUrl = authenticationUrl; - this.redirectUri = redirectUri; - this.port = port; - } + String authenticationUrl; + String redirectUri; + int port; @Override public StepResult prevResult() { @@ -113,55 +108,11 @@ public JsonObject toJson() { return json; } - public String authenticationUrl() { - return authenticationUrl; - } - - public String redirectUri() { - return redirectUri; - } - - public int port() { - return port; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - ExternalBrowser that = (ExternalBrowser) obj; - return Objects.equals(this.authenticationUrl, that.authenticationUrl) && - Objects.equals(this.redirectUri, that.redirectUri) && - this.port == that.port; - } - - @Override - public int hashCode() { - return Objects.hash(authenticationUrl, redirectUri, port); - } - - @Override - public String toString() { - return "ExternalBrowser[" + - "authenticationUrl=" + authenticationUrl + ", " + - "redirectUri=" + redirectUri + ", " + - "port=" + port + ']'; - } - } - public static final class ExternalBrowserCallback implements AbstractStep.InitialInput { - - private final Consumer callback; - - public ExternalBrowserCallback(Consumer callback) { - this.callback = callback; - } - - public Consumer callback() { - return callback; - } - + @Value + public static class ExternalBrowserCallback implements AbstractStep.InitialInput { + Consumer callback; } } diff --git a/src/main/java/net/raphimc/mcauth/step/msa/StepExternalBrowserMsaCode.java b/src/main/java/net/raphimc/minecraftauth/step/msa/StepExternalBrowserMsaCode.java similarity index 94% rename from src/main/java/net/raphimc/mcauth/step/msa/StepExternalBrowserMsaCode.java rename to src/main/java/net/raphimc/minecraftauth/step/msa/StepExternalBrowserMsaCode.java index 961777d..81ae34c 100644 --- a/src/main/java/net/raphimc/mcauth/step/msa/StepExternalBrowserMsaCode.java +++ b/src/main/java/net/raphimc/minecraftauth/step/msa/StepExternalBrowserMsaCode.java @@ -15,10 +15,10 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.msa; +package net.raphimc.minecraftauth.step.msa; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; import org.apache.http.client.HttpClient; import java.net.ServerSocket; @@ -47,7 +47,7 @@ public StepExternalBrowserMsaCode(AbstractStep. */ -package net.raphimc.mcauth.step.msa; +package net.raphimc.minecraftauth.step.msa; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; @@ -32,7 +33,6 @@ import java.time.ZoneId; import java.util.ArrayList; import java.util.List; -import java.util.Objects; import java.util.function.Consumer; public class StepMsaDeviceCode extends AbstractStep { @@ -72,7 +72,7 @@ public MsaDeviceCode applyStep(HttpClient httpClient, StepMsaDeviceCode.MsaDevic obj.get("user_code").getAsString(), obj.get("verification_uri").getAsString() ); - MinecraftAuth.LOGGER.info("Got MSA device code, expires: " + Instant.ofEpochMilli(result.expireTimeMs).atZone(ZoneId.systemDefault())); + MinecraftAuth.LOGGER.info("Got MSA device code, expires: " + Instant.ofEpochMilli(result.getExpireTimeMs()).atZone(ZoneId.systemDefault())); prevResult.callback.accept(result); return result; } @@ -88,21 +88,14 @@ public MsaDeviceCode fromJson(JsonObject json) throws Exception { ); } - public static final class MsaDeviceCode implements AbstractStep.StepResult> { + @Value + public static class MsaDeviceCode implements AbstractStep.StepResult> { - private final long expireTimeMs; - private final long intervalMs; - private final String deviceCode; - private final String userCode; - private final String verificationUri; - - public MsaDeviceCode(long expireTimeMs, long intervalMs, String deviceCode, String userCode, String verificationUri) { - this.expireTimeMs = expireTimeMs; - this.intervalMs = intervalMs; - this.deviceCode = deviceCode; - this.userCode = userCode; - this.verificationUri = verificationUri; - } + long expireTimeMs; + long intervalMs; + String deviceCode; + String userCode; + String verificationUri; @Override public StepResult prevResult() { @@ -125,67 +118,11 @@ public boolean isExpired() throws Exception { return this.expireTimeMs <= System.currentTimeMillis(); } - public long expireTimeMs() { - return expireTimeMs; - } - - public long intervalMs() { - return intervalMs; - } - - public String deviceCode() { - return deviceCode; - } - - public String userCode() { - return userCode; - } - - public String verificationUri() { - return verificationUri; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - MsaDeviceCode that = (MsaDeviceCode) obj; - return this.expireTimeMs == that.expireTimeMs && - this.intervalMs == that.intervalMs && - Objects.equals(this.deviceCode, that.deviceCode) && - Objects.equals(this.userCode, that.userCode) && - Objects.equals(this.verificationUri, that.verificationUri); - } - - @Override - public int hashCode() { - return Objects.hash(expireTimeMs, intervalMs, deviceCode, userCode, verificationUri); - } - - @Override - public String toString() { - return "MsaDeviceCode[" + - "expireTimeMs=" + expireTimeMs + ", " + - "intervalMs=" + intervalMs + ", " + - "deviceCode=" + deviceCode + ", " + - "userCode=" + userCode + ", " + - "verificationUri=" + verificationUri + ']'; - } - } - public static final class MsaDeviceCodeCallback implements AbstractStep.InitialInput { - - private final Consumer callback; - - public MsaDeviceCodeCallback(Consumer callback) { - this.callback = callback; - } - - public Consumer callback() { - return callback; - } - + @Value + public static class MsaDeviceCodeCallback implements AbstractStep.InitialInput { + Consumer callback; } } diff --git a/src/main/java/net/raphimc/mcauth/step/msa/StepMsaDeviceCodeMsaCode.java b/src/main/java/net/raphimc/minecraftauth/step/msa/StepMsaDeviceCodeMsaCode.java similarity index 93% rename from src/main/java/net/raphimc/mcauth/step/msa/StepMsaDeviceCodeMsaCode.java rename to src/main/java/net/raphimc/minecraftauth/step/msa/StepMsaDeviceCodeMsaCode.java index e56c0a5..8ad1a5a 100644 --- a/src/main/java/net/raphimc/mcauth/step/msa/StepMsaDeviceCodeMsaCode.java +++ b/src/main/java/net/raphimc/minecraftauth/step/msa/StepMsaDeviceCodeMsaCode.java @@ -15,12 +15,12 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.msa; +package net.raphimc.minecraftauth.step.msa; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; import org.apache.http.HttpStatus; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; @@ -58,7 +58,7 @@ public MsaCode applyStep(HttpClient httpClient, StepMsaDeviceCode.MsaDeviceCode while (!prevResult.isExpired() && System.currentTimeMillis() - start <= this.timeout) { final List postData = new ArrayList<>(); postData.add(new BasicNameValuePair("client_id", this.clientId)); - postData.add(new BasicNameValuePair("device_code", prevResult.deviceCode())); + postData.add(new BasicNameValuePair("device_code", prevResult.getDeviceCode())); postData.add(new BasicNameValuePair("grant_type", "device_code")); final HttpPost httpPost = new HttpPost(TOKEN_URL); @@ -72,7 +72,7 @@ public MsaCode applyStep(HttpClient httpClient, StepMsaDeviceCode.MsaDeviceCode return result; } catch (HttpResponseException e) { if (e.getStatusCode() == HttpStatus.SC_BAD_REQUEST && e.getReasonPhrase().startsWith("authorization_pending")) { - Thread.sleep(prevResult.intervalMs()); + Thread.sleep(prevResult.getIntervalMs()); continue; } throw e; diff --git a/src/main/java/net/raphimc/mcauth/step/msa/StepMsaToken.java b/src/main/java/net/raphimc/minecraftauth/step/msa/StepMsaToken.java similarity index 57% rename from src/main/java/net/raphimc/mcauth/step/msa/StepMsaToken.java rename to src/main/java/net/raphimc/minecraftauth/step/msa/StepMsaToken.java index fa5b704..f5e79e3 100644 --- a/src/main/java/net/raphimc/mcauth/step/msa/StepMsaToken.java +++ b/src/main/java/net/raphimc/minecraftauth/step/msa/StepMsaToken.java @@ -15,12 +15,13 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.msa; +package net.raphimc.minecraftauth.step.msa; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; import org.apache.http.NameValuePair; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; @@ -32,7 +33,6 @@ import java.time.ZoneId; import java.util.ArrayList; import java.util.List; -import java.util.Objects; public class StepMsaToken extends AbstractStep { @@ -44,13 +44,13 @@ public StepMsaToken(AbstractStep prevStep) { @Override public MsaToken applyStep(HttpClient httpClient, MsaCodeStep.MsaCode prevResult) throws Exception { - return this.apply(httpClient, prevResult.code(), prevResult.redirectUri() != null ? "authorization_code" : "refresh_token", prevResult); + return this.apply(httpClient, prevResult.getCode(), prevResult.getRedirectUri() != null ? "authorization_code" : "refresh_token", prevResult); } @Override public MsaToken refresh(HttpClient httpClient, MsaToken result) throws Exception { if (result == null) return super.refresh(httpClient, null); - if (result.isExpired()) return this.apply(httpClient, result.refresh_token(), "refresh_token", result.prevResult()); + if (result.isExpired()) return this.apply(httpClient, result.getRefreshToken(), "refresh_token", result.getMsaCode()); return result; } @@ -59,10 +59,10 @@ public MsaToken refresh(HttpClient httpClient, MsaToken result) throws Exception public MsaToken fromJson(JsonObject json) throws Exception { final MsaCodeStep.MsaCode prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; return new MsaToken( - json.get("user_id").getAsString(), + json.get("userId").getAsString(), json.get("expireTimeMs").getAsLong(), - json.get("access_token").getAsString(), - json.get("refresh_token").getAsString(), + json.get("accessToken").getAsString(), + json.get("refreshToken").getAsString(), prev ); } @@ -71,17 +71,17 @@ private MsaToken apply(final HttpClient httpClient, final String code, final Str MinecraftAuth.LOGGER.info("Getting MSA Token..."); final List postData = new ArrayList<>(); - postData.add(new BasicNameValuePair("client_id", prev_result.clientId())); - postData.add(new BasicNameValuePair("scope", prev_result.scope())); + postData.add(new BasicNameValuePair("client_id", prev_result.getClientId())); + postData.add(new BasicNameValuePair("scope", prev_result.getScope())); postData.add(new BasicNameValuePair("grant_type", type)); if (type.equals("refresh_token")) { postData.add(new BasicNameValuePair("refresh_token", code)); } else { postData.add(new BasicNameValuePair("code", code)); - postData.add(new BasicNameValuePair("redirect_uri", prev_result.redirectUri())); + postData.add(new BasicNameValuePair("redirect_uri", prev_result.getRedirectUri())); } - if (prev_result.clientSecret() != null) { - postData.add(new BasicNameValuePair("client_secret", prev_result.clientSecret())); + if (prev_result.getClientSecret() != null) { + postData.add(new BasicNameValuePair("client_secret", prev_result.getClientSecret())); } final HttpPost httpPost = new HttpPost(TOKEN_URL); @@ -96,34 +96,32 @@ private MsaToken apply(final HttpClient httpClient, final String code, final Str obj.get("refresh_token").getAsString(), prev_result ); - MinecraftAuth.LOGGER.info("Got MSA Token, expires: " + Instant.ofEpochMilli(result.expireTimeMs).atZone(ZoneId.systemDefault())); + MinecraftAuth.LOGGER.info("Got MSA Token, expires: " + Instant.ofEpochMilli(result.getExpireTimeMs()).atZone(ZoneId.systemDefault())); return result; } - public static final class MsaToken implements AbstractStep.StepResult { + @Value + public static class MsaToken implements AbstractStep.StepResult { - private final String user_id; - private final long expireTimeMs; - private final String access_token; - private final String refresh_token; - private final MsaCodeStep.MsaCode prevResult; + String userId; + long expireTimeMs; + String accessToken; + String refreshToken; + MsaCodeStep.MsaCode msaCode; - public MsaToken(String user_id, long expireTimeMs, String access_token, String refresh_token, MsaCodeStep.MsaCode prevResult) { - this.user_id = user_id; - this.expireTimeMs = expireTimeMs; - this.access_token = access_token; - this.refresh_token = refresh_token; - this.prevResult = prevResult; + @Override + public MsaCodeStep.MsaCode prevResult() { + return this.msaCode; } @Override public JsonObject toJson() { final JsonObject json = new JsonObject(); - json.addProperty("user_id", this.user_id); + json.addProperty("userId", this.userId); json.addProperty("expireTimeMs", this.expireTimeMs); - json.addProperty("access_token", this.access_token); - json.addProperty("refresh_token", this.refresh_token); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); + json.addProperty("accessToken", this.accessToken); + json.addProperty("refreshToken", this.refreshToken); + if (this.msaCode != null) json.add("prev", this.msaCode.toJson()); return json; } @@ -133,55 +131,7 @@ public boolean isExpired() { } public boolean isTitleClientId() { - return !this.prevResult.clientId().matches("\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}+"); - } - - public String user_id() { - return user_id; - } - - public long expireTimeMs() { - return expireTimeMs; - } - - public String access_token() { - return access_token; - } - - public String refresh_token() { - return refresh_token; - } - - @Override - public MsaCodeStep.MsaCode prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - MsaToken that = (MsaToken) obj; - return Objects.equals(this.user_id, that.user_id) && - this.expireTimeMs == that.expireTimeMs && - Objects.equals(this.access_token, that.access_token) && - Objects.equals(this.refresh_token, that.refresh_token) && - Objects.equals(this.prevResult, that.prevResult); - } - - @Override - public int hashCode() { - return Objects.hash(user_id, expireTimeMs, access_token, refresh_token, prevResult); - } - - @Override - public String toString() { - return "MsaToken[" + - "user_id=" + user_id + ", " + - "expireTimeMs=" + expireTimeMs + ", " + - "access_token=" + access_token + ", " + - "refresh_token=" + refresh_token + ", " + - "prevResult=" + prevResult + ']'; + return !this.msaCode.getClientId().matches("\\p{XDigit}{8}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}{4}-\\p{XDigit}+"); } } diff --git a/src/main/java/net/raphimc/mcauth/step/xbl/StepXblDeviceToken.java b/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblDeviceToken.java similarity index 60% rename from src/main/java/net/raphimc/mcauth/step/xbl/StepXblDeviceToken.java rename to src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblDeviceToken.java index 40a7d69..cd3e83c 100644 --- a/src/main/java/net/raphimc/mcauth/step/xbl/StepXblDeviceToken.java +++ b/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblDeviceToken.java @@ -15,13 +15,14 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.xbl; +package net.raphimc.minecraftauth.step.xbl; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.util.CryptUtil; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.util.CryptUtil; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; @@ -32,12 +33,9 @@ import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; import java.security.spec.ECGenParameterSpec; -import java.security.spec.PKCS8EncodedKeySpec; -import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.time.ZoneId; import java.util.Base64; -import java.util.Objects; import java.util.UUID; public class StepXblDeviceToken extends AbstractStep, StepXblDeviceToken.XblDeviceToken> { @@ -53,7 +51,7 @@ public StepXblDeviceToken(final String deviceType) { } @Override - public XblDeviceToken applyStep(HttpClient httpClient, StepResult prevResult) throws Exception { + public XblDeviceToken applyStep(final HttpClient httpClient, final StepResult prevResult) throws Exception { MinecraftAuth.LOGGER.info("Authenticating device with Xbox Live..."); final UUID id = UUID.randomUUID(); @@ -89,22 +87,22 @@ public XblDeviceToken applyStep(HttpClient httpClient, StepResult prevResult) obj.get("Token").getAsString(), obj.getAsJsonObject("DisplayClaims").getAsJsonObject("xdi").get("did").getAsString() ); - MinecraftAuth.LOGGER.info("Got XBL Device Token, expires: " + Instant.ofEpochMilli(result.expireTimeMs).atZone(ZoneId.systemDefault())); + MinecraftAuth.LOGGER.info("Got XBL Device Token, expires: " + Instant.ofEpochMilli(result.getExpireTimeMs()).atZone(ZoneId.systemDefault())); return result; } @Override - public XblDeviceToken refresh(HttpClient httpClient, XblDeviceToken result) throws Exception { + public XblDeviceToken refresh(final HttpClient httpClient, final XblDeviceToken result) throws Exception { if (result == null || result.isExpired()) return this.applyStep(httpClient, null); return result; } @Override - public XblDeviceToken fromJson(JsonObject json) throws Exception { + public XblDeviceToken fromJson(final JsonObject json) throws Exception { return new XblDeviceToken( - (ECPublicKey) CryptUtil.EC_KEYFACTORY.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(json.get("publicKey").getAsString()))), - (ECPrivateKey) CryptUtil.EC_KEYFACTORY.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(json.get("privateKey").getAsString()))), + CryptUtil.publicKeyFromBase64(json.get("publicKey").getAsString()), + CryptUtil.privateKeyFromBase64(json.get("privateKey").getAsString()), UUID.fromString(json.get("id").getAsString()), json.get("expireTimeMs").getAsLong(), json.get("token").getAsString(), @@ -112,23 +110,15 @@ public XblDeviceToken fromJson(JsonObject json) throws Exception { ); } - public static final class XblDeviceToken implements AbstractStep.StepResult> { - - private final ECPublicKey publicKey; - private final ECPrivateKey privateKey; - private final UUID id; - private final long expireTimeMs; - private final String token; - private final String deviceId; - - public XblDeviceToken(ECPublicKey publicKey, ECPrivateKey privateKey, UUID id, long expireTimeMs, String token, String deviceId) { - this.publicKey = publicKey; - this.privateKey = privateKey; - this.id = id; - this.expireTimeMs = expireTimeMs; - this.token = token; - this.deviceId = deviceId; - } + @Value + public static class XblDeviceToken implements AbstractStep.StepResult> { + + ECPublicKey publicKey; + ECPrivateKey privateKey; + UUID id; + long expireTimeMs; + String token; + String deviceId; @Override public StepResult prevResult() { @@ -152,59 +142,6 @@ public boolean isExpired() { return this.expireTimeMs <= System.currentTimeMillis(); } - public ECPublicKey publicKey() { - return publicKey; - } - - public ECPrivateKey privateKey() { - return privateKey; - } - - public UUID id() { - return id; - } - - public long expireTimeMs() { - return expireTimeMs; - } - - public String token() { - return token; - } - - public String deviceId() { - return deviceId; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - XblDeviceToken that = (XblDeviceToken) obj; - return Objects.equals(this.publicKey, that.publicKey) && - Objects.equals(this.privateKey, that.privateKey) && - Objects.equals(this.id, that.id) && - this.expireTimeMs == that.expireTimeMs && - Objects.equals(this.token, that.token) && - Objects.equals(this.deviceId, that.deviceId); - } - - @Override - public int hashCode() { - return Objects.hash(publicKey, privateKey, id, expireTimeMs, token, deviceId); - } - - @Override - public String toString() { - return "XblDeviceToken[" + - "publicKey=" + publicKey + ", " + - "privateKey=" + privateKey + ", " + - "id=" + id + ", " + - "expireTimeMs=" + expireTimeMs + ", " + - "token=" + token + ", " + - "deviceId=" + deviceId + ']'; - } - } } diff --git a/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblSisuAuthentication.java b/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblSisuAuthentication.java new file mode 100644 index 0000000..f0eee14 --- /dev/null +++ b/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblSisuAuthentication.java @@ -0,0 +1,239 @@ +/* + * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.minecraftauth.step.xbl; + +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.xbl.session.StepFullXblSession; +import net.raphimc.minecraftauth.step.xbl.session.StepInitialXblSession; +import net.raphimc.minecraftauth.util.CryptUtil; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; + +import java.time.Instant; +import java.time.ZoneId; + +public class StepXblSisuAuthentication extends AbstractStep> { + + public static final String XBL_SISU_URL = "https://sisu.xboxlive.com/authorize"; + + private final String relyingParty; + + public StepXblSisuAuthentication(final AbstractStep prevStep, final String relyingParty) { + super(prevStep); + + this.relyingParty = relyingParty; + } + + @Override + public StepXblSisuAuthentication.XblSisuTokens applyStep(final HttpClient httpClient, final StepInitialXblSession.InitialXblSession initialXblSession) throws Exception { + MinecraftAuth.LOGGER.info("Authenticating with Xbox Live using SISU..."); + + if (initialXblSession.getXblDeviceToken() == null) throw new IllegalStateException("A XBL Device Token is needed for SISU authentication"); + + final JsonObject postData = new JsonObject(); + postData.addProperty("AccessToken", "t=" + initialXblSession.getMsaToken().getAccessToken()); + postData.addProperty("DeviceToken", initialXblSession.getXblDeviceToken().getToken()); + postData.addProperty("AppId", initialXblSession.getMsaToken().getMsaCode().getClientId()); + postData.add("ProofKey", CryptUtil.getProofKey(initialXblSession.getXblDeviceToken().getPublicKey())); + postData.addProperty("SiteName", "user.auth.xboxlive.com"); + postData.addProperty("RelyingParty", this.relyingParty); + postData.addProperty("Sandbox", "RETAIL"); + postData.addProperty("UseModernGamertag", true); + + final HttpPost httpPost = new HttpPost(XBL_SISU_URL); + httpPost.setEntity(new StringEntity(postData.toString(), ContentType.APPLICATION_JSON)); + httpPost.addHeader(CryptUtil.getSignatureHeader(httpPost, initialXblSession.getXblDeviceToken().getPrivateKey())); + final String response = httpClient.execute(httpPost, new XblResponseHandler()); + final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); + + final XblSisuTokens result = new XblSisuTokens( + new XblSisuTokens.SisuTitleToken( + Instant.parse(obj.getAsJsonObject("TitleToken").get("NotAfter").getAsString()).toEpochMilli(), + obj.getAsJsonObject("TitleToken").get("Token").getAsString(), + obj.getAsJsonObject("TitleToken").getAsJsonObject("DisplayClaims").getAsJsonObject("xti").get("tid").getAsString() + ), + new XblSisuTokens.SisuUserToken( + Instant.parse(obj.getAsJsonObject("UserToken").get("NotAfter").getAsString()).toEpochMilli(), + obj.getAsJsonObject("UserToken").get("Token").getAsString(), + obj.getAsJsonObject("UserToken").getAsJsonObject("DisplayClaims").getAsJsonArray("xui").get(0).getAsJsonObject().get("uhs").getAsString() + ), + new XblSisuTokens.SisuXstsToken( + Instant.parse(obj.getAsJsonObject("AuthorizationToken").get("NotAfter").getAsString()).toEpochMilli(), + obj.getAsJsonObject("AuthorizationToken").get("Token").getAsString(), + obj.getAsJsonObject("AuthorizationToken").getAsJsonObject("DisplayClaims").getAsJsonArray("xui").get(0).getAsJsonObject().get("uhs").getAsString() + ), + initialXblSession + ); + + MinecraftAuth.LOGGER.info("Got XBL Title+User+XSTS Token, expires: " + Instant.ofEpochMilli(result.getExpireTimeMs()).atZone(ZoneId.systemDefault())); + return result; + } + + @Override + public StepXblXstsToken.XblXsts refresh(final HttpClient httpClient, final StepXblXstsToken.XblXsts result) throws Exception { + if (result == null || result.isExpired()) return super.refresh(httpClient, result); + + return result; + } + + @Override + public StepXblSisuAuthentication.XblSisuTokens fromJson(final JsonObject json) throws Exception { + final StepInitialXblSession.InitialXblSession initialXblSession = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("initialXblSession")) : null; + return new StepXblSisuAuthentication.XblSisuTokens( + XblSisuTokens.SisuTitleToken.fromJson(json.getAsJsonObject("titleToken")), + XblSisuTokens.SisuUserToken.fromJson(json.getAsJsonObject("userToken")), + XblSisuTokens.SisuXstsToken.fromJson(json.getAsJsonObject("xstsToken")), + initialXblSession + ); + } + + @Value + public static class XblSisuTokens implements AbstractStep.StepResult, StepXblXstsToken.XblXsts { + + SisuTitleToken titleToken; + SisuUserToken userToken; + SisuXstsToken xstsToken; + StepInitialXblSession.InitialXblSession initialXblSession; + + @Override + public long getExpireTimeMs() { + return Math.min(Math.min(this.xstsToken.expireTimeMs, this.titleToken.expireTimeMs), this.userToken.expireTimeMs); + } + + @Override + public String getToken() { + return this.xstsToken.token; + } + + @Override + public String getUserHash() { + return this.xstsToken.userHash; + } + + @Override + public StepFullXblSession.FullXblSession getFullXblSession() { + final StepXblUserToken.XblUserToken userToken = new StepXblUserToken.XblUserToken(this.userToken.expireTimeMs, this.userToken.token, this.userToken.userHash, this.initialXblSession); + final StepXblTitleToken.XblTitleToken titleToken = new StepXblTitleToken.XblTitleToken(this.titleToken.expireTimeMs, this.titleToken.token, this.titleToken.titleId, this.initialXblSession); + return new StepFullXblSession.FullXblSession(userToken, titleToken); + } + + @Override + public StepInitialXblSession.InitialXblSession prevResult() { + return this.initialXblSession; + } + + @Override + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + json.add("titleToken", this.titleToken.toJson()); + json.add("userToken", this.userToken.toJson()); + json.add("xstsToken", this.xstsToken.toJson()); + if (this.initialXblSession != null) json.add("initialXblSession", this.initialXblSession.toJson()); + return json; + } + + @Override + public boolean isExpired() { + return this.getExpireTimeMs() <= System.currentTimeMillis(); + } + + + @Value + public static class SisuTitleToken { + + long expireTimeMs; + String token; + String titleId; + + public static SisuTitleToken fromJson(final JsonObject json) { + return new SisuTitleToken( + json.get("expireTimeMs").getAsLong(), + json.get("token").getAsString(), + json.get("titleId").getAsString() + ); + } + + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + json.addProperty("expireTimeMs", this.expireTimeMs); + json.addProperty("token", this.token); + json.addProperty("titleId", this.titleId); + return json; + } + + } + + @Value + public static class SisuUserToken { + + long expireTimeMs; + String token; + String userHash; + + public static SisuUserToken fromJson(final JsonObject json) { + return new SisuUserToken( + json.get("expireTimeMs").getAsLong(), + json.get("token").getAsString(), + json.get("userHash").getAsString() + ); + } + + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + json.addProperty("expireTimeMs", this.expireTimeMs); + json.addProperty("token", this.token); + json.addProperty("userHash", this.userHash); + return json; + } + + } + + @Value + public static class SisuXstsToken { + + long expireTimeMs; + String token; + String userHash; + + public static SisuXstsToken fromJson(final JsonObject json) { + return new SisuXstsToken( + json.get("expireTimeMs").getAsLong(), + json.get("token").getAsString(), + json.get("userHash").getAsString() + ); + } + + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + json.addProperty("expireTimeMs", this.expireTimeMs); + json.addProperty("token", this.token); + json.addProperty("userHash", this.userHash); + return json; + } + + } + + } + +} diff --git a/src/main/java/net/raphimc/mcauth/step/xbl/StepXblTitleToken.java b/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblTitleToken.java similarity index 51% rename from src/main/java/net/raphimc/mcauth/step/xbl/StepXblTitleToken.java rename to src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblTitleToken.java index 8c0527b..727d76e 100644 --- a/src/main/java/net/raphimc/mcauth/step/xbl/StepXblTitleToken.java +++ b/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblTitleToken.java @@ -15,14 +15,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.xbl; +package net.raphimc.minecraftauth.step.xbl; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.step.xbl.session.StepInitialXblSession; -import net.raphimc.mcauth.util.CryptUtil; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.xbl.session.StepInitialXblSession; +import net.raphimc.minecraftauth.util.CryptUtil; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; @@ -30,29 +31,28 @@ import java.time.Instant; import java.time.ZoneId; -import java.util.Objects; public class StepXblTitleToken extends AbstractStep { public static final String XBL_TITLE_URL = "https://title.auth.xboxlive.com/title/authenticate"; - public StepXblTitleToken(AbstractStep prevStep) { + public StepXblTitleToken(final AbstractStep prevStep) { super(prevStep); } @Override - public XblTitleToken applyStep(HttpClient httpClient, StepInitialXblSession.InitialXblSession prevResult) throws Exception { + public XblTitleToken applyStep(final HttpClient httpClient, final StepInitialXblSession.InitialXblSession initialXblSession) throws Exception { MinecraftAuth.LOGGER.info("Authenticating title with Xbox Live..."); - if (prevResult.prevResult2() == null) throw new IllegalStateException("A XBL Device Token is needed for Title authentication"); + if (initialXblSession.getXblDeviceToken() == null) throw new IllegalStateException("A XBL Device Token is needed for Title authentication"); final JsonObject postData = new JsonObject(); final JsonObject properties = new JsonObject(); properties.addProperty("AuthMethod", "RPS"); properties.addProperty("SiteName", "user.auth.xboxlive.com"); - properties.addProperty("RpsTicket", "t=" + prevResult.prevResult().access_token()); - properties.addProperty("DeviceToken", prevResult.prevResult2().token()); - properties.add("ProofKey", CryptUtil.getProofKey(prevResult.prevResult2().publicKey())); + properties.addProperty("RpsTicket", "t=" + initialXblSession.getMsaToken().getAccessToken()); + properties.addProperty("DeviceToken", initialXblSession.getXblDeviceToken().getToken()); + properties.add("ProofKey", CryptUtil.getProofKey(initialXblSession.getXblDeviceToken().getPublicKey())); postData.add("Properties", properties); postData.addProperty("RelyingParty", "http://auth.xboxlive.com"); postData.addProperty("TokenType", "JWT"); @@ -60,7 +60,7 @@ public XblTitleToken applyStep(HttpClient httpClient, StepInitialXblSession.Init final HttpPost httpPost = new HttpPost(XBL_TITLE_URL); httpPost.setEntity(new StringEntity(postData.toString(), ContentType.APPLICATION_JSON)); httpPost.addHeader("x-xbl-contract-version", "1"); - httpPost.addHeader(CryptUtil.getSignatureHeader(httpPost, prevResult.prevResult2().privateKey())); + httpPost.addHeader(CryptUtil.getSignatureHeader(httpPost, initialXblSession.getXblDeviceToken().getPrivateKey())); final String response = httpClient.execute(httpPost, new XblResponseHandler()); final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); @@ -68,42 +68,41 @@ public XblTitleToken applyStep(HttpClient httpClient, StepInitialXblSession.Init Instant.parse(obj.get("NotAfter").getAsString()).toEpochMilli(), obj.get("Token").getAsString(), obj.getAsJsonObject("DisplayClaims").getAsJsonObject("xti").get("tid").getAsString(), - prevResult + initialXblSession ); - MinecraftAuth.LOGGER.info("Got XBL Title Token, expires: " + Instant.ofEpochMilli(result.expireTimeMs).atZone(ZoneId.systemDefault())); + MinecraftAuth.LOGGER.info("Got XBL Title Token, expires: " + Instant.ofEpochMilli(result.getExpireTimeMs()).atZone(ZoneId.systemDefault())); return result; } @Override - public XblTitleToken refresh(HttpClient httpClient, XblTitleToken result) throws Exception { + public XblTitleToken refresh(final HttpClient httpClient, final XblTitleToken result) throws Exception { if (result == null || result.isExpired()) return super.refresh(httpClient, result); return result; } @Override - public XblTitleToken fromJson(JsonObject json) throws Exception { - final StepInitialXblSession.InitialXblSession prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; + public XblTitleToken fromJson(final JsonObject json) throws Exception { + final StepInitialXblSession.InitialXblSession initialXblSession = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("initialXblSession")) : null; return new XblTitleToken( json.get("expireTimeMs").getAsLong(), json.get("token").getAsString(), - json.get("userHash").getAsString(), - prev + json.get("titleId").getAsString(), + initialXblSession ); } - public static final class XblTitleToken implements AbstractStep.StepResult { + @Value + public static class XblTitleToken implements AbstractStep.StepResult { - private final long expireTimeMs; - private final String token; - private final String titleId; - private final StepInitialXblSession.InitialXblSession prevResult; + long expireTimeMs; + String token; + String titleId; + StepInitialXblSession.InitialXblSession initialXblSession; - public XblTitleToken(long expireTimeMs, String token, String titleId, StepInitialXblSession.InitialXblSession prevResult) { - this.expireTimeMs = expireTimeMs; - this.token = token; - this.titleId = titleId; - this.prevResult = prevResult; + @Override + public StepInitialXblSession.InitialXblSession prevResult() { + return this.initialXblSession; } @Override @@ -112,7 +111,7 @@ public JsonObject toJson() { json.addProperty("expireTimeMs", this.expireTimeMs); json.addProperty("token", this.token); json.addProperty("titleId", this.titleId); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); + if (this.initialXblSession != null) json.add("initialXblSession", this.initialXblSession.toJson()); return json; } @@ -121,48 +120,6 @@ public boolean isExpired() { return this.expireTimeMs <= System.currentTimeMillis(); } - public long expireTimeMs() { - return expireTimeMs; - } - - public String token() { - return token; - } - - public String titleId() { - return titleId; - } - - @Override - public StepInitialXblSession.InitialXblSession prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - XblTitleToken that = (XblTitleToken) obj; - return this.expireTimeMs == that.expireTimeMs && - Objects.equals(this.token, that.token) && - Objects.equals(this.titleId, that.titleId) && - Objects.equals(this.prevResult, that.prevResult); - } - - @Override - public int hashCode() { - return Objects.hash(expireTimeMs, token, titleId, prevResult); - } - - @Override - public String toString() { - return "XblTitleToken[" + - "expireTimeMs=" + expireTimeMs + ", " + - "token=" + token + ", " + - "titleId=" + titleId + ", " + - "prevResult=" + prevResult + ']'; - } - } } diff --git a/src/main/java/net/raphimc/mcauth/step/xbl/StepXblUserToken.java b/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblUserToken.java similarity index 53% rename from src/main/java/net/raphimc/mcauth/step/xbl/StepXblUserToken.java rename to src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblUserToken.java index 426abdd..e7a5ae7 100644 --- a/src/main/java/net/raphimc/mcauth/step/xbl/StepXblUserToken.java +++ b/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblUserToken.java @@ -15,14 +15,15 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.xbl; +package net.raphimc.minecraftauth.step.xbl; import com.google.gson.JsonObject; import com.google.gson.JsonParser; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.step.xbl.session.StepInitialXblSession; -import net.raphimc.mcauth.util.CryptUtil; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.xbl.session.StepInitialXblSession; +import net.raphimc.minecraftauth.util.CryptUtil; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; @@ -30,27 +31,26 @@ import java.time.Instant; import java.time.ZoneId; -import java.util.Objects; public class StepXblUserToken extends AbstractStep { public static final String XBL_USER_URL = "https://user.auth.xboxlive.com/user/authenticate"; - public StepXblUserToken(AbstractStep prevStep) { + public StepXblUserToken(final AbstractStep prevStep) { super(prevStep); } @Override - public XblUserToken applyStep(HttpClient httpClient, StepInitialXblSession.InitialXblSession prevResult) throws Exception { + public XblUserToken applyStep(final HttpClient httpClient, final StepInitialXblSession.InitialXblSession initialXblSession) throws Exception { MinecraftAuth.LOGGER.info("Authenticating user with Xbox Live..."); final JsonObject postData = new JsonObject(); final JsonObject properties = new JsonObject(); properties.addProperty("AuthMethod", "RPS"); properties.addProperty("SiteName", "user.auth.xboxlive.com"); - properties.addProperty("RpsTicket", (prevResult.prevResult().isTitleClientId() ? "t=" : "d=") + prevResult.prevResult().access_token()); - if (prevResult.prevResult2() != null) { - properties.add("ProofKey", CryptUtil.getProofKey(prevResult.prevResult2().publicKey())); + properties.addProperty("RpsTicket", (initialXblSession.getMsaToken().isTitleClientId() ? "t=" : "d=") + initialXblSession.getMsaToken().getAccessToken()); + if (initialXblSession.getXblDeviceToken() != null) { + properties.add("ProofKey", CryptUtil.getProofKey(initialXblSession.getXblDeviceToken().getPublicKey())); } postData.add("Properties", properties); postData.addProperty("RelyingParty", "http://auth.xboxlive.com"); @@ -59,8 +59,8 @@ public XblUserToken applyStep(HttpClient httpClient, StepInitialXblSession.Initi final HttpPost httpPost = new HttpPost(XBL_USER_URL); httpPost.setEntity(new StringEntity(postData.toString(), ContentType.APPLICATION_JSON)); httpPost.addHeader("x-xbl-contract-version", "1"); - if (prevResult.prevResult2() != null) { - httpPost.addHeader(CryptUtil.getSignatureHeader(httpPost, prevResult.prevResult2().privateKey())); + if (initialXblSession.getXblDeviceToken() != null) { + httpPost.addHeader(CryptUtil.getSignatureHeader(httpPost, initialXblSession.getXblDeviceToken().getPrivateKey())); } final String response = httpClient.execute(httpPost, new XblResponseHandler()); final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); @@ -69,42 +69,41 @@ public XblUserToken applyStep(HttpClient httpClient, StepInitialXblSession.Initi Instant.parse(obj.get("NotAfter").getAsString()).toEpochMilli(), obj.get("Token").getAsString(), obj.getAsJsonObject("DisplayClaims").getAsJsonArray("xui").get(0).getAsJsonObject().get("uhs").getAsString(), - prevResult + initialXblSession ); - MinecraftAuth.LOGGER.info("Got XBL User Token, expires: " + Instant.ofEpochMilli(result.expireTimeMs).atZone(ZoneId.systemDefault())); + MinecraftAuth.LOGGER.info("Got XBL User Token, expires: " + Instant.ofEpochMilli(result.getExpireTimeMs()).atZone(ZoneId.systemDefault())); return result; } @Override - public XblUserToken refresh(HttpClient httpClient, XblUserToken result) throws Exception { + public XblUserToken refresh(final HttpClient httpClient, final XblUserToken result) throws Exception { if (result == null || result.isExpired()) return super.refresh(httpClient, result); return result; } @Override - public XblUserToken fromJson(JsonObject json) throws Exception { - final StepInitialXblSession.InitialXblSession prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; + public XblUserToken fromJson(final JsonObject json) throws Exception { + final StepInitialXblSession.InitialXblSession initialXblSession = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("initialXblSession")) : null; return new XblUserToken( json.get("expireTimeMs").getAsLong(), json.get("token").getAsString(), json.get("userHash").getAsString(), - prev + initialXblSession ); } - public static final class XblUserToken implements AbstractStep.StepResult { + @Value + public static class XblUserToken implements AbstractStep.StepResult { - private final long expireTimeMs; - private final String token; - private final String userHash; - private final StepInitialXblSession.InitialXblSession prevResult; + long expireTimeMs; + String token; + String userHash; + StepInitialXblSession.InitialXblSession initialXblSession; - public XblUserToken(long expireTimeMs, String token, String userHash, StepInitialXblSession.InitialXblSession prevResult) { - this.expireTimeMs = expireTimeMs; - this.token = token; - this.userHash = userHash; - this.prevResult = prevResult; + @Override + public StepInitialXblSession.InitialXblSession prevResult() { + return this.initialXblSession; } @Override @@ -113,7 +112,7 @@ public JsonObject toJson() { json.addProperty("expireTimeMs", this.expireTimeMs); json.addProperty("token", this.token); json.addProperty("userHash", this.userHash); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); + if (this.initialXblSession != null) json.add("initialXblSession", this.initialXblSession.toJson()); return json; } @@ -122,48 +121,6 @@ public boolean isExpired() { return this.expireTimeMs <= System.currentTimeMillis(); } - public long expireTimeMs() { - return expireTimeMs; - } - - public String token() { - return token; - } - - public String userHash() { - return userHash; - } - - @Override - public StepInitialXblSession.InitialXblSession prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - XblUserToken that = (XblUserToken) obj; - return this.expireTimeMs == that.expireTimeMs && - Objects.equals(this.token, that.token) && - Objects.equals(this.userHash, that.userHash) && - Objects.equals(this.prevResult, that.prevResult); - } - - @Override - public int hashCode() { - return Objects.hash(expireTimeMs, token, userHash, prevResult); - } - - @Override - public String toString() { - return "XblUserToken[" + - "expireTimeMs=" + expireTimeMs + ", " + - "token=" + token + ", " + - "userHash=" + userHash + ", " + - "prevResult=" + prevResult + ']'; - } - } } diff --git a/src/main/java/net/raphimc/mcauth/step/xbl/StepXblXstsToken.java b/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblXstsToken.java similarity index 50% rename from src/main/java/net/raphimc/mcauth/step/xbl/StepXblXstsToken.java rename to src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblXstsToken.java index f1303d8..a820c13 100644 --- a/src/main/java/net/raphimc/mcauth/step/xbl/StepXblXstsToken.java +++ b/src/main/java/net/raphimc/minecraftauth/step/xbl/StepXblXstsToken.java @@ -15,17 +15,18 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.xbl; +package net.raphimc.minecraftauth.step.xbl; import com.google.gson.JsonArray; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import com.google.gson.JsonPrimitive; -import net.raphimc.mcauth.MinecraftAuth; -import net.raphimc.mcauth.step.AbstractStep; -import net.raphimc.mcauth.step.xbl.session.StepFullXblSession; -import net.raphimc.mcauth.step.xbl.session.StepInitialXblSession; -import net.raphimc.mcauth.util.CryptUtil; +import lombok.Value; +import net.raphimc.minecraftauth.MinecraftAuth; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.xbl.session.StepFullXblSession; +import net.raphimc.minecraftauth.step.xbl.session.StepInitialXblSession; +import net.raphimc.minecraftauth.util.CryptUtil; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.ContentType; @@ -33,7 +34,6 @@ import java.time.Instant; import java.time.ZoneId; -import java.util.Objects; public class StepXblXstsToken extends AbstractStep> { @@ -41,25 +41,25 @@ public class StepXblXstsToken extends AbstractStep prevStep, final String relyingParty) { + public StepXblXstsToken(final AbstractStep prevStep, final String relyingParty) { super(prevStep); this.relyingParty = relyingParty; } @Override - public XblXstsToken applyStep(HttpClient httpClient, StepFullXblSession.FullXblSession prevResult) throws Exception { + public XblXstsToken applyStep(final HttpClient httpClient, final StepFullXblSession.FullXblSession fullXblSession) throws Exception { MinecraftAuth.LOGGER.info("Requesting XSTS Token..."); final JsonObject postData = new JsonObject(); final JsonObject properties = new JsonObject(); properties.addProperty("SandboxId", "RETAIL"); final JsonArray userTokens = new JsonArray(); - userTokens.add(new JsonPrimitive(prevResult.prevResult().token())); + userTokens.add(new JsonPrimitive(fullXblSession.getXblUserToken().getToken())); properties.add("UserTokens", userTokens); - if (prevResult.prevResult2() != null) { - properties.addProperty("TitleToken", prevResult.prevResult2().token()); - properties.addProperty("DeviceToken", prevResult.prevResult2().prevResult().prevResult2().token()); + if (fullXblSession.getXblTitleToken() != null) { + properties.addProperty("TitleToken", fullXblSession.getXblTitleToken().getToken()); + properties.addProperty("DeviceToken", fullXblSession.getXblTitleToken().getInitialXblSession().getXblDeviceToken().getToken()); } postData.add("Properties", properties); postData.addProperty("RelyingParty", this.relyingParty); @@ -68,8 +68,8 @@ public XblXstsToken applyStep(HttpClient httpClient, StepFullXblSession.FullXblS final HttpPost httpPost = new HttpPost(XBL_XSTS_URL); httpPost.setEntity(new StringEntity(postData.toString(), ContentType.APPLICATION_JSON)); httpPost.addHeader("x-xbl-contract-version", "1"); - if (prevResult.prevResult2() != null) { - httpPost.addHeader(CryptUtil.getSignatureHeader(httpPost, prevResult.prevResult2().prevResult().prevResult2().privateKey())); + if (fullXblSession.getXblTitleToken() != null) { + httpPost.addHeader(CryptUtil.getSignatureHeader(httpPost, fullXblSession.getXblTitleToken().getInitialXblSession().getXblDeviceToken().getPrivateKey())); } final String response = httpClient.execute(httpPost, new XblResponseHandler()); final JsonObject obj = JsonParser.parseString(response).getAsJsonObject(); @@ -78,42 +78,41 @@ public XblXstsToken applyStep(HttpClient httpClient, StepFullXblSession.FullXblS Instant.parse(obj.get("NotAfter").getAsString()).toEpochMilli(), obj.get("Token").getAsString(), obj.getAsJsonObject("DisplayClaims").getAsJsonArray("xui").get(0).getAsJsonObject().get("uhs").getAsString(), - prevResult + fullXblSession ); - MinecraftAuth.LOGGER.info("Got XSTS Token, expires: " + Instant.ofEpochMilli(result.expireTimeMs).atZone(ZoneId.systemDefault())); + MinecraftAuth.LOGGER.info("Got XSTS Token, expires: " + Instant.ofEpochMilli(result.getExpireTimeMs()).atZone(ZoneId.systemDefault())); return result; } @Override - public StepXblXstsToken.XblXsts refresh(HttpClient httpClient, StepXblXstsToken.XblXsts result) throws Exception { + public StepXblXstsToken.XblXsts refresh(final HttpClient httpClient, final StepXblXstsToken.XblXsts result) throws Exception { if (result == null || result.isExpired()) return super.refresh(httpClient, result); return result; } @Override - public XblXstsToken fromJson(JsonObject json) throws Exception { - final StepFullXblSession.FullXblSession prev = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("prev")) : null; + public XblXstsToken fromJson(final JsonObject json) throws Exception { + final StepFullXblSession.FullXblSession fullXblSession = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("fullXblSession")) : null; return new XblXstsToken( json.get("expireTimeMs").getAsLong(), json.get("token").getAsString(), json.get("userHash").getAsString(), - prev + fullXblSession ); } - public static final class XblXstsToken implements AbstractStep.StepResult, XblXsts { + @Value + public static class XblXstsToken implements AbstractStep.StepResult, XblXsts { - private final long expireTimeMs; - private final String token; - private final String userHash; - private final StepFullXblSession.FullXblSession prevResult; + long expireTimeMs; + String token; + String userHash; + StepFullXblSession.FullXblSession fullXblSession; - public XblXstsToken(long expireTimeMs, String token, String userHash, StepFullXblSession.FullXblSession prevResult) { - this.expireTimeMs = expireTimeMs; - this.token = token; - this.userHash = userHash; - this.prevResult = prevResult; + @Override + public StepFullXblSession.FullXblSession prevResult() { + return this.fullXblSession; } @Override @@ -122,7 +121,7 @@ public JsonObject toJson() { json.addProperty("expireTimeMs", this.expireTimeMs); json.addProperty("token", this.token); json.addProperty("userHash", this.userHash); - if (this.prevResult != null) json.add("prev", this.prevResult.toJson()); + if (this.fullXblSession != null) json.add("fullXblSession", this.fullXblSession.toJson()); return json; } @@ -131,71 +130,21 @@ public boolean isExpired() { return this.expireTimeMs <= System.currentTimeMillis(); } - @Override - public StepFullXblSession.FullXblSession fullXblSession() { - return this.prevResult; - } - - @Override - public long expireTimeMs() { - return expireTimeMs; - } - - @Override - public String token() { - return token; - } - - @Override - public String userHash() { - return userHash; - } - - @Override - public StepFullXblSession.FullXblSession prevResult() { - return prevResult; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - XblXstsToken that = (XblXstsToken) obj; - return this.expireTimeMs == that.expireTimeMs && - Objects.equals(this.token, that.token) && - Objects.equals(this.userHash, that.userHash) && - Objects.equals(this.prevResult, that.prevResult); - } - - @Override - public int hashCode() { - return Objects.hash(expireTimeMs, token, userHash, prevResult); - } - - @Override - public String toString() { - return "XblXstsToken[" + - "expireTimeMs=" + expireTimeMs + ", " + - "token=" + token + ", " + - "userHash=" + userHash + ", " + - "prevResult=" + prevResult + ']'; - } - } public interface XblXsts

> extends AbstractStep.StepResult

{ - long expireTimeMs(); + long getExpireTimeMs(); - String token(); + String getToken(); - String userHash(); + String getUserHash(); - default StepInitialXblSession.InitialXblSession initialXblSession() { - return this.fullXblSession().prevResult().prevResult(); - } + StepFullXblSession.FullXblSession getFullXblSession(); - StepFullXblSession.FullXblSession fullXblSession(); + default StepInitialXblSession.InitialXblSession getInitialXblSession() { + return this.getFullXblSession().getXblUserToken().getInitialXblSession(); + } @Override JsonObject toJson(); diff --git a/src/main/java/net/raphimc/mcauth/step/xbl/XblResponseHandler.java b/src/main/java/net/raphimc/minecraftauth/step/xbl/XblResponseHandler.java similarity index 95% rename from src/main/java/net/raphimc/mcauth/step/xbl/XblResponseHandler.java rename to src/main/java/net/raphimc/minecraftauth/step/xbl/XblResponseHandler.java index 12ab066..5f7d3dc 100644 --- a/src/main/java/net/raphimc/mcauth/step/xbl/XblResponseHandler.java +++ b/src/main/java/net/raphimc/minecraftauth/step/xbl/XblResponseHandler.java @@ -15,9 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.step.xbl; +package net.raphimc.minecraftauth.step.xbl; -import net.raphimc.mcauth.util.MicrosoftConstants; +import net.raphimc.minecraftauth.util.MicrosoftConstants; import org.apache.http.HttpEntity; import org.apache.http.HttpResponse; import org.apache.http.StatusLine; diff --git a/src/main/java/net/raphimc/minecraftauth/step/xbl/session/StepFullXblSession.java b/src/main/java/net/raphimc/minecraftauth/step/xbl/session/StepFullXblSession.java new file mode 100644 index 0000000..d4c3070 --- /dev/null +++ b/src/main/java/net/raphimc/minecraftauth/step/xbl/session/StepFullXblSession.java @@ -0,0 +1,72 @@ +/* + * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.minecraftauth.step.xbl.session; + +import com.google.gson.JsonObject; +import lombok.Value; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.SameInputOptionalMergeStep; +import net.raphimc.minecraftauth.step.xbl.StepXblTitleToken; +import net.raphimc.minecraftauth.step.xbl.StepXblUserToken; +import org.apache.http.client.HttpClient; + +public class StepFullXblSession extends SameInputOptionalMergeStep { + + public StepFullXblSession(final AbstractStep prevStep1, final AbstractStep prevStep2) { + super(prevStep1, prevStep2); + } + + @Override + public FullXblSession applyStep(final HttpClient httpClient, final StepXblUserToken.XblUserToken xblUserToken, final StepXblTitleToken.XblTitleToken xblTitleToken) throws Exception { + return new FullXblSession(xblUserToken, xblTitleToken); + } + + @Override + public FullXblSession fromJson(final JsonObject json) throws Exception { + final StepXblUserToken.XblUserToken xblUserToken = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("xblUserToken")) : null; + final StepXblTitleToken.XblTitleToken xblTitleToken = this.prevStep2 != null ? this.prevStep2.fromJson(json.getAsJsonObject("xblTitleToken")) : null; + return new FullXblSession(xblUserToken, xblTitleToken); + } + + @Value + public static class FullXblSession implements SameInputOptionalMergeStep.StepResult { + + StepXblUserToken.XblUserToken xblUserToken; + StepXblTitleToken.XblTitleToken xblTitleToken; + + @Override + public StepXblUserToken.XblUserToken prevResult() { + return this.xblUserToken; + } + + @Override + public StepXblTitleToken.XblTitleToken prevResult2() { + return this.xblTitleToken; + } + + @Override + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + if (this.xblUserToken != null) json.add("xblUserToken", this.xblUserToken.toJson()); + if (this.xblTitleToken != null) json.add("xblTitleToken", this.xblTitleToken.toJson()); + return json; + } + + } + +} diff --git a/src/main/java/net/raphimc/minecraftauth/step/xbl/session/StepInitialXblSession.java b/src/main/java/net/raphimc/minecraftauth/step/xbl/session/StepInitialXblSession.java new file mode 100644 index 0000000..f7b272c --- /dev/null +++ b/src/main/java/net/raphimc/minecraftauth/step/xbl/session/StepInitialXblSession.java @@ -0,0 +1,72 @@ +/* + * This file is part of MinecraftAuth - https://github.com/RaphiMC/MinecraftAuth + * Copyright (C) 2023 RK_01/RaphiMC and contributors + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +package net.raphimc.minecraftauth.step.xbl.session; + +import com.google.gson.JsonObject; +import lombok.Value; +import net.raphimc.minecraftauth.step.AbstractStep; +import net.raphimc.minecraftauth.step.OptionalMergeStep; +import net.raphimc.minecraftauth.step.msa.StepMsaToken; +import net.raphimc.minecraftauth.step.xbl.StepXblDeviceToken; +import org.apache.http.client.HttpClient; + +public class StepInitialXblSession extends OptionalMergeStep { + + public StepInitialXblSession(final AbstractStep prevStep1, final AbstractStep prevStep2) { + super(prevStep1, prevStep2); + } + + @Override + public InitialXblSession applyStep(final HttpClient httpClient, final StepMsaToken.MsaToken msaToken, final StepXblDeviceToken.XblDeviceToken xblDeviceToken) throws Exception { + return new InitialXblSession(msaToken, xblDeviceToken); + } + + @Override + public InitialXblSession fromJson(JsonObject json) throws Exception { + final StepMsaToken.MsaToken msaToken = this.prevStep != null ? this.prevStep.fromJson(json.getAsJsonObject("msaToken")) : null; + final StepXblDeviceToken.XblDeviceToken xblDeviceToken = this.prevStep2 != null ? this.prevStep2.fromJson(json.getAsJsonObject("xblDeviceToken")) : null; + return new InitialXblSession(msaToken, xblDeviceToken); + } + + @Value + public static class InitialXblSession implements OptionalMergeStep.StepResult { + + StepMsaToken.MsaToken msaToken; + StepXblDeviceToken.XblDeviceToken xblDeviceToken; + + @Override + public StepMsaToken.MsaToken prevResult() { + return this.msaToken; + } + + @Override + public StepXblDeviceToken.XblDeviceToken prevResult2() { + return this.xblDeviceToken; + } + + @Override + public JsonObject toJson() { + final JsonObject json = new JsonObject(); + if (this.msaToken != null) json.add("msaToken", this.msaToken.toJson()); + if (this.xblDeviceToken != null) json.add("xblDeviceToken", this.xblDeviceToken.toJson()); + return json; + } + + } + +} diff --git a/src/main/java/net/raphimc/mcauth/util/CryptUtil.java b/src/main/java/net/raphimc/minecraftauth/util/CryptUtil.java similarity index 86% rename from src/main/java/net/raphimc/mcauth/util/CryptUtil.java rename to src/main/java/net/raphimc/minecraftauth/util/CryptUtil.java index 3bc041f..ec6652c 100644 --- a/src/main/java/net/raphimc/mcauth/util/CryptUtil.java +++ b/src/main/java/net/raphimc/minecraftauth/util/CryptUtil.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.util; +package net.raphimc.minecraftauth.util; import com.google.gson.JsonObject; import io.jsonwebtoken.Jwts; @@ -29,8 +29,13 @@ import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.NoSuchAlgorithmException; +import java.security.PrivateKey; +import java.security.PublicKey; import java.security.interfaces.ECPrivateKey; import java.security.interfaces.ECPublicKey; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.PKCS8EncodedKeySpec; +import java.security.spec.X509EncodedKeySpec; import java.time.Instant; import java.util.Base64; @@ -52,6 +57,22 @@ public class CryptUtil { } } + public static T publicKeyFromBase64(final String base64) { + try { + return (T) EC_KEYFACTORY.generatePublic(new X509EncodedKeySpec(Base64.getDecoder().decode(base64))); + } catch (InvalidKeySpecException e) { + throw new RuntimeException("Could not decode base64 public key", e); + } + } + + public static T privateKeyFromBase64(final String base64) { + try { + return (T) EC_KEYFACTORY.generatePrivate(new PKCS8EncodedKeySpec(Base64.getDecoder().decode(base64))); + } catch (InvalidKeySpecException e) { + throw new RuntimeException("Could not decode base64 private key", e); + } + } + public static BasicHeader getSignatureHeader(final HttpUriRequest httpRequest, final ECPrivateKey privateKey) throws IOException { final long windowsTimestamp = (Instant.now().getEpochSecond() + 11644473600L) * 10000000L; diff --git a/src/main/java/net/raphimc/mcauth/util/MicrosoftConstants.java b/src/main/java/net/raphimc/minecraftauth/util/MicrosoftConstants.java similarity index 97% rename from src/main/java/net/raphimc/mcauth/util/MicrosoftConstants.java rename to src/main/java/net/raphimc/minecraftauth/util/MicrosoftConstants.java index 769f930..b79e909 100644 --- a/src/main/java/net/raphimc/mcauth/util/MicrosoftConstants.java +++ b/src/main/java/net/raphimc/minecraftauth/util/MicrosoftConstants.java @@ -15,8 +15,9 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.util; +package net.raphimc.minecraftauth.util; +import net.raphimc.minecraftauth.MinecraftAuth; import org.apache.http.Header; import org.apache.http.HttpHeaders; import org.apache.http.client.config.RequestConfig; @@ -72,7 +73,7 @@ public static CloseableHttpClient createHttpClient() { .setSocketTimeout(timeout * 1000).build(); final List

headers = getDefaultHeaders(); - headers.add(new BasicHeader(HttpHeaders.USER_AGENT, "MinecraftAuth/2.1")); + headers.add(new BasicHeader(HttpHeaders.USER_AGENT, "MinecraftAuth/" + MinecraftAuth.VERSION)); return HttpClientBuilder.create() .setDefaultRequestConfig(requestConfig) diff --git a/src/main/java/net/raphimc/mcauth/util/logging/ConsoleLogger.java b/src/main/java/net/raphimc/minecraftauth/util/logging/ConsoleLogger.java similarity index 96% rename from src/main/java/net/raphimc/mcauth/util/logging/ConsoleLogger.java rename to src/main/java/net/raphimc/minecraftauth/util/logging/ConsoleLogger.java index f4ac51a..c309b6c 100644 --- a/src/main/java/net/raphimc/mcauth/util/logging/ConsoleLogger.java +++ b/src/main/java/net/raphimc/minecraftauth/util/logging/ConsoleLogger.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.util.logging; +package net.raphimc.minecraftauth.util.logging; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/src/main/java/net/raphimc/mcauth/util/logging/ILogger.java b/src/main/java/net/raphimc/minecraftauth/util/logging/ILogger.java similarity index 95% rename from src/main/java/net/raphimc/mcauth/util/logging/ILogger.java rename to src/main/java/net/raphimc/minecraftauth/util/logging/ILogger.java index 71dd5d8..7fb20c0 100644 --- a/src/main/java/net/raphimc/mcauth/util/logging/ILogger.java +++ b/src/main/java/net/raphimc/minecraftauth/util/logging/ILogger.java @@ -15,7 +15,7 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ -package net.raphimc.mcauth.util.logging; +package net.raphimc.minecraftauth.util.logging; public interface ILogger {