Add flexibility in login packets

* It's now possible to add custom data in the login packet
* Add utility funciton to generate RSA keys
* Make the protocol able to use RSA keys with 2048 bits or more
This commit is contained in:
Eduardo Bart 2013-01-28 20:52:03 -02:00
parent 6c7a163197
commit b7eef97239
17 changed files with 84 additions and 103 deletions

View File

@ -27,8 +27,7 @@ local function tryLogin(charInfo, tries)
CharacterList.hide() CharacterList.hide()
local locale = modules.client_locales.getCurrentLocale().name g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName)
g_game.loginWorld(G.account, G.password, charInfo.worldName, charInfo.worldHost, charInfo.worldPort, charInfo.characterName, locale)
loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...')) loadBox = displayCancelBox(tr('Please wait'), tr('Connecting to game server...'))
connect(loadBox, { onCancel = function() connect(loadBox, { onCancel = function()

View File

@ -215,8 +215,7 @@ function EnterGame.doLogin()
end end
if modules.game_things.isLoaded() then if modules.game_things.isLoaded() then
local locale = modules.client_locales.getCurrentLocale().name protocolLogin:login(G.host, G.port, G.account, G.password)
protocolLogin:login(G.host, G.port, G.account, G.password, locale)
else else
loadBox:destroy() loadBox:destroy()
loadBox = nil loadBox = nil

View File

@ -66,8 +66,6 @@ GameFormatCreatureName = 22
GameSpellList = 23 GameSpellList = 23
GameClientPing = 24 GameClientPing = 24
GameExtendedClientPing = 25 GameExtendedClientPing = 25
GameUpdater = 26
GameLoginLocale = 27
GameDoubleHealth = 28 GameDoubleHealth = 28
GameDoubleSkills = 29 GameDoubleSkills = 29
GameChangeMapAwareRange = 30 GameChangeMapAwareRange = 30
@ -78,7 +76,6 @@ GameDiagonalAnimatedText = 34
GameLoginPending = 35 GameLoginPending = 35
GameNewSpeedLaw = 36 GameNewSpeedLaw = 36
GameForceFirstAutoWalkStep = 37 GameForceFirstAutoWalkStep = 37
GameLoginUUID = 38
TextColors = { TextColors = {
red = '#f55e5e', --'#c83200' red = '#f55e5e', --'#c83200'

View File

@ -5,6 +5,7 @@ function g_game.getRsa()
end end
function g_game.chooseRsa(host) function g_game.chooseRsa(host)
if currentRsa ~= CIPSOFT_RSA and currentRsa ~= OTSERV_RSA then return end
if string.ends(host, '.tibia.com') or string.ends(host, '.cipsoft.com') then if string.ends(host, '.tibia.com') or string.ends(host, '.cipsoft.com') then
g_game.setRsa(CIPSOFT_RSA) g_game.setRsa(CIPSOFT_RSA)
@ -21,11 +22,10 @@ function g_game.chooseRsa(host)
end end
end end
function g_game.setRsa(rsa) function g_game.setRsa(rsa, e)
if currentRsa ~= rsa then e = e or '65537'
g_crypt.rsaSetPublicKey(rsa, e)
currentRsa = rsa currentRsa = rsa
g_crypt.rsaSetPublicKey(currentRsa, '65537')
end
end end
function g_game.isOfficialTibia() function g_game.isOfficialTibia()

View File

@ -8,7 +8,7 @@ LoginServerUpdateNeeded = 30
LoginServerCharacterList = 100 LoginServerCharacterList = 100
LoginServerExtendedCharacterList = 101 LoginServerExtendedCharacterList = 101
function ProtocolLogin:login(host, port, accountName, accountPassword, locale) function ProtocolLogin:login(host, port, accountName, accountPassword)
if string.len(host) == 0 or port == nil or port == 0 then if string.len(host) == 0 or port == nil or port == 0 then
signalcall(self.onError, self, tr("You must enter a valid server address and port.")) signalcall(self.onError, self, tr("You must enter a valid server address and port."))
return return
@ -17,7 +17,6 @@ function ProtocolLogin:login(host, port, accountName, accountPassword, locale)
self.accountName = accountName self.accountName = accountName
self.accountPassword = accountPassword self.accountPassword = accountPassword
self.connectCallback = sendLoginPacket self.connectCallback = sendLoginPacket
self.locale = locale
self:connect(host, port) self:connect(host, port)
end end
@ -26,21 +25,11 @@ function ProtocolLogin:cancelLogin()
self:disconnect() self:disconnect()
end end
function ProtocolLogin:sendLoginPacket() function ProtocolLogin:sendLoginPacket(extended)
local msg = OutputMessage.create() local msg = OutputMessage.create()
msg:addU8(ClientOpcodes.ClientEnterAccount) msg:addU8(ClientOpcodes.ClientEnterAccount)
msg:addU16(g_game.getOs()) msg:addU16(g_game.getOs())
if g_game.getFeature(GameUpdater) then
msg:addString(g_app.getOs())
msg:addString(g_game.getUpdaterSignature())
end
if g_game.getFeature(GameLoginLocale) then
msg:addString(self.locale)
end
msg:addU16(g_game.getProtocolVersion()) msg:addU16(g_game.getProtocolVersion())
if g_game.getProtocolVersion() >= 971 then if g_game.getProtocolVersion() >= 971 then
@ -55,9 +44,10 @@ function ProtocolLogin:sendLoginPacket()
msg:addU8(0) -- clientType msg:addU8(0) -- clientType
end end
local paddingBytes = 128 local offset = msg:getMessageSize()
msg:addU8(0) -- first RSA byte must be 0
paddingBytes = paddingBytes - 1 -- first RSA byte must be 0
msg:addU8(0)
-- xtea key -- xtea key
self:generateXteaKey() self:generateXteaKey()
@ -66,25 +56,29 @@ function ProtocolLogin:sendLoginPacket()
msg:addU32(xteaKey[2]) msg:addU32(xteaKey[2])
msg:addU32(xteaKey[3]) msg:addU32(xteaKey[3])
msg:addU32(xteaKey[4]) msg:addU32(xteaKey[4])
paddingBytes = paddingBytes - 16
if g_game.getFeature(GameAccountNames) then
msg:addString(self.accountName)
else
msg:addU32(tonumber(self.accountName))
end
msg:addString(self.accountPassword)
if self.getLoginExtendedData then
local data = self:getLoginExtendedData()
msg:addString(data)
end
local paddingBytes = g_crypt.rsaGetSize() - (msg:getMessageSize() - offset)
assert(paddingBytes >= 0)
msg:addPaddingBytes(paddingBytes, 0)
msg:encryptRsa()
if g_game.getFeature(GameProtocolChecksum) then if g_game.getFeature(GameProtocolChecksum) then
self:enableChecksum() self:enableChecksum()
end end
if g_game.getFeature(GameAccountNames) then
msg:addString(self.accountName)
msg:addString(self.accountPassword)
paddingBytes = paddingBytes - (4 + string.len(self.accountName) + string.len(self.accountPassword))
else
msg:addU32(tonumber(self.accountName))
msg:addString(self.accountPassword)
paddingBytes = paddingBytes - (6 + string.len(self.accountPassword))
end
msg:addPaddingBytes(paddingBytes, 0)
msg:encryptRsa(128)
self:send(msg) self:send(msg)
self:enableXteaEncryption() self:enableXteaEncryption()
self:recv() self:recv()

View File

@ -337,8 +337,6 @@ namespace Otc
GameSpellList = 23, GameSpellList = 23,
GameClientPing = 24, GameClientPing = 24,
GameExtendedClientPing = 25, GameExtendedClientPing = 25,
GameUpdater = 26,
GameLoginLocale = 27,
GameDoubleHealth = 28, GameDoubleHealth = 28,
GameDoubleSkills = 29, GameDoubleSkills = 29,
GameChangeMapAwareRange = 30, GameChangeMapAwareRange = 30,
@ -349,7 +347,6 @@ namespace Otc
GameLoginPending = 35, GameLoginPending = 35,
GameNewSpeedLaw = 36, GameNewSpeedLaw = 36,
GameForceFirstAutoWalkStep = 37, GameForceFirstAutoWalkStep = 37,
GameLoginUUID = 38,
// 51-100 reserved to be defined in lua // 51-100 reserved to be defined in lua
LastGameFeature = 101 LastGameFeature = 101
}; };

View File

@ -473,7 +473,7 @@ void Game::processWalkCancel(Otc::Direction direction)
m_localPlayer->cancelWalk(direction); m_localPlayer->cancelWalk(direction);
} }
void Game::loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName, const std::string& locale) void Game::loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName)
{ {
if(m_protocolGame || isOnline()) if(m_protocolGame || isOnline())
stdext::throw_exception("Unable to login into a world while already online or logging."); stdext::throw_exception("Unable to login into a world while already online or logging.");
@ -488,7 +488,7 @@ void Game::loginWorld(const std::string& account, const std::string& password, c
m_localPlayer->setName(characterName); m_localPlayer->setName(characterName);
m_protocolGame = ProtocolGamePtr(new ProtocolGame); m_protocolGame = ProtocolGamePtr(new ProtocolGame);
m_protocolGame->login(account, password, worldHost, (uint16)worldPort, characterName, locale); m_protocolGame->login(account, password, worldHost, (uint16)worldPort, characterName);
m_characterName = characterName; m_characterName = characterName;
m_worldName = worldName; m_worldName = worldName;
} }

View File

@ -135,7 +135,7 @@ protected:
public: public:
// login related // login related
void loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName, const std::string& locale); void loginWorld(const std::string& account, const std::string& password, const std::string& worldName, const std::string& worldHost, int worldPort, const std::string& characterName);
void cancelLogin(); void cancelLogin();
void forceLogout(); void forceLogout();
void safeLogout(); void safeLogout();
@ -270,9 +270,6 @@ public:
void setCustomOs(int os) { m_clientCustomOs = os; } void setCustomOs(int os) { m_clientCustomOs = os; }
int getOs(); int getOs();
void setUpdaterSignature(const std::string& sig) { m_clientSignature = sig; }
std::string getUpdaterSignature() { return m_clientSignature; }
bool canPerformGameAction(); bool canPerformGameAction();
bool checkBotProtection(); bool checkBotProtection();

View File

@ -238,8 +238,6 @@ void Client::registerLuaFunctions()
g_lua.bindSingletonFunction("g_game", "setProtocolVersion", &Game::setProtocolVersion, &g_game); g_lua.bindSingletonFunction("g_game", "setProtocolVersion", &Game::setProtocolVersion, &g_game);
g_lua.bindSingletonFunction("g_game", "getClientVersion", &Game::getClientVersion, &g_game); g_lua.bindSingletonFunction("g_game", "getClientVersion", &Game::getClientVersion, &g_game);
g_lua.bindSingletonFunction("g_game", "setClientVersion", &Game::setClientVersion, &g_game); g_lua.bindSingletonFunction("g_game", "setClientVersion", &Game::setClientVersion, &g_game);
g_lua.bindSingletonFunction("g_game", "setUpdaterSignature", &Game::setUpdaterSignature, &g_game);
g_lua.bindSingletonFunction("g_game", "getUpdaterSignature", &Game::getUpdaterSignature, &g_game);
g_lua.bindSingletonFunction("g_game", "setCustomOs", &Game::setCustomOs, &g_game); g_lua.bindSingletonFunction("g_game", "setCustomOs", &Game::setCustomOs, &g_game);
g_lua.bindSingletonFunction("g_game", "getOs", &Game::getOs, &g_game); g_lua.bindSingletonFunction("g_game", "getOs", &Game::getOs, &g_game);
g_lua.bindSingletonFunction("g_game", "getCharacterName", &Game::getCharacterName, &g_game); g_lua.bindSingletonFunction("g_game", "getCharacterName", &Game::getCharacterName, &g_game);

View File

@ -26,12 +26,11 @@
#include "item.h" #include "item.h"
#include "localplayer.h" #include "localplayer.h"
void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& locale) void ProtocolGame::login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName)
{ {
m_accountName = accountName; m_accountName = accountName;
m_accountPassword = accountPassword; m_accountPassword = accountPassword;
m_characterName = characterName; m_characterName = characterName;
m_locale = locale;
connect(host, port); connect(host, port);
} }

View File

@ -31,7 +31,7 @@
class ProtocolGame : public Protocol class ProtocolGame : public Protocol
{ {
public: public:
void login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName, const std::string& locale); void login(const std::string& accountName, const std::string& accountPassword, const std::string& host, uint16 port, const std::string& characterName);
void send(const OutputMessagePtr& outputMessage); void send(const OutputMessagePtr& outputMessage);
void sendExtendedOpcode(uint8 opcode, const std::string& buffer); void sendExtendedOpcode(uint8 opcode, const std::string& buffer);
@ -235,7 +235,6 @@ private:
std::string m_accountName; std::string m_accountName;
std::string m_accountPassword; std::string m_accountPassword;
std::string m_characterName; std::string m_characterName;
std::string m_locale;
stdext::timer m_pingTimer; stdext::timer m_pingTimer;
LocalPlayerPtr m_localPlayer; LocalPlayerPtr m_localPlayer;
}; };

View File

@ -53,17 +53,7 @@ void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRando
OutputMessagePtr msg(new OutputMessage); OutputMessagePtr msg(new OutputMessage);
msg->addU8(Proto::ClientPendingGame); msg->addU8(Proto::ClientPendingGame);
msg->addU16(g_game.getOs()); msg->addU16(g_game.getOs());
if(g_game.getFeature(Otc::GameUpdater)) {
msg->addString(g_app.getOs());
msg->addString(g_game.getUpdaterSignature());
}
if(g_game.getFeature(Otc::GameLoginLocale))
msg->addString(m_locale);
msg->addU16(g_game.getProtocolVersion()); msg->addU16(g_game.getProtocolVersion());
if(g_game.getProtocolVersion() >= 971) { if(g_game.getProtocolVersion() >= 971) {
@ -71,9 +61,9 @@ void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRando
msg->addU8(0); // clientType msg->addU8(0); // clientType
} }
int paddingBytes = 128; int offset = msg->getMessageSize();
msg->addU8(0); // first RSA byte must be 0 msg->addU8(0); // first RSA byte must be 0
paddingBytes -= 1;
// xtea key // xtea key
generateXteaKey(); generateXteaKey();
@ -82,46 +72,34 @@ void ProtocolGame::sendLoginPacket(uint challengeTimestamp, uint8 challengeRando
msg->addU32(m_xteaKey[2]); msg->addU32(m_xteaKey[2]);
msg->addU32(m_xteaKey[3]); msg->addU32(m_xteaKey[3]);
msg->addU8(0); // is gm set? msg->addU8(0); // is gm set?
paddingBytes -= 17;
if(g_game.getFeature(Otc::GameProtocolChecksum)) if(g_game.getFeature(Otc::GameAccountNames))
enableChecksum();
if(g_game.getFeature(Otc::GameAccountNames)) {
msg->addString(m_accountName); msg->addString(m_accountName);
msg->addString(m_characterName); else
msg->addString(m_accountPassword);
paddingBytes -= 6 + m_accountName.length() + m_characterName.length() + m_accountPassword.length();
} else {
msg->addU32(stdext::from_string<uint32>(m_accountName)); msg->addU32(stdext::from_string<uint32>(m_accountName));
msg->addString(m_characterName); msg->addString(m_characterName);
msg->addString(m_accountPassword); msg->addString(m_accountPassword);
paddingBytes -= 8 + m_characterName.length() + m_accountPassword.length();
}
if(g_game.getFeature(Otc::GameLoginUUID)) {
std::string uuid = g_crypt.getMachineUUID();
msg->addString(uuid);
paddingBytes -= 2 + uuid.length();
}
if(g_game.getFeature(Otc::GameChallengeOnLogin)) { if(g_game.getFeature(Otc::GameChallengeOnLogin)) {
msg->addU32(challengeTimestamp); msg->addU32(challengeTimestamp);
msg->addU8(challengeRandom); msg->addU8(challengeRandom);
paddingBytes -= 5;
} }
if(paddingBytes < 0) { std::string extended = callLuaField<std::string>("getLoginExtendedData");
g_game.processLoginError("AccountName or Password or CharacterName are too big!\nPlease contact game support."); if(!extended.empty())
g_game.processDisconnect(); msg->addString(extended);
return;
}
// complete the 128 bytes for rsa encryption with zeros // complete the bytes for rsa encryption with zeros
int paddingBytes = g_crypt.rsaGetSize() - (msg->getMessageSize() - offset);
assert(paddingBytes >= 0);
msg->addPaddingBytes(paddingBytes); msg->addPaddingBytes(paddingBytes);
// encrypt with RSA // encrypt with RSA
msg->encryptRsa(128); msg->encryptRsa();
if(g_game.getFeature(Otc::GameProtocolChecksum))
enableChecksum();
send(msg); send(msg);

View File

@ -117,9 +117,11 @@ void Application::registerLuaFunctions()
g_lua.bindSingletonFunction("g_crypt", "decrypt", &Crypt::decrypt, &g_crypt); g_lua.bindSingletonFunction("g_crypt", "decrypt", &Crypt::decrypt, &g_crypt);
g_lua.bindSingletonFunction("g_crypt", "sha1Encode", &Crypt::sha1Encode, &g_crypt); g_lua.bindSingletonFunction("g_crypt", "sha1Encode", &Crypt::sha1Encode, &g_crypt);
g_lua.bindSingletonFunction("g_crypt", "md5Encode", &Crypt::md5Encode, &g_crypt); g_lua.bindSingletonFunction("g_crypt", "md5Encode", &Crypt::md5Encode, &g_crypt);
g_lua.bindSingletonFunction("g_crypt", "rsaGenerateKey", &Crypt::rsaGenerateKey, &g_crypt);
g_lua.bindSingletonFunction("g_crypt", "rsaSetPublicKey", &Crypt::rsaSetPublicKey, &g_crypt); g_lua.bindSingletonFunction("g_crypt", "rsaSetPublicKey", &Crypt::rsaSetPublicKey, &g_crypt);
g_lua.bindSingletonFunction("g_crypt", "rsaSetPrivateKey", &Crypt::rsaSetPrivateKey, &g_crypt); g_lua.bindSingletonFunction("g_crypt", "rsaSetPrivateKey", &Crypt::rsaSetPrivateKey, &g_crypt);
g_lua.bindSingletonFunction("g_crypt", "rsaCheckKey", &Crypt::rsaCheckKey, &g_crypt); g_lua.bindSingletonFunction("g_crypt", "rsaCheckKey", &Crypt::rsaCheckKey, &g_crypt);
g_lua.bindSingletonFunction("g_crypt", "rsaGetSize", &Crypt::rsaGetSize, &g_crypt);
// Clock // Clock
g_lua.registerSingletonClass("g_clock"); g_lua.registerSingletonClass("g_clock");

View File

@ -89,12 +89,14 @@ void OutputMessage::addPaddingBytes(int bytes, uint8 byte)
m_messageSize += bytes; m_messageSize += bytes;
} }
void OutputMessage::encryptRsa(int size) void OutputMessage::encryptRsa()
{ {
int size = g_crypt.rsaGetSize();
if(m_messageSize < size) if(m_messageSize < size)
throw stdext::exception("insufficient bytes in buffer to encrypt"); throw stdext::exception("insufficient bytes in buffer to encrypt");
g_crypt.rsaEncrypt((unsigned char*)m_buffer + m_writePos - size, size); if(!g_crypt.rsaEncrypt((unsigned char*)m_buffer + m_writePos - size, size))
throw stdext::exception("rsa encryption failed");
} }
void OutputMessage::writeChecksum() void OutputMessage::writeChecksum()

View File

@ -49,7 +49,7 @@ public:
void addString(const std::string& buffer); void addString(const std::string& buffer);
void addPaddingBytes(int bytes, uint8 byte = 0); void addPaddingBytes(int bytes, uint8 byte = 0);
void encryptRsa(int size); void encryptRsa();
uint16 getWritePos() { return m_writePos; } uint16 getWritePos() { return m_writePos; }
uint16 getMessageSize() { return m_messageSize; } uint16 getMessageSize() { return m_messageSize; }

View File

@ -292,11 +292,21 @@ std::string Crypt::sha512Encode(const std::string& decoded_string, bool upperCas
return result; return result;
} }
void Crypt::rsaGenerateKey(int bits, int e)
{
RSA *rsa = RSA_generate_key(bits, e, nullptr, nullptr);
g_logger.info(stdext::format("%d bits (%d bytes) RSA key generated", bits, bits / 8));
g_logger.info(std::string("p = ") + BN_bn2dec(m_rsa->p));
g_logger.info(std::string("q = ") + BN_bn2dec(m_rsa->q));
g_logger.info(std::string("d = ") + BN_bn2dec(m_rsa->d));
g_logger.info(std::string("n = ") + BN_bn2dec(m_rsa->n));
g_logger.info(std::string("e = ") + BN_bn2dec(m_rsa->e));
RSA_free(rsa);
}
void Crypt::rsaSetPublicKey(const std::string& n, const std::string& e) void Crypt::rsaSetPublicKey(const std::string& n, const std::string& e)
{ {
RSA_free(m_rsa);
m_rsa = RSA_new();
BN_dec2bn(&m_rsa->n, n.c_str()); BN_dec2bn(&m_rsa->n, n.c_str());
BN_dec2bn(&m_rsa->e, e.c_str()); BN_dec2bn(&m_rsa->e, e.c_str());
} }
@ -331,12 +341,20 @@ bool Crypt::rsaCheckKey()
bool Crypt::rsaEncrypt(unsigned char *msg, int size) bool Crypt::rsaEncrypt(unsigned char *msg, int size)
{ {
assert(size <= 128); if(size != RSA_size(m_rsa))
return false;
return RSA_public_encrypt(size, msg, msg, m_rsa, RSA_NO_PADDING) != -1; return RSA_public_encrypt(size, msg, msg, m_rsa, RSA_NO_PADDING) != -1;
} }
bool Crypt::rsaDecrypt(unsigned char *msg, int size) bool Crypt::rsaDecrypt(unsigned char *msg, int size)
{ {
assert(size <= 128); if(size != RSA_size(m_rsa))
return false;
return RSA_private_decrypt(size, msg, msg, m_rsa, RSA_NO_PADDING) != -1; return RSA_private_decrypt(size, msg, msg, m_rsa, RSA_NO_PADDING) != -1;
} }
int Crypt::rsaGetSize()
{
return RSA_size(m_rsa);
}

View File

@ -49,11 +49,13 @@ public:
std::string sha256Encode(const std::string& decoded_string, bool upperCase); std::string sha256Encode(const std::string& decoded_string, bool upperCase);
std::string sha512Encode(const std::string& decoded_string, bool upperCase); std::string sha512Encode(const std::string& decoded_string, bool upperCase);
void rsaGenerateKey(int bits, int e);
void rsaSetPublicKey(const std::string& n, const std::string& e); void rsaSetPublicKey(const std::string& n, const std::string& e);
void rsaSetPrivateKey(const std::string &p, const std::string &q, const std::string &d); void rsaSetPrivateKey(const std::string &p, const std::string &q, const std::string &d);
bool rsaCheckKey(); bool rsaCheckKey();
bool rsaEncrypt(unsigned char *msg, int size); bool rsaEncrypt(unsigned char *msg, int size);
bool rsaDecrypt(unsigned char *msg, int size); bool rsaDecrypt(unsigned char *msg, int size);
int rsaGetSize();
private: private:
std::string getMachineKey(); std::string getMachineKey();