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.
This commit is contained in:
Felix Geyer
2012-04-21 16:45:46 +02:00
parent e8ac70120b
commit 8acd6f74d8
23 changed files with 376 additions and 81 deletions

View File

@@ -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");
entry->attachments()->set(key, value, isProtected);
if (isProtected && !value.isEmpty()) {
m_randomStream->processInPlace(value);
}
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()) {