diff options
Diffstat (limited to 'tests/TestOpVaultReader.cpp')
-rw-r--r-- | tests/TestOpVaultReader.cpp | 276 |
1 files changed, 82 insertions, 194 deletions
diff --git a/tests/TestOpVaultReader.cpp b/tests/TestOpVaultReader.cpp index 15f30f2c9..94b5c35f0 100644 --- a/tests/TestOpVaultReader.cpp +++ b/tests/TestOpVaultReader.cpp @@ -24,6 +24,7 @@ #include "core/Tools.h" #include "crypto/Crypto.h" #include "format/OpVaultReader.h" +#include "totp/totp.h" #include <QJsonArray> #include <QJsonDocument> @@ -36,112 +37,30 @@ QTEST_GUILESS_MAIN(TestOpVaultReader) -QPair<QString, QString>* split1PTextExportKV(QByteArray& line) -{ - const auto eq = line.indexOf('='); - if (-1 == eq) { - qWarning() << "Bogus key=value pair: <<" << line << ">>"; - return nullptr; - } - auto k = QString::fromUtf8(line.mid(0, eq)); - const auto start = eq + 1; - auto v = QString::fromUtf8(line.mid(start), (line.size() - 1) - start); - return new QPair<QString, QString>(k, v); -} - -QSharedPointer<QJsonArray> read1PasswordTextExport(QFile& f) -{ - if (!f.open(QIODevice::ReadOnly)) { - qCritical("Unable to open your text export file for reading"); - return {}; - } - - auto result = QSharedPointer<QJsonArray>::create(); - QJsonObject current; - - while (!f.atEnd()) { - auto line = f.readLine(1024); - - if (line.size() == 1 and line[0] == '\n') { - if (!current.isEmpty()) { - result->append(current); - } - current = QJsonObject(); - continue; - } - const auto kv = split1PTextExportKV(line); - if (kv == nullptr) { - break; - } - QString k = kv->first; - - const auto multiLine1 = line.indexOf("=\"\""); - const auto multiLine2 = line.indexOf("=\""); - const auto isML1 = -1 != multiLine1; - const auto isML2 = -1 != multiLine2; - if (isML1 or isML2) { - QStringList lines; - const int skipEQ = isML1 ? (multiLine1 + 3) : (multiLine2 + 2); - lines.append(QString::fromUtf8(line.mid(skipEQ))); - while (!f.atEnd()) { - line = f.readLine(1024); - const auto endMarker = line.indexOf(isML1 ? "\"\"\n" : "\"\n"); - if (-1 != endMarker) { - line[endMarker] = '\n'; - lines.append(QString::fromUtf8(line.mid(0, endMarker))); - break; - } else { - lines.append(QString::fromUtf8(line)); - } - } - auto v = lines.join(""); - current[k] = v; - } else { - current[k] = kv->second; - } - delete kv; - } - if (!current.isEmpty()) { - result->append(current); - } - f.close(); - - return result; -} - void TestOpVaultReader::initTestCase() { QVERIFY(Crypto::init()); - // https://cache.agilebits.com/security-kb/freddy-2013-12-04.tar.gz - m_opVaultPath = QString("%1/%2").arg(KEEPASSX_TEST_DATA_DIR, "/freddy-2013-12-04.opvault"); - m_opVaultTextExportPath = QString(m_opVaultPath).replace(".opvault", ".opvault.txt"); - - m_password = "freddy"; - - QFile testData(m_opVaultTextExportPath); - auto data = read1PasswordTextExport(testData); - QVERIFY(data); - QCOMPARE(data->size(), 27); - - m_categoryMap.insert("001", "Login"); - m_categoryMap.insert("002", "Credit Card"); - m_categoryMap.insert("003", "Secure Note"); - m_categoryMap.insert("004", "Identity"); - m_categoryMap.insert("005", "Password"); - m_categoryMap.insert("099", "Tombstone"); - m_categoryMap.insert("100", "Software License"); - m_categoryMap.insert("101", "Bank Account"); - m_categoryMap.insert("102", "Database"); - m_categoryMap.insert("103", "Driver License"); - m_categoryMap.insert("104", "Outdoor License"); - m_categoryMap.insert("105", "Membership"); - m_categoryMap.insert("106", "Passport"); - m_categoryMap.insert("107", "Rewards"); - m_categoryMap.insert("108", "SSN"); - m_categoryMap.insert("109", "Router"); - m_categoryMap.insert("110", "Server"); - m_categoryMap.insert("111", "Email"); + m_opVaultPath = QStringLiteral("%1/%2").arg(KEEPASSX_TEST_DATA_DIR, QStringLiteral("/keepassxc.opvault")); + + m_categories = QStringList({QStringLiteral("Login"), + QStringLiteral("Credit Card"), + QStringLiteral("Secure Note"), + QStringLiteral("Identity"), + QStringLiteral("Password"), + QStringLiteral("Tombstone"), + QStringLiteral("Software License"), + QStringLiteral("Bank Account"), + QStringLiteral("Database"), + QStringLiteral("Driver License"), + QStringLiteral("Outdoor License"), + QStringLiteral("Membership"), + QStringLiteral("Passport"), + QStringLiteral("Rewards"), + QStringLiteral("SSN"), + QStringLiteral("Router"), + QStringLiteral("Server"), + QStringLiteral("Email")}); } void TestOpVaultReader::testReadIntoDatabase() @@ -149,100 +68,69 @@ void TestOpVaultReader::testReadIntoDatabase() QDir opVaultDir(m_opVaultPath); OpVaultReader reader; - QScopedPointer<Database> db(reader.readDatabase(opVaultDir, m_password)); - QVERIFY2(!reader.hasError(), qPrintable(reader.errorString())); + QScopedPointer<Database> db(reader.readDatabase(opVaultDir, "a")); QVERIFY(db); - QVERIFY(!db->children().isEmpty()); - - Group* rootGroup = db->rootGroup(); - QVERIFY(rootGroup); - - QFile testDataFile(m_opVaultTextExportPath); - auto testData = read1PasswordTextExport(testDataFile); - QVERIFY(testData); - - QMap<QUuid, QJsonObject> objectsByUuid; - QMap<QString, QList<QJsonObject>> objectsByCategory; - for (QJsonArray::const_iterator it = testData->constBegin(); it != testData->constEnd(); ++it) { - QJsonObject value = (*it).toObject(); - auto cat = value["category"].toString(); - QVERIFY2(m_categoryMap.contains(cat), qPrintable(QString("BOGUS, unmapped category \"%1\"").arg(cat))); - - auto catName = m_categoryMap[cat]; - if (!objectsByCategory.contains(catName)) { - QList<QJsonObject> theList; - objectsByCategory[catName] = theList; - } - objectsByCategory[catName].append(value); - - QUuid u = Tools::hexToUuid(value["uuid"].toString()); - objectsByUuid[u] = value; - } - QCOMPARE(objectsByUuid.size(), 27); - - for (QUuid u : objectsByUuid.keys()) { - QJsonObject o = objectsByUuid[u]; - const auto e = db->rootGroup()->findEntryByUuid(u); - QVERIFY2(e, qPrintable(QString("Expected to find UUID %1").arg(u.toString()))); - - auto jsonTitle = o["title"].toString(); - QCOMPARE(jsonTitle, e->title()); - } + QVERIFY2(!reader.hasError(), qPrintable(reader.errorString())); - for (QString& catName : m_categoryMap.values()) { - const auto g = rootGroup->findChildByName(catName); - QVERIFY2(g, qPrintable(QString("Expected to find Group(%1)").arg(catName))); - for (QJsonObject testEntry : objectsByCategory[catName]) { - auto uuidStr = testEntry["uuid"].toString(); - auto jsonTitle = testEntry["title"].toString(); - - QUuid u = Tools::hexToUuid(uuidStr); - const auto entry = g->findEntryByUuid(u); - QVERIFY2(entry, qPrintable(QString("Expected to find Group(%1).entry(%2)").arg(catName).arg(uuidStr))); - QCOMPARE(entry->title(), jsonTitle); + // Confirm specific entry details are valid + auto entry = db->rootGroup()->findEntryByPath("/Login/KeePassXC"); + QVERIFY(entry); + QCOMPARE(entry->title(), QStringLiteral("KeePassXC")); + QCOMPARE(entry->username(), QStringLiteral("keepassxc")); + QCOMPARE(entry->password(), QStringLiteral("opvault")); + QCOMPARE(entry->url(), QStringLiteral("https://www.keepassxc.org")); + QCOMPARE(entry->notes(), QStringLiteral("KeePassXC Account")); + // Check extra URL's + QCOMPARE(entry->attribute("KP2A_URL_1"), QStringLiteral("https://snapshot.keepassxc.org")); + // Check TOTP + QVERIFY(entry->hasTotp()); + // Check attachments + auto attachments = entry->attachments(); + QCOMPARE(attachments->keys().count(), 1); + QCOMPARE(*attachments->values().begin(), QByteArray("attachment")); + + // Confirm expired entries + entry = db->rootGroup()->findEntryByPath("/Login/Expired Login"); + QVERIFY(entry->isExpired()); + + // Confirm advanced attributes + entry = db->rootGroup()->findEntryByPath("/Credit Card/My Credit Card"); + QVERIFY(entry); + auto attr = entry->attributes(); + QCOMPARE(attr->value("cardholder"), QStringLiteral("Team KeePassXC")); + QVERIFY(!attr->value("validFrom").isEmpty()); + QCOMPARE(attr->value("details_pin"), QStringLiteral("1234")); + QVERIFY(attr->isProtected("details_pin")); + + // Confirm address fields + entry = db->rootGroup()->findEntryByPath("/Identity/Team KeePassXC"); + QVERIFY(entry); + attr = entry->attributes(); + QCOMPARE(attr->value("address_street"), QStringLiteral("123 Password Lane")); + + // Confirm complex passwords + entry = db->rootGroup()->findEntryByPath("/Password/Complex Password"); + QVERIFY(entry); + QCOMPARE(entry->password(), QStringLiteral("HfgcHjEL}iO}^3N!?*cv~O:9GJZQ0>oC")); + QVERIFY(entry->hasTotp()); + auto totpSettings = entry->totpSettings(); + QCOMPARE(totpSettings->digits, static_cast<unsigned int>(8)); + QCOMPARE(totpSettings->step, static_cast<unsigned int>(45)); + + // Confirm trashed entries are sent to the recycle bin + auto recycleBin = db->metadata()->recycleBin(); + QVERIFY(recycleBin); + QVERIFY(!recycleBin->isEmpty()); + QVERIFY(recycleBin->findEntryByPath("Trashed Password")); + + // Confirm created groups align with category names + for (const auto group : db->rootGroup()->children()) { + if (group == recycleBin) { + continue; } + QVERIFY2(m_categories.contains(group->name()), + qPrintable(QStringLiteral("Invalid group name: %1").arg(group->name()))); + // Confirm each group is not empty + QVERIFY2(!group->isEmpty(), qPrintable(QStringLiteral("Group %1 is empty").arg(group->name()))); } } - -void TestOpVaultReader::testKeyDerivation() -{ - OpVaultReader reader; - QDir opVaultDir(m_opVaultPath); - - // yes, the reader checks this too, but in our case best to fail early - QVERIFY(opVaultDir.exists()); - QVERIFY(opVaultDir.isReadable()); - - QDir defDir = QDir(opVaultDir); - defDir.cd("default"); - QFile profileJs(defDir.absoluteFilePath("profile.js")); - QVERIFY(profileJs.exists()); - - auto profileObj = reader.readAndAssertJsonFile(profileJs, "var profile=", ";"); - - QByteArray salt = QByteArray::fromBase64(profileObj["salt"].toString().toUtf8()); - unsigned long iter = profileObj["iterations"].toInt(); - const auto derived = reader.deriveKeysFromPassPhrase(salt, m_password, iter); - QVERIFY(derived); - QVERIFY(!derived->error); - - QByteArray encHex = derived->encrypt.toHex(); - QByteArray hmacHex = derived->hmac.toHex(); - delete derived; - - QCOMPARE(QString::fromUtf8(encHex), - QStringLiteral("63b075de858949559d4faa9d348bf10bdaa0e567ad943d7803f2291c9342aaaa")); - QCOMPARE(QString::fromUtf8(hmacHex), - QStringLiteral("ff3ab426ce55bf097b252b3f2df1c4ba4312a6960180844d7a625bc0ab40c35e")); -} - -void TestOpVaultReader::testBandEntry1() -{ - OpVaultReader reader; - QByteArray json(R"({"hello": "world"})"); - QJsonDocument doc = QJsonDocument::fromJson(json); - QJsonObject data; - QByteArray entryKey; - QByteArray entryHmacKey; - QVERIFY(!reader.decryptBandEntry(doc.object(), data, entryKey, entryHmacKey)); -} |