Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/format/KeePass1Reader.cpp392
-rw-r--r--src/format/KeePass1Reader.h14
-rw-r--r--tests/TestKeePass1Reader.cpp44
-rw-r--r--tests/TestKeePass1Reader.h4
-rw-r--r--tests/TestKeePass2XmlReader.h2
-rw-r--r--tests/data/basic.kdbbin876 -> 1100 bytes
6 files changed, 454 insertions, 2 deletions
diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp
index d85f16127..5b4725bad 100644
--- a/src/format/KeePass1Reader.cpp
+++ b/src/format/KeePass1Reader.cpp
@@ -22,6 +22,8 @@
#include "core/Database.h"
#include "core/Endian.h"
+#include "core/Entry.h"
+#include "core/Group.h"
#include "crypto/CryptoHash.h"
#include "format/KeePass1.h"
#include "keys/CompositeKey.h"
@@ -50,7 +52,9 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
const QByteArray& keyfileData)
{
QScopedPointer<Database> db(new Database());
+ QScopedPointer<Group> tmpParent(new Group());
m_db = db.data();
+ m_tmpParent = tmpParent.data();
m_device = device;
m_error = false;
m_errorStr = QString();
@@ -142,6 +146,55 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
return 0;
}
+ QList<Group*> groups;
+ for (quint32 i = 0; i < numGroups; i++) {
+ Group* group = readGroup(cipherStream.data());
+ if (!group) {
+ return 0;
+ }
+ groups.append(group);
+ }
+
+ QList<Entry*> entries;
+ for (quint32 i = 0; i < numEntries; i++) {
+ Entry* entry = readEntry(cipherStream.data());
+ if (!entry) {
+ return 0;
+ }
+ entries.append(entry);
+ }
+
+ if (!constructGroupTree(groups)) {
+ return 0;
+ }
+
+ Q_FOREACH (Entry* entry, entries) {
+ if (isMetaStream(entry)) {
+ if (!parseMetaStream(entry)) {
+ return 0;
+ }
+
+ delete entry;
+ }
+ }
+
+ Q_ASSERT(m_tmpParent->children().isEmpty());
+
+ Q_FOREACH (Entry* entry, m_tmpParent->entries()) {
+ qWarning("Orphaned entry found, assigning to root group.");
+ entry->setGroup(m_db->rootGroup());
+ }
+
+ Q_FOREACH (Group* group, groups) {
+ group->setUpdateTimeinfo(true);
+ }
+
+ Q_FOREACH (Entry* entry, entries) {
+ entry->setUpdateTimeinfo(true);
+ }
+
+
+
return db.take();
}
@@ -271,7 +324,6 @@ bool KeePass1Reader::verifyKey(SymmetricCipherStream* cipherStream)
if (readResult != buffer.size()) {
buffer.resize(readResult);
}
- qDebug("read %d", buffer.size());
contentHash.addData(buffer);
}
} while (readResult == buffer.size());
@@ -279,12 +331,350 @@ bool KeePass1Reader::verifyKey(SymmetricCipherStream* cipherStream)
return contentHash.result() == m_contentHashHeader;
}
+Group* KeePass1Reader::readGroup(QIODevice* cipherStream)
+{
+ QScopedPointer<Group> group(new Group());
+ group->setUpdateTimeinfo(false);
+ group->setParent(m_tmpParent);
+
+ TimeInfo timeInfo;
+ // TODO: make sure these are initalized
+ quint32 groupId;
+ quint32 groupLevel;
+ bool ok;
+ bool reachedEnd = false;
+
+ do {
+ quint16 fieldType = Endian::readUInt16(cipherStream, KeePass1::BYTEORDER, &ok);
+ if (!ok) {
+ return 0;
+ }
+
+ int fieldSize = static_cast<int>(Endian::readUInt32(cipherStream, KeePass1::BYTEORDER, &ok));
+ if (!ok) {
+ return 0;
+ }
+
+ QByteArray fieldData = cipherStream->read(fieldSize);
+ if (fieldData.size() != fieldSize) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+
+ switch (fieldType) {
+ case 0x0000:
+ // ignore field
+ break;
+ case 0x0001:
+ if (fieldSize != 4) {
+ return 0;
+ }
+ groupId = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
+ break;
+ case 0x0002:
+ group->setName(QString::fromUtf8(fieldData.constData()));
+ break;
+ case 0x0003:
+ {
+ if (fieldSize != 5) {
+ return 0;
+ }
+ QDateTime dateTime = dateFromPackedStruct(fieldData);
+ if (dateTime.isValid()) {
+ timeInfo.setCreationTime(dateTime);
+ }
+ break;
+ }
+ case 0x0004:
+ {
+ if (fieldSize != 5) {
+ return 0;
+ }
+ QDateTime dateTime = dateFromPackedStruct(fieldData);
+ if (dateTime.isValid()) {
+ timeInfo.setLastModificationTime(dateTime);
+ }
+ break;
+ }
+ case 0x0005:
+ {
+ if (fieldSize != 5) {
+ return 0;
+ }
+ QDateTime dateTime = dateFromPackedStruct(fieldData);
+ if (dateTime.isValid()) {
+ timeInfo.setLastAccessTime(dateTime);
+ }
+ break;
+ }
+ case 0x0006:
+ {
+ if (fieldSize != 5) {
+ return 0;
+ }
+ QDateTime dateTime = dateFromPackedStruct(fieldData);
+ if (dateTime.isValid()) {
+ timeInfo.setExpires(true);
+ timeInfo.setExpiryTime(dateTime);
+ }
+ break;
+ }
+ case 0x0007:
+ {
+ if (fieldSize != 4) {
+ return 0;
+ }
+ quint32 iconNumber = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
+ group->setIcon(iconNumber);
+ break;
+ }
+ case 0x0008:
+ {
+ if (fieldSize != 2) {
+ return 0;
+ }
+ groupLevel = Endian::bytesToUInt16(fieldData, KeePass1::BYTEORDER);
+ break;
+ }
+ case 0x0009:
+ // flags, ignore field
+ break;
+ case 0xFFFF:
+ reachedEnd = true;
+ break;
+ default:
+ // invalid field
+ return 0;
+ }
+ } while (!reachedEnd);
+
+ group->setUuid(Uuid::random());
+ group->setTimeInfo(timeInfo);
+ m_groupIds.insert(groupId, group.data());
+ m_groupLevels.insert(group.data(), groupLevel);
+
+ return group.take();
+}
+
+Entry* KeePass1Reader::readEntry(QIODevice* cipherStream)
+{
+ QScopedPointer<Entry> entry(new Entry());
+ entry->setUpdateTimeinfo(false);
+ entry->setGroup(m_tmpParent);
+
+ TimeInfo timeInfo;
+ QString binaryName;
+ bool ok;
+ bool reachedEnd = false;
+
+ do {
+ quint16 fieldType = Endian::readUInt16(cipherStream, KeePass1::BYTEORDER, &ok);
+ if (!ok) {
+ return 0;
+ }
+
+ int fieldSize = static_cast<int>(Endian::readUInt32(cipherStream, KeePass1::BYTEORDER, &ok));
+ if (!ok) {
+ return 0;
+ }
+
+ QByteArray fieldData = cipherStream->read(fieldSize);
+ if (fieldData.size() != fieldSize) {
+ // TODO error
+ Q_ASSERT(false);
+ return 0;
+ }
+
+ switch (fieldType) {
+ case 0x0000:
+ // ignore field
+ break;
+ case 0x0001:
+ if (fieldSize != 16) {
+ return 0;
+ }
+ m_entryUuids.insert(fieldData, entry.data());
+ break;
+ case 0x0002:
+ {
+ if (fieldSize != 4) {
+ return 0;
+ }
+ quint32 groupId = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
+ entry->setGroup(m_groupIds.value(groupId));
+ break;
+ }
+ case 0x0003:
+ {
+ if (fieldSize != 4) {
+ return 0;
+ }
+ quint32 iconNumber = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER);
+ entry->setIcon(iconNumber);
+ break;
+ }
+ case 0x0004:
+ entry->setTitle(QString::fromUtf8(fieldData.constData()));
+ break;
+ case 0x0005:
+ entry->setUrl(QString::fromUtf8(fieldData.constData()));
+ break;
+ case 0x0006:
+ entry->setUsername(QString::fromUtf8(fieldData.constData()));
+ break;
+ case 0x0007:
+ entry->setPassword(QString::fromUtf8(fieldData.constData()));
+ break;
+ case 0x0008:
+ entry->setNotes(QString::fromUtf8(fieldData.constData()));
+ break;
+ case 0x0009:
+ {
+ if (fieldSize != 5) {
+ return 0;
+ }
+ QDateTime dateTime = dateFromPackedStruct(fieldData);
+ if (dateTime.isValid()) {
+ timeInfo.setCreationTime(dateTime);
+ }
+ break;
+ }
+ case 0x000A:
+ {
+ if (fieldSize != 5) {
+ return 0;
+ }
+ QDateTime dateTime = dateFromPackedStruct(fieldData);
+ if (dateTime.isValid()) {
+ timeInfo.setLastModificationTime(dateTime);
+ }
+ break;
+ }
+ case 0x000B:
+ {
+ if (fieldSize != 5) {
+ return 0;
+ }
+ QDateTime dateTime = dateFromPackedStruct(fieldData);
+ if (dateTime.isValid()) {
+ timeInfo.setLastAccessTime(dateTime);
+ }
+ break;
+ }
+ case 0x000C:
+ {
+ if (fieldSize != 5) {
+ return 0;
+ }
+ QDateTime dateTime = dateFromPackedStruct(fieldData);
+ if (dateTime.isValid()) {
+ timeInfo.setExpires(true);
+ timeInfo.setExpiryTime(dateTime);
+ }
+ break;
+ }
+ case 0x000D:
+ binaryName = QString::fromUtf8(fieldData.constData());
+ break;
+ case 0x000E:
+ if (fieldSize != 0) {
+ entry->attachments()->set(binaryName, fieldData);
+ }
+ break;
+ case 0xFFFF:
+ reachedEnd = true;
+ break;
+ default:
+ // invalid field
+ return 0;
+ }
+ } while (!reachedEnd);
+
+ entry->setTimeInfo(timeInfo);
+
+ return entry.take();
+}
+
+bool KeePass1Reader::constructGroupTree(const QList<Group*> groups)
+{
+ for (int i = 0; i < groups.size(); i++) {
+ quint32 level = m_groupLevels.value(groups[i]);
+
+ if (level == 0) {
+ groups[i]->setParent(m_db->rootGroup());
+ }
+ else {
+ for (int j = (i - 1); j >= 0; j--) {
+ if (m_groupLevels.value(groups[j]) < level) {
+ if ((m_groupLevels.value(groups[j]) - level) != 1) {
+ return false;
+ }
+
+ groups[i]->setParent(groups[j]);
+ break;
+ }
+ }
+ }
+
+ if (groups[i]->parent() == m_tmpParent) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool KeePass1Reader::parseMetaStream(const Entry* entry)
+{
+ // TODO: implement
+ return true;
+}
+
void KeePass1Reader::raiseError(const QString& str)
{
m_error = true;
m_errorStr = str;
}
+QDateTime KeePass1Reader::dateFromPackedStruct(const QByteArray& data)
+{
+ Q_ASSERT(data.size() == 5);
+
+ quint32 dw1 = static_cast<uchar>(data.at(0));
+ quint32 dw2 = static_cast<uchar>(data.at(1));
+ quint32 dw3 = static_cast<uchar>(data.at(2));
+ quint32 dw4 = static_cast<uchar>(data.at(3));
+ quint32 dw5 = static_cast<uchar>(data.at(4));
+
+ int y = (dw1 << 6) | (dw2 >> 2);
+ int mon = ((dw2 & 0x00000003) << 2) | (dw3 >> 6);
+ int d = (dw3 >> 1) & 0x0000001F;
+ int h = ((dw3 & 0x00000001) << 4) | (dw4 >> 4);
+ int min = ((dw4 & 0x0000000F) << 2) | (dw5 >> 6);
+ int s = dw5 & 0x0000003F;
+
+ QDateTime dateTime = QDateTime(QDate(y, mon, d), QTime(h, min, s), Qt::UTC);
+
+ // check for the special "never" datetime
+ if (dateTime == QDateTime(QDate(2999, 12, 28), QTime(23, 59, 59), Qt::UTC)) {
+ return QDateTime();
+ }
+ else {
+ return dateTime;
+ }
+}
+
+bool KeePass1Reader::isMetaStream(const Entry* entry)
+{
+ return entry->attachments()->keys().contains("bin-stream")
+ && !entry->notes().isEmpty()
+ && entry->title() == "Meta-Info"
+ && entry->username() == "SYSTEM"
+ && entry->url() == "$"
+ && entry->iconNumber() == 0;
+}
+
QByteArray KeePass1Key::rawKey() const
{
diff --git a/src/format/KeePass1Reader.h b/src/format/KeePass1Reader.h
index e3d5eafe1..f0459438b 100644
--- a/src/format/KeePass1Reader.h
+++ b/src/format/KeePass1Reader.h
@@ -19,8 +19,12 @@
#define KEEPASSX_KEEPASS1READER_H
#include <QtCore/QCoreApplication>
+#include <QtCore/QDateTime>
+#include <QtCore/QHash>
class Database;
+class Entry;
+class Group;
class SymmetricCipherStream;
class QIODevice;
@@ -49,9 +53,16 @@ private:
qint64 contentPos);
QByteArray key(const QByteArray& password, const QByteArray& keyfileData);
bool verifyKey(SymmetricCipherStream* cipherStream);
+ Group* readGroup(QIODevice* cipherStream);
+ Entry* readEntry(QIODevice* cipherStream);
+ bool constructGroupTree(const QList<Group*> groups);
+ bool parseMetaStream(const Entry* entry);
void raiseError(const QString& str);
+ static QDateTime dateFromPackedStruct(const QByteArray& data);
+ static bool isMetaStream(const Entry* entry);
Database* m_db;
+ Group* m_tmpParent;
QIODevice* m_device;
quint32 m_encryptionFlags;
QByteArray m_masterSeed;
@@ -59,6 +70,9 @@ private:
QByteArray m_contentHashHeader;
QByteArray m_transformSeed;
quint32 m_transformRounds;
+ QHash<quint32, Group*> m_groupIds;
+ QHash<Group*, quint32> m_groupLevels;
+ QHash<QByteArray, Entry*> m_entryUuids;
bool m_error;
QString m_errorStr;
diff --git a/tests/TestKeePass1Reader.cpp b/tests/TestKeePass1Reader.cpp
index 1dc31ee2a..54f7ac1f3 100644
--- a/tests/TestKeePass1Reader.cpp
+++ b/tests/TestKeePass1Reader.cpp
@@ -22,6 +22,8 @@
#include "config-keepassx-tests.h"
#include "tests.h"
#include "core/Database.h"
+#include "core/Entry.h"
+#include "core/Group.h"
#include "crypto/Crypto.h"
#include "format/KeePass1Reader.h"
@@ -39,7 +41,49 @@ void TestKeePass1Reader::testBasic()
QVERIFY(db);
QVERIFY(!reader.hasError());
+ QCOMPARE(db->rootGroup()->children().size(), 2);
+
+ Group* group1 = db->rootGroup()->children().at(0);
+ QCOMPARE(group1->name(), QString("Internet"));
+ QCOMPARE(group1->iconNumber(), 1);
+ QCOMPARE(group1->entries().size(), 2);
+
+ Entry* entry11 = group1->entries().at(0);
+ QCOMPARE(entry11->title(), QString("Test entry"));
+ QCOMPARE(entry11->iconNumber(), 1);
+ QCOMPARE(entry11->username(), QString("I"));
+ QCOMPARE(entry11->url(), QString("http://example.com/"));
+ QCOMPARE(entry11->password(), QString("secretpassword"));
+ QCOMPARE(entry11->notes(), QString("Lorem ipsum\ndolor sit amet"));
+ QVERIFY(entry11->timeInfo().expires());
+ QCOMPARE(entry11->timeInfo().expiryTime(), genDT(2012, 5, 9, 10, 32));
+ QCOMPARE(entry11->attachments()->keys().size(), 1);
+ QCOMPARE(entry11->attachments()->keys().first(), QString("attachment.txt"));
+ QCOMPARE(entry11->attachments()->value("attachment.txt"), QByteArray("hello world\n"));
+
+ Entry* entry12 = group1->entries().at(1);
+ QCOMPARE(entry12->title(), QString(""));
+ QCOMPARE(entry12->iconNumber(), 0);
+ QCOMPARE(entry12->username(), QString(""));
+ QCOMPARE(entry12->url(), QString(""));
+ QCOMPARE(entry12->password(), QString(""));
+ QCOMPARE(entry12->notes(), QString(""));
+ QVERIFY(!entry12->timeInfo().expires());
+ QCOMPARE(entry12->attachments()->keys().size(), 0);
+
+ Group* group2 = db->rootGroup()->children().at(1);
+ QCOMPARE(group2->name(), QString("eMail"));
+ QCOMPARE(group2->iconNumber(), 19);
+ QCOMPARE(group2->entries().size(), 0);
+
delete db;
}
+QDateTime TestKeePass1Reader::genDT(int year, int month, int day, int hour, int min)
+{
+ QDate date(year, month, day);
+ QTime time(hour, min, 0);
+ return QDateTime(date, time, Qt::UTC);
+}
+
KEEPASSX_QTEST_CORE_MAIN(TestKeePass1Reader)
diff --git a/tests/TestKeePass1Reader.h b/tests/TestKeePass1Reader.h
index 390174c4a..55f4a9fd3 100644
--- a/tests/TestKeePass1Reader.h
+++ b/tests/TestKeePass1Reader.h
@@ -18,6 +18,7 @@
#ifndef KEEPASSX_TESTKEEPASS1READER_H
#define KEEPASSX_TESTKEEPASS1READER_H
+#include <QtCore/QDateTime>
#include <QtCore/QObject>
class TestKeePass1Reader : public QObject
@@ -27,6 +28,9 @@ class TestKeePass1Reader : public QObject
private Q_SLOTS:
void initTestCase();
void testBasic();
+
+private:
+ static QDateTime genDT(int year, int month, int day, int hour, int min);
};
#endif // KEEPASSX_TESTKEEPASS1READER_H
diff --git a/tests/TestKeePass2XmlReader.h b/tests/TestKeePass2XmlReader.h
index 415d734d1..2ed25fadb 100644
--- a/tests/TestKeePass2XmlReader.h
+++ b/tests/TestKeePass2XmlReader.h
@@ -42,7 +42,7 @@ private Q_SLOTS:
void cleanupTestCase();
private:
- QDateTime genDT(int year, int month, int day, int hour, int min, int second);
+ static QDateTime genDT(int year, int month, int day, int hour, int min, int second);
Database* m_db;
};
diff --git a/tests/data/basic.kdb b/tests/data/basic.kdb
index 251c3edf0..76402e419 100644
--- a/tests/data/basic.kdb
+++ b/tests/data/basic.kdb
Binary files differ