001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package net.tridentsdk.server.packets.login; 019 020import com.google.gson.Gson; 021import io.netty.buffer.ByteBuf; 022import net.tridentsdk.server.netty.ClientConnection; 023import net.tridentsdk.server.netty.Codec; 024import net.tridentsdk.server.netty.packet.InPacket; 025import net.tridentsdk.server.netty.packet.Packet; 026import net.tridentsdk.server.netty.packet.RSA; 027import net.tridentsdk.server.netty.protocol.Protocol; 028import net.tridentsdk.server.player.TridentPlayer; 029import net.tridentsdk.util.TridentLogger; 030 031import javax.net.ssl.HttpsURLConnection; 032import java.io.BufferedReader; 033import java.io.InputStreamReader; 034import java.math.BigInteger; 035import java.net.URL; 036import java.net.URLEncoder; 037import java.security.MessageDigest; 038import java.util.Arrays; 039import java.util.List; 040import java.util.UUID; 041import java.util.regex.Pattern; 042 043public class PacketLoginInEncryptionResponse extends InPacket { 044 /** 045 * Gson instance 046 */ 047 protected static final Gson GSON = new Gson(); 048 /** 049 * Pattern used to format the UUID 050 */ 051 protected static final Pattern idDash; 052 053 static { 054 idDash = Pattern.compile("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})"); 055 } 056 057 /** 058 * Length of the secret key 059 */ 060 protected short secretLength; 061 /** 062 * Length of the token 063 */ 064 protected short tokenLength; 065 066 /** 067 * Secret token used as an AES encryption key (encrypted using login keypair) 068 */ 069 protected byte[] encryptedSecret; 070 /** 071 * Login token (encrypted using the login keypair) 072 */ 073 protected byte[] encryptedToken; 074 075 @Override 076 public int id() { 077 return 0x01; 078 } 079 080 @Override 081 public Packet decode(ByteBuf buf) { 082 this.secretLength = (short) Codec.readVarInt32(buf); 083 this.encryptedSecret = new byte[(int) this.secretLength]; 084 085 buf.readBytes(this.encryptedSecret); 086 087 this.tokenLength = (short) Codec.readVarInt32(buf); 088 this.encryptedToken = new byte[(int) this.tokenLength]; 089 090 buf.readBytes(this.encryptedToken); 091 092 return this; 093 } 094 095 /** 096 * Gets the length of the secret 097 * 098 * @return the secret length 099 */ 100 public short secretLength() { 101 return this.secretLength; 102 } 103 104 /** 105 * Gets the length of the client token 106 * 107 * @return the token client length 108 */ 109 public short tokenLength() { 110 return this.tokenLength; 111 } 112 113 @Override 114 public void handleReceived(ClientConnection connection) { 115 // Decrypt and store the shared secret and token 116 byte[] sharedSecret = null; 117 byte[] token = null; 118 119 try { 120 sharedSecret = RSA.decrypt(this.encryptedSecret, connection.loginKeyPair().getPrivate()); 121 token = RSA.decrypt(this.encryptedToken, connection.loginKeyPair().getPrivate()); 122 } catch (Exception e) { 123 TridentLogger.get().error(e); 124 } 125 126 // Check that we got the same verification token; 127 if (!Arrays.equals(connection.verificationToken(), token)) { 128 TridentLogger.get().log("Client with IP " + connection.address().getHostName() + 129 " has sent an invalid token!"); 130 131 connection.logout(); 132 return; 133 } 134 135 String name = LoginHandler.getInstance().name(connection.address()); 136 StringBuilder sb = new StringBuilder(); 137 138 try { 139 // Contact Mojang's session servers, to finalize creating the session as well as get the client's UUID 140 URL url = new URL("https://sessionserver.mojang.com/session/minecraft/hasJoined?username=" + 141 URLEncoder.encode(name, "UTF-8") + "&serverId=" + 142 new BigInteger(HashGenerator.getHash(connection, sharedSecret)).toString(16)); 143 HttpsURLConnection c = (HttpsURLConnection) url.openConnection(); 144 int code = c.getResponseCode(); 145 146 // If the code isn't 200 OK, logout and inform the client of so 147 if (code != 200) { 148 connection.sendPacket(new PacketLoginOutDisconnect().setJsonMessage("Unable to create session")); 149 150 connection.logout(); 151 return; 152 } 153 154 BufferedReader reader = new BufferedReader(new InputStreamReader(c.getInputStream())); 155 String line; 156 157 while ((line = reader.readLine()) != null) { 158 sb.append(line); 159 sb.append('\n'); 160 } 161 162 reader.close(); 163 } catch (Exception ex) { 164 TridentLogger.get().error(ex); 165 166 connection.sendPacket(new PacketLoginOutDisconnect().setJsonMessage("An internal error occurred while " + 167 "validating session. Perhaps authentication servers are down?")); 168 connection.logout(); 169 return; 170 } 171 172 // Enable encryption from hereon out 173 connection.enableEncryption(sharedSecret); 174 175 // Read the JSON response 176 SessionResponse response = GSON.fromJson(sb.toString(), SessionResponse.class); 177 PacketLoginOutSuccess packet = new PacketLoginOutSuccess(); 178 179 //Replaces the '-' less UUID from session server, with the required '-' filled UUID 180 packet.set("uuid", idDash.matcher(response.id).replaceAll("$1-$2-$3-$4-$5")); 181 packet.set("username", response.name); 182 183 // Send the client PacketLoginOutSuccess and set the new stage to PLAY 184 connection.enableCompression(); 185 connection.sendPacket(packet); 186 connection.setStage(Protocol.ClientStage.PLAY); 187 188 // Store the UUID to be used when spawning the player 189 UUID id = UUID.fromString(packet.uniqueId()); 190 191 // Remove stored information in LoginManager and spawn the player 192 LoginHandler.getInstance().finish(connection.address()); 193 TridentPlayer.spawnPlayer(connection, id, name); 194 } 195 196 protected static final class HashGenerator { 197 198 private HashGenerator() { 199 } 200 201 /** 202 * Used to generate the hash for the serverId 203 * 204 * @param connection Connection of the client 205 * @param secret Scared secret 206 * @return Generated Hash 207 */ 208 static byte[] getHash(ClientConnection connection, byte... secret) throws Exception { 209 byte[][] b = { secret, connection.loginKeyPair().getPublic().getEncoded() }; 210 MessageDigest digest = MessageDigest.getInstance("SHA-1"); 211 212 for (byte[] bytes : b) { 213 digest.update(bytes); 214 } 215 216 return digest.digest(); 217 } 218 } 219 220 /** 221 * Response received from the session server 222 */ 223 public static class SessionResponse { 224 /** 225 * Id of the player 226 */ 227 String id; 228 /** 229 * Name of the player 230 */ 231 String name; 232 /** 233 * List or JsonArray of properties 234 */ 235 List<Properties> properties; 236 boolean legacy; 237 238 public static class Properties { 239 String name; 240 String value; 241 String signature; 242 } 243 } 244}