keepassxc

Форк
0
/
Kdbx4Reader.cpp 
444 строки · 14.9 Кб
1
/*
2
 *  Copyright (C) 2017 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 "Kdbx4Reader.h"
19

20
#include <QBuffer>
21
#include <QJsonObject>
22

23
#include "core/AsyncTask.h"
24
#include "core/Endian.h"
25
#include "core/Group.h"
26
#include "crypto/CryptoHash.h"
27
#include "format/KdbxXmlReader.h"
28
#include "format/KeePass2RandomStream.h"
29
#include "streams/HmacBlockStream.h"
30
#include "streams/StoreDataStream.h"
31
#include "streams/SymmetricCipherStream.h"
32
#include "streams/qtiocompressor.h"
33

34
bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
35
                                   const QByteArray& headerData,
36
                                   QSharedPointer<const CompositeKey> key,
37
                                   Database* db)
38
{
39
    Q_ASSERT((db->formatVersion() & KeePass2::FILE_VERSION_CRITICAL_MASK) == KeePass2::FILE_VERSION_4);
40

41
    m_binaryPool.clear();
42

43
    if (hasError()) {
44
        return false;
45
    }
46

47
    // check if all required headers were present
48
    if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || db->cipher().isNull()) {
49
        raiseError(tr("missing database headers"));
50
        return false;
51
    }
52

53
    bool ok = AsyncTask::runAndWaitForFuture([&] { return db->setKey(key, false, false); });
54
    if (!ok) {
55
        raiseError(tr("Unable to calculate database key: %1").arg(db->keyError()));
56
        return false;
57
    }
58

59
    CryptoHash hash(CryptoHash::Sha256);
60
    hash.addData(m_masterSeed);
61
    hash.addData(db->transformedDatabaseKey());
62
    QByteArray finalKey = hash.result();
63

64
    QByteArray headerSha256 = device->read(32);
65
    QByteArray headerHmac = device->read(32);
66
    if (headerSha256.size() != 32 || headerHmac.size() != 32) {
67
        raiseError(tr("Invalid header checksum size"));
68
        return false;
69
    }
70
    if (headerSha256 != CryptoHash::hash(headerData, CryptoHash::Sha256)) {
71
        raiseError(tr("Header SHA256 mismatch"));
72
        return false;
73
    }
74

75
    // clang-format off
76
    QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, db->transformedDatabaseKey());
77
    if (headerHmac != CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
78
        raiseError(tr("Invalid credentials were provided, please try again.\n"
79
                      "If this reoccurs, then your database file may be corrupt.") + " " + tr("(HMAC mismatch)"));
80
        return false;
81
    }
82
    HmacBlockStream hmacStream(device, hmacKey);
83
    if (!hmacStream.open(QIODevice::ReadOnly)) {
84
        raiseError(hmacStream.errorString());
85
        return false;
86
    }
87

88
    auto mode = SymmetricCipher::cipherUuidToMode(db->cipher());
89
    if (mode == SymmetricCipher::InvalidMode) {
90
        raiseError(tr("Unknown cipher"));
91
        return false;
92
    }
93
    SymmetricCipherStream cipherStream(&hmacStream);
94
    if (!cipherStream.init(mode, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)) {
95
        raiseError(cipherStream.errorString());
96
        return false;
97
    }
98
    if (!cipherStream.open(QIODevice::ReadOnly)) {
99
        raiseError(cipherStream.errorString());
100
        return false;
101
    }
102
    // clang-format on
103

104
    QIODevice* xmlDevice = nullptr;
105
    QScopedPointer<QtIOCompressor> ioCompressor;
106

107
    if (db->compressionAlgorithm() == Database::CompressionNone) {
108
        xmlDevice = &cipherStream;
109
    } else {
110
        ioCompressor.reset(new QtIOCompressor(&cipherStream));
111
        ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
112
        if (!ioCompressor->open(QIODevice::ReadOnly)) {
113
            raiseError(ioCompressor->errorString());
114
            return false;
115
        }
116
        xmlDevice = ioCompressor.data();
117
    }
118

119
    while (readInnerHeaderField(xmlDevice) && !hasError()) {
120
    }
121

122
    if (hasError()) {
123
        return false;
124
    }
125

126
    // TODO: Convert m_irsAlgo to Mode
127
    switch (m_irsAlgo) {
128
    case KeePass2::ProtectedStreamAlgo::Salsa20:
129
        mode = SymmetricCipher::Salsa20;
130
        break;
131
    case KeePass2::ProtectedStreamAlgo::ChaCha20:
132
        mode = SymmetricCipher::ChaCha20;
133
        break;
134
    default:
135
        mode = SymmetricCipher::InvalidMode;
136
    }
137

138
    KeePass2RandomStream randomStream;
139
    if (!randomStream.init(mode, m_protectedStreamKey)) {
140
        raiseError(randomStream.errorString());
141
        return false;
142
    }
143

144
    Q_ASSERT(xmlDevice);
145

146
    KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, binaryPool());
147
    xmlReader.readDatabase(xmlDevice, db, &randomStream);
148

149
    if (xmlReader.hasError()) {
150
        raiseError(xmlReader.errorString());
151
        return false;
152
    }
153

154
    return true;
155
}
156

157
bool Kdbx4Reader::readHeaderField(StoreDataStream& device, Database* db)
158
{
159
    QByteArray fieldIDArray = device.read(1);
160
    if (fieldIDArray.size() != 1) {
161
        raiseError(tr("Invalid header id size"));
162
        return false;
163
    }
164
    char fieldID = fieldIDArray.at(0);
165

166
    bool ok;
167
    auto fieldLen = Endian::readSizedInt<quint32>(&device, KeePass2::BYTEORDER, &ok);
168
    if (!ok) {
169
        raiseError(tr("Invalid header field length: field %1").arg(fieldID));
170
        return false;
171
    }
172

173
    QByteArray fieldData;
174
    if (fieldLen != 0) {
175
        fieldData = device.read(fieldLen);
176
        if (static_cast<quint32>(fieldData.size()) != fieldLen) {
177
            raiseError(tr("Invalid header data length: field %1, %2 expected, %3 found")
178
                           .arg(static_cast<int>(fieldID))
179
                           .arg(fieldLen)
180
                           .arg(fieldData.size()));
181
            return false;
182
        }
183
    }
184

185
    switch (static_cast<KeePass2::HeaderFieldID>(fieldID)) {
186
    case KeePass2::HeaderFieldID::EndOfHeader:
187
        return false;
188

189
    case KeePass2::HeaderFieldID::CipherID:
190
        setCipher(fieldData);
191
        break;
192

193
    case KeePass2::HeaderFieldID::CompressionFlags:
194
        setCompressionFlags(fieldData);
195
        break;
196

197
    case KeePass2::HeaderFieldID::MasterSeed:
198
        setMasterSeed(fieldData);
199
        break;
200

201
    case KeePass2::HeaderFieldID::EncryptionIV:
202
        setEncryptionIV(fieldData);
203
        break;
204

205
    case KeePass2::HeaderFieldID::KdfParameters: {
206
        QBuffer bufIoDevice(&fieldData);
207
        if (!bufIoDevice.open(QIODevice::ReadOnly)) {
208
            raiseError(tr("Failed to open buffer for KDF parameters in header"));
209
            return false;
210
        }
211
        QVariantMap kdfParams = readVariantMap(&bufIoDevice);
212
        QSharedPointer<Kdf> kdf = KeePass2::kdfFromParameters(kdfParams);
213
        if (!kdf) {
214
            raiseError(tr("Unsupported key derivation function (KDF) or invalid parameters"));
215
            return false;
216
        }
217
        db->setKdf(kdf);
218
        break;
219
    }
220

221
    case KeePass2::HeaderFieldID::PublicCustomData: {
222
        QBuffer variantBuffer;
223
        variantBuffer.setBuffer(&fieldData);
224
        variantBuffer.open(QBuffer::ReadOnly);
225
        QVariantMap data = readVariantMap(&variantBuffer);
226
        db->setPublicCustomData(data);
227
        break;
228
    }
229

230
    case KeePass2::HeaderFieldID::ProtectedStreamKey:
231
    case KeePass2::HeaderFieldID::TransformRounds:
232
    case KeePass2::HeaderFieldID::TransformSeed:
233
    case KeePass2::HeaderFieldID::StreamStartBytes:
234
    case KeePass2::HeaderFieldID::InnerRandomStreamID:
235
        raiseError(tr("Legacy header fields found in KDBX4 file."));
236
        return false;
237

238
    default:
239
        qWarning("Unknown header field read: id=%d", fieldID);
240
        break;
241
    }
242

243
    return true;
244
}
245

246
/**
247
 * Helper method for reading KDBX4 inner header fields.
248
 *
249
 * @param device input device
250
 * @return true if there are more inner header fields
251
 */
252
bool Kdbx4Reader::readInnerHeaderField(QIODevice* device)
253
{
254
    QByteArray fieldIDArray = device->read(1);
255
    if (fieldIDArray.size() != 1) {
256
        raiseError(tr("Invalid inner header id size"));
257
        return false;
258
    }
259
    auto fieldID = static_cast<KeePass2::InnerHeaderFieldID>(fieldIDArray.at(0));
260

261
    bool ok;
262
    auto fieldLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
263
    if (!ok) {
264
        raiseError(tr("Invalid inner header field length: field %1").arg(static_cast<int>(fieldID)));
265
        return false;
266
    }
267

268
    QByteArray fieldData;
269
    if (fieldLen != 0) {
270
        fieldData = device->read(fieldLen);
271
        if (static_cast<quint32>(fieldData.size()) != fieldLen) {
272
            raiseError(tr("Invalid inner header data length: field %1, %2 expected, %3 found")
273
                           .arg(static_cast<int>(fieldID))
274
                           .arg(fieldLen)
275
                           .arg(fieldData.size()));
276
            return false;
277
        }
278
    }
279

280
    switch (fieldID) {
281
    case KeePass2::InnerHeaderFieldID::End:
282
        return false;
283

284
    case KeePass2::InnerHeaderFieldID::InnerRandomStreamID:
285
        setInnerRandomStreamID(fieldData);
286
        break;
287

288
    case KeePass2::InnerHeaderFieldID::InnerRandomStreamKey:
289
        setProtectedStreamKey(fieldData);
290
        break;
291

292
    case KeePass2::InnerHeaderFieldID::Binary: {
293
        if (fieldLen < 1) {
294
            raiseError(tr("Invalid inner header binary size"));
295
            return false;
296
        }
297
        auto data = fieldData.mid(1);
298
        m_binaryPool.insert(QString::number(m_binaryPool.size()), data);
299
        break;
300
    }
301
    }
302

303
    return true;
304
}
305

306
/**
307
 * Helper method for reading a serialized variant map.
308
 *
309
 * @param device input device
310
 * @return de-serialized variant map
311
 */
312
QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device)
313
{
314
    bool ok;
315
    quint16 version =
316
        Endian::readSizedInt<quint16>(device, KeePass2::BYTEORDER, &ok) & KeePass2::VARIANTMAP_CRITICAL_MASK;
317
    quint16 maxVersion = KeePass2::VARIANTMAP_VERSION & KeePass2::VARIANTMAP_CRITICAL_MASK;
318
    if (!ok || (version > maxVersion)) {
319
        //: Translation: variant map = data structure for storing meta data
320
        raiseError(tr("Unsupported KeePass variant map version."));
321
        return {};
322
    }
323

324
    QVariantMap vm;
325
    QByteArray fieldTypeArray;
326
    KeePass2::VariantMapFieldType fieldType = KeePass2::VariantMapFieldType::End;
327
    while (((fieldTypeArray = device->read(1)).size() == 1)
328
           && ((fieldType = static_cast<KeePass2::VariantMapFieldType>(fieldTypeArray.at(0)))
329
               != KeePass2::VariantMapFieldType::End)) {
330
        auto nameLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
331
        if (!ok) {
332
            //: Translation: variant map = data structure for storing meta data
333
            raiseError(tr("Invalid variant map entry name length"));
334
            return {};
335
        }
336
        QByteArray nameBytes;
337
        if (nameLen != 0) {
338
            nameBytes = device->read(nameLen);
339
            if (static_cast<quint32>(nameBytes.size()) != nameLen) {
340
                //: Translation: variant map = data structure for storing meta data
341
                raiseError(tr("Invalid variant map entry name data"));
342
                return {};
343
            }
344
        }
345
        QString name = QString::fromUtf8(nameBytes);
346

347
        auto valueLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
348
        if (!ok) {
349
            //: Translation: variant map = data structure for storing meta data
350
            raiseError(tr("Invalid variant map entry value length"));
351
            return {};
352
        }
353
        QByteArray valueBytes;
354
        if (valueLen != 0) {
355
            valueBytes = device->read(valueLen);
356
            if (static_cast<quint32>(valueBytes.size()) != valueLen) {
357
                //: Translation comment: variant map = data structure for storing meta data
358
                raiseError(tr("Invalid variant map entry value data"));
359
                return {};
360
            }
361
        }
362

363
        switch (fieldType) {
364
        case KeePass2::VariantMapFieldType::Bool:
365
            if (valueLen == 1) {
366
                vm.insert(name, QVariant(valueBytes.at(0) != 0));
367
            } else {
368
                //: Translation: variant map = data structure for storing meta data
369
                raiseError(tr("Invalid variant map Bool entry value length"));
370
                return {};
371
            }
372
            break;
373

374
        case KeePass2::VariantMapFieldType::Int32:
375
            if (valueLen == 4) {
376
                vm.insert(name, QVariant(Endian::bytesToSizedInt<qint32>(valueBytes, KeePass2::BYTEORDER)));
377
            } else {
378
                //: Translation: variant map = data structure for storing meta data
379
                raiseError(tr("Invalid variant map Int32 entry value length"));
380
                return {};
381
            }
382
            break;
383

384
        case KeePass2::VariantMapFieldType::UInt32:
385
            if (valueLen == 4) {
386
                vm.insert(name, QVariant(Endian::bytesToSizedInt<quint32>(valueBytes, KeePass2::BYTEORDER)));
387
            } else {
388
                //: Translation: variant map = data structure for storing meta data
389
                raiseError(tr("Invalid variant map UInt32 entry value length"));
390
                return {};
391
            }
392
            break;
393

394
        case KeePass2::VariantMapFieldType::Int64:
395
            if (valueLen == 8) {
396
                vm.insert(name, QVariant(Endian::bytesToSizedInt<qint64>(valueBytes, KeePass2::BYTEORDER)));
397
            } else {
398
                //: Translation: variant map = data structure for storing meta data
399
                raiseError(tr("Invalid variant map Int64 entry value length"));
400
                return {};
401
            }
402
            break;
403

404
        case KeePass2::VariantMapFieldType::UInt64:
405
            if (valueLen == 8) {
406
                vm.insert(name, QVariant(Endian::bytesToSizedInt<quint64>(valueBytes, KeePass2::BYTEORDER)));
407
            } else {
408
                //: Translation: variant map = data structure for storing meta data
409
                raiseError(tr("Invalid variant map UInt64 entry value length"));
410
                return {};
411
            }
412
            break;
413

414
        case KeePass2::VariantMapFieldType::String:
415
            vm.insert(name, QVariant(QString::fromUtf8(valueBytes)));
416
            break;
417

418
        case KeePass2::VariantMapFieldType::ByteArray:
419
            vm.insert(name, QVariant(valueBytes));
420
            break;
421

422
        default:
423
            //: Translation: variant map = data structure for storing meta data
424
            raiseError(tr("Invalid variant map entry type"));
425
            return {};
426
        }
427
    }
428

429
    if (fieldTypeArray.size() != 1) {
430
        //: Translation: variant map = data structure for storing meta data
431
        raiseError(tr("Invalid variant map field type size"));
432
        return {};
433
    }
434

435
    return vm;
436
}
437

438
/**
439
 * @return mapping from attachment keys to binary data
440
 */
441
QHash<QString, QByteArray> Kdbx4Reader::binaryPool() const
442
{
443
    return m_binaryPool;
444
}
445

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

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

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

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