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