diff --git a/docs/man/keepassxc-cli.1.adoc b/docs/man/keepassxc-cli.1.adoc index 0efddfe5..1abb657e 100644 --- a/docs/man/keepassxc-cli.1.adoc +++ b/docs/man/keepassxc-cli.1.adoc @@ -37,7 +37,7 @@ It provides the ability to query and modify the entries of a KeePass database, d The same password generation options as documented for the generate command can be used when the *-g* option is set. *analyze* [_options_] <__database__>:: - Analyzes passwords in a database for weaknesses. + Analyzes passwords in a database for weaknesses using offline HIBP SHA-1 hash lookup. *clip* [_options_] <__database__> <__entry__> [_timeout_]:: Copies an attribute or the current TOTP (if the *-t* option is specified) of a database entry to the clipboard. @@ -199,6 +199,10 @@ The same password generation options as documented for the generate command can Such files are available from https://haveibeenpwned.com/Passwords; note that they are large, and so this operation typically takes some time (minutes up to an hour or so). +*--okon* <__okon-cli path__>:: + Use the specified okon-cli program to perform offline breach checks. You can obtain okon-cli from https://github.com/stryku/okon. + When using this option, *-H, --hibp* must point to a post-processed okon file (e.g. file.okon). + === Clip options *-a*, *--attribute*:: Copies the specified attribute to the clipboard. diff --git a/src/cli/Analyze.cpp b/src/cli/Analyze.cpp index 64eac9e7..11bb824e 100644 --- a/src/cli/Analyze.cpp +++ b/src/cli/Analyze.cpp @@ -34,11 +34,17 @@ const QCommandLineOption Analyze::HIBPDatabaseOption = QCommandLineOption( "https://haveibeenpwned.com/Passwords."), QObject::tr("FILENAME")); +const QCommandLineOption Analyze::OkonOption = + QCommandLineOption("okon", + QObject::tr("Path to okon-cli to search a formatted HIBP file"), + QObject::tr("okon-cli")); + Analyze::Analyze() { name = QString("analyze"); description = QObject::tr("Analyze passwords for weaknesses and problems."); options.append(Analyze::HIBPDatabaseOption); + options.append(Analyze::OkonOption); } int Analyze::executeWithDatabase(QSharedPointer database, QSharedPointer parser) @@ -46,20 +52,36 @@ int Analyze::executeWithDatabase(QSharedPointer database, QSharedPoint auto& out = Utils::STDOUT; auto& err = Utils::STDERR; - QString hibpDatabase = parser->value(Analyze::HIBPDatabaseOption); - QFile hibpFile(hibpDatabase); - if (!hibpFile.open(QFile::ReadOnly)) { - err << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) << endl; + QList> findings; + QString error; + + auto hibpDatabase = parser->value(Analyze::HIBPDatabaseOption); + if (!QFile::exists(hibpDatabase) || hibpDatabase.isEmpty()) { + err << QObject::tr("Cannot find HIBP file: %1").arg(hibpDatabase); return EXIT_FAILURE; } - out << QObject::tr("Evaluating database entries against HIBP file, this will take a while...") << endl; + auto okon = parser->value(Analyze::OkonOption); + if (!okon.isEmpty()) { + out << QObject::tr("Evaluating database entries using okon...") << endl; - QList> findings; - QString error; - if (!HibpOffline::report(database, hibpFile, findings, &error)) { - err << error << endl; - return EXIT_FAILURE; + if (!HibpOffline::okonReport(database, okon, hibpDatabase, findings, &error)) { + err << error << endl; + return EXIT_FAILURE; + } + } else { + QFile hibpFile(hibpDatabase); + if (!hibpFile.open(QFile::ReadOnly)) { + err << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) << endl; + return EXIT_FAILURE; + } + + out << QObject::tr("Evaluating database entries against HIBP file, this will take a while...") << endl; + + if (!HibpOffline::report(database, hibpFile, findings, &error)) { + err << error << endl; + return EXIT_FAILURE; + } } for (auto& finding : findings) { @@ -76,5 +98,9 @@ void Analyze::printHibpFinding(const Entry* entry, int count, QTextStream& out) path.prepend("/").prepend(g->name()); } - out << QObject::tr("Password for '%1' has been leaked %2 time(s)!", "", count).arg(path).arg(count) << endl; + if (count > 0) { + out << QObject::tr("Password for '%1' has been leaked %2 time(s)!", "", count).arg(path).arg(count) << endl; + } else { + out << QObject::tr("Password for '%1' has been leaked!", "", count).arg(path) << endl; + } } diff --git a/src/cli/Analyze.h b/src/cli/Analyze.h index fbd3dff3..a616093a 100644 --- a/src/cli/Analyze.h +++ b/src/cli/Analyze.h @@ -27,6 +27,7 @@ public: int executeWithDatabase(QSharedPointer db, QSharedPointer parser) override; static const QCommandLineOption HIBPDatabaseOption; + static const QCommandLineOption OkonOption; private: void printHibpFinding(const Entry* entry, int count, QTextStream& out); diff --git a/src/core/HibpOffline.cpp b/src/core/HibpOffline.cpp index ebc5d9a4..4b48c452 100644 --- a/src/core/HibpOffline.cpp +++ b/src/core/HibpOffline.cpp @@ -19,6 +19,7 @@ #include #include +#include #include "core/Database.h" #include "core/Group.h" @@ -106,4 +107,45 @@ namespace HibpOffline } } } + + bool okonReport(QSharedPointer db, + const QString& okon, + const QString& okonDatabase, + QList>& findings, + QString* error) + { + if (!okonDatabase.endsWith(".okon")) { + *error = QObject::tr("To use okon you must provide a post-processed file (e.g. file.okon)"); + return false; + } + + QProcess okonProcess; + + for (const auto* entry : db->rootGroup()->entriesRecursive()) { + if (!entry->isRecycled()) { + const auto sha1 = QCryptographicHash::hash(entry->password().toUtf8(), QCryptographicHash::Sha1); + okonProcess.start(okon, {"--path", okonDatabase, "--hash", QString::fromLatin1(sha1.toHex())}); + if (!okonProcess.waitForStarted()) { + *error = QObject::tr("Could not start okon process: %1").arg(okon); + return false; + } + + if (!okonProcess.waitForFinished()) { + *error = QObject::tr("Error: okon process did not finish"); + return false; + } + + switch (okonProcess.exitCode()) { + case 1: + findings.append({entry, -1}); + break; + case 2: + *error = QObject::tr("Failed to load okon processed database: %1").arg(okonDatabase); + return false; + } + } + } + + return true; + } } // namespace HibpOffline diff --git a/src/core/HibpOffline.h b/src/core/HibpOffline.h index 3ff8b910..4548f58e 100644 --- a/src/core/HibpOffline.h +++ b/src/core/HibpOffline.h @@ -31,6 +31,12 @@ namespace HibpOffline QIODevice& hibpInput, QList>& findings, QString* error); -} + + bool okonReport(QSharedPointer db, + const QString& okon, + const QString& okonDatabase, + QList>& findings, + QString* error); +} // namespace HibpOffline #endif // KEEPASSXC_HIBPOFFLINE_H