mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-24 08:29:48 -05:00
CLI: Add support for okon in offline HIBP checks
* Closes #5447 * Add option `--okon <okon-cli path>` to trigger the use of the okon cli tool to process a database's entries. When using this option the `-H, --hibp` option must point to a post-processed okon file instead of the standard HIBP text file. * Updated documentation
This commit is contained in:
parent
e1c2537084
commit
7426693f1d
@ -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.
|
The same password generation options as documented for the generate command can be used when the *-g* option is set.
|
||||||
|
|
||||||
*analyze* [_options_] <__database__>::
|
*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_]::
|
*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.
|
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;
|
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).
|
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
|
=== Clip options
|
||||||
*-a*, *--attribute*::
|
*-a*, *--attribute*::
|
||||||
Copies the specified attribute to the clipboard.
|
Copies the specified attribute to the clipboard.
|
||||||
|
@ -34,11 +34,17 @@ const QCommandLineOption Analyze::HIBPDatabaseOption = QCommandLineOption(
|
|||||||
"https://haveibeenpwned.com/Passwords."),
|
"https://haveibeenpwned.com/Passwords."),
|
||||||
QObject::tr("FILENAME"));
|
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()
|
Analyze::Analyze()
|
||||||
{
|
{
|
||||||
name = QString("analyze");
|
name = QString("analyze");
|
||||||
description = QObject::tr("Analyze passwords for weaknesses and problems.");
|
description = QObject::tr("Analyze passwords for weaknesses and problems.");
|
||||||
options.append(Analyze::HIBPDatabaseOption);
|
options.append(Analyze::HIBPDatabaseOption);
|
||||||
|
options.append(Analyze::OkonOption);
|
||||||
}
|
}
|
||||||
|
|
||||||
int Analyze::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
|
int Analyze::executeWithDatabase(QSharedPointer<Database> database, QSharedPointer<QCommandLineParser> parser)
|
||||||
@ -46,20 +52,36 @@ int Analyze::executeWithDatabase(QSharedPointer<Database> database, QSharedPoint
|
|||||||
auto& out = Utils::STDOUT;
|
auto& out = Utils::STDOUT;
|
||||||
auto& err = Utils::STDERR;
|
auto& err = Utils::STDERR;
|
||||||
|
|
||||||
QString hibpDatabase = parser->value(Analyze::HIBPDatabaseOption);
|
QList<QPair<const Entry*, int>> findings;
|
||||||
QFile hibpFile(hibpDatabase);
|
QString error;
|
||||||
if (!hibpFile.open(QFile::ReadOnly)) {
|
|
||||||
err << QObject::tr("Failed to open HIBP file %1: %2").arg(hibpDatabase).arg(hibpFile.errorString()) << endl;
|
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;
|
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<QPair<const Entry*, int>> findings;
|
if (!HibpOffline::okonReport(database, okon, hibpDatabase, findings, &error)) {
|
||||||
QString error;
|
err << error << endl;
|
||||||
if (!HibpOffline::report(database, hibpFile, findings, &error)) {
|
return EXIT_FAILURE;
|
||||||
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) {
|
for (auto& finding : findings) {
|
||||||
@ -76,5 +98,9 @@ void Analyze::printHibpFinding(const Entry* entry, int count, QTextStream& out)
|
|||||||
path.prepend("/").prepend(g->name());
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ public:
|
|||||||
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) override;
|
int executeWithDatabase(QSharedPointer<Database> db, QSharedPointer<QCommandLineParser> parser) override;
|
||||||
|
|
||||||
static const QCommandLineOption HIBPDatabaseOption;
|
static const QCommandLineOption HIBPDatabaseOption;
|
||||||
|
static const QCommandLineOption OkonOption;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void printHibpFinding(const Entry* entry, int count, QTextStream& out);
|
void printHibpFinding(const Entry* entry, int count, QTextStream& out);
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include <QCryptographicHash>
|
#include <QCryptographicHash>
|
||||||
#include <QMultiHash>
|
#include <QMultiHash>
|
||||||
|
#include <QProcess>
|
||||||
|
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
@ -106,4 +107,45 @@ namespace HibpOffline
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool okonReport(QSharedPointer<Database> db,
|
||||||
|
const QString& okon,
|
||||||
|
const QString& okonDatabase,
|
||||||
|
QList<QPair<const Entry*, int>>& 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
|
} // namespace HibpOffline
|
||||||
|
@ -31,6 +31,12 @@ namespace HibpOffline
|
|||||||
QIODevice& hibpInput,
|
QIODevice& hibpInput,
|
||||||
QList<QPair<const Entry*, int>>& findings,
|
QList<QPair<const Entry*, int>>& findings,
|
||||||
QString* error);
|
QString* error);
|
||||||
}
|
|
||||||
|
bool okonReport(QSharedPointer<Database> db,
|
||||||
|
const QString& okon,
|
||||||
|
const QString& okonDatabase,
|
||||||
|
QList<QPair<const Entry*, int>>& findings,
|
||||||
|
QString* error);
|
||||||
|
} // namespace HibpOffline
|
||||||
|
|
||||||
#endif // KEEPASSXC_HIBPOFFLINE_H
|
#endif // KEEPASSXC_HIBPOFFLINE_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user