diff --git a/src/Protocol/Protocol17x.cpp b/src/Protocol/Protocol17x.cpp index 0d349de0..267c18a9 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 72544b57..6a75e41c 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 3f9f8a4a..ba2b46d5 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 15eafc00..b5280c59 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: