Properly save custom header data

Ensure adding custom data upgrades to KDBX4
Implement review feedback
This commit is contained in:
Janek Bevendorff 2018-02-18 21:01:22 +01:00
parent 114f00e1e8
commit 5410d78bbb
26 changed files with 189 additions and 174 deletions

View File

@ -1,6 +1,5 @@
/* /*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de> * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -50,21 +49,15 @@ bool CustomData::containsValue(const QString& value) const
void CustomData::set(const QString& key, const QString& value) void CustomData::set(const QString& key, const QString& value)
{ {
bool emitModified = false;
bool addAttribute = !m_data.contains(key); bool addAttribute = !m_data.contains(key);
bool changeValue = !addAttribute && (m_data.value(key) != value); bool changeValue = !addAttribute && (m_data.value(key) != value);
if (addAttribute ) { if (addAttribute) {
emit aboutToBeAdded(key); emit aboutToBeAdded(key);
} }
if (addAttribute || changeValue) { if (addAttribute || changeValue) {
m_data.insert(key, value); m_data.insert(key, value);
emitModified = true;
}
if (emitModified) {
emit modified(); emit modified();
} }
@ -85,13 +78,10 @@ void CustomData::remove(const QString& key)
void CustomData::rename(const QString& oldKey, const QString& newKey) void CustomData::rename(const QString& oldKey, const QString& newKey)
{ {
if (!m_data.contains(oldKey)) { const bool containsOldKey = m_data.contains(oldKey);
Q_ASSERT(false); const bool containsNewKey = m_data.contains(newKey);
return; Q_ASSERT(containsOldKey && !containsNewKey);
} if (!containsOldKey || containsNewKey) {
if (m_data.contains(newKey)) {
Q_ASSERT(false);
return; return;
} }
@ -108,16 +98,17 @@ void CustomData::rename(const QString& oldKey, const QString& newKey)
void CustomData::copyDataFrom(const CustomData* other) void CustomData::copyDataFrom(const CustomData* other)
{ {
if (*this != *other) { if (*this == *other) {
emit aboutToBeReset(); return;
m_data = other->m_data;
emit reset();
emit modified();
} }
}
emit aboutToBeReset();
m_data = other->m_data;
emit reset();
emit modified();
}
bool CustomData::operator==(const CustomData& other) const bool CustomData::operator==(const CustomData& other) const
{ {
return (m_data == other.m_data); return (m_data == other.m_data);
@ -143,7 +134,12 @@ bool CustomData::isEmpty() const
return m_data.isEmpty(); return m_data.isEmpty();
} }
int CustomData::dataSize() int CustomData::size() const
{
return m_data.size();
}
int CustomData::dataSize() const
{ {
int size = 0; int size = 0;

View File

@ -1,6 +1,5 @@
/* /*
* Copyright (C) 2012 Felix Geyer <debfx@fobos.de> * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by * it under the terms of the GNU General Public License as published by
@ -16,17 +15,17 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef KEEPASSX_CUSTOMDATA_H #ifndef KEEPASSXC_CUSTOMDATA_H
#define KEEPASSX_CUSTOMDATA_H #define KEEPASSXC_CUSTOMDATA_H
#include <QMap> #include <QHash>
#include <QObject> #include <QObject>
#include <QSet> #include <QSet>
#include <QStringList> #include <QStringList>
class CustomData : public QObject class CustomData : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
explicit CustomData(QObject* parent = nullptr); explicit CustomData(QObject* parent = nullptr);
@ -40,7 +39,8 @@ public:
void rename(const QString& oldKey, const QString& newKey); void rename(const QString& oldKey, const QString& newKey);
void clear(); void clear();
bool isEmpty() const; bool isEmpty() const;
int dataSize(); int size() const;
int dataSize() const;
void copyDataFrom(const CustomData* other); void copyDataFrom(const CustomData* other);
bool operator==(const CustomData& other) const; bool operator==(const CustomData& other) const;
bool operator!=(const CustomData& other) const; bool operator!=(const CustomData& other) const;
@ -61,4 +61,4 @@ private:
QHash<QString, QString> m_data; QHash<QString, QString> m_data;
}; };
#endif // KEEPASSX_CUSTOMDATA_H #endif // KEEPASSXC_CUSTOMDATA_H

View File

@ -320,6 +320,17 @@ bool Database::verifyKey(const CompositeKey& key) const
return (m_data.key.rawKey() == key.rawKey()); return (m_data.key.rawKey() == key.rawKey());
} }
QVariantMap Database::publicCustomData() const
{
return m_data.publicCustomData;
}
void Database::setPublicCustomData(const QVariantMap& customData)
{
m_data.publicCustomData = customData;
}
void Database::createRecycleBin() void Database::createRecycleBin()
{ {
Group* recycleBin = Group::createRecycleBin(); Group* recycleBin = Group::createRecycleBin();
@ -578,14 +589,6 @@ void Database::setKdf(QSharedPointer<Kdf> kdf)
m_data.kdf = std::move(kdf); m_data.kdf = std::move(kdf);
} }
void Database::setPublicCustomData(QByteArray data) {
m_data.publicCustomData = data;
}
QByteArray Database::publicCustomData() const {
return m_data.publicCustomData;
}
bool Database::changeKdf(QSharedPointer<Kdf> kdf) bool Database::changeKdf(QSharedPointer<Kdf> kdf)
{ {
kdf->randomizeSeed(); kdf->randomizeSeed();

View File

@ -59,12 +59,12 @@ public:
Uuid cipher; Uuid cipher;
CompressionAlgorithm compressionAlgo; CompressionAlgorithm compressionAlgo;
QByteArray transformedMasterKey; QByteArray transformedMasterKey;
QByteArray publicCustomData;
QSharedPointer<Kdf> kdf; QSharedPointer<Kdf> kdf;
CompositeKey key; CompositeKey key;
bool hasKey; bool hasKey;
QByteArray masterSeed; QByteArray masterSeed;
QByteArray challengeResponseKey; QByteArray challengeResponseKey;
QVariantMap publicCustomData;
}; };
Database(); Database();
@ -93,7 +93,6 @@ public:
Uuid cipher() const; Uuid cipher() const;
Database::CompressionAlgorithm compressionAlgo() const; Database::CompressionAlgorithm compressionAlgo() const;
QSharedPointer<Kdf> kdf() const; QSharedPointer<Kdf> kdf() const;
QByteArray publicCustomData() const;
QByteArray transformedMasterKey() const; QByteArray transformedMasterKey() const;
const CompositeKey& key() const; const CompositeKey& key() const;
QByteArray challengeResponseKey() const; QByteArray challengeResponseKey() const;
@ -102,11 +101,12 @@ public:
void setCipher(const Uuid& cipher); void setCipher(const Uuid& cipher);
void setCompressionAlgo(Database::CompressionAlgorithm algo); void setCompressionAlgo(Database::CompressionAlgorithm algo);
void setKdf(QSharedPointer<Kdf> kdf); void setKdf(QSharedPointer<Kdf> kdf);
void setPublicCustomData(QByteArray data);
bool setKey(const CompositeKey& key, bool updateChangedTime = true, bool setKey(const CompositeKey& key, bool updateChangedTime = true,
bool updateTransformSalt = false); bool updateTransformSalt = false);
bool hasKey() const; bool hasKey() const;
bool verifyKey(const CompositeKey& key) const; bool verifyKey(const CompositeKey& key) const;
QVariantMap publicCustomData() const;
void setPublicCustomData(const QVariantMap& customData);
void recycleEntry(Entry* entry); void recycleEntry(Entry* entry);
void recycleGroup(Group* group); void recycleGroup(Group* group);
void emptyRecycleBin(); void emptyRecycleBin();

View File

@ -342,12 +342,12 @@ const EntryAttachments* Entry::attachments() const
return m_attachments; return m_attachments;
} }
CustomData *Entry::customData() CustomData* Entry::customData()
{ {
return m_customData; return m_customData;
} }
const CustomData *Entry::customData() const const CustomData* Entry::customData() const
{ {
return m_customData; return m_customData;
} }
@ -618,7 +618,7 @@ void Entry::truncateHistory()
size += historyItem->attributes()->attributesSize(); size += historyItem->attributes()->attributesSize();
size += historyItem->autoTypeAssociations()->associationsSize(); size += historyItem->autoTypeAssociations()->associationsSize();
size += historyItem->attachments()->attachmentsSize(); size += historyItem->attachments()->attachmentsSize();
size += customData()->dataSize(); size += historyItem->customData()->dataSize();
const QStringList tags = historyItem->tags().split(delimiter, QString::SkipEmptyParts); const QStringList tags = historyItem->tags().split(delimiter, QString::SkipEmptyParts);
for (const QString& tag : tags) { for (const QString& tag : tags) {
size += tag.toUtf8().size(); size += tag.toUtf8().size();

View File

@ -108,8 +108,8 @@ public:
const EntryAttributes* attributes() const; const EntryAttributes* attributes() const;
EntryAttachments* attachments(); EntryAttachments* attachments();
const EntryAttachments* attachments() const; const EntryAttachments* attachments() const;
CustomData *customData(); CustomData* customData();
const CustomData *customData() const; const CustomData* customData() const;
static const int DefaultIconNumber; static const int DefaultIconNumber;
static const int ResolveMaximumDepth; static const int ResolveMaximumDepth;
@ -231,11 +231,10 @@ private:
Uuid m_uuid; Uuid m_uuid;
EntryData m_data; EntryData m_data;
EntryAttributes* const m_attributes; QPointer<EntryAttributes> m_attributes;
EntryAttachments* const m_attachments; QPointer<EntryAttachments> m_attachments;
AutoTypeAssociations* const m_autoTypeAssociations; QPointer<AutoTypeAssociations> m_autoTypeAssociations;
QPointer<CustomData> m_customData;
CustomData* const m_customData;
QList<Entry*> m_history; QList<Entry*> m_history;
Entry* m_tmpHistoryItem; Entry* m_tmpHistoryItem;

View File

@ -254,12 +254,12 @@ bool Group::isExpired() const
return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < QDateTime::currentDateTimeUtc(); return m_data.timeInfo.expires() && m_data.timeInfo.expiryTime() < QDateTime::currentDateTimeUtc();
} }
CustomData *Group::customData() CustomData* Group::customData()
{ {
return m_customData; return m_customData;
} }
const CustomData *Group::customData() const const CustomData* Group::customData() const
{ {
return m_customData; return m_customData;
} }

View File

@ -84,8 +84,8 @@ public:
bool resolveAutoTypeEnabled() const; bool resolveAutoTypeEnabled() const;
Entry* lastTopVisibleEntry() const; Entry* lastTopVisibleEntry() const;
bool isExpired() const; bool isExpired() const;
CustomData *customData(); CustomData* customData();
const CustomData *customData() const; const CustomData* customData() const;
static const int DefaultIconNumber; static const int DefaultIconNumber;
static const int RecycleBinIconNumber; static const int RecycleBinIconNumber;
@ -191,7 +191,7 @@ private:
QList<Group*> m_children; QList<Group*> m_children;
QList<Entry*> m_entries; QList<Entry*> m_entries;
CustomData *const m_customData; QPointer<CustomData> m_customData;
QPointer<Group> m_parent; QPointer<Group> m_parent;

View File

@ -294,12 +294,12 @@ int Metadata::historyMaxSize() const
return m_data.historyMaxSize; return m_data.historyMaxSize;
} }
CustomData *Metadata::customData() CustomData* Metadata::customData()
{ {
return m_customData; return m_customData;
} }
const CustomData *Metadata::customData() const const CustomData* Metadata::customData() const
{ {
return m_customData; return m_customData;
} }

View File

@ -98,8 +98,8 @@ public:
int masterKeyChangeForce() const; int masterKeyChangeForce() const;
int historyMaxItems() const; int historyMaxItems() const;
int historyMaxSize() const; int historyMaxSize() const;
CustomData *customData(); CustomData* customData();
const CustomData *customData() const; const CustomData* customData() const;
static const int DefaultHistoryMaxItems; static const int DefaultHistoryMaxItems;
static const int DefaultHistoryMaxSize; static const int DefaultHistoryMaxSize;
@ -175,7 +175,7 @@ private:
QDateTime m_masterKeyChanged; QDateTime m_masterKeyChanged;
QDateTime m_settingsChanged; QDateTime m_settingsChanged;
CustomData *const m_customData; QPointer<CustomData> m_customData;
bool m_updateDatetime; bool m_updateDatetime;
}; };

View File

@ -209,9 +209,14 @@ bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
break; break;
} }
case KeePass2::HeaderFieldID::PublicCustomData: case KeePass2::HeaderFieldID::PublicCustomData: {
m_db->setPublicCustomData(fieldData); QBuffer variantBuffer;
variantBuffer.setBuffer(&fieldData);
variantBuffer.open(QBuffer::ReadOnly);
QVariantMap data = readVariantMap(&variantBuffer);
m_db->setPublicCustomData(data);
break; break;
}
case KeePass2::HeaderFieldID::ProtectedStreamKey: case KeePass2::HeaderFieldID::ProtectedStreamKey:
case KeePass2::HeaderFieldID::TransformRounds: case KeePass2::HeaderFieldID::TransformRounds:
@ -291,10 +296,10 @@ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device)
} }
/** /**
* Helper method for reading KDF parameters into variant map. * Helper method for reading a serialized variant map.
* *
* @param device input device * @param device input device
* @return filled variant map * @return de-serialized variant map
*/ */
QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device) QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device)
{ {

View File

@ -22,6 +22,8 @@
#include "streams/HmacBlockStream.h" #include "streams/HmacBlockStream.h"
#include "core/Database.h" #include "core/Database.h"
#include "core/Metadata.h"
#include "core/CustomData.h"
#include "crypto/CryptoHash.h" #include "crypto/CryptoHash.h"
#include "crypto/Random.h" #include "crypto/Random.h"
#include "format/KeePass2RandomStream.h" #include "format/KeePass2RandomStream.h"
@ -86,10 +88,14 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
raiseError(tr("Failed to serialize KDF parameters variant map")); raiseError(tr("Failed to serialize KDF parameters variant map"));
return false; return false;
} }
QByteArray publicCustomData = db->publicCustomData();
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::KdfParameters, kdfParamBytes)); CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::KdfParameters, kdfParamBytes));
QVariantMap publicCustomData = db->publicCustomData();
if (!publicCustomData.isEmpty()) { if (!publicCustomData.isEmpty()) {
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::PublicCustomData, publicCustomData)); QByteArray serialized;
serializeVariantMap(publicCustomData, serialized);
CHECK_RETURN_FALSE(writeHeaderField<quint32>(
&header, KeePass2::HeaderFieldID::PublicCustomData, serialized));
} }
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EndOfHeader, endOfHeader)); CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EndOfHeader, endOfHeader));
@ -230,7 +236,7 @@ void Kdbx4Writer::writeAttachments(QIODevice* device, Database* db)
} }
/** /**
* Serialize KDF parameter variant map to byte array. * Serialize variant map to byte array.
* *
* @param map input variant map * @param map input variant map
* @param outputBytes output byte array * @param outputBytes output byte array

View File

@ -397,7 +397,7 @@ void KdbxXmlReader::parseBinaries()
} }
} }
void KdbxXmlReader::parseCustomData(CustomData *customData) void KdbxXmlReader::parseCustomData(CustomData* customData)
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData");
@ -410,7 +410,7 @@ void KdbxXmlReader::parseCustomData(CustomData *customData)
} }
} }
void KdbxXmlReader::parseCustomDataItem(CustomData *customData) void KdbxXmlReader::parseCustomDataItem(CustomData* customData)
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item");
@ -748,7 +748,7 @@ Entry* KdbxXmlReader::parseEntry(bool history)
} }
continue; continue;
} }
if (m_xml.name() == "CustomData" ){ if (m_xml.name() == "CustomData") {
parseCustomData(entry->customData()); parseCustomData(entry->customData());
continue; continue;
} }

View File

@ -66,8 +66,8 @@ protected:
virtual void parseCustomIcons(); virtual void parseCustomIcons();
virtual void parseIcon(); virtual void parseIcon();
virtual void parseBinaries(); virtual void parseBinaries();
virtual void parseCustomData(CustomData *customData); virtual void parseCustomData(CustomData* customData);
virtual void parseCustomDataItem(CustomData *customData); virtual void parseCustomDataItem(CustomData* customData);
virtual bool parseRoot(); virtual bool parseRoot();
virtual Group* parseGroup(); virtual Group* parseGroup();
virtual void parseDeletedObjects(); virtual void parseDeletedObjects();

View File

@ -129,7 +129,9 @@ void KdbxXmlWriter::writeMetadata()
if (m_kdbxVersion < KeePass2::FILE_VERSION_4) { if (m_kdbxVersion < KeePass2::FILE_VERSION_4) {
writeBinaries(); writeBinaries();
} }
writeCustomData(m_meta->customData()); if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
writeCustomData(m_meta->customData());
}
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
@ -218,8 +220,11 @@ void KdbxXmlWriter::writeBinaries()
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KdbxXmlWriter::writeCustomData(const CustomData *customData) void KdbxXmlWriter::writeCustomData(const CustomData* customData)
{ {
if (customData->isEmpty()) {
return;
}
m_xml.writeStartElement("CustomData"); m_xml.writeStartElement("CustomData");
const QList<QString> keyList = customData->keys(); const QList<QString> keyList = customData->keys();
@ -276,7 +281,7 @@ void KdbxXmlWriter::writeGroup(const Group* group)
writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry()); writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry());
if (!group->customData()->isEmpty()){ if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
writeCustomData(group->customData()); writeCustomData(group->customData());
} }
@ -405,7 +410,7 @@ void KdbxXmlWriter::writeEntry(const Entry* entry)
writeAutoType(entry); writeAutoType(entry);
if (!entry->customData()->isEmpty()){ if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
writeCustomData(entry->customData()); writeCustomData(entry->customData());
} }

View File

@ -51,7 +51,7 @@ private:
void writeCustomIcons(); void writeCustomIcons();
void writeIcon(const Uuid& uuid, const QImage& icon); void writeIcon(const Uuid& uuid, const QImage& icon);
void writeBinaries(); void writeBinaries();
void writeCustomData(const CustomData *customData); void writeCustomData(const CustomData* customData);
void writeCustomDataItem(const QString& key, const QString& value); void writeCustomDataItem(const QString& key, const QString& value);
void writeRoot(); void writeRoot();
void writeGroup(const Group* group); void writeGroup(const Group* group);

View File

@ -19,6 +19,8 @@
#include <QFile> #include <QFile>
#include "core/Database.h" #include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/kdf/AesKdf.h" #include "crypto/kdf/AesKdf.h"
#include "format/KeePass2Writer.h" #include "format/KeePass2Writer.h"
#include "format/Kdbx3Writer.h" #include "format/Kdbx3Writer.h"
@ -48,12 +50,24 @@ bool KeePass2Writer::writeDatabase(const QString& filename, Database* db)
* @param db source database * @param db source database
* @return true on success * @return true on success
*/ */
bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) { bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) {
m_error = false; m_error = false;
m_errorStr.clear(); m_errorStr.clear();
// determine KDBX3 vs KDBX4 // determine KDBX3 vs KDBX4
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 && db->publicCustomData().isEmpty()) { bool hasCustomData = !db->publicCustomData().isEmpty() || (db->metadata()->customData() && !db->metadata()->customData()->isEmpty());
if (!hasCustomData) {
for (const auto& entry: db->rootGroup()->entriesRecursive(true)) {
if ((entry->customData() && !entry->customData()->isEmpty()) ||
(entry->group() && entry->group()->customData() && !entry->group()->customData()->isEmpty())) {
hasCustomData = true;
break;
}
}
}
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 && !hasCustomData) {
m_version = KeePass2::FILE_VERSION_3_1; m_version = KeePass2::FILE_VERSION_3_1;
m_writer.reset(new Kdbx3Writer()); m_writer.reset(new Kdbx3Writer());
} else { } else {

View File

@ -18,8 +18,6 @@
#include "EditWidgetProperties.h" #include "EditWidgetProperties.h"
#include "ui_EditWidgetProperties.h" #include "ui_EditWidgetProperties.h"
#include <QStandardItemModel>
EditWidgetProperties::EditWidgetProperties(QWidget* parent) EditWidgetProperties::EditWidgetProperties(QWidget* parent)
: QWidget(parent) : QWidget(parent)
, m_ui(new Ui::EditWidgetProperties()) , m_ui(new Ui::EditWidgetProperties())
@ -29,14 +27,14 @@ EditWidgetProperties::EditWidgetProperties(QWidget* parent)
m_ui->setupUi(this); m_ui->setupUi(this);
m_ui->customDataTable->setModel(m_customDataModel); m_ui->customDataTable->setModel(m_customDataModel);
this->connect( m_ui->removeCustomDataButton, SIGNAL(clicked()), SLOT(removeSelectedPluginData())); connect(m_ui->removeCustomDataButton, SIGNAL(clicked()), SLOT(removeSelectedPluginData()));
} }
EditWidgetProperties::~EditWidgetProperties() EditWidgetProperties::~EditWidgetProperties()
{ {
} }
void EditWidgetProperties::setFields(const TimeInfo &timeInfo, const Uuid &uuid) void EditWidgetProperties::setFields(const TimeInfo& timeInfo, const Uuid& uuid)
{ {
static const QString timeFormat("d MMM yyyy HH:mm:ss"); static const QString timeFormat("d MMM yyyy HH:mm:ss");
m_ui->modifiedEdit->setText( m_ui->modifiedEdit->setText(
@ -48,35 +46,37 @@ void EditWidgetProperties::setFields(const TimeInfo &timeInfo, const Uuid &uuid)
m_ui->uuidEdit->setText(uuid.toHex()); m_ui->uuidEdit->setText(uuid.toHex());
} }
void EditWidgetProperties::setCustomData(const CustomData *customData) void EditWidgetProperties::setCustomData(const CustomData* customData)
{ {
Q_ASSERT(customData != nullptr); Q_ASSERT(customData);
m_customData->copyDataFrom(customData); m_customData->copyDataFrom(customData);
this->updateModel(); this->updateModel();
} }
const CustomData *EditWidgetProperties::customData() const const CustomData* EditWidgetProperties::customData() const
{ {
return m_customData; return m_customData;
} }
void EditWidgetProperties::removeSelectedPluginData() void EditWidgetProperties::removeSelectedPluginData()
{ {
const QItemSelectionModel *pSelectionModel = m_ui->customDataTable->selectionModel(); const QItemSelectionModel* itemSelectionModel = m_ui->customDataTable->selectionModel();
if (pSelectionModel){ if (itemSelectionModel) {
for( const QModelIndex& index : pSelectionModel->selectedRows(0) ){ for (const QModelIndex& index : itemSelectionModel->selectedRows(0)) {
const QString key = index.data().toString(); const QString key = index.data().toString();
m_customData->remove(key); m_customData->remove(key);
} }
this->updateModel(); updateModel();
} }
} }
void EditWidgetProperties::updateModel() void EditWidgetProperties::updateModel()
{ {
m_customDataModel->clear(); m_customDataModel->clear();
for( const QString& key : m_customData->keys() ){ for (const QString& key : m_customData->keys()) {
m_customDataModel->appendRow(QList<QStandardItem*>() << new QStandardItem( key ) << new QStandardItem( m_customData->value( key ) )); m_customDataModel->appendRow(QList<QStandardItem*>()
<< new QStandardItem(key)
<< new QStandardItem(m_customData->value(key)));
} }
} }

View File

@ -38,10 +38,10 @@ public:
explicit EditWidgetProperties(QWidget* parent = nullptr); explicit EditWidgetProperties(QWidget* parent = nullptr);
~EditWidgetProperties(); ~EditWidgetProperties();
void setFields(const TimeInfo &timeInfo, const Uuid &uuid); void setFields(const TimeInfo& timeInfo, const Uuid& uuid);
void setCustomData(const CustomData *customData); void setCustomData(const CustomData* customData);
const CustomData *customData() const; const CustomData* customData() const;
private slots: private slots:
void removeSelectedPluginData(); void removeSelectedPluginData();

View File

@ -33,7 +33,6 @@ QTEST_GUILESS_MAIN(TestKdbx3)
void TestKdbx3::initTestCaseImpl() void TestKdbx3::initTestCaseImpl()
{ {
} }
Database* TestKdbx3::readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) Database* TestKdbx3::readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString)

View File

@ -131,6 +131,7 @@ void TestKdbx4::testFormat400Upgrade()
{ {
QFETCH(Uuid, kdfUuid); QFETCH(Uuid, kdfUuid);
QFETCH(Uuid, cipherUuid); QFETCH(Uuid, cipherUuid);
QFETCH(bool, addCustomData);
QFETCH(quint32, expectedVersion); QFETCH(quint32, expectedVersion);
QScopedPointer<Database> sourceDb(new Database()); QScopedPointer<Database> sourceDb(new Database());
@ -147,6 +148,12 @@ void TestKdbx4::testFormat400Upgrade()
// upgrade to KDBX 4 by changing KDF and Cipher // upgrade to KDBX 4 by changing KDF and Cipher
sourceDb->changeKdf(KeePass2::uuidToKdf(kdfUuid)); sourceDb->changeKdf(KeePass2::uuidToKdf(kdfUuid));
sourceDb->setCipher(cipherUuid); sourceDb->setCipher(cipherUuid);
if (addCustomData) {
sourceDb->metadata()->customData()->set("CustomPublicData", "Hey look, I turned myself into a pickle!");
sourceDb->rootGroup()->customData()->set("CustomGroupData", "I just killed my family! I don't care who they were!");
}
KeePass2Writer writer; KeePass2Writer writer;
writer.writeDatabase(&buffer, sourceDb.data()); writer.writeDatabase(&buffer, sourceDb.data());
if (writer.hasError()) { if (writer.hasError()) {
@ -165,26 +172,39 @@ void TestKdbx4::testFormat400Upgrade()
QCOMPARE(targetDb->metadata()->name(), sourceDb->metadata()->name()); QCOMPARE(targetDb->metadata()->name(), sourceDb->metadata()->name());
QCOMPARE(reader.version(), expectedVersion); QCOMPARE(reader.version(), expectedVersion);
QCOMPARE(targetDb->kdf()->uuid(), sourceDb->kdf()->uuid());
QCOMPARE(targetDb->cipher(), cipherUuid); QCOMPARE(targetDb->cipher(), cipherUuid);
QCOMPARE(*targetDb->metadata()->customData(), *sourceDb->metadata()->customData());
QCOMPARE(*targetDb->rootGroup()->customData(), *sourceDb->rootGroup()->customData());
} }
void TestKdbx4::testFormat400Upgrade_data() void TestKdbx4::testFormat400Upgrade_data()
{ {
QTest::addColumn<Uuid>("kdfUuid"); QTest::addColumn<Uuid>("kdfUuid");
QTest::addColumn<Uuid>("cipherUuid"); QTest::addColumn<Uuid>("cipherUuid");
QTest::addColumn<bool>("addCustomData");
QTest::addColumn<quint32>("expectedVersion"); QTest::addColumn<quint32>("expectedVersion");
auto constexpr kdbx3 = KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK; auto constexpr kdbx3 = KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK;
auto constexpr kdbx4 = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK; auto constexpr kdbx4 = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << kdbx4; QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << false << kdbx4;
QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << kdbx4; QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << false << kdbx4;
QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << kdbx3; QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << false << kdbx3;
QTest::newRow("Argon2 + ChaCha20") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_CHACHA20 << kdbx4; QTest::newRow("Argon2 + AES + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << true << kdbx4;
QTest::newRow("AES-KDF + ChaCha20") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << kdbx4; QTest::newRow("AES-KDF + AES + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << true << kdbx4;
QTest::newRow("AES-KDF (legacy) + ChaCha20") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_CHACHA20 << kdbx3; QTest::newRow("AES-KDF (legacy) + AES + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << true << kdbx4;
QTest::newRow("Argon2 + Twofish") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_TWOFISH << kdbx4;
QTest::newRow("AES-KDF + Twofish") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_TWOFISH << kdbx4; QTest::newRow("Argon2 + ChaCha20") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
QTest::newRow("AES-KDF (legacy) + Twofish") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_TWOFISH << kdbx3; QTest::newRow("AES-KDF + ChaCha20") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << false << kdbx4;
QTest::newRow("AES-KDF (legacy) + ChaCha20") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_CHACHA20 << false << kdbx3;
QTest::newRow("Argon2 + ChaCha20 + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_CHACHA20 << true << kdbx4;
QTest::newRow("AES-KDF + ChaCha20 + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << true << kdbx4;
QTest::newRow("AES-KDF (legacy) + ChaCha20 + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_CHACHA20 << true << kdbx4;
QTest::newRow("Argon2 + Twofish") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_TWOFISH << false << kdbx4;
QTest::newRow("AES-KDF + Twofish") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_TWOFISH << false << kdbx4;
QTest::newRow("AES-KDF (legacy) + Twofish") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_TWOFISH << false << kdbx3;
QTest::newRow("Argon2 + Twofish + CustomData") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_TWOFISH << true << kdbx4;
QTest::newRow("AES-KDF + Twofish + CustomData") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_TWOFISH << true << kdbx4;
QTest::newRow("AES-KDF (legacy) + Twofish + CustomData") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_TWOFISH << true << kdbx4;
} }

View File

@ -124,15 +124,6 @@ void TestKeePass2Format::testXmlCustomIcons()
} }
} }
void TestKeePass2Format::testXmlCustomData()
{
QHash<QString, QString> customFields = m_xmlDb->metadata()->customFields();
QCOMPARE(customFields.size(), 2);
QCOMPARE(customFields.value("A Sample Test Key"), QString("valu"));
QCOMPARE(customFields.value("custom key"), QString("blub"));
}
void TestKeePass2Format::testXmlGroupRoot() void TestKeePass2Format::testXmlGroupRoot()
{ {
const Group* group = m_xmlDb->rootGroup(); const Group* group = m_xmlDb->rootGroup();
@ -155,7 +146,6 @@ void TestKeePass2Format::testXmlGroupRoot()
QCOMPARE(group->autoTypeEnabled(), Group::Inherit); QCOMPARE(group->autoTypeEnabled(), Group::Inherit);
QCOMPARE(group->searchingEnabled(), Group::Inherit); QCOMPARE(group->searchingEnabled(), Group::Inherit);
QCOMPARE(group->lastTopVisibleEntry()->uuid().toBase64(), QString("+wSUOv6qf0OzW8/ZHAs2sA==")); QCOMPARE(group->lastTopVisibleEntry()->uuid().toBase64(), QString("+wSUOv6qf0OzW8/ZHAs2sA=="));
QCOMPARE(group->children().size(), 3); QCOMPARE(group->children().size(), 3);
QVERIFY(m_xmlDb->metadata()->recycleBin() == m_xmlDb->rootGroup()->children().at(2)); QVERIFY(m_xmlDb->metadata()->recycleBin() == m_xmlDb->rootGroup()->children().at(2));

View File

@ -40,7 +40,6 @@ private slots:
*/ */
void testXmlMetadata(); void testXmlMetadata();
void testXmlCustomIcons(); void testXmlCustomIcons();
void testXmlCustomData();
void testXmlGroupRoot(); void testXmlGroupRoot();
void testXmlGroup1(); void testXmlGroup1();
void testXmlGroup2(); void testXmlGroup2();

View File

@ -40,31 +40,31 @@ void TestModified::testSignals()
CompositeKey compositeKey; CompositeKey compositeKey;
Database* db = new Database(); QScopedPointer<Database> db(new Database());
Group* root = db->rootGroup(); auto* root = db->rootGroup();
QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate()));
db->setKey(compositeKey); db->setKey(compositeKey);
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
Group* group1 = new Group(); auto* group1 = new Group();
group1->setParent(root); group1->setParent(root);
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
Group* group2 = new Group(); auto* group2 = new Group();
group2->setParent(root); group2->setParent(root);
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
group2->setParent(root, 0); group2->setParent(root, 0);
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
Entry* entry1 = new Entry(); auto* entry1 = new Entry();
entry1->setGroup(group1); entry1->setGroup(group1);
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
Database* db2 = new Database(); QScopedPointer<Database> db2(new Database());
Group* root2 = db2->rootGroup(); auto* root2 = db2->rootGroup();
QSignalSpy spyModified2(db2, SIGNAL(modifiedImmediate())); QSignalSpy spyModified2(db2.data(), SIGNAL(modifiedImmediate()));
group1->setParent(root2); group1->setParent(root2);
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
@ -74,7 +74,7 @@ void TestModified::testSignals()
QCOMPARE(spyModified.count(), spyCount); QCOMPARE(spyModified.count(), spyCount);
QCOMPARE(spyModified2.count(), ++spyCount2); QCOMPARE(spyModified2.count(), ++spyCount2);
Entry* entry2 = new Entry(); auto* entry2 = new Entry();
entry2->setGroup(group2); entry2->setGroup(group2);
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
QCOMPARE(spyModified2.count(), spyCount2); QCOMPARE(spyModified2.count(), spyCount2);
@ -87,11 +87,11 @@ void TestModified::testSignals()
QCOMPARE(spyModified.count(), spyCount); QCOMPARE(spyModified.count(), spyCount);
QCOMPARE(spyModified2.count(), ++spyCount2); QCOMPARE(spyModified2.count(), ++spyCount2);
Group* group3 = new Group(); auto* group3 = new Group();
group3->setParent(root); group3->setParent(root);
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
Group* group4 = new Group(); auto* group4 = new Group();
group4->setParent(group3); group4->setParent(group3);
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
@ -103,21 +103,18 @@ void TestModified::testSignals()
QCOMPARE(spyModified.count(), spyCount); QCOMPARE(spyModified.count(), spyCount);
QCOMPARE(spyModified2.count(), spyCount2); QCOMPARE(spyModified2.count(), spyCount2);
delete db;
delete db2;
} }
void TestModified::testGroupSets() void TestModified::testGroupSets()
{ {
int spyCount = 0; int spyCount = 0;
Database* db = new Database(); QScopedPointer<Database> db(new Database());
Group* root = db->rootGroup(); auto* root = db->rootGroup();
Group* group = new Group(); auto* group = new Group();
group->setParent(root); group->setParent(root);
QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate()));
root->setUuid(Uuid::random()); root->setUuid(Uuid::random());
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
@ -169,22 +166,20 @@ void TestModified::testGroupSets()
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
group->setIcon(group->iconUuid()); group->setIcon(group->iconUuid());
QCOMPARE(spyModified.count(), spyCount); QCOMPARE(spyModified.count(), spyCount);
delete db;
} }
void TestModified::testEntrySets() void TestModified::testEntrySets()
{ {
int spyCount = 0; int spyCount = 0;
Database* db = new Database(); QScopedPointer<Database> db(new Database());
Group* root = db->rootGroup(); auto* root = db->rootGroup();
Group* group = new Group(); auto* group = new Group();
group->setParent(root); group->setParent(root);
Entry* entry = new Entry(); auto* entry = new Entry();
entry->setGroup(group); entry->setGroup(group);
QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate()));
entry->setUuid(Uuid::random()); entry->setUuid(Uuid::random());
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
@ -284,8 +279,6 @@ void TestModified::testEntrySets()
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
entry->attributes()->set("test key2", entry->attributes()->value("test key2"), true); entry->attributes()->set("test key2", entry->attributes()->value("test key2"), true);
QCOMPARE(spyModified.count(), spyCount); QCOMPARE(spyModified.count(), spyCount);
delete db;
} }
void TestModified::testHistoryItems() void TestModified::testHistoryItems()
@ -313,7 +306,7 @@ void TestModified::testHistoryItems()
entry->setTitle("b"); entry->setTitle("b");
entry->endUpdate(); entry->endUpdate();
QCOMPARE(entry->historyItems().size(), ++historyItemsSize); QCOMPARE(entry->historyItems().size(), ++historyItemsSize);
Entry *historyEntry = entry->historyItems().at(historyItemsSize - 1); auto *historyEntry = entry->historyItems().at(historyItemsSize - 1);
QCOMPARE(historyEntry->title(), QString("a")); QCOMPARE(historyEntry->title(), QString("a"));
QCOMPARE(historyEntry->uuid(), entry->uuid()); QCOMPARE(historyEntry->uuid(), entry->uuid());
QCOMPARE(historyEntry->tags(), entry->tags()); QCOMPARE(historyEntry->tags(), entry->tags());
@ -342,12 +335,11 @@ void TestModified::testHistoryItems()
QVERIFY(!entry->historyItems().at(historyItemsSize - 1)->attributes()->keys().contains("k")); QVERIFY(!entry->historyItems().at(historyItemsSize - 1)->attributes()->keys().contains("k"));
QScopedPointer<Database> db(new Database()); QScopedPointer<Database> db(new Database());
Group* root = db->rootGroup(); auto* root = db->rootGroup();
db->metadata()->setHistoryMaxItems(3); db->metadata()->setHistoryMaxItems(3);
db->metadata()->setHistoryMaxSize(-1); db->metadata()->setHistoryMaxSize(-1);
Entry* historyEntry2; auto* entry2 = new Entry();
Entry* entry2 = new Entry();
entry2->setGroup(root); entry2->setGroup(root);
entry2->beginUpdate(); entry2->beginUpdate();
entry2->setTitle("1"); entry2->setTitle("1");
@ -373,7 +365,7 @@ void TestModified::testHistoryItems()
entry2->endUpdate(); entry2->endUpdate();
QCOMPARE(entry2->historyItems().size(), 1); QCOMPARE(entry2->historyItems().size(), 1);
historyEntry2 = entry2->historyItems().at(0); auto* historyEntry2 = entry2->historyItems().at(0);
QCOMPARE(historyEntry2->title(), QString("4")); QCOMPARE(historyEntry2->title(), QString("4"));
db->metadata()->setHistoryMaxItems(-1); db->metadata()->setHistoryMaxItems(-1);
@ -451,7 +443,6 @@ void TestModified::testHistoryMaxSize()
QScopedPointer<Database> db(new Database()); QScopedPointer<Database> db(new Database());
const QString key("test"); const QString key("test");
auto entry1 = new Entry(); auto entry1 = new Entry();
entry1->setGroup(db->rootGroup()); entry1->setGroup(db->rootGroup());
QCOMPARE(entry1->historyItems().size(), 0); QCOMPARE(entry1->historyItems().size(), 0);
@ -584,15 +575,15 @@ void TestModified::testHistoryMaxSize()
void TestModified::testCustomData() void TestModified::testCustomData()
{ {
int spyCount = 0; int spyCount = 0;
Database* db = new Database(); QScopedPointer<Database> db(new Database());
Group* root = db->rootGroup(); auto* root = db->rootGroup();
Group* group = new Group(); auto* group = new Group();
group->setParent(root); group->setParent(root);
Entry* entry = new Entry(); auto* entry = new Entry();
entry->setGroup(group); entry->setGroup(group);
QSignalSpy spyModified(db, SIGNAL(modifiedImmediate())); QSignalSpy spyModified(db.data(), SIGNAL(modifiedImmediate()));
db->metadata()->customData()->set("Key", "Value"); db->metadata()->customData()->set("Key", "Value");
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
@ -608,6 +599,4 @@ void TestModified::testCustomData()
QCOMPARE(spyModified.count(), ++spyCount); QCOMPARE(spyModified.count(), ++spyCount);
group->customData()->set("Key", "Value"); group->customData()->set("Key", "Value");
QCOMPARE(spyModified.count(), spyCount); QCOMPARE(spyModified.count(), spyCount);
delete db;
} }

View File

@ -39,16 +39,6 @@
<Binary ID="0" Compressed="True">H4sIAAAAAAAEAO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3mkuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrIHz9+fB8/InZ29+7t3//0wcHD/wfGx4SmCgAAAA==</Binary> <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> <Binary ID="1" Compressed="True">H4sIAAAAAAAEAO29B2AcSZYlJi9tynt/SvVK1+B0oQiAYBMk2JBAEOzBiM3mkuwdaUcjKasqgcplVmVdZhZAzO2dvPfee++999577733ujudTif33/8/XGZkAWz2zkrayZ4hgKrIHz9+fB8/IrLJdJafX8yLn377/wCfD1fOCwAAAA==</Binary>
</Binaries> </Binaries>
<CustomData>
<Item>
<Key>A Sample Test Key</Key>
<Value>valu</Value>
</Item>
<Item>
<Key>custom key</Key>
<Value>blub</Value>
</Item>
</CustomData>
</Meta> </Meta>
<Root> <Root>
<Group> <Group>