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}