Permalink
Browse files

Implemented new Yggdrasil authentication scheme, but it is not in use…

… yet.

Renamed LoginSession to LegacySession.
  • Loading branch information...
sk89q committed Oct 18, 2013
1 parent 7d29593 commit f3ca9205c5de41d1b8481b8dce2f4b079955e633
View
15 pom.xml
@@ -30,11 +30,16 @@
<artifactId>simplenbt</artifactId>
<version>1.0.4-SNAPSHOT</version>
</dependency>
- <dependency>
- <groupId>com.github.jponge</groupId>
- <artifactId>lzma-java</artifactId>
- <version>1.2</version>
- </dependency>
+ <dependency>
+ <groupId>com.github.jponge</groupId>
+ <artifactId>lzma-java</artifactId>
+ <version>1.2</version>
+ </dependency>
+ <dependency> <!-- GSON is small compared to Jackson -->
+ <groupId>com.google.code.gson</groupId>
+ <artifactId>gson</artifactId>
+ <version>2.2.4</version>
+ </dependency>
</dependencies>
<build>
<sourceDirectory>${basedir}/src/main/java/</sourceDirectory>
@@ -34,7 +34,7 @@
import com.sk89q.mclauncher.config.SettingsList;
import com.sk89q.mclauncher.event.ProgressListener;
import com.sk89q.mclauncher.launch.LaunchProcessBuilder;
-import com.sk89q.mclauncher.session.LoginSession;
+import com.sk89q.mclauncher.session.LegacySession;
import com.sk89q.mclauncher.session.MinecraftSession;
import com.sk89q.mclauncher.session.MinecraftSession.InvalidCredentialsException;
import com.sk89q.mclauncher.session.MinecraftSession.LoginException;
@@ -120,7 +120,7 @@ private void createSession() throws ExecutionException, InterruptedException {
throw new ExecutionException("Login once before using offline mode.");
}
} else {
- session = new LoginSession(identity.getId());
+ session = new LegacySession(identity.getId());
}
LauncherUtils.checkInterrupted();
@@ -38,7 +38,7 @@
/**
* Manages a login session.
*/
-public class LoginSession implements MinecraftSession {
+public class LegacySession implements MinecraftSession {
private static final String MINECRAFT_LOGIN_URL = "https://login.minecraft.net/";
private static final String LAUNCHER_VERSION = "13";
@@ -55,7 +55,7 @@
*
* @param username username
*/
- public LoginSession(String username) {
+ public LegacySession(String username) {
this.username = username;
try {
this.loginURL = new URL(MINECRAFT_LOGIN_URL);
@@ -22,9 +22,6 @@
/**
* Represents a Minecraft session.
- *
- * @see LoginSession the authenticated session
- * @see OfflineSession the offline session
*/
public interface MinecraftSession {
@@ -0,0 +1,177 @@
+/*
+ * SK's Minecraft Launcher
+ * Copyright (C) 2010, 2011 Albert Pham <http://www.sk89q.com>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU 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 <http://www.gnu.org/licenses/>.
+*/
+
+package com.sk89q.mclauncher.session;
+
+import com.google.gson.Gson;
+import com.sk89q.mclauncher.Launcher;
+import com.sk89q.mclauncher.security.X509KeyRing;
+import com.sk89q.mclauncher.util.LauncherUtils;
+
+import javax.net.ssl.HttpsURLConnection;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.TrustManager;
+import java.io.*;
+import java.net.URL;
+import java.security.KeyManagementException;
+import java.security.NoSuchAlgorithmException;
+
+/**
+ * Implements the newer Yggdrasil authentication scheme used since the
+ * Minecraft 1.6 update.
+ */
+public class YggdrasilSession implements MinecraftSession {
+
+ private static final int READ_TIMEOUT = 1000 * 60 * 10;
+ private final Gson gson = new Gson();
+
+ private String username;
+ private String accessToken;
+ private String clientToken;
+
+ /**
+ * Construct the session.
+ *
+ * @param username username
+ */
+ public YggdrasilSession(String username) {
+ this.username = username;
+ }
+
+ @Override
+ public void login(String password) throws IOException, LoginException {
+ HttpsURLConnection conn = null;
+ URL url = new URL("https://authserver.mojang.com/authenticate");
+ String payload = gson.toJson(new AuthenticatePayload(username, password));
+
+ // Get trust chain
+ TrustManager[] trustManagers = new TrustManager[] {
+ Launcher.getInstance().getKeyRing().getKeyStore(
+ X509KeyRing.Ring.MINECRAFT_LOGIN)};
+
+ try {
+ conn = (HttpsURLConnection) url.openConnection();
+ SSLContext ctx = SSLContext.getInstance("TLS");
+ ctx.init(null, trustManagers, null);
+ conn.setSSLSocketFactory(ctx.getSocketFactory());
+ conn.setRequestMethod("POST");
+ conn.setRequestProperty("Content-Type", "application/json");
+ conn.setRequestProperty("Content-Length",
+ Integer.toString(payload.getBytes().length));
+ conn.setRequestProperty("Content-Language", "en-US");
+ conn.setUseCaches(false);
+ conn.setDoInput(true);
+ conn.setDoOutput(true);
+ conn.setReadTimeout(READ_TIMEOUT);
+
+ conn.connect();
+
+ DataOutputStream out = new DataOutputStream(conn.getOutputStream());
+ out.writeBytes(payload);
+ out.flush();
+ out.close();
+
+ InputStream is = conn.getResponseCode() == 200 ?
+ conn.getInputStream() : conn.getErrorStream();
+ String result = LauncherUtils.toString(is, "UTF-8");
+
+ if (conn.getResponseCode() != 200) {
+ ErrorResponse error = gson.fromJson(result, ErrorResponse.class);
+ throw new LoginException(error.getErrorMessage());
+ } else {
+ AuthenticateResponse response = gson.fromJson(result, AuthenticateResponse.class);
+ accessToken = response.getAccessToken();
+ clientToken = response.getClientToken();
+ }
+ } catch (KeyManagementException e) {
+ throw new LoginException("Failed to process PKI keys: " + e.getMessage(), e);
+ } catch (NoSuchAlgorithmException e) {
+ throw new LoginException("Failed to initiate TLS: " + e.getMessage(), e);
+ } finally {
+ if (conn != null) conn.disconnect();
+ conn = null;
+ }
+ }
+
+ @Override
+ public boolean isValid() {
+ return clientToken != null;
+ }
+
+ @Override
+ public String getUsername() {
+ return username;
+ }
+
+ @Override
+ public String getSessionId() {
+ return clientToken;
+ }
+
+ public String getAccessToken() {
+ return accessToken;
+ }
+
+ private static class Agent {
+ private final String name = "SKMCLauncher";
+ private final int version = 1;
+ }
+
+ private static class AuthenticatePayload {
+ private final Agent agent = new Agent();
+ private final String username;
+ private final String password;
+
+ public AuthenticatePayload(String username, String password) {
+ this.username = username;
+ this.password = password;
+ }
+ }
+
+ private static class AuthenticateResponse {
+ private String accessToken;
+ private String clientToken;
+
+ private String getAccessToken() {
+ return accessToken;
+ }
+
+ private String getClientToken() {
+ return clientToken;
+ }
+ }
+
+ private static class ErrorResponse {
+ private String error;
+ private String errorMessage;
+ private String cause;
+
+ private String getError() {
+ return error;
+ }
+
+ private String getErrorMessage() {
+ return errorMessage;
+ }
+
+ private String getCause() {
+ return cause;
+ }
+ }
+
+}
@@ -30,7 +30,7 @@
import com.sk89q.mclauncher.Launcher;
import com.sk89q.mclauncher.config.Configuration;
import com.sk89q.mclauncher.model.PackageManifest;
-import com.sk89q.mclauncher.session.LoginSession;
+import com.sk89q.mclauncher.session.LegacySession;
import com.sk89q.mclauncher.session.MinecraftSession;
import com.sk89q.mclauncher.util.LauncherUtils;
@@ -47,7 +47,7 @@
private final Configuration configuration;
private final UpdateCache cache;
- private final LoginSession session;
+ private final LegacySession session;
/**
* Create a new configuration.
@@ -57,7 +57,7 @@
* @param cache the update cache
*/
public VanillaUpdateCheck(Configuration configuration,
- LoginSession session, UpdateCache cache) {
+ LegacySession session, UpdateCache cache) {
this.configuration = configuration;
this.session = session;
this.cache = cache;
@@ -140,11 +140,11 @@ public void importLauncherUpdateVersion(UpdateCache cache) throws IOException {
public static UpdateCheck fromSession(Configuration configuration,
MinecraftSession session, UpdateCache cache) throws UpdateException {
- if (!(session instanceof LoginSession)) {
+ if (!(session instanceof LegacySession)) {
throw new UpdateException("An update is not possible when in offline mode.");
}
- return new VanillaUpdateCheck(configuration, (LoginSession) session, cache);
+ return new VanillaUpdateCheck(configuration, (LegacySession) session, cache);
}
}
@@ -18,20 +18,7 @@
package com.sk89q.mclauncher.util;
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.BufferedReader;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.io.Writer;
+import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.MessageDigest;
@@ -402,4 +389,25 @@ public static String joinUnixPath(String path, String path2) {
}
}
+ /**
+ * Read an {@link InputStream} to a string.
+ *
+ * @param is the input stream
+ * @param encoding the encoding to read with
+ * @return the string
+ * @throws IOException on I/O error
+ */
+ public static String toString(InputStream is, String encoding) throws IOException {
+ BufferedReader reader = new BufferedReader(
+ new InputStreamReader(is, encoding));
+
+ StringBuilder s = new StringBuilder();
+ char[] buf = new char[1024];
+ int len = 0;
+ while ((len = reader.read(buf)) != -1) {
+ s.append(buf, 0, len);
+ }
+ return s.toString();
+ }
+
}

0 comments on commit f3ca920

Please sign in to comment.