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.
master
madmaxoft 2014-01-28 23:53:07 +01:00
parent 9de52252ac
commit bc6fc859f4
4 changed files with 120 additions and 15 deletions

View File

@ -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:

View File

@ -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);
} ;

View File

@ -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;

View File

@ -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: