TestKeePass2Format.cpp 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816
  1. /*
  2. * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
  3. *
  4. * This program is free software: you can redistribute it and/or modify
  5. * it under the terms of the GNU General Public License as published by
  6. * the Free Software Foundation, either version 2 or (at your option)
  7. * version 3 of the License.
  8. *
  9. * This program is distributed in the hope that it will be useful,
  10. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. * GNU General Public License for more details.
  13. *
  14. * You should have received a copy of the GNU General Public License
  15. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  16. */
  17. #include "TestKeePass2Format.h"
  18. #include "mock/MockClock.h"
  19. #include "core/Group.h"
  20. #include "core/Metadata.h"
  21. #include "crypto/Crypto.h"
  22. #include "keys/FileKey.h"
  23. #include "keys/PasswordKey.h"
  24. #include "mock/MockChallengeResponseKey.h"
  25. #include "FailDevice.h"
  26. #include "config-keepassx-tests.h"
  27. #include <QtTest>
  28. void TestKeePass2Format::initTestCase()
  29. {
  30. QVERIFY(Crypto::init());
  31. // read raw XML database
  32. bool hasError;
  33. QString errorString;
  34. m_xmlDb = readXml(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"), true, hasError, errorString);
  35. if (hasError) {
  36. QFAIL(qPrintable(QString("Error while reading XML: ").append(errorString)));
  37. }
  38. QVERIFY(m_xmlDb.data());
  39. // construct and write KDBX to buffer
  40. auto key = QSharedPointer<CompositeKey>::create();
  41. key->addKey(QSharedPointer<PasswordKey>::create("test"));
  42. m_kdbxSourceDb = QSharedPointer<Database>::create();
  43. m_kdbxSourceDb->setKey(key);
  44. m_kdbxSourceDb->metadata()->setName("TESTDB");
  45. Group* group = m_kdbxSourceDb->rootGroup();
  46. group->setUuid(QUuid::createUuid());
  47. group->setNotes("I'm a note!");
  48. auto entry = new Entry();
  49. entry->setPassword(QString::fromUtf8("\xc3\xa4\xa3\xb6\xc3\xbc\xe9\x9b\xbb\xe7\xb4\x85"));
  50. entry->setUuid(QUuid::createUuid());
  51. entry->attributes()->set("test", "protectedTest", true);
  52. QVERIFY(entry->attributes()->isProtected("test"));
  53. entry->attachments()->set("myattach.txt", QByteArray("this is an attachment"));
  54. entry->attachments()->set("aaa.txt", QByteArray("also an attachment"));
  55. entry->setGroup(group);
  56. auto groupNew = new Group();
  57. groupNew->setUuid(QUuid::createUuid());
  58. groupNew->setName("TESTGROUP");
  59. groupNew->setNotes("I'm a sub group note!");
  60. groupNew->setParent(group);
  61. m_kdbxTargetBuffer.open(QBuffer::ReadWrite);
  62. writeKdbx(&m_kdbxTargetBuffer, m_kdbxSourceDb.data(), hasError, errorString);
  63. if (hasError) {
  64. QFAIL(qPrintable(QString("Error while writing database: ").append(errorString)));
  65. }
  66. // call sub class init method
  67. initTestCaseImpl();
  68. }
  69. void TestKeePass2Format::testXmlMetadata()
  70. {
  71. QCOMPARE(m_xmlDb->metadata()->generator(), QString("KeePass"));
  72. QCOMPARE(m_xmlDb->metadata()->name(), QString("ANAME"));
  73. QCOMPARE(m_xmlDb->metadata()->nameChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 53));
  74. QCOMPARE(m_xmlDb->metadata()->description(), QString("ADESC"));
  75. QCOMPARE(m_xmlDb->metadata()->descriptionChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 27, 12));
  76. QCOMPARE(m_xmlDb->metadata()->defaultUserName(), QString("DEFUSERNAME"));
  77. QCOMPARE(m_xmlDb->metadata()->defaultUserNameChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 27, 45));
  78. QCOMPARE(m_xmlDb->metadata()->maintenanceHistoryDays(), 127);
  79. QCOMPARE(m_xmlDb->metadata()->color(), QString("#FFEF00"));
  80. QCOMPARE(m_xmlDb->metadata()->databaseKeyChanged(), MockClock::datetimeUtc(2012, 4, 5, 17, 9, 34));
  81. QCOMPARE(m_xmlDb->metadata()->databaseKeyChangeRec(), 101);
  82. QCOMPARE(m_xmlDb->metadata()->databaseKeyChangeForce(), -1);
  83. QCOMPARE(m_xmlDb->metadata()->protectTitle(), false);
  84. QCOMPARE(m_xmlDb->metadata()->protectUsername(), true);
  85. QCOMPARE(m_xmlDb->metadata()->protectPassword(), false);
  86. QCOMPARE(m_xmlDb->metadata()->protectUrl(), true);
  87. QCOMPARE(m_xmlDb->metadata()->protectNotes(), false);
  88. QCOMPARE(m_xmlDb->metadata()->recycleBinEnabled(), true);
  89. QVERIFY(m_xmlDb->metadata()->recycleBin() != nullptr);
  90. QCOMPARE(m_xmlDb->metadata()->recycleBin()->name(), QString("Recycle Bin"));
  91. QCOMPARE(m_xmlDb->metadata()->recycleBinChanged(), MockClock::datetimeUtc(2010, 8, 25, 16, 12, 57));
  92. QVERIFY(m_xmlDb->metadata()->entryTemplatesGroup() == nullptr);
  93. QCOMPARE(m_xmlDb->metadata()->entryTemplatesGroupChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 19));
  94. QVERIFY(m_xmlDb->metadata()->lastSelectedGroup() != nullptr);
  95. QCOMPARE(m_xmlDb->metadata()->lastSelectedGroup()->name(), QString("NewDatabase"));
  96. QVERIFY(m_xmlDb->metadata()->lastTopVisibleGroup() == m_xmlDb->metadata()->lastSelectedGroup());
  97. QCOMPARE(m_xmlDb->metadata()->historyMaxItems(), -1);
  98. QCOMPARE(m_xmlDb->metadata()->historyMaxSize(), 5242880);
  99. }
  100. void TestKeePass2Format::testXmlCustomIcons()
  101. {
  102. QCOMPARE(m_xmlDb->metadata()->customIconsOrder().size(), 1);
  103. QUuid uuid = QUuid::fromRfc4122(QByteArray::fromBase64("++vyI+daLk6omox4a6kQGA=="));
  104. QVERIFY(m_xmlDb->metadata()->hasCustomIcon(uuid));
  105. QByteArray icon = m_xmlDb->metadata()->customIcon(uuid).data;
  106. QVERIFY(icon.startsWith(
  107. "\x89PNG\r\n\x1A\n\x00\x00\x00\rIHDR\x00\x00\x00\x10\x00\x00\x00\x10\b\x06\x00\x00\x00\x1F\xF3\xFF"));
  108. }
  109. void TestKeePass2Format::testXmlGroupRoot()
  110. {
  111. const Group* group = m_xmlDb->rootGroup();
  112. QVERIFY(group);
  113. QCOMPARE(group->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("lmU+9n0aeESKZvcEze+bRg==")));
  114. QCOMPARE(group->name(), QString("NewDatabase"));
  115. QCOMPARE(group->notes(), QString(""));
  116. QCOMPARE(group->iconNumber(), 49);
  117. QCOMPARE(group->iconUuid(), QUuid());
  118. QVERIFY(group->isExpanded());
  119. TimeInfo ti = group->timeInfo();
  120. QCOMPARE(ti.lastModificationTime(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 27));
  121. QCOMPARE(ti.creationTime(), MockClock::datetimeUtc(2010, 8, 7, 17, 24, 27));
  122. QCOMPARE(ti.lastAccessTime(), MockClock::datetimeUtc(2010, 8, 9, 9, 9, 44));
  123. QCOMPARE(ti.expiryTime(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 17));
  124. QVERIFY(!ti.expires());
  125. QCOMPARE(ti.usageCount(), 52);
  126. QCOMPARE(ti.locationChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 27));
  127. QCOMPARE(group->defaultAutoTypeSequence(), QString(""));
  128. QCOMPARE(group->autoTypeEnabled(), Group::Inherit);
  129. QCOMPARE(group->searchingEnabled(), Group::Inherit);
  130. QCOMPARE(group->lastTopVisibleEntry()->uuid(),
  131. QUuid::fromRfc4122(QByteArray::fromBase64("+wSUOv6qf0OzW8/ZHAs2sA==")));
  132. QCOMPARE(group->children().size(), 3);
  133. QVERIFY(m_xmlDb->metadata()->recycleBin() == m_xmlDb->rootGroup()->children().at(2));
  134. QCOMPARE(group->entries().size(), 2);
  135. }
  136. void TestKeePass2Format::testXmlGroup1()
  137. {
  138. const Group* group = m_xmlDb->rootGroup()->children().at(0);
  139. QCOMPARE(group->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("AaUYVdXsI02h4T1RiAlgtg==")));
  140. QCOMPARE(group->name(), QString("General"));
  141. QCOMPARE(group->notes(), QString("Group Notez"));
  142. QCOMPARE(group->iconNumber(), 48);
  143. QCOMPARE(group->iconUuid(), QUuid());
  144. QCOMPARE(group->isExpanded(), true);
  145. QCOMPARE(group->defaultAutoTypeSequence(), QString("{Password}{ENTER}"));
  146. QCOMPARE(group->autoTypeEnabled(), Group::Enable);
  147. QCOMPARE(group->searchingEnabled(), Group::Disable);
  148. QVERIFY(!group->lastTopVisibleEntry());
  149. }
  150. void TestKeePass2Format::testXmlGroup2()
  151. {
  152. const Group* group = m_xmlDb->rootGroup()->children().at(1);
  153. QCOMPARE(group->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("1h4NtL5DK0yVyvaEnN//4A==")));
  154. QCOMPARE(group->name(), QString("Windows"));
  155. QCOMPARE(group->isExpanded(), false);
  156. QCOMPARE(group->children().size(), 1);
  157. const Group* child = group->children().first();
  158. QCOMPARE(child->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("HoYE/BjLfUSW257pCHJ/eA==")));
  159. QCOMPARE(child->name(), QString("Subsub"));
  160. QCOMPARE(child->entries().size(), 1);
  161. const Entry* entry = child->entries().first();
  162. QCOMPARE(entry->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("GZpdQvGXOU2kaKRL/IVAGg==")));
  163. QCOMPARE(entry->title(), QString("Subsub Entry"));
  164. }
  165. void TestKeePass2Format::testXmlEntry1()
  166. {
  167. const Entry* entry = m_xmlDb->rootGroup()->entries().at(0);
  168. QCOMPARE(entry->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("+wSUOv6qf0OzW8/ZHAs2sA==")));
  169. QCOMPARE(entry->historyItems().size(), 2);
  170. QCOMPARE(entry->iconNumber(), 0);
  171. QCOMPARE(entry->iconUuid(), QUuid());
  172. QVERIFY(entry->foregroundColor().isEmpty());
  173. QVERIFY(entry->backgroundColor().isEmpty());
  174. QCOMPARE(entry->overrideUrl(), QString(""));
  175. QCOMPARE(entry->tags(), QString("a b c"));
  176. const TimeInfo ti = entry->timeInfo();
  177. QCOMPARE(ti.lastModificationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 19, 25));
  178. QCOMPARE(ti.creationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 13, 54));
  179. QCOMPARE(ti.lastAccessTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 19, 25));
  180. QCOMPARE(ti.expiryTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 12, 57));
  181. QVERIFY(!ti.expires());
  182. QCOMPARE(ti.usageCount(), 8);
  183. QCOMPARE(ti.locationChanged(), MockClock::datetimeUtc(2010, 8, 25, 16, 13, 54));
  184. QList<QString> attrs = entry->attributes()->keys();
  185. QCOMPARE(entry->attributes()->value("Notes"), QString("Notes"));
  186. QVERIFY(!entry->attributes()->isProtected("Notes"));
  187. QVERIFY(attrs.removeOne("Notes"));
  188. QCOMPARE(entry->attributes()->value("Password"), QString("Password"));
  189. QVERIFY(!entry->attributes()->isProtected("Password"));
  190. QVERIFY(attrs.removeOne("Password"));
  191. QCOMPARE(entry->attributes()->value("Title"), QString("Sample Entry 1"));
  192. QVERIFY(!entry->attributes()->isProtected("Title"));
  193. QVERIFY(attrs.removeOne("Title"));
  194. QCOMPARE(entry->attributes()->value("URL"), QString(""));
  195. QVERIFY(entry->attributes()->isProtected("URL"));
  196. QVERIFY(attrs.removeOne("URL"));
  197. QCOMPARE(entry->attributes()->value("UserName"), QString("User Name"));
  198. QVERIFY(entry->attributes()->isProtected("UserName"));
  199. QVERIFY(attrs.removeOne("UserName"));
  200. QVERIFY(attrs.isEmpty());
  201. QCOMPARE(entry->title(), entry->attributes()->value("Title"));
  202. QCOMPARE(entry->url(), entry->attributes()->value("URL"));
  203. QCOMPARE(entry->username(), entry->attributes()->value("UserName"));
  204. QCOMPARE(entry->password(), entry->attributes()->value("Password"));
  205. QCOMPARE(entry->notes(), entry->attributes()->value("Notes"));
  206. QCOMPARE(entry->attachments()->keys().size(), 1);
  207. QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("abcdefghijk"));
  208. QCOMPARE(entry->historyItems().at(0)->attachments()->keys().size(), 1);
  209. QCOMPARE(entry->historyItems().at(0)->attachments()->value("myattach.txt"), QByteArray("0123456789"));
  210. QCOMPARE(entry->historyItems().at(1)->attachments()->keys().size(), 1);
  211. QCOMPARE(entry->historyItems().at(1)->attachments()->value("myattach.txt"), QByteArray("abcdefghijk"));
  212. QCOMPARE(entry->autoTypeEnabled(), false);
  213. QCOMPARE(entry->autoTypeObfuscation(), 0);
  214. QCOMPARE(entry->defaultAutoTypeSequence(), QString(""));
  215. QCOMPARE(entry->autoTypeAssociations()->size(), 1);
  216. const AutoTypeAssociations::Association assoc1 = entry->autoTypeAssociations()->get(0);
  217. QCOMPARE(assoc1.window, QString("Target Window"));
  218. QCOMPARE(assoc1.sequence, QString(""));
  219. }
  220. void TestKeePass2Format::testXmlEntry2()
  221. {
  222. const Entry* entry = m_xmlDb->rootGroup()->entries().at(1);
  223. QCOMPARE(entry->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("4jbADG37hkiLh2O0qUdaOQ==")));
  224. QCOMPARE(entry->iconNumber(), 0);
  225. QCOMPARE(entry->iconUuid(), QUuid::fromRfc4122(QByteArray::fromBase64("++vyI+daLk6omox4a6kQGA==")));
  226. // TODO: test entry->icon()
  227. QCOMPARE(entry->foregroundColor(), QString("#FF0000"));
  228. QCOMPARE(entry->backgroundColor(), QString("#FFFF00"));
  229. QCOMPARE(entry->overrideUrl(), QString("http://override.net/"));
  230. QCOMPARE(entry->tags(), QString(""));
  231. const TimeInfo ti = entry->timeInfo();
  232. QCOMPARE(ti.usageCount(), 7);
  233. QList<QString> attrs = entry->attributes()->keys();
  234. QCOMPARE(entry->attributes()->value("CustomString"), QString("isavalue"));
  235. QVERIFY(attrs.removeOne("CustomString"));
  236. QCOMPARE(entry->attributes()->value("Notes"), QString(""));
  237. QVERIFY(attrs.removeOne("Notes"));
  238. QCOMPARE(entry->attributes()->value("Password"), QString("Jer60Hz8o9XHvxBGcRqT"));
  239. QVERIFY(attrs.removeOne("Password"));
  240. QCOMPARE(entry->attributes()->value("Protected String"), QString("y")); // TODO: should have a protection attribute
  241. QVERIFY(attrs.removeOne("Protected String"));
  242. QCOMPARE(entry->attributes()->value("Title"), QString("Sample Entry 2"));
  243. QVERIFY(attrs.removeOne("Title"));
  244. QCOMPARE(entry->attributes()->value("URL"), QString("http://www.keepassx.org/"));
  245. QVERIFY(attrs.removeOne("URL"));
  246. QCOMPARE(entry->attributes()->value("UserName"), QString("notDEFUSERNAME"));
  247. QVERIFY(attrs.removeOne("UserName"));
  248. QVERIFY(attrs.isEmpty());
  249. QCOMPARE(entry->attachments()->keys().size(), 1);
  250. QCOMPARE(QString::fromLatin1(entry->attachments()->value("myattach.txt")), QString("abcdefghijk"));
  251. QCOMPARE(entry->autoTypeEnabled(), true);
  252. QCOMPARE(entry->autoTypeObfuscation(), 1);
  253. QCOMPARE(entry->defaultAutoTypeSequence(), QString("{USERNAME}{TAB}{PASSWORD}{ENTER}"));
  254. QCOMPARE(entry->autoTypeAssociations()->size(), 2);
  255. const AutoTypeAssociations::Association assoc1 = entry->autoTypeAssociations()->get(0);
  256. QCOMPARE(assoc1.window, QString("Target Window"));
  257. QCOMPARE(assoc1.sequence, QString("{Title}{UserName}"));
  258. const AutoTypeAssociations::Association assoc2 = entry->autoTypeAssociations()->get(1);
  259. QCOMPARE(assoc2.window, QString("Target Window 2"));
  260. QCOMPARE(assoc2.sequence, QString("{Title}{UserName} test"));
  261. }
  262. void TestKeePass2Format::testXmlEntryHistory()
  263. {
  264. const Entry* entryMain = m_xmlDb->rootGroup()->entries().at(0);
  265. QCOMPARE(entryMain->historyItems().size(), 2);
  266. {
  267. const Entry* entry = entryMain->historyItems().at(0);
  268. QCOMPARE(entry->uuid(), entryMain->uuid());
  269. QVERIFY(!entry->parent());
  270. QCOMPARE(entry->timeInfo().lastModificationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 13, 54));
  271. QCOMPARE(entry->timeInfo().usageCount(), 3);
  272. QCOMPARE(entry->title(), QString("Sample Entry"));
  273. QCOMPARE(entry->url(), QString("http://www.somesite.com/"));
  274. }
  275. {
  276. const Entry* entry = entryMain->historyItems().at(1);
  277. QCOMPARE(entry->uuid(), entryMain->uuid());
  278. QVERIFY(!entry->parent());
  279. QCOMPARE(entry->timeInfo().lastModificationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 15, 43));
  280. QCOMPARE(entry->timeInfo().usageCount(), 7);
  281. QCOMPARE(entry->title(), QString("Sample Entry 1"));
  282. QCOMPARE(entry->url(), QString("http://www.somesite.com/"));
  283. }
  284. }
  285. void TestKeePass2Format::testXmlDeletedObjects()
  286. {
  287. QList<DeletedObject> objList = m_xmlDb->deletedObjects();
  288. DeletedObject delObj;
  289. delObj = objList.takeFirst();
  290. QCOMPARE(delObj.uuid, QUuid::fromRfc4122(QByteArray::fromBase64("5K/bzWCSmkCv5OZxYl4N/w==")));
  291. QCOMPARE(delObj.deletionTime, MockClock::datetimeUtc(2010, 8, 25, 16, 14, 12));
  292. delObj = objList.takeFirst();
  293. QCOMPARE(delObj.uuid, QUuid::fromRfc4122(QByteArray::fromBase64("80h8uSNWgkKhKCp1TgXF7g==")));
  294. QCOMPARE(delObj.deletionTime, MockClock::datetimeUtc(2010, 8, 25, 16, 14, 14));
  295. QVERIFY(objList.isEmpty());
  296. }
  297. void TestKeePass2Format::testXmlBroken()
  298. {
  299. QFETCH(QString, baseName);
  300. QFETCH(bool, strictMode);
  301. QFETCH(bool, expectError);
  302. QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName);
  303. QVERIFY(QFile::exists(xmlFile));
  304. bool hasError;
  305. QString errorString;
  306. auto db = readXml(xmlFile, strictMode, hasError, errorString);
  307. if (hasError) {
  308. qWarning("Reader error: %s", qPrintable(errorString));
  309. }
  310. QCOMPARE(hasError, expectError);
  311. }
  312. // clang-format off
  313. void TestKeePass2Format::testXmlBroken_data()
  314. {
  315. QTest::addColumn<QString>("baseName");
  316. QTest::addColumn<bool>("strictMode");
  317. QTest::addColumn<bool>("expectError");
  318. // testfile strict? error?
  319. QTest::newRow("BrokenNoGroupUuid (strict)") << "BrokenNoGroupUuid" << true << true;
  320. QTest::newRow("BrokenNoGroupUuid (not strict)") << "BrokenNoGroupUuid" << false << false;
  321. QTest::newRow("BrokenNoEntryUuid (strict)") << "BrokenNoEntryUuid" << true << true;
  322. QTest::newRow("BrokenNoEntryUuid (not strict)") << "BrokenNoEntryUuid" << false << false;
  323. QTest::newRow("BrokenNoRootGroup (strict)") << "BrokenNoRootGroup" << true << true;
  324. QTest::newRow("BrokenNoRootGroup (not strict)") << "BrokenNoRootGroup" << false << true;
  325. QTest::newRow("BrokenTwoRoots (strict)") << "BrokenTwoRoots" << true << true;
  326. QTest::newRow("BrokenTwoRoots (not strict)") << "BrokenTwoRoots" << false << true;
  327. QTest::newRow("BrokenTwoRootGroups (strict)") << "BrokenTwoRootGroups" << true << true;
  328. QTest::newRow("BrokenTwoRootGroups (not strict)") << "BrokenTwoRootGroups" << false << true;
  329. QTest::newRow("BrokenGroupReference (strict)") << "BrokenGroupReference" << true << false;
  330. QTest::newRow("BrokenGroupReference (not strict)") << "BrokenGroupReference" << false << false;
  331. QTest::newRow("BrokenDeletedObjects (strict)") << "BrokenDeletedObjects" << true << true;
  332. QTest::newRow("BrokenDeletedObjects (not strict)") << "BrokenDeletedObjects" << false << false;
  333. QTest::newRow("BrokenDifferentEntryHistoryUuid (strict)") << "BrokenDifferentEntryHistoryUuid" << true << true;
  334. QTest::newRow("BrokenDifferentEntryHistoryUuid (not strict)") << "BrokenDifferentEntryHistoryUuid" << false << false;
  335. }
  336. // clang-format on
  337. void TestKeePass2Format::testXmlEmptyUuids()
  338. {
  339. QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "EmptyUuids");
  340. QVERIFY(QFile::exists(xmlFile));
  341. bool hasError;
  342. QString errorString;
  343. auto db = readXml(xmlFile, true, hasError, errorString);
  344. if (hasError) {
  345. qWarning("Reader error: %s", qPrintable(errorString));
  346. }
  347. QVERIFY(!hasError);
  348. }
  349. void TestKeePass2Format::testXmlInvalidXmlChars()
  350. {
  351. QScopedPointer<Database> dbWrite(new Database());
  352. QString strPlainInvalid =
  353. QString().append(QChar(0x02)).append(QChar(0x19)).append(QChar(0xFFFE)).append(QChar(0xFFFF));
  354. QString strPlainValid = QString()
  355. .append(QChar(0x09))
  356. .append(QChar(0x0A))
  357. .append(QChar(0x20))
  358. .append(QChar(0xD7FF))
  359. .append(QChar(0xE000))
  360. .append(QChar(0xFFFD));
  361. // U+10437 in UTF-16: D801 DC37
  362. // high low surrogate
  363. QString strSingleHighSurrogate1 = QString().append(QChar(0xD801));
  364. QString strSingleHighSurrogate2 = QString().append(QChar(0x31)).append(QChar(0xD801)).append(QChar(0x32));
  365. QString strHighHighSurrogate = QString().append(QChar(0xD801)).append(QChar(0xD801));
  366. QString strSingleLowSurrogate1 = QString().append(QChar(0xDC37));
  367. QString strSingleLowSurrogate2 = QString().append(QChar((0x31))).append(QChar(0xDC37)).append(QChar(0x32));
  368. QString strLowLowSurrogate = QString().append(QChar(0xDC37)).append(QChar(0xDC37));
  369. QString strSurrogateValid1 = QString().append(QChar(0xD801)).append(QChar(0xDC37));
  370. QString strSurrogateValid2 =
  371. QString().append(QChar(0x31)).append(QChar(0xD801)).append(QChar(0xDC37)).append(QChar(0x32));
  372. auto entry = new Entry();
  373. entry->setUuid(QUuid::createUuid());
  374. entry->setGroup(dbWrite->rootGroup());
  375. entry->attributes()->set("PlainInvalid", strPlainInvalid);
  376. entry->attributes()->set("PlainValid", strPlainValid);
  377. entry->attributes()->set("SingleHighSurrogate1", strSingleHighSurrogate1);
  378. entry->attributes()->set("SingleHighSurrogate2", strSingleHighSurrogate2);
  379. entry->attributes()->set("HighHighSurrogate", strHighHighSurrogate);
  380. entry->attributes()->set("SingleLowSurrogate1", strSingleLowSurrogate1);
  381. entry->attributes()->set("SingleLowSurrogate2", strSingleLowSurrogate2);
  382. entry->attributes()->set("LowLowSurrogate", strLowLowSurrogate);
  383. entry->attributes()->set("SurrogateValid1", strSurrogateValid1);
  384. entry->attributes()->set("SurrogateValid2", strSurrogateValid2);
  385. QBuffer buffer;
  386. buffer.open(QIODevice::ReadWrite);
  387. bool hasError;
  388. QString errorString;
  389. writeXml(&buffer, dbWrite.data(), hasError, errorString);
  390. QVERIFY(!hasError);
  391. buffer.seek(0);
  392. auto dbRead = readXml(&buffer, true, hasError, errorString);
  393. if (hasError) {
  394. qWarning("Database read error: %s", qPrintable(errorString));
  395. }
  396. QVERIFY(!hasError);
  397. QVERIFY(dbRead.data());
  398. QCOMPARE(dbRead->rootGroup()->entries().size(), 1);
  399. Entry* entryRead = dbRead->rootGroup()->entries().at(0);
  400. EntryAttributes* attrRead = entryRead->attributes();
  401. QCOMPARE(attrRead->value("PlainInvalid"), QString());
  402. QCOMPARE(attrRead->value("PlainValid"), strPlainValid);
  403. QCOMPARE(attrRead->value("SingleHighSurrogate1"), QString());
  404. QCOMPARE(attrRead->value("SingleHighSurrogate2"), QString("12"));
  405. QCOMPARE(attrRead->value("HighHighSurrogate"), QString());
  406. QCOMPARE(attrRead->value("SingleLowSurrogate1"), QString());
  407. QCOMPARE(attrRead->value("SingleLowSurrogate2"), QString("12"));
  408. QCOMPARE(attrRead->value("LowLowSurrogate"), QString());
  409. QCOMPARE(attrRead->value("SurrogateValid1"), strSurrogateValid1);
  410. QCOMPARE(attrRead->value("SurrogateValid2"), strSurrogateValid2);
  411. }
  412. void TestKeePass2Format::testXmlRepairUuidHistoryItem()
  413. {
  414. QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid");
  415. QVERIFY(QFile::exists(xmlFile));
  416. bool hasError;
  417. QString errorString;
  418. auto db = readXml(xmlFile, false, hasError, errorString);
  419. if (hasError) {
  420. qWarning("Database read error: %s", qPrintable(errorString));
  421. }
  422. QVERIFY(!hasError);
  423. QList<Entry*> entries = db->rootGroup()->entries();
  424. QCOMPARE(entries.size(), 1);
  425. Entry* entry = entries.at(0);
  426. QList<Entry*> historyItems = entry->historyItems();
  427. QCOMPARE(historyItems.size(), 1);
  428. Entry* historyItem = historyItems.at(0);
  429. QVERIFY(!entry->uuid().isNull());
  430. QVERIFY(!historyItem->uuid().isNull());
  431. QCOMPARE(historyItem->uuid(), entry->uuid());
  432. }
  433. void TestKeePass2Format::testReadBackTargetDb()
  434. {
  435. // read back previously constructed KDBX
  436. auto key = QSharedPointer<CompositeKey>::create();
  437. key->addKey(QSharedPointer<PasswordKey>::create("test"));
  438. bool hasError;
  439. QString errorString;
  440. m_kdbxTargetBuffer.seek(0);
  441. m_kdbxTargetDb = QSharedPointer<Database>::create();
  442. readKdbx(&m_kdbxTargetBuffer, key, m_kdbxTargetDb, hasError, errorString);
  443. if (hasError) {
  444. QFAIL(qPrintable(QString("Error while reading database: ").append(errorString)));
  445. }
  446. QVERIFY(m_kdbxTargetDb.data());
  447. }
  448. void TestKeePass2Format::testKdbxBasic()
  449. {
  450. QCOMPARE(m_kdbxTargetDb->metadata()->name(), m_kdbxSourceDb->metadata()->name());
  451. QVERIFY(m_kdbxTargetDb->rootGroup());
  452. QCOMPARE(m_kdbxTargetDb->rootGroup()->children()[0]->name(), m_kdbxSourceDb->rootGroup()->children()[0]->name());
  453. QCOMPARE(m_kdbxTargetDb->rootGroup()->notes(), m_kdbxSourceDb->rootGroup()->notes());
  454. QCOMPARE(m_kdbxTargetDb->rootGroup()->children()[0]->notes(), m_kdbxSourceDb->rootGroup()->children()[0]->notes());
  455. }
  456. void TestKeePass2Format::testKdbxProtectedAttributes()
  457. {
  458. QCOMPARE(m_kdbxTargetDb->rootGroup()->entries().size(), 1);
  459. Entry* entry = m_kdbxTargetDb->rootGroup()->entries().at(0);
  460. QCOMPARE(entry->attributes()->value("test"), QString("protectedTest"));
  461. QCOMPARE(entry->attributes()->isProtected("test"), true);
  462. }
  463. void TestKeePass2Format::testKdbxAttachments()
  464. {
  465. Entry* entry = m_kdbxTargetDb->rootGroup()->entries().at(0);
  466. QCOMPARE(entry->attachments()->keys().size(), 2);
  467. QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("this is an attachment"));
  468. QCOMPARE(entry->attachments()->value("aaa.txt"), QByteArray("also an attachment"));
  469. }
  470. void TestKeePass2Format::testKdbxNonAsciiPasswords()
  471. {
  472. QCOMPARE(m_kdbxTargetDb->rootGroup()->entries()[0]->password(),
  473. m_kdbxSourceDb->rootGroup()->entries()[0]->password());
  474. }
  475. void TestKeePass2Format::testKdbxDeviceFailure()
  476. {
  477. auto key = QSharedPointer<CompositeKey>::create();
  478. key->addKey(QSharedPointer<PasswordKey>::create("test"));
  479. QScopedPointer<Database> db(new Database());
  480. db->setKey(key);
  481. // Disable compression so we write a predictable number of bytes.
  482. db->setCompressionAlgorithm(Database::CompressionNone);
  483. auto entry = new Entry();
  484. entry->setParent(db->rootGroup());
  485. QByteArray attachment(4096, 'Z');
  486. entry->attachments()->set("test", attachment);
  487. FailDevice failDevice(512);
  488. QVERIFY(failDevice.open(QIODevice::WriteOnly));
  489. bool hasError;
  490. QString errorString;
  491. writeKdbx(&failDevice, db.data(), hasError, errorString);
  492. QVERIFY(hasError);
  493. QCOMPARE(errorString, QString("FAILDEVICE"));
  494. }
  495. Q_DECLARE_METATYPE(QSharedPointer<CompositeKey>)
  496. void TestKeePass2Format::testKdbxKeyChange()
  497. {
  498. QFETCH(QSharedPointer<CompositeKey>, key1);
  499. QFETCH(QSharedPointer<CompositeKey>, key2);
  500. bool hasError;
  501. QString errorString;
  502. // write new database
  503. QBuffer buffer;
  504. buffer.open(QBuffer::ReadWrite);
  505. buffer.seek(0);
  506. QSharedPointer<Database> db(new Database());
  507. db->changeKdf(fastKdf(KeePass2::uuidToKdf(m_kdbxSourceDb->kdf()->uuid())));
  508. auto oldGroup =
  509. db->setRootGroup(m_kdbxSourceDb->rootGroup()->clone(Entry::CloneNoFlags, Group::CloneIncludeEntries));
  510. delete oldGroup;
  511. db->setKey(key1);
  512. writeKdbx(&buffer, db.data(), hasError, errorString);
  513. if (hasError) {
  514. QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
  515. }
  516. // read database
  517. db = QSharedPointer<Database>::create();
  518. buffer.seek(0);
  519. readKdbx(&buffer, key1, db, hasError, errorString);
  520. if (hasError) {
  521. QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
  522. }
  523. QVERIFY(db.data());
  524. // change key
  525. db->setKey(key2);
  526. // write database
  527. buffer.seek(0);
  528. writeKdbx(&buffer, db.data(), hasError, errorString);
  529. if (hasError) {
  530. QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
  531. }
  532. // read database
  533. db = QSharedPointer<Database>::create();
  534. buffer.seek(0);
  535. readKdbx(&buffer, key2, db, hasError, errorString);
  536. if (hasError) {
  537. QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
  538. }
  539. QVERIFY(db.data());
  540. QVERIFY(db->rootGroup() != m_kdbxSourceDb->rootGroup());
  541. QVERIFY(db->rootGroup()->uuid() == m_kdbxSourceDb->rootGroup()->uuid());
  542. }
  543. void TestKeePass2Format::testKdbxKeyChange_data()
  544. {
  545. QTest::addColumn<QSharedPointer<CompositeKey>>("key1");
  546. QTest::addColumn<QSharedPointer<CompositeKey>>("key2");
  547. auto passwordKey1 = QSharedPointer<PasswordKey>::create("abc");
  548. auto passwordKey2 = QSharedPointer<PasswordKey>::create("def");
  549. QByteArray fileKeyBytes1("uvw");
  550. QBuffer fileKeyBuffer1(&fileKeyBytes1);
  551. fileKeyBuffer1.open(QBuffer::ReadOnly);
  552. auto fileKey1 = QSharedPointer<FileKey>::create();
  553. fileKey1->load(&fileKeyBuffer1);
  554. QByteArray fileKeyBytes2("xzy");
  555. QBuffer fileKeyBuffer2(&fileKeyBytes1);
  556. fileKeyBuffer2.open(QBuffer::ReadOnly);
  557. auto fileKey2 = QSharedPointer<FileKey>::create();
  558. fileKey2->load(&fileKeyBuffer2);
  559. auto crKey1 = QSharedPointer<MockChallengeResponseKey>::create(QByteArray("123"));
  560. auto crKey2 = QSharedPointer<MockChallengeResponseKey>::create(QByteArray("456"));
  561. // empty key
  562. auto compositeKey0 = QSharedPointer<CompositeKey>::create();
  563. // all in
  564. auto compositeKey1_1 = QSharedPointer<CompositeKey>::create();
  565. compositeKey1_1->addKey(passwordKey1);
  566. compositeKey1_1->addKey(fileKey1);
  567. compositeKey1_1->addChallengeResponseKey(crKey1);
  568. auto compositeKey1_2 = QSharedPointer<CompositeKey>::create();
  569. compositeKey1_2->addKey(passwordKey2);
  570. compositeKey1_2->addKey(fileKey2);
  571. compositeKey1_2->addChallengeResponseKey(crKey2);
  572. QTest::newRow("Change: Empty Key -> Full Key") << compositeKey0 << compositeKey1_1;
  573. QTest::newRow("Change: Full Key -> Empty Key") << compositeKey1_1 << compositeKey0;
  574. QTest::newRow("Change: Full Key 1 -> Full Key 2") << compositeKey1_1 << compositeKey1_2;
  575. // only password
  576. auto compositeKey2_1 = QSharedPointer<CompositeKey>::create();
  577. compositeKey2_1->addKey(passwordKey1);
  578. auto compositeKey2_2 = QSharedPointer<CompositeKey>::create();
  579. compositeKey2_2->addKey(passwordKey2);
  580. QTest::newRow("Change: Password -> Empty Key") << compositeKey2_1 << compositeKey0;
  581. QTest::newRow("Change: Empty Key -> Password") << compositeKey0 << compositeKey2_1;
  582. QTest::newRow("Change: Full Key -> Password 1") << compositeKey1_1 << compositeKey2_1;
  583. QTest::newRow("Change: Full Key -> Password 2") << compositeKey1_1 << compositeKey2_2;
  584. QTest::newRow("Change: Password 1 -> Full Key") << compositeKey2_1 << compositeKey1_1;
  585. QTest::newRow("Change: Password 2 -> Full Key") << compositeKey2_2 << compositeKey1_1;
  586. QTest::newRow("Change: Password 1 -> Password 2") << compositeKey2_1 << compositeKey2_2;
  587. // only key file
  588. auto compositeKey3_1 = QSharedPointer<CompositeKey>::create();
  589. compositeKey3_1->addKey(fileKey1);
  590. auto compositeKey3_2 = QSharedPointer<CompositeKey>::create();
  591. compositeKey3_2->addKey(fileKey2);
  592. QTest::newRow("Change: Key File -> Empty Key") << compositeKey3_1 << compositeKey0;
  593. QTest::newRow("Change: Empty Key -> Key File") << compositeKey0 << compositeKey3_1;
  594. QTest::newRow("Change: Full Key -> Key File 1") << compositeKey1_1 << compositeKey3_1;
  595. QTest::newRow("Change: Full Key -> Key File 2") << compositeKey1_1 << compositeKey3_2;
  596. QTest::newRow("Change: Key File 1 -> Full Key") << compositeKey3_1 << compositeKey1_1;
  597. QTest::newRow("Change: Key File 2 -> Full Key") << compositeKey3_2 << compositeKey1_1;
  598. QTest::newRow("Change: Key File 1 -> Key File 2") << compositeKey3_1 << compositeKey3_2;
  599. // only cr key
  600. auto compositeKey4_1 = QSharedPointer<CompositeKey>::create();
  601. compositeKey4_1->addChallengeResponseKey(crKey1);
  602. auto compositeKey4_2 = QSharedPointer<CompositeKey>::create();
  603. compositeKey4_2->addChallengeResponseKey(crKey2);
  604. QTest::newRow("Change: CR Key -> Empty Key") << compositeKey4_1 << compositeKey0;
  605. QTest::newRow("Change: Empty Key -> CR Key") << compositeKey0 << compositeKey4_1;
  606. QTest::newRow("Change: Full Key -> CR Key 1") << compositeKey1_1 << compositeKey4_1;
  607. QTest::newRow("Change: Full Key -> CR Key 2") << compositeKey1_1 << compositeKey4_2;
  608. QTest::newRow("Change: CR Key 1 -> Full Key") << compositeKey4_1 << compositeKey1_1;
  609. QTest::newRow("Change: CR Key 2 -> Full Key") << compositeKey4_2 << compositeKey1_1;
  610. QTest::newRow("Change: CR Key 1 -> CR Key 2") << compositeKey4_1 << compositeKey4_2;
  611. // rotate
  612. QTest::newRow("Change: Password -> Key File") << compositeKey2_1 << compositeKey3_1;
  613. QTest::newRow("Change: Key File -> Password") << compositeKey3_1 << compositeKey2_1;
  614. QTest::newRow("Change: Password -> Key File") << compositeKey2_1 << compositeKey3_1;
  615. QTest::newRow("Change: Key File -> Password") << compositeKey3_1 << compositeKey2_1;
  616. QTest::newRow("Change: Password -> CR Key") << compositeKey2_1 << compositeKey4_1;
  617. QTest::newRow("Change: CR Key -> Password") << compositeKey4_1 << compositeKey2_1;
  618. QTest::newRow("Change: Key File -> CR Key") << compositeKey3_1 << compositeKey4_1;
  619. QTest::newRow("Change: CR Key -> Key File") << compositeKey4_1 << compositeKey3_1;
  620. // leave one out
  621. auto compositeKey5_1 = QSharedPointer<CompositeKey>::create();
  622. compositeKey5_1->addKey(fileKey1);
  623. compositeKey5_1->addChallengeResponseKey(crKey1);
  624. auto compositeKey5_2 = QSharedPointer<CompositeKey>::create();
  625. compositeKey5_2->addKey(passwordKey1);
  626. compositeKey5_2->addChallengeResponseKey(crKey1);
  627. auto compositeKey5_3 = QSharedPointer<CompositeKey>::create();
  628. compositeKey5_3->addKey(passwordKey1);
  629. compositeKey5_3->addKey(fileKey1);
  630. QTest::newRow("Change: Full Key -> No Password") << compositeKey1_1 << compositeKey5_1;
  631. QTest::newRow("Change: No Password -> Full Key") << compositeKey5_1 << compositeKey1_1;
  632. QTest::newRow("Change: Full Key -> No Key File") << compositeKey1_1 << compositeKey5_2;
  633. QTest::newRow("Change: No Key File -> Full Key") << compositeKey5_2 << compositeKey1_1;
  634. QTest::newRow("Change: Full Key -> No CR Key") << compositeKey1_1 << compositeKey5_3;
  635. QTest::newRow("Change: No CR Key -> Full Key") << compositeKey5_3 << compositeKey1_1;
  636. }
  637. /**
  638. * Test for catching mapping errors with duplicate attachments.
  639. */
  640. void TestKeePass2Format::testDuplicateAttachments()
  641. {
  642. auto db = QSharedPointer<Database>::create();
  643. db->setKey(QSharedPointer<CompositeKey>::create());
  644. const QByteArray attachment1("abc");
  645. const QByteArray attachment2("def");
  646. const QByteArray attachment3("ghi");
  647. auto entry1 = new Entry();
  648. entry1->setGroup(db->rootGroup());
  649. entry1->setUuid(QUuid::fromRfc4122("aaaaaaaaaaaaaaaa"));
  650. entry1->attachments()->set("a", attachment1);
  651. auto entry2 = new Entry();
  652. entry2->setGroup(db->rootGroup());
  653. entry2->setUuid(QUuid::fromRfc4122("bbbbbbbbbbbbbbbb"));
  654. entry2->attachments()->set("b1", attachment1);
  655. entry2->beginUpdate();
  656. entry2->attachments()->set("b2", attachment1);
  657. entry2->endUpdate();
  658. entry2->beginUpdate();
  659. entry2->attachments()->set("b3", attachment2);
  660. entry2->endUpdate();
  661. entry2->beginUpdate();
  662. entry2->attachments()->set("b4", attachment2);
  663. entry2->endUpdate();
  664. auto entry3 = new Entry();
  665. entry3->setGroup(db->rootGroup());
  666. entry3->setUuid(QUuid::fromRfc4122("cccccccccccccccc"));
  667. entry3->attachments()->set("c1", attachment2);
  668. entry3->attachments()->set("c2", attachment2);
  669. entry3->attachments()->set("c3", attachment3);
  670. QBuffer buffer;
  671. buffer.open(QBuffer::ReadWrite);
  672. bool hasError = false;
  673. QString errorString;
  674. writeKdbx(&buffer, db.data(), hasError, errorString);
  675. if (hasError) {
  676. QFAIL(qPrintable(QString("Error while writing database: %1").arg(errorString)));
  677. }
  678. buffer.seek(0);
  679. readKdbx(&buffer, QSharedPointer<CompositeKey>::create(), db, hasError, errorString);
  680. if (hasError) {
  681. QFAIL(qPrintable(QString("Error while reading database: %1").arg(errorString)));
  682. }
  683. QCOMPARE(db->rootGroup()->entries()[0]->attachments()->value("a"), attachment1);
  684. QCOMPARE(db->rootGroup()->entries()[1]->attachments()->value("b1"), attachment1);
  685. QCOMPARE(db->rootGroup()->entries()[1]->attachments()->value("b2"), attachment1);
  686. QCOMPARE(db->rootGroup()->entries()[1]->attachments()->value("b3"), attachment2);
  687. QCOMPARE(db->rootGroup()->entries()[1]->attachments()->value("b4"), attachment2);
  688. QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[0]->attachments()->value("b1"), attachment1);
  689. QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[1]->attachments()->value("b1"), attachment1);
  690. QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[1]->attachments()->value("b2"), attachment1);
  691. QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[2]->attachments()->value("b1"), attachment1);
  692. QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[2]->attachments()->value("b2"), attachment1);
  693. QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[2]->attachments()->value("b3"), attachment2);
  694. QCOMPARE(db->rootGroup()->entries()[2]->attachments()->value("c1"), attachment2);
  695. QCOMPARE(db->rootGroup()->entries()[2]->attachments()->value("c2"), attachment2);
  696. QCOMPARE(db->rootGroup()->entries()[2]->attachments()->value("c3"), attachment3);
  697. }
  698. /**
  699. * Fast "dummy" KDF
  700. */
  701. QSharedPointer<Kdf> fastKdf(QSharedPointer<Kdf> kdf)
  702. {
  703. kdf->setRounds(1);
  704. if (kdf->uuid() == KeePass2::KDF_ARGON2D or kdf->uuid() == KeePass2::KDF_ARGON2ID) {
  705. kdf->processParameters({{KeePass2::KDFPARAM_ARGON2_MEMORY, 1024}, {KeePass2::KDFPARAM_ARGON2_PARALLELISM, 1}});
  706. }
  707. return kdf;
  708. }