keepassxc

Форк
0
/
FileKey.cpp 
463 строки · 12.3 Кб
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 "FileKey.h"
20

21
#include "core/Tools.h"
22
#include "crypto/CryptoHash.h"
23
#include "crypto/Random.h"
24

25
#include <QDataStream>
26
#include <QFile>
27
#include <QXmlStreamReader>
28

29
QUuid FileKey::UUID("a584cbc4-c9b4-437e-81bb-362ca9709273");
30

31
constexpr int FileKey::SHA256_SIZE;
32

33
FileKey::FileKey()
34
    : Key(UUID)
35
    , m_key(SHA256_SIZE)
36
{
37
}
38

39
/**
40
 * Read key file from device while trying to detect its file format.
41
 *
42
 * If no legacy key file format was detected, the SHA-256 hash of the
43
 * key file will be used, allowing usage of arbitrary files as key files.
44
 * In case of a detected legacy key file format, the raw byte contents
45
 * will be extracted from the file.
46
 *
47
 * Supported legacy formats are:
48
 *  - KeePass 2 XML key file
49
 *  - Fixed 32 byte binary
50
 *  - Fixed 32 byte ASCII hex-encoded binary
51
 *
52
 * Usage of legacy formats is discouraged and support for them may be
53
 * removed in a future version.
54
 *
55
 * @param device input device
56
 * @param errorMsg error message in case of fatal failure
57
 * @return true if key file was loaded successfully
58
 */
59
bool FileKey::load(QIODevice* device, QString* errorMsg)
60
{
61
    m_type = None;
62

63
    // we may need to read the file multiple times
64
    if (device->isSequential()) {
65
        return false;
66
    }
67

68
    if (device->size() == 0 || !device->reset()) {
69
        return false;
70
    }
71

72
    // load XML key file v1 or v2
73
    QString xmlError;
74
    if (loadXml(device, &xmlError)) {
75
        return true;
76
    }
77

78
    if (!device->reset() || !xmlError.isEmpty()) {
79
        if (errorMsg) {
80
            *errorMsg = xmlError;
81
        }
82
        return false;
83
    }
84

85
    // try legacy key file formats
86
    if (loadBinary(device)) {
87
        return true;
88
    }
89

90
    if (!device->reset()) {
91
        return false;
92
    }
93

94
    if (loadHex(device)) {
95
        return true;
96
    }
97

98
    // if no legacy format was detected, generate SHA-256 hash of key file
99
    if (!device->reset()) {
100
        return false;
101
    }
102
    if (loadHashed(device)) {
103
        return true;
104
    }
105

106
    return false;
107
}
108

109
/**
110
 * Load key file from path while trying to detect its file format.
111
 *
112
 * If no legacy key file format was detected, the SHA-256 hash of the
113
 * key file will be used, allowing usage of arbitrary files as key files.
114
 * In case of a detected legacy key file format, the raw byte contents
115
 * will be extracted from the file.
116
 *
117
 * Supported legacy formats are:
118
 *  - KeePass 2 XML key file
119
 *  - Fixed 32 byte binary
120
 *  - Fixed 32 byte ASCII hex-encoded binary
121
 *
122
 * Usage of legacy formats is discouraged and support for them may be
123
 * removed in a future version.
124
 *
125
 * @param fileName input file name
126
 * @param errorMsg error message if loading failed
127
 * @return true if key file was loaded successfully
128
 */
129
bool FileKey::load(const QString& fileName, QString* errorMsg)
130
{
131
    QFile file(fileName);
132
    if (!file.open(QFile::ReadOnly)) {
133
        if (errorMsg) {
134
            *errorMsg = file.errorString();
135
        }
136
        return false;
137
    }
138

139
    bool result = load(&file, errorMsg);
140
    file.close();
141

142
    if (errorMsg && !errorMsg->isEmpty()) {
143
        return false;
144
    }
145

146
    if (file.error()) {
147
        result = false;
148
        if (errorMsg) {
149
            *errorMsg = file.errorString();
150
        }
151
    } else {
152
        // Store the file path for serialization
153
        m_file = fileName;
154
    }
155

156
    return result;
157
}
158

159
/**
160
 * @return key data as bytes
161
 */
162
QByteArray FileKey::rawKey() const
163
{
164
    return {m_key.data(), int(m_key.size())};
165
}
166

167
void FileKey::setRawKey(const QByteArray& data)
168
{
169
    Q_ASSERT(data.size() == SHA256_SIZE);
170
    m_key.assign(data.begin(), data.end());
171
}
172

173
QByteArray FileKey::serialize() const
174
{
175
    QByteArray data;
176
    QDataStream stream(&data, QIODevice::WriteOnly);
177
    stream << uuid().toRfc4122() << rawKey() << static_cast<qint32>(m_type) << m_file;
178
    return data;
179
}
180

181
void FileKey::deserialize(const QByteArray& data)
182
{
183
    QDataStream stream(data);
184
    QByteArray uuidData;
185
    stream >> uuidData;
186
    if (uuid().toRfc4122() == uuidData) {
187
        QByteArray key;
188
        qint32 type;
189
        stream >> key >> type >> m_file;
190

191
        setRawKey(key);
192
        m_type = static_cast<Type>(type);
193
    }
194
}
195

196
/**
197
 * Generate a new key file with random bytes.
198
 *
199
 * @param device output device
200
 * @param number of random bytes to generate
201
 */
202
void FileKey::createRandom(QIODevice* device, int size)
203
{
204
    device->write(randomGen()->randomArray(size));
205
}
206

207
/**
208
 * Generate a new key file in the KeePass2 XML format v2.
209
 *
210
 * @param device output device
211
 * @param number of random bytes to generate
212
 */
213
void FileKey::createXMLv2(QIODevice* device, int size)
214
{
215
    QXmlStreamWriter w(device);
216
    w.setAutoFormatting(true);
217
    w.setAutoFormattingIndent(4);
218
    w.writeStartDocument();
219

220
    w.writeStartElement("KeyFile");
221

222
    w.writeStartElement("Meta");
223
    w.writeTextElement("Version", "2.0");
224
    w.writeEndElement();
225

226
    w.writeStartElement("Key");
227
    w.writeStartElement("Data");
228

229
    QByteArray key = randomGen()->randomArray(size);
230
    CryptoHash hash(CryptoHash::Sha256);
231
    hash.addData(key);
232
    QByteArray result = hash.result().left(4);
233
    key = key.toHex().toUpper();
234

235
    w.writeAttribute("Hash", result.toHex().toUpper());
236
    w.writeCharacters("\n            ");
237
    for (int i = 0; i < key.size(); ++i) {
238
        // Pretty-print hex value (not strictly necessary, but nicer to read and KeePass2 does it)
239
        if (i != 0 && i % 32 == 0) {
240
            w.writeCharacters("\n            ");
241
        } else if (i != 0 && i % 8 == 0) {
242
            w.writeCharacters(" ");
243
        }
244
        w.writeCharacters(QChar(key[i]));
245
    }
246
    Botan::secure_scrub_memory(key.data(), static_cast<std::size_t>(key.capacity()));
247
    w.writeCharacters("\n        ");
248

249
    w.writeEndElement();
250
    w.writeEndElement();
251

252
    w.writeEndDocument();
253
}
254

255
/**
256
 * Create a new key file from random bytes.
257
 *
258
 * @param fileName output file name
259
 * @param errorMsg error message if generation failed
260
 * @param number of random bytes to generate
261
 * @return true on successful creation
262
 */
263
bool FileKey::create(const QString& fileName, QString* errorMsg)
264
{
265
    QFile file(fileName);
266
    if (!file.open(QFile::WriteOnly)) {
267
        if (errorMsg) {
268
            *errorMsg = file.errorString();
269
        }
270
        return false;
271
    }
272
    if (fileName.endsWith(".keyx")) {
273
        createXMLv2(&file);
274
    } else {
275
        createRandom(&file);
276
    }
277
    file.close();
278
    file.setPermissions(QFile::ReadUser);
279

280
    if (file.error()) {
281
        if (errorMsg) {
282
            *errorMsg = file.errorString();
283
        }
284

285
        return false;
286
    }
287

288
    return true;
289
}
290

291
/**
292
 * Load key file in legacy KeePass 2 XML format.
293
 *
294
 * @param device input device
295
 * @return true on success
296
 */
297
bool FileKey::loadXml(QIODevice* device, QString* errorMsg)
298
{
299
    QXmlStreamReader xmlReader(device);
300

301
    if (xmlReader.error()) {
302
        return false;
303
    }
304
    if (xmlReader.readNextStartElement() && xmlReader.name() != "KeyFile") {
305
        return false;
306
    }
307

308
    struct
309
    {
310
        QString version;
311
        QByteArray hash;
312
        QByteArray data;
313
    } keyFileData;
314

315
    while (!xmlReader.error() && xmlReader.readNextStartElement()) {
316
        if (xmlReader.name() == "Meta") {
317
            while (!xmlReader.error() && xmlReader.readNextStartElement()) {
318
                if (xmlReader.name() == "Version") {
319
                    keyFileData.version = xmlReader.readElementText();
320
                    if (keyFileData.version.startsWith("1.0")) {
321
                        m_type = KeePass2XML;
322
                    } else if (keyFileData.version == "2.0") {
323
                        m_type = KeePass2XMLv2;
324
                    } else {
325
                        if (errorMsg) {
326
                            *errorMsg = QObject::tr("Unsupported key file version: %1").arg(keyFileData.version);
327
                        }
328
                        return false;
329
                    }
330
                }
331
            }
332
        } else if (xmlReader.name() == "Key") {
333
            while (!xmlReader.error() && xmlReader.readNextStartElement()) {
334
                if (xmlReader.name() == "Data") {
335
                    keyFileData.hash = QByteArray::fromHex(xmlReader.attributes().value("Hash").toLatin1());
336
                    keyFileData.data = xmlReader.readElementText().simplified().replace(" ", "").toLatin1();
337

338
                    if (keyFileData.version.startsWith("1.0") && Tools::isBase64(keyFileData.data)) {
339
                        keyFileData.data = QByteArray::fromBase64(keyFileData.data);
340
                    } else if (keyFileData.version == "2.0" && Tools::isHex(keyFileData.data)) {
341
                        keyFileData.data = QByteArray::fromHex(keyFileData.data);
342

343
                        CryptoHash hash(CryptoHash::Sha256);
344
                        hash.addData(keyFileData.data);
345
                        QByteArray result = hash.result().left(4);
346
                        if (keyFileData.hash != result) {
347
                            if (errorMsg) {
348
                                *errorMsg = QObject::tr("Checksum mismatch! Key file may be corrupt.");
349
                            }
350
                            return false;
351
                        }
352
                    } else {
353
                        if (errorMsg) {
354
                            *errorMsg = QObject::tr("Unexpected key file data! Key file may be corrupt.");
355
                        }
356
                        return false;
357
                    }
358
                }
359
            }
360
        }
361
    }
362

363
    bool ok = false;
364
    if (!xmlReader.error() && !keyFileData.data.isEmpty()) {
365
        std::memcpy(m_key.data(), keyFileData.data.data(), std::min(SHA256_SIZE, keyFileData.data.size()));
366
        ok = true;
367
    }
368

369
    Botan::secure_scrub_memory(keyFileData.data.data(), static_cast<std::size_t>(keyFileData.data.capacity()));
370

371
    return ok;
372
}
373

374
/**
375
 * Load fixed 32-bit binary key file.
376
 *
377
 * @param device input device
378
 * @return true on success
379
 * @deprecated
380
 */
381
bool FileKey::loadBinary(QIODevice* device)
382
{
383
    if (device->size() != 32) {
384
        return false;
385
    }
386

387
    Botan::secure_vector<char> data(32);
388
    if (device->read(data.data(), 32) != 32 || !device->atEnd()) {
389
        return false;
390
    }
391

392
    m_key = data;
393
    m_type = FixedBinary;
394
    return true;
395
}
396

397
/**
398
 * Load hex-encoded representation of fixed 32-bit binary key file.
399
 *
400
 * @param device input device
401
 * @return true on success
402
 * @deprecated
403
 */
404
bool FileKey::loadHex(QIODevice* device)
405
{
406
    if (device->size() != 64) {
407
        return false;
408
    }
409

410
    QByteArray data;
411
    if (!Tools::readAllFromDevice(device, data) || data.size() != 64) {
412
        return false;
413
    }
414

415
    if (!Tools::isHex(data)) {
416
        return false;
417
    }
418

419
    data = QByteArray::fromHex(data);
420
    if (data.size() != 32) {
421
        return false;
422
    }
423

424
    std::memcpy(m_key.data(), data.data(), std::min(SHA256_SIZE, data.size()));
425
    Botan::secure_scrub_memory(data.data(), static_cast<std::size_t>(data.capacity()));
426

427
    m_type = FixedBinaryHex;
428
    return true;
429
}
430

431
/**
432
 * Generate SHA-256 hash of arbitrary text or binary key file.
433
 *
434
 * @param device input device
435
 * @return true on success
436
 */
437
bool FileKey::loadHashed(QIODevice* device)
438
{
439
    CryptoHash cryptoHash(CryptoHash::Sha256);
440

441
    QByteArray buffer;
442
    do {
443
        if (!Tools::readFromDevice(device, buffer)) {
444
            return false;
445
        }
446
        cryptoHash.addData(buffer);
447
    } while (!buffer.isEmpty());
448

449
    buffer = cryptoHash.result();
450
    std::memcpy(m_key.data(), buffer.data(), std::min(SHA256_SIZE, buffer.size()));
451
    Botan::secure_scrub_memory(buffer.data(), static_cast<std::size_t>(buffer.capacity()));
452

453
    m_type = Hashed;
454
    return true;
455
}
456

457
/**
458
 * @return type of loaded key file
459
 */
460
FileKey::Type FileKey::type() const
461
{
462
    return m_type;
463
}
464

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

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

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

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