Changeset View
Changeset View
Standalone View
Standalone View
source/network/scripting/JSInterface_Network.cpp
Show All 23 Lines | |||||
#include "lib/types.h" | #include "lib/types.h" | ||||
#include "lobby/IXmppClient.h" | #include "lobby/IXmppClient.h" | ||||
#include "network/NetClient.h" | #include "network/NetClient.h" | ||||
#include "network/NetMessage.h" | #include "network/NetMessage.h" | ||||
#include "network/NetServer.h" | #include "network/NetServer.h" | ||||
#include "network/StunClient.h" | #include "network/StunClient.h" | ||||
#include "ps/CLogger.h" | #include "ps/CLogger.h" | ||||
#include "ps/Game.h" | #include "ps/Game.h" | ||||
#include "ps/GUID.h" | |||||
#include "ps/Hashing.h" | |||||
#include "ps/Pyrogenesis.h" | |||||
#include "ps/Util.h" | #include "ps/Util.h" | ||||
#include "scriptinterface/ScriptInterface.h" | #include "scriptinterface/ScriptInterface.h" | ||||
#include "third_party/encryption/pkcs5_pbkdf2.h" | #include "third_party/encryption/pkcs5_pbkdf2.h" | ||||
u16 JSI_Network::GetDefaultPort(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | u16 JSI_Network::GetDefaultPort(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | ||||
{ | { | ||||
return PS_DEFAULT_PORT; | return PS_DEFAULT_PORT; | ||||
} | } | ||||
bool JSI_Network::HasNetServer(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | bool JSI_Network::HasNetServer(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | ||||
{ | { | ||||
return !!g_NetServer; | return !!g_NetServer; | ||||
} | } | ||||
bool JSI_Network::HasNetClient(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | bool JSI_Network::HasNetClient(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate)) | ||||
{ | { | ||||
return !!g_NetClient; | return !!g_NetClient; | ||||
} | } | ||||
CStr JSI_Network::HashPassword(const CStr& password) | |||||
{ | |||||
if (password.empty()) | |||||
return password; | |||||
ENSURE(sodium_init() >= 0); | |||||
const int DIGESTSIZE = crypto_hash_sha256_BYTES; | |||||
constexpr int ITERATIONS = 1737; | |||||
cassert(DIGESTSIZE == 32); | |||||
static const unsigned char salt_base[DIGESTSIZE] = { | |||||
244, 243, 249, 244, 32, 33, 19, 35, 16, 11, 12, 13, 14, 15, 16, 17, | |||||
18, 19, 20, 32, 33, 244, 224, 127, 129, 130, 140, 153, 88, 123, 234, 123 }; | |||||
// initialize the salt buffer | |||||
unsigned char salt_buffer[DIGESTSIZE] = { 0 }; | |||||
crypto_hash_sha256_state state; | |||||
crypto_hash_sha256_init(&state); | |||||
crypto_hash_sha256_update(&state, salt_base, sizeof(salt_base)); | |||||
crypto_hash_sha256_final(&state, salt_buffer); | |||||
// PBKDF2 to create the buffer | |||||
unsigned char encrypted[DIGESTSIZE]; | |||||
pbkdf2(encrypted, (unsigned char*)password.c_str(), password.length(), salt_buffer, DIGESTSIZE, ITERATIONS); | |||||
return CStr(Hexify(encrypted, DIGESTSIZE)).UpperCase(); | |||||
} | |||||
void JSI_Network::StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName, bool useSTUN, const CStr& password) | void JSI_Network::StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName, bool useSTUN, const CStr& password) | ||||
{ | { | ||||
ENSURE(!g_NetClient); | ENSURE(!g_NetClient); | ||||
ENSURE(!g_NetServer); | ENSURE(!g_NetServer); | ||||
ENSURE(!g_Game); | ENSURE(!g_Game); | ||||
// Always use lobby authentication for lobby matches to prevent impersonation and smurfing, in particular through mods that implemented an UI for arbitrary or other players nicknames. | // Always use lobby authentication for lobby matches to prevent impersonation and smurfing, in particular through mods that implemented an UI for arbitrary or other players nicknames. | ||||
bool hasLobby = !!g_XmppClient; | bool hasLobby = !!g_XmppClient; | ||||
Show All 26 Lines | void JSI_Network::StartNetworkHost(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const u16 serverPort, const CStr& hostLobbyName, bool useSTUN, const CStr& password) | ||||
if (!g_NetServer->SetupConnection(serverPort)) | if (!g_NetServer->SetupConnection(serverPort)) | ||||
{ | { | ||||
ScriptRequest rq(pCmptPrivate->pScriptInterface); | ScriptRequest rq(pCmptPrivate->pScriptInterface); | ||||
ScriptException::Raise(rq, "Failed to start server"); | ScriptException::Raise(rq, "Failed to start server"); | ||||
SAFE_DELETE(g_NetServer); | SAFE_DELETE(g_NetServer); | ||||
return; | return; | ||||
} | } | ||||
// We will get hashed password from clients, so hash it once for server | /** | ||||
CStr hashedPass = HashPassword(password); | * Password security - we want 0 A.D. to protect players from malicious hosts. We assume that clients | ||||
* might mistakenly send a personal password instead of the game password (e.g. enter their mail account's password on autopilot). | |||||
* Malicious dedicated servers might be set up to farm these failed logins and possibly obtain user credentials. | |||||
* Therefore, we hash the passwords on the client side before sending them to the server. | |||||
* This still makes the passwords potentially recoverable, but makes it much harder at scale. | |||||
* To prevent the creation of rainbow tables, hash with: | |||||
* - the host name | |||||
* - the client name (this makes rainbow tables completely unworkable unless a specific user is targeted, | |||||
* but that would require both computing the matching rainbow table _and_ for that specific user to mistype a personal password, | |||||
* at which point we assume the attacker would/could probably just rather use another means of obtaining the password). | |||||
wraitii: Per https://xkcd.com/538/ | |||||
* - the password itself | |||||
* - the engine version (so that the hashes change periodically) | |||||
* TODO: it should be possible to implement SRP or something along those lines to completely protect from this, | |||||
* but the cost/benefit ratio is probably not worth it. | |||||
*/ | |||||
CStr hashedPass = HashPassword(password, playerName.ToUTF8().LowerCase() + password + engine_version); | |||||
Not Done Inline Actionslowercase the playername, iq is using lowercased version, they will not match ref rP24733, also in all other cases Silier: lowercase the playername, iq is using lowercased version, they will not match ref rP24733, also… | |||||
Not Done Inline Actionsyou shoud use jid here not playername Silier: you shoud use jid here not playername | |||||
g_NetServer->SetPassword(hashedPass); | g_NetServer->SetPassword(hashedPass); | ||||
g_Game = new CGame(true); | g_Game = new CGame(true); | ||||
g_NetClient = new CNetClient(g_Game, true); | g_NetClient = new CNetClient(g_Game, true); | ||||
g_NetClient->SetUserName(playerName); | g_NetClient->SetUserName(playerName); | ||||
g_NetClient->SetHostingPlayerName(hostLobbyName); | g_NetClient->SetHostingPlayerName(hostLobbyName); | ||||
// Hash the password further with the client name. | |||||
hashedPass = HashPassword(hashedPass, playerName.ToUTF8().LowerCase()); | |||||
g_NetClient->SetGamePassword(hashedPass); | g_NetClient->SetGamePassword(hashedPass); | ||||
g_NetClient->SetupServerData("127.0.0.1", serverPort, false); | g_NetClient->SetupServerData("127.0.0.1", serverPort, false); | ||||
Not Done Inline ActionsMissing the CStr include? Stan: Missing the CStr include? | |||||
if (!g_NetClient->SetupConnection(nullptr)) | if (!g_NetClient->SetupConnection(nullptr)) | ||||
{ | { | ||||
ScriptRequest rq(pCmptPrivate->pScriptInterface); | ScriptRequest rq(pCmptPrivate->pScriptInterface); | ||||
ScriptException::Raise(rq, "Failed to connect to server"); | ScriptException::Raise(rq, "Failed to connect to server"); | ||||
SAFE_DELETE(g_NetClient); | SAFE_DELETE(g_NetClient); | ||||
SAFE_DELETE(g_Game); | SAFE_DELETE(g_Game); | ||||
} | } | ||||
} | } | ||||
void JSI_Network::StartNetworkJoin(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool useSTUN, const CStr& hostJID) | void JSI_Network::StartNetworkJoin(ScriptInterface::CmptPrivate* pCmptPrivate, const CStrW& playerName, const CStr& serverAddress, u16 serverPort, bool useSTUN, const CStr& hostJID) | ||||
{ | { | ||||
ENSURE(!g_NetClient); | ENSURE(!g_NetClient); | ||||
ENSURE(!g_NetServer); | ENSURE(!g_NetServer); | ||||
ENSURE(!g_Game); | ENSURE(!g_Game); | ||||
g_Game = new CGame(true); | g_Game = new CGame(true); | ||||
g_NetClient = new CNetClient(g_Game, false); | g_NetClient = new CNetClient(g_Game, false); | ||||
g_NetClient->SetUserName(playerName); | g_NetClient->SetUserName(playerName); | ||||
Not Done Inline ActionsDoes that mean it will break all passwords at each version change? Stan: Does that mean it will break all passwords at each version change? | |||||
Done Inline ActionsYes, but these are game passwords, so that's OK. wraitii: Yes, but these are game passwords, so that's OK. | |||||
g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); | g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); | ||||
g_NetClient->SetupServerData(serverAddress, serverPort, useSTUN); | g_NetClient->SetupServerData(serverAddress, serverPort, useSTUN); | ||||
if (!g_NetClient->SetupConnection(nullptr)) | if (!g_NetClient->SetupConnection(nullptr)) | ||||
{ | { | ||||
ScriptRequest rq(pCmptPrivate->pScriptInterface); | ScriptRequest rq(pCmptPrivate->pScriptInterface); | ||||
ScriptException::Raise(rq, "Failed to connect to server"); | ScriptException::Raise(rq, "Failed to connect to server"); | ||||
SAFE_DELETE(g_NetClient); | SAFE_DELETE(g_NetClient); | ||||
SAFE_DELETE(g_Game); | SAFE_DELETE(g_Game); | ||||
} | } | ||||
} | } | ||||
void JSI_Network::StartNetworkJoinLobby(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), const CStrW& playerName, const CStr& hostJID, const CStr& password) | void JSI_Network::StartNetworkJoinLobby(ScriptInterface::CmptPrivate* UNUSED(pCmptPrivate), const CStrW& playerName, const CStr& hostJID, const CStr& password) | ||||
{ | { | ||||
ENSURE(!!g_XmppClient); | ENSURE(!!g_XmppClient); | ||||
ENSURE(!g_NetClient); | ENSURE(!g_NetClient); | ||||
ENSURE(!g_NetServer); | ENSURE(!g_NetServer); | ||||
ENSURE(!g_Game); | ENSURE(!g_Game); | ||||
CStr hashedPass = HashPassword(password); | CStr hashedPass = HashPassword(password, playerName.ToUTF8().LowerCase() + password + engine_version); | ||||
// Hash the password further with the client name. | |||||
hashedPass = HashPassword(hashedPass, playerName.ToUTF8().LowerCase()); | |||||
Not Done Inline Actionsyou wanted hostJID here ? Silier: you wanted hostJID here ? | |||||
Not Done Inline Actionsas i said playername contains rating and that can be anything custom, not recoverable from xmpp client jid Silier: as i said playername contains rating and that can be anything custom, not recoverable from xmpp… | |||||
Not Done Inline ActionsI guess you want to remove that? and the two above? Stan: I guess you want to remove that? and the two above? | |||||
Done Inline ActionsIndeed :p wraitii: Indeed :p | |||||
g_Game = new CGame(true); | g_Game = new CGame(true); | ||||
g_NetClient = new CNetClient(g_Game, false); | g_NetClient = new CNetClient(g_Game, false); | ||||
g_NetClient->SetUserName(playerName); | g_NetClient->SetUserName(playerName); | ||||
g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); | g_NetClient->SetHostingPlayerName(hostJID.substr(0, hostJID.find("@"))); | ||||
g_NetClient->SetGamePassword(hashedPass); | g_NetClient->SetGamePassword(hashedPass); | ||||
g_XmppClient->SendIqGetConnectionData(hostJID, hashedPass.c_str()); | g_XmppClient->SendIqGetConnectionData(hostJID, hashedPass.c_str()); | ||||
} | } | ||||
▲ Show 20 Lines • Show All 109 Lines • Show Last 20 Lines |
Wildfire Games · Phabricator
Per https://xkcd.com/538/