Parse KeePass 1 database meta streams.

Refs #2
This commit is contained in:
Felix Geyer 2012-05-10 11:44:25 +02:00
parent 79b15e2ac6
commit 65f2790170
5 changed files with 198 additions and 25 deletions

View File

@ -19,11 +19,13 @@
#include <QtCore/QFile>
#include <QtCore/QTextCodec>
#include <QtGui/QImage>
#include "core/Database.h"
#include "core/Endian.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/CryptoHash.h"
#include "format/KeePass1.h"
#include "keys/CompositeKey.h"
@ -170,9 +172,7 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
Q_FOREACH (Entry* entry, entries) {
if (isMetaStream(entry)) {
if (!parseMetaStream(entry)) {
return 0;
}
parseMetaStream(entry);
delete entry;
}
@ -189,12 +189,10 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
group->setUpdateTimeinfo(true);
}
Q_FOREACH (Entry* entry, entries) {
Q_FOREACH (Entry* entry, m_db->rootGroup()->entriesRecursive()) {
entry->setUpdateTimeinfo(true);
}
return db.take();
}
@ -607,7 +605,7 @@ bool KeePass1Reader::constructGroupTree(const QList<Group*> groups)
else {
for (int j = (i - 1); j >= 0; j--) {
if (m_groupLevels.value(groups[j]) < level) {
if ((m_groupLevels.value(groups[j]) - level) != 1) {
if ((level - m_groupLevels.value(groups[j])) != 1) {
return false;
}
@ -625,9 +623,127 @@ bool KeePass1Reader::constructGroupTree(const QList<Group*> groups)
return true;
}
bool KeePass1Reader::parseMetaStream(const Entry* entry)
void KeePass1Reader::parseMetaStream(const Entry* entry)
{
// TODO: implement
QByteArray data = entry->attachments()->value("bin-stream");
if (entry->notes() == "KPX_GROUP_TREE_STATE") {
if (!parseGroupTreeState(data)) {
qWarning("Unable to parse group tree state metastream.");
}
}
else if (entry->notes() == "KPX_CUSTOM_ICONS_4") {
if (!parseCustomIcons4(data)) {
qWarning("Unable to parse custom icons metastream.");
}
}
else {
qWarning("Ignoring unknown metastream \"%s\".", entry->notes().toLocal8Bit().constData());
}
}
bool KeePass1Reader::parseGroupTreeState(const QByteArray& data)
{
if (data.size() < 4) {
return false;
}
int pos = 0;
quint32 num = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
pos += 4;
if ((data.size() - 4) != (num * 5)) {
return false;
}
for (quint32 i = 0; i < num; i++) {
quint32 groupId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
pos += 4;
bool expanded = data.at(pos);
pos += 1;
if (m_groupIds.contains(groupId)) {
m_groupIds[groupId]->setExpanded(expanded);
}
}
return true;
}
bool KeePass1Reader::parseCustomIcons4(const QByteArray& data)
{
if (data.size() < 12) {
return false;
}
int pos = 0;
quint32 numIcons = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
pos += 4;
quint32 numEntries = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
pos += 4;
quint32 numGroups = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
pos += 4;
QList<Uuid> iconUuids;
for (quint32 i = 0; i < numIcons; i++) {
if (data.size() < (pos + 4)) {
return false;
}
quint32 iconSize = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
pos += 4;
if (data.size() < (pos + iconSize)) {
return false;
}
QImage icon = QImage::fromData(data.mid(pos, iconSize));
pos += iconSize;
if (icon.width() != 16 || icon.height() != 16) {
icon = icon.scaled(16, 16);
}
Uuid uuid = Uuid::random();
iconUuids.append(uuid);
m_db->metadata()->addCustomIcon(uuid, icon);
}
if (data.size() < (pos + numEntries * 20)) {
return false;
}
for (quint32 i = 0; i < numEntries; i++) {
QByteArray entryUuid = data.mid(pos, 16);
pos += 16;
int iconId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
pos += 4;
if (m_entryUuids.contains(entryUuid) && (iconId < iconUuids.size())) {
m_entryUuids[entryUuid]->setIcon(iconUuids[iconId]);
}
}
if (data.size() < (pos + numGroups * 8)) {
return false;
}
for (quint32 i = 0; i < numGroups; i++) {
quint32 groupId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
pos += 4;
int iconId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER);
pos += 4;
if (m_groupIds.contains(groupId) && (iconId < iconUuids.size())) {
m_groupIds[groupId]->setIcon(iconUuids[iconId]);
}
}
return true;
}

View File

@ -56,7 +56,9 @@ private:
Group* readGroup(QIODevice* cipherStream);
Entry* readEntry(QIODevice* cipherStream);
bool constructGroupTree(const QList<Group*> groups);
bool parseMetaStream(const Entry* entry);
void parseMetaStream(const Entry* entry);
bool parseGroupTreeState(const QByteArray& data);
bool parseCustomIcons4(const QByteArray& data);
void raiseError(const QString& str);
static QDateTime dateFromPackedStruct(const QByteArray& data);
static bool isMetaStream(const Entry* entry);

View File

@ -24,29 +24,31 @@
#include "core/Database.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/Crypto.h"
#include "format/KeePass1Reader.h"
void TestKeePass1Reader::initTestCase()
{
Crypto::init();
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/basic.kdb");
KeePass1Reader reader;
m_db = reader.readDatabase(filename, "masterpw", QByteArray());
QVERIFY(m_db);
QVERIFY(!reader.hasError());
}
void TestKeePass1Reader::testBasic()
{
QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/basic.kdb");
QCOMPARE(m_db->rootGroup()->children().size(), 2);
KeePass1Reader reader;
Database* db = reader.readDatabase(filename, "masterpw", QByteArray());
QVERIFY(db);
QVERIFY(!reader.hasError());
QCOMPARE(db->rootGroup()->children().size(), 2);
Group* group1 = db->rootGroup()->children().at(0);
Group* group1 = m_db->rootGroup()->children().at(0);
QCOMPARE(group1->name(), QString("Internet"));
QCOMPARE(group1->iconNumber(), 1);
QCOMPARE(group1->children().size(), 2);
QCOMPARE(group1->entries().size(), 2);
QCOMPARE(group1->iconNumber(), 1);
Entry* entry11 = group1->entries().at(0);
QCOMPARE(entry11->title(), QString("Test entry"));
@ -71,12 +73,58 @@ void TestKeePass1Reader::testBasic()
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);
Group* group11 = group1->children().at(0);
QCOMPARE(group11->name(), QString("Subgroup 1"));
QCOMPARE(group11->children().size(), 1);
delete db;
Group* group111 = group11->children().at(0);
QCOMPARE(group111->name(), QString("Unexpanded"));
QCOMPARE(group111->children().size(), 1);
Group* group1111 = group111->children().at(0);
QCOMPARE(group1111->name(), QString("abc"));
QCOMPARE(group1111->children().size(), 0);
Group* group12 = group1->children().at(1);
QCOMPARE(group12->name(), QString("Subgroup 2"));
QCOMPARE(group12->children().size(), 0);
Group* group2 = m_db->rootGroup()->children().at(1);
QCOMPARE(group2->name(), QString("eMail"));
QCOMPARE(group2->entries().size(), 1);
QCOMPARE(group2->iconNumber(), 19);
}
void TestKeePass1Reader::testCustomIcons()
{
QCOMPARE(m_db->metadata()->customIcons().size(), 1);
Entry* entry = m_db->rootGroup()->children().at(1)->entries().first();
QCOMPARE(entry->icon().width(), 16);
QCOMPARE(entry->icon().height(), 16);
for (int x = 0; x < 16; x++) {
for (int y = 0; y < 16; y++) {
QRgb rgb = entry->icon().pixel(x, y);
QCOMPARE(qRed(rgb), 8);
QCOMPARE(qGreen(rgb), 160);
QCOMPARE(qBlue(rgb), 60);
}
}
}
void TestKeePass1Reader::testGroupExpanded()
{
QCOMPARE(m_db->rootGroup()->children().at(0)->isExpanded(), true);
QCOMPARE(m_db->rootGroup()->children().at(0)->children().at(0)->isExpanded(), true);
QCOMPARE(m_db->rootGroup()->children().at(0)->children().at(0)->children().at(0)->isExpanded(),
false);
}
void TestKeePass1Reader::cleanupTestCase()
{
delete m_db;
}
QDateTime TestKeePass1Reader::genDT(int year, int month, int day, int hour, int min)

View File

@ -21,6 +21,8 @@
#include <QtCore/QDateTime>
#include <QtCore/QObject>
class Database;
class TestKeePass1Reader : public QObject
{
Q_OBJECT
@ -28,9 +30,14 @@ class TestKeePass1Reader : public QObject
private Q_SLOTS:
void initTestCase();
void testBasic();
void testCustomIcons();
void testGroupExpanded();
void cleanupTestCase();
private:
static QDateTime genDT(int year, int month, int day, int hour, int min);
Database* m_db;
};
#endif // KEEPASSX_TESTKEEPASS1READER_H

Binary file not shown.