Fix TestCli and TestGui entropy tests across platforms

* zxcvbn wordlists can vary between platforms depending on packager adjustments (ie, zxcvbn-c vs Ubuntu Focal)
This commit is contained in:
Jonathan White 2024-03-31 07:12:01 -04:00
parent 3a86381df8
commit caece405fb
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
2 changed files with 39 additions and 64 deletions

View File

@ -26,6 +26,7 @@
#include "crypto/Crypto.h" #include "crypto/Crypto.h"
#include "keys/FileKey.h" #include "keys/FileKey.h"
#include "keys/drivers/YubiKey.h" #include "keys/drivers/YubiKey.h"
#include "zxcvbn/zxcvbn.h"
#include "cli/Add.h" #include "cli/Add.h"
#include "cli/AddGroup.h" #include "cli/AddGroup.h"
@ -1186,90 +1187,72 @@ void TestCli::testEdit()
void TestCli::testEstimate_data() void TestCli::testEstimate_data()
{ {
// clang-format off
QTest::addColumn<QString>("input"); QTest::addColumn<QString>("input");
QTest::addColumn<QString>("length");
QTest::addColumn<QString>("entropy");
QTest::addColumn<QString>("log10");
QTest::addColumn<QStringList>("searchStrings"); QTest::addColumn<QStringList>("searchStrings");
QTest::newRow("Dictionary") << "password" QTest::newRow("Dictionary")
<< "8" << "password"
<< "1.0" << QStringList{"Type: Dictionary", "\tpassword"};
<< "0.3" << QStringList{"Type: Dictionary", "\tpassword"};
QTest::newRow("Spatial") << "zxcv" QTest::newRow("Spatial")
<< "4" << "sdfg"
<< "10.3" << QStringList{"Type: Spatial", "\tsdfg"};
<< "3.1" << QStringList{"Type: Spatial", "\tzxcv"};
QTest::newRow("Spatial(Rep)") << "sdfgsdfg" QTest::newRow("Spatial(Rep)")
<< "8" << "sdfgsdfg"
<< "11.3" << QStringList{"Type: Spatial(Rep)", "\tsdfgsdfg"};
<< "3.4" << QStringList{"Type: Spatial(Rep)", "\tsdfgsdfg"};
QTest::newRow("Dictionary / Sequence") QTest::newRow("Dictionary / Sequence")
<< "password123" << "password123"
<< "11" << QStringList{"Type: Dictionary", "Type: Sequence", "\tpassword", "\t123"};
<< "4.5"
<< "1.3" << QStringList{"Type: Dictionary", "Type: Sequence", "\tpassword", "\t123"};
QTest::newRow("Dict+Leet") << "p455w0rd" QTest::newRow("Dict+Leet")
<< "8" << "p455w0rd"
<< "2.5" << QStringList{"Type: Dict+Leet", "\tp455w0rd"};
<< "0.7" << QStringList{"Type: Dict+Leet", "\tp455w0rd"};
QTest::newRow("Dictionary(Rep)") << "hellohello" QTest::newRow("Dictionary(Rep)")
<< "10" << "hellohello"
<< "7.3" << QStringList{"Type: Dictionary(Rep)", "\thellohello"};
<< "2.2" << QStringList{"Type: Dictionary(Rep)", "\thellohello"};
QTest::newRow("Sequence(Rep) / Dictionary") QTest::newRow("Sequence(Rep) / Dictionary")
<< "456456foobar" << "456456foobar"
<< "12" << QStringList{"Type: Sequence(Rep)", "Type: Dictionary", "\t456456", "\tfoobar"};
<< "16.7"
<< "5.0" << QStringList{"Type: Sequence(Rep)", "Type: Dictionary", "\t456456", "\tfoobar"};
QTest::newRow("Bruteforce(Rep) / Bruteforce") QTest::newRow("Bruteforce(Rep) / Bruteforce")
<< "xzxzy" << "xzxzy"
<< "5" << QStringList{"Type: Bruteforce(Rep)", "Type: Bruteforce", "\txzxz", "\ty"};
<< "16.1"
<< "4.8" << QStringList{"Type: Bruteforce(Rep)", "Type: Bruteforce", "\txzxz", "\ty"};
QTest::newRow("Dictionary / Date(Rep)") QTest::newRow("Dictionary / Date(Rep)")
<< "pass20182018" << "pass20182018"
<< "12" << QStringList{"Type: Dictionary", "Type: Date(Rep)", "\tpass", "\t20182018"};
<< "15.1"
<< "4.56" << QStringList{"Type: Dictionary", "Type: Date(Rep)", "\tpass", "\t20182018"};
QTest::newRow("Dictionary / Date / Bruteforce") QTest::newRow("Dictionary / Date / Bruteforce")
<< "mypass2018-2" << "mypass2018-2"
<< "12" << QStringList{"Type: Dictionary", "Type: Date", "Type: Bruteforce", "\tmypass", "\t2018", "\t-2"};
<< "32.9"
<< "9.9" << QStringList{"Type: Dictionary", "Type: Date", "Type: Bruteforce", "\tmypass", "\t2018", "\t-2"};
QTest::newRow("Strong Password") << "E*!%.Qw{t.X,&bafw)\"Q!ah$%;U/" QTest::newRow("Strong Password")
<< "28" << "E*!%.Qw{t.X,&bafw)\"Q!ah$%;U/"
<< "165.7" << QStringList{"Type: Bruteforce", "\tE*"};
<< "49.8" << QStringList{"Type: Bruteforce", "\tE*"};
// TODO: detect passphrases and adjust entropy calculation accordingly (issue #2347) // TODO: detect passphrases and adjust entropy calculation accordingly (issue #2347)
QTest::newRow("Strong Passphrase") QTest::newRow("Strong Passphrase")
<< "squint wooing resupply dangle isolation axis headsman" << "squint wooing resupply dangle isolation axis headsman"
<< "53" << QStringList{"Type: Dictionary", "Type: Bruteforce", "Multi-word extra bits 22.0", "\tsquint", "\t ", "\twooing"};
<< "151.2" // clang-format on
<< "45.5"
<< QStringList{
"Type: Dictionary", "Type: Bruteforce", "Multi-word extra bits 22.0", "\tsquint", "\t ", "\twooing"};
} }
void TestCli::testEstimate() void TestCli::testEstimate()
{ {
QFETCH(QString, input); QFETCH(QString, input);
QFETCH(QString, length);
QFETCH(QString, entropy);
QFETCH(QString, log10);
QFETCH(QStringList, searchStrings); QFETCH(QStringList, searchStrings);
// Calculate expected values since zxcvbn output can vary by platform if different wordlists are used
const auto e = ZxcvbnMatch(input.toUtf8(), nullptr, nullptr);
auto length = QString::number(input.length());
auto entropy = QString("%1").arg(e, 0, 'f', 3);
auto log10 = QString("%1").arg(e * 0.301029996, 0, 'f', 3);
Estimate estimateCmd; Estimate estimateCmd;
QVERIFY(!estimateCmd.name.isEmpty()); QVERIFY(!estimateCmd.name.isEmpty());
QVERIFY(estimateCmd.getDescriptionLine().contains(estimateCmd.name)); QVERIFY(estimateCmd.getDescriptionLine().contains(estimateCmd.name));

View File

@ -31,6 +31,7 @@
#include <QToolBar> #include <QToolBar>
#include "config-keepassx-tests.h" #include "config-keepassx-tests.h"
#include "core/PasswordHealth.h"
#include "core/Tools.h" #include "core/Tools.h"
#include "crypto/Crypto.h" #include "crypto/Crypto.h"
#include "gui/ApplicationSettingsWidget.h" #include "gui/ApplicationSettingsWidget.h"
@ -682,43 +683,33 @@ void TestGui::testAddEntry()
void TestGui::testPasswordEntryEntropy_data() void TestGui::testPasswordEntryEntropy_data()
{ {
QTest::addColumn<QString>("password"); QTest::addColumn<QString>("password");
QTest::addColumn<QString>("expectedEntropyLabel");
QTest::addColumn<QString>("expectedStrengthLabel"); QTest::addColumn<QString>("expectedStrengthLabel");
QTest::newRow("Empty password") << "" QTest::newRow("Empty password") << ""
<< "Entropy: 0.00 bit"
<< "Password Quality: Poor"; << "Password Quality: Poor";
QTest::newRow("Well-known password") << "hello" QTest::newRow("Well-known password") << "hello"
<< "Entropy: 6.38 bit"
<< "Password Quality: Poor"; << "Password Quality: Poor";
QTest::newRow("Password composed of well-known words.") << "helloworld" QTest::newRow("Password composed of well-known words.") << "helloworld"
<< "Entropy: 13.10 bit"
<< "Password Quality: Poor"; << "Password Quality: Poor";
QTest::newRow("Password composed of well-known words with number.") << "password1" QTest::newRow("Password composed of well-known words with number.") << "password1"
<< "Entropy: 4.00 bit"
<< "Password Quality: Poor"; << "Password Quality: Poor";
QTest::newRow("Password out of small character space.") << "D0g.................." QTest::newRow("Password out of small character space.") << "D0g.................."
<< "Entropy: 19.02 bit"
<< "Password Quality: Poor"; << "Password Quality: Poor";
QTest::newRow("XKCD, easy substitutions.") << "Tr0ub4dour&3" QTest::newRow("XKCD, easy substitutions.") << "Tr0ub4dour&3"
<< "Entropy: 30.87 bit"
<< "Password Quality: Poor"; << "Password Quality: Poor";
QTest::newRow("XKCD, word generator.") << "correcthorsebatterystaple" QTest::newRow("XKCD, word generator.") << "correcthorsebatterystaple"
<< "Entropy: 47.98 bit"
<< "Password Quality: Weak"; << "Password Quality: Weak";
QTest::newRow("Random characters, medium length.") << "YQC3kbXbjC652dTDH" QTest::newRow("Random characters, medium length.") << "YQC3kbXbjC652dTDH"
<< "Entropy: 95.83 bit"
<< "Password Quality: Good"; << "Password Quality: Good";
QTest::newRow("Random characters, long.") << "Bs5ZFfthWzR8DGFEjaCM6bGqhmCT4km" QTest::newRow("Random characters, long.") << "Bs5ZFfthWzR8DGFEjaCM6bGqhmCT4km"
<< "Entropy: 174.59 bit"
<< "Password Quality: Excellent"; << "Password Quality: Excellent";
QTest::newRow("Long password using Zxcvbn chunk estimation") QTest::newRow("Long password using Zxcvbn chunk estimation")
@ -726,7 +717,6 @@ void TestGui::testPasswordEntryEntropy_data()
"ashamed-anatomy-daybed-jam-swear-strudel-neatness-stalemate-unbundle-flavored-relation-emergency-underrate-" "ashamed-anatomy-daybed-jam-swear-strudel-neatness-stalemate-unbundle-flavored-relation-emergency-underrate-"
"registry-getting-award-unveiled-unshaken-stagnate-cartridge-magnitude-ointment-hardener-enforced-scrubbed-" "registry-getting-award-unveiled-unshaken-stagnate-cartridge-magnitude-ointment-hardener-enforced-scrubbed-"
"radial-fiddling-envelope-unpaved-moisture-unused-crawlers-quartered-crushed-kangaroo-tiptop-doily" "radial-fiddling-envelope-unpaved-moisture-unused-crawlers-quartered-crushed-kangaroo-tiptop-doily"
<< "Entropy: 1205.85 bit"
<< "Password Quality: Excellent"; << "Password Quality: Excellent";
QTest::newRow("Longer password above Zxcvbn threshold") QTest::newRow("Longer password above Zxcvbn threshold")
@ -744,7 +734,6 @@ void TestGui::testPasswordEntryEntropy_data()
"disparate-decorated-washroom-threefold-muzzle-buckwheat-kerosene-swell-why-reprocess-correct-shady-" "disparate-decorated-washroom-threefold-muzzle-buckwheat-kerosene-swell-why-reprocess-correct-shady-"
"impatient-slit-banshee-scrubbed-dreadful-unlocking-urologist-hurried-citable-fragment-septic-lapped-" "impatient-slit-banshee-scrubbed-dreadful-unlocking-urologist-hurried-citable-fragment-septic-lapped-"
"prankish-phantom-unpaved-moisture-unused-crawlers-quartered-crushed-kangaroo-lapel-emporium-renounce" "prankish-phantom-unpaved-moisture-unused-crawlers-quartered-crushed-kangaroo-lapel-emporium-renounce"
<< "Entropy: 4210.27 bit"
<< "Password Quality: Excellent"; << "Password Quality: Excellent";
} }
@ -793,11 +782,14 @@ void TestGui::testPasswordEntryEntropy()
auto* strengthLabel = pwGeneratorWidget->findChild<QLabel*>("strengthLabel"); auto* strengthLabel = pwGeneratorWidget->findChild<QLabel*>("strengthLabel");
QFETCH(QString, password); QFETCH(QString, password);
QFETCH(QString, expectedEntropyLabel);
QFETCH(QString, expectedStrengthLabel); QFETCH(QString, expectedStrengthLabel);
// Dynamically calculate entropy due to variances with zxcvbn wordlists
PasswordHealth health(password);
auto expectedEntropy = QString("Entropy: %1 bit").arg(QString::number(health.entropy(), 'f', 2));
generatedPassword->setText(password); generatedPassword->setText(password);
QCOMPARE(entropyLabel->text(), expectedEntropyLabel); QCOMPARE(entropyLabel->text(), expectedEntropy);
QCOMPARE(strengthLabel->text(), expectedStrengthLabel); QCOMPARE(strengthLabel->text(), expectedStrengthLabel);
QTest::mouseClick(generatedPassword, Qt::LeftButton); QTest::mouseClick(generatedPassword, Qt::LeftButton);