From 539364846a89987ac2679988653f50332cb91d26 Mon Sep 17 00:00:00 2001 From: "madmaxoft@gmail.com" Date: Thu, 30 Aug 2012 21:06:13 +0000 Subject: Implemented 1.3.2 protocol encryption using CryptoPP, up to Client Status packet (http://wiki.vg/Protocol_FAQ step 14) git-svn-id: http://mc-server.googlecode.com/svn/trunk@808 0a769ca7-a7f5-676a-18bf-c427514a06d6 --- source/Protocol132.cpp | 200 +++++++++++++++++++++++++++++++++++++++++- source/Protocol132.h | 23 +++++ source/ProtocolRecognizer.cpp | 3 +- source/cServer.cpp | 18 ++++ source/cServer.h | 10 +++ 5 files changed, 248 insertions(+), 6 deletions(-) (limited to 'source') diff --git a/source/Protocol132.cpp b/source/Protocol132.cpp index faff57004..2a4a830c6 100644 --- a/source/Protocol132.cpp +++ b/source/Protocol132.cpp @@ -5,6 +5,10 @@ #include "Globals.h" #include "Protocol132.h" +#include "cRoot.h" +#include "cServer.h" +#include "cClientHandle.h" +#include "CryptoPP/osrng.h" @@ -28,11 +32,24 @@ typedef unsigned char Byte; +using namespace CryptoPP; + + + + + +const int MAX_ENC_LEN = 512; // Maximum size of the encrypted message; should be 128, but who knows... + + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cProtocol132: cProtocol132::cProtocol132(cClientHandle * a_Client) : - super(a_Client) + super(a_Client), + m_IsEncrypted(false) { } @@ -42,8 +59,36 @@ cProtocol132::cProtocol132(cClientHandle * a_Client) : void cProtocol132::DataReceived(const char * a_Data, int a_Size) { - // TODO: Protocol decryption - super::DataReceived(a_Data, a_Size); + if (m_IsEncrypted) + { + byte Decrypted[512]; + while (a_Size > 0) + { + int NumBytes = (a_Size > sizeof(Decrypted)) ? sizeof(Decrypted) : a_Size; + m_Decryptor.ProcessData(Decrypted, (byte *)a_Data, NumBytes); + super::DataReceived((const char *)Decrypted, NumBytes); + a_Size -= NumBytes; + a_Data += NumBytes; + } + } + else + { + super::DataReceived(a_Data, a_Size); + } +} + + + + + +int cProtocol132::ParsePacket(unsigned char a_PacketType) +{ + switch (a_PacketType) + { + default: return super::ParsePacket(a_PacketType); // off-load previously known packets into cProtocol125 + case 0xcd: return ParseClientStatuses(); + case 0xfc: return ParseEncryptionKeyResponse(); + } } @@ -55,11 +100,158 @@ int cProtocol132::ParseHandshake(void) HANDLE_PACKET_READ(ReadByte, Byte, ProtocolVersion); HANDLE_PACKET_READ(ReadBEUTF16String16, AString, Username); HANDLE_PACKET_READ(ReadBEUTF16String16, AString, ServerHost); - HANDLE_PACKET_READ(ReadBEInt, int, ServerPort); + HANDLE_PACKET_READ(ReadBEInt, int, ServerPort); m_Username = Username; + + AString key; + CryptoPP::StringSink sink(key); // GCC won't allow inline instantiation in the following line, damned temporary refs + cRoot::Get()->GetServer()->GetPublicKey().Save(sink); + + // Send a 0xFD Encryption Key Request http://wiki.vg/Protocol#Encryption_Key_Request_.280xFD.29 + WriteByte((char)0xfd); + WriteString("MCServer"); + WriteShort((short)key.size()); + SendData(key.data(), key.size()); + WriteShort(4); + WriteInt((int)this); // Using 'this' as the cryptographic nonce, so that we don't have to generate one each time :) + return PARSE_OK; +} + + + + + +int cProtocol132::ParseLogin(void) +{ + // Login packet not used in 1.3.2 + return PARSE_ERROR; +} + + + + + +int cProtocol132::ParseClientStatuses(void) +{ + HANDLE_PACKET_READ(ReadByte, byte, Status); + + // DEBUG: + // Kick the client, we don't have all the packets yet and sending wrong packets makes the client freeze + // m_Client->Kick("I don't speak your language (yet)"); + + // TODO: + // m_Client->HandleLogin(39, m_Username); + return PARSE_OK; } + +int cProtocol132::ParseEncryptionKeyResponse(void) +{ + HANDLE_PACKET_READ(ReadBEShort, short, EncKeyLength); + AString EncKey; + if (!m_ReceivedData.ReadString(EncKey, EncKeyLength)) + { + return PARSE_INCOMPLETE; + } + HANDLE_PACKET_READ(ReadBEShort, short, EncNonceLength); + AString EncNonce; + if (!m_ReceivedData.ReadString(EncNonce, EncNonceLength)) + { + return PARSE_INCOMPLETE; + } + if ((EncKeyLength > MAX_ENC_LEN) || (EncNonceLength > MAX_ENC_LEN)) + { + LOGD("Too long encryption"); + m_Client->Kick("Hacked client"); + return PARSE_OK; + } + + HandleEncryptionKeyResponse(EncKey, EncNonce); + return PARSE_OK; +} + + + + + +void cProtocol132::SendData(const char * a_Data, int a_Size) +{ + if (m_IsEncrypted) + { + byte Encrypted[1024]; // Larger buffer, we may be sending lots of data (chunks) + while (a_Size > 0) + { + int NumBytes = (a_Size > sizeof(Encrypted)) ? sizeof(Encrypted) : a_Size; + m_Encryptor.ProcessData(Encrypted, (byte *)a_Data, NumBytes); + super::SendData((const char *)Encrypted, NumBytes); + a_Size -= NumBytes; + a_Data += NumBytes; + } + } + else + { + super::SendData(a_Data, a_Size); + } +} + + + + + +void cProtocol132::HandleEncryptionKeyResponse(const AString & a_EncKey, const AString & a_EncNonce) +{ + // Decrypt EncNonce using privkey + RSAES::Decryptor rsaDecryptor(cRoot::Get()->GetServer()->GetPrivateKey()); + AutoSeededRandomPool rng; + byte DecryptedNonce[MAX_ENC_LEN]; + DecodingResult res = rsaDecryptor.Decrypt(rng, (const byte *)a_EncNonce.data(), a_EncNonce.size(), DecryptedNonce); + if (!res.isValidCoding || (res.messageLength != 4)) + { + LOGD("Bad nonce length"); + m_Client->Kick("Hacked client"); + return; + } + if (ntohl(*((int *)DecryptedNonce)) != (unsigned)this) + { + LOGD("Bad nonce value"); + m_Client->Kick("Hacked client"); + return; + } + + // Decrypt the symmetric encryption key using privkey: + byte DecryptedKey[MAX_ENC_LEN]; + res = rsaDecryptor.Decrypt(rng, (const byte *)a_EncKey.data(), a_EncKey.size(), DecryptedKey); + if (!res.isValidCoding || (res.messageLength != 16)) + { + LOGD("Bad key length"); + m_Client->Kick("Hacked client"); + return; + } + + // Send encryption key response: + WriteByte((char)0xfc); + WriteShort(0); + WriteShort(0); + + StartEncryption(DecryptedKey); + return; +} + + + + + +void cProtocol132::StartEncryption(const byte * a_Key) +{ + m_Encryptor.SetKey(a_Key, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(a_Key, 16))(Name::FeedbackSize(), 1)); + m_Decryptor.SetKey(a_Key, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(a_Key, 16))(Name::FeedbackSize(), 1)); + m_IsEncrypted = true; +} + + + + diff --git a/source/Protocol132.h b/source/Protocol132.h index af3e78ecc..a0e16b585 100644 --- a/source/Protocol132.h +++ b/source/Protocol132.h @@ -10,6 +10,8 @@ #pragma once #include "Protocol125.h" +#include "CryptoPP/modes.h" +#include "CryptoPP/aes.h" @@ -26,8 +28,29 @@ public: /// Called when client sends some data: virtual void DataReceived(const char * a_Data, int a_Size) override; + /// Handling of the additional packets: + virtual int ParsePacket(unsigned char a_PacketType) override; + // Modified packets: virtual int ParseHandshake(void) override; + virtual int ParseLogin (void) override; + + // New packets: + virtual int ParseClientStatuses (void); + virtual int ParseEncryptionKeyResponse(void); + + virtual void SendData(const char * a_Data, int a_Size); + +protected: + bool m_IsEncrypted; + CryptoPP::CFB_Mode::Decryption m_Decryptor; // ((byte*)sDecryptedSharedSecret.c_str(),(unsigned int)16, IV, 1); + CryptoPP::CFB_Mode::Encryption m_Encryptor; + + /// Decrypts the key and nonce, checks nonce, starts the symmetric encryption + void HandleEncryptionKeyResponse(const AString & a_EncKey, const AString & a_EncNonce); + + /// Starts the symmetric encryption with the specified key + void StartEncryption(const byte * a_Key); } ; diff --git a/source/ProtocolRecognizer.cpp b/source/ProtocolRecognizer.cpp index af917b7de..d0a3f4637 100644 --- a/source/ProtocolRecognizer.cpp +++ b/source/ProtocolRecognizer.cpp @@ -43,7 +43,6 @@ void cProtocolRecognizer::DataReceived(const char * a_Data, int a_Size) { return; } - LOGD("ProtocolRecognizer at %p recognized protocol %p", this, m_Protocol); // The protocol has just been recognized, dump the whole m_Buffer contents into it for parsing: AString Dump; @@ -491,7 +490,7 @@ bool cProtocolRecognizer::TryRecognizeProtocol(void) { return false; } - if (ch == 0x39) + if (ch == 39) { m_Protocol = new cProtocol132(m_Client); return true; diff --git a/source/cServer.cpp b/source/cServer.cpp index 330a69873..157de2388 100644 --- a/source/cServer.cpp +++ b/source/cServer.cpp @@ -241,6 +241,8 @@ bool cServer::InitServer( int a_Port ) m_NotifyWriteThread.Start(this); + PrepareKeys(); + return true; } @@ -283,6 +285,22 @@ cServer::~cServer() +void cServer::PrepareKeys(void) +{ + // TODO: Save and load key for persistence across sessions + // But generating the key takes only a moment, do we even need that? + + LOG("Generating protocol encryption keypair..."); + CryptoPP::AutoSeededRandomPool rng; + m_PrivateKey.GenerateRandomWithKeySize(rng, 1024); + CryptoPP::RSA::PublicKey pk(m_PrivateKey); + m_PublicKey = pk; +} + + + + + void cServer::BroadcastChat(const AString & a_Message, const cClientHandle * a_Exclude) { cCSLock Lock(m_CSClients); diff --git a/source/cServer.h b/source/cServer.h index a97bcbd7a..7baaa0d6a 100644 --- a/source/cServer.h +++ b/source/cServer.h @@ -12,6 +12,8 @@ #define CSERVER_H_INCLUDED #include "cSocketThreads.h" +#include "CryptoPP/rsa.h" +#include "CryptoPP/osrng.h" @@ -66,6 +68,9 @@ public: //tolua_export void RemoveClient(const cSocket * a_Socket); // Removes the socket from m_SocketThreads + CryptoPP::RSA::PrivateKey & GetPrivateKey(void) { return m_PrivateKey; } + CryptoPP::RSA::PublicKey & GetPublicKey (void) { return m_PublicKey; } + private: friend class cRoot; // so cRoot can create and destroy cServer @@ -114,10 +119,15 @@ private: int m_iServerPort; bool m_bRestarting; + + CryptoPP::RSA::PrivateKey m_PrivateKey; + CryptoPP::RSA::PublicKey m_PublicKey; cServer(); ~cServer(); + /// Loads, or generates, if missing, RSA keys for protocol encryption + void PrepareKeys(void); }; //tolua_export -- cgit v1.2.3