keepassxc

Форк
0
/
KeePass1Reader.cpp 
1020 строк · 29.9 Кб
1
/*
2
 *  Copyright (C) 2012 Felix Geyer <debfx@fobos.de>
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 "KeePass1Reader.h"
19

20
#include <QFile>
21
#include <QFileInfo>
22
#include <QTextCodec>
23

24
#include "core/Endian.h"
25
#include "core/Group.h"
26
#include "core/Metadata.h"
27
#include "core/Tools.h"
28
#include "crypto/CryptoHash.h"
29
#include "format/KeePass1.h"
30
#include "keys/FileKey.h"
31
#include "streams/SymmetricCipherStream.h"
32

33
class KeePass1Key : public CompositeKey
34
{
35
public:
36
    QByteArray rawKey() const override;
37
    virtual void clear();
38
    void setPassword(const QByteArray& password);
39
    void setKeyfileData(const QByteArray& keyfileData);
40

41
private:
42
    QByteArray m_password;
43
    QByteArray m_keyfileData;
44
};
45

46
KeePass1Reader::KeePass1Reader()
47
    : m_tmpParent(nullptr)
48
    , m_device(nullptr)
49
    , m_encryptionFlags(0)
50
    , m_transformRounds(0)
51
    , m_error(false)
52
{
53
}
54

55
QSharedPointer<Database>
56
KeePass1Reader::readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice)
57
{
58
    m_error = false;
59
    m_errorStr.clear();
60

61
    QByteArray keyfileData;
62
    auto newFileKey = QSharedPointer<FileKey>::create();
63

64
    if (keyfileDevice) {
65
        keyfileData = readKeyfile(keyfileDevice);
66

67
        if (keyfileData.isEmpty()) {
68
            raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
69
            return {};
70
        }
71
        if (!keyfileDevice->seek(0)) {
72
            raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
73
            return {};
74
        }
75

76
        if (!newFileKey->load(keyfileDevice)) {
77
            raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
78
            return {};
79
        }
80
    }
81

82
    auto db = QSharedPointer<Database>::create();
83
    QScopedPointer<Group> tmpParent(new Group());
84
    m_db = db;
85
    m_tmpParent = tmpParent.data();
86
    m_device = device;
87

88
    bool ok;
89

90
    auto signature1 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
91
    if (!ok || signature1 != KeePass1::SIGNATURE_1) {
92
        raiseError(tr("Not a KeePass database."));
93
        return {};
94
    }
95

96
    auto signature2 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
97
    if (!ok || signature2 != KeePass1::SIGNATURE_2) {
98
        raiseError(tr("Not a KeePass database."));
99
        return {};
100
    }
101

102
    m_encryptionFlags = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
103
    if (!ok || !(m_encryptionFlags & KeePass1::Rijndael || m_encryptionFlags & KeePass1::Twofish)) {
104
        raiseError(tr("Unsupported encryption algorithm."));
105
        return {};
106
    }
107

108
    auto version = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
109
    if (!ok
110
        || (version & KeePass1::FILE_VERSION_CRITICAL_MASK)
111
               != (KeePass1::FILE_VERSION & KeePass1::FILE_VERSION_CRITICAL_MASK)) {
112
        raiseError(tr("Unsupported KeePass database version."));
113
        return {};
114
    }
115

116
    m_masterSeed = m_device->read(16);
117
    if (m_masterSeed.size() != 16) {
118
        raiseError("Unable to read master seed");
119
        return {};
120
    }
121

122
    m_encryptionIV = m_device->read(16);
123
    if (m_encryptionIV.size() != 16) {
124
        raiseError(tr("Unable to read encryption IV", "IV = Initialization Vector for symmetric cipher"));
125
        return {};
126
    }
127

128
    auto numGroups = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
129
    if (!ok) {
130
        raiseError(tr("Invalid number of groups"));
131
        return {};
132
    }
133

134
    auto numEntries = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
135
    if (!ok) {
136
        raiseError(tr("Invalid number of entries"));
137
        return {};
138
    }
139

140
    m_contentHashHeader = m_device->read(32);
141
    if (m_contentHashHeader.size() != 32) {
142
        raiseError(tr("Invalid content hash size"));
143
        return {};
144
    }
145

146
    m_transformSeed = m_device->read(32);
147
    if (m_transformSeed.size() != 32) {
148
        raiseError(tr("Invalid transform seed size"));
149
        return {};
150
    }
151

152
    m_transformRounds = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
153
    if (!ok) {
154
        raiseError(tr("Invalid number of transform rounds"));
155
        return {};
156
    }
157
    auto kdf = QSharedPointer<AesKdf>::create(true);
158
    kdf->setRounds(m_transformRounds);
159
    kdf->setSeed(m_transformSeed);
160
    db->setKdf(kdf);
161

162
    qint64 contentPos = m_device->pos();
163

164
    QScopedPointer<SymmetricCipherStream> cipherStream(testKeys(password, keyfileData, contentPos));
165

166
    if (!cipherStream) {
167
        return {};
168
    }
169

170
    QList<Group*> groups;
171
    for (quint32 i = 0; i < numGroups; i++) {
172
        Group* group = readGroup(cipherStream.data());
173
        if (!group) {
174
            return {};
175
        }
176
        groups.append(group);
177
    }
178

179
    QList<Entry*> entries;
180
    for (quint32 i = 0; i < numEntries; i++) {
181
        Entry* entry = readEntry(cipherStream.data());
182
        if (!entry) {
183
            return {};
184
        }
185
        entries.append(entry);
186
    }
187

188
    if (!constructGroupTree(groups)) {
189
        raiseError(tr("Unable to construct group tree"));
190
        return {};
191
    }
192

193
    for (Entry* entry : asConst(entries)) {
194
        if (isMetaStream(entry)) {
195
            parseMetaStream(entry);
196

197
            delete entry;
198
        } else {
199
            quint32 groupId = m_entryGroupIds.value(entry);
200
            if (!m_groupIds.contains(groupId)) {
201
                qWarning("Orphaned entry found, assigning to root group.");
202
                entry->setGroup(m_db->rootGroup());
203
            } else {
204
                entry->setGroup(m_groupIds.value(groupId));
205
            }
206
            entry->setUuid(QUuid::createUuid());
207
        }
208
    }
209

210
    db->rootGroup()->setName(tr("Root"));
211

212
    const QList<Group*> children = db->rootGroup()->children();
213
    for (Group* group : children) {
214
        if (group->name() == "Backup") {
215
            group->setSearchingEnabled(Group::Disable);
216
            group->setAutoTypeEnabled(Group::Disable);
217
        }
218
    }
219

220
    Q_ASSERT(m_tmpParent->children().isEmpty());
221
    Q_ASSERT(m_tmpParent->entries().isEmpty());
222

223
    for (Group* group : asConst(groups)) {
224
        group->setUpdateTimeinfo(true);
225
    }
226

227
    const QList<Entry*> dbEntries = m_db->rootGroup()->entriesRecursive();
228
    for (Entry* entry : dbEntries) {
229
        entry->setUpdateTimeinfo(true);
230
    }
231

232
    auto key = QSharedPointer<CompositeKey>::create();
233
    if (!password.isEmpty()) {
234
        key->addKey(QSharedPointer<PasswordKey>::create(password));
235
    }
236
    if (keyfileDevice) {
237
        key->addKey(newFileKey);
238
    }
239

240
    if (!db->setKey(key)) {
241
        raiseError(tr("Unable to calculate database key"));
242
        return {};
243
    }
244

245
    return db;
246
}
247

248
QSharedPointer<Database>
249
KeePass1Reader::readDatabase(QIODevice* device, const QString& password, const QString& keyfileName)
250
{
251
    QScopedPointer<QFile> keyFile;
252
    if (!keyfileName.isEmpty()) {
253
        keyFile.reset(new QFile(keyfileName));
254
        if (!keyFile->open(QFile::ReadOnly)) {
255
            raiseError(keyFile->errorString());
256
            return {};
257
        }
258
    }
259

260
    return QSharedPointer<Database>(readDatabase(device, password, keyFile.data()));
261
}
262

263
QSharedPointer<Database>
264
KeePass1Reader::readDatabase(const QString& filename, const QString& password, const QString& keyfileName)
265
{
266
    QFile dbFile(filename);
267
    if (!dbFile.open(QFile::ReadOnly)) {
268
        raiseError(dbFile.errorString());
269
        return {};
270
    }
271

272
    auto db = readDatabase(&dbFile, password, keyfileName);
273

274
    if (dbFile.error() != QFile::NoError) {
275
        raiseError(dbFile.errorString());
276
        return {};
277
    }
278

279
    if (db) {
280
        db->metadata()->setName(QFileInfo(filename).completeBaseName());
281
    }
282

283
    return db;
284
}
285

286
bool KeePass1Reader::hasError()
287
{
288
    return m_error;
289
}
290

291
QString KeePass1Reader::errorString()
292
{
293
    return m_errorStr;
294
}
295

296
SymmetricCipherStream*
297
KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData, qint64 contentPos)
298
{
299
    const QList<PasswordEncoding> encodings = {Windows1252, Latin1, UTF8};
300

301
    QScopedPointer<SymmetricCipherStream> cipherStream;
302
    QByteArray passwordData;
303
    QTextCodec* codec = QTextCodec::codecForName("Windows-1252");
304
    QByteArray passwordDataCorrect = codec->fromUnicode(password);
305

306
    for (PasswordEncoding encoding : encodings) {
307
        if (encoding == Windows1252) {
308
            passwordData = passwordDataCorrect;
309
        } else if (encoding == Latin1) {
310
            // KeePassX used Latin-1 encoding for passwords until version 0.3.1
311
            // but KeePass/Win32 uses Windows Codepage 1252.
312
            passwordData = password.toLatin1();
313

314
            if (passwordData == passwordDataCorrect) {
315
                continue;
316
            } else {
317
                qWarning("Testing password encoded as Latin-1.");
318
            }
319
        } else if (encoding == UTF8) {
320
            // KeePassX used UTF-8 encoding for passwords until version 0.2.2
321
            // but KeePass/Win32 uses Windows Codepage 1252.
322
            passwordData = password.toUtf8();
323

324
            if (passwordData == passwordDataCorrect) {
325
                continue;
326
            } else {
327
                qWarning("Testing password encoded as UTF-8.");
328
            }
329
        }
330

331
        QByteArray finalKey = key(passwordData, keyfileData);
332
        if (finalKey.isEmpty()) {
333
            return nullptr;
334
        }
335

336
        cipherStream.reset(new SymmetricCipherStream(m_device));
337

338
        auto mode = SymmetricCipher::Aes256_CBC;
339
        if (m_encryptionFlags & KeePass1::Twofish) {
340
            mode = SymmetricCipher::Twofish_CBC;
341
        }
342
        if (!cipherStream->init(mode, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)) {
343
            raiseError(cipherStream->errorString());
344
            return nullptr;
345
        }
346
        if (!cipherStream->open(QIODevice::ReadOnly)) {
347
            raiseError(cipherStream->errorString());
348
            return nullptr;
349
        }
350

351
        bool success = verifyKey(cipherStream.data());
352

353
        cipherStream->reset();
354
        cipherStream->close();
355
        if (!m_device->seek(contentPos)) {
356
            QString msg = tr("unable to seek to content position");
357
            if (!m_device->errorString().isEmpty()) {
358
                msg.append("\n").append(m_device->errorString());
359
            }
360
            raiseError(msg);
361

362
            return nullptr;
363
        }
364

365
        if (success) {
366
            if (!cipherStream->init(mode, SymmetricCipher::Decrypt, finalKey, m_encryptionIV)) {
367
                raiseError(cipherStream->errorString());
368
                return nullptr;
369
            }
370
            cipherStream->open(QIODevice::ReadOnly);
371
            break;
372
        } else {
373
            cipherStream.reset();
374
        }
375
    }
376

377
    if (!cipherStream) {
378
        raiseError(tr("Invalid credentials were provided, please try again.\n"
379
                      "If this reoccurs, then your database file may be corrupt."));
380
    }
381

382
    return cipherStream.take();
383
}
384

385
QByteArray KeePass1Reader::key(const QByteArray& password, const QByteArray& keyfileData)
386
{
387
    Q_ASSERT(!m_masterSeed.isEmpty());
388
    Q_ASSERT(!m_transformSeed.isEmpty());
389

390
    KeePass1Key key;
391
    key.setPassword(password);
392
    key.setKeyfileData(keyfileData);
393

394
    QByteArray transformedKey;
395
    bool result = key.transform(*m_db->kdf(), transformedKey);
396

397
    if (!result) {
398
        raiseError(tr("Key transformation failed"));
399
        return {};
400
    }
401

402
    CryptoHash hash(CryptoHash::Sha256);
403
    hash.addData(m_masterSeed);
404
    hash.addData(transformedKey);
405
    return hash.result();
406
}
407

408
bool KeePass1Reader::verifyKey(SymmetricCipherStream* cipherStream)
409
{
410
    CryptoHash contentHash(CryptoHash::Sha256);
411
    QByteArray buffer;
412

413
    do {
414
        if (!Tools::readFromDevice(cipherStream, buffer)) {
415
            return false;
416
        }
417
        contentHash.addData(buffer);
418
    } while (!buffer.isEmpty());
419

420
    return contentHash.result() == m_contentHashHeader;
421
}
422

423
Group* KeePass1Reader::readGroup(QIODevice* cipherStream)
424
{
425
    QScopedPointer<Group> group(new Group());
426
    group->setUpdateTimeinfo(false);
427
    group->setParent(m_tmpParent);
428

429
    TimeInfo timeInfo;
430

431
    quint32 groupId = 0;
432
    quint32 groupLevel = 0;
433
    bool groupIdSet = false;
434
    bool groupLevelSet = false;
435

436
    bool ok;
437
    bool reachedEnd = false;
438

439
    do {
440
        auto fieldType = Endian::readSizedInt<quint16>(cipherStream, KeePass1::BYTEORDER, &ok);
441
        if (!ok) {
442
            raiseError(tr("Invalid group field type number"));
443
            return nullptr;
444
        }
445

446
        auto fieldSize = static_cast<int>(Endian::readSizedInt<quint32>(cipherStream, KeePass1::BYTEORDER, &ok));
447
        if (!ok) {
448
            raiseError(tr("Invalid group field size"));
449
            return nullptr;
450
        }
451

452
        QByteArray fieldData = cipherStream->read(fieldSize);
453
        if (fieldData.size() != fieldSize) {
454
            raiseError(tr("Read group field data doesn't match size"));
455
            return nullptr;
456
        }
457

458
        switch (fieldType) {
459
        case 0x0000:
460
            // ignore field
461
            break;
462
        case 0x0001:
463
            if (fieldSize != 4) {
464
                raiseError(tr("Incorrect group id field size"));
465
                return nullptr;
466
            }
467
            groupId = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
468
            groupIdSet = true;
469
            break;
470
        case 0x0002:
471
            group->setName(QString::fromUtf8(fieldData.constData()));
472
            break;
473
        case 0x0003: {
474
            if (fieldSize != 5) {
475
                raiseError(tr("Incorrect group creation time field size"));
476
                return nullptr;
477
            }
478
            QDateTime dateTime = dateFromPackedStruct(fieldData);
479
            if (dateTime.isValid()) {
480
                timeInfo.setCreationTime(dateTime);
481
            }
482
            break;
483
        }
484
        case 0x0004: {
485
            if (fieldSize != 5) {
486
                raiseError(tr("Incorrect group modification time field size"));
487
                return nullptr;
488
            }
489
            QDateTime dateTime = dateFromPackedStruct(fieldData);
490
            if (dateTime.isValid()) {
491
                timeInfo.setLastModificationTime(dateTime);
492
            }
493
            break;
494
        }
495
        case 0x0005: {
496
            if (fieldSize != 5) {
497
                raiseError(tr("Incorrect group access time field size"));
498
            }
499
            QDateTime dateTime = dateFromPackedStruct(fieldData);
500
            if (dateTime.isValid()) {
501
                timeInfo.setLastAccessTime(dateTime);
502
            }
503
            break;
504
        }
505
        case 0x0006: {
506
            if (fieldSize != 5) {
507
                raiseError(tr("Incorrect group expiry time field size"));
508
            }
509
            QDateTime dateTime = dateFromPackedStruct(fieldData);
510
            if (dateTime.isValid()) {
511
                timeInfo.setExpires(true);
512
                timeInfo.setExpiryTime(dateTime);
513
            }
514
            break;
515
        }
516
        case 0x0007: {
517
            if (fieldSize != 4) {
518
                raiseError(tr("Incorrect group icon field size"));
519
                return nullptr;
520
            }
521
            auto iconNumber = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
522
            group->setIcon(iconNumber);
523
            break;
524
        }
525
        case 0x0008: {
526
            if (fieldSize != 2) {
527
                raiseError(tr("Incorrect group level field size"));
528
                return nullptr;
529
            }
530
            groupLevel = Endian::bytesToSizedInt<quint16>(fieldData, KeePass1::BYTEORDER);
531
            groupLevelSet = true;
532
            break;
533
        }
534
        case 0x0009:
535
            // flags, ignore field
536
            break;
537
        case 0xFFFF:
538
            reachedEnd = true;
539
            break;
540
        default:
541
            // invalid field
542
            raiseError(tr("Invalid group field type"));
543
            return nullptr;
544
        }
545
    } while (!reachedEnd);
546

547
    if (!groupIdSet || !groupLevelSet) {
548
        raiseError(tr("Missing group id or level"));
549
        return nullptr;
550
    }
551

552
    group->setUuid(QUuid::createUuid());
553
    group->setTimeInfo(timeInfo);
554
    m_groupIds.insert(groupId, group.data());
555
    m_groupLevels.insert(group.data(), groupLevel);
556

557
    return group.take();
558
}
559

560
Entry* KeePass1Reader::readEntry(QIODevice* cipherStream)
561
{
562
    QScopedPointer<Entry> entry(new Entry());
563
    entry->setUpdateTimeinfo(false);
564
    entry->setGroup(m_tmpParent);
565

566
    TimeInfo timeInfo;
567
    QString binaryName;
568
    bool ok;
569
    bool reachedEnd = false;
570

571
    do {
572
        auto fieldType = Endian::readSizedInt<quint16>(cipherStream, KeePass1::BYTEORDER, &ok);
573
        if (!ok) {
574
            raiseError(tr("Missing entry field type number"));
575
            return nullptr;
576
        }
577

578
        auto fieldSize = static_cast<int>(Endian::readSizedInt<quint32>(cipherStream, KeePass1::BYTEORDER, &ok));
579
        if (!ok) {
580
            raiseError(tr("Invalid entry field size"));
581
            return nullptr;
582
        }
583

584
        QByteArray fieldData = cipherStream->read(fieldSize);
585
        if (fieldData.size() != fieldSize) {
586
            raiseError(tr("Read entry field data doesn't match size"));
587
            return nullptr;
588
        }
589

590
        switch (fieldType) {
591
        case 0x0000:
592
            // ignore field
593
            break;
594
        case 0x0001:
595
            if (fieldSize != 16) {
596
                raiseError(tr("Invalid entry UUID field size"));
597
                return nullptr;
598
            }
599
            m_entryUuids.insert(fieldData, entry.data());
600
            break;
601
        case 0x0002: {
602
            if (fieldSize != 4) {
603
                raiseError(tr("Invalid entry group id field size"));
604
                return nullptr;
605
            }
606
            auto groupId = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
607
            m_entryGroupIds.insert(entry.data(), groupId);
608
            break;
609
        }
610
        case 0x0003: {
611
            if (fieldSize != 4) {
612
                raiseError(tr("Invalid entry icon field size"));
613
                return nullptr;
614
            }
615
            auto iconNumber = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
616
            entry->setIcon(iconNumber);
617
            break;
618
        }
619
        case 0x0004:
620
            entry->setTitle(QString::fromUtf8(fieldData.constData()));
621
            break;
622
        case 0x0005:
623
            entry->setUrl(QString::fromUtf8(fieldData.constData()));
624
            break;
625
        case 0x0006:
626
            entry->setUsername(QString::fromUtf8(fieldData.constData()));
627
            break;
628
        case 0x0007:
629
            entry->setPassword(QString::fromUtf8(fieldData.constData()));
630
            break;
631
        case 0x0008:
632
            parseNotes(QString::fromUtf8(fieldData.constData()), entry.data());
633
            break;
634
        case 0x0009: {
635
            if (fieldSize != 5) {
636
                raiseError(tr("Invalid entry creation time field size"));
637
                return nullptr;
638
            }
639
            QDateTime dateTime = dateFromPackedStruct(fieldData);
640
            if (dateTime.isValid()) {
641
                timeInfo.setCreationTime(dateTime);
642
            }
643
            break;
644
        }
645
        case 0x000A: {
646
            if (fieldSize != 5) {
647
                raiseError(tr("Invalid entry modification time field size"));
648
                return nullptr;
649
            }
650
            QDateTime dateTime = dateFromPackedStruct(fieldData);
651
            if (dateTime.isValid()) {
652
                timeInfo.setLastModificationTime(dateTime);
653
            }
654
            break;
655
        }
656
        case 0x000B: {
657
            if (fieldSize != 5) {
658
                raiseError(tr("Invalid entry creation time field size"));
659
                return nullptr;
660
            }
661
            QDateTime dateTime = dateFromPackedStruct(fieldData);
662
            if (dateTime.isValid()) {
663
                timeInfo.setLastAccessTime(dateTime);
664
            }
665
            break;
666
        }
667
        case 0x000C: {
668
            if (fieldSize != 5) {
669
                raiseError(tr("Invalid entry expiry time field size"));
670
                return nullptr;
671
            }
672
            QDateTime dateTime = dateFromPackedStruct(fieldData);
673
            if (dateTime.isValid()) {
674
                timeInfo.setExpires(true);
675
                timeInfo.setExpiryTime(dateTime);
676
            }
677
            break;
678
        }
679
        case 0x000D:
680
            binaryName = QString::fromUtf8(fieldData.constData());
681
            break;
682
        case 0x000E:
683
            if (fieldSize != 0) {
684
                entry->attachments()->set(binaryName, fieldData);
685
            }
686
            break;
687
        case 0xFFFF:
688
            reachedEnd = true;
689
            break;
690
        default:
691
            // invalid field
692
            raiseError(tr("Invalid entry field type"));
693
            return nullptr;
694
        }
695
    } while (!reachedEnd);
696

697
    entry->setTimeInfo(timeInfo);
698

699
    return entry.take();
700
}
701

702
void KeePass1Reader::parseNotes(const QString& rawNotes, Entry* entry)
703
{
704
    QRegExp sequenceRegexp("Auto-Type(?:-(\\d+))?: (.+)", Qt::CaseInsensitive, QRegExp::RegExp2);
705
    QRegExp windowRegexp("Auto-Type-Window(?:-(\\d+))?: (.+)", Qt::CaseInsensitive, QRegExp::RegExp2);
706
    QHash<int, QString> sequences;
707
    QMap<int, QStringList> windows;
708

709
    QStringList notes;
710

711
    bool lastLineAutoType = false;
712
    const QStringList rawNotesLines = rawNotes.split("\n");
713
    for (QString line : rawNotesLines) {
714
        line.remove("\r");
715

716
        if (sequenceRegexp.exactMatch(line)) {
717
            if (sequenceRegexp.cap(1).isEmpty()) {
718
                entry->setDefaultAutoTypeSequence(sequenceRegexp.cap(2));
719
            } else {
720
                sequences[sequenceRegexp.cap(1).toInt()] = sequenceRegexp.cap(2);
721
            }
722

723
            lastLineAutoType = true;
724
        } else if (windowRegexp.exactMatch(line)) {
725
            int nr;
726
            if (windowRegexp.cap(1).isEmpty()) {
727
                nr = -1; // special number that matches no other sequence
728
            } else {
729
                nr = windowRegexp.cap(1).toInt();
730
            }
731

732
            windows[nr].append(windowRegexp.cap(2));
733

734
            lastLineAutoType = true;
735
        } else {
736
            // don't add empty lines following a removed auto-type line
737
            if (!lastLineAutoType || !line.isEmpty()) {
738
                notes.append(line);
739
            }
740
            lastLineAutoType = false;
741
        }
742
    }
743

744
    entry->setNotes(notes.join("\n"));
745

746
    QMapIterator<int, QStringList> i(windows);
747
    while (i.hasNext()) {
748
        i.next();
749

750
        QString sequence = sequences.value(i.key());
751
        const QStringList windowList = i.value();
752

753
        for (const QString& window : windowList) {
754
            AutoTypeAssociations::Association assoc;
755
            assoc.window = window;
756
            assoc.sequence = sequence;
757
            entry->autoTypeAssociations()->add(assoc);
758
        }
759
    }
760
}
761

762
bool KeePass1Reader::constructGroupTree(const QList<Group*>& groups)
763
{
764
    for (int i = 0; i < groups.size(); i++) {
765
        quint32 level = m_groupLevels.value(groups[i]);
766

767
        if (level == 0) {
768
            groups[i]->setParent(m_db->rootGroup());
769
        } else {
770
            for (int j = (i - 1); j >= 0; j--) {
771
                if (m_groupLevels.value(groups[j]) < level) {
772
                    if ((level - m_groupLevels.value(groups[j])) != 1) {
773
                        return false;
774
                    }
775

776
                    groups[i]->setParent(groups[j]);
777
                    break;
778
                }
779
            }
780
        }
781

782
        if (groups[i]->parentGroup() == m_tmpParent) {
783
            return false;
784
        }
785
    }
786

787
    return true;
788
}
789

790
void KeePass1Reader::parseMetaStream(const Entry* entry)
791
{
792
    QByteArray data = entry->attachments()->value("bin-stream");
793

794
    if (entry->notes() == "KPX_GROUP_TREE_STATE") {
795
        if (!parseGroupTreeState(data)) {
796
            qWarning("Unable to parse group tree state metastream.");
797
        }
798
    } else if (entry->notes() == "KPX_CUSTOM_ICONS_4") {
799
        if (!parseCustomIcons4(data)) {
800
            qWarning("Unable to parse custom icons metastream.");
801
        }
802
    } else {
803
        qWarning("Ignoring unknown metastream \"%s\".", entry->notes().toLocal8Bit().constData());
804
    }
805
}
806

807
bool KeePass1Reader::parseGroupTreeState(const QByteArray& data)
808
{
809
    if (data.size() < 4) {
810
        return false;
811
    }
812

813
    int pos = 0;
814
    auto num = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
815
    pos += 4;
816

817
    if (static_cast<quint32>(data.size() - 4) != (num * 5)) {
818
        return false;
819
    }
820

821
    for (quint32 i = 0; i < num; i++) {
822
        auto groupId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
823
        pos += 4;
824

825
        bool expanded = data.at(pos);
826
        pos += 1;
827

828
        if (m_groupIds.contains(groupId)) {
829
            m_groupIds[groupId]->setExpanded(expanded);
830
        }
831
    }
832

833
    return true;
834
}
835

836
bool KeePass1Reader::parseCustomIcons4(const QByteArray& data)
837
{
838
    if (data.size() < 12) {
839
        return false;
840
    }
841

842
    int pos = 0;
843

844
    auto numIcons = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
845
    pos += 4;
846

847
    auto numEntries = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
848
    pos += 4;
849

850
    auto numGroups = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
851
    pos += 4;
852

853
    QList<QUuid> iconUuids;
854

855
    for (quint32 i = 0; i < numIcons; i++) {
856
        if (data.size() < (pos + 4)) {
857
            return false;
858
        }
859
        auto iconSize = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
860
        pos += 4;
861

862
        if (static_cast<quint32>(data.size()) < (pos + iconSize)) {
863
            return false;
864
        }
865
        QByteArray icon = data.mid(pos, iconSize);
866
        pos += iconSize;
867

868
        QUuid uuid = QUuid::createUuid();
869
        iconUuids.append(uuid);
870
        m_db->metadata()->addCustomIcon(uuid, icon);
871
    }
872

873
    if (static_cast<quint32>(data.size()) < (pos + numEntries * 20)) {
874
        return false;
875
    }
876

877
    for (quint32 i = 0; i < numEntries; i++) {
878
        QByteArray entryUuid = data.mid(pos, 16);
879
        pos += 16;
880

881
        auto iconId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
882
        pos += 4;
883

884
        if (m_entryUuids.contains(entryUuid) && (iconId < static_cast<quint32>(iconUuids.size()))) {
885
            m_entryUuids[entryUuid]->setIcon(iconUuids[iconId]);
886
        }
887
    }
888

889
    if (static_cast<quint32>(data.size()) < (pos + numGroups * 8)) {
890
        return false;
891
    }
892

893
    for (quint32 i = 0; i < numGroups; i++) {
894
        auto groupId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
895
        pos += 4;
896

897
        auto iconId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
898
        pos += 4;
899

900
        if (m_groupIds.contains(groupId) && (iconId < static_cast<quint32>(iconUuids.size()))) {
901
            m_groupIds[groupId]->setIcon(iconUuids[iconId]);
902
        }
903
    }
904

905
    return true;
906
}
907

908
void KeePass1Reader::raiseError(const QString& errorMessage)
909
{
910
    m_error = true;
911
    m_errorStr = errorMessage;
912
}
913

914
QDateTime KeePass1Reader::dateFromPackedStruct(const QByteArray& data)
915
{
916
    Q_ASSERT(data.size() == 5);
917

918
    quint32 dw1 = static_cast<uchar>(data.at(0));
919
    quint32 dw2 = static_cast<uchar>(data.at(1));
920
    quint32 dw3 = static_cast<uchar>(data.at(2));
921
    quint32 dw4 = static_cast<uchar>(data.at(3));
922
    quint32 dw5 = static_cast<uchar>(data.at(4));
923

924
    int y = (dw1 << 6) | (dw2 >> 2);
925
    int mon = ((dw2 & 0x00000003) << 2) | (dw3 >> 6);
926
    int d = (dw3 >> 1) & 0x0000001F;
927
    int h = ((dw3 & 0x00000001) << 4) | (dw4 >> 4);
928
    int min = ((dw4 & 0x0000000F) << 2) | (dw5 >> 6);
929
    int s = dw5 & 0x0000003F;
930

931
    QDateTime dateTime = QDateTime(QDate(y, mon, d), QTime(h, min, s), Qt::UTC);
932

933
    // check for the special "never" datetime
934
    if (dateTime == QDateTime(QDate(2999, 12, 28), QTime(23, 59, 59), Qt::UTC)) {
935
        return {};
936
    } else {
937
        return dateTime;
938
    }
939
}
940

941
bool KeePass1Reader::isMetaStream(const Entry* entry)
942
{
943
    return entry->attachments()->keys().contains("bin-stream") && !entry->notes().isEmpty()
944
           && entry->title() == "Meta-Info" && entry->username() == "SYSTEM" && entry->url() == "$"
945
           && entry->iconNumber() == 0;
946
}
947

948
QByteArray KeePass1Reader::readKeyfile(QIODevice* device)
949
{
950
    if (device->size() == 0) {
951
        return {};
952
    }
953

954
    if (device->size() == 32) {
955
        QByteArray data = device->read(32);
956
        if (data.size() != 32) {
957
            return {};
958
        }
959

960
        return data;
961
    }
962

963
    if (device->size() == 64) {
964
        QByteArray data = device->read(64);
965

966
        if (data.size() != 64) {
967
            return {};
968
        }
969

970
        if (Tools::isHex(data)) {
971
            return QByteArray::fromHex(data);
972
        } else {
973
            device->seek(0);
974
        }
975
    }
976

977
    CryptoHash cryptoHash(CryptoHash::Sha256);
978
    QByteArray buffer;
979

980
    do {
981
        if (!Tools::readFromDevice(device, buffer)) {
982
            return {};
983
        }
984
        cryptoHash.addData(buffer);
985
    } while (!buffer.isEmpty());
986

987
    return cryptoHash.result();
988
}
989

990
QByteArray KeePass1Key::rawKey() const
991
{
992
    if (m_keyfileData.isEmpty()) {
993
        return CryptoHash::hash(m_password, CryptoHash::Sha256);
994
    } else if (m_password.isEmpty()) {
995
        return m_keyfileData;
996
    } else {
997
        CryptoHash keyHash(CryptoHash::Sha256);
998
        keyHash.addData(CryptoHash::hash(m_password, CryptoHash::Sha256));
999
        keyHash.addData(m_keyfileData);
1000
        return keyHash.result();
1001
    }
1002
}
1003

1004
void KeePass1Key::clear()
1005
{
1006
    CompositeKey::clear();
1007

1008
    m_password.clear();
1009
    m_keyfileData.clear();
1010
}
1011

1012
void KeePass1Key::setPassword(const QByteArray& password)
1013
{
1014
    m_password = password;
1015
}
1016

1017
void KeePass1Key::setKeyfileData(const QByteArray& keyfileData)
1018
{
1019
    m_keyfileData = keyfileData;
1020
}
1021

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

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

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

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