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