keepassxc

Форк
0
/
TestKeys.cpp 
329 строк · 10.8 Кб
1
/*
2
 *  Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
3
 *  Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU General Public License as published by
7
 *  the Free Software Foundation, either version 2 or (at your option)
8
 *  version 3 of the License.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU General Public License
16
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
#include "TestKeys.h"
20

21
#include <QBuffer>
22
#include <QTest>
23

24
#include "config-keepassx-tests.h"
25

26
#include "core/Database.h"
27
#include "core/Metadata.h"
28
#include "crypto/Crypto.h"
29
#include "crypto/CryptoHash.h"
30
#include "crypto/kdf/AesKdf.h"
31
#include "format/KeePass2Reader.h"
32
#include "format/KeePass2Writer.h"
33
#include "keys/CompositeKey.h"
34
#include "keys/FileKey.h"
35
#include "keys/PasswordKey.h"
36
#include "mock/MockChallengeResponseKey.h"
37

38
QTEST_GUILESS_MAIN(TestKeys)
39
Q_DECLARE_METATYPE(FileKey::Type);
40

41
void TestKeys::initTestCase()
42
{
43
    QVERIFY(Crypto::init());
44
}
45

46
void TestKeys::testComposite()
47
{
48
    auto compositeKey1 = QSharedPointer<CompositeKey>::create();
49
    auto passwordKey1 = QSharedPointer<PasswordKey>::create();
50
    auto passwordKey2 = QSharedPointer<PasswordKey>::create("test");
51

52
    // make sure that addKey() creates a copy of the keys
53
    compositeKey1->addKey(passwordKey1);
54
    compositeKey1->addKey(passwordKey2);
55

56
    AesKdf kdf;
57
    kdf.setRounds(1);
58
    QByteArray transformed1;
59
    QVERIFY(compositeKey1->transform(kdf, transformed1));
60
    QCOMPARE(transformed1.size(), 32);
61

62
    QScopedPointer<CompositeKey> compositeKey3(new CompositeKey());
63
    QScopedPointer<CompositeKey> compositeKey4(new CompositeKey());
64

65
    // test clear()
66
    compositeKey3->addKey(QSharedPointer<PasswordKey>::create("test"));
67
    compositeKey3->clear();
68
    QCOMPARE(compositeKey3->rawKey(), compositeKey4->rawKey());
69

70
    // Test serialization
71
    auto data = compositeKey1->serialize();
72
    compositeKey3->deserialize(data);
73
    QCOMPARE(compositeKey1->rawKey(), compositeKey3->rawKey());
74
}
75

76
void TestKeys::testFileKey()
77
{
78
    QFETCH(FileKey::Type, type);
79
    QFETCH(QString, keyExt);
80
    QFETCH(bool, fileKeyOk);
81

82
    QString name = QString("FileKey").append(QTest::currentDataTag());
83

84
    KeePass2Reader reader;
85

86
    QString dbFilename = QString("%1/%2.kdbx").arg(QString(KEEPASSX_TEST_DATA_DIR), name);
87
    QString keyFilename = QString("%1/%2.%3").arg(QString(KEEPASSX_TEST_DATA_DIR), name, keyExt);
88

89
    auto compositeKey = QSharedPointer<CompositeKey>::create();
90
    auto fileKey = QSharedPointer<FileKey>::create();
91
    QString error;
92
    QVERIFY(fileKey->load(keyFilename, &error) == fileKeyOk);
93
    QVERIFY(error.isEmpty() == fileKeyOk);
94
    QCOMPARE(fileKey->type(), type);
95

96
    // Test for same behaviour on code path without error parameter
97
    auto fileKeyNoErrorParam = QSharedPointer<FileKey>::create();
98
    QVERIFY(fileKeyNoErrorParam->load(keyFilename) == fileKeyOk);
99
    QCOMPARE(fileKeyNoErrorParam->type(), type);
100

101
    QCOMPARE(fileKey->rawKey(), fileKeyNoErrorParam->rawKey());
102

103
    if (!fileKeyOk) {
104
        return;
105
    }
106

107
    QCOMPARE(fileKey->rawKey().size(), 32);
108

109
    compositeKey->addKey(fileKey);
110

111
    auto db = QSharedPointer<Database>::create();
112
    QVERIFY(db->open(dbFilename, compositeKey, nullptr));
113
    QVERIFY(!reader.hasError());
114
    QCOMPARE(db->metadata()->name(), QString("%1 Database").arg(name));
115
}
116

117
// clang-format off
118
void TestKeys::testFileKey_data()
119
{
120
    QTest::addColumn<FileKey::Type>("type");
121
    QTest::addColumn<QString>("keyExt");
122
    QTest::addColumn<bool>("fileKeyOk");
123
    QTest::newRow("Xml")             << FileKey::KeePass2XML    << QString("key")  << true;
124
    QTest::newRow("XmlBrokenBase64") << FileKey::KeePass2XML    << QString("key")  << false;
125
    QTest::newRow("XmlV2")           << FileKey::KeePass2XMLv2  << QString("keyx") << true;
126
    QTest::newRow("XmlV2HashFail")   << FileKey::KeePass2XMLv2  << QString("keyx") << false;
127
    QTest::newRow("XmlV2BrokenHex")  << FileKey::KeePass2XMLv2  << QString("keyx") << false;
128
    QTest::newRow("Binary")          << FileKey::FixedBinary    << QString("key")  << true;
129
    QTest::newRow("Hex")             << FileKey::FixedBinaryHex << QString("key")  << true;
130
    QTest::newRow("Hashed")          << FileKey::Hashed         << QString("key")  << true;
131
}
132
// clang-format on
133

134
void TestKeys::testCreateFileKey()
135
{
136
    QBuffer keyBuffer1;
137
    keyBuffer1.open(QBuffer::ReadWrite);
138

139
    FileKey::createRandom(&keyBuffer1, 128);
140
    QCOMPARE(keyBuffer1.size(), 128);
141

142
    QBuffer keyBuffer2;
143
    keyBuffer2.open(QBuffer::ReadWrite);
144
    FileKey::createRandom(&keyBuffer2, 64);
145
    QCOMPARE(keyBuffer2.size(), 64);
146
}
147

148
void TestKeys::testCreateAndOpenFileKey()
149
{
150
    const QString dbName("testCreateFileKey database");
151

152
    QBuffer keyBuffer;
153
    keyBuffer.open(QBuffer::ReadWrite);
154

155
    FileKey::createRandom(&keyBuffer);
156
    keyBuffer.reset();
157

158
    auto fileKey = QSharedPointer<FileKey>::create();
159
    QVERIFY(fileKey->load(&keyBuffer));
160
    auto compositeKey = QSharedPointer<CompositeKey>::create();
161
    compositeKey->addKey(fileKey);
162

163
    QScopedPointer<Database> dbOrg(new Database());
164
    QVERIFY(dbOrg->setKey(compositeKey));
165
    dbOrg->metadata()->setName(dbName);
166

167
    QBuffer dbBuffer;
168
    dbBuffer.open(QBuffer::ReadWrite);
169

170
    KeePass2Writer writer;
171
    writer.writeDatabase(&dbBuffer, dbOrg.data());
172
    bool writeSuccess = writer.writeDatabase(&dbBuffer, dbOrg.data());
173
    if (writer.hasError()) {
174
        QFAIL(writer.errorString().toUtf8().constData());
175
    }
176
    QVERIFY(writeSuccess);
177
    dbBuffer.reset();
178

179
    KeePass2Reader reader;
180
    auto dbRead = QSharedPointer<Database>::create();
181
    reader.readDatabase(&dbBuffer, compositeKey, dbRead.data());
182
    if (reader.hasError()) {
183
        QFAIL(reader.errorString().toUtf8().constData());
184
    }
185
    QVERIFY(dbRead);
186
    QCOMPARE(dbRead->metadata()->name(), dbName);
187
}
188

189
void TestKeys::testFileKeyHash()
190
{
191
    QBuffer keyBuffer;
192
    keyBuffer.open(QBuffer::ReadWrite);
193

194
    FileKey::createRandom(&keyBuffer);
195

196
    CryptoHash cryptoHash(CryptoHash::Sha256);
197
    cryptoHash.addData(keyBuffer.data());
198

199
    FileKey fileKey;
200
    fileKey.load(&keyBuffer);
201

202
    QCOMPARE(fileKey.rawKey(), cryptoHash.result());
203
}
204

205
void TestKeys::testFileKeyError()
206
{
207
    bool result;
208
    QString errorMsg;
209
    const QString fileName(QString(KEEPASSX_TEST_DATA_DIR).append("/does/not/exist"));
210

211
    FileKey fileKey;
212
    result = fileKey.load(fileName, &errorMsg);
213
    QVERIFY(!result);
214
    QVERIFY(!errorMsg.isEmpty());
215
    errorMsg = "";
216

217
    result = FileKey::create(fileName, &errorMsg);
218
    QVERIFY(!result);
219
    QVERIFY(!errorMsg.isEmpty());
220
    errorMsg = "";
221
}
222

223
void TestKeys::benchmarkTransformKey()
224
{
225
    QByteArray env = qgetenv("BENCHMARK");
226

227
    if (env.isEmpty() || env == "0" || env == "no") {
228
        QSKIP("Benchmark skipped. Set env variable BENCHMARK=1 to enable.");
229
    }
230

231
    auto pwKey = QSharedPointer<PasswordKey>::create();
232
    pwKey->setPassword("password");
233
    auto compositeKey = QSharedPointer<CompositeKey>::create();
234
    compositeKey->addKey(pwKey);
235

236
    QByteArray seed(32, '\x4B');
237

238
    QByteArray result;
239
    AesKdf kdf;
240
    kdf.setSeed(seed);
241
    kdf.setRounds(1e6);
242

243
    QBENCHMARK
244
    {
245
        Q_UNUSED(!compositeKey->transform(kdf, result));
246
    };
247
}
248

249
void TestKeys::testCompositeKeyComponents()
250
{
251
    auto passwordKeyEnc = QSharedPointer<PasswordKey>::create("password");
252
    auto fileKeyEnc = QSharedPointer<FileKey>::create();
253
    QString error;
254
    fileKeyEnc->load(QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed.key"), &error);
255
    if (!error.isEmpty()) {
256
        QFAIL(qPrintable(error));
257
    }
258
    auto challengeResponseKeyEnc = QSharedPointer<MockChallengeResponseKey>::create(QByteArray(16, 0x10));
259

260
    auto compositeKeyEnc = QSharedPointer<CompositeKey>::create();
261
    compositeKeyEnc->addKey(passwordKeyEnc);
262
    compositeKeyEnc->addKey(fileKeyEnc);
263
    compositeKeyEnc->addChallengeResponseKey(challengeResponseKeyEnc);
264

265
    auto db1 = QSharedPointer<Database>::create();
266
    db1->setKey(compositeKeyEnc);
267

268
    KeePass2Writer writer;
269
    QBuffer buffer;
270
    buffer.open(QBuffer::ReadWrite);
271
    QVERIFY(writer.writeDatabase(&buffer, db1.data()));
272

273
    buffer.seek(0);
274
    auto db2 = QSharedPointer<Database>::create();
275
    KeePass2Reader reader;
276
    auto compositeKeyDec1 = QSharedPointer<CompositeKey>::create();
277

278
    // try decryption and subsequently add key components until decryption is successful
279
    QVERIFY(!reader.readDatabase(&buffer, compositeKeyDec1, db2.data()));
280
    QVERIFY(reader.hasError());
281

282
    compositeKeyDec1->addKey(passwordKeyEnc);
283
    buffer.seek(0);
284
    QVERIFY(!reader.readDatabase(&buffer, compositeKeyDec1, db2.data()));
285
    QVERIFY(reader.hasError());
286

287
    compositeKeyDec1->addKey(fileKeyEnc);
288
    buffer.seek(0);
289
    QVERIFY(!reader.readDatabase(&buffer, compositeKeyDec1, db2.data()));
290
    QVERIFY(reader.hasError());
291

292
    compositeKeyDec1->addChallengeResponseKey(challengeResponseKeyEnc);
293
    buffer.seek(0);
294
    QVERIFY(reader.readDatabase(&buffer, compositeKeyDec1, db2.data()));
295
    // now we should be able to open the database
296
    if (reader.hasError()) {
297
        QFAIL(qPrintable(reader.errorString()));
298
    }
299

300
    // try the same again, but this time with one wrong key component each time
301
    auto compositeKeyDec2 = QSharedPointer<CompositeKey>::create();
302
    compositeKeyDec2->addKey(QSharedPointer<PasswordKey>::create("wrong password"));
303
    compositeKeyDec2->addKey(fileKeyEnc);
304
    compositeKeyDec2->addChallengeResponseKey(challengeResponseKeyEnc);
305
    buffer.seek(0);
306
    QVERIFY(!reader.readDatabase(&buffer, compositeKeyDec2, db2.data()));
307
    QVERIFY(reader.hasError());
308

309
    auto compositeKeyDec3 = QSharedPointer<CompositeKey>::create();
310
    compositeKeyDec3->addKey(passwordKeyEnc);
311
    auto fileKeyWrong = QSharedPointer<FileKey>::create();
312
    fileKeyWrong->load(QString("%1/%2").arg(QString(KEEPASSX_TEST_DATA_DIR), "FileKeyHashed2.key"), &error);
313
    if (!error.isEmpty()) {
314
        QFAIL(qPrintable(error));
315
    }
316
    compositeKeyDec3->addKey(fileKeyWrong);
317
    compositeKeyDec3->addChallengeResponseKey(challengeResponseKeyEnc);
318
    buffer.seek(0);
319
    QVERIFY(!reader.readDatabase(&buffer, compositeKeyDec3, db2.data()));
320
    QVERIFY(reader.hasError());
321

322
    auto compositeKeyDec4 = QSharedPointer<CompositeKey>::create();
323
    compositeKeyDec4->addKey(passwordKeyEnc);
324
    compositeKeyDec4->addKey(fileKeyEnc);
325
    compositeKeyDec4->addChallengeResponseKey(QSharedPointer<MockChallengeResponseKey>::create(QByteArray(16, 0x20)));
326
    buffer.seek(0);
327
    QVERIFY(!reader.readDatabase(&buffer, compositeKeyDec4, db2.data()));
328
    QVERIFY(reader.hasError());
329
}
330

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

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

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

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