package com.asteria.net.login; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.ByteToMessageDecoder; import java.math.BigInteger; import java.util.List; import com.asteria.net.ISAACCipher; import com.asteria.net.NetworkConstants; import com.asteria.net.message.LoginDetailsMessage; import com.asteria.net.message.MessageBuilder; /** * The {@link ByteToMessageDecoder} implementation that will manage the * post-handshake section of the login protocol. * * @author lare96 <http://github.com/lare96> */ public final class PostLoginHandshakeHandler extends ByteToMessageDecoder { @Override protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { // Read the login type, validate it. if (in.readableBytes() < 2) return; int type = in.readByte(); if (type != 16 && type != 18) throw new Exception("Invalid login type [" + type + "]"); // Decode the block length, validate RSA block size. int blockLength = in.readUnsignedByte(); int loginEncryptPacketSize = blockLength - (36 + 1 + 1 + 2); if (loginEncryptPacketSize <= 0) throw new Exception("Invalid RSA packet size [" + loginEncryptPacketSize + "]"); if (in.readableBytes() < blockLength) return; // Read the client version, validate it. in.readByte(); int version = in.readShort(); if (version != 317) throw new Exception("Invalid client version [" + version + "]"); // Read and ignore the data for CRC keys. in.readByte(); for (int i = 0; i < 9; i++) in.readInt(); // Either decode RSA, or proceed normally depending on the network // settings. loginEncryptPacketSize--; in.readByte(); String username = null; String password = null; ISAACCipher encryptor = null; ISAACCipher decryptor = null; if (NetworkConstants.DECODE_RSA) { byte[] encryptionBytes = new byte[loginEncryptPacketSize]; in.readBytes(encryptionBytes); ByteBuf rsaBuffer = Unpooled.wrappedBuffer(new BigInteger(encryptionBytes).modPow(NetworkConstants.RSA_EXPONENT, NetworkConstants.RSA_MODULUS).toByteArray()); int rsaOpcode = rsaBuffer.readByte(); if (rsaOpcode != 10) throw new Exception("Invalid RSA opcode [" + rsaOpcode + "]"); long clientHalf = rsaBuffer.readLong(); long serverHalf = rsaBuffer.readLong(); int[] isaacSeed = { (int) (clientHalf >> 32), (int) clientHalf, (int) (serverHalf >> 32), (int) serverHalf }; decryptor = new ISAACCipher(isaacSeed); for (int i = 0; i < isaacSeed.length; i++) isaacSeed[i] += 50; encryptor = new ISAACCipher(isaacSeed); rsaBuffer.readInt(); MessageBuilder db = MessageBuilder.create(rsaBuffer); username = db.getString(); password = db.getString(); } else { in.readByte(); long clientHalf = in.readLong(); long serverHalf = in.readLong(); int[] isaacSeed = { (int) (clientHalf >> 32), (int) clientHalf, (int) (serverHalf >> 32), (int) serverHalf }; decryptor = new ISAACCipher(isaacSeed); for (int i = 0; i < isaacSeed.length; i++) isaacSeed[i] += 50; encryptor = new ISAACCipher(isaacSeed); in.readInt(); MessageBuilder db = MessageBuilder.create(in); username = db.getString().toLowerCase().replaceAll("_", " ").trim(); password = db.getString().toLowerCase(); } // Finally, we've decoded all the data we need for the final response of // the login protocol. Here we send it as an upstream Netty message to // be handled in the PlayerIO class. out.add(new LoginDetailsMessage(ctx, username, password, encryptor, decryptor)); } }