18
#include "TestKeePass2Format.h"
19
#include "mock/MockClock.h"
21
#include "core/Group.h"
22
#include "core/Metadata.h"
23
#include "crypto/Crypto.h"
24
#include "keys/FileKey.h"
25
#include "keys/PasswordKey.h"
26
#include "mock/MockChallengeResponseKey.h"
28
#include "FailDevice.h"
29
#include "config-keepassx-tests.h"
32
void TestKeePass2Format::initTestCase()
34
QVERIFY(Crypto::init());
39
m_xmlDb = readXml(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"), true, hasError, errorString);
41
QFAIL(qPrintable(QString("Error while reading XML: ").append(errorString)));
43
QVERIFY(m_xmlDb.data());
46
auto key = QSharedPointer<CompositeKey>::create();
47
key->addKey(QSharedPointer<PasswordKey>::create("test"));
49
m_kdbxSourceDb = QSharedPointer<Database>::create();
50
m_kdbxSourceDb->setKey(key);
51
m_kdbxSourceDb->metadata()->setName("TESTDB");
52
Group* group = m_kdbxSourceDb->rootGroup();
53
group->setUuid(QUuid::createUuid());
54
group->setNotes("I'm a note!");
55
auto entry = new Entry();
56
entry->setPassword(QString::fromUtf8("\xc3\xa4\xa3\xb6\xc3\xbc\xe9\x9b\xbb\xe7\xb4\x85"));
57
entry->setUuid(QUuid::createUuid());
58
entry->attributes()->set("test", "protectedTest", true);
59
QVERIFY(entry->attributes()->isProtected("test"));
60
entry->attachments()->set("myattach.txt", QByteArray("this is an attachment"));
61
entry->attachments()->set("aaa.txt", QByteArray("also an attachment"));
62
entry->setGroup(group);
63
auto groupNew = new Group();
64
groupNew->setUuid(QUuid::createUuid());
65
groupNew->setName("TESTGROUP");
66
groupNew->setNotes("I'm a sub group note!");
67
groupNew->setParent(group);
69
m_kdbxTargetBuffer.open(QBuffer::ReadWrite);
70
writeKdbx(&m_kdbxTargetBuffer, m_kdbxSourceDb.data(), hasError, errorString);
72
QFAIL(qPrintable(QString("Error while writing database: ").append(errorString)));
79
void TestKeePass2Format::testXmlMetadata()
81
QCOMPARE(m_xmlDb->metadata()->generator(), QString("KeePass"));
82
QCOMPARE(m_xmlDb->metadata()->name(), QString("ANAME"));
83
QCOMPARE(m_xmlDb->metadata()->nameChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 53));
84
QCOMPARE(m_xmlDb->metadata()->description(), QString("ADESC"));
85
QCOMPARE(m_xmlDb->metadata()->descriptionChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 27, 12));
86
QCOMPARE(m_xmlDb->metadata()->defaultUserName(), QString("DEFUSERNAME"));
87
QCOMPARE(m_xmlDb->metadata()->defaultUserNameChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 27, 45));
88
QCOMPARE(m_xmlDb->metadata()->maintenanceHistoryDays(), 127);
89
QCOMPARE(m_xmlDb->metadata()->color(), QString("#FFEF00"));
90
QCOMPARE(m_xmlDb->metadata()->databaseKeyChanged(), MockClock::datetimeUtc(2012, 4, 5, 17, 9, 34));
91
QCOMPARE(m_xmlDb->metadata()->databaseKeyChangeRec(), 101);
92
QCOMPARE(m_xmlDb->metadata()->databaseKeyChangeForce(), -1);
93
QCOMPARE(m_xmlDb->metadata()->protectTitle(), false);
94
QCOMPARE(m_xmlDb->metadata()->protectUsername(), true);
95
QCOMPARE(m_xmlDb->metadata()->protectPassword(), false);
96
QCOMPARE(m_xmlDb->metadata()->protectUrl(), true);
97
QCOMPARE(m_xmlDb->metadata()->protectNotes(), false);
98
QCOMPARE(m_xmlDb->metadata()->recycleBinEnabled(), true);
99
QVERIFY(m_xmlDb->metadata()->recycleBin() != nullptr);
100
QCOMPARE(m_xmlDb->metadata()->recycleBin()->name(), QString("Recycle Bin"));
101
QCOMPARE(m_xmlDb->metadata()->recycleBinChanged(), MockClock::datetimeUtc(2010, 8, 25, 16, 12, 57));
102
QVERIFY(m_xmlDb->metadata()->entryTemplatesGroup() == nullptr);
103
QCOMPARE(m_xmlDb->metadata()->entryTemplatesGroupChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 19));
104
QVERIFY(m_xmlDb->metadata()->lastSelectedGroup() != nullptr);
105
QCOMPARE(m_xmlDb->metadata()->lastSelectedGroup()->name(), QString("NewDatabase"));
106
QVERIFY(m_xmlDb->metadata()->lastTopVisibleGroup() == m_xmlDb->metadata()->lastSelectedGroup());
107
QCOMPARE(m_xmlDb->metadata()->historyMaxItems(), -1);
108
QCOMPARE(m_xmlDb->metadata()->historyMaxSize(), 5242880);
111
void TestKeePass2Format::testXmlCustomIcons()
113
QCOMPARE(m_xmlDb->metadata()->customIconsOrder().size(), 1);
114
QUuid uuid = QUuid::fromRfc4122(QByteArray::fromBase64("++vyI+daLk6omox4a6kQGA=="));
115
QVERIFY(m_xmlDb->metadata()->hasCustomIcon(uuid));
116
QByteArray icon = m_xmlDb->metadata()->customIcon(uuid).data;
118
QVERIFY(icon.startsWith(
119
"\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"));
122
void TestKeePass2Format::testXmlGroupRoot()
124
const Group* group = m_xmlDb->rootGroup();
126
QCOMPARE(group->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("lmU+9n0aeESKZvcEze+bRg==")));
127
QCOMPARE(group->name(), QString("NewDatabase"));
128
QCOMPARE(group->notes(), QString(""));
129
QCOMPARE(group->iconNumber(), 49);
130
QCOMPARE(group->iconUuid(), QUuid());
131
QVERIFY(group->isExpanded());
132
TimeInfo ti = group->timeInfo();
133
QCOMPARE(ti.lastModificationTime(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 27));
134
QCOMPARE(ti.creationTime(), MockClock::datetimeUtc(2010, 8, 7, 17, 24, 27));
135
QCOMPARE(ti.lastAccessTime(), MockClock::datetimeUtc(2010, 8, 9, 9, 9, 44));
136
QCOMPARE(ti.expiryTime(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 17));
137
QVERIFY(!ti.expires());
138
QCOMPARE(ti.usageCount(), 52);
139
QCOMPARE(ti.locationChanged(), MockClock::datetimeUtc(2010, 8, 8, 17, 24, 27));
140
QCOMPARE(group->defaultAutoTypeSequence(), QString(""));
141
QCOMPARE(group->autoTypeEnabled(), Group::Inherit);
142
QCOMPARE(group->searchingEnabled(), Group::Inherit);
143
QCOMPARE(group->lastTopVisibleEntry()->uuid(),
144
QUuid::fromRfc4122(QByteArray::fromBase64("+wSUOv6qf0OzW8/ZHAs2sA==")));
145
QCOMPARE(group->children().size(), 3);
146
QVERIFY(m_xmlDb->metadata()->recycleBin() == m_xmlDb->rootGroup()->children().at(2));
148
QCOMPARE(group->entries().size(), 2);
151
void TestKeePass2Format::testXmlGroup1()
153
const Group* group = m_xmlDb->rootGroup()->children().at(0);
155
QCOMPARE(group->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("AaUYVdXsI02h4T1RiAlgtg==")));
156
QCOMPARE(group->name(), QString("General"));
157
QCOMPARE(group->notes(), QString("Group Notez"));
158
QCOMPARE(group->iconNumber(), 48);
159
QCOMPARE(group->iconUuid(), QUuid());
160
QCOMPARE(group->isExpanded(), true);
161
QCOMPARE(group->defaultAutoTypeSequence(), QString("{Password}{ENTER}"));
162
QCOMPARE(group->autoTypeEnabled(), Group::Enable);
163
QCOMPARE(group->searchingEnabled(), Group::Disable);
164
QVERIFY(!group->lastTopVisibleEntry());
167
void TestKeePass2Format::testXmlGroup2()
169
const Group* group = m_xmlDb->rootGroup()->children().at(1);
171
QCOMPARE(group->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("1h4NtL5DK0yVyvaEnN//4A==")));
172
QCOMPARE(group->name(), QString("Windows"));
173
QCOMPARE(group->isExpanded(), false);
175
QCOMPARE(group->children().size(), 1);
176
const Group* child = group->children().first();
178
QCOMPARE(child->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("HoYE/BjLfUSW257pCHJ/eA==")));
179
QCOMPARE(child->name(), QString("Subsub"));
180
QCOMPARE(child->entries().size(), 1);
182
const Entry* entry = child->entries().first();
183
QCOMPARE(entry->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("GZpdQvGXOU2kaKRL/IVAGg==")));
184
QCOMPARE(entry->title(), QString("Subsub Entry"));
187
void TestKeePass2Format::testXmlEntry1()
189
const Entry* entry = m_xmlDb->rootGroup()->entries().at(0);
191
QCOMPARE(entry->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("+wSUOv6qf0OzW8/ZHAs2sA==")));
192
QCOMPARE(entry->historyItems().size(), 2);
193
QCOMPARE(entry->iconNumber(), 0);
194
QCOMPARE(entry->iconUuid(), QUuid());
195
QVERIFY(entry->foregroundColor().isEmpty());
196
QVERIFY(entry->backgroundColor().isEmpty());
197
QCOMPARE(entry->overrideUrl(), QString(""));
198
QCOMPARE(entry->tags(), QString("a b c"));
200
const TimeInfo ti = entry->timeInfo();
201
QCOMPARE(ti.lastModificationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 19, 25));
202
QCOMPARE(ti.creationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 13, 54));
203
QCOMPARE(ti.lastAccessTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 19, 25));
204
QCOMPARE(ti.expiryTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 12, 57));
205
QVERIFY(!ti.expires());
206
QCOMPARE(ti.usageCount(), 8);
207
QCOMPARE(ti.locationChanged(), MockClock::datetimeUtc(2010, 8, 25, 16, 13, 54));
209
QList<QString> attrs = entry->attributes()->keys();
210
QCOMPARE(entry->attributes()->value("Notes"), QString("Notes"));
211
QVERIFY(!entry->attributes()->isProtected("Notes"));
212
QVERIFY(attrs.removeOne("Notes"));
213
QCOMPARE(entry->attributes()->value("Password"), QString("Password"));
214
QVERIFY(!entry->attributes()->isProtected("Password"));
215
QVERIFY(attrs.removeOne("Password"));
216
QCOMPARE(entry->attributes()->value("Title"), QString("Sample Entry 1"));
217
QVERIFY(!entry->attributes()->isProtected("Title"));
218
QVERIFY(attrs.removeOne("Title"));
219
QCOMPARE(entry->attributes()->value("URL"), QString(""));
220
QVERIFY(entry->attributes()->isProtected("URL"));
221
QVERIFY(attrs.removeOne("URL"));
222
QCOMPARE(entry->attributes()->value("UserName"), QString("User Name"));
223
QVERIFY(entry->attributes()->isProtected("UserName"));
224
QVERIFY(attrs.removeOne("UserName"));
225
QVERIFY(attrs.isEmpty());
227
QCOMPARE(entry->title(), entry->attributes()->value("Title"));
228
QCOMPARE(entry->url(), entry->attributes()->value("URL"));
229
QCOMPARE(entry->username(), entry->attributes()->value("UserName"));
230
QCOMPARE(entry->password(), entry->attributes()->value("Password"));
231
QCOMPARE(entry->notes(), entry->attributes()->value("Notes"));
233
QCOMPARE(entry->attachments()->keys().size(), 1);
234
QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("abcdefghijk"));
235
QCOMPARE(entry->historyItems().at(0)->attachments()->keys().size(), 1);
236
QCOMPARE(entry->historyItems().at(0)->attachments()->value("myattach.txt"), QByteArray("0123456789"));
237
QCOMPARE(entry->historyItems().at(1)->attachments()->keys().size(), 1);
238
QCOMPARE(entry->historyItems().at(1)->attachments()->value("myattach.txt"), QByteArray("abcdefghijk"));
240
QCOMPARE(entry->autoTypeEnabled(), false);
241
QCOMPARE(entry->autoTypeObfuscation(), 0);
242
QCOMPARE(entry->defaultAutoTypeSequence(), QString(""));
243
QCOMPARE(entry->autoTypeAssociations()->size(), 1);
244
const AutoTypeAssociations::Association assoc1 = entry->autoTypeAssociations()->get(0);
245
QCOMPARE(assoc1.window, QString("Target Window"));
246
QCOMPARE(assoc1.sequence, QString(""));
249
void TestKeePass2Format::testXmlEntry2()
251
const Entry* entry = m_xmlDb->rootGroup()->entries().at(1);
253
QCOMPARE(entry->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("4jbADG37hkiLh2O0qUdaOQ==")));
254
QCOMPARE(entry->iconNumber(), 0);
255
QCOMPARE(entry->iconUuid(), QUuid::fromRfc4122(QByteArray::fromBase64("++vyI+daLk6omox4a6kQGA==")));
257
QCOMPARE(entry->foregroundColor(), QString("#FF0000"));
258
QCOMPARE(entry->backgroundColor(), QString("#FFFF00"));
259
QCOMPARE(entry->overrideUrl(), QString("http://override.net/"));
260
QCOMPARE(entry->tags(), QString(""));
262
const TimeInfo ti = entry->timeInfo();
263
QCOMPARE(ti.usageCount(), 7);
265
QList<QString> attrs = entry->attributes()->keys();
266
QCOMPARE(entry->attributes()->value("CustomString"), QString("isavalue"));
267
QVERIFY(attrs.removeOne("CustomString"));
268
QCOMPARE(entry->attributes()->value("Notes"), QString(""));
269
QVERIFY(attrs.removeOne("Notes"));
270
QCOMPARE(entry->attributes()->value("Password"), QString("Jer60Hz8o9XHvxBGcRqT"));
271
QVERIFY(attrs.removeOne("Password"));
272
QCOMPARE(entry->attributes()->value("Protected String"), QString("y"));
273
QVERIFY(attrs.removeOne("Protected String"));
274
QCOMPARE(entry->attributes()->value("Title"), QString("Sample Entry 2"));
275
QVERIFY(attrs.removeOne("Title"));
276
QCOMPARE(entry->attributes()->value("URL"), QString("http://www.keepassx.org/"));
277
QVERIFY(attrs.removeOne("URL"));
278
QCOMPARE(entry->attributes()->value("UserName"), QString("notDEFUSERNAME"));
279
QVERIFY(attrs.removeOne("UserName"));
280
QVERIFY(attrs.isEmpty());
282
QCOMPARE(entry->attachments()->keys().size(), 1);
283
QCOMPARE(QString::fromLatin1(entry->attachments()->value("myattach.txt")), QString("abcdefghijk"));
285
QCOMPARE(entry->autoTypeEnabled(), true);
286
QCOMPARE(entry->autoTypeObfuscation(), 1);
287
QCOMPARE(entry->defaultAutoTypeSequence(), QString("{USERNAME}{TAB}{PASSWORD}{ENTER}"));
288
QCOMPARE(entry->autoTypeAssociations()->size(), 2);
289
const AutoTypeAssociations::Association assoc1 = entry->autoTypeAssociations()->get(0);
290
QCOMPARE(assoc1.window, QString("Target Window"));
291
QCOMPARE(assoc1.sequence, QString("{Title}{UserName}"));
292
const AutoTypeAssociations::Association assoc2 = entry->autoTypeAssociations()->get(1);
293
QCOMPARE(assoc2.window, QString("Target Window 2"));
294
QCOMPARE(assoc2.sequence, QString("{Title}{UserName} test"));
297
void TestKeePass2Format::testXmlEntryHistory()
299
const Entry* entryMain = m_xmlDb->rootGroup()->entries().at(0);
300
QCOMPARE(entryMain->historyItems().size(), 2);
303
const Entry* entry = entryMain->historyItems().at(0);
304
QCOMPARE(entry->uuid(), entryMain->uuid());
305
QVERIFY(!entry->parent());
306
QCOMPARE(entry->timeInfo().lastModificationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 13, 54));
307
QCOMPARE(entry->timeInfo().usageCount(), 3);
308
QCOMPARE(entry->title(), QString("Sample Entry"));
309
QCOMPARE(entry->url(), QString("http://www.somesite.com/"));
313
const Entry* entry = entryMain->historyItems().at(1);
314
QCOMPARE(entry->uuid(), entryMain->uuid());
315
QVERIFY(!entry->parent());
316
QCOMPARE(entry->timeInfo().lastModificationTime(), MockClock::datetimeUtc(2010, 8, 25, 16, 15, 43));
317
QCOMPARE(entry->timeInfo().usageCount(), 7);
318
QCOMPARE(entry->title(), QString("Sample Entry 1"));
319
QCOMPARE(entry->url(), QString("http://www.somesite.com/"));
323
void TestKeePass2Format::testXmlDeletedObjects()
325
QList<DeletedObject> objList = m_xmlDb->deletedObjects();
326
DeletedObject delObj;
328
delObj = objList.takeFirst();
329
QCOMPARE(delObj.uuid, QUuid::fromRfc4122(QByteArray::fromBase64("5K/bzWCSmkCv5OZxYl4N/w==")));
330
QCOMPARE(delObj.deletionTime, MockClock::datetimeUtc(2010, 8, 25, 16, 14, 12));
332
delObj = objList.takeFirst();
333
QCOMPARE(delObj.uuid, QUuid::fromRfc4122(QByteArray::fromBase64("80h8uSNWgkKhKCp1TgXF7g==")));
334
QCOMPARE(delObj.deletionTime, MockClock::datetimeUtc(2010, 8, 25, 16, 14, 14));
336
QVERIFY(objList.isEmpty());
339
void TestKeePass2Format::testXmlBroken()
341
QFETCH(QString, baseName);
342
QFETCH(bool, strictMode);
343
QFETCH(bool, expectError);
345
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName);
346
QVERIFY(QFile::exists(xmlFile));
349
auto db = readXml(xmlFile, strictMode, hasError, errorString);
351
qWarning("Reader error: %s", qPrintable(errorString));
353
QCOMPARE(hasError, expectError);
357
void TestKeePass2Format::testXmlBroken_data()
359
QTest::addColumn<QString>("baseName");
360
QTest::addColumn<bool>("strictMode");
361
QTest::addColumn<bool>("expectError");
364
QTest::newRow("BrokenNoGroupUuid (strict)") << "BrokenNoGroupUuid" << true << true;
365
QTest::newRow("BrokenNoGroupUuid (not strict)") << "BrokenNoGroupUuid" << false << false;
366
QTest::newRow("BrokenNoEntryUuid (strict)") << "BrokenNoEntryUuid" << true << true;
367
QTest::newRow("BrokenNoEntryUuid (not strict)") << "BrokenNoEntryUuid" << false << false;
368
QTest::newRow("BrokenNoRootGroup (strict)") << "BrokenNoRootGroup" << true << true;
369
QTest::newRow("BrokenNoRootGroup (not strict)") << "BrokenNoRootGroup" << false << true;
370
QTest::newRow("BrokenTwoRoots (strict)") << "BrokenTwoRoots" << true << true;
371
QTest::newRow("BrokenTwoRoots (not strict)") << "BrokenTwoRoots" << false << true;
372
QTest::newRow("BrokenTwoRootGroups (strict)") << "BrokenTwoRootGroups" << true << true;
373
QTest::newRow("BrokenTwoRootGroups (not strict)") << "BrokenTwoRootGroups" << false << true;
374
QTest::newRow("BrokenGroupReference (strict)") << "BrokenGroupReference" << true << false;
375
QTest::newRow("BrokenGroupReference (not strict)") << "BrokenGroupReference" << false << false;
376
QTest::newRow("BrokenDeletedObjects (strict)") << "BrokenDeletedObjects" << true << true;
377
QTest::newRow("BrokenDeletedObjects (not strict)") << "BrokenDeletedObjects" << false << false;
378
QTest::newRow("BrokenDifferentEntryHistoryUuid (strict)") << "BrokenDifferentEntryHistoryUuid" << true << true;
379
QTest::newRow("BrokenDifferentEntryHistoryUuid (not strict)") << "BrokenDifferentEntryHistoryUuid" << false << false;
383
void TestKeePass2Format::testXmlEmptyUuids()
386
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "EmptyUuids");
387
QVERIFY(QFile::exists(xmlFile));
390
auto db = readXml(xmlFile, true, hasError, errorString);
392
qWarning("Reader error: %s", qPrintable(errorString));
397
void TestKeePass2Format::testXmlInvalidXmlChars()
399
QScopedPointer<Database> dbWrite(new Database());
401
QString strPlainInvalid =
402
QString().append(QChar(0x02)).append(QChar(0x19)).append(QChar(0xFFFE)).append(QChar(0xFFFF));
403
QString strPlainValid = QString()
407
.append(QChar(0xD7FF))
408
.append(QChar(0xE000))
409
.append(QChar(0xFFFD));
412
QString strSingleHighSurrogate1 = QString().append(QChar(0xD801));
413
QString strSingleHighSurrogate2 = QString().append(QChar(0x31)).append(QChar(0xD801)).append(QChar(0x32));
414
QString strHighHighSurrogate = QString().append(QChar(0xD801)).append(QChar(0xD801));
415
QString strSingleLowSurrogate1 = QString().append(QChar(0xDC37));
416
QString strSingleLowSurrogate2 = QString().append(QChar((0x31))).append(QChar(0xDC37)).append(QChar(0x32));
417
QString strLowLowSurrogate = QString().append(QChar(0xDC37)).append(QChar(0xDC37));
418
QString strSurrogateValid1 = QString().append(QChar(0xD801)).append(QChar(0xDC37));
419
QString strSurrogateValid2 =
420
QString().append(QChar(0x31)).append(QChar(0xD801)).append(QChar(0xDC37)).append(QChar(0x32));
422
auto entry = new Entry();
423
entry->setUuid(QUuid::createUuid());
424
entry->setGroup(dbWrite->rootGroup());
425
entry->attributes()->set("PlainInvalid", strPlainInvalid);
426
entry->attributes()->set("PlainValid", strPlainValid);
427
entry->attributes()->set("SingleHighSurrogate1", strSingleHighSurrogate1);
428
entry->attributes()->set("SingleHighSurrogate2", strSingleHighSurrogate2);
429
entry->attributes()->set("HighHighSurrogate", strHighHighSurrogate);
430
entry->attributes()->set("SingleLowSurrogate1", strSingleLowSurrogate1);
431
entry->attributes()->set("SingleLowSurrogate2", strSingleLowSurrogate2);
432
entry->attributes()->set("LowLowSurrogate", strLowLowSurrogate);
433
entry->attributes()->set("SurrogateValid1", strSurrogateValid1);
434
entry->attributes()->set("SurrogateValid2", strSurrogateValid2);
437
buffer.open(QIODevice::ReadWrite);
440
writeXml(&buffer, dbWrite.data(), hasError, errorString);
444
auto dbRead = readXml(&buffer, true, hasError, errorString);
446
qWarning("Database read error: %s", qPrintable(errorString));
449
QVERIFY(dbRead.data());
450
QCOMPARE(dbRead->rootGroup()->entries().size(), 1);
451
Entry* entryRead = dbRead->rootGroup()->entries().at(0);
452
EntryAttributes* attrRead = entryRead->attributes();
454
QCOMPARE(attrRead->value("PlainInvalid"), QString());
455
QCOMPARE(attrRead->value("PlainValid"), strPlainValid);
456
QCOMPARE(attrRead->value("SingleHighSurrogate1"), QString());
457
QCOMPARE(attrRead->value("SingleHighSurrogate2"), QString("12"));
458
QCOMPARE(attrRead->value("HighHighSurrogate"), QString());
459
QCOMPARE(attrRead->value("SingleLowSurrogate1"), QString());
460
QCOMPARE(attrRead->value("SingleLowSurrogate2"), QString("12"));
461
QCOMPARE(attrRead->value("LowLowSurrogate"), QString());
462
QCOMPARE(attrRead->value("SurrogateValid1"), strSurrogateValid1);
463
QCOMPARE(attrRead->value("SurrogateValid2"), strSurrogateValid2);
466
void TestKeePass2Format::testXmlRepairUuidHistoryItem()
468
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid");
469
QVERIFY(QFile::exists(xmlFile));
472
auto db = readXml(xmlFile, false, hasError, errorString);
474
qWarning("Database read error: %s", qPrintable(errorString));
478
QList<Entry*> entries = db->rootGroup()->entries();
479
QCOMPARE(entries.size(), 1);
480
Entry* entry = entries.at(0);
482
QList<Entry*> historyItems = entry->historyItems();
483
QCOMPARE(historyItems.size(), 1);
484
Entry* historyItem = historyItems.at(0);
486
QVERIFY(!entry->uuid().isNull());
487
QVERIFY(!historyItem->uuid().isNull());
488
QCOMPARE(historyItem->uuid(), entry->uuid());
491
void TestKeePass2Format::testReadBackTargetDb()
494
auto key = QSharedPointer<CompositeKey>::create();
495
key->addKey(QSharedPointer<PasswordKey>::create("test"));
500
m_kdbxTargetBuffer.seek(0);
501
m_kdbxTargetDb = QSharedPointer<Database>::create();
502
readKdbx(&m_kdbxTargetBuffer, key, m_kdbxTargetDb, hasError, errorString);
504
QFAIL(qPrintable(QString("Error while reading database: ").append(errorString)));
506
QVERIFY(m_kdbxTargetDb.data());
509
void TestKeePass2Format::testKdbxBasic()
511
QCOMPARE(m_kdbxTargetDb->metadata()->name(), m_kdbxSourceDb->metadata()->name());
512
QVERIFY(m_kdbxTargetDb->rootGroup());
513
QCOMPARE(m_kdbxTargetDb->rootGroup()->children()[0]->name(), m_kdbxSourceDb->rootGroup()->children()[0]->name());
514
QCOMPARE(m_kdbxTargetDb->rootGroup()->notes(), m_kdbxSourceDb->rootGroup()->notes());
515
QCOMPARE(m_kdbxTargetDb->rootGroup()->children()[0]->notes(), m_kdbxSourceDb->rootGroup()->children()[0]->notes());
518
void TestKeePass2Format::testKdbxProtectedAttributes()
520
QCOMPARE(m_kdbxTargetDb->rootGroup()->entries().size(), 1);
521
Entry* entry = m_kdbxTargetDb->rootGroup()->entries().at(0);
522
QCOMPARE(entry->attributes()->value("test"), QString("protectedTest"));
523
QCOMPARE(entry->attributes()->isProtected("test"), true);
526
void TestKeePass2Format::testKdbxAttachments()
528
Entry* entry = m_kdbxTargetDb->rootGroup()->entries().at(0);
529
QCOMPARE(entry->attachments()->keys().size(), 2);
530
QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("this is an attachment"));
531
QCOMPARE(entry->attachments()->value("aaa.txt"), QByteArray("also an attachment"));
534
void TestKeePass2Format::testKdbxNonAsciiPasswords()
536
QCOMPARE(m_kdbxTargetDb->rootGroup()->entries()[0]->password(),
537
m_kdbxSourceDb->rootGroup()->entries()[0]->password());
540
void TestKeePass2Format::testKdbxDeviceFailure()
542
auto key = QSharedPointer<CompositeKey>::create();
543
key->addKey(QSharedPointer<PasswordKey>::create("test"));
544
QScopedPointer<Database> db(new Database());
547
db->setCompressionAlgorithm(Database::CompressionNone);
549
auto entry = new Entry();
550
entry->setParent(db->rootGroup());
551
QByteArray attachment(4096, 'Z');
552
entry->attachments()->set("test", attachment);
554
FailDevice failDevice(512);
555
QVERIFY(failDevice.open(QIODevice::WriteOnly));
558
writeKdbx(&failDevice, db.data(), hasError, errorString);
560
QCOMPARE(errorString, QString("FAILDEVICE"));
563
Q_DECLARE_METATYPE(QSharedPointer<CompositeKey>)
565
void TestKeePass2Format::testKdbxKeyChange()
567
QFETCH(QSharedPointer<CompositeKey>, key1);
568
QFETCH(QSharedPointer<CompositeKey>, key2);
575
buffer.open(QBuffer::ReadWrite);
577
QSharedPointer<Database> db(new Database());
578
db->changeKdf(fastKdf(KeePass2::uuidToKdf(m_kdbxSourceDb->kdf()->uuid())));
580
db->setRootGroup(m_kdbxSourceDb->rootGroup()->clone(Entry::CloneNoFlags, Group::CloneIncludeEntries));
584
writeKdbx(&buffer, db.data(), hasError, errorString);
586
QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
590
db = QSharedPointer<Database>::create();
592
readKdbx(&buffer, key1, db, hasError, errorString);
594
QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
603
writeKdbx(&buffer, db.data(), hasError, errorString);
605
QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
609
db = QSharedPointer<Database>::create();
611
readKdbx(&buffer, key2, db, hasError, errorString);
613
QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
616
QVERIFY(db->rootGroup() != m_kdbxSourceDb->rootGroup());
617
QVERIFY(db->rootGroup()->uuid() == m_kdbxSourceDb->rootGroup()->uuid());
620
void TestKeePass2Format::testKdbxKeyChange_data()
622
QTest::addColumn<QSharedPointer<CompositeKey>>("key1");
623
QTest::addColumn<QSharedPointer<CompositeKey>>("key2");
625
auto passwordKey1 = QSharedPointer<PasswordKey>::create("abc");
626
auto passwordKey2 = QSharedPointer<PasswordKey>::create("def");
628
QByteArray fileKeyBytes1("uvw");
629
QBuffer fileKeyBuffer1(&fileKeyBytes1);
630
fileKeyBuffer1.open(QBuffer::ReadOnly);
631
auto fileKey1 = QSharedPointer<FileKey>::create();
632
fileKey1->load(&fileKeyBuffer1);
634
QByteArray fileKeyBytes2("xzy");
635
QBuffer fileKeyBuffer2(&fileKeyBytes1);
636
fileKeyBuffer2.open(QBuffer::ReadOnly);
637
auto fileKey2 = QSharedPointer<FileKey>::create();
638
fileKey2->load(&fileKeyBuffer2);
640
auto crKey1 = QSharedPointer<MockChallengeResponseKey>::create(QByteArray("123"));
641
auto crKey2 = QSharedPointer<MockChallengeResponseKey>::create(QByteArray("456"));
644
auto compositeKey0 = QSharedPointer<CompositeKey>::create();
647
auto compositeKey1_1 = QSharedPointer<CompositeKey>::create();
648
compositeKey1_1->addKey(passwordKey1);
649
compositeKey1_1->addKey(fileKey1);
650
compositeKey1_1->addChallengeResponseKey(crKey1);
651
auto compositeKey1_2 = QSharedPointer<CompositeKey>::create();
652
compositeKey1_2->addKey(passwordKey2);
653
compositeKey1_2->addKey(fileKey2);
654
compositeKey1_2->addChallengeResponseKey(crKey2);
656
QTest::newRow("Change: Empty Key -> Full Key") << compositeKey0 << compositeKey1_1;
657
QTest::newRow("Change: Full Key -> Empty Key") << compositeKey1_1 << compositeKey0;
658
QTest::newRow("Change: Full Key 1 -> Full Key 2") << compositeKey1_1 << compositeKey1_2;
661
auto compositeKey2_1 = QSharedPointer<CompositeKey>::create();
662
compositeKey2_1->addKey(passwordKey1);
663
auto compositeKey2_2 = QSharedPointer<CompositeKey>::create();
664
compositeKey2_2->addKey(passwordKey2);
666
QTest::newRow("Change: Password -> Empty Key") << compositeKey2_1 << compositeKey0;
667
QTest::newRow("Change: Empty Key -> Password") << compositeKey0 << compositeKey2_1;
668
QTest::newRow("Change: Full Key -> Password 1") << compositeKey1_1 << compositeKey2_1;
669
QTest::newRow("Change: Full Key -> Password 2") << compositeKey1_1 << compositeKey2_2;
670
QTest::newRow("Change: Password 1 -> Full Key") << compositeKey2_1 << compositeKey1_1;
671
QTest::newRow("Change: Password 2 -> Full Key") << compositeKey2_2 << compositeKey1_1;
672
QTest::newRow("Change: Password 1 -> Password 2") << compositeKey2_1 << compositeKey2_2;
675
auto compositeKey3_1 = QSharedPointer<CompositeKey>::create();
676
compositeKey3_1->addKey(fileKey1);
677
auto compositeKey3_2 = QSharedPointer<CompositeKey>::create();
678
compositeKey3_2->addKey(fileKey2);
680
QTest::newRow("Change: Key File -> Empty Key") << compositeKey3_1 << compositeKey0;
681
QTest::newRow("Change: Empty Key -> Key File") << compositeKey0 << compositeKey3_1;
682
QTest::newRow("Change: Full Key -> Key File 1") << compositeKey1_1 << compositeKey3_1;
683
QTest::newRow("Change: Full Key -> Key File 2") << compositeKey1_1 << compositeKey3_2;
684
QTest::newRow("Change: Key File 1 -> Full Key") << compositeKey3_1 << compositeKey1_1;
685
QTest::newRow("Change: Key File 2 -> Full Key") << compositeKey3_2 << compositeKey1_1;
686
QTest::newRow("Change: Key File 1 -> Key File 2") << compositeKey3_1 << compositeKey3_2;
689
auto compositeKey4_1 = QSharedPointer<CompositeKey>::create();
690
compositeKey4_1->addChallengeResponseKey(crKey1);
691
auto compositeKey4_2 = QSharedPointer<CompositeKey>::create();
692
compositeKey4_2->addChallengeResponseKey(crKey2);
694
QTest::newRow("Change: CR Key -> Empty Key") << compositeKey4_1 << compositeKey0;
695
QTest::newRow("Change: Empty Key -> CR Key") << compositeKey0 << compositeKey4_1;
696
QTest::newRow("Change: Full Key -> CR Key 1") << compositeKey1_1 << compositeKey4_1;
697
QTest::newRow("Change: Full Key -> CR Key 2") << compositeKey1_1 << compositeKey4_2;
698
QTest::newRow("Change: CR Key 1 -> Full Key") << compositeKey4_1 << compositeKey1_1;
699
QTest::newRow("Change: CR Key 2 -> Full Key") << compositeKey4_2 << compositeKey1_1;
700
QTest::newRow("Change: CR Key 1 -> CR Key 2") << compositeKey4_1 << compositeKey4_2;
703
QTest::newRow("Change: Password -> Key File") << compositeKey2_1 << compositeKey3_1;
704
QTest::newRow("Change: Key File -> Password") << compositeKey3_1 << compositeKey2_1;
705
QTest::newRow("Change: Password -> Key File") << compositeKey2_1 << compositeKey3_1;
706
QTest::newRow("Change: Key File -> Password") << compositeKey3_1 << compositeKey2_1;
707
QTest::newRow("Change: Password -> CR Key") << compositeKey2_1 << compositeKey4_1;
708
QTest::newRow("Change: CR Key -> Password") << compositeKey4_1 << compositeKey2_1;
709
QTest::newRow("Change: Key File -> CR Key") << compositeKey3_1 << compositeKey4_1;
710
QTest::newRow("Change: CR Key -> Key File") << compositeKey4_1 << compositeKey3_1;
713
auto compositeKey5_1 = QSharedPointer<CompositeKey>::create();
714
compositeKey5_1->addKey(fileKey1);
715
compositeKey5_1->addChallengeResponseKey(crKey1);
716
auto compositeKey5_2 = QSharedPointer<CompositeKey>::create();
717
compositeKey5_2->addKey(passwordKey1);
718
compositeKey5_2->addChallengeResponseKey(crKey1);
719
auto compositeKey5_3 = QSharedPointer<CompositeKey>::create();
720
compositeKey5_3->addKey(passwordKey1);
721
compositeKey5_3->addKey(fileKey1);
723
QTest::newRow("Change: Full Key -> No Password") << compositeKey1_1 << compositeKey5_1;
724
QTest::newRow("Change: No Password -> Full Key") << compositeKey5_1 << compositeKey1_1;
725
QTest::newRow("Change: Full Key -> No Key File") << compositeKey1_1 << compositeKey5_2;
726
QTest::newRow("Change: No Key File -> Full Key") << compositeKey5_2 << compositeKey1_1;
727
QTest::newRow("Change: Full Key -> No CR Key") << compositeKey1_1 << compositeKey5_3;
728
QTest::newRow("Change: No CR Key -> Full Key") << compositeKey5_3 << compositeKey1_1;
734
void TestKeePass2Format::testDuplicateAttachments()
736
auto db = QSharedPointer<Database>::create();
737
db->setKey(QSharedPointer<CompositeKey>::create());
739
const QByteArray attachment1("abc");
740
const QByteArray attachment2("def");
741
const QByteArray attachment3("ghi");
743
auto entry1 = new Entry();
744
entry1->setGroup(db->rootGroup());
745
entry1->setUuid(QUuid::fromRfc4122("aaaaaaaaaaaaaaaa"));
746
entry1->attachments()->set("a", attachment1);
748
auto entry2 = new Entry();
749
entry2->setGroup(db->rootGroup());
750
entry2->setUuid(QUuid::fromRfc4122("bbbbbbbbbbbbbbbb"));
751
entry2->attachments()->set("b1", attachment1);
752
entry2->beginUpdate();
753
entry2->attachments()->set("b2", attachment1);
755
entry2->beginUpdate();
756
entry2->attachments()->set("b3", attachment2);
758
entry2->beginUpdate();
759
entry2->attachments()->set("b4", attachment2);
762
auto entry3 = new Entry();
763
entry3->setGroup(db->rootGroup());
764
entry3->setUuid(QUuid::fromRfc4122("cccccccccccccccc"));
765
entry3->attachments()->set("c1", attachment2);
766
entry3->attachments()->set("c2", attachment2);
767
entry3->attachments()->set("c3", attachment3);
770
buffer.open(QBuffer::ReadWrite);
772
bool hasError = false;
774
writeKdbx(&buffer, db.data(), hasError, errorString);
776
QFAIL(qPrintable(QString("Error while writing database: %1").arg(errorString)));
780
readKdbx(&buffer, QSharedPointer<CompositeKey>::create(), db, hasError, errorString);
782
QFAIL(qPrintable(QString("Error while reading database: %1").arg(errorString)));
785
QCOMPARE(db->rootGroup()->entries()[0]->attachments()->value("a"), attachment1);
787
QCOMPARE(db->rootGroup()->entries()[1]->attachments()->value("b1"), attachment1);
788
QCOMPARE(db->rootGroup()->entries()[1]->attachments()->value("b2"), attachment1);
789
QCOMPARE(db->rootGroup()->entries()[1]->attachments()->value("b3"), attachment2);
790
QCOMPARE(db->rootGroup()->entries()[1]->attachments()->value("b4"), attachment2);
791
QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[0]->attachments()->value("b1"), attachment1);
792
QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[1]->attachments()->value("b1"), attachment1);
793
QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[1]->attachments()->value("b2"), attachment1);
794
QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[2]->attachments()->value("b1"), attachment1);
795
QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[2]->attachments()->value("b2"), attachment1);
796
QCOMPARE(db->rootGroup()->entries()[1]->historyItems()[2]->attachments()->value("b3"), attachment2);
798
QCOMPARE(db->rootGroup()->entries()[2]->attachments()->value("c1"), attachment2);
799
QCOMPARE(db->rootGroup()->entries()[2]->attachments()->value("c2"), attachment2);
800
QCOMPARE(db->rootGroup()->entries()[2]->attachments()->value("c3"), attachment3);
806
QSharedPointer<Kdf> fastKdf(QSharedPointer<Kdf> kdf)
810
if (kdf->uuid() == KeePass2::KDF_ARGON2D or kdf->uuid() == KeePass2::KDF_ARGON2ID) {
811
kdf->processParameters({{KeePass2::KDFPARAM_ARGON2_MEMORY, 1024}, {KeePass2::KDFPARAM_ARGON2_PARALLELISM, 1}});