mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-10 00:04:48 -07:00
allow login using hashed passwords (#4464)
* Support getting a user's password salt via initial websocket connection (added to Event_ServerIdentification) * Nonsense stuff to figure out later * move passwordhasher to correct location * protobuf changes * add ext to protobuf * implement request password salt server side * add supportspasswordhash to server identification * check backwards compatibility * reset some changes to master * implement get password salt client side * implement checking hashed passwords on server login * check for registration requirement on getting password salt * properly check password salt response and show errors * remove unused property * add password salt to list of response types Co-authored-by: ZeldaZach <zahalpern+github@gmail.com>
This commit is contained in:
parent
b0845837c2
commit
45d86e7ab7
23 changed files with 193 additions and 26 deletions
|
|
@ -6,8 +6,10 @@ add_subdirectory(pb)
|
|||
|
||||
SET(common_SOURCES
|
||||
decklist.cpp
|
||||
expression.cpp
|
||||
featureset.cpp
|
||||
get_pb_extension.cpp
|
||||
passwordhasher.cpp
|
||||
rng_abstract.cpp
|
||||
rng_sfmt.cpp
|
||||
server.cpp
|
||||
|
|
@ -17,8 +19,8 @@ SET(common_SOURCES
|
|||
server_card.cpp
|
||||
server_cardzone.cpp
|
||||
server_counter.cpp
|
||||
server_game.cpp
|
||||
server_database_interface.cpp
|
||||
server_game.cpp
|
||||
server_player.cpp
|
||||
server_protocolhandler.cpp
|
||||
server_remoteuserinterface.cpp
|
||||
|
|
@ -26,7 +28,6 @@ SET(common_SOURCES
|
|||
server_room.cpp
|
||||
serverinfo_user_container.cpp
|
||||
sfmt/SFMT.c
|
||||
expression.cpp
|
||||
)
|
||||
|
||||
set(ORACLE_LIBS)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ QMap<QString, bool> FeatureSet::getDefaultFeatureList()
|
|||
|
||||
void FeatureSet::initalizeFeatureList(QMap<QString, bool> &featureList)
|
||||
{
|
||||
// default features [name], [is required to connect]
|
||||
featureList.insert("client_id", false);
|
||||
featureList.insert("client_ver", false);
|
||||
featureList.insert("feature_set", false);
|
||||
|
|
@ -25,6 +26,7 @@ void FeatureSet::initalizeFeatureList(QMap<QString, bool> &featureList)
|
|||
featureList.insert("idle_client", false);
|
||||
featureList.insert("forgot_password", false);
|
||||
featureList.insert("websocket", false);
|
||||
featureList.insert("hashed_password_login", false);
|
||||
// These are temp to force users onto a newer client
|
||||
featureList.insert("2.7.0_min_version", false);
|
||||
featureList.insert("2.8.0_min_version", false);
|
||||
|
|
|
|||
44
common/passwordhasher.cpp
Normal file
44
common/passwordhasher.cpp
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#include "passwordhasher.h"
|
||||
|
||||
#include "rng_sfmt.h"
|
||||
|
||||
#include <QCryptographicHash>
|
||||
|
||||
void PasswordHasher::initialize()
|
||||
{
|
||||
// dummy
|
||||
}
|
||||
|
||||
QString PasswordHasher::computeHash(const QString &password, const QString &salt)
|
||||
{
|
||||
QCryptographicHash::Algorithm algo = QCryptographicHash::Sha512;
|
||||
const int rounds = 1000;
|
||||
|
||||
QByteArray hash = (salt + password).toUtf8();
|
||||
for (int i = 0; i < rounds; ++i) {
|
||||
hash = QCryptographicHash::hash(hash, algo);
|
||||
}
|
||||
QString hashedPass = salt + QString(hash.toBase64());
|
||||
return hashedPass;
|
||||
}
|
||||
|
||||
QString PasswordHasher::generateRandomSalt(const int len)
|
||||
{
|
||||
static const char alphanum[] = "0123456789"
|
||||
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
"abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
QString ret;
|
||||
int size = sizeof(alphanum) - 1;
|
||||
|
||||
for (int i = 0; i < len; ++i) {
|
||||
ret.append(alphanum[rng->rand(0, size)]);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
QString PasswordHasher::generateActivationToken()
|
||||
{
|
||||
return QCryptographicHash::hash(generateRandomSalt().toUtf8(), QCryptographicHash::Md5).toBase64().left(16);
|
||||
}
|
||||
15
common/passwordhasher.h
Normal file
15
common/passwordhasher.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#ifndef PASSWORDHASHER_H
|
||||
#define PASSWORDHASHER_H
|
||||
|
||||
#include <QObject>
|
||||
|
||||
class PasswordHasher
|
||||
{
|
||||
public:
|
||||
static void initialize();
|
||||
static QString computeHash(const QString &password, const QString &salt);
|
||||
static QString generateRandomSalt(const int len = 16);
|
||||
static QString generateActivationToken();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
@ -115,6 +115,7 @@ SET(PROTO_FILES
|
|||
moderator_commands.proto
|
||||
move_card_to_zone.proto
|
||||
response_activate.proto
|
||||
response_adjust_mod.proto
|
||||
response_ban_history.proto
|
||||
response_deck_download.proto
|
||||
response_deck_list.proto
|
||||
|
|
@ -126,13 +127,13 @@ SET(PROTO_FILES
|
|||
response_join_room.proto
|
||||
response_list_users.proto
|
||||
response_login.proto
|
||||
response_password_salt.proto
|
||||
response_register.proto
|
||||
response_replay_download.proto
|
||||
response_replay_list.proto
|
||||
response_adjust_mod.proto
|
||||
response_viewlog_history.proto
|
||||
response_warn_history.proto
|
||||
response_warn_list.proto
|
||||
response_viewlog_history.proto
|
||||
response.proto
|
||||
room_commands.proto
|
||||
room_event.proto
|
||||
|
|
|
|||
|
|
@ -5,7 +5,12 @@ message Event_ServerIdentification {
|
|||
extend SessionEvent {
|
||||
optional Event_ServerIdentification ext = 500;
|
||||
}
|
||||
enum ServerOptions {
|
||||
NoOptions = 0;
|
||||
SupportsPasswordHash = 1;
|
||||
}
|
||||
optional string server_name = 1;
|
||||
optional string server_version = 2;
|
||||
optional uint32 protocol_version = 3;
|
||||
optional ServerOptions server_options = 4 [default = NoOptions];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ message Response {
|
|||
WARN_LIST = 1014;
|
||||
VIEW_LOG = 1015;
|
||||
FORGOT_PASSWORD_REQUEST = 1016;
|
||||
PASSWORD_SALT = 1017;
|
||||
REPLAY_LIST = 1100;
|
||||
REPLAY_DOWNLOAD = 1101;
|
||||
}
|
||||
|
|
|
|||
9
common/pb/response_password_salt.proto
Normal file
9
common/pb/response_password_salt.proto
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
syntax = "proto2";
|
||||
import "response.proto";
|
||||
|
||||
message Response_PasswordSalt {
|
||||
extend Response {
|
||||
optional Response_PasswordSalt ext = 1017;
|
||||
}
|
||||
optional string password_salt = 1;
|
||||
}
|
||||
|
|
@ -27,6 +27,7 @@ message SessionCommand {
|
|||
FORGOT_PASSWORD_REQUEST = 1021;
|
||||
FORGOT_PASSWORD_RESET = 1022;
|
||||
FORGOT_PASSWORD_CHALLENGE = 1023;
|
||||
REQUEST_PASSWORD_SALT = 1024;
|
||||
REPLAY_LIST = 1100;
|
||||
REPLAY_DOWNLOAD = 1101;
|
||||
REPLAY_MODIFY_MATCH = 1102;
|
||||
|
|
@ -50,6 +51,7 @@ message Command_Login {
|
|||
optional string clientid = 3;
|
||||
optional string clientver = 4;
|
||||
repeated string clientfeatures = 5;
|
||||
optional string hashed_password = 6;
|
||||
}
|
||||
|
||||
message Command_Message {
|
||||
|
|
@ -191,3 +193,10 @@ message Command_ForgotPasswordChallenge {
|
|||
optional string clientid = 2;
|
||||
optional string email = 3;
|
||||
}
|
||||
|
||||
message Command_RequestPasswordSalt {
|
||||
extend SessionCommand {
|
||||
optional Command_RequestPasswordSalt ext = 1024;
|
||||
}
|
||||
required string user_name = 1;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -79,6 +79,7 @@ Server_DatabaseInterface *Server::getDatabaseInterface() const
|
|||
AuthenticationResult Server::loginUser(Server_ProtocolHandler *session,
|
||||
QString &name,
|
||||
const QString &password,
|
||||
bool passwordNeedsHash,
|
||||
QString &reasonStr,
|
||||
int &secondsLeft,
|
||||
QString &clientid,
|
||||
|
|
@ -99,8 +100,8 @@ AuthenticationResult Server::loginUser(Server_ProtocolHandler *session,
|
|||
|
||||
Server_DatabaseInterface *databaseInterface = getDatabaseInterface();
|
||||
|
||||
AuthenticationResult authState =
|
||||
databaseInterface->checkUserPassword(session, name, password, clientid, reasonStr, secondsLeft);
|
||||
AuthenticationResult authState = databaseInterface->checkUserPassword(session, name, password, clientid, reasonStr,
|
||||
secondsLeft, passwordNeedsHash);
|
||||
if (authState == NotLoggedIn || authState == UserIsBanned || authState == UsernameInvalid ||
|
||||
authState == UserIsInactive)
|
||||
return authState;
|
||||
|
|
|
|||
|
|
@ -62,6 +62,7 @@ public:
|
|||
AuthenticationResult loginUser(Server_ProtocolHandler *session,
|
||||
QString &name,
|
||||
const QString &password,
|
||||
bool passwordNeedsHash,
|
||||
QString &reason,
|
||||
int &secondsLeft,
|
||||
QString &clientid,
|
||||
|
|
|
|||
|
|
@ -18,7 +18,8 @@ public:
|
|||
const QString &password,
|
||||
const QString &clientId,
|
||||
QString &reasonStr,
|
||||
int &secondsLeft) = 0;
|
||||
int &secondsLeft,
|
||||
bool passwordNeedsHash) = 0;
|
||||
virtual bool checkUserIsBanned(const QString & /* ipAddress */,
|
||||
const QString & /* userName */,
|
||||
const QString & /* clientId */,
|
||||
|
|
@ -35,6 +36,10 @@ public:
|
|||
{
|
||||
return false;
|
||||
}
|
||||
virtual QString getUserSalt(const QString & /* user */)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
virtual QMap<QString, ServerInfo_User> getBuddyList(const QString & /* name */)
|
||||
{
|
||||
return QMap<QString, ServerInfo_User>();
|
||||
|
|
|
|||
|
|
@ -440,21 +440,32 @@ Response::ResponseCode Server_ProtocolHandler::cmdPing(const Command_Ping & /*cm
|
|||
|
||||
Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd, ResponseContainer &rc)
|
||||
{
|
||||
|
||||
QString userName = QString::fromStdString(cmd.user_name()).simplified();
|
||||
QString clientId = QString::fromStdString(cmd.clientid()).simplified();
|
||||
QString clientVersion = QString::fromStdString(cmd.clientver()).simplified();
|
||||
|
||||
if (userInfo != 0)
|
||||
QString password;
|
||||
bool needsHash = false;
|
||||
if (cmd.has_password()) {
|
||||
password = QString::fromStdString(cmd.password());
|
||||
needsHash = true;
|
||||
} else if (cmd.has_hashed_password()) {
|
||||
password = QString::fromStdString(cmd.hashed_password());
|
||||
} else {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
if (userInfo != 0) {
|
||||
return Response::RespContextError;
|
||||
}
|
||||
|
||||
// check client feature set against server feature set
|
||||
FeatureSet features;
|
||||
QMap<QString, bool> receivedClientFeatures;
|
||||
QMap<QString, bool> missingClientFeatures;
|
||||
|
||||
for (int i = 0; i < cmd.clientfeatures().size(); ++i)
|
||||
for (int i = 0; i < cmd.clientfeatures().size(); ++i) {
|
||||
receivedClientFeatures.insert(QString::fromStdString(cmd.clientfeatures(i)).simplified(), false);
|
||||
}
|
||||
|
||||
missingClientFeatures =
|
||||
features.identifyMissingFeatures(receivedClientFeatures, server->getServerRequiredFeatureList());
|
||||
|
|
@ -464,8 +475,9 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
|
|||
Response_Login *re = new Response_Login;
|
||||
re->set_denied_reason_str("Client upgrade required");
|
||||
QMap<QString, bool>::iterator i;
|
||||
for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i)
|
||||
for (i = missingClientFeatures.begin(); i != missingClientFeatures.end(); ++i) {
|
||||
re->add_missing_features(i.key().toStdString().c_str());
|
||||
}
|
||||
rc.setResponseExtension(re);
|
||||
return Response::RespClientUpdateRequired;
|
||||
}
|
||||
|
|
@ -474,8 +486,8 @@ Response::ResponseCode Server_ProtocolHandler::cmdLogin(const Command_Login &cmd
|
|||
QString reasonStr;
|
||||
int banSecondsLeft = 0;
|
||||
QString connectionType = getConnectionType();
|
||||
AuthenticationResult res = server->loginUser(this, userName, QString::fromStdString(cmd.password()), reasonStr,
|
||||
banSecondsLeft, clientId, clientVersion, connectionType);
|
||||
AuthenticationResult res = server->loginUser(this, userName, password, needsHash, reasonStr, banSecondsLeft,
|
||||
clientId, clientVersion, connectionType);
|
||||
switch (res) {
|
||||
case UserIsBanned: {
|
||||
Response_Login *re = new Response_Login;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue