diff options
author | Felix Geyer <debfx@fobos.de> | 2012-04-21 18:45:46 +0400 |
---|---|---|
committer | Felix Geyer <debfx@fobos.de> | 2012-04-21 18:45:46 +0400 |
commit | 8acd6f74d8c4ef396787c2e7b1f4ef91b5670a1e (patch) | |
tree | 410948b4ae7b1181b6a56633c6a15296bcfee075 | |
parent | e8ac70120b113b28cd42ac7a5e72663c912fbe64 (diff) |
Support KeePass format 3.00 (used by KeePass2 >= 2.15).
Closes #6
Attachments are now stored in a pool under Metadata instead of in entries.
The protected flag of attachments isn't supported anymore.
New metadata attributes: color, historyMaxItems and historyMaxSize.
Dropped metadata attribute: autoEnableVisualHiding.
-rw-r--r-- | src/core/EntryAttachments.cpp | 26 | ||||
-rw-r--r-- | src/core/EntryAttachments.h | 4 | ||||
-rw-r--r-- | src/core/Group.cpp | 19 | ||||
-rw-r--r-- | src/core/Group.h | 1 | ||||
-rw-r--r-- | src/core/Metadata.cpp | 42 | ||||
-rw-r--r-- | src/core/Metadata.h | 16 | ||||
-rw-r--r-- | src/format/KeePass2.h | 3 | ||||
-rw-r--r-- | src/format/KeePass2Reader.cpp | 5 | ||||
-rw-r--r-- | src/format/KeePass2XmlReader.cpp | 127 | ||||
-rw-r--r-- | src/format/KeePass2XmlReader.h | 6 | ||||
-rw-r--r-- | src/format/KeePass2XmlWriter.cpp | 91 | ||||
-rw-r--r-- | src/format/KeePass2XmlWriter.h | 4 | ||||
-rw-r--r-- | tests/TestKeePass2Reader.cpp | 34 | ||||
-rw-r--r-- | tests/TestKeePass2Reader.h | 1 | ||||
-rw-r--r-- | tests/TestKeePass2Writer.cpp | 10 | ||||
-rw-r--r-- | tests/TestKeePass2Writer.h | 1 | ||||
-rw-r--r-- | tests/TestKeePass2XmlReader.cpp | 25 | ||||
-rw-r--r-- | tests/data/Compressed.kdbx | bin | 1870 -> 1918 bytes | |||
-rw-r--r-- | tests/data/Format200.kdbx | bin | 0 -> 2302 bytes | |||
-rw-r--r-- | tests/data/NewDatabase.xml | 41 | ||||
-rw-r--r-- | tests/data/NonAscii.kdbx | bin | 2574 -> 2798 bytes | |||
-rw-r--r-- | tests/data/NonAscii.kdbx.key | 1 | ||||
-rw-r--r-- | tests/data/ProtectedStrings.kdbx | bin | 1950 -> 1934 bytes |
23 files changed, 376 insertions, 81 deletions
diff --git a/src/core/EntryAttachments.cpp b/src/core/EntryAttachments.cpp index 2843f9318..820d24a67 100644 --- a/src/core/EntryAttachments.cpp +++ b/src/core/EntryAttachments.cpp @@ -32,12 +32,7 @@ QByteArray EntryAttachments::value(const QString& key) const return m_attachments.value(key); } -bool EntryAttachments::isProtected(const QString& key) const -{ - return m_protectedAttachments.contains(key); -} - -void EntryAttachments::set(const QString& key, const QByteArray& value, bool protect) +void EntryAttachments::set(const QString& key, const QByteArray& value) { bool emitModified = false; bool addAttachment = !m_attachments.contains(key); @@ -51,17 +46,6 @@ void EntryAttachments::set(const QString& key, const QByteArray& value, bool pro emitModified = true; } - if (protect != m_protectedAttachments.contains(key)) { - if (protect) { - m_protectedAttachments.insert(key); - } - else { - m_protectedAttachments.remove(key); - } - - emitModified = true; - } - if (addAttachment) { Q_EMIT added(key); } @@ -84,7 +68,6 @@ void EntryAttachments::remove(const QString& key) Q_EMIT aboutToBeRemoved(key); m_attachments.remove(key); - m_protectedAttachments.remove(key); Q_EMIT removed(key); Q_EMIT modified(); @@ -96,13 +79,9 @@ void EntryAttachments::copyFrom(const EntryAttachments* other) Q_EMIT aboutToBeReset(); m_attachments.clear(); - m_protectedAttachments.clear(); Q_FOREACH (const QString& key, other->keys()) { m_attachments.insert(key, other->value(key)); - if (other->isProtected(key)) { - m_protectedAttachments.insert(key); - } } Q_EMIT reset(); @@ -119,7 +98,6 @@ void EntryAttachments::clear() Q_EMIT aboutToBeReset(); m_attachments.clear(); - m_protectedAttachments.clear(); Q_EMIT reset(); Q_EMIT modified(); @@ -127,5 +105,5 @@ void EntryAttachments::clear() bool EntryAttachments::operator!=(const EntryAttachments& other) const { - return m_attachments != other.m_attachments || m_protectedAttachments != other.m_protectedAttachments; + return m_attachments != other.m_attachments; } diff --git a/src/core/EntryAttachments.h b/src/core/EntryAttachments.h index 915c3b751..be1876f0c 100644 --- a/src/core/EntryAttachments.h +++ b/src/core/EntryAttachments.h @@ -29,8 +29,7 @@ public: explicit EntryAttachments(QObject* parent = 0); QList<QString> keys() const; QByteArray value(const QString& key) const; - bool isProtected(const QString& key) const; - void set(const QString& key, const QByteArray& value, bool protect = false); + void set(const QString& key, const QByteArray& value); void remove(const QString& key); void copyFrom(const EntryAttachments* other); void clear(); @@ -48,7 +47,6 @@ Q_SIGNALS: private: QMap<QString, QByteArray> m_attachments; - QSet<QString> m_protectedAttachments; }; #endif // KEEPASSX_ENTRYATTACHMENTS_H diff --git a/src/core/Group.cpp b/src/core/Group.cpp index b37821bfd..8e5fe113c 100644 --- a/src/core/Group.cpp +++ b/src/core/Group.cpp @@ -305,6 +305,25 @@ const QList<Entry*>& Group::entries() const return m_entries; } +QList<Entry*> Group::entriesRecursive(bool includeHistoryItems) +{ + QList<Entry*> entryList; + + entryList.append(m_entries); + + if (includeHistoryItems) { + Q_FOREACH (Entry* entry, m_entries) { + entryList.append(entry->historyItems()); + } + } + + Q_FOREACH (Group* group, m_children) { + entryList.append(group->entriesRecursive(includeHistoryItems)); + } + + return entryList; +} + void Group::addEntry(Entry *entry) { Q_ASSERT(entry); diff --git a/src/core/Group.h b/src/core/Group.h index 6fc657318..3cbd2e573 100644 --- a/src/core/Group.h +++ b/src/core/Group.h @@ -74,6 +74,7 @@ public: const QList<Group*>& children() const; QList<Entry*> entries(); const QList<Entry*>& entries() const; + QList<Entry*> entriesRecursive(bool includeHistoryItems = false); Q_SIGNALS: void dataChanged(Group* group); diff --git a/src/core/Metadata.cpp b/src/core/Metadata.cpp index e549e0d3f..e0f5dcfda 100644 --- a/src/core/Metadata.cpp +++ b/src/core/Metadata.cpp @@ -34,6 +34,8 @@ Metadata::Metadata(Database* parent) m_recycleBinEnabled = true; m_masterKeyChangeRec = -1; m_masterKeyChangeForce = -1; + m_historyMaxItems = 10; + m_historyMaxSize = 6291456; QDateTime now = QDateTime::currentDateTimeUtc(); m_nameChanged = now; @@ -48,7 +50,7 @@ Metadata::Metadata(Database* parent) m_protectPassword = true; m_protectUrl = false; m_protectNotes = false; - m_autoEnableVisualHiding = false; + // m_autoEnableVisualHiding = false; m_updateDatetime = true; } @@ -122,6 +124,11 @@ int Metadata::maintenanceHistoryDays() const return m_maintenanceHistoryDays; } +QColor Metadata::color() const +{ + return m_color; +} + bool Metadata::protectTitle() const { return m_protectTitle; @@ -147,10 +154,10 @@ bool Metadata::protectNotes() const return m_protectNotes; } -bool Metadata::autoEnableVisualHiding() const +/*bool Metadata::autoEnableVisualHiding() const { return m_autoEnableVisualHiding; -} +}*/ QImage Metadata::customIcon(const Uuid& uuid) const { @@ -217,6 +224,16 @@ int Metadata::masterKeyChangeForce() const return m_masterKeyChangeForce; } +int Metadata::historyMaxItems() const +{ + return m_historyMaxItems; +} + +int Metadata::historyMaxSize() const +{ + return m_historyMaxSize; +} + QHash<QString, QString> Metadata::customFields() const { return m_customFields; @@ -264,6 +281,11 @@ void Metadata::setMaintenanceHistoryDays(int value) set(m_maintenanceHistoryDays, value); } +void Metadata::setColor(const QColor& value) +{ + set(m_color, value); +} + void Metadata::setProtectTitle(bool value) { set(m_protectTitle, value); @@ -289,10 +311,10 @@ void Metadata::setProtectNotes(bool value) set(m_protectNotes, value); } -void Metadata::setAutoEnableVisualHiding(bool value) +/*void Metadata::setAutoEnableVisualHiding(bool value) { set(m_autoEnableVisualHiding, value); -} +}*/ void Metadata::addCustomIcon(const Uuid& uuid, const QImage& icon) { @@ -362,6 +384,16 @@ void Metadata::setMasterKeyChangeForce(int value) set(m_masterKeyChangeForce, value); } +void Metadata::setHistoryMaxItems(int value) +{ + set(m_historyMaxItems, value); +} + +void Metadata::setHistoryMaxSize(int value) +{ + set(m_historyMaxSize, value); +} + void Metadata::addCustomField(const QString& key, const QString& value) { Q_ASSERT(!m_customFields.contains(key)); diff --git a/src/core/Metadata.h b/src/core/Metadata.h index b2fe3d957..60f8d2d0a 100644 --- a/src/core/Metadata.h +++ b/src/core/Metadata.h @@ -20,6 +20,7 @@ #include <QtCore/QDateTime> #include <QtCore/QHash> +#include <QtGui/QColor> #include <QtGui/QImage> #include "core/Uuid.h" @@ -42,12 +43,13 @@ public: QString defaultUserName() const; QDateTime defaultUserNameChanged() const; int maintenanceHistoryDays() const; + QColor color() const; bool protectTitle() const; bool protectUsername() const; bool protectPassword() const; bool protectUrl() const; bool protectNotes() const; - bool autoEnableVisualHiding() const; + // bool autoEnableVisualHiding() const; QImage customIcon(const Uuid& uuid) const; QHash<Uuid, QImage> customIcons() const; bool recycleBinEnabled() const; @@ -61,6 +63,8 @@ public: QDateTime masterKeyChanged() const; int masterKeyChangeRec() const; int masterKeyChangeForce() const; + int historyMaxItems() const; + int historyMaxSize() const; QHash<QString, QString> customFields() const; void setGenerator(const QString& value); @@ -71,12 +75,13 @@ public: void setDefaultUserName(const QString& value); void setDefaultUserNameChanged(const QDateTime& value); void setMaintenanceHistoryDays(int value); + void setColor(const QColor& value); void setProtectTitle(bool value); void setProtectUsername(bool value); void setProtectPassword(bool value); void setProtectUrl(bool value); void setProtectNotes(bool value); - void setAutoEnableVisualHiding(bool value); + // void setAutoEnableVisualHiding(bool value); void addCustomIcon(const Uuid& uuid, const QImage& icon); void removeCustomIcon(const Uuid& uuid); void setRecycleBinEnabled(bool value); @@ -89,6 +94,8 @@ public: void setMasterKeyChanged(const QDateTime& value); void setMasterKeyChangeRec(int value); void setMasterKeyChangeForce(int value); + void setHistoryMaxItems(int value); + void setHistoryMaxSize(int value); void addCustomField(const QString& key, const QString& value); void removeCustomField(const QString& key); void setUpdateDatetime(bool value); @@ -111,13 +118,14 @@ private: QString m_defaultUserName; QDateTime m_defaultUserNameChanged; int m_maintenanceHistoryDays; + QColor m_color; bool m_protectTitle; bool m_protectUsername; bool m_protectPassword; bool m_protectUrl; bool m_protectNotes; - bool m_autoEnableVisualHiding; + // bool m_autoEnableVisualHiding; QHash<Uuid, QImage> m_customIcons; @@ -132,6 +140,8 @@ private: QDateTime m_masterKeyChanged; int m_masterKeyChangeRec; int m_masterKeyChangeForce; + int m_historyMaxItems; + int m_historyMaxSize; QHash<QString, QString> m_customFields; diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index 94bb1378b..edb8ec5c9 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -26,7 +26,8 @@ namespace KeePass2 { const quint32 SIGNATURE_1 = 0x9AA2D903; const quint32 SIGNATURE_2 = 0xB54BFB67; - const quint32 FILE_VERSION = 0x00020000; + const quint32 FILE_VERSION = 0x00030000; + const quint32 FILE_VERSION_MIN = 0x00020000; const quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000; const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian; diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 7fd569935..a4e58a681 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -60,9 +60,8 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke } quint32 version = Endian::readUInt32(m_device, KeePass2::BYTEORDER, &ok) & KeePass2::FILE_VERSION_CRITICAL_MASK; - quint32 expectedVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; - // TODO do we support old Kdbx versions? - if (!ok || (version != expectedVersion)) { + quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; + if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) { raiseError(tr("Unsupported KeePass database version.")); return 0; } diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/KeePass2XmlReader.cpp index d61772825..95ca47796 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/KeePass2XmlReader.cpp @@ -17,6 +17,7 @@ #include "KeePass2XmlReader.h" +#include <QtCore/QBuffer> #include <QtCore/QFile> #include "core/Database.h" @@ -24,6 +25,7 @@ #include "core/Group.h" #include "core/Metadata.h" #include "format/KeePass2RandomStream.h" +#include "streams/QtIOCompressor" KeePass2XmlReader::KeePass2XmlReader() : m_randomStream(0) @@ -63,6 +65,27 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra } } + QSet<QString> poolKeys = m_binaryPool.keys().toSet(); + QSet<QString> entryKeys = m_binaryMap.keys().toSet(); + QSet<QString> unmappedKeys = entryKeys - poolKeys; + QSet<QString> unusedKeys = poolKeys - entryKeys; + + if (!unmappedKeys.isEmpty()) { + raiseError(17); + } + + if (!m_xml.error()) { + Q_FOREACH (const QString& key, unusedKeys) { + qWarning("KeePass2XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); + } + } + + QHash<QString, QPair<Entry*, QString> >::const_iterator i; + for (i = m_binaryMap.constBegin(); i != m_binaryMap.constEnd(); ++i) { + QPair<Entry*, QString> target = i.value(); + target.first->attachments()->set(target.second, m_binaryPool[i.key()]); + } + m_meta->setUpdateDatetime(true); Q_FOREACH (Group* group, m_groups) { group->setUpdateTimeinfo(true); @@ -151,6 +174,9 @@ void KeePass2XmlReader::parseMeta() else if (m_xml.name() == "MaintenanceHistoryDays") { m_meta->setMaintenanceHistoryDays(readNumber()); } + else if (m_xml.name() == "Color") { + m_meta->setColor(readColor()); + } else if (m_xml.name() == "MasterKeyChanged") { m_meta->setMasterKeyChanged(readDateTime()); } @@ -187,6 +213,15 @@ void KeePass2XmlReader::parseMeta() else if (m_xml.name() == "LastTopVisibleGroup") { m_meta->setLastTopVisibleGroup(getGroup(readUuid())); } + else if (m_xml.name() == "HistoryMaxItems") { + m_meta->setHistoryMaxItems(readNumber()); + } + else if (m_xml.name() == "HistoryMaxSize") { + m_meta->setHistoryMaxSize(readNumber()); + } + else if (m_xml.name() == "Binaries") { + parseBinaries(); + } else if (m_xml.name() == "CustomData") { parseCustomData(); } @@ -216,9 +251,9 @@ void KeePass2XmlReader::parseMemoryProtection() else if (m_xml.name() == "ProtectNotes") { m_meta->setProtectNotes(readBool()); } - else if (m_xml.name() == "AutoEnableVisualHiding") { + /*else if (m_xml.name() == "AutoEnableVisualHiding") { m_meta->setAutoEnableVisualHiding(readBool()); - } + }*/ else { skipCurrentElement(); } @@ -259,6 +294,37 @@ void KeePass2XmlReader::parseIcon() } } +void KeePass2XmlReader::parseBinaries() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries"); + + while (!m_xml.error() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Binary") { + QXmlStreamAttributes attr = m_xml.attributes(); + + QString id = attr.value("ID").toString(); + + QByteArray data; + if (attr.value("Compressed").compare("True", Qt::CaseInsensitive) == 0) { + data = readCompressedBinary(); + } + else { + data = readBinary(); + } + + if (m_binaryPool.contains(id)) { + qWarning("KeePass2XmlReader::parseBinaries: overwriting binary item \"%s\"", + qPrintable(id)); + } + + m_binaryPool.insert(id, data); + } + else { + skipCurrentElement(); + } + } +} + void KeePass2XmlReader::parseCustomData() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData"); @@ -547,7 +613,8 @@ void KeePass2XmlReader::parseEntryString(Entry *entry) QXmlStreamAttributes attr = m_xml.attributes(); QString value = readString(); - bool isProtected = attr.hasAttribute("Protected") && (attr.value("Protected") == "True"); + bool isProtected = attr.value("Protected") == "True"; + bool protectInMemory = attr.value("ProtectInMemory") == "True"; if (isProtected && !value.isEmpty()) { if (m_randomStream) { @@ -558,7 +625,7 @@ void KeePass2XmlReader::parseEntryString(Entry *entry) } } - entry->attributes()->set(key, value, isProtected); + entry->attributes()->set(key, value, isProtected || protectInMemory); } else { skipCurrentElement(); @@ -576,16 +643,24 @@ void KeePass2XmlReader::parseEntryBinary(Entry *entry) key = readString(); } else if (m_xml.name() == "Value") { - QByteArray value = readBinary(); QXmlStreamAttributes attr = m_xml.attributes(); - bool isProtected = attr.hasAttribute("Protected") && (attr.value("Protected") == "True"); - - if (isProtected && !value.isEmpty()) { - m_randomStream->processInPlace(value); + if (attr.hasAttribute("Ref")) { + m_binaryMap.insertMulti(attr.value("Ref").toString(), qMakePair(entry, key)); + m_xml.skipCurrentElement(); } + else { + // format compatbility + QByteArray value = readBinary(); + bool isProtected = attr.hasAttribute("Protected") + && (attr.value("Protected") == "True"); + + if (isProtected && !value.isEmpty()) { + m_randomStream->processInPlace(value); + } - entry->attachments()->set(key, value, isProtected); + entry->attachments()->set(key, value); + } } else { skipCurrentElement(); @@ -782,6 +857,38 @@ QByteArray KeePass2XmlReader::readBinary() return QByteArray::fromBase64(readString().toAscii()); } +QByteArray KeePass2XmlReader::readCompressedBinary() +{ + QByteArray rawData = readBinary(); + + QBuffer buffer(&rawData); + buffer.open(QIODevice::ReadOnly); + + QtIOCompressor compressor(&buffer); + compressor.setStreamFormat(QtIOCompressor::GzipFormat); + compressor.open(QIODevice::ReadOnly); + + QByteArray result; + qint64 readBytes = 0; + qint64 readResult; + do { + result.resize(result.size() + 16384); + readResult = compressor.read(result.data() + readBytes, result.size() - readBytes); + if (readResult > 0) { + readBytes += readResult; + } + } while (readResult > 0); + + if (readResult == -1) { + raiseError(16); + return QByteArray(); + } + else { + result.resize(static_cast<int>(readBytes)); + return result; + } +} + Group* KeePass2XmlReader::getGroup(const Uuid& uuid) { if (uuid.isNull()) { diff --git a/src/format/KeePass2XmlReader.h b/src/format/KeePass2XmlReader.h index 4e2cb0872..4f7288b62 100644 --- a/src/format/KeePass2XmlReader.h +++ b/src/format/KeePass2XmlReader.h @@ -20,6 +20,8 @@ #include <QtCore/QCoreApplication> #include <QtCore/QDateTime> +#include <QtCore/QHash> +#include <QtCore/QPair> #include <QtCore/QXmlStreamReader> #include <QtGui/QColor> @@ -51,6 +53,7 @@ private: void parseMemoryProtection(); void parseCustomIcons(); void parseIcon(); + void parseBinaries(); void parseCustomData(); void parseCustomDataItem(); void parseRoot(); @@ -72,6 +75,7 @@ private: int readNumber(); Uuid readUuid(); QByteArray readBinary(); + QByteArray readCompressedBinary(); Group* getGroup(const Uuid& uuid); Entry* getEntry(const Uuid& uuid); @@ -85,6 +89,8 @@ private: Group* m_tmpParent; QList<Group*> m_groups; QList<Entry*> m_entries; + QHash<QString, QByteArray> m_binaryPool; + QHash<QString, QPair<Entry*, QString> > m_binaryMap; }; #endif // KEEPASSX_KEEPASS2XMLREADER_H diff --git a/src/format/KeePass2XmlWriter.cpp b/src/format/KeePass2XmlWriter.cpp index 73d80b782..3c03a0aad 100644 --- a/src/format/KeePass2XmlWriter.cpp +++ b/src/format/KeePass2XmlWriter.cpp @@ -22,6 +22,7 @@ #include "core/Metadata.h" #include "format/KeePass2RandomStream.h" +#include "streams/QtIOCompressor" KeePass2XmlWriter::KeePass2XmlWriter() : m_db(0) @@ -39,6 +40,8 @@ void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2R m_meta = db->metadata(); m_randomStream = randomStream; + generateIdMap(); + m_xml.setDevice(device); m_xml.writeStartDocument("1.0", true); @@ -60,6 +63,21 @@ void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db, Kee writeDatabase(&file, db, randomStream); } +void KeePass2XmlWriter::generateIdMap() +{ + QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true); + int nextId = 0; + + Q_FOREACH (Entry* entry, allEntries) { + Q_FOREACH (const QString& key, entry->attachments()->keys()) { + QByteArray data = entry->attachments()->value(key); + if (!m_idMap.contains(data)) { + m_idMap.insert(data, nextId++); + } + } + } +} + void KeePass2XmlWriter::writeMetadata() { m_xml.writeStartElement("Meta"); @@ -72,6 +90,7 @@ void KeePass2XmlWriter::writeMetadata() writeString("DefaultUserName", m_meta->defaultUserName()); writeDateTime("DefaultUserNameChanged", m_meta->defaultUserNameChanged()); writeNumber("MaintenanceHistoryDays", m_meta->maintenanceHistoryDays()); + writeColor("Color", m_meta->color()); writeDateTime("MasterKeyChanged", m_meta->masterKeyChanged()); writeNumber("MasterKeyChangeRec", m_meta->masterKeyChangeRec()); writeNumber("MasterKeyChangeForce", m_meta->masterKeyChangeForce()); @@ -84,6 +103,9 @@ void KeePass2XmlWriter::writeMetadata() writeDateTime("EntryTemplatesGroupChanged", m_meta->entryTemplatesGroupChanged()); writeUuid("LastSelectedGroup", m_meta->lastSelectedGroup()); writeUuid("LastTopVisibleGroup", m_meta->lastTopVisibleGroup()); + writeNumber("HistoryMaxItems", m_meta->historyMaxItems()); + writeNumber("HistoryMaxSize", m_meta->historyMaxSize()); + writeBinaries(); writeCustomData(); m_xml.writeEndElement(); @@ -98,7 +120,7 @@ void KeePass2XmlWriter::writeMemoryProtection() writeBool("ProtectPassword", m_meta->protectPassword()); writeBool("ProtectURL", m_meta->protectUrl()); writeBool("ProtectNotes", m_meta->protectNotes()); - writeBool("AutoEnableVisualHiding", m_meta->autoEnableVisualHiding()); + // writeBool("AutoEnableVisualHiding", m_meta->autoEnableVisualHiding()); m_xml.writeEndElement(); } @@ -132,6 +154,43 @@ void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) m_xml.writeEndElement(); } +void KeePass2XmlWriter::writeBinaries() +{ + m_xml.writeStartElement("Binaries"); + + QHash<QByteArray, int>::const_iterator i; + for (i = m_idMap.constBegin(); i != m_idMap.constEnd(); ++i) { + m_xml.writeStartElement("Binary"); + + m_xml.writeAttribute("ID", QString::number(i.value())); + + if (m_db->compressionAlgo() == Database::CompressionGZip) { + m_xml.writeAttribute("Compressed", "True"); + + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + + QtIOCompressor compressor(&buffer); + compressor.setStreamFormat(QtIOCompressor::GzipFormat); + compressor.open(QIODevice::WriteOnly); + + qint64 bytesWritten = compressor.write(i.key()); + Q_ASSERT(bytesWritten == i.key().size()); + compressor.close(); + + buffer.seek(0); + m_xml.writeCharacters(QString::fromAscii(buffer.readAll().toBase64())); + } + else { + m_xml.writeCharacters(QString::fromAscii(i.key().toBase64())); + } + + m_xml.writeEndElement(); + } + + m_xml.writeEndElement(); +} + void KeePass2XmlWriter::writeCustomData() { m_xml.writeStartElement("CustomData"); @@ -262,8 +321,7 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry) ((key == "Password") && m_meta->protectPassword()) || ((key == "URL") && m_meta->protectUrl()) || ((key == "Notes") && m_meta->protectNotes()) || - entry->attributes()->isProtected(key) ) && - m_randomStream; + entry->attributes()->isProtected(key) ); writeString("Key", key); @@ -271,9 +329,15 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry) QString value; if (protect) { - m_xml.writeAttribute("Protected", "True"); - QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8()); - value = QString::fromAscii(rawData.toBase64()); + if (m_randomStream) { + m_xml.writeAttribute("Protected", "True"); + QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8()); + value = QString::fromAscii(rawData.toBase64()); + } + else { + m_xml.writeAttribute("ProtectInMemory", "True"); + value = entry->attributes()->value(key); + } } else { value = entry->attributes()->value(key); @@ -288,23 +352,10 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry) Q_FOREACH (const QString& key, entry->attachments()->keys()) { m_xml.writeStartElement("Binary"); - bool protect = entry->attachments()->isProtected(key) && m_randomStream; - writeString("Key", key); m_xml.writeStartElement("Value"); - QString value; - - if (protect) { - m_xml.writeAttribute("Protected", "True"); - QByteArray rawData = m_randomStream->process(entry->attachments()->value(key)); - value = QString::fromAscii(rawData.toBase64()); - } - else { - value = entry->attachments()->value(key); - } - - m_xml.writeCharacters(value); + m_xml.writeAttribute("Ref", QString::number(m_idMap[entry->attachments()->value(key)])); m_xml.writeEndElement(); m_xml.writeEndElement(); diff --git a/src/format/KeePass2XmlWriter.h b/src/format/KeePass2XmlWriter.h index 044f2fd90..14a5d83a3 100644 --- a/src/format/KeePass2XmlWriter.h +++ b/src/format/KeePass2XmlWriter.h @@ -43,10 +43,13 @@ public: QString errorString(); private: + void generateIdMap(); + void writeMetadata(); void writeMemoryProtection(); void writeCustomIcons(); void writeIcon(const Uuid& uuid, const QImage& icon); + void writeBinaries(); void writeCustomData(); void writeCustomDataItem(const QString& key, const QString& value); void writeRoot(); @@ -75,6 +78,7 @@ private: Database* m_db; Metadata* m_meta; KeePass2RandomStream* m_randomStream; + QHash<QByteArray, int> m_idMap; }; #endif // KEEPASSX_KEEPASS2XMLWRITER_H diff --git a/tests/TestKeePass2Reader.cpp b/tests/TestKeePass2Reader.cpp index a0897a74c..39f9e0b1a 100644 --- a/tests/TestKeePass2Reader.cpp +++ b/tests/TestKeePass2Reader.cpp @@ -82,8 +82,6 @@ void TestKeePass2Reader::testProtectedStrings() QCOMPARE(entry->attributes()->value("TestProtected"), QString("ABC")); QCOMPARE(entry->attributes()->value("TestUnprotected"), QString("DEF")); - QVERIFY(!db->metadata()->protectTitle()); - QVERIFY(db->metadata()->protectUsername()); QVERIFY(db->metadata()->protectPassword()); QVERIFY(entry->attributes()->isProtected("TestProtected")); QVERIFY(!entry->attributes()->isProtected("TestUnprotected")); @@ -92,4 +90,36 @@ void TestKeePass2Reader::testProtectedStrings() delete reader; } +void TestKeePass2Reader::testFormat200() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format200.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("a")); + KeePass2Reader* reader = new KeePass2Reader(); + Database* db = reader->readDatabase(filename, key); + QVERIFY(db); + QVERIFY(!reader->hasError()); + + QCOMPARE(db->rootGroup()->name(), QString("Format200")); + QVERIFY(!db->metadata()->protectTitle()); + QVERIFY(db->metadata()->protectUsername()); + QVERIFY(!db->metadata()->protectPassword()); + QVERIFY(db->metadata()->protectUrl()); + QVERIFY(!db->metadata()->protectNotes()); + + QCOMPARE(db->rootGroup()->entries().size(), 1); + Entry* entry = db->rootGroup()->entries().at(0); + + QCOMPARE(entry->title(), QString("Sample Entry")); + QCOMPARE(entry->username(), QString("User Name")); + QCOMPARE(entry->attachments()->keys().size(), 2); + QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("abcdefghijk")); + QCOMPARE(entry->attachments()->value("test.txt"), QByteArray("this is a test")); + + QCOMPARE(entry->historyItems().size(), 2); + QCOMPARE(entry->historyItems().at(0)->attachments()->keys().size(), 0); + QCOMPARE(entry->historyItems().at(1)->attachments()->keys().size(), 1); + QCOMPARE(entry->historyItems().at(1)->attachments()->value("myattach.txt"), QByteArray("abcdefghijk")); +} + KEEPASSX_QTEST_CORE_MAIN(TestKeePass2Reader) diff --git a/tests/TestKeePass2Reader.h b/tests/TestKeePass2Reader.h index b2a75f66e..27680c855 100644 --- a/tests/TestKeePass2Reader.h +++ b/tests/TestKeePass2Reader.h @@ -29,6 +29,7 @@ private Q_SLOTS: void testNonAscii(); void testCompressed(); void testProtectedStrings(); + void testFormat200(); }; #endif // KEEPASSX_TESTKEEPASS2READER_H diff --git a/tests/TestKeePass2Writer.cpp b/tests/TestKeePass2Writer.cpp index 6e3a25838..903de99be 100644 --- a/tests/TestKeePass2Writer.cpp +++ b/tests/TestKeePass2Writer.cpp @@ -46,6 +46,8 @@ void TestKeePass2Writer::initTestCase() entry->setUuid(Uuid::random()); entry->attributes()->set("test", "protectedTest", true); QVERIFY(entry->attributes()->isProtected("test")); + entry->attachments()->set("myattach.txt", QByteArray("this is an attachment")); + entry->attachments()->set("aaa.txt", QByteArray("also an attachment")); entry->setGroup(group); Group* groupNew = new Group(); groupNew->setUuid(Uuid::random()); @@ -83,4 +85,12 @@ void TestKeePass2Writer::testProtectedAttributes() QCOMPARE(entry->attributes()->isProtected("test"), true); } +void TestKeePass2Writer::testAttachments() +{ + Entry* entry = m_dbTest->rootGroup()->entries().at(0); + QCOMPARE(entry->attachments()->keys().size(), 2); + QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("this is an attachment")); + QCOMPARE(entry->attachments()->value("aaa.txt"), QByteArray("also an attachment")); +} + KEEPASSX_QTEST_CORE_MAIN(TestKeePass2Writer) diff --git a/tests/TestKeePass2Writer.h b/tests/TestKeePass2Writer.h index e0e333a89..a18433cd5 100644 --- a/tests/TestKeePass2Writer.h +++ b/tests/TestKeePass2Writer.h @@ -30,6 +30,7 @@ private Q_SLOTS: void initTestCase(); void testBasic(); void testProtectedAttributes(); + void testAttachments(); private: Database* m_dbOrg; diff --git a/tests/TestKeePass2XmlReader.cpp b/tests/TestKeePass2XmlReader.cpp index 146dad65b..b32e45ff1 100644 --- a/tests/TestKeePass2XmlReader.cpp +++ b/tests/TestKeePass2XmlReader.cpp @@ -84,12 +84,15 @@ void TestKeePass2XmlReader::testMetadata() QCOMPARE(m_db->metadata()->defaultUserName(), QString("DEFUSERNAME")); QCOMPARE(m_db->metadata()->defaultUserNameChanged(), genDT(2010, 8, 8, 17, 27, 45)); QCOMPARE(m_db->metadata()->maintenanceHistoryDays(), 127); + QCOMPARE(m_db->metadata()->color(), QColor(0xff, 0xef, 0x00)); + QCOMPARE(m_db->metadata()->masterKeyChanged(), genDT(2012, 4, 5, 17, 9, 34)); + QCOMPARE(m_db->metadata()->masterKeyChangeRec(), 101); + QCOMPARE(m_db->metadata()->masterKeyChangeForce(), -1); QCOMPARE(m_db->metadata()->protectTitle(), false); QCOMPARE(m_db->metadata()->protectUsername(), true); QCOMPARE(m_db->metadata()->protectPassword(), false); QCOMPARE(m_db->metadata()->protectUrl(), true); QCOMPARE(m_db->metadata()->protectNotes(), false); - QCOMPARE(m_db->metadata()->autoEnableVisualHiding(), false); QCOMPARE(m_db->metadata()->recycleBinEnabled(), true); QVERIFY(m_db->metadata()->recycleBin() != 0); QCOMPARE(m_db->metadata()->recycleBin()->name(), QString("Recycle Bin")); @@ -99,6 +102,8 @@ void TestKeePass2XmlReader::testMetadata() QVERIFY(m_db->metadata()->lastSelectedGroup() != 0); QCOMPARE(m_db->metadata()->lastSelectedGroup()->name(), QString("NewDatabase")); QVERIFY(m_db->metadata()->lastTopVisibleGroup() == m_db->metadata()->lastSelectedGroup()); + QCOMPARE(m_db->metadata()->historyMaxItems(), -1); + QCOMPARE(m_db->metadata()->historyMaxSize(), 5242880); } void TestKeePass2XmlReader::testCustomIcons() @@ -199,11 +204,13 @@ void TestKeePass2XmlReader::testEntry1() const Entry* entry = m_db->rootGroup()->entries().at(0); QCOMPARE(entry->uuid().toBase64(), QString("+wSUOv6qf0OzW8/ZHAs2sA==")); + QCOMPARE(entry->historyItems().size(), 2); QCOMPARE(entry->iconNumber(), 0); QCOMPARE(entry->iconUuid(), Uuid()); QVERIFY(!entry->foregroundColor().isValid()); QVERIFY(!entry->backgroundColor().isValid()); QCOMPARE(entry->overrideUrl(), QString("")); + QCOMPARE(entry->tags(), QString("a b c")); const TimeInfo ti = entry->timeInfo(); QCOMPARE(ti.lastModificationTime(), genDT(2010, 8, 25, 16, 19, 25)); @@ -216,14 +223,19 @@ void TestKeePass2XmlReader::testEntry1() QList<QString> attrs = entry->attributes()->keys(); QCOMPARE(entry->attributes()->value("Notes"), QString("Notes")); + QVERIFY(!entry->attributes()->isProtected("Notes")); QVERIFY(attrs.removeOne("Notes")); QCOMPARE(entry->attributes()->value("Password"), QString("Password")); + QVERIFY(!entry->attributes()->isProtected("Password")); QVERIFY(attrs.removeOne("Password")); QCOMPARE(entry->attributes()->value("Title"), QString("Sample Entry 1")); + QVERIFY(!entry->attributes()->isProtected("Title")); QVERIFY(attrs.removeOne("Title")); QCOMPARE(entry->attributes()->value("URL"), QString("")); + QVERIFY(entry->attributes()->isProtected("URL")); QVERIFY(attrs.removeOne("URL")); QCOMPARE(entry->attributes()->value("UserName"), QString("User Name")); + QVERIFY(entry->attributes()->isProtected("UserName")); QVERIFY(attrs.removeOne("UserName")); QVERIFY(attrs.isEmpty()); @@ -233,7 +245,13 @@ void TestKeePass2XmlReader::testEntry1() QCOMPARE(entry->password(), entry->attributes()->value("Password")); QCOMPARE(entry->notes(), entry->attributes()->value("Notes")); - QCOMPARE(entry->attachments()->keys().size(), 0); + QCOMPARE(entry->attachments()->keys().size(), 1); + QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("abcdefghijk")); + QCOMPARE(entry->historyItems().at(0)->attachments()->keys().size(), 1); + QCOMPARE(entry->historyItems().at(0)->attachments()->value("myattach.txt"), QByteArray("0123456789")); + QCOMPARE(entry->historyItems().at(1)->attachments()->keys().size(), 1); + QCOMPARE(entry->historyItems().at(1)->attachments()->value("myattach.txt"), QByteArray("abcdefghijk")); + QCOMPARE(entry->autoTypeEnabled(), false); QCOMPARE(entry->autoTypeObfuscation(), 0); QCOMPARE(entry->defaultAutoTypeSequence(), QString("")); @@ -254,6 +272,7 @@ void TestKeePass2XmlReader::testEntry2() QCOMPARE(entry->foregroundColor(), QColor(255, 0, 0)); QCOMPARE(entry->backgroundColor(), QColor(255, 255, 0)); QCOMPARE(entry->overrideUrl(), QString("http://override.net/")); + QCOMPARE(entry->tags(), QString("")); const TimeInfo ti = entry->timeInfo(); QCOMPARE(ti.usageCount(), 7); @@ -276,7 +295,7 @@ void TestKeePass2XmlReader::testEntry2() QVERIFY(attrs.isEmpty()); QCOMPARE(entry->attachments()->keys().size(), 1); - QCOMPARE(QString(entry->attachments()->value("testattach.txt")), QString("42")); + QCOMPARE(QString(entry->attachments()->value("myattach.txt")), QString("abcdefghijk")); QCOMPARE(entry->autoTypeEnabled(), true); QCOMPARE(entry->autoTypeObfuscation(), 1); diff --git a/tests/data/Compressed.kdbx b/tests/data/Compressed.kdbx Binary files differindex fd3108b93..af12d73aa 100644 --- a/tests/data/Compressed.kdbx +++ b/tests/data/Compressed.kdbx diff --git a/tests/data/Format200.kdbx b/tests/data/Format200.kdbx Binary files differnew file mode 100644 index 000000000..c3b26cdbc --- /dev/null +++ b/tests/data/Format200.kdbx diff --git a/tests/data/NewDatabase.xml b/tests/data/NewDatabase.xml index 26b245f24..d07b83693 100644 --- a/tests/data/NewDatabase.xml +++ b/tests/data/NewDatabase.xml @@ -9,13 +9,16 @@ <DefaultUserName>DEFUSERNAME</DefaultUserName>
<DefaultUserNameChanged>2010-08-08T17:27:45Z</DefaultUserNameChanged>
<MaintenanceHistoryDays>127</MaintenanceHistoryDays>
+ <Color>#FFEF00</Color>
+ <MasterKeyChanged>2012-04-05T17:09:34Z</MasterKeyChanged>
+ <MasterKeyChangeRec>101</MasterKeyChangeRec>
+ <MasterKeyChangeForce>-1</MasterKeyChangeForce>
<MemoryProtection>
<ProtectTitle>False</ProtectTitle>
<ProtectUserName>True</ProtectUserName>
<ProtectPassword>False</ProtectPassword>
<ProtectURL>True</ProtectURL>
<ProtectNotes>False</ProtectNotes>
- <AutoEnableVisualHiding>False</AutoEnableVisualHiding>
</MemoryProtection>
<CustomIcons>
<Icon>
@@ -30,6 +33,12 @@ <EntryTemplatesGroupChanged>2010-08-08T17:24:19Z</EntryTemplatesGroupChanged>
<LastSelectedGroup>lmU+9n0aeESKZvcEze+bRg==</LastSelectedGroup>
<LastTopVisibleGroup>lmU+9n0aeESKZvcEze+bRg==</LastTopVisibleGroup>
+ <HistoryMaxItems>-1</HistoryMaxItems>
+ <HistoryMaxSize>5242880</HistoryMaxSize>
+ <Binaries>
+ <Binary ID="0" Compressed="True">H4sIAAAAAAAEAO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3mkuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrIHz9+fB8/InZ29+7t3//0wcHD/wfGx4SmCgAAAA==</Binary>
+ <Binary ID="1" Compressed="True">H4sIAAAAAAAEAO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3mkuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrIHz9+fB8/IrLJdJafX8yLn377/wCfD1fOCwAAAA==</Binary>
+ </Binaries>
<CustomData>
<Item>
<Key>A Sample Test Key</Key>
@@ -67,6 +76,7 @@ <ForegroundColor />
<BackgroundColor />
<OverrideURL />
+ <Tags>a b c</Tags>
<Times>
<LastModificationTime>2010-08-25T16:19:25Z</LastModificationTime>
<CreationTime>2010-08-25T16:13:54Z</CreationTime>
@@ -90,12 +100,16 @@ </String>
<String>
<Key>URL</Key>
- <Value />
+ <Value ProtectInMemory="True" />
</String>
<String>
<Key>UserName</Key>
- <Value>User Name</Value>
+ <Value ProtectInMemory="True">User Name</Value>
</String>
+ <Binary>
+ <Key>myattach.txt</Key>
+ <Value Ref="1" />
+ </Binary>
<AutoType>
<Enabled>False</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
@@ -111,6 +125,7 @@ <ForegroundColor />
<BackgroundColor />
<OverrideURL />
+ <Tags />
<Times>
<LastModificationTime>2010-08-25T16:13:54Z</LastModificationTime>
<CreationTime>2010-08-25T16:13:54Z</CreationTime>
@@ -140,6 +155,10 @@ <Key>UserName</Key>
<Value>User Name</Value>
</String>
+ <Binary>
+ <Key>myattach.txt</Key>
+ <Value Ref="0" />
+ </Binary>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
@@ -155,6 +174,7 @@ <ForegroundColor />
<BackgroundColor />
<OverrideURL />
+ <Tags />
<Times>
<LastModificationTime>2010-08-25T16:15:43Z</LastModificationTime>
<CreationTime>2010-08-25T16:13:54Z</CreationTime>
@@ -184,6 +204,10 @@ <Key>UserName</Key>
<Value>User Name</Value>
</String>
+ <Binary>
+ <Key>myattach.txt</Key>
+ <Value Ref="1" />
+ </Binary>
<AutoType>
<Enabled>True</Enabled>
<DataTransferObfuscation>0</DataTransferObfuscation>
@@ -202,6 +226,7 @@ <ForegroundColor>#FF0000</ForegroundColor>
<BackgroundColor>#FFFF00</BackgroundColor>
<OverrideURL>http://override.net/</OverrideURL>
+ <Tags />
<Times>
<LastModificationTime>2010-08-25T16:20:24Z</LastModificationTime>
<CreationTime>2010-08-25T16:15:45Z</CreationTime>
@@ -233,15 +258,15 @@ </String>
<String>
<Key>URL</Key>
- <Value>http://www.keepassx.org/</Value>
+ <Value ProtectInMemory="True">http://www.keepassx.org/</Value>
</String>
<String>
<Key>UserName</Key>
- <Value>notDEFUSERNAME</Value>
+ <Value ProtectInMemory="True">notDEFUSERNAME</Value>
</String>
<Binary>
- <Key>testattach.txt</Key>
- <Value>NDI=</Value>
+ <Key>myattach.txt</Key>
+ <Value Ref="1" />
</Binary>
<AutoType>
<Enabled>True</Enabled>
@@ -322,6 +347,7 @@ <ForegroundColor />
<BackgroundColor />
<OverrideURL />
+ <Tags />
<Times>
<LastModificationTime>2010-08-25T16:21:15Z</LastModificationTime>
<CreationTime>2010-08-25T16:20:45Z</CreationTime>
@@ -384,6 +410,7 @@ <ForegroundColor />
<BackgroundColor />
<OverrideURL />
+ <Tags />
<Times>
<LastModificationTime>2010-08-25T16:20:32Z</LastModificationTime>
<CreationTime>2010-08-25T16:20:27Z</CreationTime>
diff --git a/tests/data/NonAscii.kdbx b/tests/data/NonAscii.kdbx Binary files differindex d4e4ec547..285831f8e 100644 --- a/tests/data/NonAscii.kdbx +++ b/tests/data/NonAscii.kdbx diff --git a/tests/data/NonAscii.kdbx.key b/tests/data/NonAscii.kdbx.key new file mode 100644 index 000000000..3a3610971 --- /dev/null +++ b/tests/data/NonAscii.kdbx.key @@ -0,0 +1 @@ +Δöض diff --git a/tests/data/ProtectedStrings.kdbx b/tests/data/ProtectedStrings.kdbx Binary files differindex 9b903f7ae..2614097fc 100644 --- a/tests/data/ProtectedStrings.kdbx +++ b/tests/data/ProtectedStrings.kdbx |