Skip to content

Commit

Permalink
Get udp packets decrypting and correctly authenticating.
Browse files Browse the repository at this point in the history
I think encryption is likely still wrong though, as packets sent from server to client have a slightly different header.
  • Loading branch information
TLeonardUK committed Aug 8, 2021
1 parent c33c576 commit 9e085bb
Show file tree
Hide file tree
Showing 15 changed files with 361 additions and 33 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@ Bin/
Intermediate/
Research/
vcpkg_installed/
.vs/
.vs/
Temp/
vcpkg/
13 changes: 7 additions & 6 deletions Source/Server/Core/Crypto/CWCCipher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

#include "Core/Utils/Logging.h"
#include "Core/Utils/Random.h"
#include "Core/Utils/Endian.h"
#include "Core/Utils/Strings.h"

CWCCipher::CWCCipher(const std::vector<uint8_t>& InKey)
: Key(InKey)
Expand All @@ -13,12 +15,11 @@ CWCCipher::CWCCipher(const std::vector<uint8_t>& InKey)

bool CWCCipher::Encrypt(const std::vector<uint8_t>& Input, std::vector<uint8_t>& Output)
{
std::vector<uint8_t> IV(12, 0);
std::vector<uint8_t> Tag(17, 0);
std::vector<uint8_t> IV(11, 0);
std::vector<uint8_t> Tag(16, 0);
std::vector<uint8_t> Payload = Input;

FillRandomBytes(IV);
IV[11] = '\0';

if (cwc_encrypt_message(IV.data(), 11, IV.data(), 11, (unsigned char*)Payload.data(), Payload.size(), Tag.data(), 16, &CwcContext) == RETURN_ERROR)
{
Expand All @@ -36,9 +37,9 @@ bool CWCCipher::Encrypt(const std::vector<uint8_t>& Input, std::vector<uint8_t>&

bool CWCCipher::Decrypt(const std::vector<uint8_t>& Input, std::vector<uint8_t>& Output)
{
std::vector<uint8_t> IV(12);
std::vector<uint8_t> Tag(17);

std::vector<uint8_t> IV(11);
std::vector<uint8_t> Tag(16);
// Actually enough data for any data?
if (Input.size() < 11 + 16 + 1)
{
Expand Down
88 changes: 88 additions & 0 deletions Source/Server/Core/Crypto/CWCUDPCipher.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
// Dark Souls 3 - Open Server

#include "Core/Crypto/CWCUDPCipher.h"

#include "Core/Utils/Logging.h"
#include "Core/Utils/Random.h"
#include "Core/Utils/Endian.h"
#include "Core/Utils/Strings.h"

CWCUDPCipher::CWCUDPCipher(const std::vector<uint8_t>& InKey, uint64_t InAuthToken)
: Key(InKey)
, AuthToken(InAuthToken)
{
cwc_init_and_key(InKey.data(), InKey.size(), &CwcContext);

// Auth token bytes to encode in the header are the reversed auth token.
uint8_t* InAuthTokenBytes = reinterpret_cast<uint8_t*>(&InAuthToken);
AuthTokenHeaderBytes.assign(InAuthTokenBytes, InAuthTokenBytes + 8);
}

bool CWCUDPCipher::Encrypt(const std::vector<uint8_t>& Input, std::vector<uint8_t>& Output)
{
std::vector<uint8_t> IV(11, 0);
std::vector<uint8_t> Tag(16, 0);
std::vector<uint8_t> Payload = Input;
std::vector<uint8_t> PacketType = { 0 };

FillRandomBytes(IV);

// TODO: I have the distinct feeling this is different when replying as the packet type
// doesn't get sent when going server->client ...
std::vector<uint8_t> Header;
Header.resize(20);
memcpy(Header.data(), IV.data(), 11);
memcpy(Header.data() + 11, AuthTokenHeaderBytes.data(), 8);
memcpy(Header.data() + 19, PacketType.data(), 1);

if (cwc_encrypt_message(IV.data(), 11, Header.data(), Header.size(), (unsigned char*)Payload.data(), Payload.size(), Tag.data(), 16, &CwcContext) == RETURN_ERROR)
{
return false;
}

Output.resize(Payload.size() + 11 + 16 + 1);

memcpy(Output.data(), IV.data(), 11);
memcpy(Output.data() + 11, Tag.data(), 16);
memcpy(Output.data() + 11 + 16, PacketType.data(), 1);
memcpy(Output.data() + 11 + 16 + 1, Payload.data(), Payload.size());

Log("Encrypt: PayloadSize=%i IV=%s Tag=%s Header=%s", Payload.size(), BytesToHex(IV).c_str(), BytesToHex(Tag).c_str(), BytesToHex(Header).c_str());

return true;
}

bool CWCUDPCipher::Decrypt(const std::vector<uint8_t>& Input, std::vector<uint8_t>& Output)
{
std::vector<uint8_t> IV(11);
std::vector<uint8_t> Tag(16);
std::vector<uint8_t> PacketType(1);

// Actually enough data for any data?
if (Input.size() < 11 + 16 + 1 + 1)
{
return false;
}

Output.resize(Input.size() - 11 - 16 - 1);

memcpy(IV.data(), Input.data(), 11);
memcpy(Tag.data(), Input.data() + 11, 16);
memcpy(PacketType.data(), Input.data() + 11 + 16, 1);
memcpy(Output.data(), Input.data() + 11 + 16 + 1, Output.size());

std::vector<uint8_t> Header;
Header.resize(20);
memcpy(Header.data(), IV.data(), 11);
memcpy(Header.data() + 11, AuthTokenHeaderBytes.data(), 8);
memcpy(Header.data() + 19, PacketType.data(), 1);

Log("Decrypt: PayloadSize=%i IV=%s Tag=%s Header=%s", Output.size(), BytesToHex(IV).c_str(), BytesToHex(Tag).c_str(), BytesToHex(Header).c_str());

if (cwc_decrypt_message(IV.data(), 11, Header.data(), Header.size(), (unsigned char*)Output.data(), Output.size(), Tag.data(), 16, &CwcContext) == RETURN_ERROR)
{
return false;
}

return true;
}
29 changes: 29 additions & 0 deletions Source/Server/Core/Crypto/CWCUDPCipher.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Dark Souls 3 - Open Server

#pragma once

#include "Core/Crypto/Cipher.h"

#include "cwc.h"

#include <vector>

class CWCUDPCipher
: public Cipher
{
public:

CWCUDPCipher(const std::vector<uint8_t>& key, uint64_t AuthToken);

bool Encrypt(const std::vector<uint8_t>& input, std::vector<uint8_t>& Output) override;
bool Decrypt(const std::vector<uint8_t>& input, std::vector<uint8_t>& Output) override;

private:
std::vector<uint8_t> Key;

cwc_ctx CwcContext;

uint64_t AuthToken;
std::vector<uint8_t> AuthTokenHeaderBytes;

};
4 changes: 2 additions & 2 deletions Source/Server/Core/Crypto/RSACipher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ bool RSACipher::Encrypt(const std::vector<uint8_t>& Input, std::vector<uint8_t>&
}

int EncryptedLength = RSA_private_encrypt((int)Input.size(), Input.data(), Output.data(), RsaInstance, OpenSSLPaddingMode);
if (!EncryptedLength)
if (EncryptedLength < 0)
{
std::vector<char> buffer;
buffer.resize(1024);
Expand Down Expand Up @@ -66,7 +66,7 @@ bool RSACipher::Decrypt(const std::vector<uint8_t>& Input, std::vector<uint8_t>&
}

int DecryptedLength = RSA_private_decrypt((int)Input.size(), Input.data(), Output.data(), RsaInstance, OpenSSLPaddingMode);
if (!DecryptedLength)
if (DecryptedLength < 0)
{
std::vector<char> buffer;
buffer.resize(1024);
Expand Down
18 changes: 18 additions & 0 deletions Source/Server/Core/Utils/Strings.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Dark Souls 3 - Open Server

#include "Core/Utils/Strings.h"

#include <sstream>
#include <iomanip>

std::string BytesToHex(const std::vector<uint8_t>& Bytes)
{
std::stringstream ss;
for (uint8_t Value : Bytes)
{
ss << std::uppercase << std::setfill('0') << std::setw(2) << std::hex << (int)Value;
//ss << " ";
}

return ss.str();
}
8 changes: 8 additions & 0 deletions Source/Server/Core/Utils/Strings.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Dark Souls 3 - Open Server

#pragma once

#include <string>
#include <vector>

std::string BytesToHex(const std::vector<uint8_t>& Bytes);
4 changes: 4 additions & 0 deletions Source/Server/Server.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
<ClInclude Include="Config\RuntimeConfig.h" />
<ClInclude Include="Core\Crypto\Cipher.h" />
<ClInclude Include="Core\Crypto\CWCCipher.h" />
<ClInclude Include="Core\Crypto\CWCUDPCipher.h" />
<ClInclude Include="Core\Crypto\RSACipher.h" />
<ClInclude Include="Core\Crypto\RSAKeyPair.h" />
<ClInclude Include="Core\Network\NetConnection.h" />
Expand All @@ -101,6 +102,7 @@
<ClInclude Include="Core\Utils\File.h" />
<ClInclude Include="Core\Utils\Logging.h" />
<ClInclude Include="Core\Utils\Random.h" />
<ClInclude Include="Core\Utils\Strings.h" />
<ClInclude Include="Platform\Platform.h" />
<ClInclude Include="Protobuf\Frpg2PlayerData.pb.h" />
<ClInclude Include="Protobuf\Frpg2RequestMessage.pb.h" />
Expand All @@ -121,13 +123,15 @@
</ItemGroup>
<ItemGroup>
<ClCompile Include="Core\Crypto\CWCCipher.cpp" />
<ClCompile Include="Core\Crypto\CWCUDPCipher.cpp" />
<ClCompile Include="Core\Crypto\RSACipher.cpp" />
<ClCompile Include="Core\Crypto\RSAKeyPair.cpp" />
<ClCompile Include="Core\Network\NetConnectionTCP.cpp" />
<ClCompile Include="Core\Network\NetConnectionUDP.cpp" />
<ClCompile Include="Core\Utils\File.cpp" />
<ClCompile Include="Core\Utils\Logging.cpp" />
<ClCompile Include="Core\Utils\Random.cpp" />
<ClCompile Include="Core\Utils\Strings.cpp" />
<ClCompile Include="Entry.cpp" />
<ClCompile Include="Platform\Win32\Win32Platform.cpp" />
<ClCompile Include="Protobuf\Frpg2PlayerData.pb.cc" />
Expand Down
12 changes: 12 additions & 0 deletions Source/Server/Server.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@
<ClInclude Include="Server\Streams\Frpg2UdpPacketStream.h">
<Filter>Server\Streams</Filter>
</ClInclude>
<ClInclude Include="Core\Utils\Strings.h">
<Filter>Core\Utils</Filter>
</ClInclude>
<ClInclude Include="Core\Crypto\CWCUDPCipher.h">
<Filter>Core\Crypto</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="Server\Server.cpp">
Expand Down Expand Up @@ -201,6 +207,12 @@
<ClCompile Include="Server\Streams\Frpg2UdpPacketStream.cpp">
<Filter>Server\Streams</Filter>
</ClCompile>
<ClCompile Include="Core\Utils\Strings.cpp">
<Filter>Core\Utils</Filter>
</ClCompile>
<ClCompile Include="Core\Crypto\CWCUDPCipher.cpp">
<Filter>Core\Crypto</Filter>
</ClCompile>
</ItemGroup>
<ItemGroup>
<None Include="Directory.Build.props" />
Expand Down
25 changes: 14 additions & 11 deletions Source/Server/Server/AuthService/AuthClient.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "Core/Utils/Logging.h"
#include "Core/Utils/Random.h"
#include "Core/Utils/Strings.h"
#include "Core/Network/NetConnection.h"

#include "Config/BuildConfig.h"
Expand Down Expand Up @@ -75,14 +76,14 @@ bool AuthClient::Poll()
return true;
}

Log("[%s] Recieved handshake request.", GetName().c_str());

// Covert the CWC key to a byte buffer.
std::string string = Request.aes_cwc_key();
uint8_t* string_ptr = reinterpret_cast<uint8_t*>(string.data());

CwcKey.assign(string_ptr, string_ptr + string.length());

Log("[%s] Recieved handshake request, Key=%s", GetName().c_str(), BytesToHex(CwcKey).c_str());

// Disable cipher while we send this "hardcoded" message.
MessageStream->SetCipher(nullptr, nullptr);

Expand Down Expand Up @@ -157,18 +158,20 @@ bool AuthClient::Poll()
return true;
}

Log("[%s] Recieved key exchange bytes.", GetName().c_str());
// This is our authentication key for the game session.
Frpg2Message KeyResponse;
KeyResponse.Payload.resize(16);
// Lower 8 bytes are what the client sent, upper 8 bytes are random data we fill in.
FillRandomBytes(KeyResponse.Payload);
memcpy(KeyResponse.Payload.data(), Message.Payload.data(), 8);

// Response is the 8 bytes the client sent us plus another random 8 bytes?
// This I think is our authentication key for the game session..
Frpg2Message Response;
Response.Payload.resize(16);
FillRandomBytes(Response.Payload);
memcpy(Response.Payload.data(), Message.Payload.data(), 8);
Log("[%s] Recieved key exchange bytes, game session key = %s", GetName().c_str(), BytesToHex(KeyResponse.Payload).c_str());

GameCwcKey = Response.Payload;
// Note: Supposedly the "normal" cwc key should work for the game session - it doesn't seem to though.
// This key however does output plaintext, but with a message tag failure. Huuum
GameCwcKey = KeyResponse.Payload;

if (!MessageStream->Send(Response, Message.Header.request_index))
if (!MessageStream->Send(KeyResponse, Message.Header.request_index))
{
Warning("[%s] Disconnecting client as failed to send key exchange.", GetName().c_str());
return true;
Expand Down
4 changes: 4 additions & 0 deletions Source/Server/Server/GameService/GameService.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include "Core/Network/NetConnection.h"
#include "Core/Network/NetConnectionUDP.h"
#include "Core/Utils/Logging.h"
#include "Core/Utils/Strings.h"

#include "Config/BuildConfig.h"
#include "Config/RuntimeConfig.h"
Expand Down Expand Up @@ -110,6 +111,9 @@ void GameService::HandleClientConnection(std::shared_ptr<NetConnection> ClientCo

GameClientAuthenticationState& AuthState = (*AuthStateIter).second;


Log("[%s] Client will use cipher key %s", ClientConnection->GetName().c_str(), BytesToHex(AuthState.CwcKey).c_str());

std::shared_ptr<GameClient> Client = std::make_shared<GameClient>(this, ClientConnection, AuthState.CwcKey, AuthState.AuthToken);
Clients.push_back(Client);
}
Expand Down
10 changes: 7 additions & 3 deletions Source/Server/Server/Streams/Frpg2UdpPacketStream.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,17 @@

#include "Core/Utils/Logging.h"

#include "Core/Crypto/CWCCipher.h"
#include "Core/Crypto/CWCUDPCipher.h"

Frpg2UdpPacketStream::Frpg2UdpPacketStream(std::shared_ptr<NetConnection> InConnection, const std::vector<uint8_t>& InCwcKey, uint64_t InAuthToken)
: Connection(InConnection)
, CwcKey(InCwcKey)
, AuthToken(InAuthToken)
{
EncryptionCipher = std::make_shared<CWCCipher>(InCwcKey);
DecryptionCipher = std::make_shared<CWCCipher>(InCwcKey);
// Udp cwc cipher seems to leave one byte padding between the iv/tag and the payload.
// TODO: This is a hacky fix, do this a better way.
EncryptionCipher = std::make_shared<CWCUDPCipher>(InCwcKey, AuthToken);
DecryptionCipher = std::make_shared<CWCUDPCipher>(InCwcKey, AuthToken);

RecieveBuffer.resize(64 * 1024);
}
Expand Down Expand Up @@ -62,7 +64,9 @@ bool Frpg2UdpPacketStream::Pump()

if (DecryptionCipher)
{
// TODO: Hum we are getting -something- that looks like plaintext, but tags are wrong?
std::vector<uint8_t> EncryptedBuffer = Packet.Payload;

if (!DecryptionCipher->Decrypt(EncryptedBuffer, Packet.Payload))
{
Warning("[%s] Failed to decrypt packet payload.", Connection->GetName().c_str());
Expand Down
Loading

0 comments on commit 9e085bb

Please sign in to comment.