From bc6fc859f4c245d17d3bbc2028d93b42b1232d01 Mon Sep 17 00:00:00 2001 From: madmaxoft Date: Tue, 28 Jan 2014 23:53:07 +0100 Subject: [PATCH] Protocol 1.7: Forced encryption on all connections. This is for testing purposes only, to find bugs in the encryption. Once the encryption is deemed stable, it will be enabled only for servers with enabled Authentication. --- src/Protocol/Protocol17x.cpp | 102 +++++++++++++++++++++++++++++++++-- src/Protocol/Protocol17x.h | 2 + src/Server.cpp | 3 +- src/Server.h | 28 ++++++---- 4 files changed, 120 insertions(+), 15 deletions(-) diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index 0d349de03..267c18a99 100644 --- a/src/Protocol/Protocol17x.cpp +++ b/src/Protocol/Protocol17x.cpp @@ -53,6 +53,12 @@ Implements the 1.7.x protocol classes: +const int MAX_ENC_LEN = 512; // Maximum size of the encrypted message; should be 128, but who knows... + + + + + // fwd: main.cpp: extern bool g_ShouldLogCommIn, g_ShouldLogCommOut; @@ -1351,7 +1357,64 @@ void cProtocol172::HandlePacketStatusRequest(cByteBuffer & a_ByteBuffer) void cProtocol172::HandlePacketLoginEncryptionResponse(cByteBuffer & a_ByteBuffer) { - // TODO: Add protocol encryption + short EncKeyLength, EncNonceLength; + a_ByteBuffer.ReadBEShort(EncKeyLength); + AString EncKey; + if (!a_ByteBuffer.ReadString(EncKey, EncKeyLength)) + { + return; + } + a_ByteBuffer.ReadBEShort(EncNonceLength); + AString EncNonce; + if (!a_ByteBuffer.ReadString(EncNonce, EncNonceLength)) + { + return; + } + if ((EncKeyLength > MAX_ENC_LEN) || (EncNonceLength > MAX_ENC_LEN)) + { + LOGD("Too long encryption"); + m_Client->Kick("Hacked client"); + return; + } + + // Decrypt EncNonce using privkey + cRSAPrivateKey & rsaDecryptor = cRoot::Get()->GetServer()->GetPrivateKey(); + Int32 DecryptedNonce[MAX_ENC_LEN / sizeof(Int32)]; + int res = rsaDecryptor.Decrypt((const Byte *)EncNonce.data(), EncNonce.size(), (Byte *)DecryptedNonce, sizeof(DecryptedNonce)); + if (res != 4) + { + LOGD("Bad nonce length: got %d, exp %d", res, 4); + m_Client->Kick("Hacked client"); + return; + } + if (ntohl(DecryptedNonce[0]) != (unsigned)(uintptr_t)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((const Byte *)EncKey.data(), EncKey.size(), DecryptedKey, sizeof(DecryptedKey)); + if (res != 16) + { + LOGD("Bad key length"); + m_Client->Kick("Hacked client"); + return; + } + + StartEncryption(DecryptedKey); + + // Send login success: + { + cPacketizer Pkt(*this, 0x02); // Login success packet + Pkt.WriteString(Printf("%d", m_Client->GetUniqueID())); // TODO: proper UUID + Pkt.WriteString(m_Client->GetUsername()); + } + + m_State = 3; // State = Game + m_Client->HandleLogin(4, m_Client->GetUsername()); } @@ -1363,14 +1426,26 @@ void cProtocol172::HandlePacketLoginStart(cByteBuffer & a_ByteBuffer) AString Username; a_ByteBuffer.ReadVarUTF8String(Username); - // TODO: Protocol encryption should be set up here if not localhost / auth - if (!m_Client->HandleHandshake(Username)) { // The client is not welcome here, they have been sent a Kick packet already return; } + // If auth is required, then send the encryption request: + // if (cRoot::Get()->GetServer()->ShouldAuthenticate()) + { + cPacketizer Pkt(*this, 0x01); + Pkt.WriteString(cRoot::Get()->GetServer()->GetServerID()); + const AString & PubKeyDer = cRoot::Get()->GetServer()->GetPublicKeyDER(); + Pkt.WriteShort(PubKeyDer.size()); + Pkt.WriteBuf(PubKeyDer.data(), PubKeyDer.size()); + Pkt.WriteShort(4); + Pkt.WriteInt((int)(intptr_t)this); // Using 'this' as the cryptographic nonce, so that we don't have to generate one each time :) + m_Client->SetUsername(Username); + return; + } + // Send login success: { cPacketizer Pkt(*this, 0x02); // Login success packet @@ -1882,6 +1957,27 @@ void cProtocol172::ParseItemMetadata(cItem & a_Item, const AString & a_Metadata) +void cProtocol172::StartEncryption(const Byte * a_Key) +{ + m_Encryptor.Init(a_Key, a_Key); + m_Decryptor.Init(a_Key, a_Key); + m_IsEncrypted = true; + + // Prepare the m_AuthServerID: + cSHA1Checksum Checksum; + const AString & ServerID = cRoot::Get()->GetServer()->GetServerID(); + Checksum.Update((const Byte *)ServerID.c_str(), ServerID.length()); + Checksum.Update(a_Key, 16); + Checksum.Update((const Byte *)cRoot::Get()->GetServer()->GetPublicKeyDER().data(), cRoot::Get()->GetServer()->GetPublicKeyDER().size()); + Byte Digest[20]; + Checksum.Finalize(Digest); + cSHA1Checksum::DigestToJava(Digest, m_AuthServerID); +} + + + + + /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // cProtocol172::cPacketizer: diff --git a/src/Protocol/Protocol17x.h b/src/Protocol/Protocol17x.h index 72544b575..6a75e41c8 100644 --- a/src/Protocol/Protocol17x.h +++ b/src/Protocol/Protocol17x.h @@ -281,6 +281,8 @@ protected: /// Parses item metadata as read by ReadItem(), into the item enchantments. void ParseItemMetadata(cItem & a_Item, const AString & a_Metadata); + + void StartEncryption(const Byte * a_Key); } ; diff --git a/src/Server.cpp b/src/Server.cpp index 3f9f8a4ac..ba2b46d55 100644 --- a/src/Server.cpp +++ b/src/Server.cpp @@ -237,7 +237,8 @@ bool cServer::InitServer(cIniFile & a_SettingsIni) m_bIsConnected = true; m_ServerID = "-"; - if (a_SettingsIni.GetValueSetB("Authentication", "Authenticate", true)) + m_ShouldAuthenticate = a_SettingsIni.GetValueSetB("Authentication", "Authenticate", true); + if (m_ShouldAuthenticate) { MTRand mtrand1; unsigned int r1 = (mtrand1.randInt() % 1147483647) + 1000000000; diff --git a/src/Server.h b/src/Server.h index 15eafc00a..b5280c59d 100644 --- a/src/Server.h +++ b/src/Server.h @@ -71,13 +71,13 @@ public: // tolua_export bool Command(cClientHandle & a_Client, AString & a_Cmd); - /// Executes the console command, sends output through the specified callback + /** Executes the console command, sends output through the specified callback */ void ExecuteConsoleCommand(const AString & a_Cmd, cCommandOutputCallback & a_Output); - /// Lists all available console commands and their helpstrings + /** Lists all available console commands and their helpstrings */ void PrintHelp(const AStringVector & a_Split, cCommandOutputCallback & a_Output); - /// Binds the built-in console commands with the plugin manager + /** Binds the built-in console commands with the plugin manager */ static void BindBuiltInConsoleCommands(void); void Shutdown(void); @@ -97,13 +97,13 @@ public: // tolua_export void RemoveClient(const cClientHandle * a_Client); // Removes the clienthandle from m_SocketThreads - /// Don't tick a_Client anymore, it will be ticked from its cPlayer instead + /** Don't tick a_Client anymore, it will be ticked from its cPlayer instead */ void ClientMovedToWorld(const cClientHandle * a_Client); - /// Notifies the server that a player was created; the server uses this to adjust the number of players + /** Notifies the server that a player was created; the server uses this to adjust the number of players */ void PlayerCreated(const cPlayer * a_Player); - /// Notifies the server that a player is being destroyed; the server uses this to adjust the number of players + /** Notifies the server that a player is being destroyed; the server uses this to adjust the number of players */ void PlayerDestroying(const cPlayer * a_Player); /** Returns base64 encoded favicon data (obtained from favicon.png) */ @@ -112,11 +112,13 @@ public: // tolua_export cRSAPrivateKey & GetPrivateKey(void) { return m_PrivateKey; } const AString & GetPublicKeyDER(void) const { return m_PublicKeyDER; } + bool ShouldAuthenticate(void) const { return m_ShouldAuthenticate; } + private: friend class cRoot; // so cRoot can create and destroy cServer - /// When NotifyClientWrite() is called, it is queued for this thread to process (to avoid deadlocks between cSocketThreads, cClientHandle and cChunkMap) + /** When NotifyClientWrite() is called, it is queued for this thread to process (to avoid deadlocks between cSocketThreads, cClientHandle and cChunkMap) */ class cNotifyWriteThread : public cIsThread { @@ -140,7 +142,7 @@ private: void NotifyClientWrite(const cClientHandle * a_Client); } ; - /// The server tick thread takes care of the players who aren't yet spawned in a world + /** The server tick thread takes care of the players who aren't yet spawned in a world */ class cTickThread : public cIsThread { @@ -195,18 +197,22 @@ private: cTickThread m_TickThread; cEvent m_RestartEvent; - /// The server ID used for client authentication + /** The server ID used for client authentication */ AString m_ServerID; + /** If true, players will be online-authenticated agains Mojang servers. + This setting is the same as the "online-mode" setting in Vanilla. */ + bool m_ShouldAuthenticate; + cServer(void); - /// Loads, or generates, if missing, RSA keys for protocol encryption + /** Loads, or generates, if missing, RSA keys for protocol encryption */ void PrepareKeys(void); bool Tick(float a_Dt); - /// Ticks the clients in m_Clients, manages the list in respect to removing clients + /** Ticks the clients in m_Clients, manages the list in respect to removing clients */ void TickClients(float a_Dt); // cListenThread::cCallback overrides: