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