using Supercell.Laser.Server.Networking.Security; using Supercell.Laser.Titan.Library.Blake; namespace Supercell.Laser.Server.Networking { using Supercell.Laser.Logic.Message; using Supercell.Laser.Server.Message; using Supercell.Laser.Titan.Cryptography; using Supercell.Laser.Titan.Library; using Supercell.Laser.Titan.Math; using Supercell.Laser.Server.Logic; using Supercell.Laser.Server.Settings; using System.Linq; public class Messaging { private const int HeaderLength = 7; private const int PepperHandshakePayloadLength = 72; public byte[] SessionToken { get; } private Connection Connection; private int PepperState; private StreamEncrypter Encrypter; private StreamEncrypter Decrypter; private MessageFactory MessageFactory; private byte[] RNonce; private byte[] SNonce; private byte[] SecretKey; public int Seed { get; set; } public int PepperHandshakeState => PepperState; public Messaging(Connection connection) { Connection = connection; MessageFactory = MessageFactory.Instance; PepperState = 2; SessionToken = new byte[24]; SNonce = new byte[24]; SecretKey = new byte[32]; TweetNaCl.RandomBytes(SessionToken); TweetNaCl.RandomBytes(SNonce); TweetNaCl.RandomBytes(SecretKey); } public void Send(GameMessage message) { if (message.GetMessageType() == 20100) { EncryptAndWrite(message); } else { Processor.Send(Connection, message); } } public void EncryptAndWrite(GameMessage message) { if (message.GetEncodingLength() == 0) message.Encode(); byte[] payload = new byte[message.GetEncodingLength()]; Buffer.BlockCopy(message.GetMessageBytes(), 0, payload, 0, payload.Length); int messageType = message.GetMessageType(); int version = message.GetVersion(); switch (PepperState) { case 4: payload = SendPepperLoginResponse(payload); break; case 5: byte[] encrypted = new byte[payload.Length + Encrypter.GetEncryptionOverhead()]; Encrypter.Encrypt(payload, encrypted, payload.Length); payload = encrypted; break; } byte[] stream = new byte[payload.Length + 7]; int length = payload.Length; stream[0] = (byte)(messageType >> 8); stream[1] = (byte)(messageType); stream[2] = (byte)(length >> 16); stream[3] = (byte)(length >> 8); stream[4] = (byte)(length); stream[5] = (byte)(version >> 8); stream[6] = (byte)(version); Buffer.BlockCopy(payload, 0, stream, 7, payload.Length); Connection.Write(stream); } public int OnReceive() { long bufferedBytes = Connection.Memory.Length; if (bufferedBytes > Connection.MaxPendingTcpBytes) { Logger.Warning($"TCP buffer overflow attempt from {(Connection == null ? "conn#?" : Connection.Describe())}: buffered={bufferedBytes}"); return -1; } if (bufferedBytes < HeaderLength) { return 0; } Connection.Memory.Position = 0; while (Connection.Memory.Length - Connection.Memory.Position >= HeaderLength) { long packetStartPosition = Connection.Memory.Position; byte[] headerBuffer = new byte[HeaderLength]; Connection.Memory.Read(headerBuffer, 0, HeaderLength); int type = headerBuffer[0] << 8 | headerBuffer[1]; int length = headerBuffer[2] << 16 | headerBuffer[3] << 8 | headerBuffer[4]; int version = headerBuffer[5] << 8 | headerBuffer[6]; if (length < 0 || length > Connection.MaxTcpPayloadLength) { Logger.Warning($"Rejected TCP packet from {(Connection == null ? "conn#?" : Connection.Describe())}: type={type} length={length}"); return -1; } if (Connection.Memory.Length - Connection.Memory.Position < length) { Connection.Memory.Position = packetStartPosition; return 0; } byte[] payload = new byte[length]; if (Connection.Memory.Read(payload, 0, length) < length) { Connection.Memory.Position = packetStartPosition; return 0; } if (ReadNewMessage(type, length, version, payload) != 0) { return -1; } } long position = Connection.Memory.Position; byte[] all = Connection.Memory.ToArray(); byte[] buffer = all.Skip((int)position).ToArray(); Connection.Memory = new MemoryStream(); Connection.Memory.Write(buffer, 0, buffer.Length); return 0; } private int ReadNewMessage(int type, int length, int version, byte[] payload) { switch (PepperState) { case 2: if (type == 10100) PepperState = 3; else { Logger.Warning($"Invalid handshake from {(Connection == null ? "conn#?" : Connection.Describe())}: expected 10100, got type={type} length={length} version={version} state={PepperState}"); return -1; } break; case 3: if (type != 10101) { Logger.Warning($"Invalid handshake from {(Connection == null ? "conn#?" : Connection.Describe())}: expected 10101, got type={type} length={length} version={version} state={PepperState}"); return -1; } if (length < PepperHandshakePayloadLength) { Logger.Warning($"Invalid 10101 payload from {(Connection == null ? "conn#?" : Connection.Describe())}: length={length}, min={PepperHandshakePayloadLength}, state={PepperState}"); return -1; } payload = HandlePepperLogin(payload); if (payload == null) { Logger.Warning($"Pepper login failed for {(Connection == null ? "conn#?" : Connection.Describe())}: type={type} length={length} version={version} state={PepperState}"); return -1; } break; case 5: if (length < Decrypter.GetEncryptionOverhead()) { Logger.Warning($"Encrypted payload too short from {(Connection == null ? "conn#?" : Connection.Describe())}: type={type} length={length} overhead={Decrypter.GetEncryptionOverhead()} state={PepperState}"); return -1; } byte[] decrypted = new byte[length - Decrypter.GetEncryptionOverhead()]; int result = Decrypter.Decrypt(payload, decrypted, length); payload = decrypted; if (result != 0) { Logger.Warning($"Decrypt failed for {(Connection == null ? "conn#?" : Connection.Describe())}: type={type} length={length} version={version} state={PepperState} result={result}"); return -1; } break; } GameMessage message = MessageFactory.CreateMessageByType(type); if (message != null) { try { message.SetVersion(version); message.GetByteStream().SetByteArray(payload, payload.Length); message.Decode(); } catch (Exception exception) { Logger.Warning($"Failed to decode packet {type} from {(Connection == null ? "conn#?" : Connection.Describe())}: {exception.Message} | length={length} version={version} state={PepperState}"); return -1; } if (message.GetMessageType() == 10100) { Connection.MessageManager.ReceiveMessage(message); } else { Processor.Receive(Connection, message); } } else { Logger.Warning($"Unknown packet {type} from {(Connection == null ? "conn#?" : Connection.Describe())} — ignored"); } return 0; } private byte[] client_pk; private byte[] client_sk; private byte[] HandlePepperLogin(byte[] payload) { try { if (payload == null || payload.Length < PepperHandshakePayloadLength) { return null; } client_pk = payload.Take(32).ToArray(); client_sk = new byte[32]; LogicRandom rand = new LogicRandom(Seed); int c = 0; for (int i = 0; i < 12; i++) { c = rand.Rand(256); } for (int i = 0; i < 32; i++) { client_sk[i] = (byte)(rand.Rand(256) ^ c); } if (!TweetNaCl.CryptoScalarmultBase(client_sk).SequenceEqual(client_pk)) { Logger.Warning($"Cryptography error for {(Connection == null ? "conn#?" : Connection.Describe())}: 10101 public key is invalid."); return null; } Blake2BHasher hasher = new Blake2BHasher(); hasher.Update(client_pk); hasher.Update(PepperKey.SERVER_PK); byte[] nonce = hasher.Finish(); byte[] decrypted = TweetNaCl.CryptoBoxOpen(payload.Skip(32).ToArray(), nonce, PepperKey.SERVER_PK, client_sk); if (decrypted == null || decrypted.Length < 48) { return null; } byte[] session_token = decrypted.Take(24).ToArray(); if (!session_token.SequenceEqual(SessionToken)) { Logger.Warning($"Cryptography error for {(Connection == null ? "conn#?" : Connection.Describe())}: session token is invalid."); return null; } PepperState = 4; RNonce = decrypted.Skip(24).Take(24).ToArray(); return decrypted.Skip(48).ToArray(); } catch (TweetNaCl.InvalidCipherTextException exception) { Logger.Exception($"Failed to decrypt 10101 for {(Connection == null ? "conn#?" : Connection.Describe())}", exception); return null; } } private byte[] SendPepperLoginResponse(byte[] payload) { byte[] packet = new byte[payload.Length + 32 + 24]; Buffer.BlockCopy(SNonce, 0, packet, 0, 24); Buffer.BlockCopy(SecretKey, 0, packet, 24, 32); Buffer.BlockCopy(payload, 0, packet, 24 + 32, payload.Length); Blake2BHasher hasher = new Blake2BHasher(); hasher.Update(RNonce); hasher.Update(client_pk); hasher.Update(PepperKey.SERVER_PK); byte[] nonce = hasher.Finish(); byte[] encrypted = TweetNaCl.CryptoBox(packet, nonce, PepperKey.SERVER_PK, client_sk); PepperState = 5; Decrypter = new PepperEncrypter(SecretKey, RNonce); Encrypter = new PepperEncrypter(SecretKey, SNonce); return encrypted; } } }