diff --git a/share/demo.kdbx b/share/demo.kdbx
index 7be77579f0..b1a37e99fc 100644
Binary files a/share/demo.kdbx and b/share/demo.kdbx differ
diff --git a/share/translations/keepassxc_en.ts b/share/translations/keepassxc_en.ts
index daef0a8de5..b30987eee6 100644
--- a/share/translations/keepassxc_en.ts
+++ b/share/translations/keepassxc_en.ts
@@ -1535,6 +1535,28 @@ If you do not have a key file, please leave the field empty.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
DatabaseSettingWidgetMetaData
@@ -1808,18 +1830,6 @@ Are you sure you want to continue without a password?
Database format:
-
-
- This is only important if you need to use your database with other programs.
-
-
-
- KDBX 4.0 (recommended)
-
-
-
- KDBX 3.1
-
Database decryption time is unchanged
@@ -1919,6 +1929,22 @@ If you keep this number, your database may take hours, days, or even longer to o
If you keep this number, your database will not be protected from brute force attacks.
+
+
+
+
+
+
+
+
+
+
+ KDBX 4.0 (recommended) {4 ?}
+
+
+
+ KDBX 3
+
DatabaseSettingsWidgetFdoSecrets
@@ -6523,10 +6549,6 @@ Available commands:
AES-KDF (KDBX 4)
-
-
- AES-KDF (KDBX 3.1)
-
TOTP
@@ -7498,6 +7520,10 @@ Please consider generating a new key file.
+
+
+ AES-KDF (KDBX 3.1) {3)?}
+
QtIOCompressor
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index 5738a3b0aa..9bdf2cd0c2 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -116,14 +116,15 @@ bool Database::open(QSharedPointer key, QString* error, bool
* @param error error message in case of failure
* @return true on success
*/
-bool Database::open(const QString& filePath, QSharedPointer key, QString* error, bool readOnly)
+KeePass2Reader::Status
+Database::open(const QString& filePath, QSharedPointer key, QString* error, bool readOnly)
{
QFile dbFile(filePath);
if (!dbFile.exists()) {
if (error) {
*error = tr("File %1 does not exist.").arg(filePath);
}
- return false;
+ return KeePass2Reader::Status::Error;
}
// Don't autodetect read-only mode, as it triggers an upstream bug.
@@ -137,17 +138,18 @@ bool Database::open(const QString& filePath, QSharedPointer
if (error) {
*error = tr("Unable to open file %1.").arg(filePath);
}
- return false;
+ return KeePass2Reader::Status::Error;
}
setEmitModified(false);
KeePass2Reader reader;
- if (!reader.readDatabase(&dbFile, std::move(key), this)) {
+ KeePass2Reader::Status status = reader.readDatabase(&dbFile, std::move(key), this);
+ if (status == KeePass2Reader::Status::Error) {
if (error) {
*error = tr("Error while reading the database: %1").arg(reader.errorString());
}
- return false;
+ return status;
}
setReadOnly(readOnly);
@@ -160,7 +162,17 @@ bool Database::open(const QString& filePath, QSharedPointer
m_fileWatcher->start(canonicalFilePath(), 30, 1);
setEmitModified(true);
- return true;
+ return status;
+}
+
+quint32 Database::formatVersion() const
+{
+ return m_data.formatVersion;
+}
+
+void Database::setFormatVersion(quint32 version)
+{
+ m_data.formatVersion = version;
}
bool Database::isSaving()
@@ -935,6 +947,7 @@ void Database::setKdf(QSharedPointer kdf)
{
Q_ASSERT(!m_data.isReadOnly);
m_data.kdf = std::move(kdf);
+ setFormatVersion(KeePass2Writer::needsKdbxVersion(this, true, m_data.kdf.isNull()));
}
bool Database::changeKdf(const QSharedPointer& kdf)
diff --git a/src/core/Database.h b/src/core/Database.h
index c42025f850..b9cbed0f8f 100644
--- a/src/core/Database.h
+++ b/src/core/Database.h
@@ -29,6 +29,7 @@
#include "core/ModifiableObject.h"
#include "crypto/kdf/AesKdf.h"
#include "format/KeePass2.h"
+#include "format/KeePass2Reader.h"
#include "keys/CompositeKey.h"
#include "keys/PasswordKey.h"
@@ -75,10 +76,10 @@ class Database : public ModifiableObject
~Database() override;
bool open(QSharedPointer key, QString* error = nullptr, bool readOnly = false);
- bool open(const QString& filePath,
- QSharedPointer key,
- QString* error = nullptr,
- bool readOnly = false);
+ KeePass2Reader::Status open(const QString& filePath,
+ QSharedPointer key,
+ QString* error = nullptr,
+ bool readOnly = false);
bool save(SaveAction action = Atomic, const QString& backupFilePath = QString(), QString* error = nullptr);
bool saveAs(const QString& filePath,
SaveAction action = Atomic,
@@ -87,6 +88,9 @@ class Database : public ModifiableObject
bool extract(QByteArray&, QString* error = nullptr);
bool import(const QString& xmlExportPath, QString* error = nullptr);
+ quint32 formatVersion() const;
+ void setFormatVersion(quint32 version);
+
void releaseData();
bool isInitialized() const;
@@ -166,6 +170,7 @@ public slots:
private:
struct DatabaseData
{
+ quint32 formatVersion = 0;
QString filePath;
bool isReadOnly = false;
QUuid cipher = KeePass2::CIPHER_AES256;
diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp
index 5c718d6a0d..66935b7f79 100644
--- a/src/format/Kdbx3Reader.cpp
+++ b/src/format/Kdbx3Reader.cpp
@@ -34,7 +34,7 @@ bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
QSharedPointer key,
Database* db)
{
- Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3_1);
+ Q_ASSERT((db->formatVersion() & KeePass2::FILE_VERSION_CRITICAL_MASK) <= KeePass2::FILE_VERSION_3);
if (hasError()) {
return false;
@@ -120,7 +120,7 @@ bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
return false;
}
- Q_ASSERT(!xmlReader.headerHash().isEmpty() || m_kdbxVersion < KeePass2::FILE_VERSION_3_1);
+ Q_ASSERT(!xmlReader.headerHash().isEmpty() || db->formatVersion() < KeePass2::FILE_VERSION_3_1);
if (!xmlReader.headerHash().isEmpty()) {
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);
diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp
index 7ba4c3f36c..2770239a7b 100644
--- a/src/format/Kdbx3Writer.cpp
+++ b/src/format/Kdbx3Writer.cpp
@@ -68,7 +68,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
QBuffer header;
header.open(QIODevice::WriteOnly);
- writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, formatVersion());
+ writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, db->formatVersion());
CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
CHECK_RETURN_FALSE(
@@ -137,7 +137,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
return false;
}
- KdbxXmlWriter xmlWriter(formatVersion());
+ KdbxXmlWriter xmlWriter(db->formatVersion());
xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash);
// Explicitly close/reset streams so they are flushed and we can detect
@@ -161,8 +161,3 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
return true;
}
-
-quint32 Kdbx3Writer::formatVersion()
-{
- return KeePass2::FILE_VERSION_3_1;
-}
diff --git a/src/format/Kdbx3Writer.h b/src/format/Kdbx3Writer.h
index 45b0a8b510..eb98a470db 100644
--- a/src/format/Kdbx3Writer.h
+++ b/src/format/Kdbx3Writer.h
@@ -29,7 +29,6 @@ class Kdbx3Writer : public KdbxWriter
public:
bool writeDatabase(QIODevice* device, Database* db) override;
- quint32 formatVersion() override;
};
#endif // KEEPASSX_KDBX3WRITER_H
diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp
index c25c3e31b0..1dc7067dd2 100644
--- a/src/format/Kdbx4Reader.cpp
+++ b/src/format/Kdbx4Reader.cpp
@@ -36,7 +36,7 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
QSharedPointer key,
Database* db)
{
- Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
+ Q_ASSERT((db->formatVersion() & KeePass2::FILE_VERSION_CRITICAL_MASK) == KeePass2::FILE_VERSION_4);
m_binaryPool.clear();
diff --git a/src/format/Kdbx4Writer.cpp b/src/format/Kdbx4Writer.cpp
index cbd6f95119..08a0df013e 100644
--- a/src/format/Kdbx4Writer.cpp
+++ b/src/format/Kdbx4Writer.cpp
@@ -66,7 +66,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
QBuffer header;
header.open(QIODevice::WriteOnly);
- writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, formatVersion());
+ writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, db->formatVersion());
CHECK_RETURN_FALSE(
writeHeaderField(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
@@ -166,7 +166,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
return false;
}
- KdbxXmlWriter xmlWriter(formatVersion());
+ KdbxXmlWriter xmlWriter(db->formatVersion());
xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash);
// Explicitly close/reset streams so they are flushed and we can detect
@@ -306,8 +306,3 @@ bool Kdbx4Writer::serializeVariantMap(const QVariantMap& map, QByteArray& output
CHECK_RETURN_FALSE(buf.write(endBytes) == 1);
return true;
}
-
-quint32 Kdbx4Writer::formatVersion()
-{
- return KeePass2::FILE_VERSION_4;
-}
diff --git a/src/format/Kdbx4Writer.h b/src/format/Kdbx4Writer.h
index 8ef82f18f7..c8540245b1 100644
--- a/src/format/Kdbx4Writer.h
+++ b/src/format/Kdbx4Writer.h
@@ -29,7 +29,6 @@ class Kdbx4Writer : public KdbxWriter
public:
bool writeDatabase(QIODevice* device, Database* db) override;
- quint32 formatVersion() override;
private:
bool writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data);
diff --git a/src/format/KdbxReader.cpp b/src/format/KdbxReader.cpp
index 94ccf33423..5610897c84 100644
--- a/src/format/KdbxReader.cpp
+++ b/src/format/KdbxReader.cpp
@@ -74,14 +74,12 @@ bool KdbxReader::readDatabase(QIODevice* device, QSharedPointersetFormatVersion(version);
// read header fields
while (readHeaderField(headerStream, m_db) && !hasError()) {
diff --git a/src/format/KdbxReader.h b/src/format/KdbxReader.h
index cbc13b20bd..a7b9fc37e4 100644
--- a/src/format/KdbxReader.h
+++ b/src/format/KdbxReader.h
@@ -83,8 +83,6 @@ class KdbxReader
void raiseError(const QString& errorMessage);
- quint32 m_kdbxVersion = 0;
-
QByteArray m_masterSeed;
QByteArray m_encryptionIV;
QByteArray m_streamStartBytes;
diff --git a/src/format/KdbxWriter.cpp b/src/format/KdbxWriter.cpp
index b69cedbf78..b7758c7517 100644
--- a/src/format/KdbxWriter.cpp
+++ b/src/format/KdbxWriter.cpp
@@ -71,7 +71,7 @@ void KdbxWriter::extractDatabase(QByteArray& xmlOutput, Database* db)
QBuffer buffer;
buffer.setBuffer(&xmlOutput);
buffer.open(QIODevice::WriteOnly);
- KdbxXmlWriter writer(formatVersion());
+ KdbxXmlWriter writer(db->formatVersion());
writer.disableInnerStreamProtection(true);
writer.writeDatabase(&buffer, db);
}
diff --git a/src/format/KdbxWriter.h b/src/format/KdbxWriter.h
index d5e214a512..bec8fc8df9 100644
--- a/src/format/KdbxWriter.h
+++ b/src/format/KdbxWriter.h
@@ -52,11 +52,6 @@ class KdbxWriter
*/
virtual bool writeDatabase(QIODevice* device, Database* db) = 0;
- /**
- * Get the database format version for the writer.
- */
- virtual quint32 formatVersion() = 0;
-
void extractDatabase(QByteArray& xmlOutput, Database* db);
bool hasError() const;
diff --git a/src/format/KdbxXmlWriter.cpp b/src/format/KdbxXmlWriter.cpp
index 4b49b69728..35ed5ffdb3 100644
--- a/src/format/KdbxXmlWriter.cpp
+++ b/src/format/KdbxXmlWriter.cpp
@@ -166,7 +166,7 @@ void KdbxXmlWriter::writeIcon(const QUuid& uuid, const Metadata::CustomIconData&
m_xml.writeStartElement("Icon");
writeUuid("UUID", uuid);
- if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
+ if (m_kdbxVersion >= KeePass2::FILE_VERSION_4_1) {
if (!iconData.name.isEmpty()) {
writeString("Name", iconData.name);
}
@@ -243,7 +243,7 @@ void KdbxXmlWriter::writeCustomDataItem(const QString& key,
writeString("Key", key);
writeString("Value", item.value);
- if (writeLastModified && m_kdbxVersion >= KeePass2::FILE_VERSION_4 && item.lastModified.isValid()) {
+ if (writeLastModified && m_kdbxVersion >= KeePass2::FILE_VERSION_4_1 && item.lastModified.isValid()) {
writeDateTime("LastModificationTime", item.lastModified);
}
@@ -291,9 +291,9 @@ void KdbxXmlWriter::writeGroup(const Group* group)
if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
writeCustomData(group->customData());
- if (!group->previousParentGroupUuid().isNull()) {
- writeUuid("PreviousParentGroup", group->previousParentGroupUuid());
- }
+ }
+ if (m_kdbxVersion >= KeePass2::FILE_VERSION_4_1 && !group->previousParentGroupUuid().isNull()) {
+ writeUuid("PreviousParentGroup", group->previousParentGroupUuid());
}
const QList& entryList = group->entries();
@@ -363,7 +363,7 @@ void KdbxXmlWriter::writeEntry(const Entry* entry)
writeString("Tags", entry->tags());
writeTimes(entry->timeInfo());
- if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
+ if (m_kdbxVersion >= KeePass2::FILE_VERSION_4_1) {
if (entry->excludeFromReports()) {
writeBool("QualityCheck", false);
}
diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp
index cc57ccffa5..bf991f8805 100644
--- a/src/format/KeePass2.cpp
+++ b/src/format/KeePass2.cpp
@@ -56,7 +56,7 @@ const QList> KeePass2::KDFS{
qMakePair(KeePass2::KDF_ARGON2D, QObject::tr("Argon2d (KDBX 4 – recommended)")),
qMakePair(KeePass2::KDF_ARGON2ID, QObject::tr("Argon2id (KDBX 4)")),
qMakePair(KeePass2::KDF_AES_KDBX4, QObject::tr("AES-KDF (KDBX 4)")),
- qMakePair(KeePass2::KDF_AES_KDBX3, QObject::tr("AES-KDF (KDBX 3.1)"))};
+ qMakePair(KeePass2::KDF_AES_KDBX3, QObject::tr("AES-KDF (KDBX 3)"))};
QByteArray KeePass2::hmacKey(const QByteArray& masterSeed, const QByteArray& transformedMasterKey)
{
diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h
index c42183295c..5aed903c3a 100644
--- a/src/format/KeePass2.h
+++ b/src/format/KeePass2.h
@@ -31,16 +31,18 @@ namespace KeePass2
constexpr quint32 SIGNATURE_2 = 0xB54BFB67;
constexpr quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000;
+ constexpr quint32 FILE_VERSION_4_1 = 0x00040001;
constexpr quint32 FILE_VERSION_4 = 0x00040000;
constexpr quint32 FILE_VERSION_3_1 = 0x00030001;
constexpr quint32 FILE_VERSION_3 = 0x00030000;
constexpr quint32 FILE_VERSION_2 = 0x00020000;
constexpr quint32 FILE_VERSION_MIN = FILE_VERSION_2;
+ constexpr quint32 FILE_VERSION_MAX = FILE_VERSION_4_1;
constexpr quint16 VARIANTMAP_VERSION = 0x0100;
constexpr quint16 VARIANTMAP_CRITICAL_MASK = 0xFF00;
- const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
+ constexpr QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
extern const QUuid CIPHER_AES128;
extern const QUuid CIPHER_AES256;
diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp
index 3c9cd23bed..fed62d87ce 100644
--- a/src/format/KeePass2Reader.cpp
+++ b/src/format/KeePass2Reader.cpp
@@ -31,22 +31,23 @@
* @param db Database to read into
* @return true on success
*/
-bool KeePass2Reader::readDatabase(const QString& filename, QSharedPointer key, Database* db)
+KeePass2Reader::Status
+KeePass2Reader::readDatabase(const QString& filename, QSharedPointer key, Database* db)
{
QFile file(filename);
if (!file.open(QFile::ReadOnly)) {
raiseError(file.errorString());
- return false;
+ return Error;
}
- bool ok = readDatabase(&file, std::move(key), db);
+ Status status = readDatabase(&file, std::move(key), db);
if (file.error() != QFile::NoError) {
raiseError(file.errorString());
- return false;
+ return Error;
}
- return ok;
+ return status;
}
/**
@@ -57,7 +58,8 @@ bool KeePass2Reader::readDatabase(const QString& filename, QSharedPointer key, Database* db)
+KeePass2Reader::Status
+KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer key, Database* db)
{
m_error = false;
m_errorStr.clear();
@@ -67,7 +69,7 @@ bool KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer 'Import KeePass 1 database…'.\n"
"This is a one-way migration. You won't be able to open the imported "
"database with the old KeePassX 0.4 version."));
- return false;
+ return Error;
} else if (!(signature1 == KeePass2::SIGNATURE_1 && signature2 == KeePass2::SIGNATURE_2)) {
raiseError(tr("Not a KeePass database."));
- return false;
+ return Error;
}
- // mask out minor version
- m_version &= KeePass2::FILE_VERSION_CRITICAL_MASK;
-
- quint32 maxVersion = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
- if (m_version < KeePass2::FILE_VERSION_MIN || m_version > maxVersion) {
+ if (m_version < KeePass2::FILE_VERSION_MIN
+ || (m_version & KeePass2::FILE_VERSION_CRITICAL_MASK) > KeePass2::FILE_VERSION_MAX) {
raiseError(tr("Unsupported KeePass 2 database version."));
- return false;
+ return Error;
}
// determine file format (KDBX 2/3 or 4)
@@ -97,7 +96,10 @@ bool KeePass2Reader::readDatabase(QIODevice* device, QSharedPointerreadDatabase(device, std::move(key), db);
+ if (!m_reader->readDatabase(device, std::move(key), db)) {
+ return Error;
+ }
+ return hasMinorVersionMismatch() ? VersionWarn : Ok;
}
bool KeePass2Reader::hasError() const
@@ -110,6 +112,14 @@ QString KeePass2Reader::errorString() const
return !m_reader.isNull() ? m_reader->errorString() : m_errorStr;
}
+/**
+ * @return whether the KDBX minor version is greater than the newest supported.
+ */
+bool KeePass2Reader::hasMinorVersionMismatch() const
+{
+ return m_version > KeePass2::FILE_VERSION_MAX;
+}
+
/**
* @return detected KDBX version
*/
diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h
index a3f5c38b8d..6372d5ea4c 100644
--- a/src/format/KeePass2Reader.h
+++ b/src/format/KeePass2Reader.h
@@ -27,10 +27,18 @@ class KeePass2Reader
Q_DECLARE_TR_FUNCTIONS(KdbxReader)
public:
- bool readDatabase(const QString& filename, QSharedPointer key, Database* db);
- bool readDatabase(QIODevice* device, QSharedPointer key, Database* db);
+ enum Status
+ {
+ Error = 0,
+ Ok = 1,
+ VersionWarn = 2
+ };
+
+ Status readDatabase(const QString& filename, QSharedPointer key, Database* db);
+ Status readDatabase(QIODevice* device, QSharedPointer key, Database* db);
bool hasError() const;
+ bool hasMinorVersionMismatch() const;
QString errorString() const;
QSharedPointer reader() const;
diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp
index 86480e74cc..5ff810a9e2 100644
--- a/src/format/KeePass2Writer.cpp
+++ b/src/format/KeePass2Writer.cpp
@@ -40,44 +40,56 @@ bool KeePass2Writer::writeDatabase(const QString& filename, Database* db)
return writeDatabase(&file, db);
}
+#define VERSION_MAX(a, b) \
+ a = qMax(a, b); \
+ if (a >= KeePass2::FILE_VERSION_MAX) { \
+ return a; \
+ }
+
/**
- * @return true if the database should upgrade to KDBX4.
+ * Get the minimum KDBX version required for writing the database.
*/
-bool KeePass2Writer::implicitKDBXUpgradeNeeded(Database const* db)
+quint32 KeePass2Writer::needsKdbxVersion(Database const* db, bool ignoreCurrent, bool ignoreKdf)
{
- if (db->kdf()->uuid() != KeePass2::KDF_AES_KDBX3) {
- return false;
+ quint32 version = KeePass2::FILE_VERSION_3_1;
+ if (!ignoreCurrent) {
+ VERSION_MAX(version, db->formatVersion())
+ }
+
+ if (!ignoreKdf && !db->kdf().isNull() && !db->kdf()->uuid().isNull()
+ && db->kdf()->uuid() != KeePass2::KDF_AES_KDBX3) {
+ VERSION_MAX(version, KeePass2::FILE_VERSION_4)
}
if (!db->publicCustomData().isEmpty()) {
- return true;
+ VERSION_MAX(version, KeePass2::FILE_VERSION_4)
}
for (const auto& group : db->rootGroup()->groupsRecursive(true)) {
if (group->customData() && !group->customData()->isEmpty()) {
- return true;
+ VERSION_MAX(version, KeePass2::FILE_VERSION_4)
}
if (!group->tags().isEmpty()) {
- return true;
+ VERSION_MAX(version, KeePass2::FILE_VERSION_4_1)
}
if (group->previousParentGroup()) {
- return true;
+ VERSION_MAX(version, KeePass2::FILE_VERSION_4_1)
}
for (const auto& entry : group->entries()) {
if (entry->customData() && !entry->customData()->isEmpty()) {
- return true;
+ VERSION_MAX(version, KeePass2::FILE_VERSION_4)
}
if (entry->excludeFromReports()) {
- return true;
+ VERSION_MAX(version, KeePass2::FILE_VERSION_4_1)
}
if (entry->previousParentGroup()) {
- return true;
+ VERSION_MAX(version, KeePass2::FILE_VERSION_4_1)
}
for (const auto& historyItem : entry->historyItems()) {
if (historyItem->customData() && !historyItem->customData()->isEmpty()) {
- return true;
+ VERSION_MAX(version, KeePass2::FILE_VERSION_4)
}
}
}
@@ -87,11 +99,11 @@ bool KeePass2Writer::implicitKDBXUpgradeNeeded(Database const* db)
for (const QUuid& uuid : customIconsOrder) {
const auto& icon = db->metadata()->customIcon(uuid);
if (!icon.name.isEmpty() || icon.lastModified.isValid()) {
- return true;
+ VERSION_MAX(version, KeePass2::FILE_VERSION_4_1)
}
}
- return false;
+ return version;
}
/**
@@ -106,8 +118,8 @@ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
m_error = false;
m_errorStr.clear();
- bool upgradeNeeded = implicitKDBXUpgradeNeeded(db);
- if (upgradeNeeded) {
+ m_version = needsKdbxVersion(db);
+ if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 && m_version >= KeePass2::FILE_VERSION_4) {
// We MUST re-transform the key, because challenge-response hashing has changed in KDBX 4.
// If we forget to re-transform, the database will be saved WITHOUT a challenge-response key component!
auto kdf = KeePass2::uuidToKdf(KeePass2::KDF_AES_KDBX4);
@@ -115,12 +127,12 @@ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
db->changeKdf(kdf);
}
+ db->setFormatVersion(m_version);
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) {
- Q_ASSERT(!upgradeNeeded);
- m_version = KeePass2::FILE_VERSION_3_1;
+ Q_ASSERT(m_version <= KeePass2::FILE_VERSION_3_1);
m_writer.reset(new Kdbx3Writer());
} else {
- m_version = KeePass2::FILE_VERSION_4;
+ Q_ASSERT(m_version >= KeePass2::FILE_VERSION_4);
m_writer.reset(new Kdbx4Writer());
}
@@ -132,11 +144,13 @@ void KeePass2Writer::extractDatabase(Database* db, QByteArray& xmlOutput)
m_error = false;
m_errorStr.clear();
+ m_version = needsKdbxVersion(db);
+ db->setFormatVersion(m_version);
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) {
- m_version = KeePass2::FILE_VERSION_3_1;
+ Q_ASSERT(m_version <= KeePass2::FILE_VERSION_3_1);
m_writer.reset(new Kdbx3Writer());
} else {
- m_version = KeePass2::FILE_VERSION_4;
+ Q_ASSERT(m_version >= KeePass2::FILE_VERSION_4);
m_writer.reset(new Kdbx4Writer());
}
diff --git a/src/format/KeePass2Writer.h b/src/format/KeePass2Writer.h
index 049b1555cd..88f66336c2 100644
--- a/src/format/KeePass2Writer.h
+++ b/src/format/KeePass2Writer.h
@@ -33,7 +33,7 @@ class KeePass2Writer
bool writeDatabase(const QString& filename, Database* db);
bool writeDatabase(QIODevice* device, Database* db);
void extractDatabase(Database* db, QByteArray& xmlOutput);
- static bool implicitKDBXUpgradeNeeded(Database const* db);
+ static quint32 needsKdbxVersion(Database const* db, bool ignoreCurrent = false, bool ignoreKdf = false);
QSharedPointer writer() const;
quint32 version() const;
diff --git a/src/gui/DatabaseOpenWidget.cpp b/src/gui/DatabaseOpenWidget.cpp
index 43e529551e..e708f31e75 100644
--- a/src/gui/DatabaseOpenWidget.cpp
+++ b/src/gui/DatabaseOpenWidget.cpp
@@ -202,11 +202,31 @@ void DatabaseOpenWidget::openDatabase()
QApplication::setOverrideCursor(QCursor(Qt::WaitCursor));
m_ui->passwordFormFrame->setEnabled(false);
QCoreApplication::processEvents();
- bool ok = m_db->open(m_filename, databaseKey, &error, false);
+ KeePass2Reader::Status status = m_db->open(m_filename, databaseKey, &error, false);
QApplication::restoreOverrideCursor();
m_ui->passwordFormFrame->setEnabled(true);
- if (ok) {
+ if (status == KeePass2Reader::Status::VersionWarn) {
+ QScopedPointer msgBox(new QMessageBox(this));
+ msgBox->setIcon(QMessageBox::Warning);
+ msgBox->setWindowTitle(tr("Database Version Mismatch"));
+ msgBox->setText(tr("The database you are trying to open was most likely\n"
+ "created by a newer version of KeePassXC.\n\n"
+ "You can try to open it anyway, but it may be incomplete\n"
+ "and saving any changes may incur data loss.\n\n"
+ "We recommend you update your KeePassXC installation."));
+ auto btn = msgBox->addButton(tr("Open database anyway"), QMessageBox::ButtonRole::AcceptRole);
+ msgBox->setDefaultButton(btn);
+ msgBox->addButton(QMessageBox::Cancel);
+ msgBox->exec();
+ if (msgBox->clickedButton() != btn) {
+ m_db.reset(new Database());
+ m_ui->messageWidget->showMessage(tr("Database unlock canceled."), MessageWidget::MessageType::Error);
+ return;
+ }
+ }
+
+ if (status != KeePass2Reader::Status::Error) {
#ifdef WITH_XC_TOUCHID
QHash useTouchID = config()->get(Config::UseTouchID).toHash();
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp
index 1a967b7739..03d30798cf 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp
+++ b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.cpp
@@ -24,6 +24,7 @@
#include "core/Metadata.h"
#include "crypto/kdf/Argon2Kdf.h"
#include "format/KeePass2.h"
+#include "format/KeePass2Writer.h"
#include "gui/MessageBox.h"
const char* DatabaseSettingsWidgetEncryption::CD_DECRYPTION_TIME_PREFERENCE_KEY = "KPXC_DECRYPTION_TIME_PREFERENCE";
@@ -36,12 +37,13 @@ DatabaseSettingsWidgetEncryption::DatabaseSettingsWidgetEncryption(QWidget* pare
connect(m_ui->transformBenchmarkButton, SIGNAL(clicked()), SLOT(benchmarkTransformRounds()));
connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeKdf(int)));
+ m_ui->formatCannotBeChanged->setVisible(false);
connect(m_ui->memorySpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryChanged(int)));
connect(m_ui->parallelismSpinBox, SIGNAL(valueChanged(int)), this, SLOT(parallelismChanged(int)));
- m_ui->compatibilitySelection->addItem(tr("KDBX 4.0 (recommended)"), KeePass2::KDF_ARGON2D.toByteArray());
- m_ui->compatibilitySelection->addItem(tr("KDBX 3.1"), KeePass2::KDF_AES_KDBX3.toByteArray());
+ m_ui->compatibilitySelection->addItem(tr("KDBX 4 (recommended)"), KeePass2::KDF_ARGON2D.toByteArray());
+ m_ui->compatibilitySelection->addItem(tr("KDBX 3"), KeePass2::KDF_AES_KDBX3.toByteArray());
m_ui->decryptionTimeSlider->setMinimum(Kdf::MIN_ENCRYPTION_TIME / 100);
m_ui->decryptionTimeSlider->setMaximum(Kdf::MAX_ENCRYPTION_TIME / 100);
m_ui->decryptionTimeSlider->setValue(Kdf::DEFAULT_ENCRYPTION_TIME / 100);
@@ -93,6 +95,7 @@ void DatabaseSettingsWidgetEncryption::initialize()
m_db->setCipher(KeePass2::CIPHER_AES256);
isDirty = true;
}
+ bool kdbx3Enabled = KeePass2Writer::needsKdbxVersion(m_db.data(), true, true) <= KeePass2::FILE_VERSION_3_1;
// check if the DB's custom data has a decryption time setting stored
// and set the slider to it, otherwise just state that the time is unchanged
@@ -115,9 +118,14 @@ void DatabaseSettingsWidgetEncryption::initialize()
updateFormatCompatibility(m_db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 ? KDBX3 : KDBX4, isDirty);
setupAlgorithmComboBox();
- setupKdfComboBox();
+ setupKdfComboBox(kdbx3Enabled);
loadKdfParameters();
+ if (!kdbx3Enabled) {
+ m_ui->compatibilitySelection->setEnabled(false);
+ m_ui->formatCannotBeChanged->setVisible(true);
+ }
+
m_isDirty = isDirty;
}
@@ -143,13 +151,15 @@ void DatabaseSettingsWidgetEncryption::setupAlgorithmComboBox()
}
}
-void DatabaseSettingsWidgetEncryption::setupKdfComboBox()
+void DatabaseSettingsWidgetEncryption::setupKdfComboBox(bool enableKdbx3)
{
- // Setup kdf combo box
+ // Set up kdf combo box
bool block = m_ui->kdfComboBox->blockSignals(true);
m_ui->kdfComboBox->clear();
for (auto& kdf : asConst(KeePass2::KDFS)) {
- m_ui->kdfComboBox->addItem(kdf.second.toUtf8(), kdf.first.toByteArray());
+ if (kdf.first != KeePass2::KDF_AES_KDBX3 or enableKdbx3) {
+ m_ui->kdfComboBox->addItem(kdf.second.toUtf8(), kdf.first.toByteArray());
+ }
}
m_ui->kdfComboBox->blockSignals(block);
}
@@ -393,8 +403,8 @@ void DatabaseSettingsWidgetEncryption::updateFormatCompatibility(int index, bool
m_ui->compatibilitySelection->blockSignals(block);
}
+ QUuid kdfUuid(m_ui->compatibilitySelection->itemData(index).toByteArray());
if (retransform) {
- QUuid kdfUuid(m_ui->compatibilitySelection->itemData(index).toByteArray());
auto kdf = KeePass2::uuidToKdf(kdfUuid);
m_db->setKdf(kdf);
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h
index 2c7b5bac9f..c3d7ccf749 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h
+++ b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.h
@@ -61,7 +61,7 @@ private slots:
void updateDecryptionTime(int value);
void updateFormatCompatibility(int index, bool retransform = true);
void setupAlgorithmComboBox();
- void setupKdfComboBox();
+ void setupKdfComboBox(bool enableKdbx3);
void loadKdfParameters();
void updateKdfFields();
void activateChangeDecryptionTime();
diff --git a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.ui b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.ui
index 97da37475e..2b8598862e 100644
--- a/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.ui
+++ b/src/gui/dbsettings/DatabaseSettingsWidgetEncryption.ui
@@ -183,6 +183,9 @@
-
+
+ 2
+
-
@@ -203,12 +206,24 @@
+ -
+
+
+
+ true
+
+
+
+ Format cannot be changed: Your database uses KDBX 4 features
+
+
+
-
- This is only important if you need to use your database with other programs.
+ Unless you need to open your database with other programs, always use the latest format.
diff --git a/tests/TestKdbx2.cpp b/tests/TestKdbx2.cpp
index bf22be375a..c0da4d4a39 100644
--- a/tests/TestKdbx2.cpp
+++ b/tests/TestKdbx2.cpp
@@ -87,7 +87,7 @@ void TestKdbx2::testFormat200Upgrade()
reader.readDatabase(filename, key, db.data());
QVERIFY2(!reader.hasError(), reader.errorString().toStdString().c_str());
QVERIFY(!db.isNull());
- QCOMPARE(reader.version(), KeePass2::FILE_VERSION_2 & KeePass2::FILE_VERSION_CRITICAL_MASK);
+ QCOMPARE(reader.version(), KeePass2::FILE_VERSION_2);
QCOMPARE(db->kdf()->uuid(), KeePass2::KDF_AES_KDBX3);
QBuffer buffer;
@@ -110,6 +110,6 @@ void TestKdbx2::testFormat200Upgrade()
// database should now be upgraded to KDBX 3 without data loss
verifyKdbx2Db(targetDb);
- QCOMPARE(reader.version(), KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK);
+ QCOMPARE(reader.version(), KeePass2::FILE_VERSION_3_1);
QCOMPARE(targetDb->kdf()->uuid(), KeePass2::KDF_AES_KDBX3);
}
diff --git a/tests/TestKdbx3.cpp b/tests/TestKdbx3.cpp
index 27fa70c11b..bab8ab8dc5 100644
--- a/tests/TestKdbx3.cpp
+++ b/tests/TestKdbx3.cpp
@@ -75,22 +75,7 @@ void TestKdbx3::readKdbx(QIODevice* device,
if (hasError) {
errorString = reader.errorString();
}
- QCOMPARE(reader.version(), KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK);
-}
-
-void TestKdbx3::readKdbx(const QString& path,
- QSharedPointer key,
- QSharedPointer db,
- bool& hasError,
- QString& errorString)
-{
- KeePass2Reader reader;
- reader.readDatabase(path, key, db.data());
- hasError = reader.hasError();
- if (hasError) {
- errorString = reader.errorString();
- }
- QCOMPARE(reader.version(), KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK);
+ QCOMPARE(reader.version(), KeePass2::FILE_VERSION_3_1);
}
void TestKdbx3::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString)
diff --git a/tests/TestKdbx3.h b/tests/TestKdbx3.h
index ca571fbc88..deb965d903 100644
--- a/tests/TestKdbx3.h
+++ b/tests/TestKdbx3.h
@@ -44,11 +44,6 @@ private slots:
QSharedPointer db,
bool& hasError,
QString& errorString) override;
- void readKdbx(const QString& path,
- QSharedPointer key,
- QSharedPointer db,
- bool& hasError,
- QString& errorString) override;
void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) override;
};
diff --git a/tests/TestKdbx4.cpp b/tests/TestKdbx4.cpp
index 4521140dc4..9bd210ca50 100644
--- a/tests/TestKdbx4.cpp
+++ b/tests/TestKdbx4.cpp
@@ -92,21 +92,6 @@ void TestKdbx4Argon2::readKdbx(QIODevice* device,
QCOMPARE(reader.version(), KeePass2::FILE_VERSION_4);
}
-void TestKdbx4Argon2::readKdbx(const QString& path,
- QSharedPointer key,
- QSharedPointer db,
- bool& hasError,
- QString& errorString)
-{
- KeePass2Reader reader;
- reader.readDatabase(path, key, db.data());
- hasError = reader.hasError();
- if (hasError) {
- errorString = reader.errorString();
- }
- QCOMPARE(reader.version(), KeePass2::FILE_VERSION_4);
-}
-
void TestKdbx4Argon2::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString)
{
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) {
@@ -218,8 +203,8 @@ void TestKdbx4Format::testFormat400Upgrade_data()
QTest::addColumn("addCustomData");
QTest::addColumn("expectedVersion");
- 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 kdbx3 = KeePass2::FILE_VERSION_3_1;
+ auto constexpr kdbx4 = KeePass2::FILE_VERSION_4;
QTest::newRow("Argon2d + AES") << KeePass2::KDF_ARGON2D << KeePass2::CIPHER_AES256 << false << kdbx4;
QTest::newRow("Argon2id + AES") << KeePass2::KDF_ARGON2ID << KeePass2::CIPHER_AES256 << false << kdbx4;
@@ -255,7 +240,7 @@ void TestKdbx4Format::testFormat410Upgrade()
Database db;
db.changeKdf(fastKdf(db.kdf()));
QCOMPARE(db.kdf()->uuid(), KeePass2::KDF_AES_KDBX3);
- QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_3_1);
auto group1 = new Group();
group1->setUuid(QUuid::createUuid());
@@ -271,44 +256,45 @@ void TestKdbx4Format::testFormat410Upgrade()
// Groups with tags
group1->setTags("tag");
- QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_4_1);
group1->setTags("");
- QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_3_1);
// PasswordQuality flag set
entry->setExcludeFromReports(true);
- QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_4_1);
entry->setExcludeFromReports(false);
- QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_3_1);
// Previous parent group set on group
group1->setPreviousParentGroup(group2);
QCOMPARE(group1->previousParentGroup(), group2);
- QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_4_1);
group1->setPreviousParentGroup(nullptr);
- QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_3_1);
// Previous parent group set on entry
entry->setPreviousParentGroup(group2);
QCOMPARE(entry->previousParentGroup(), group2);
- QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_4_1);
entry->setPreviousParentGroup(nullptr);
- QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_3_1);
// Custom icons with name or modification date
Metadata::CustomIconData customIcon;
auto iconUuid = QUuid::createUuid();
db.metadata()->addCustomIcon(iconUuid, customIcon);
- QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_3_1);
customIcon.name = "abc";
db.metadata()->removeCustomIcon(iconUuid);
db.metadata()->addCustomIcon(iconUuid, customIcon);
- QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_4_1);
customIcon.name.clear();
customIcon.lastModified = Clock::currentDateTimeUtc();
db.metadata()->removeCustomIcon(iconUuid);
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_3_1);
db.metadata()->addCustomIcon(iconUuid, customIcon);
- QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
+ QCOMPARE(KeePass2Writer::needsKdbxVersion(&db), KeePass2::FILE_VERSION_4_1);
}
void TestKdbx4Format::testUpgradeMasterKeyIntegrity()
@@ -394,8 +380,8 @@ void TestKdbx4Format::testUpgradeMasterKeyIntegrity()
if (reader.hasError()) {
QFAIL(qPrintable(reader.errorString()));
}
- QCOMPARE(reader.version(), expectedVersion & KeePass2::FILE_VERSION_CRITICAL_MASK);
- if (expectedVersion != KeePass2::FILE_VERSION_3) {
+ QCOMPARE(reader.version(), expectedVersion);
+ if (expectedVersion >= KeePass2::FILE_VERSION_4) {
QVERIFY(db2->kdf()->uuid() != KeePass2::KDF_AES_KDBX3);
}
}
@@ -405,9 +391,9 @@ void TestKdbx4Format::testUpgradeMasterKeyIntegrity_data()
QTest::addColumn("upgradeAction");
QTest::addColumn("expectedVersion");
- QTest::newRow("Upgrade: none") << QString("none") << KeePass2::FILE_VERSION_3;
- QTest::newRow("Upgrade: none (meta-customdata)") << QString("meta-customdata") << KeePass2::FILE_VERSION_3;
- QTest::newRow("Upgrade: none (explicit kdf-aes-kdbx3)") << QString("kdf-aes-kdbx3") << KeePass2::FILE_VERSION_3;
+ QTest::newRow("Upgrade: none") << QString("none") << KeePass2::FILE_VERSION_3_1;
+ QTest::newRow("Upgrade: none (meta-customdata)") << QString("meta-customdata") << KeePass2::FILE_VERSION_3_1;
+ QTest::newRow("Upgrade: none (explicit kdf-aes-kdbx3)") << QString("kdf-aes-kdbx3") << KeePass2::FILE_VERSION_3_1;
QTest::newRow("Upgrade (explicit): kdf-argon2") << QString("kdf-argon2") << KeePass2::FILE_VERSION_4;
QTest::newRow("Upgrade (explicit): kdf-aes-kdbx4") << QString("kdf-aes-kdbx4") << KeePass2::FILE_VERSION_4;
QTest::newRow("Upgrade (implicit): public-customdata") << QString("public-customdata") << KeePass2::FILE_VERSION_4;
diff --git a/tests/TestKdbx4.h b/tests/TestKdbx4.h
index 0eddef6c0d..5d7f6cc500 100644
--- a/tests/TestKdbx4.h
+++ b/tests/TestKdbx4.h
@@ -32,11 +32,6 @@ class TestKdbx4Argon2 : public TestKeePass2Format
readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) override;
void writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override;
- void readKdbx(const QString& path,
- QSharedPointer key,
- QSharedPointer db,
- bool& hasError,
- QString& errorString) override;
void readKdbx(QIODevice* device,
QSharedPointer key,
QSharedPointer db,
diff --git a/tests/TestKeePass2Format.h b/tests/TestKeePass2Format.h
index 759172ce61..140739c031 100644
--- a/tests/TestKeePass2Format.h
+++ b/tests/TestKeePass2Format.h
@@ -76,11 +76,6 @@ private slots:
QSharedPointer db,
bool& hasError,
QString& errorString) = 0;
- virtual void readKdbx(const QString& path,
- QSharedPointer key,
- QSharedPointer db,
- bool& hasError,
- QString& errorString) = 0;
virtual void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) = 0;
QSharedPointer m_xmlDb;