Cockatrice/libcockatrice_settings/libcockatrice/settings/servers_settings.cpp
Lukas Brübach 7a3067d4a8 [Security] Add challenge-response auth with scrypt verifiers and stop storing plaintext passwords
Challenge-response authentication: the client derives a scrypt verifier
(RFC 7914, EVP_PBE_scrypt, N=32768, r=8, p=1) and authenticates with
HMAC-SHA256(key, nonce), so neither the password nor its hash is
transmitted. The stored format becomes
"$scrypt$<n>$<r>$<p>$<salt>$<verifier>" and Response_PasswordSalt
now carries the cost parameters. Strict servers only accept scrypt
verifiers; legacy accounts are migrated after a successful login.

Fix #344 for challenge-response servers: a saved profile stores the
derived verifier under the password key instead of the plaintext password.
The connect dialog loads it without revealing it, autoconnect passes it
through, the change-password dialog no longer prefills the old password
field with it, and the client only persists the verifier when
"Save password" is checked.

Took 3 minutes

Took 1 minute

Took 10 seconds

Took 7 minutes
2026-09-04 13:49:22 +02:00

348 lines
13 KiB
C++

#include "servers_settings.h"
#include <QDebug>
#include <utility>
ServersSettings::ServersSettings(const QString &settingPath, QObject *parent)
: SettingsManager(settingPath + "servers.ini", "server", QString(), parent)
{
}
void ServersSettings::setPreviousHostLogin(int previous)
{
setValue(previous, "previousHostLogin");
}
int ServersSettings::getPreviousHostLogin() const
{
QVariant previous = getValue("previousHostLogin");
return previous == QVariant() ? 1 : previous.toInt();
}
void ServersSettings::setPreviousHostList(QStringList list)
{
setValue(list, "previousHosts");
}
QStringList ServersSettings::getPreviousHostList() const
{
return getValue("previousHosts").toStringList();
}
void ServersSettings::setPrevioushostName(const QString &name)
{
setValue(name, "previousHostName");
}
QString ServersSettings::getSaveName(QString defaultname)
{
int index = getPrevioushostindex(getPrevioushostName());
QVariant saveName = getValue(QString("saveName%1").arg(index), "server", "server_details");
return saveName == QVariant() ? std::move(defaultname) : saveName.toString();
}
QString ServersSettings::getSite(QString defaultSite)
{
int index = getPrevioushostindex(getPrevioushostName());
QVariant site = getValue(QString("site%1").arg(index), "server", "server_details");
return site == QVariant() ? std::move(defaultSite) : site.toString();
}
QString ServersSettings::getPrevioushostName() const
{
QVariant value = getValue("previousHostName");
return value == QVariant() ? "Rooster Ranges" : value.toString();
}
int ServersSettings::getPrevioushostindex(const QString &saveName) const
{
int size = getValue("totalServers", "server", "server_details").toInt();
for (int i = 0; i <= size; ++i) {
if (saveName == getValue(QString("saveName%1").arg(i), "server", "server_details").toString()) {
return i;
}
}
return -1;
}
QString ServersSettings::getHostname(QString defaultHost) const
{
int index = getPrevioushostindex(getPrevioushostName());
QVariant hostname = getValue(QString("server%1").arg(index), "server", "server_details");
return hostname == QVariant() ? std::move(defaultHost) : hostname.toString();
}
QString ServersSettings::getPort(QString defaultPort) const
{
int index = getPrevioushostindex(getPrevioushostName());
QVariant port = getValue(QString("port%1").arg(index), "server", "server_details");
qCDebug(ServersSettingsLog) << "getPort() index = " << index << " port.val = " << port.toString();
return port == QVariant() ? std::move(defaultPort) : port.toString();
}
QString ServersSettings::getPlayerName(QString defaultName) const
{
int index = getPrevioushostindex(getPrevioushostName());
QVariant name = getValue(QString("username%1").arg(index), "server", "server_details");
qCDebug(ServersSettingsLog) << "getPlayerName() index = " << index << " name.val = " << name.toString();
return name == QVariant() ? std::move(defaultName) : name.toString();
}
QString ServersSettings::getPassword()
{
int index = getPrevioushostindex(getPrevioushostName());
if (getSavePassword()) {
return getValue(QString("password%1").arg(index), "server", "server_details").toString();
}
return QString();
}
bool ServersSettings::getSavePassword() const
{
int index = getPrevioushostindex(getPrevioushostName());
bool save = getValue(QString("savePassword%1").arg(index), "server", "server_details").toBool();
return save;
}
void ServersSettings::setAutoConnect(int autoconnect)
{
setValue(autoconnect, "autoConnect");
}
int ServersSettings::getAutoConnect() const
{
QVariant autoconnect = getValue("autoConnect");
return autoconnect == QVariant() ? 0 : autoconnect.toInt();
}
void ServersSettings::setFPHostName(QString hostname)
{
setValue(hostname, "fpHostName");
}
QString ServersSettings::getFPHostname(QString defaultHost) const
{
QVariant hostname = getValue("fpHostName");
return hostname == QVariant() ? std::move(defaultHost) : hostname.toString();
}
void ServersSettings::setFPPort(QString port)
{
setValue(port, "fpPort");
}
QString ServersSettings::getFPPort(QString defaultPort) const
{
QVariant port = getValue("fpPort");
return port == QVariant() ? std::move(defaultPort) : port.toString();
}
void ServersSettings::setFPPlayerName(QString playerName)
{
setValue(playerName, "fpPlayerName");
}
void ServersSettings::setServerPassword(const QString &saveName, const QString &password)
{
const int index = getPrevioushostindex(saveName);
if (index >= 0) {
setValue(password, QString("password%1").arg(index), "server", "server_details");
}
}
QString ServersSettings::getFPPlayerName(QString defaultName) const
{
QVariant name = getValue("fpPlayerName");
return name == QVariant() ? std::move(defaultName) : name.toString();
}
void ServersSettings::setClearDebugLogStatus(bool abIsChecked)
{
setValue(abIsChecked, "saveDebugLog");
}
bool ServersSettings::getClearDebugLogStatus(bool abDefaultValue) const
{
QVariant cbFlushLog = getValue("saveDebugLog");
return cbFlushLog == QVariant() ? abDefaultValue : cbFlushLog.toBool();
}
void ServersSettings::addNewServer(const QString &saveName,
const QString &serv,
const QString &port,
const QString &username,
const QString &password,
bool savePassword,
const QString &site)
{
if (updateExistingServer(saveName, serv, port, username, password, savePassword, site)) {
return;
}
int index = getValue("totalServers", "server", "server_details").toInt() + 1;
setValue(saveName, QString("saveName%1").arg(index), "server", "server_details");
setValue(serv, QString("server%1").arg(index), "server", "server_details");
setValue(port, QString("port%1").arg(index), "server", "server_details");
setValue(username, QString("username%1").arg(index), "server", "server_details");
setValue(savePassword, QString("savePassword%1").arg(index), "server", "server_details");
setValue(index, "totalServers", "server", "server_details");
setValue(password, QString("password%1").arg(index), "server", "server_details");
setValue(site, QString("site%1").arg(index), "server", "server_details");
}
void ServersSettings::removeServer(QString servAddr)
{
int size = getValue("totalServers", "server", "server_details").toInt();
bool found = false;
for (int i = 0; i <= size; ++i) {
if (!found) {
// find entry and overwrite it
if (servAddr == getValue(QString("server%1").arg(i), "server", "server_details").toString()) {
found = true;
}
} else {
// move all other entries after it one back, overwriting the previous one
int previous = i - 1; // we delete only one entry
setValue(getValue(QString("server%1").arg(i), "server", "server_details"),
QString("server%1").arg(previous), "server", "server_details");
setValue(getValue(QString("port%1").arg(i), "server", "server_details"), QString("port%1").arg(previous),
"server", "server_details");
setValue(getValue(QString("username%1").arg(i), "server", "server_details"),
QString("username%1").arg(previous), "server", "server_details");
setValue(getValue(QString("savePassword%1").arg(i), "server", "server_details"),
QString("savePassword%1").arg(previous), "server", "server_details");
setValue(getValue(QString("password%1").arg(i), "server", "server_details"),
QString("password%1").arg(previous), "server", "server_details");
setValue(getValue(QString("saveName%1").arg(i), "server", "server_details"),
QString("saveName%1").arg(previous), "server", "server_details");
setValue(getValue(QString("site%1").arg(i), "server", "server_details"), QString("site%1").arg(previous),
"server", "server_details");
}
}
// if we have deleted an entry, adjust the total
if (found) {
setValue(size - 1, "totalServers", "server", "server_details");
// delete last value
deleteValue(QString("server%1").arg(size), "server", "server_details");
deleteValue(QString("port%1").arg(size), "server", "server_details");
deleteValue(QString("username%1").arg(size), "server", "server_details");
deleteValue(QString("savePassword%1").arg(size), "server", "server_details");
deleteValue(QString("password%1").arg(size), "server", "server_details");
deleteValue(QString("saveName%1").arg(size), "server", "server_details");
deleteValue(QString("site%1").arg(size), "server", "server_details");
}
}
/**
* Will only update fields with new values, ignores empty values
*/
bool ServersSettings::updateExistingServerWithoutLoss(QString saveName, QString serv, QString port, QString site)
{
int size = getValue("totalServers", "server", "server_details").toInt();
for (int i = 0; i <= size; ++i) {
if (serv == getValue(QString("server%1").arg(i), "server", "server_details").toString()) {
if (!port.isEmpty()) {
setValue(port, QString("port%1").arg(i), "server", "server_details");
}
if (!site.isEmpty()) {
setValue(site, QString("site%1").arg(i), "server", "server_details");
}
setValue(saveName, QString("saveName%1").arg(i), "server", "server_details");
return true;
}
}
return false;
}
bool ServersSettings::updateExistingServer(QString saveName,
QString serv,
QString port,
QString username,
QString password,
bool savePassword,
QString site)
{
int size = getValue("totalServers", "server", "server_details").toInt();
for (int i = 0; i <= size; ++i) {
if (serv == getValue(QString("server%1").arg(i), "server", "server_details").toString()) {
setValue(port, QString("port%1").arg(i), "server", "server_details");
if (!username.isEmpty()) {
setValue(username, QString("username%1").arg(i), "server", "server_details");
}
if (savePassword && !password.isEmpty()) {
setValue(password, QString("password%1").arg(i), "server", "server_details");
} else {
setValue(QString(), QString("password%1").arg(i), "server", "server_details");
}
if (!site.isEmpty()) {
setValue(site, QString("site%1").arg(i), "server", "server_details");
}
setValue(savePassword, QString("savePassword%1").arg(i), "server", "server_details");
setValue(saveName, QString("saveName%1").arg(i), "server", "server_details");
return true;
}
}
return false;
}
int ServersSettings::findServerIndex(const QString &host, const QString &port) const
{
int size = getValue("totalServers", "server", "server_details").toInt();
for (int i = 0; i <= size; ++i) {
QString storedHost = getValue(QString("server%1").arg(i), "server", "server_details").toString();
QString storedPort = getValue(QString("port%1").arg(i), "server", "server_details").toString();
if (storedHost == host && storedPort == port) {
return i;
}
}
return -1;
}
bool ServersSettings::hasUsername(const QString &host, const QString &port) const
{
int index = findServerIndex(host, port);
if (index < 0) {
return false;
}
QString user = getValue(QString("username%1").arg(index), "server", "server_details").toString();
return !user.isEmpty();
}
bool ServersSettings::hasCredentials(const QString &host, const QString &port) const
{
int index = findServerIndex(host, port);
if (index < 0) {
return false;
}
bool save = getValue(QString("savePassword%1").arg(index), "server", "server_details").toBool();
QString password = getValue(QString("password%1").arg(index), "server", "server_details").toString();
return save && !password.isEmpty();
}
bool ServersSettings::hasLoginData(const QString &host, const QString &port) const
{
return hasUsername(host, port) && hasCredentials(host, port);
}