keepassxc

Форк
0
/
TestKeePass2Format.cpp 
815 строк · 36.6 Кб
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

18
#include "TestKeePass2Format.h"
19
#include "mock/MockClock.h"
20

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"
27

28
#include "FailDevice.h"
29
#include "config-keepassx-tests.h"
30
#include <QtTest>
31

32
void TestKeePass2Format::initTestCase()
33
{
34
    QVERIFY(Crypto::init());
35

36
    // read raw XML database
37
    bool hasError;
38
    QString errorString;
39
    m_xmlDb = readXml(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"), true, hasError, errorString);
40
    if (hasError) {
41
        QFAIL(qPrintable(QString("Error while reading XML: ").append(errorString)));
42
    }
43
    QVERIFY(m_xmlDb.data());
44

45
    // construct and write KDBX to buffer
46
    auto key = QSharedPointer<CompositeKey>::create();
47
    key->addKey(QSharedPointer<PasswordKey>::create("test"));
48

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);
68

69
    m_kdbxTargetBuffer.open(QBuffer::ReadWrite);
70
    writeKdbx(&m_kdbxTargetBuffer, m_kdbxSourceDb.data(), hasError, errorString);
71
    if (hasError) {
72
        QFAIL(qPrintable(QString("Error while writing database: ").append(errorString)));
73
    }
74

75
    // call sub class init method
76
    initTestCaseImpl();
77
}
78

79
void TestKeePass2Format::testXmlMetadata()
80
{
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);
109
}
110

111
void TestKeePass2Format::testXmlCustomIcons()
112
{
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;
117

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"));
120
}
121

122
void TestKeePass2Format::testXmlGroupRoot()
123
{
124
    const Group* group = m_xmlDb->rootGroup();
125
    QVERIFY(group);
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));
147

148
    QCOMPARE(group->entries().size(), 2);
149
}
150

151
void TestKeePass2Format::testXmlGroup1()
152
{
153
    const Group* group = m_xmlDb->rootGroup()->children().at(0);
154

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());
165
}
166

167
void TestKeePass2Format::testXmlGroup2()
168
{
169
    const Group* group = m_xmlDb->rootGroup()->children().at(1);
170

171
    QCOMPARE(group->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("1h4NtL5DK0yVyvaEnN//4A==")));
172
    QCOMPARE(group->name(), QString("Windows"));
173
    QCOMPARE(group->isExpanded(), false);
174

175
    QCOMPARE(group->children().size(), 1);
176
    const Group* child = group->children().first();
177

178
    QCOMPARE(child->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("HoYE/BjLfUSW257pCHJ/eA==")));
179
    QCOMPARE(child->name(), QString("Subsub"));
180
    QCOMPARE(child->entries().size(), 1);
181

182
    const Entry* entry = child->entries().first();
183
    QCOMPARE(entry->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("GZpdQvGXOU2kaKRL/IVAGg==")));
184
    QCOMPARE(entry->title(), QString("Subsub Entry"));
185
}
186

187
void TestKeePass2Format::testXmlEntry1()
188
{
189
    const Entry* entry = m_xmlDb->rootGroup()->entries().at(0);
190

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"));
199

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));
208

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());
226

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"));
232

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"));
239

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(""));
247
}
248

249
void TestKeePass2Format::testXmlEntry2()
250
{
251
    const Entry* entry = m_xmlDb->rootGroup()->entries().at(1);
252

253
    QCOMPARE(entry->uuid(), QUuid::fromRfc4122(QByteArray::fromBase64("4jbADG37hkiLh2O0qUdaOQ==")));
254
    QCOMPARE(entry->iconNumber(), 0);
255
    QCOMPARE(entry->iconUuid(), QUuid::fromRfc4122(QByteArray::fromBase64("++vyI+daLk6omox4a6kQGA==")));
256
    // TODO: test entry->icon()
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(""));
261

262
    const TimeInfo ti = entry->timeInfo();
263
    QCOMPARE(ti.usageCount(), 7);
264

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")); // TODO: should have a protection attribute
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());
281

282
    QCOMPARE(entry->attachments()->keys().size(), 1);
283
    QCOMPARE(QString::fromLatin1(entry->attachments()->value("myattach.txt")), QString("abcdefghijk"));
284

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"));
295
}
296

297
void TestKeePass2Format::testXmlEntryHistory()
298
{
299
    const Entry* entryMain = m_xmlDb->rootGroup()->entries().at(0);
300
    QCOMPARE(entryMain->historyItems().size(), 2);
301

302
    {
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/"));
310
    }
311

312
    {
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/"));
320
    }
321
}
322

323
void TestKeePass2Format::testXmlDeletedObjects()
324
{
325
    QList<DeletedObject> objList = m_xmlDb->deletedObjects();
326
    DeletedObject delObj;
327

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));
331

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));
335

336
    QVERIFY(objList.isEmpty());
337
}
338

339
void TestKeePass2Format::testXmlBroken()
340
{
341
    QFETCH(QString, baseName);
342
    QFETCH(bool, strictMode);
343
    QFETCH(bool, expectError);
344

345
    QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName);
346
    QVERIFY(QFile::exists(xmlFile));
347
    bool hasError;
348
    QString errorString;
349
    auto db = readXml(xmlFile, strictMode, hasError, errorString);
350
    if (hasError) {
351
        qWarning("Reader error: %s", qPrintable(errorString));
352
    }
353
    QCOMPARE(hasError, expectError);
354
}
355

356
// clang-format off
357
void TestKeePass2Format::testXmlBroken_data()
358
{
359
    QTest::addColumn<QString>("baseName");
360
    QTest::addColumn<bool>("strictMode");
361
    QTest::addColumn<bool>("expectError");
362

363
    //                                                                testfile                            strict?  error?
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;
380
}
381
// clang-format on
382

383
void TestKeePass2Format::testXmlEmptyUuids()
384
{
385

386
    QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "EmptyUuids");
387
    QVERIFY(QFile::exists(xmlFile));
388
    bool hasError;
389
    QString errorString;
390
    auto db = readXml(xmlFile, true, hasError, errorString);
391
    if (hasError) {
392
        qWarning("Reader error: %s", qPrintable(errorString));
393
    }
394
    QVERIFY(!hasError);
395
}
396

397
void TestKeePass2Format::testXmlInvalidXmlChars()
398
{
399
    QScopedPointer<Database> dbWrite(new Database());
400

401
    QString strPlainInvalid =
402
        QString().append(QChar(0x02)).append(QChar(0x19)).append(QChar(0xFFFE)).append(QChar(0xFFFF));
403
    QString strPlainValid = QString()
404
                                .append(QChar(0x09))
405
                                .append(QChar(0x0A))
406
                                .append(QChar(0x20))
407
                                .append(QChar(0xD7FF))
408
                                .append(QChar(0xE000))
409
                                .append(QChar(0xFFFD));
410
    // U+10437 in UTF-16: D801 DC37
411
    //                    high low  surrogate
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));
421

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);
435

436
    QBuffer buffer;
437
    buffer.open(QIODevice::ReadWrite);
438
    bool hasError;
439
    QString errorString;
440
    writeXml(&buffer, dbWrite.data(), hasError, errorString);
441
    QVERIFY(!hasError);
442
    buffer.seek(0);
443

444
    auto dbRead = readXml(&buffer, true, hasError, errorString);
445
    if (hasError) {
446
        qWarning("Database read error: %s", qPrintable(errorString));
447
    }
448
    QVERIFY(!hasError);
449
    QVERIFY(dbRead.data());
450
    QCOMPARE(dbRead->rootGroup()->entries().size(), 1);
451
    Entry* entryRead = dbRead->rootGroup()->entries().at(0);
452
    EntryAttributes* attrRead = entryRead->attributes();
453

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);
464
}
465

466
void TestKeePass2Format::testXmlRepairUuidHistoryItem()
467
{
468
    QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid");
469
    QVERIFY(QFile::exists(xmlFile));
470
    bool hasError;
471
    QString errorString;
472
    auto db = readXml(xmlFile, false, hasError, errorString);
473
    if (hasError) {
474
        qWarning("Database read error: %s", qPrintable(errorString));
475
    }
476
    QVERIFY(!hasError);
477

478
    QList<Entry*> entries = db->rootGroup()->entries();
479
    QCOMPARE(entries.size(), 1);
480
    Entry* entry = entries.at(0);
481

482
    QList<Entry*> historyItems = entry->historyItems();
483
    QCOMPARE(historyItems.size(), 1);
484
    Entry* historyItem = historyItems.at(0);
485

486
    QVERIFY(!entry->uuid().isNull());
487
    QVERIFY(!historyItem->uuid().isNull());
488
    QCOMPARE(historyItem->uuid(), entry->uuid());
489
}
490

491
void TestKeePass2Format::testReadBackTargetDb()
492
{
493
    // read back previously constructed KDBX
494
    auto key = QSharedPointer<CompositeKey>::create();
495
    key->addKey(QSharedPointer<PasswordKey>::create("test"));
496

497
    bool hasError;
498
    QString errorString;
499

500
    m_kdbxTargetBuffer.seek(0);
501
    m_kdbxTargetDb = QSharedPointer<Database>::create();
502
    readKdbx(&m_kdbxTargetBuffer, key, m_kdbxTargetDb, hasError, errorString);
503
    if (hasError) {
504
        QFAIL(qPrintable(QString("Error while reading database: ").append(errorString)));
505
    }
506
    QVERIFY(m_kdbxTargetDb.data());
507
}
508

509
void TestKeePass2Format::testKdbxBasic()
510
{
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());
516
}
517

518
void TestKeePass2Format::testKdbxProtectedAttributes()
519
{
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);
524
}
525

526
void TestKeePass2Format::testKdbxAttachments()
527
{
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"));
532
}
533

534
void TestKeePass2Format::testKdbxNonAsciiPasswords()
535
{
536
    QCOMPARE(m_kdbxTargetDb->rootGroup()->entries()[0]->password(),
537
             m_kdbxSourceDb->rootGroup()->entries()[0]->password());
538
}
539

540
void TestKeePass2Format::testKdbxDeviceFailure()
541
{
542
    auto key = QSharedPointer<CompositeKey>::create();
543
    key->addKey(QSharedPointer<PasswordKey>::create("test"));
544
    QScopedPointer<Database> db(new Database());
545
    db->setKey(key);
546
    // Disable compression so we write a predictable number of bytes.
547
    db->setCompressionAlgorithm(Database::CompressionNone);
548

549
    auto entry = new Entry();
550
    entry->setParent(db->rootGroup());
551
    QByteArray attachment(4096, 'Z');
552
    entry->attachments()->set("test", attachment);
553

554
    FailDevice failDevice(512);
555
    QVERIFY(failDevice.open(QIODevice::WriteOnly));
556
    bool hasError;
557
    QString errorString;
558
    writeKdbx(&failDevice, db.data(), hasError, errorString);
559
    QVERIFY(hasError);
560
    QCOMPARE(errorString, QString("FAILDEVICE"));
561
}
562

563
Q_DECLARE_METATYPE(QSharedPointer<CompositeKey>)
564

565
void TestKeePass2Format::testKdbxKeyChange()
566
{
567
    QFETCH(QSharedPointer<CompositeKey>, key1);
568
    QFETCH(QSharedPointer<CompositeKey>, key2);
569

570
    bool hasError;
571
    QString errorString;
572

573
    // write new database
574
    QBuffer buffer;
575
    buffer.open(QBuffer::ReadWrite);
576
    buffer.seek(0);
577
    QSharedPointer<Database> db(new Database());
578
    db->changeKdf(fastKdf(KeePass2::uuidToKdf(m_kdbxSourceDb->kdf()->uuid())));
579
    auto oldGroup =
580
        db->setRootGroup(m_kdbxSourceDb->rootGroup()->clone(Entry::CloneNoFlags, Group::CloneIncludeEntries));
581
    delete oldGroup;
582

583
    db->setKey(key1);
584
    writeKdbx(&buffer, db.data(), hasError, errorString);
585
    if (hasError) {
586
        QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
587
    }
588

589
    // read database
590
    db = QSharedPointer<Database>::create();
591
    buffer.seek(0);
592
    readKdbx(&buffer, key1, db, hasError, errorString);
593
    if (hasError) {
594
        QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
595
    }
596
    QVERIFY(db.data());
597

598
    // change key
599
    db->setKey(key2);
600

601
    // write database
602
    buffer.seek(0);
603
    writeKdbx(&buffer, db.data(), hasError, errorString);
604
    if (hasError) {
605
        QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
606
    }
607

608
    // read database
609
    db = QSharedPointer<Database>::create();
610
    buffer.seek(0);
611
    readKdbx(&buffer, key2, db, hasError, errorString);
612
    if (hasError) {
613
        QFAIL(qPrintable(QStringLiteral("Error while reading database: ").append(errorString)));
614
    }
615
    QVERIFY(db.data());
616
    QVERIFY(db->rootGroup() != m_kdbxSourceDb->rootGroup());
617
    QVERIFY(db->rootGroup()->uuid() == m_kdbxSourceDb->rootGroup()->uuid());
618
}
619

620
void TestKeePass2Format::testKdbxKeyChange_data()
621
{
622
    QTest::addColumn<QSharedPointer<CompositeKey>>("key1");
623
    QTest::addColumn<QSharedPointer<CompositeKey>>("key2");
624

625
    auto passwordKey1 = QSharedPointer<PasswordKey>::create("abc");
626
    auto passwordKey2 = QSharedPointer<PasswordKey>::create("def");
627

628
    QByteArray fileKeyBytes1("uvw");
629
    QBuffer fileKeyBuffer1(&fileKeyBytes1);
630
    fileKeyBuffer1.open(QBuffer::ReadOnly);
631
    auto fileKey1 = QSharedPointer<FileKey>::create();
632
    fileKey1->load(&fileKeyBuffer1);
633

634
    QByteArray fileKeyBytes2("xzy");
635
    QBuffer fileKeyBuffer2(&fileKeyBytes1);
636
    fileKeyBuffer2.open(QBuffer::ReadOnly);
637
    auto fileKey2 = QSharedPointer<FileKey>::create();
638
    fileKey2->load(&fileKeyBuffer2);
639

640
    auto crKey1 = QSharedPointer<MockChallengeResponseKey>::create(QByteArray("123"));
641
    auto crKey2 = QSharedPointer<MockChallengeResponseKey>::create(QByteArray("456"));
642

643
    // empty key
644
    auto compositeKey0 = QSharedPointer<CompositeKey>::create();
645

646
    // all in
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);
655

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;
659

660
    // only password
661
    auto compositeKey2_1 = QSharedPointer<CompositeKey>::create();
662
    compositeKey2_1->addKey(passwordKey1);
663
    auto compositeKey2_2 = QSharedPointer<CompositeKey>::create();
664
    compositeKey2_2->addKey(passwordKey2);
665

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;
673

674
    // only key file
675
    auto compositeKey3_1 = QSharedPointer<CompositeKey>::create();
676
    compositeKey3_1->addKey(fileKey1);
677
    auto compositeKey3_2 = QSharedPointer<CompositeKey>::create();
678
    compositeKey3_2->addKey(fileKey2);
679

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;
687

688
    // only cr key
689
    auto compositeKey4_1 = QSharedPointer<CompositeKey>::create();
690
    compositeKey4_1->addChallengeResponseKey(crKey1);
691
    auto compositeKey4_2 = QSharedPointer<CompositeKey>::create();
692
    compositeKey4_2->addChallengeResponseKey(crKey2);
693

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;
701

702
    // rotate
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;
711

712
    // leave one out
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);
722

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;
729
}
730

731
/**
732
 * Test for catching mapping errors with duplicate attachments.
733
 */
734
void TestKeePass2Format::testDuplicateAttachments()
735
{
736
    auto db = QSharedPointer<Database>::create();
737
    db->setKey(QSharedPointer<CompositeKey>::create());
738

739
    const QByteArray attachment1("abc");
740
    const QByteArray attachment2("def");
741
    const QByteArray attachment3("ghi");
742

743
    auto entry1 = new Entry();
744
    entry1->setGroup(db->rootGroup());
745
    entry1->setUuid(QUuid::fromRfc4122("aaaaaaaaaaaaaaaa"));
746
    entry1->attachments()->set("a", attachment1);
747

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);
754
    entry2->endUpdate();
755
    entry2->beginUpdate();
756
    entry2->attachments()->set("b3", attachment2);
757
    entry2->endUpdate();
758
    entry2->beginUpdate();
759
    entry2->attachments()->set("b4", attachment2);
760
    entry2->endUpdate();
761

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);
768

769
    QBuffer buffer;
770
    buffer.open(QBuffer::ReadWrite);
771

772
    bool hasError = false;
773
    QString errorString;
774
    writeKdbx(&buffer, db.data(), hasError, errorString);
775
    if (hasError) {
776
        QFAIL(qPrintable(QString("Error while writing database: %1").arg(errorString)));
777
    }
778

779
    buffer.seek(0);
780
    readKdbx(&buffer, QSharedPointer<CompositeKey>::create(), db, hasError, errorString);
781
    if (hasError) {
782
        QFAIL(qPrintable(QString("Error while reading database: %1").arg(errorString)));
783
    }
784

785
    QCOMPARE(db->rootGroup()->entries()[0]->attachments()->value("a"), attachment1);
786

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);
797

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);
801
}
802

803
/**
804
 * Fast "dummy" KDF
805
 */
806
QSharedPointer<Kdf> fastKdf(QSharedPointer<Kdf> kdf)
807
{
808
    kdf->setRounds(1);
809

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}});
812
    }
813

814
    return kdf;
815
}
816

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.