Correctly set KDBX envelope version

Shows a warning when trying to open with a newer minor version than what is currently supported.

We always try to save with the lowest KDBX version possible for maximum compatibility.
This commit is contained in:
Janek Bevendorff 2021-11-20 00:32:09 +01:00
parent 67603ab42e
commit a3dc977e58
30 changed files with 209 additions and 162 deletions

Binary file not shown.

View File

@ -1535,6 +1535,28 @@ If you do not have a key file, please leave the field empty.</source>
<source>Please present or touch your YubiKey to continue</source> <source>Please present or touch your YubiKey to continue</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Database Version Mismatch</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>The database you are trying to open was most likely
created by a newer version of KeePassXC.
You can try to open it anyway, but it may be incomplete
and saving any changes may incur data loss.
We recommend you update your KeePassXC installation.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Open database anyway</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Database unlock canceled.</source>
<translation type="unfinished"></translation>
</message>
</context> </context>
<context> <context>
<name>DatabaseSettingWidgetMetaData</name> <name>DatabaseSettingWidgetMetaData</name>
@ -1808,18 +1830,6 @@ Are you sure you want to continue without a password?</translation>
<source>Database format:</source> <source>Database format:</source>
<translation>Database format:</translation> <translation>Database format:</translation>
</message> </message>
<message>
<source>This is only important if you need to use your database with other programs.</source>
<translation>This is only important if you need to use your database with other programs.</translation>
</message>
<message>
<source>KDBX 4.0 (recommended)</source>
<translation>KDBX 4.0 (recommended)</translation>
</message>
<message>
<source>KDBX 3.1</source>
<translation>KDBX 3.1</translation>
</message>
<message> <message>
<source>unchanged</source> <source>unchanged</source>
<comment>Database decryption time is unchanged</comment> <comment>Database decryption time is unchanged</comment>
@ -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.</source> If you keep this number, your database will not be protected from brute force attacks.</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>Format cannot be changed: Your database uses KDBX 4 features</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>Unless you need to open your database with other programs, always use the latest format.</source>
<translation type="unfinished"></translation>
</message>
<message>
<source>KDBX 4 (recommended)</source>
<translation type="unfinished">KDBX 4.0 (recommended) {4 ?}</translation>
</message>
<message>
<source>KDBX 3</source>
<translation type="unfinished">KDBX 3</translation>
</message>
</context> </context>
<context> <context>
<name>DatabaseSettingsWidgetFdoSecrets</name> <name>DatabaseSettingsWidgetFdoSecrets</name>
@ -6523,10 +6549,6 @@ Available commands:
<source>AES-KDF (KDBX 4)</source> <source>AES-KDF (KDBX 4)</source>
<translation>AES-KDF (KDBX 4)</translation> <translation>AES-KDF (KDBX 4)</translation>
</message> </message>
<message>
<source>AES-KDF (KDBX 3.1)</source>
<translation>AES-KDF (KDBX 3.1)</translation>
</message>
<message> <message>
<source>Invalid Settings</source> <source>Invalid Settings</source>
<comment>TOTP</comment> <comment>TOTP</comment>
@ -7498,6 +7520,10 @@ Please consider generating a new key file.</source>
<source>Attachments:</source> <source>Attachments:</source>
<translation type="unfinished"></translation> <translation type="unfinished"></translation>
</message> </message>
<message>
<source>AES-KDF (KDBX 3)</source>
<translation type="unfinished">AES-KDF (KDBX 3.1) {3)?}</translation>
</message>
</context> </context>
<context> <context>
<name>QtIOCompressor</name> <name>QtIOCompressor</name>

View File

@ -163,6 +163,27 @@ bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey>
return true; return true;
} }
/**
* KDBX format version.
*/
quint32 Database::formatVersion() const
{
return m_data.formatVersion;
}
void Database::setFormatVersion(quint32 version)
{
m_data.formatVersion = version;
}
/**
* Whether the KDBX minor version is greater than the newest supported.
*/
bool Database::hasMinorVersionMismatch() const
{
return m_data.formatVersion > KeePass2::FILE_VERSION_MAX;
}
bool Database::isSaving() bool Database::isSaving()
{ {
bool locked = m_saveMutex.tryLock(); bool locked = m_saveMutex.tryLock();
@ -935,6 +956,7 @@ void Database::setKdf(QSharedPointer<Kdf> kdf)
{ {
Q_ASSERT(!m_data.isReadOnly); Q_ASSERT(!m_data.isReadOnly);
m_data.kdf = std::move(kdf); m_data.kdf = std::move(kdf);
setFormatVersion(KeePass2Writer::kdbxVersionRequired(this, true, m_data.kdf.isNull()));
} }
bool Database::changeKdf(const QSharedPointer<Kdf>& kdf) bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)

View File

@ -87,6 +87,10 @@ public:
bool extract(QByteArray&, QString* error = nullptr); bool extract(QByteArray&, QString* error = nullptr);
bool import(const QString& xmlExportPath, QString* error = nullptr); bool import(const QString& xmlExportPath, QString* error = nullptr);
quint32 formatVersion() const;
void setFormatVersion(quint32 version);
bool hasMinorVersionMismatch() const;
void releaseData(); void releaseData();
bool isInitialized() const; bool isInitialized() const;
@ -166,6 +170,7 @@ signals:
private: private:
struct DatabaseData struct DatabaseData
{ {
quint32 formatVersion = 0;
QString filePath; QString filePath;
bool isReadOnly = false; bool isReadOnly = false;
QUuid cipher = KeePass2::CIPHER_AES256; QUuid cipher = KeePass2::CIPHER_AES256;

View File

@ -34,7 +34,7 @@ bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
QSharedPointer<const CompositeKey> key, QSharedPointer<const CompositeKey> key,
Database* db) 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()) { if (hasError()) {
return false; return false;
@ -120,7 +120,7 @@ bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
return false; 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()) { if (!xmlReader.headerHash().isEmpty()) {
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256); QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);

View File

@ -68,7 +68,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
QBuffer header; QBuffer header;
header.open(QIODevice::WriteOnly); 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<quint16>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122())); CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
CHECK_RETURN_FALSE( CHECK_RETURN_FALSE(
@ -137,7 +137,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
return false; return false;
} }
KdbxXmlWriter xmlWriter(formatVersion()); KdbxXmlWriter xmlWriter(db->formatVersion());
xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash); xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash);
// Explicitly close/reset streams so they are flushed and we can detect // 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; return true;
} }
quint32 Kdbx3Writer::formatVersion()
{
return KeePass2::FILE_VERSION_3_1;
}

View File

@ -29,7 +29,6 @@ class Kdbx3Writer : public KdbxWriter
public: public:
bool writeDatabase(QIODevice* device, Database* db) override; bool writeDatabase(QIODevice* device, Database* db) override;
quint32 formatVersion() override;
}; };
#endif // KEEPASSX_KDBX3WRITER_H #endif // KEEPASSX_KDBX3WRITER_H

View File

@ -36,7 +36,7 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
QSharedPointer<const CompositeKey> key, QSharedPointer<const CompositeKey> key,
Database* db) 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(); m_binaryPool.clear();

View File

@ -66,7 +66,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
QBuffer header; QBuffer header;
header.open(QIODevice::WriteOnly); 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( CHECK_RETURN_FALSE(
writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122())); writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
@ -166,7 +166,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
return false; return false;
} }
KdbxXmlWriter xmlWriter(formatVersion()); KdbxXmlWriter xmlWriter(db->formatVersion());
xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash); xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash);
// Explicitly close/reset streams so they are flushed and we can detect // 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); CHECK_RETURN_FALSE(buf.write(endBytes) == 1);
return true; return true;
} }
quint32 Kdbx4Writer::formatVersion()
{
return KeePass2::FILE_VERSION_4;
}

View File

@ -29,7 +29,6 @@ class Kdbx4Writer : public KdbxWriter
public: public:
bool writeDatabase(QIODevice* device, Database* db) override; bool writeDatabase(QIODevice* device, Database* db) override;
quint32 formatVersion() override;
private: private:
bool writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data); bool writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data);

View File

@ -74,14 +74,12 @@ bool KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const CompositeK
headerStream.open(QIODevice::ReadOnly); headerStream.open(QIODevice::ReadOnly);
// read KDBX magic numbers // read KDBX magic numbers
quint32 sig1, sig2; quint32 sig1, sig2, version;
if (!readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion)) { if (!readMagicNumbers(&headerStream, sig1, sig2, version)) {
return false; return false;
} }
m_kdbxSignature = qMakePair(sig1, sig2); m_kdbxSignature = qMakePair(sig1, sig2);
m_db->setFormatVersion(version);
// mask out minor version
m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK;
// read header fields // read header fields
while (readHeaderField(headerStream, m_db) && !hasError()) { while (readHeaderField(headerStream, m_db) && !hasError()) {

View File

@ -83,8 +83,6 @@ protected:
void raiseError(const QString& errorMessage); void raiseError(const QString& errorMessage);
quint32 m_kdbxVersion = 0;
QByteArray m_masterSeed; QByteArray m_masterSeed;
QByteArray m_encryptionIV; QByteArray m_encryptionIV;
QByteArray m_streamStartBytes; QByteArray m_streamStartBytes;

View File

@ -71,7 +71,7 @@ void KdbxWriter::extractDatabase(QByteArray& xmlOutput, Database* db)
QBuffer buffer; QBuffer buffer;
buffer.setBuffer(&xmlOutput); buffer.setBuffer(&xmlOutput);
buffer.open(QIODevice::WriteOnly); buffer.open(QIODevice::WriteOnly);
KdbxXmlWriter writer(formatVersion()); KdbxXmlWriter writer(db->formatVersion());
writer.disableInnerStreamProtection(true); writer.disableInnerStreamProtection(true);
writer.writeDatabase(&buffer, db); writer.writeDatabase(&buffer, db);
} }

View File

@ -52,11 +52,6 @@ public:
*/ */
virtual bool writeDatabase(QIODevice* device, Database* db) = 0; 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); void extractDatabase(QByteArray& xmlOutput, Database* db);
bool hasError() const; bool hasError() const;

View File

@ -166,7 +166,7 @@ void KdbxXmlWriter::writeIcon(const QUuid& uuid, const Metadata::CustomIconData&
m_xml.writeStartElement("Icon"); m_xml.writeStartElement("Icon");
writeUuid("UUID", uuid); writeUuid("UUID", uuid);
if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) { if (m_kdbxVersion >= KeePass2::FILE_VERSION_4_1) {
if (!iconData.name.isEmpty()) { if (!iconData.name.isEmpty()) {
writeString("Name", iconData.name); writeString("Name", iconData.name);
} }
@ -243,7 +243,7 @@ void KdbxXmlWriter::writeCustomDataItem(const QString& key,
writeString("Key", key); writeString("Key", key);
writeString("Value", item.value); 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); writeDateTime("LastModificationTime", item.lastModified);
} }
@ -291,9 +291,9 @@ void KdbxXmlWriter::writeGroup(const Group* group)
if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) { if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
writeCustomData(group->customData()); 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<Entry*>& entryList = group->entries(); const QList<Entry*>& entryList = group->entries();
@ -363,7 +363,7 @@ void KdbxXmlWriter::writeEntry(const Entry* entry)
writeString("Tags", entry->tags()); writeString("Tags", entry->tags());
writeTimes(entry->timeInfo()); writeTimes(entry->timeInfo());
if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) { if (m_kdbxVersion >= KeePass2::FILE_VERSION_4_1) {
if (entry->excludeFromReports()) { if (entry->excludeFromReports()) {
writeBool("QualityCheck", false); writeBool("QualityCheck", false);
} }

View File

@ -56,7 +56,7 @@ const QList<QPair<QUuid, QString>> KeePass2::KDFS{
qMakePair(KeePass2::KDF_ARGON2D, QObject::tr("Argon2d (KDBX 4 recommended)")), qMakePair(KeePass2::KDF_ARGON2D, QObject::tr("Argon2d (KDBX 4 recommended)")),
qMakePair(KeePass2::KDF_ARGON2ID, QObject::tr("Argon2id (KDBX 4)")), qMakePair(KeePass2::KDF_ARGON2ID, QObject::tr("Argon2id (KDBX 4)")),
qMakePair(KeePass2::KDF_AES_KDBX4, QObject::tr("AES-KDF (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) QByteArray KeePass2::hmacKey(const QByteArray& masterSeed, const QByteArray& transformedMasterKey)
{ {

View File

@ -31,16 +31,18 @@ namespace KeePass2
constexpr quint32 SIGNATURE_2 = 0xB54BFB67; constexpr quint32 SIGNATURE_2 = 0xB54BFB67;
constexpr quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000; constexpr quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000;
constexpr quint32 FILE_VERSION_4_1 = 0x00040001;
constexpr quint32 FILE_VERSION_4 = 0x00040000; constexpr quint32 FILE_VERSION_4 = 0x00040000;
constexpr quint32 FILE_VERSION_3_1 = 0x00030001; constexpr quint32 FILE_VERSION_3_1 = 0x00030001;
constexpr quint32 FILE_VERSION_3 = 0x00030000; constexpr quint32 FILE_VERSION_3 = 0x00030000;
constexpr quint32 FILE_VERSION_2 = 0x00020000; constexpr quint32 FILE_VERSION_2 = 0x00020000;
constexpr quint32 FILE_VERSION_MIN = FILE_VERSION_2; 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_VERSION = 0x0100;
constexpr quint16 VARIANTMAP_CRITICAL_MASK = 0xFF00; 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_AES128;
extern const QUuid CIPHER_AES256; extern const QUuid CIPHER_AES256;

View File

@ -81,11 +81,8 @@ bool KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const Compos
return false; return false;
} }
// mask out minor version if (m_version < KeePass2::FILE_VERSION_MIN
m_version &= KeePass2::FILE_VERSION_CRITICAL_MASK; || (m_version & KeePass2::FILE_VERSION_CRITICAL_MASK) > KeePass2::FILE_VERSION_MAX) {
quint32 maxVersion = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
if (m_version < KeePass2::FILE_VERSION_MIN || m_version > maxVersion) {
raiseError(tr("Unsupported KeePass 2 database version.")); raiseError(tr("Unsupported KeePass 2 database version."));
return false; return false;
} }

View File

@ -40,44 +40,57 @@ bool KeePass2Writer::writeDatabase(const QString& filename, Database* db)
return writeDatabase(&file, 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::kdbxVersionRequired(Database const* db, bool ignoreCurrent, bool ignoreKdf)
{ {
if (db->kdf()->uuid() != KeePass2::KDF_AES_KDBX3) { quint32 version = KeePass2::FILE_VERSION_3_1;
return false; 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()) { if (!db->publicCustomData().isEmpty()) {
return true; VERSION_MAX(version, KeePass2::FILE_VERSION_4)
} }
for (const auto& group : db->rootGroup()->groupsRecursive(true)) { for (const auto* group : db->rootGroup()->groupsRecursive(true)) {
if (group->customData() && !group->customData()->isEmpty()) { if (group->customData() && !group->customData()->isEmpty()) {
return true; VERSION_MAX(version, KeePass2::FILE_VERSION_4)
} }
if (!group->tags().isEmpty()) { if (!group->tags().isEmpty()) {
return true; VERSION_MAX(version, KeePass2::FILE_VERSION_4_1)
} }
if (group->previousParentGroup()) { if (group->previousParentGroup()) {
return true; VERSION_MAX(version, KeePass2::FILE_VERSION_4_1)
} }
for (const auto& entry : group->entries()) { for (const auto* entry : group->entries()) {
if (entry->customData() && !entry->customData()->isEmpty()) { if (entry->customData() && !entry->customData()->isEmpty()) {
return true; VERSION_MAX(version, KeePass2::FILE_VERSION_4)
} }
if (entry->excludeFromReports()) { if (entry->excludeFromReports()) {
return true; VERSION_MAX(version, KeePass2::FILE_VERSION_4_1)
} }
if (entry->previousParentGroup()) { if (entry->previousParentGroup()) {
return true; VERSION_MAX(version, KeePass2::FILE_VERSION_4_1)
} }
for (const auto& historyItem : entry->historyItems()) { for (const auto* historyItem : entry->historyItems()) {
if (historyItem->customData() && !historyItem->customData()->isEmpty()) { if (historyItem->customData() && !historyItem->customData()->isEmpty()) {
return true; VERSION_MAX(version, KeePass2::FILE_VERSION_4)
} }
} }
} }
@ -87,11 +100,11 @@ bool KeePass2Writer::implicitKDBXUpgradeNeeded(Database const* db)
for (const QUuid& uuid : customIconsOrder) { for (const QUuid& uuid : customIconsOrder) {
const auto& icon = db->metadata()->customIcon(uuid); const auto& icon = db->metadata()->customIcon(uuid);
if (!icon.name.isEmpty() || icon.lastModified.isValid()) { if (!icon.name.isEmpty() || icon.lastModified.isValid()) {
return true; VERSION_MAX(version, KeePass2::FILE_VERSION_4_1)
} }
} }
return false; return version;
} }
/** /**
@ -106,8 +119,8 @@ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
m_error = false; m_error = false;
m_errorStr.clear(); m_errorStr.clear();
bool upgradeNeeded = implicitKDBXUpgradeNeeded(db); m_version = kdbxVersionRequired(db);
if (upgradeNeeded) { 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. // 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! // 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); auto kdf = KeePass2::uuidToKdf(KeePass2::KDF_AES_KDBX4);
@ -115,12 +128,12 @@ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
db->changeKdf(kdf); db->changeKdf(kdf);
} }
db->setFormatVersion(m_version);
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) { if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) {
Q_ASSERT(!upgradeNeeded); Q_ASSERT(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 {
m_version = KeePass2::FILE_VERSION_4; Q_ASSERT(m_version >= KeePass2::FILE_VERSION_4);
m_writer.reset(new Kdbx4Writer()); m_writer.reset(new Kdbx4Writer());
} }
@ -132,11 +145,13 @@ void KeePass2Writer::extractDatabase(Database* db, QByteArray& xmlOutput)
m_error = false; m_error = false;
m_errorStr.clear(); m_errorStr.clear();
m_version = kdbxVersionRequired(db);
db->setFormatVersion(m_version);
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) { 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()); m_writer.reset(new Kdbx3Writer());
} else { } else {
m_version = KeePass2::FILE_VERSION_4; Q_ASSERT(m_version >= KeePass2::FILE_VERSION_4);
m_writer.reset(new Kdbx4Writer()); m_writer.reset(new Kdbx4Writer());
} }

View File

@ -33,7 +33,7 @@ public:
bool writeDatabase(const QString& filename, Database* db); bool writeDatabase(const QString& filename, Database* db);
bool writeDatabase(QIODevice* device, Database* db); bool writeDatabase(QIODevice* device, Database* db);
void extractDatabase(Database* db, QByteArray& xmlOutput); void extractDatabase(Database* db, QByteArray& xmlOutput);
static bool implicitKDBXUpgradeNeeded(Database const* db); static quint32 kdbxVersionRequired(Database const* db, bool ignoreCurrent = false, bool ignoreKdf = false);
QSharedPointer<KdbxWriter> writer() const; QSharedPointer<KdbxWriter> writer() const;
quint32 version() const; quint32 version() const;

View File

@ -206,6 +206,26 @@ void DatabaseOpenWidget::openDatabase()
QApplication::restoreOverrideCursor(); QApplication::restoreOverrideCursor();
m_ui->passwordFormFrame->setEnabled(true); m_ui->passwordFormFrame->setEnabled(true);
if (ok && m_db->hasMinorVersionMismatch()) {
QScopedPointer<QMessageBox> 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 (ok) { if (ok) {
#ifdef WITH_XC_TOUCHID #ifdef WITH_XC_TOUCHID
QHash<QString, QVariant> useTouchID = config()->get(Config::UseTouchID).toHash(); QHash<QString, QVariant> useTouchID = config()->get(Config::UseTouchID).toHash();

View File

@ -24,6 +24,7 @@
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/kdf/Argon2Kdf.h" #include "crypto/kdf/Argon2Kdf.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
#include "format/KeePass2Writer.h"
#include "gui/MessageBox.h" #include "gui/MessageBox.h"
const char* DatabaseSettingsWidgetEncryption::CD_DECRYPTION_TIME_PREFERENCE_KEY = "KPXC_DECRYPTION_TIME_PREFERENCE"; 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->transformBenchmarkButton, SIGNAL(clicked()), SLOT(benchmarkTransformRounds()));
connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeKdf(int))); 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->memorySpinBox, SIGNAL(valueChanged(int)), this, SLOT(memoryChanged(int)));
connect(m_ui->parallelismSpinBox, SIGNAL(valueChanged(int)), this, SLOT(parallelismChanged(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 4 (recommended)"), KeePass2::KDF_ARGON2D.toByteArray());
m_ui->compatibilitySelection->addItem(tr("KDBX 3.1"), KeePass2::KDF_AES_KDBX3.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->setMinimum(Kdf::MIN_ENCRYPTION_TIME / 100);
m_ui->decryptionTimeSlider->setMaximum(Kdf::MAX_ENCRYPTION_TIME / 100); m_ui->decryptionTimeSlider->setMaximum(Kdf::MAX_ENCRYPTION_TIME / 100);
m_ui->decryptionTimeSlider->setValue(Kdf::DEFAULT_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); m_db->setCipher(KeePass2::CIPHER_AES256);
isDirty = true; isDirty = true;
} }
bool kdbx3Enabled = KeePass2Writer::kdbxVersionRequired(m_db.data(), true, true) <= KeePass2::FILE_VERSION_3_1;
// check if the DB's custom data has a decryption time setting stored // 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 // 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); updateFormatCompatibility(m_db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 ? KDBX3 : KDBX4, isDirty);
setupAlgorithmComboBox(); setupAlgorithmComboBox();
setupKdfComboBox(); setupKdfComboBox(kdbx3Enabled);
loadKdfParameters(); loadKdfParameters();
if (!kdbx3Enabled) {
m_ui->compatibilitySelection->setEnabled(false);
m_ui->formatCannotBeChanged->setVisible(true);
}
m_isDirty = isDirty; 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); bool block = m_ui->kdfComboBox->blockSignals(true);
m_ui->kdfComboBox->clear(); m_ui->kdfComboBox->clear();
for (auto& kdf : asConst(KeePass2::KDFS)) { 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); m_ui->kdfComboBox->blockSignals(block);
} }
@ -393,8 +403,8 @@ void DatabaseSettingsWidgetEncryption::updateFormatCompatibility(int index, bool
m_ui->compatibilitySelection->blockSignals(block); m_ui->compatibilitySelection->blockSignals(block);
} }
QUuid kdfUuid(m_ui->compatibilitySelection->itemData(index).toByteArray());
if (retransform) { if (retransform) {
QUuid kdfUuid(m_ui->compatibilitySelection->itemData(index).toByteArray());
auto kdf = KeePass2::uuidToKdf(kdfUuid); auto kdf = KeePass2::uuidToKdf(kdfUuid);
m_db->setKdf(kdf); m_db->setKdf(kdf);

View File

@ -61,7 +61,7 @@ private slots:
void updateDecryptionTime(int value); void updateDecryptionTime(int value);
void updateFormatCompatibility(int index, bool retransform = true); void updateFormatCompatibility(int index, bool retransform = true);
void setupAlgorithmComboBox(); void setupAlgorithmComboBox();
void setupKdfComboBox(); void setupKdfComboBox(bool enableKdbx3);
void loadKdfParameters(); void loadKdfParameters();
void updateKdfFields(); void updateKdfFields();
void activateChangeDecryptionTime(); void activateChangeDecryptionTime();

View File

@ -183,6 +183,9 @@
</item> </item>
<item> <item>
<layout class="QFormLayout" name="formLayout"> <layout class="QFormLayout" name="formLayout">
<property name="verticalSpacing">
<number>2</number>
</property>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="compatibilityLabel"> <widget class="QLabel" name="compatibilityLabel">
<property name="text"> <property name="text">
@ -203,12 +206,24 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1">
<widget class="QLabel" name="formatCannotBeChanged">
<property name="font">
<font>
<italic>true</italic>
</font>
</property>
<property name="text">
<string>Format cannot be changed: Your database uses KDBX 4 features</string>
</property>
</widget>
</item>
</layout> </layout>
</item> </item>
<item> <item>
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>This is only important if you need to use your database with other programs.</string> <string>Unless you need to open your database with other programs, always use the latest format.</string>
</property> </property>
</widget> </widget>
</item> </item>

View File

@ -87,7 +87,7 @@ void TestKdbx2::testFormat200Upgrade()
reader.readDatabase(filename, key, db.data()); reader.readDatabase(filename, key, db.data());
QVERIFY2(!reader.hasError(), reader.errorString().toStdString().c_str()); QVERIFY2(!reader.hasError(), reader.errorString().toStdString().c_str());
QVERIFY(!db.isNull()); 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); QCOMPARE(db->kdf()->uuid(), KeePass2::KDF_AES_KDBX3);
QBuffer buffer; QBuffer buffer;
@ -110,6 +110,6 @@ void TestKdbx2::testFormat200Upgrade()
// database should now be upgraded to KDBX 3 without data loss // database should now be upgraded to KDBX 3 without data loss
verifyKdbx2Db(targetDb); 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); QCOMPARE(targetDb->kdf()->uuid(), KeePass2::KDF_AES_KDBX3);
} }

View File

@ -75,22 +75,7 @@ void TestKdbx3::readKdbx(QIODevice* device,
if (hasError) { if (hasError) {
errorString = reader.errorString(); 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::readKdbx(const QString& path,
QSharedPointer<const CompositeKey> key,
QSharedPointer<Database> 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);
} }
void TestKdbx3::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) void TestKdbx3::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString)

View File

@ -44,11 +44,6 @@ protected:
QSharedPointer<Database> db, QSharedPointer<Database> db,
bool& hasError, bool& hasError,
QString& errorString) override; QString& errorString) override;
void readKdbx(const QString& path,
QSharedPointer<const CompositeKey> key,
QSharedPointer<Database> db,
bool& hasError,
QString& errorString) override;
void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) override; void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) override;
}; };

View File

@ -92,21 +92,6 @@ void TestKdbx4Argon2::readKdbx(QIODevice* device,
QCOMPARE(reader.version(), KeePass2::FILE_VERSION_4); QCOMPARE(reader.version(), KeePass2::FILE_VERSION_4);
} }
void TestKdbx4Argon2::readKdbx(const QString& path,
QSharedPointer<const CompositeKey> key,
QSharedPointer<Database> 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) void TestKdbx4Argon2::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString)
{ {
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) { if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) {
@ -218,8 +203,8 @@ void TestKdbx4Format::testFormat400Upgrade_data()
QTest::addColumn<bool>("addCustomData"); 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;
auto constexpr kdbx4 = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK; auto constexpr kdbx4 = KeePass2::FILE_VERSION_4;
QTest::newRow("Argon2d + AES") << KeePass2::KDF_ARGON2D << KeePass2::CIPHER_AES256 << false << kdbx4; QTest::newRow("Argon2d + AES") << KeePass2::KDF_ARGON2D << KeePass2::CIPHER_AES256 << false << kdbx4;
QTest::newRow("Argon2id + AES") << KeePass2::KDF_ARGON2ID << 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; Database db;
db.changeKdf(fastKdf(db.kdf())); db.changeKdf(fastKdf(db.kdf()));
QCOMPARE(db.kdf()->uuid(), KeePass2::KDF_AES_KDBX3); QCOMPARE(db.kdf()->uuid(), KeePass2::KDF_AES_KDBX3);
QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_3_1);
auto group1 = new Group(); auto group1 = new Group();
group1->setUuid(QUuid::createUuid()); group1->setUuid(QUuid::createUuid());
@ -271,44 +256,45 @@ void TestKdbx4Format::testFormat410Upgrade()
// Groups with tags // Groups with tags
group1->setTags("tag"); group1->setTags("tag");
QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
group1->setTags(""); group1->setTags("");
QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_3_1);
// PasswordQuality flag set // PasswordQuality flag set
entry->setExcludeFromReports(true); entry->setExcludeFromReports(true);
QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
entry->setExcludeFromReports(false); entry->setExcludeFromReports(false);
QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_3_1);
// Previous parent group set on group // Previous parent group set on group
group1->setPreviousParentGroup(group2); group1->setPreviousParentGroup(group2);
QCOMPARE(group1->previousParentGroup(), group2); QCOMPARE(group1->previousParentGroup(), group2);
QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
group1->setPreviousParentGroup(nullptr); group1->setPreviousParentGroup(nullptr);
QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_3_1);
// Previous parent group set on entry // Previous parent group set on entry
entry->setPreviousParentGroup(group2); entry->setPreviousParentGroup(group2);
QCOMPARE(entry->previousParentGroup(), group2); QCOMPARE(entry->previousParentGroup(), group2);
QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
entry->setPreviousParentGroup(nullptr); entry->setPreviousParentGroup(nullptr);
QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_3_1);
// Custom icons with name or modification date // Custom icons with name or modification date
Metadata::CustomIconData customIcon; Metadata::CustomIconData customIcon;
auto iconUuid = QUuid::createUuid(); auto iconUuid = QUuid::createUuid();
db.metadata()->addCustomIcon(iconUuid, customIcon); db.metadata()->addCustomIcon(iconUuid, customIcon);
QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_3_1);
customIcon.name = "abc"; customIcon.name = "abc";
db.metadata()->removeCustomIcon(iconUuid); db.metadata()->removeCustomIcon(iconUuid);
db.metadata()->addCustomIcon(iconUuid, customIcon); db.metadata()->addCustomIcon(iconUuid, customIcon);
QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
customIcon.name.clear(); customIcon.name.clear();
customIcon.lastModified = Clock::currentDateTimeUtc(); customIcon.lastModified = Clock::currentDateTimeUtc();
db.metadata()->removeCustomIcon(iconUuid); db.metadata()->removeCustomIcon(iconUuid);
QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_3_1);
db.metadata()->addCustomIcon(iconUuid, customIcon); db.metadata()->addCustomIcon(iconUuid, customIcon);
QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db)); QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
} }
void TestKdbx4Format::testUpgradeMasterKeyIntegrity() void TestKdbx4Format::testUpgradeMasterKeyIntegrity()
@ -394,8 +380,8 @@ void TestKdbx4Format::testUpgradeMasterKeyIntegrity()
if (reader.hasError()) { if (reader.hasError()) {
QFAIL(qPrintable(reader.errorString())); QFAIL(qPrintable(reader.errorString()));
} }
QCOMPARE(reader.version(), expectedVersion & KeePass2::FILE_VERSION_CRITICAL_MASK); QCOMPARE(reader.version(), expectedVersion);
if (expectedVersion != KeePass2::FILE_VERSION_3) { if (expectedVersion >= KeePass2::FILE_VERSION_4) {
QVERIFY(db2->kdf()->uuid() != KeePass2::KDF_AES_KDBX3); QVERIFY(db2->kdf()->uuid() != KeePass2::KDF_AES_KDBX3);
} }
} }
@ -405,9 +391,9 @@ void TestKdbx4Format::testUpgradeMasterKeyIntegrity_data()
QTest::addColumn<QString>("upgradeAction"); QTest::addColumn<QString>("upgradeAction");
QTest::addColumn<quint32>("expectedVersion"); QTest::addColumn<quint32>("expectedVersion");
QTest::newRow("Upgrade: none") << QString("none") << 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; 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; 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-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 (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; QTest::newRow("Upgrade (implicit): public-customdata") << QString("public-customdata") << KeePass2::FILE_VERSION_4;

View File

@ -32,11 +32,6 @@ protected:
readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) override; readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) override;
void writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; void writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override;
void readKdbx(const QString& path,
QSharedPointer<const CompositeKey> key,
QSharedPointer<Database> db,
bool& hasError,
QString& errorString) override;
void readKdbx(QIODevice* device, void readKdbx(QIODevice* device,
QSharedPointer<const CompositeKey> key, QSharedPointer<const CompositeKey> key,
QSharedPointer<Database> db, QSharedPointer<Database> db,

View File

@ -76,11 +76,6 @@ protected:
QSharedPointer<Database> db, QSharedPointer<Database> db,
bool& hasError, bool& hasError,
QString& errorString) = 0; QString& errorString) = 0;
virtual void readKdbx(const QString& path,
QSharedPointer<const CompositeKey> key,
QSharedPointer<Database> db,
bool& hasError,
QString& errorString) = 0;
virtual void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) = 0; virtual void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) = 0;
QSharedPointer<Database> m_xmlDb; QSharedPointer<Database> m_xmlDb;