summaryrefslogtreecommitdiffstats
path: root/ProtoProxy/Connection.cpp
diff options
context:
space:
mode:
authormadmaxoft@gmail.com <madmaxoft@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2012-09-02 23:38:13 +0200
committermadmaxoft@gmail.com <madmaxoft@gmail.com@0a769ca7-a7f5-676a-18bf-c427514a06d6>2012-09-02 23:38:13 +0200
commit532ed91a72b797e11c56f6a7032e8e8f6d582617 (patch)
tree366b6c6dcd2eb28acad5cbcbb4448f7e4581a5b4 /ProtoProxy/Connection.cpp
parentAdded writing support to cByteBuffer (will be used by ProtoProxy) (diff)
downloadcuberite-532ed91a72b797e11c56f6a7032e8e8f6d582617.tar
cuberite-532ed91a72b797e11c56f6a7032e8e8f6d582617.tar.gz
cuberite-532ed91a72b797e11c56f6a7032e8e8f6d582617.tar.bz2
cuberite-532ed91a72b797e11c56f6a7032e8e8f6d582617.tar.lz
cuberite-532ed91a72b797e11c56f6a7032e8e8f6d582617.tar.xz
cuberite-532ed91a72b797e11c56f6a7032e8e8f6d582617.tar.zst
cuberite-532ed91a72b797e11c56f6a7032e8e8f6d582617.zip
Diffstat (limited to '')
-rw-r--r--ProtoProxy/Connection.cpp515
1 files changed, 499 insertions, 16 deletions
diff --git a/ProtoProxy/Connection.cpp b/ProtoProxy/Connection.cpp
index fe37d5003..5cc603641 100644
--- a/ProtoProxy/Connection.cpp
+++ b/ProtoProxy/Connection.cpp
@@ -11,12 +11,66 @@
+#define HANDLE_CLIENT_PACKET_READ(Proc, Type, Var) \
+ Type Var; \
+ { \
+ if (!m_ClientBuffer.Proc(Var)) \
+ { \
+ return; \
+ } \
+ }
+
+#define HANDLE_SERVER_PACKET_READ(Proc, Type, Var) \
+ Type Var; \
+ { \
+ if (!m_ServerBuffer.Proc(Var)) \
+ { \
+ return; \
+ } \
+ }
+
+#define CLIENTSEND(...) SendData(m_ClientSocket, __VA_ARGS__, "Client")
+#define SERVERSEND(...) SendData(m_ServerSocket, __VA_ARGS__, "Server")
+#define CLIENTENCRYPTSEND(...) SendEncryptedData(m_ClientSocket, m_ClientEncryptor, __VA_ARGS__, "Client")
+#define SERVERENCRYPTSEND(...) SendEncryptedData(m_ServerSocket, m_ServerEncryptor, __VA_ARGS__, "Server")
+
+#define MAX_ENC_LEN 1024
+
+
+
+typedef unsigned char Byte;
+
+
+
+
+
+enum
+{
+ PACKET_HANDSHAKE = 0x02,
+ PACKET_ENCRYPTION_KEY_RESPONSE = 0xfc,
+ PACKET_ENCRYPTION_KEY_REQUEST = 0xfd,
+ PACKET_PING = 0xfe,
+ PACKET_KICK = 0xff,
+} ;
+
+
+
+
+
+///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
+// cConnection:
+
cConnection::cConnection(SOCKET a_ClientSocket, cServer & a_Server) :
m_Server(a_Server),
m_LogFile(NULL),
m_ClientSocket(a_ClientSocket),
m_ServerSocket(-1),
- m_BeginTick(clock())
+ m_BeginTick(clock()),
+ m_ClientState(csUnencrypted),
+ m_ServerState(csUnencrypted),
+ m_ClientBuffer(64 KiB),
+ m_ServerBuffer(64 KiB),
+ m_Nonce(0)
{
AString fnam;
Printf(fnam, "Log_%d.log", (int)time(NULL));
@@ -55,23 +109,26 @@ void cConnection::Run(void)
if (res <= 0)
{
printf("select() failed: %d; aborting client", WSAGetLastError());
- return;
+ break;
}
if (FD_ISSET(m_ServerSocket, &ReadFDs))
{
if (!RelayFromServer())
{
- return;
+ break;
}
}
if (FD_ISSET(m_ClientSocket, &ReadFDs))
{
if (!RelayFromClient())
{
- return;
+ break;
}
}
}
+ Log("Relaying ended, closing sockets");
+ closesocket(m_ServerSocket);
+ closesocket(m_ClientSocket);
}
@@ -88,8 +145,12 @@ void cConnection::Log(const char * a_Format, ...)
AString FullMsg;
Printf(FullMsg, "[%5.3f] %s\n", GetRelativeTime(), msg.c_str());
+ // Log to file:
cCSLock Lock(m_CSLog);
fputs(FullMsg.c_str(), m_LogFile);
+
+ // Log to screen:
+ puts(FullMsg.c_str());
}
@@ -107,8 +168,12 @@ void cConnection::DataLog(const void * a_Data, int a_Size, const char * a_Format
AString Hex;
Printf(FullMsg, "[%5.3f] %s\n%s", GetRelativeTime(), msg.c_str(), CreateHexDump(Hex, a_Data, a_Size, 16).c_str());
+ // Log to file:
cCSLock Lock(m_CSLog);
fputs(FullMsg.c_str(), m_LogFile);
+
+ // Log to screen:
+ puts(FullMsg.c_str());
}
@@ -131,6 +196,7 @@ bool cConnection::ConnectToServer(void)
printf("connection to server failed: %d\n", WSAGetLastError());
return false;
}
+ Log("Connected to SERVER");
return true;
}
@@ -148,14 +214,27 @@ bool cConnection::RelayFromServer(void)
return false;
}
- DataLog(Buffer, res, "Received %d bytes from the server", res);
- // TODO: Process the data
+ DataLog(Buffer, res, "Received %d bytes from the SERVER", res);
- res = send(m_ClientSocket, Buffer, res, 0);
- if (res <= 0)
+ switch (m_ServerState)
{
- Log("Client closed the socket: %d, %d; aborting connection", res, WSAGetLastError());
- return false;
+ case csUnencrypted:
+ {
+ return DecodeServersPackets(Buffer, res);
+ }
+ case csEncryptedUnderstood:
+ {
+ m_ServerDecryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res);
+ DataLog(Buffer, res, "Decrypted %d bytes from the SERVER", res);
+ return DecodeServersPackets(Buffer, res);
+ }
+ case csEncryptedUnknown:
+ {
+ m_ServerDecryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res);
+ DataLog(Buffer, res, "Decrypted %d bytes from the SERVER", res);
+ m_ClientEncryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res);
+ return CLIENTSEND(Buffer, res);
+ }
}
return true;
@@ -175,14 +254,27 @@ bool cConnection::RelayFromClient(void)
return false;
}
- DataLog(Buffer, res, "Received %d bytes from the client", res);
- // TODO: Process the data
+ DataLog(Buffer, res, "Received %d bytes from the CLIENT", res);
- res = send(m_ServerSocket, Buffer, res, 0);
- if (res <= 0)
+ switch (m_ClientState)
{
- Log("Server closed the socket: %d, %d; aborting connection", res, WSAGetLastError());
- return false;
+ case csUnencrypted:
+ {
+ return DecodeClientsPackets(Buffer, res);
+ }
+ case csEncryptedUnderstood:
+ {
+ m_ClientDecryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res);
+ DataLog(Buffer, res, "Decrypted %d bytes from the CLIENT", res);
+ return DecodeClientsPackets(Buffer, res);
+ }
+ case csEncryptedUnknown:
+ {
+ m_ClientDecryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res);
+ DataLog(Buffer, res, "Decrypted %d bytes from the CLIENT", res);
+ m_ServerEncryptor.ProcessData((byte *)Buffer, (byte *)Buffer, res);
+ return SERVERSEND(Buffer, res);
+ }
}
return true;
@@ -201,3 +293,394 @@ double cConnection::GetRelativeTime(void)
+
+bool cConnection::SendData(SOCKET a_Socket, const char * a_Data, int a_Size, const char * a_Peer)
+{
+ DataLog(a_Data, a_Size, "Sending data to %s", a_Peer);
+
+ int res = send(a_Socket, a_Data, a_Size, 0);
+ if (res <= 0)
+ {
+ Log("%s closed the socket: %d, %d; aborting connection", a_Peer, res, WSAGetLastError());
+ return false;
+ }
+ return true;
+}
+
+
+
+
+
+bool cConnection::SendData(SOCKET a_Socket, cByteBuffer & a_Data, const char * a_Peer)
+{
+ AString All;
+ a_Data.ReadAll(All);
+ a_Data.CommitRead();
+ return SendData(a_Socket, All.data(), All.size(), a_Peer);
+}
+
+
+
+
+
+bool cConnection::SendEncryptedData(SOCKET a_Socket, Encryptor & a_Encryptor, const char * a_Data, int a_Size, const char * a_Peer)
+{
+ DataLog(a_Data, a_Size, "Sending encrypted %d bytes to %s", a_Size, a_Peer);
+ const byte * Data = (const byte *)a_Data;
+ while (a_Size > 0)
+ {
+ byte Buffer[512];
+ int NumBytes = (a_Size > sizeof(Buffer)) ? sizeof(Buffer) : a_Size;
+ a_Encryptor.ProcessData(Buffer, Data, NumBytes);
+ bool res = SendData(a_Socket, (const char *)Buffer, NumBytes, a_Peer);
+ if (!res)
+ {
+ return false;
+ }
+ Data += NumBytes;
+ a_Size -= NumBytes;
+ }
+ return true;
+}
+
+
+
+
+
+bool cConnection::SendEncryptedData(SOCKET a_Socket, Encryptor & a_Encryptor, cByteBuffer & a_Data, const char * a_Peer)
+{
+ AString All;
+ a_Data.ReadAll(All);
+ a_Data.CommitRead();
+ return SendEncryptedData(a_Socket, a_Encryptor, All.data(), All.size(), a_Peer);
+}
+
+
+
+
+
+bool cConnection::DecodeClientsPackets(const char * a_Data, int a_Size)
+{
+ if (!m_ClientBuffer.Write(a_Data, a_Size))
+ {
+ Log("Too much queued data for the server, aborting connection");
+ return false;
+ }
+
+ while (m_ClientBuffer.CanReadBytes(1))
+ {
+ unsigned char PacketType;
+ m_ClientBuffer.ReadByte(PacketType);
+ switch (PacketType)
+ {
+ case PACKET_ENCRYPTION_KEY_RESPONSE: HandleClientEncryptionKeyResponse(); break;
+ case PACKET_HANDSHAKE: HandleClientHandshake(); break;
+ case PACKET_PING: HandleClientPing(); break;
+ default:
+ {
+ if (m_ClientState == csEncryptedUnderstood)
+ {
+ Log("Unknown packet 0x%02x from the client while encrypted; continuing to relay blind only", PacketType);
+ m_ClientState = csEncryptedUnknown;
+ m_ClientBuffer.ResetRead();
+ if (m_ServerState == csUnencrypted)
+ {
+ SERVERSEND(m_ClientBuffer);
+ }
+ else
+ {
+ SERVERENCRYPTSEND(m_ClientBuffer);
+ }
+ return true;
+ }
+ else
+ {
+ Log("Unknown packet 0x%02x from the client while unencrypted; aborting connection", PacketType);
+ return false;
+ }
+ }
+ } // switch (PacketType)
+ m_ClientBuffer.CommitRead();
+ } // while (CanReadBytes(1))
+ return true;
+}
+
+
+
+
+
+bool cConnection::DecodeServersPackets(const char * a_Data, int a_Size)
+{
+ if (!m_ServerBuffer.Write(a_Data, a_Size))
+ {
+ Log("Too much queued data for the client, aborting connection");
+ return false;
+ }
+
+ if (
+ (m_ServerState == csEncryptedUnderstood) &&
+ (m_ClientState == csUnencrypted)
+ )
+ {
+ // Client hasn't finished encryption handshake yet, don't send them any data yet
+ }
+
+ while (m_ServerBuffer.CanReadBytes(1))
+ {
+ unsigned char PacketType;
+ m_ServerBuffer.ReadByte(PacketType);
+ switch (PacketType)
+ {
+ case PACKET_ENCRYPTION_KEY_REQUEST: HandleServerEncryptionKeyRequest(); break;
+ case PACKET_ENCRYPTION_KEY_RESPONSE: HandleServerEncryptionKeyResponse(); break;
+ case PACKET_KICK: HandleServerKick(); break;
+ default:
+ {
+ if (m_ServerState == csEncryptedUnderstood)
+ {
+ Log("Unknown packet 0x%02x from the server while encrypted; continuing to relay blind only", PacketType);
+ m_ServerState = csEncryptedUnknown;
+ m_ServerBuffer.ResetRead();
+ if (m_ClientState == csUnencrypted)
+ {
+ CLIENTSEND(m_ServerBuffer);
+ }
+ else
+ {
+ CLIENTENCRYPTSEND(m_ServerBuffer);
+ }
+ return true;
+ }
+ else
+ {
+ Log("Unknown packet 0x%02x from the server while unencrypted; aborting connection", PacketType);
+ return false;
+ }
+ }
+ } // switch (PacketType)
+ m_ServerBuffer.CommitRead();
+ } // while (CanReadBytes(1))
+ return true;
+}
+
+
+
+
+
+void cConnection::HandleClientEncryptionKeyResponse(void)
+{
+ HANDLE_CLIENT_PACKET_READ(ReadBEShort, short, EncKeyLength);
+ AString EncKey;
+ if (!m_ClientBuffer.ReadString(EncKey, EncKeyLength))
+ {
+ return;
+ }
+ HANDLE_CLIENT_PACKET_READ(ReadBEShort, short, EncNonceLength);
+ AString EncNonce;
+ if (!m_ClientBuffer.ReadString(EncNonce, EncNonceLength))
+ {
+ return;
+ }
+ if ((EncKeyLength > MAX_ENC_LEN) || (EncNonceLength > MAX_ENC_LEN))
+ {
+ Log("Client: Too long encryption params");
+ return;
+ }
+ StartClientEncryption(EncKey, EncNonce);
+}
+
+
+
+
+
+void cConnection::HandleClientHandshake(void)
+{
+ // Read the packet from the client:
+ HANDLE_CLIENT_PACKET_READ(ReadByte, Byte, ProtocolVersion);
+ HANDLE_CLIENT_PACKET_READ(ReadBEUTF16String16, AString, Username);
+ HANDLE_CLIENT_PACKET_READ(ReadBEUTF16String16, AString, ServerHost);
+ HANDLE_CLIENT_PACKET_READ(ReadBEInt, int, ServerPort);
+ m_ClientBuffer.CommitRead();
+
+ // Send the same packet to the server:
+ cByteBuffer ToServer(512);
+ ToServer.WriteByte (PACKET_HANDSHAKE);
+ ToServer.WriteByte (ProtocolVersion);
+ ToServer.WriteBEUTF16String16(Username);
+ ToServer.WriteBEUTF16String16(ServerHost);
+ ToServer.WriteBEInt (m_Server.GetConnectPort());
+ SERVERSEND(ToServer);
+}
+
+
+
+
+
+void cConnection::HandleClientPing(void)
+{
+ Log("Received a PACKET_PING from the CLIENT");
+ m_ClientBuffer.ResetRead();
+ SERVERSEND(m_ClientBuffer);
+}
+
+
+
+
+
+void cConnection::HandleServerEncryptionKeyRequest(void)
+{
+ // Read the packet from the server:
+ HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, ServerID);
+ HANDLE_SERVER_PACKET_READ(ReadBEShort, short, PublicKeyLength);
+ AString PublicKey;
+ if (!m_ServerBuffer.ReadString(PublicKey, PublicKeyLength))
+ {
+ return;
+ }
+ HANDLE_SERVER_PACKET_READ(ReadBEShort, short, NonceLength);
+ AString Nonce;
+ if (!m_ServerBuffer.ReadString(Nonce, NonceLength))
+ {
+ return;
+ }
+ Log("Got PACKET_ENCRYPTION_KEY_REQUEST from the SERVER:");
+ Log(" ServerID = %s", ServerID.c_str());
+
+ // Reply to the server:
+ SendEncryptionKeyResponse(PublicKey, Nonce);
+
+ // Send a 0xFD Encryption Key Request http://wiki.vg/Protocol#0xFD to the client, using our own key:
+ Log("Sending PACKET_ENCRYPTION_KEY_REQUEST to the CLIENT");
+ AString key;
+ StringSink sink(key); // GCC won't allow inline instantiation in the following line, damned temporary refs
+ m_Server.GetPublicKey().Save(sink);
+ cByteBuffer ToClient(512);
+ ToClient.WriteByte (PACKET_ENCRYPTION_KEY_REQUEST);
+ ToClient.WriteBEUTF16String16(ServerID);
+ ToClient.WriteBEShort ((short)key.size());
+ ToClient.WriteBuf (key.data(), key.size());
+ ToClient.WriteBEShort (4);
+ ToClient.WriteBEInt (m_Nonce); // Using 'this' as the cryptographic nonce, so that we don't have to generate one each time :)
+ CLIENTSEND(ToClient);
+}
+
+
+
+
+
+void cConnection::HandleServerEncryptionKeyResponse(void)
+{
+ HANDLE_SERVER_PACKET_READ(ReadBEInt, int, Lengths);
+ if (Lengths != 0)
+ {
+ Log("Lengths are not zero!");
+ return;
+ }
+ m_ServerState = csEncryptedUnderstood;
+}
+
+
+
+
+
+void cConnection::HandleServerKick(void)
+{
+ HANDLE_SERVER_PACKET_READ(ReadBEUTF16String16, AString, Reason);
+ Log("Received PACKET_KICK from the SERVER:");
+ Log(" Reason = \"%s\"", Reason.c_str());
+
+ cByteBuffer ToClient(Reason.size() * 2 + 4);
+ ToClient.WriteByte(PACKET_KICK);
+ ToClient.WriteBEUTF16String16(Reason);
+ CLIENTSEND(ToClient);
+}
+
+
+
+
+
+void cConnection::SendEncryptionKeyResponse(const AString & a_ServerPublicKey, const AString & a_Nonce)
+{
+ // Generate the shared secret and encrypt using the server's public key
+ byte SharedSecret[16];
+ byte EncryptedSecret[128];
+ memset(SharedSecret, 0, sizeof(SharedSecret)); // Use all zeroes for the initial secret
+ RSA::PublicKey pk;
+ CryptoPP::StringSource src(a_ServerPublicKey, true);
+ ByteQueue bq;
+ src.TransferTo(bq);
+ bq.MessageEnd();
+ pk.Load(bq);
+ RSAES<PKCS1v15>::Encryptor rsaEncryptor(pk);
+ AutoSeededRandomPool rng;
+ int EncryptedLength = rsaEncryptor.FixedCiphertextLength();
+ ASSERT(EncryptedLength <= sizeof(EncryptedSecret));
+ rsaEncryptor.Encrypt(rng, SharedSecret, sizeof(SharedSecret), EncryptedSecret);
+ m_ServerEncryptor.SetKey(SharedSecret, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(SharedSecret, 16))(Name::FeedbackSize(), 1));
+ m_ServerDecryptor.SetKey(SharedSecret, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(SharedSecret, 16))(Name::FeedbackSize(), 1));
+
+ // Encrypt the nonce:
+ byte EncryptedNonce[128];
+ rsaEncryptor.Encrypt(rng, (const byte *)(a_Nonce.data()), a_Nonce.size(), EncryptedNonce);
+
+ // Send the packet to the server:
+ Log("Sending PACKET_ENCRYPTION_KEY_RESPONSE to the SERVER");
+ cByteBuffer ToServer(1024);
+ ToServer.WriteByte(PACKET_ENCRYPTION_KEY_RESPONSE);
+ ToServer.WriteBEShort(EncryptedLength);
+ ToServer.WriteBuf(EncryptedSecret, EncryptedLength);
+ ToServer.WriteBEShort(EncryptedLength);
+ ToServer.WriteBuf(EncryptedNonce, EncryptedLength);
+ SERVERSEND(ToServer);
+}
+
+
+
+
+
+void cConnection::StartClientEncryption(const AString & a_EncKey, const AString & a_EncNonce)
+{
+ // Decrypt EncNonce using privkey
+ RSAES<PKCS1v15>::Decryptor rsaDecryptor(m_Server.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))
+ {
+ Log("Client: Bad nonce length");
+ return;
+ }
+ if (ntohl(*((int *)DecryptedNonce)) != m_Nonce)
+ {
+ Log("Bad nonce value");
+ return;
+ }
+
+ // Decrypt the symmetric encryption key using privkey:
+ byte SharedSecret[MAX_ENC_LEN];
+ res = rsaDecryptor.Decrypt(rng, (const byte *)a_EncKey.data(), a_EncKey.size(), SharedSecret);
+ if (!res.isValidCoding || (res.messageLength != 16))
+ {
+ Log("Bad key length");
+ return;
+ }
+
+ // Send encryption key response:
+ cByteBuffer ToClient(6);
+ ToClient.WriteByte((char)0xfc);
+ ToClient.WriteBEShort(0);
+ ToClient.WriteBEShort(0);
+ CLIENTSEND(ToClient);
+
+ // Start the encryption:
+ m_ClientEncryptor.SetKey(SharedSecret, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(SharedSecret, 16))(Name::FeedbackSize(), 1));
+ m_ClientDecryptor.SetKey(SharedSecret, 16, MakeParameters(Name::IV(), ConstByteArrayParameter(SharedSecret, 16))(Name::FeedbackSize(), 1));
+ m_ClientState = csEncryptedUnderstood;
+
+ // Handle all postponed server data
+ DecodeServersPackets(NULL, 0);
+}
+
+
+
+