Refactor Database and Database widgets (#2491)

The Database, DatabaseWidget, and DatabaseTabWidget classes share many responsibilities in inconsistent ways resulting in impenetrable and unmaintainable code and a diverse set of bugs and architecture restrictions. This patch reworks the architecture, responsibilities of, and dependencies between these classes.

The core changes are:

* Move loading and saving logic from widgets into the Database class
* Get rid of the DatabaseManagerStruct and move all the information contained in it into the Database
* Let database objects keep track of modifications and dirty/clean state instead of handing this to external widgets
* Move GUI interactions for loading and saving from the DatabaseTabWidget into the DatabaseWidget (resolves #2494 as a side-effect)
* Heavily clean up DatabaseTabWidget and degrade it to a slightly glorified QTabWidget
* Use QSharedPointers for all Database objects
* Remove the modifiedImmediate signal and replace it with a markAsModified() method
* Implement proper tabName() method instead of reading back titles from GUI widgets (resolves #1389 and its duplicates #2146 #855)
* Fix unwanted AES-KDF downgrade if database uses Argon2 and has CustomData
* Improve code

This patch is also the first major step towards solving issues #476 and #2322.
This commit is contained in:
Janek Bevendorff 2018-11-22 11:47:31 +01:00 committed by GitHub
parent 917c4cc18b
commit d612cad09a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
115 changed files with 2116 additions and 2165 deletions

View file

@ -23,7 +23,7 @@
#include "core/Database.h"
#include "core/Group.h"
bool CsvExporter::exportDatabase(const QString& filename, const Database* db)
bool CsvExporter::exportDatabase(const QString& filename, QSharedPointer<const Database> db)
{
QFile file(filename);
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
@ -33,7 +33,7 @@ bool CsvExporter::exportDatabase(const QString& filename, const Database* db)
return exportDatabase(&file, db);
}
bool CsvExporter::exportDatabase(QIODevice* device, const Database* db)
bool CsvExporter::exportDatabase(QIODevice* device, QSharedPointer<const Database> db)
{
QString header;
addColumn(header, "Group");

View file

@ -20,6 +20,7 @@
#define KEEPASSX_CSVEXPORTER_H
#include <QString>
#include <QtCore/QSharedPointer>
class Database;
class Group;
@ -28,8 +29,8 @@ class QIODevice;
class CsvExporter
{
public:
bool exportDatabase(const QString& filename, const Database* db);
bool exportDatabase(QIODevice* device, const Database* db);
bool exportDatabase(const QString& filename, QSharedPointer<const Database> db);
bool exportDatabase(QIODevice* device, QSharedPointer<const Database> db);
QString errorString() const;
private:

View file

@ -29,77 +29,77 @@
#include <QBuffer>
Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
bool keepDatabase)
bool Kdbx3Reader::readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
Database* db)
{
Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3_1);
if (hasError()) {
return nullptr;
return false;
}
// check if all required headers were present
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_streamStartBytes.isEmpty()
|| m_protectedStreamKey.isEmpty()
|| m_db->cipher().isNull()) {
|| db->cipher().isNull()) {
raiseError(tr("missing database headers"));
return nullptr;
return false;
}
if (!m_db->setKey(key, false)) {
if (!db->setKey(key, false)) {
raiseError(tr("Unable to calculate master key"));
return nullptr;
return false;
}
if (!m_db->challengeMasterSeed(m_masterSeed)) {
if (!db->challengeMasterSeed(m_masterSeed)) {
raiseError(tr("Unable to issue challenge-response."));
return nullptr;
return false;
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
hash.addData(m_db->challengeResponseKey());
hash.addData(m_db->transformedMasterKey());
hash.addData(db->challengeResponseKey());
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher());
SymmetricCipherStream cipherStream(
device, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString());
return nullptr;
return false;
}
if (!cipherStream.open(QIODevice::ReadOnly)) {
raiseError(cipherStream.errorString());
return nullptr;
return false;
}
QByteArray realStart = cipherStream.read(32);
if (realStart != m_streamStartBytes) {
raiseError(tr("Wrong key or database file is corrupt."));
return nullptr;
return false;
}
HashedBlockStream hashedStream(&cipherStream);
if (!hashedStream.open(QIODevice::ReadOnly)) {
raiseError(hashedStream.errorString());
return nullptr;
return false;
}
QIODevice* xmlDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (m_db->compressionAlgo() == Database::CompressionNone) {
if (db->compressionAlgorithm() == Database::CompressionNone) {
xmlDevice = &hashedStream;
} else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
if (!ioCompressor->open(QIODevice::ReadOnly)) {
raiseError(ioCompressor->errorString());
return nullptr;
return false;
}
xmlDevice = ioCompressor.data();
}
@ -107,20 +107,17 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20);
if (!randomStream.init(m_protectedStreamKey)) {
raiseError(randomStream.errorString());
return nullptr;
return false;
}
Q_ASSERT(xmlDevice);
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3_1);
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
xmlReader.readDatabase(xmlDevice, db, &randomStream);
if (xmlReader.hasError()) {
raiseError(xmlReader.errorString());
if (keepDatabase) {
return m_db.take();
}
return nullptr;
return false;
}
Q_ASSERT(!xmlReader.headerHash().isEmpty() || m_kdbxVersion < KeePass2::FILE_VERSION_3_1);
@ -129,15 +126,17 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device,
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);
if (headerHash != xmlReader.headerHash()) {
raiseError(tr("Header doesn't match hash"));
return nullptr;
return false;
}
}
return m_db.take();
return true;
}
bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream)
bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream, Database* db)
{
Q_UNUSED(db);
QByteArray fieldIDArray = headerStream.read(1);
if (fieldIDArray.size() != 1) {
raiseError(tr("Invalid header id size"));

View file

@ -29,13 +29,13 @@ class Kdbx3Reader : public KdbxReader
Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader)
public:
Database* readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
bool keepDatabase) override;
bool readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
Database* db) override;
protected:
bool readHeaderField(StoreDataStream& headerStream) override;
bool readHeaderField(StoreDataStream& headerStream, Database* db) override;
};
#endif // KEEPASSX_KDBX3READER_H

View file

@ -67,7 +67,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CompressionFlags,
Endian::sizedIntToBytes<qint32>(db->compressionAlgo(),
Endian::sizedIntToBytes<qint32>(db->compressionAlgorithm(),
KeePass2::BYTEORDER)));
auto kdf = db->kdf();
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
@ -112,7 +112,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
QIODevice* outputDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) {
if (db->compressionAlgorithm() == Database::CompressionNone) {
outputDevice = &hashedStream;
} else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));

View file

@ -28,85 +28,85 @@
#include "streams/QtIOCompressor"
#include "streams/SymmetricCipherStream.h"
Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
bool keepDatabase)
bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
Database* db)
{
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
m_binaryPoolInverse.clear();
if (hasError()) {
return nullptr;
return false;
}
// check if all required headers were present
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_db->cipher().isNull()) {
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || db->cipher().isNull()) {
raiseError(tr("missing database headers"));
return nullptr;
return false;
}
if (!m_db->setKey(key, false, false)) {
if (!db->setKey(key, false, false)) {
raiseError(tr("Unable to calculate master key"));
return nullptr;
return false;
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
hash.addData(m_db->transformedMasterKey());
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
QByteArray headerSha256 = device->read(32);
QByteArray headerHmac = device->read(32);
if (headerSha256.size() != 32 || headerHmac.size() != 32) {
raiseError(tr("Invalid header checksum size"));
return nullptr;
return false;
}
if (headerSha256 != CryptoHash::hash(headerData, CryptoHash::Sha256)) {
raiseError(tr("Header SHA256 mismatch"));
return nullptr;
return false;
}
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, m_db->transformedMasterKey());
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, db->transformedMasterKey());
if (headerHmac
!= CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
raiseError(tr("Wrong key or database file is corrupt. (HMAC mismatch)"));
return nullptr;
return false;
}
HmacBlockStream hmacStream(device, hmacKey);
if (!hmacStream.open(QIODevice::ReadOnly)) {
raiseError(hmacStream.errorString());
return nullptr;
return false;
}
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(db->cipher());
if (cipher == SymmetricCipher::InvalidAlgorithm) {
raiseError(tr("Unknown cipher"));
return nullptr;
return false;
}
SymmetricCipherStream cipherStream(
&hmacStream, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString());
return nullptr;
return false;
}
if (!cipherStream.open(QIODevice::ReadOnly)) {
raiseError(cipherStream.errorString());
return nullptr;
return false;
}
QIODevice* xmlDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (m_db->compressionAlgo() == Database::CompressionNone) {
if (db->compressionAlgorithm() == Database::CompressionNone) {
xmlDevice = &cipherStream;
} else {
ioCompressor.reset(new QtIOCompressor(&cipherStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
if (!ioCompressor->open(QIODevice::ReadOnly)) {
raiseError(ioCompressor->errorString());
return nullptr;
return false;
}
xmlDevice = ioCompressor.data();
}
@ -115,32 +115,29 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
}
if (hasError()) {
return nullptr;
return false;
}
KeePass2RandomStream randomStream(m_irsAlgo);
if (!randomStream.init(m_protectedStreamKey)) {
raiseError(randomStream.errorString());
return nullptr;
return false;
}
Q_ASSERT(xmlDevice);
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, binaryPool());
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
xmlReader.readDatabase(xmlDevice, db, &randomStream);
if (xmlReader.hasError()) {
raiseError(xmlReader.errorString());
if (keepDatabase) {
return m_db.take();
}
return nullptr;
return false;
}
return m_db.take();
return true;
}
bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
bool Kdbx4Reader::readHeaderField(StoreDataStream& device, Database* db)
{
QByteArray fieldIDArray = device.read(1);
if (fieldIDArray.size() != 1) {
@ -197,7 +194,7 @@ bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
raiseError(tr("Unsupported key derivation function (KDF) or invalid parameters"));
return false;
}
m_db->setKdf(kdf);
db->setKdf(kdf);
break;
}
@ -206,7 +203,7 @@ bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
variantBuffer.setBuffer(&fieldData);
variantBuffer.open(QBuffer::ReadOnly);
QVariantMap data = readVariantMap(&variantBuffer);
m_db->setPublicCustomData(data);
db->setPublicCustomData(data);
break;
}

View file

@ -30,15 +30,15 @@ class Kdbx4Reader : public KdbxReader
Q_DECLARE_TR_FUNCTIONS(Kdbx4Reader)
public:
Database* readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
bool keepDatabase) override;
bool readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
Database* db) override;
QHash<QByteArray, QString> binaryPoolInverse() const;
QHash<QString, QByteArray> binaryPool() const;
protected:
bool readHeaderField(StoreDataStream& headerStream) override;
bool readHeaderField(StoreDataStream& headerStream, Database* db) override;
private:
bool readInnerHeaderField(QIODevice* device);

View file

@ -75,7 +75,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toRfc4122()));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CompressionFlags,
Endian::sizedIntToBytes(static_cast<int>(db->compressionAlgo()),
Endian::sizedIntToBytes(static_cast<int>(db->compressionAlgorithm()),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
@ -138,7 +138,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
QIODevice* outputDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) {
if (db->compressionAlgorithm() == Database::CompressionNone) {
outputDevice = cipherStream.data();
} else {
ioCompressor.reset(new QtIOCompressor(cipherStream.data()));

View file

@ -59,14 +59,14 @@ bool KdbxReader::readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig
*
* @param device input device
* @param key database encryption composite key
* @param keepDatabase keep database in case of read failure
* @return pointer to the read database, nullptr on failure
* @param db database to read into
* @return true on success
*/
Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase)
bool KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db)
{
device->seek(0);
m_db.reset(new Database());
m_db = db;
m_xmlData.clear();
m_masterSeed.clear();
m_encryptionIV.clear();
@ -79,7 +79,7 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const Compo
// read KDBX magic numbers
quint32 sig1, sig2;
if (!readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion)) {
return nullptr;
return false;
}
m_kdbxSignature = qMakePair(sig1, sig2);
@ -87,24 +87,24 @@ Database* KdbxReader::readDatabase(QIODevice* device, QSharedPointer<const Compo
m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK;
// read header fields
while (readHeaderField(headerStream) && !hasError()) {
while (readHeaderField(headerStream, m_db) && !hasError()) {
}
headerStream.close();
if (hasError()) {
return nullptr;
return false;
}
// read payload
auto* db = readDatabaseImpl(device, headerStream.storedData(), std::move(key), keepDatabase);
bool ok = readDatabaseImpl(device, headerStream.storedData(), std::move(key), db);
if (saveXml()) {
m_xmlData.clear();
decryptXmlInnerStream(m_xmlData, db);
}
return db;
return ok;
}
bool KdbxReader::hasError() const
@ -175,7 +175,7 @@ void KdbxReader::setCompressionFlags(const QByteArray& data)
raiseError(tr("Unsupported compression algorithm"));
return;
}
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
m_db->setCompressionAlgorithm(static_cast<Database::CompressionAlgorithm>(id));
}
/**

View file

@ -40,7 +40,7 @@ public:
virtual ~KdbxReader() = default;
static bool readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version);
Database* readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase = false);
bool readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db);
bool hasError() const;
QString errorString() const;
@ -57,22 +57,22 @@ protected:
* @param device input device at the payload starting position
* @param KDBX header data as bytes
* @param key database encryption composite key
* @param keepDatabase keep database in case of read failure
* @return pointer to the read database, nullptr on failure
* @param db database to read into
* @return true on success
*/
virtual Database*
readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
bool keepDatabase) = 0;
virtual bool readDatabaseImpl(QIODevice* device,
const QByteArray& headerData,
QSharedPointer<const CompositeKey> key,
Database* db) = 0;
/**
* Read next header field from stream.
*
* @param headerStream input header stream
* @param database to read header field for
* @return true if there are more header fields
*/
virtual bool readHeaderField(StoreDataStream& headerStream) = 0;
virtual bool readHeaderField(StoreDataStream& headerStream, Database* db) = 0;
virtual void setCipher(const QByteArray& data);
virtual void setCompressionFlags(const QByteArray& data);
@ -88,9 +88,6 @@ protected:
void decryptXmlInnerStream(QByteArray& xmlOutput, Database* db) const;
QScopedPointer<Database> m_db;
QPair<quint32, quint32> m_kdbxSignature;
quint32 m_kdbxVersion = 0;
QByteArray m_masterSeed;
@ -102,6 +99,9 @@ protected:
QByteArray m_xmlData;
private:
QPair<quint32, quint32> m_kdbxSignature;
QPointer<Database> m_db;
bool m_saveXml = false;
bool m_error = false;
QString m_errorStr = "";

View file

@ -56,7 +56,7 @@ KdbxXmlReader::KdbxXmlReader(quint32 version, QHash<QString, QByteArray> binary
* @param device input file
* @return pointer to the new database
*/
Database* KdbxXmlReader::readDatabase(const QString& filename)
QSharedPointer<Database> KdbxXmlReader::readDatabase(const QString& filename)
{
QFile file(filename);
file.open(QIODevice::ReadOnly);
@ -69,10 +69,10 @@ Database* KdbxXmlReader::readDatabase(const QString& filename)
* @param device input device
* @return pointer to the new database
*/
Database* KdbxXmlReader::readDatabase(QIODevice* device)
QSharedPointer<Database> KdbxXmlReader::readDatabase(QIODevice* device)
{
auto db = new Database();
readDatabase(device, db);
auto db = QSharedPointer<Database>::create();
readDatabase(device, db.data());
return db;
}

View file

@ -45,8 +45,8 @@ public:
explicit KdbxXmlReader(quint32 version, QHash<QString, QByteArray> binaryPool);
virtual ~KdbxXmlReader() = default;
virtual Database* readDatabase(const QString& filename);
virtual Database* readDatabase(QIODevice* device);
virtual QSharedPointer<Database> readDatabase(const QString& filename);
virtual QSharedPointer<Database> readDatabase(QIODevice* device);
virtual void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
bool hasError() const;

View file

@ -190,7 +190,7 @@ void KdbxXmlWriter::writeBinaries()
m_xml.writeAttribute("ID", QString::number(i.value()));
QByteArray data;
if (m_db->compressionAlgo() == Database::CompressionGZip) {
if (m_db->compressionAlgorithm() == Database::CompressionGZip) {
m_xml.writeAttribute("Compressed", "True");
QBuffer buffer;

View file

@ -48,8 +48,7 @@ private:
};
KeePass1Reader::KeePass1Reader()
: m_db(nullptr)
, m_tmpParent(nullptr)
: m_tmpParent(nullptr)
, m_device(nullptr)
, m_encryptionFlags(0)
, m_transformRounds(0)
@ -57,7 +56,7 @@ KeePass1Reader::KeePass1Reader()
{
}
Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice)
QSharedPointer<Database> KeePass1Reader::readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice)
{
m_error = false;
m_errorStr.clear();
@ -70,22 +69,22 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
if (keyfileData.isEmpty()) {
raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
return nullptr;
return {};
}
if (!keyfileDevice->seek(0)) {
raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
return nullptr;
return {};
}
if (!newFileKey->load(keyfileDevice)) {
raiseError(tr("Unable to read keyfile.").append("\n").append(keyfileDevice->errorString()));
return nullptr;
return {};
}
}
QScopedPointer<Database> db(new Database());
auto db = QSharedPointer<Database>::create();
QScopedPointer<Group> tmpParent(new Group());
m_db = db.data();
m_db = db;
m_tmpParent = tmpParent.data();
m_device = device;
@ -94,19 +93,19 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
auto signature1 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok || signature1 != KeePass1::SIGNATURE_1) {
raiseError(tr("Not a KeePass database."));
return nullptr;
return {};
}
auto signature2 = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok || signature2 != KeePass1::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
return {};
}
m_encryptionFlags = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok || !(m_encryptionFlags & KeePass1::Rijndael || m_encryptionFlags & KeePass1::Twofish)) {
raiseError(tr("Unsupported encryption algorithm."));
return nullptr;
return {};
}
auto version = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
@ -114,49 +113,49 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
|| (version & KeePass1::FILE_VERSION_CRITICAL_MASK)
!= (KeePass1::FILE_VERSION & KeePass1::FILE_VERSION_CRITICAL_MASK)) {
raiseError(tr("Unsupported KeePass database version."));
return nullptr;
return {};
}
m_masterSeed = m_device->read(16);
if (m_masterSeed.size() != 16) {
raiseError("Unable to read master seed");
return nullptr;
return {};
}
m_encryptionIV = m_device->read(16);
if (m_encryptionIV.size() != 16) {
raiseError(tr("Unable to read encryption IV", "IV = Initialization Vector for symmetric cipher"));
return nullptr;
return {};
}
auto numGroups = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok) {
raiseError(tr("Invalid number of groups"));
return nullptr;
return {};
}
auto numEntries = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok) {
raiseError(tr("Invalid number of entries"));
return nullptr;
return {};
}
m_contentHashHeader = m_device->read(32);
if (m_contentHashHeader.size() != 32) {
raiseError(tr("Invalid content hash size"));
return nullptr;
return {};
}
m_transformSeed = m_device->read(32);
if (m_transformSeed.size() != 32) {
raiseError(tr("Invalid transform seed size"));
return nullptr;
return {};
}
m_transformRounds = Endian::readSizedInt<quint32>(m_device, KeePass1::BYTEORDER, &ok);
if (!ok) {
raiseError(tr("Invalid number of transform rounds"));
return nullptr;
return {};
}
auto kdf = QSharedPointer<AesKdf>::create(true);
kdf->setRounds(m_transformRounds);
@ -168,14 +167,14 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
QScopedPointer<SymmetricCipherStream> cipherStream(testKeys(password, keyfileData, contentPos));
if (!cipherStream) {
return nullptr;
return {};
}
QList<Group*> groups;
for (quint32 i = 0; i < numGroups; i++) {
Group* group = readGroup(cipherStream.data());
if (!group) {
return nullptr;
return {};
}
groups.append(group);
}
@ -184,14 +183,14 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
for (quint32 i = 0; i < numEntries; i++) {
Entry* entry = readEntry(cipherStream.data());
if (!entry) {
return nullptr;
return {};
}
entries.append(entry);
}
if (!constructGroupTree(groups)) {
raiseError(tr("Unable to construct group tree"));
return nullptr;
return {};
}
for (Entry* entry : asConst(entries)) {
@ -243,41 +242,39 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor
if (!db->setKey(key)) {
raiseError(tr("Unable to calculate master key"));
return nullptr;
return {};
}
return db.take();
return db;
}
Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& password, const QString& keyfileName)
QSharedPointer<Database> KeePass1Reader::readDatabase(QIODevice* device, const QString& password, const QString& keyfileName)
{
QScopedPointer<QFile> keyFile;
if (!keyfileName.isEmpty()) {
keyFile.reset(new QFile(keyfileName));
if (!keyFile->open(QFile::ReadOnly)) {
raiseError(keyFile->errorString());
return nullptr;
return {};
}
}
QScopedPointer<Database> db(readDatabase(device, password, keyFile.data()));
return db.take();
return QSharedPointer<Database>(readDatabase(device, password, keyFile.data()));
}
Database* KeePass1Reader::readDatabase(const QString& filename, const QString& password, const QString& keyfileName)
QSharedPointer<Database> KeePass1Reader::readDatabase(const QString& filename, const QString& password, const QString& keyfileName)
{
QFile dbFile(filename);
if (!dbFile.open(QFile::ReadOnly)) {
raiseError(dbFile.errorString());
return nullptr;
return {};
}
Database* db = readDatabase(&dbFile, password, keyfileName);
auto db = readDatabase(&dbFile, password, keyfileName);
if (dbFile.error() != QFile::NoError) {
raiseError(dbFile.errorString());
return nullptr;
return {};
}
return db;
@ -293,8 +290,7 @@ QString KeePass1Reader::errorString()
return m_errorStr;
}
SymmetricCipherStream*
KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData, qint64 contentPos)
SymmetricCipherStream* KeePass1Reader::testKeys(const QString& password, const QByteArray& keyfileData, qint64 contentPos)
{
const QList<PasswordEncoding> encodings = {Windows1252, Latin1, UTF8};

View file

@ -21,6 +21,7 @@
#include <QCoreApplication>
#include <QDateTime>
#include <QHash>
#include <QSharedPointer>
class Database;
class Entry;
@ -34,9 +35,9 @@ class KeePass1Reader
public:
KeePass1Reader();
Database* readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice);
Database* readDatabase(QIODevice* device, const QString& password, const QString& keyfileName);
Database* readDatabase(const QString& filename, const QString& password, const QString& keyfileName);
QSharedPointer<Database> readDatabase(QIODevice* device, const QString& password, QIODevice* keyfileDevice);
QSharedPointer<Database> readDatabase(QIODevice* device, const QString& password, const QString& keyfileName);
QSharedPointer<Database> readDatabase(const QString& filename, const QString& password, const QString& keyfileName);
bool hasError();
QString errorString();
@ -63,7 +64,7 @@ private:
static QDateTime dateFromPackedStruct(const QByteArray& data);
static bool isMetaStream(const Entry* entry);
Database* m_db;
QSharedPointer<Database> m_db;
Group* m_tmpParent;
QIODevice* m_device;
quint32 m_encryptionFlags;

View file

@ -21,31 +21,31 @@
#include "format/KeePass1.h"
#include <QFile>
#include <utility>
/**
* Read database from file and detect correct file format.
*
* @param filename input file
* @param key database encryption composite key
* @return pointer to the read database, nullptr on failure
* @param db Database to read into
* @return true on success
*/
Database* KeePass2Reader::readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key)
bool KeePass2Reader::readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key, Database* db)
{
QFile file(filename);
if (!file.open(QFile::ReadOnly)) {
raiseError(file.errorString());
return nullptr;
return false;
}
QScopedPointer<Database> db(readDatabase(&file, std::move(key)));
bool ok = readDatabase(&file, std::move(key), db);
if (file.error() != QFile::NoError) {
raiseError(file.errorString());
return nullptr;
return false;
}
return db.take();
return ok;
}
/**
@ -53,10 +53,10 @@ Database* KeePass2Reader::readDatabase(const QString& filename, QSharedPointer<c
*
* @param device input device
* @param key database encryption composite key
* @param keepDatabase keep database in case of read failure
* @return pointer to the read database, nullptr on failure
* @param db Database to read into
* @return true on success
*/
Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase)
bool KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db)
{
m_error = false;
m_errorStr.clear();
@ -69,7 +69,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const C
if (!ok || signature1 != KeePass2::SIGNATURE_1 || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
return false;
}
if (signature2 == KeePass1::SIGNATURE_2) {
@ -77,13 +77,13 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const C
"You can import it by clicking on Database > '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 nullptr;
return false;
}
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."));
return nullptr;
return false;
}
// determine file format (KDBX 2/3 or 4)
@ -94,7 +94,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, QSharedPointer<const C
}
m_reader->setSaveXml(m_saveXml);
return m_reader->readDatabase(device, std::move(key), keepDatabase);
return m_reader->readDatabase(device, std::move(key), db);
}
bool KeePass2Reader::hasError() const

View file

@ -35,8 +35,8 @@ class KeePass2Reader
Q_DECLARE_TR_FUNCTIONS(KdbxReader)
public:
Database* readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key);
Database* readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, bool keepDatabase = false);
bool readDatabase(const QString& filename, QSharedPointer<const CompositeKey> key, Database* db);
bool readDatabase(QIODevice* device, QSharedPointer<const CompositeKey> key, Database* db);
bool hasError() const;
QString errorString() const;

View file

@ -48,6 +48,10 @@ bool KeePass2Writer::writeDatabase(const QString& filename, Database* db)
*/
bool KeePass2Writer::implicitUpgradeNeeded(Database const* db) const
{
if (db->kdf()->uuid() != KeePass2::KDF_AES_KDBX3 ) {
return false;
}
if (!db->publicCustomData().isEmpty()) {
return true;
}