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 <QImage>
22 #include <QTextCodec>
23 
24 #include "core/Database.h"
25 #include "core/Endian.h"
26 #include "core/Entry.h"
27 #include "core/Group.h"
28 #include "core/Metadata.h"
29 #include "core/Tools.h"
30 #include "crypto/CryptoHash.h"
31 #include "crypto/kdf/AesKdf.h"
32 #include "format/KeePass1.h"
33 #include "keys/FileKey.h"
34 #include "keys/PasswordKey.h"
35 #include "streams/SymmetricCipherStream.h"
36 
37 class KeePass1Key : public CompositeKey
38 {
39 public:
40     QByteArray rawKey() const override;
41     virtual void clear();
42     void setPassword(const QByteArray& password);
43     void setKeyfileData(const QByteArray& keyfileData);
44 
45 private:
46     QByteArray m_password;
47     QByteArray m_keyfileData;
48 };
49 
KeePass1Reader()50 KeePass1Reader::KeePass1Reader()
51     : m_tmpParent(nullptr)
52     , m_device(nullptr)
53     , m_encryptionFlags(0)
54     , m_transformRounds(0)
55     , m_error(false)
56 {
57 }
58 
59 QSharedPointer<Database>
readDatabase(QIODevice * device,const QString & password,QIODevice * keyfileDevice)60 KeePass1Reader::readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice)
61 {
62     m_error = false;
63     m_errorStr.clear();
64 
65     QByteArray keyfileData;
66     auto newFileKey = QSharedPointer<FileKey>::create();
67 
68     if (keyfileDevice) {
69         keyfileData = readKeyfile(keyfileDevice);
70 
71         if (keyfileData.isEmpty()) {
72             raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
73             return {};
74         }
75         if (!keyfileDevice->seek(0)) {
76             raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
77             return {};
78         }
79 
80         if (!newFileKey->load(keyfileDevice)) {
81             raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
82             return {};
83         }
84     }
85 
86     auto db = QSharedPointer<Database>::create();
87     QScopedPointer<Group> tmpParent(new Group());
88     m_db = db;
89     m_tmpParent = tmpParent.data();
90     m_device = device;
91 
92     bool ok;
93 
94     auto signature1 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
95     if (!ok || signature1 != KeePass1::SIGNATURE_1) {
96         raiseError(tr("Not a KeePass database."));
97         return {};
98     }
99 
100     auto signature2 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
101     if (!ok || signature2 != KeePass1::SIGNATURE_2) {
102         raiseError(tr("Not a KeePass database."));
103         return {};
104     }
105 
106     m_encryptionFlags = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
107     if (!ok || !(m_encryptionFlags & KeePass1::Rijndael || m_encryptionFlags & KeePass1::Twofish)) {
108         raiseError(tr("Unsupported encryption algorithm."));
109         return {};
110     }
111 
112     auto version = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
113     if (!ok
114         || (version & KeePass1::FILE_VERSION_CRITICAL_MASK)
115                != (KeePass1::FILE_VERSION & KeePass1::FILE_VERSION_CRITICAL_MASK)) {
116         raiseError(tr("Unsupported KeePass database version."));
117         return {};
118     }
119 
120     m_masterSeed = m_device->read(16);
121     if (m_masterSeed.size() != 16) {
122         raiseError("Unable to read master seed");
123         return {};
124     }
125 
126     m_encryptionIV = m_device->read(16);
127     if (m_encryptionIV.size() != 16) {
128         raiseError(tr("Unable to read encryption IV", "IV = Initialization Vector for symmetric cipher"));
129         return {};
130     }
131 
132     auto numGroups = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
133     if (!ok) {
134         raiseError(tr("Invalid number of groups"));
135         return {};
136     }
137 
138     auto numEntries = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
139     if (!ok) {
140         raiseError(tr("Invalid number of entries"));
141         return {};
142     }
143 
144     m_contentHashHeader = m_device->read(32);
145     if (m_contentHashHeader.size() != 32) {
146         raiseError(tr("Invalid content hash size"));
147         return {};
148     }
149 
150     m_transformSeed = m_device->read(32);
151     if (m_transformSeed.size() != 32) {
152         raiseError(tr("Invalid transform seed size"));
153         return {};
154     }
155 
156     m_transformRounds = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
157     if (!ok) {
158         raiseError(tr("Invalid number of transform rounds"));
159         return {};
160     }
161     auto kdf = QSharedPointer<AesKdf>::create(true);
162     kdf->setRounds(m_transformRounds);
163     kdf->setSeed(m_transformSeed);
164     db->setKdf(kdf);
165 
166     qint64 contentPos = m_device->pos();
167 
168     QScopedPointer<SymmetricCipherStream> cipherStream(testKeys(password, keyfileData, contentPos));
169 
170     if (!cipherStream) {
171         return {};
172     }
173 
174     QList<Group*> groups;
175     for (quint32 i = 0; i < numGroups; i++) {
176         Group* group = readGroup(cipherStream.data());
177         if (!group) {
178             return {};
179         }
180         groups.append(group);
181     }
182 
183     QList<Entry*> entries;
184     for (quint32 i = 0; i < numEntries; i++) {
185         Entry* entry = readEntry(cipherStream.data());
186         if (!entry) {
187             return {};
188         }
189         entries.append(entry);
190     }
191 
192     if (!constructGroupTree(groups)) {
193         raiseError(tr("Unable to construct group tree"));
194         return {};
195     }
196 
197     for (Entry* entry : asConst(entries)) {
198         if (isMetaStream(entry)) {
199             parseMetaStream(entry);
200 
201             delete entry;
202         } else {
203             quint32 groupId = m_entryGroupIds.value(entry);
204             if (!m_groupIds.contains(groupId)) {
205                 qWarning("Orphaned entry found, assigning to root group.");
206                 entry->setGroup(m_db->rootGroup());
207             } else {
208                 entry->setGroup(m_groupIds.value(groupId));
209             }
210             entry->setUuid(QUuid::createUuid());
211         }
212     }
213 
214     db->rootGroup()->setName(tr("Root"));
215 
216     const QList<Group*> children = db->rootGroup()->children();
217     for (Group* group : children) {
218         if (group->name() == "Backup") {
219             group->setSearchingEnabled(Group::Disable);
220             group->setAutoTypeEnabled(Group::Disable);
221         }
222     }
223 
224     Q_ASSERT(m_tmpParent->children().isEmpty());
225     Q_ASSERT(m_tmpParent->entries().isEmpty());
226 
227     for (Group* group : asConst(groups)) {
228         group->setUpdateTimeinfo(true);
229     }
230 
231     const QList<Entry*> dbEntries = m_db->rootGroup()->entriesRecursive();
232     for (Entry* entry : dbEntries) {
233         entry->setUpdateTimeinfo(true);
234     }
235 
236     auto key = QSharedPointer<CompositeKey>::create();
237     if (!password.isEmpty()) {
238         key->addKey(QSharedPointer<PasswordKey>::create(password));
239     }
240     if (keyfileDevice) {
241         key->addKey(newFileKey);
242     }
243 
244     if (!db->setKey(key)) {
245         raiseError(tr("Unable to calculate database key"));
246         return {};
247     }
248 
249     return db;
250 }
251 
252 QSharedPointer<Database>
readDatabase(QIODevice * device,const QString & password,const QString & keyfileName)253 KeePass1Reader::readDatabase(QIODevice* device, const QString& password, const QString& keyfileName)
254 {
255     QScopedPointer<QFile> keyFile;
256     if (!keyfileName.isEmpty()) {
257         keyFile.reset(new QFile(keyfileName));
258         if (!keyFile->open(QFile::ReadOnly)) {
259             raiseError(keyFile->errorString());
260             return {};
261         }
262     }
263 
264     return QSharedPointer<Database>(readDatabase(device, password, keyFile.data()));
265 }
266 
267 QSharedPointer<Database>
readDatabase(const QString & filename,const QString & password,const QString & keyfileName)268 KeePass1Reader::readDatabase(const QString& filename, const QString& password, const QString& keyfileName)
269 {
270     QFile dbFile(filename);
271     if (!dbFile.open(QFile::ReadOnly)) {
272         raiseError(dbFile.errorString());
273         return {};
274     }
275 
276     auto db = readDatabase(&dbFile, password, keyfileName);
277 
278     if (dbFile.error() != QFile::NoError) {
279         raiseError(dbFile.errorString());
280         return {};
281     }
282 
283     return db;
284 }
285 
hasError()286 bool KeePass1Reader::hasError()
287 {
288     return m_error;
289 }
290 
errorString()291 QString KeePass1Reader::errorString()
292 {
293     return m_errorStr;
294 }
295 
296 SymmetricCipherStream*
testKeys(const QString & password,const QByteArray & keyfileData,qint64 contentPos)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         if (m_encryptionFlags & KeePass1::Rijndael) {
336             cipherStream.reset(new SymmetricCipherStream(
337                 m_device, SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
338         } else {
339             cipherStream.reset(new SymmetricCipherStream(
340                 m_device, SymmetricCipher::Twofish, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
341         }
342 
343         if (!cipherStream->init(finalKey, m_encryptionIV)) {
344             raiseError(cipherStream->errorString());
345             return nullptr;
346         }
347         if (!cipherStream->open(QIODevice::ReadOnly)) {
348             raiseError(cipherStream->errorString());
349             return nullptr;
350         }
351 
352         bool success = verifyKey(cipherStream.data());
353 
354         cipherStream->reset();
355         cipherStream->close();
356         if (!m_device->seek(contentPos)) {
357             QString msg = tr("unable to seek to content position");
358             if (!m_device->errorString().isEmpty()) {
359                 msg.append("\n").append(m_device->errorString());
360             }
361             raiseError(msg);
362 
363             return nullptr;
364         }
365         cipherStream->open(QIODevice::ReadOnly);
366 
367         if (success) {
368             break;
369         } else {
370             cipherStream.reset();
371         }
372     }
373 
374     if (!cipherStream) {
375         raiseError(tr("Invalid credentials were provided, please try again.\n"
376                       "If this reoccurs, then your database file may be corrupt."));
377     }
378 
379     return cipherStream.take();
380 }
381 
key(const QByteArray & password,const QByteArray & keyfileData)382 QByteArray KeePass1Reader::key(const QByteArray& password, const QByteArray& keyfileData)
383 {
384     Q_ASSERT(!m_masterSeed.isEmpty());
385     Q_ASSERT(!m_transformSeed.isEmpty());
386 
387     KeePass1Key key;
388     key.setPassword(password);
389     key.setKeyfileData(keyfileData);
390 
391     QByteArray transformedKey;
392     bool result = key.transform(*m_db->kdf(), transformedKey);
393 
394     if (!result) {
395         raiseError(tr("Key transformation failed"));
396         return QByteArray();
397     }
398 
399     CryptoHash hash(CryptoHash::Sha256);
400     hash.addData(m_masterSeed);
401     hash.addData(transformedKey);
402     return hash.result();
403 }
404 
verifyKey(SymmetricCipherStream * cipherStream)405 bool KeePass1Reader::verifyKey(SymmetricCipherStream* cipherStream)
406 {
407     CryptoHash contentHash(CryptoHash::Sha256);
408     QByteArray buffer;
409 
410     do {
411         if (!Tools::readFromDevice(cipherStream, buffer)) {
412             return false;
413         }
414         contentHash.addData(buffer);
415     } while (!buffer.isEmpty());
416 
417     return contentHash.result() == m_contentHashHeader;
418 }
419 
readGroup(QIODevice * cipherStream)420 Group* KeePass1Reader::readGroup(QIODevice* cipherStream)
421 {
422     QScopedPointer<Group> group(new Group());
423     group->setUpdateTimeinfo(false);
424     group->setParent(m_tmpParent);
425 
426     TimeInfo timeInfo;
427 
428     quint32 groupId = 0;
429     quint32 groupLevel = 0;
430     bool groupIdSet = false;
431     bool groupLevelSet = false;
432 
433     bool ok;
434     bool reachedEnd = false;
435 
436     do {
437         quint16 fieldType = Endian::readSizedInt<quint16>(cipherStream, KeePass1::BYTEORDER, &ok);
438         if (!ok) {
439             raiseError(tr("Invalid group field type number"));
440             return nullptr;
441         }
442 
443         int fieldSize = static_cast<int>(Endian::readSizedInt<quint32>(cipherStream, KeePass1::BYTEORDER, &ok));
444         if (!ok) {
445             raiseError(tr("Invalid group field size"));
446             return nullptr;
447         }
448 
449         QByteArray fieldData = cipherStream->read(fieldSize);
450         if (fieldData.size() != fieldSize) {
451             raiseError(tr("Read group field data doesn't match size"));
452             return nullptr;
453         }
454 
455         switch (fieldType) {
456         case 0x0000:
457             // ignore field
458             break;
459         case 0x0001:
460             if (fieldSize != 4) {
461                 raiseError(tr("Incorrect group id field size"));
462                 return nullptr;
463             }
464             groupId = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
465             groupIdSet = true;
466             break;
467         case 0x0002:
468             group->setName(QString::fromUtf8(fieldData.constData()));
469             break;
470         case 0x0003: {
471             if (fieldSize != 5) {
472                 raiseError(tr("Incorrect group creation time field size"));
473                 return nullptr;
474             }
475             QDateTime dateTime = dateFromPackedStruct(fieldData);
476             if (dateTime.isValid()) {
477                 timeInfo.setCreationTime(dateTime);
478             }
479             break;
480         }
481         case 0x0004: {
482             if (fieldSize != 5) {
483                 raiseError(tr("Incorrect group modification time field size"));
484                 return nullptr;
485             }
486             QDateTime dateTime = dateFromPackedStruct(fieldData);
487             if (dateTime.isValid()) {
488                 timeInfo.setLastModificationTime(dateTime);
489             }
490             break;
491         }
492         case 0x0005: {
493             if (fieldSize != 5) {
494                 raiseError(tr("Incorrect group access time field size"));
495             }
496             QDateTime dateTime = dateFromPackedStruct(fieldData);
497             if (dateTime.isValid()) {
498                 timeInfo.setLastAccessTime(dateTime);
499             }
500             break;
501         }
502         case 0x0006: {
503             if (fieldSize != 5) {
504                 raiseError(tr("Incorrect group expiry time field size"));
505             }
506             QDateTime dateTime = dateFromPackedStruct(fieldData);
507             if (dateTime.isValid()) {
508                 timeInfo.setExpires(true);
509                 timeInfo.setExpiryTime(dateTime);
510             }
511             break;
512         }
513         case 0x0007: {
514             if (fieldSize != 4) {
515                 raiseError(tr("Incorrect group icon field size"));
516                 return nullptr;
517             }
518             quint32 iconNumber = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
519             group->setIcon(iconNumber);
520             break;
521         }
522         case 0x0008: {
523             if (fieldSize != 2) {
524                 raiseError(tr("Incorrect group level field size"));
525                 return nullptr;
526             }
527             groupLevel = Endian::bytesToSizedInt<quint16>(fieldData, KeePass1::BYTEORDER);
528             groupLevelSet = true;
529             break;
530         }
531         case 0x0009:
532             // flags, ignore field
533             break;
534         case 0xFFFF:
535             reachedEnd = true;
536             break;
537         default:
538             // invalid field
539             raiseError(tr("Invalid group field type"));
540             return nullptr;
541         }
542     } while (!reachedEnd);
543 
544     if (!groupIdSet || !groupLevelSet) {
545         raiseError(tr("Missing group id or level"));
546         return nullptr;
547     }
548 
549     group->setUuid(QUuid::createUuid());
550     group->setTimeInfo(timeInfo);
551     m_groupIds.insert(groupId, group.data());
552     m_groupLevels.insert(group.data(), groupLevel);
553 
554     return group.take();
555 }
556 
readEntry(QIODevice * cipherStream)557 Entry* KeePass1Reader::readEntry(QIODevice* cipherStream)
558 {
559     QScopedPointer<Entry> entry(new Entry());
560     entry->setUpdateTimeinfo(false);
561     entry->setGroup(m_tmpParent);
562 
563     TimeInfo timeInfo;
564     QString binaryName;
565     bool ok;
566     bool reachedEnd = false;
567 
568     do {
569         quint16 fieldType = Endian::readSizedInt<quint16>(cipherStream, KeePass1::BYTEORDER, &ok);
570         if (!ok) {
571             raiseError(tr("Missing entry field type number"));
572             return nullptr;
573         }
574 
575         int fieldSize = static_cast<int>(Endian::readSizedInt<quint32>(cipherStream, KeePass1::BYTEORDER, &ok));
576         if (!ok) {
577             raiseError(tr("Invalid entry field size"));
578             return nullptr;
579         }
580 
581         QByteArray fieldData = cipherStream->read(fieldSize);
582         if (fieldData.size() != fieldSize) {
583             raiseError(tr("Read entry field data doesn't match size"));
584             return nullptr;
585         }
586 
587         switch (fieldType) {
588         case 0x0000:
589             // ignore field
590             break;
591         case 0x0001:
592             if (fieldSize != 16) {
593                 raiseError(tr("Invalid entry uuid field size"));
594                 return nullptr;
595             }
596             m_entryUuids.insert(fieldData, entry.data());
597             break;
598         case 0x0002: {
599             if (fieldSize != 4) {
600                 raiseError(tr("Invalid entry group id field size"));
601                 return nullptr;
602             }
603             quint32 groupId = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
604             m_entryGroupIds.insert(entry.data(), groupId);
605             break;
606         }
607         case 0x0003: {
608             if (fieldSize != 4) {
609                 raiseError(tr("Invalid entry icon field size"));
610                 return nullptr;
611             }
612             quint32 iconNumber = Endian::bytesToSizedInt<quint32>(fieldData, KeePass1::BYTEORDER);
613             entry->setIcon(iconNumber);
614             break;
615         }
616         case 0x0004:
617             entry->setTitle(QString::fromUtf8(fieldData.constData()));
618             break;
619         case 0x0005:
620             entry->setUrl(QString::fromUtf8(fieldData.constData()));
621             break;
622         case 0x0006:
623             entry->setUsername(QString::fromUtf8(fieldData.constData()));
624             break;
625         case 0x0007:
626             entry->setPassword(QString::fromUtf8(fieldData.constData()));
627             break;
628         case 0x0008:
629             parseNotes(QString::fromUtf8(fieldData.constData()), entry.data());
630             break;
631         case 0x0009: {
632             if (fieldSize != 5) {
633                 raiseError(tr("Invalid entry creation time field size"));
634                 return nullptr;
635             }
636             QDateTime dateTime = dateFromPackedStruct(fieldData);
637             if (dateTime.isValid()) {
638                 timeInfo.setCreationTime(dateTime);
639             }
640             break;
641         }
642         case 0x000A: {
643             if (fieldSize != 5) {
644                 raiseError(tr("Invalid entry modification time field size"));
645                 return nullptr;
646             }
647             QDateTime dateTime = dateFromPackedStruct(fieldData);
648             if (dateTime.isValid()) {
649                 timeInfo.setLastModificationTime(dateTime);
650             }
651             break;
652         }
653         case 0x000B: {
654             if (fieldSize != 5) {
655                 raiseError(tr("Invalid entry creation time field size"));
656                 return nullptr;
657             }
658             QDateTime dateTime = dateFromPackedStruct(fieldData);
659             if (dateTime.isValid()) {
660                 timeInfo.setLastAccessTime(dateTime);
661             }
662             break;
663         }
664         case 0x000C: {
665             if (fieldSize != 5) {
666                 raiseError(tr("Invalid entry expiry time field size"));
667                 return nullptr;
668             }
669             QDateTime dateTime = dateFromPackedStruct(fieldData);
670             if (dateTime.isValid()) {
671                 timeInfo.setExpires(true);
672                 timeInfo.setExpiryTime(dateTime);
673             }
674             break;
675         }
676         case 0x000D:
677             binaryName = QString::fromUtf8(fieldData.constData());
678             break;
679         case 0x000E:
680             if (fieldSize != 0) {
681                 entry->attachments()->set(binaryName, fieldData);
682             }
683             break;
684         case 0xFFFF:
685             reachedEnd = true;
686             break;
687         default:
688             // invalid field
689             raiseError(tr("Invalid entry field type"));
690             return nullptr;
691         }
692     } while (!reachedEnd);
693 
694     entry->setTimeInfo(timeInfo);
695 
696     return entry.take();
697 }
698 
parseNotes(const QString & rawNotes,Entry * entry)699 void KeePass1Reader::parseNotes(const QString& rawNotes, Entry* entry)
700 {
701     QRegExp sequenceRegexp("Auto-Type(?:-(\\d+))?: (.+)", Qt::CaseInsensitive, QRegExp::RegExp2);
702     QRegExp windowRegexp("Auto-Type-Window(?:-(\\d+))?: (.+)", Qt::CaseInsensitive, QRegExp::RegExp2);
703     QHash<int, QString> sequences;
704     QMap<int, QStringList> windows;
705 
706     QStringList notes;
707 
708     bool lastLineAutoType = false;
709     const QStringList rawNotesLines = rawNotes.split("\n");
710     for (QString line : rawNotesLines) {
711         line.remove("\r");
712 
713         if (sequenceRegexp.exactMatch(line)) {
714             if (sequenceRegexp.cap(1).isEmpty()) {
715                 entry->setDefaultAutoTypeSequence(sequenceRegexp.cap(2));
716             } else {
717                 sequences[sequenceRegexp.cap(1).toInt()] = sequenceRegexp.cap(2);
718             }
719 
720             lastLineAutoType = true;
721         } else if (windowRegexp.exactMatch(line)) {
722             int nr;
723             if (windowRegexp.cap(1).isEmpty()) {
724                 nr = -1; // special number that matches no other sequence
725             } else {
726                 nr = windowRegexp.cap(1).toInt();
727             }
728 
729             windows[nr].append(windowRegexp.cap(2));
730 
731             lastLineAutoType = true;
732         } else {
733             // don't add empty lines following a removed auto-type line
734             if (!lastLineAutoType || !line.isEmpty()) {
735                 notes.append(line);
736             }
737             lastLineAutoType = false;
738         }
739     }
740 
741     entry->setNotes(notes.join("\n"));
742 
743     QMapIterator<int, QStringList> i(windows);
744     while (i.hasNext()) {
745         i.next();
746 
747         QString sequence = sequences.value(i.key());
748         const QStringList windowList = i.value();
749 
750         for (const QString& window : windowList) {
751             AutoTypeAssociations::Association assoc;
752             assoc.window = window;
753             assoc.sequence = sequence;
754             entry->autoTypeAssociations()->add(assoc);
755         }
756     }
757 }
758 
constructGroupTree(const QList<Group * > & groups)759 bool KeePass1Reader::constructGroupTree(const QList<Group*>& groups)
760 {
761     for (int i = 0; i < groups.size(); i++) {
762         quint32 level = m_groupLevels.value(groups[i]);
763 
764         if (level == 0) {
765             groups[i]->setParent(m_db->rootGroup());
766         } else {
767             for (int j = (i - 1); j >= 0; j--) {
768                 if (m_groupLevels.value(groups[j]) < level) {
769                     if ((level - m_groupLevels.value(groups[j])) != 1) {
770                         return false;
771                     }
772 
773                     groups[i]->setParent(groups[j]);
774                     break;
775                 }
776             }
777         }
778 
779         if (groups[i]->parentGroup() == m_tmpParent) {
780             return false;
781         }
782     }
783 
784     return true;
785 }
786 
parseMetaStream(const Entry * entry)787 void KeePass1Reader::parseMetaStream(const Entry* entry)
788 {
789     QByteArray data = entry->attachments()->value("bin-stream");
790 
791     if (entry->notes() == "KPX_GROUP_TREE_STATE") {
792         if (!parseGroupTreeState(data)) {
793             qWarning("Unable to parse group tree state metastream.");
794         }
795     } else if (entry->notes() == "KPX_CUSTOM_ICONS_4") {
796         if (!parseCustomIcons4(data)) {
797             qWarning("Unable to parse custom icons metastream.");
798         }
799     } else {
800         qWarning("Ignoring unknown metastream \"%s\".", entry->notes().toLocal8Bit().constData());
801     }
802 }
803 
parseGroupTreeState(const QByteArray & data)804 bool KeePass1Reader::parseGroupTreeState(const QByteArray& data)
805 {
806     if (data.size() < 4) {
807         return false;
808     }
809 
810     int pos = 0;
811     quint32 num = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
812     pos += 4;
813 
814     if (static_cast<quint32>(data.size() - 4) != (num * 5)) {
815         return false;
816     }
817 
818     for (quint32 i = 0; i < num; i++) {
819         quint32 groupId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
820         pos += 4;
821 
822         bool expanded = data.at(pos);
823         pos += 1;
824 
825         if (m_groupIds.contains(groupId)) {
826             m_groupIds[groupId]->setExpanded(expanded);
827         }
828     }
829 
830     return true;
831 }
832 
parseCustomIcons4(const QByteArray & data)833 bool KeePass1Reader::parseCustomIcons4(const QByteArray& data)
834 {
835     if (data.size() < 12) {
836         return false;
837     }
838 
839     int pos = 0;
840 
841     quint32 numIcons = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
842     pos += 4;
843 
844     quint32 numEntries = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
845     pos += 4;
846 
847     quint32 numGroups = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
848     pos += 4;
849 
850     QList<QUuid> iconUuids;
851 
852     for (quint32 i = 0; i < numIcons; i++) {
853         if (data.size() < (pos + 4)) {
854             return false;
855         }
856         quint32 iconSize = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
857         pos += 4;
858 
859         if (static_cast<quint32>(data.size()) < (pos + iconSize)) {
860             return false;
861         }
862         QImage icon = QImage::fromData(data.mid(pos, iconSize));
863         pos += iconSize;
864 
865         if (icon.width() != 16 || icon.height() != 16) {
866             icon = icon.scaled(16, 16);
867         }
868 
869         QUuid uuid = QUuid::createUuid();
870         iconUuids.append(uuid);
871         m_db->metadata()->addCustomIcon(uuid, icon);
872     }
873 
874     if (static_cast<quint32>(data.size()) < (pos + numEntries * 20)) {
875         return false;
876     }
877 
878     for (quint32 i = 0; i < numEntries; i++) {
879         QByteArray entryUuid = data.mid(pos, 16);
880         pos += 16;
881 
882         quint32 iconId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
883         pos += 4;
884 
885         if (m_entryUuids.contains(entryUuid) && (iconId < static_cast<quint32>(iconUuids.size()))) {
886             m_entryUuids[entryUuid]->setIcon(iconUuids[iconId]);
887         }
888     }
889 
890     if (static_cast<quint32>(data.size()) < (pos + numGroups * 8)) {
891         return false;
892     }
893 
894     for (quint32 i = 0; i < numGroups; i++) {
895         quint32 groupId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
896         pos += 4;
897 
898         quint32 iconId = Endian::bytesToSizedInt<quint32>(data.mid(pos, 4), KeePass1::BYTEORDER);
899         pos += 4;
900 
901         if (m_groupIds.contains(groupId) && (iconId < static_cast<quint32>(iconUuids.size()))) {
902             m_groupIds[groupId]->setIcon(iconUuids[iconId]);
903         }
904     }
905 
906     return true;
907 }
908 
raiseError(const QString & errorMessage)909 void KeePass1Reader::raiseError(const QString& errorMessage)
910 {
911     m_error = true;
912     m_errorStr = errorMessage;
913 }
914 
dateFromPackedStruct(const QByteArray & data)915 QDateTime KeePass1Reader::dateFromPackedStruct(const QByteArray& data)
916 {
917     Q_ASSERT(data.size() == 5);
918 
919     quint32 dw1 = static_cast<uchar>(data.at(0));
920     quint32 dw2 = static_cast<uchar>(data.at(1));
921     quint32 dw3 = static_cast<uchar>(data.at(2));
922     quint32 dw4 = static_cast<uchar>(data.at(3));
923     quint32 dw5 = static_cast<uchar>(data.at(4));
924 
925     int y = (dw1 << 6) | (dw2 >> 2);
926     int mon = ((dw2 & 0x00000003) << 2) | (dw3 >> 6);
927     int d = (dw3 >> 1) & 0x0000001F;
928     int h = ((dw3 & 0x00000001) << 4) | (dw4 >> 4);
929     int min = ((dw4 & 0x0000000F) << 2) | (dw5 >> 6);
930     int s = dw5 & 0x0000003F;
931 
932     QDateTime dateTime = QDateTime(QDate(y, mon, d), QTime(h, min, s), Qt::UTC);
933 
934     // check for the special "never" datetime
935     if (dateTime == QDateTime(QDate(2999, 12, 28), QTime(23, 59, 59), Qt::UTC)) {
936         return QDateTime();
937     } else {
938         return dateTime;
939     }
940 }
941 
isMetaStream(const Entry * entry)942 bool KeePass1Reader::isMetaStream(const Entry* entry)
943 {
944     return entry->attachments()->keys().contains("bin-stream") && !entry->notes().isEmpty()
945            && entry->title() == "Meta-Info" && entry->username() == "SYSTEM" && entry->url() == "$"
946            && entry->iconNumber() == 0;
947 }
948 
readKeyfile(QIODevice * device)949 QByteArray KeePass1Reader::readKeyfile(QIODevice* device)
950 {
951     if (device->size() == 0) {
952         return QByteArray();
953     }
954 
955     if (device->size() == 32) {
956         QByteArray data = device->read(32);
957         if (data.size() != 32) {
958             return QByteArray();
959         }
960 
961         return data;
962     }
963 
964     if (device->size() == 64) {
965         QByteArray data = device->read(64);
966 
967         if (data.size() != 64) {
968             return QByteArray();
969         }
970 
971         if (Tools::isHex(data)) {
972             return QByteArray::fromHex(data);
973         } else {
974             device->seek(0);
975         }
976     }
977 
978     CryptoHash cryptoHash(CryptoHash::Sha256);
979     QByteArray buffer;
980 
981     do {
982         if (!Tools::readFromDevice(device, buffer)) {
983             return QByteArray();
984         }
985         cryptoHash.addData(buffer);
986     } while (!buffer.isEmpty());
987 
988     return cryptoHash.result();
989 }
990 
rawKey() const991 QByteArray KeePass1Key::rawKey() const
992 {
993     if (m_keyfileData.isEmpty()) {
994         return CryptoHash::hash(m_password, CryptoHash::Sha256);
995     } else if (m_password.isEmpty()) {
996         return m_keyfileData;
997     } else {
998         CryptoHash keyHash(CryptoHash::Sha256);
999         keyHash.addData(CryptoHash::hash(m_password, CryptoHash::Sha256));
1000         keyHash.addData(m_keyfileData);
1001         return keyHash.result();
1002     }
1003 }
1004 
clear()1005 void KeePass1Key::clear()
1006 {
1007     CompositeKey::clear();
1008 
1009     m_password.clear();
1010     m_keyfileData.clear();
1011 }
1012 
setPassword(const QByteArray & password)1013 void KeePass1Key::setPassword(const QByteArray& password)
1014 {
1015     m_password = password;
1016 }
1017 
setKeyfileData(const QByteArray & keyfileData)1018 void KeePass1Key::setKeyfileData(const QByteArray& keyfileData)
1019 {
1020     m_keyfileData = keyfileData;
1021 }
1022