mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
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:
parent
67603ab42e
commit
a3dc977e58
BIN
share/demo.kdbx
BIN
share/demo.kdbx
Binary file not shown.
@ -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>
|
||||
<translation type="unfinished"></translation>
|
||||
</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>
|
||||
<name>DatabaseSettingWidgetMetaData</name>
|
||||
@ -1808,18 +1830,6 @@ Are you sure you want to continue without a password?</translation>
|
||||
<source>Database format:</source>
|
||||
<translation>Database format:</translation>
|
||||
</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>
|
||||
<source>unchanged</source>
|
||||
<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>
|
||||
<translation type="unfinished"></translation>
|
||||
</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>
|
||||
<name>DatabaseSettingsWidgetFdoSecrets</name>
|
||||
@ -6523,10 +6549,6 @@ Available commands:
|
||||
<source>AES-KDF (KDBX 4)</source>
|
||||
<translation>AES-KDF (KDBX 4)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>AES-KDF (KDBX 3.1)</source>
|
||||
<translation>AES-KDF (KDBX 3.1)</translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>Invalid Settings</source>
|
||||
<comment>TOTP</comment>
|
||||
@ -7498,6 +7520,10 @@ Please consider generating a new key file.</source>
|
||||
<source>Attachments:</source>
|
||||
<translation type="unfinished"></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source>AES-KDF (KDBX 3)</source>
|
||||
<translation type="unfinished">AES-KDF (KDBX 3.1) {3)?}</translation>
|
||||
</message>
|
||||
</context>
|
||||
<context>
|
||||
<name>QtIOCompressor</name>
|
||||
|
@ -163,6 +163,27 @@ bool Database::open(const QString& filePath, QSharedPointer<const CompositeKey>
|
||||
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 locked = m_saveMutex.tryLock();
|
||||
@ -935,6 +956,7 @@ void Database::setKdf(QSharedPointer<Kdf> kdf)
|
||||
{
|
||||
Q_ASSERT(!m_data.isReadOnly);
|
||||
m_data.kdf = std::move(kdf);
|
||||
setFormatVersion(KeePass2Writer::kdbxVersionRequired(this, true, m_data.kdf.isNull()));
|
||||
}
|
||||
|
||||
bool Database::changeKdf(const QSharedPointer<Kdf>& kdf)
|
||||
|
@ -87,6 +87,10 @@ public:
|
||||
bool extract(QByteArray&, QString* error = nullptr);
|
||||
bool import(const QString& xmlExportPath, QString* error = nullptr);
|
||||
|
||||
quint32 formatVersion() const;
|
||||
void setFormatVersion(quint32 version);
|
||||
bool hasMinorVersionMismatch() const;
|
||||
|
||||
void releaseData();
|
||||
|
||||
bool isInitialized() const;
|
||||
@ -166,6 +170,7 @@ signals:
|
||||
private:
|
||||
struct DatabaseData
|
||||
{
|
||||
quint32 formatVersion = 0;
|
||||
QString filePath;
|
||||
bool isReadOnly = false;
|
||||
QUuid cipher = KeePass2::CIPHER_AES256;
|
||||
|
@ -34,7 +34,7 @@ bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
|
||||
QSharedPointer<const CompositeKey> 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);
|
||||
|
@ -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<quint16>(&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;
|
||||
}
|
||||
|
@ -29,7 +29,6 @@ class Kdbx3Writer : public KdbxWriter
|
||||
|
||||
public:
|
||||
bool writeDatabase(QIODevice* device, Database* db) override;
|
||||
quint32 formatVersion() override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_KDBX3WRITER_H
|
||||
|
@ -36,7 +36,7 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
||||
QSharedPointer<const CompositeKey> 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();
|
||||
|
||||
|
@ -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<quint32>(&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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -74,14 +74,12 @@ bool KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const CompositeK
|
||||
headerStream.open(QIODevice::ReadOnly);
|
||||
|
||||
// read KDBX magic numbers
|
||||
quint32 sig1, sig2;
|
||||
if (!readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion)) {
|
||||
quint32 sig1, sig2, version;
|
||||
if (!readMagicNumbers(&headerStream, sig1, sig2, version)) {
|
||||
return false;
|
||||
}
|
||||
m_kdbxSignature = qMakePair(sig1, sig2);
|
||||
|
||||
// mask out minor version
|
||||
m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||
m_db->setFormatVersion(version);
|
||||
|
||||
// read header fields
|
||||
while (readHeaderField(headerStream, m_db) && !hasError()) {
|
||||
|
@ -83,8 +83,6 @@ protected:
|
||||
|
||||
void raiseError(const QString& errorMessage);
|
||||
|
||||
quint32 m_kdbxVersion = 0;
|
||||
|
||||
QByteArray m_masterSeed;
|
||||
QByteArray m_encryptionIV;
|
||||
QByteArray m_streamStartBytes;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -52,11 +52,6 @@ public:
|
||||
*/
|
||||
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;
|
||||
|
@ -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<Entry*>& 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);
|
||||
}
|
||||
|
@ -56,7 +56,7 @@ const QList<QPair<QUuid, QString>> 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)
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -81,11 +81,8 @@ bool KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const Compos
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
|
@ -40,44 +40,57 @@ 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::kdbxVersionRequired(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)) {
|
||||
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()) {
|
||||
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()) {
|
||||
for (const auto* historyItem : entry->historyItems()) {
|
||||
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) {
|
||||
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 +119,8 @@ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
|
||||
m_error = false;
|
||||
m_errorStr.clear();
|
||||
|
||||
bool upgradeNeeded = implicitKDBXUpgradeNeeded(db);
|
||||
if (upgradeNeeded) {
|
||||
m_version = kdbxVersionRequired(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 +128,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 +145,13 @@ void KeePass2Writer::extractDatabase(Database* db, QByteArray& xmlOutput)
|
||||
m_error = false;
|
||||
m_errorStr.clear();
|
||||
|
||||
m_version = kdbxVersionRequired(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());
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ public:
|
||||
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 kdbxVersionRequired(Database const* db, bool ignoreCurrent = false, bool ignoreKdf = false);
|
||||
|
||||
QSharedPointer<KdbxWriter> writer() const;
|
||||
quint32 version() const;
|
||||
|
@ -206,6 +206,26 @@ void DatabaseOpenWidget::openDatabase()
|
||||
QApplication::restoreOverrideCursor();
|
||||
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) {
|
||||
#ifdef WITH_XC_TOUCHID
|
||||
QHash<QString, QVariant> useTouchID = config()->get(Config::UseTouchID).toHash();
|
||||
|
@ -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::kdbxVersionRequired(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);
|
||||
|
||||
|
@ -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();
|
||||
|
@ -183,6 +183,9 @@
|
||||
</item>
|
||||
<item>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<property name="verticalSpacing">
|
||||
<number>2</number>
|
||||
</property>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="compatibilityLabel">
|
||||
<property name="text">
|
||||
@ -203,12 +206,24 @@
|
||||
</property>
|
||||
</widget>
|
||||
</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>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label">
|
||||
<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>
|
||||
</widget>
|
||||
</item>
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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<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);
|
||||
QCOMPARE(reader.version(), KeePass2::FILE_VERSION_3_1);
|
||||
}
|
||||
|
||||
void TestKdbx3::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString)
|
||||
|
@ -44,11 +44,6 @@ protected:
|
||||
QSharedPointer<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 writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) override;
|
||||
};
|
||||
|
||||
|
@ -92,21 +92,6 @@ void TestKdbx4Argon2::readKdbx(QIODevice* device,
|
||||
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)
|
||||
{
|
||||
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) {
|
||||
@ -218,8 +203,8 @@ void TestKdbx4Format::testFormat400Upgrade_data()
|
||||
QTest::addColumn<bool>("addCustomData");
|
||||
QTest::addColumn<quint32>("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::kdbxVersionRequired(&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::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
|
||||
group1->setTags("");
|
||||
QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
|
||||
QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_3_1);
|
||||
|
||||
// PasswordQuality flag set
|
||||
entry->setExcludeFromReports(true);
|
||||
QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
|
||||
QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
|
||||
entry->setExcludeFromReports(false);
|
||||
QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
|
||||
QCOMPARE(KeePass2Writer::kdbxVersionRequired(&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::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
|
||||
group1->setPreviousParentGroup(nullptr);
|
||||
QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
|
||||
QCOMPARE(KeePass2Writer::kdbxVersionRequired(&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::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
|
||||
entry->setPreviousParentGroup(nullptr);
|
||||
QVERIFY(!KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
|
||||
QCOMPARE(KeePass2Writer::kdbxVersionRequired(&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::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_3_1);
|
||||
customIcon.name = "abc";
|
||||
db.metadata()->removeCustomIcon(iconUuid);
|
||||
db.metadata()->addCustomIcon(iconUuid, customIcon);
|
||||
QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
|
||||
QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_4_1);
|
||||
customIcon.name.clear();
|
||||
customIcon.lastModified = Clock::currentDateTimeUtc();
|
||||
db.metadata()->removeCustomIcon(iconUuid);
|
||||
QCOMPARE(KeePass2Writer::kdbxVersionRequired(&db), KeePass2::FILE_VERSION_3_1);
|
||||
db.metadata()->addCustomIcon(iconUuid, customIcon);
|
||||
QVERIFY(KeePass2Writer::implicitKDBXUpgradeNeeded(&db));
|
||||
QCOMPARE(KeePass2Writer::kdbxVersionRequired(&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<QString>("upgradeAction");
|
||||
QTest::addColumn<quint32>("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;
|
||||
|
@ -32,11 +32,6 @@ protected:
|
||||
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<const CompositeKey> key,
|
||||
QSharedPointer<Database> db,
|
||||
bool& hasError,
|
||||
QString& errorString) override;
|
||||
void readKdbx(QIODevice* device,
|
||||
QSharedPointer<const CompositeKey> key,
|
||||
QSharedPointer<Database> db,
|
||||
|
@ -76,11 +76,6 @@ protected:
|
||||
QSharedPointer<Database> db,
|
||||
bool& hasError,
|
||||
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;
|
||||
|
||||
QSharedPointer<Database> m_xmlDb;
|
||||
|
Loading…
Reference in New Issue
Block a user