Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'tests/TestOpVaultReader.cpp')
-rw-r--r--tests/TestOpVaultReader.cpp250
1 files changed, 250 insertions, 0 deletions
diff --git a/tests/TestOpVaultReader.cpp b/tests/TestOpVaultReader.cpp
new file mode 100644
index 000000000..af332fd32
--- /dev/null
+++ b/tests/TestOpVaultReader.cpp
@@ -0,0 +1,250 @@
+/*
+ * Copyright (C) 2019 KeePassXC Team <team@keepassxc.org>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 2 or (at your option)
+ * version 3 of the License.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "TestOpVaultReader.h"
+
+#include "config-keepassx-tests.h"
+#include "core/Database.h"
+#include "core/Group.h"
+#include "core/Metadata.h"
+#include "core/Tools.h"
+#include "crypto/Crypto.h"
+#include "format/OpVaultReader.h"
+
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QJsonObject>
+#include <QList>
+#include <QPair>
+#include <QStringList>
+#include <QTest>
+#include <QUuid>
+
+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);
+}
+
+QJsonArray* read1PasswordTextExport(QFile& f)
+{
+ auto result = new QJsonArray;
+ auto current = new QJsonObject;
+
+ if (!f.open(QIODevice::ReadOnly)) {
+ qCritical("Unable to open your text export file for reading");
+ return nullptr;
+ }
+
+ while (!f.atEnd()) {
+ auto line = f.readLine(1024);
+
+ if (line.size() == 1 and line[0] == '\n') {
+ if (!current->isEmpty()) {
+ result->append(*current);
+ }
+ current = new 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);
+ QJsonArray* data = read1PasswordTextExport(testData);
+ QVERIFY(data);
+ QCOMPARE(data->size(), 27);
+ delete data;
+
+ 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");
+}
+
+void TestOpVaultReader::testReadIntoDatabase()
+{
+ QDir opVaultDir(m_opVaultPath);
+
+ auto reader = new OpVaultReader();
+ auto db = reader->readDatabase(opVaultDir, m_password);
+ QVERIFY2(!reader->hasError(), qPrintable(reader->errorString()));
+ 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;
+ }
+ delete testData;
+ 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());
+ }
+
+ 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);
+ }
+ }
+}
+
+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()
+{
+ auto reader = new OpVaultReader();
+ QByteArray json(R"({"hello": "world"})");
+ QJsonDocument doc = QJsonDocument::fromJson(json);
+ QJsonObject data;
+ QByteArray entryKey;
+ QByteArray entryHmacKey;
+ QVERIFY(!reader->decryptBandEntry(doc.object(), data, entryKey, entryHmacKey));
+}