Compare commits

..

3 commits

Author SHA1 Message Date
Lukas Brübach
a469c4ef7c
[Security] Refuse KDF downgrade; trim profile name and warn on silent credential drop 2026-09-05 22:48:07 +02:00
Lukas Brübach
91fc6e78f1
[Security] Harden credential format acceptance and challenge nonce validation 2026-09-05 22:48:07 +02:00
Lukas Brübach
1baf286738
[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-05 22:48:07 +02:00
7 changed files with 3 additions and 50 deletions

View file

@ -176,12 +176,8 @@ jobs:
shell: bash
run: |
source .ci/docker.sh
args=()
[[ $GITHUB_REF == "refs/heads/master" ]] && args+=(--evict-ccache "$CCACHE_EVICTION_AGE")
args+=(--ccache "$CCACHE_SIZE")
args+=(--cmake-generator "$CMAKE_GENERATOR")
RUN --server --debug --test "${args[@]}"
RUN --server --debug --test --ccache "$CCACHE_SIZE" \
--cmake-generator "$CMAKE_GENERATOR"
- name: "Build release package"
id: build

View file

@ -12,13 +12,11 @@ TallyMenu::TallyMenu()
aTallyNone = createTallyAction(TallyType::None);
aTallySubtypes = createTallyAction(TallyType::Subtypes);
aTallyTotalPower = createTallyAction(TallyType::TotalPower);
aTallyTotalToughness = createTallyAction(TallyType::TotalToughness);
addAction(aTallyNone);
addSeparator();
addAction(aTallySubtypes);
addAction(aTallyTotalPower);
addAction(aTallyTotalToughness);
retranslateUi();
}
@ -56,5 +54,4 @@ void TallyMenu::retranslateUi()
aTallyNone->setText(tr("None"));
aTallySubtypes->setText(tr("Subtypes"));
aTallyTotalPower->setText(tr("Total Power"));
aTallyTotalToughness->setText(tr("Total Toughness"));
}

View file

@ -24,7 +24,6 @@ private:
QAction *aTallyNone = nullptr;
QAction *aTallySubtypes = nullptr;
QAction *aTallyTotalPower = nullptr;
QAction *aTallyTotalToughness = nullptr;
QAction *createTallyAction(TallyType tallyType);
};

View file

@ -34,31 +34,3 @@ QList<TallyRow> StatsTally::computeTotalPower(const QList<CardItem *> &cards)
QString name = QCoreApplication::translate("StatsTally", "Total Power");
return {TallyRow{name, QString::number(total)}};
}
static int sumToughness(const QList<CardItem *> &cards)
{
int total = 0;
for (auto card : cards) {
QVariantList parsed = CardItem::parsePT(card->getPT());
if (parsed.size() == 2) {
int toughness = parsed.at(1).toInt(); // toInt will default to 0 if it's not an int
total += qMax(toughness, 0);
}
}
return total;
}
QList<TallyRow> StatsTally::computeTotalToughness(const QList<CardItem *> &cards)
{
// don't bother if none of the cards have pt
bool hasPT =
std::any_of(cards.cbegin(), cards.cend(), [](const CardItem *card) { return !card->getPT().isEmpty(); });
if (!hasPT) {
return {};
}
int total = sumToughness(cards);
QString name = QCoreApplication::translate("StatsTally", "Total Toughness");
return {TallyRow{name, QString::number(total)}};
}

View file

@ -16,14 +16,6 @@ namespace StatsTally
*/
QList<TallyRow> computeTotalPower(const QList<CardItem *> &cards);
/**
* @brief Sums the toughness of all selected cards
*
* @param cards The list of selected card items to analyze.
* @return A single row containing the total, or an empty list if none of the cards have pt
*/
QList<TallyRow> computeTotalToughness(const QList<CardItem *> &cards);
} // namespace StatsTally
#endif // COCKATRICE_STATS_TALLY_H

View file

@ -21,8 +21,6 @@ QList<TallyRow> Tally::compute(const QList<CardItem *> &cards, const TallyType t
return SubtypeTally::countSubtypes(cards);
case TallyType::TotalPower:
return StatsTally::computeTotalPower(cards);
case TallyType::TotalToughness:
return StatsTally::computeTotalToughness(cards);
}
return {};
}

View file

@ -21,8 +21,7 @@ enum class TallyType
None,
Subtypes,
TotalPower,
TotalToughness,
MaxValue = TotalToughness // sentinel value
MaxValue = TotalPower // sentinel value
};
namespace Tally