From 2e19af5032513f3041e1c8936d629b52a8737304 Mon Sep 17 00:00:00 2001 From: angelsl Date: Sun, 12 Nov 2017 20:20:57 +0800 Subject: [PATCH 01/39] Pull out KDFs into their own class hierarchy In preparation for multiple KDFs in KDBX 4 --- src/CMakeLists.txt | 4 +- src/core/Database.cpp | 103 ++++++++--------- src/core/Database.h | 22 ++-- src/crypto/kdf/AesKdf.cpp | 171 +++++++++++++++++++++++++++++ src/crypto/kdf/AesKdf.h | 62 +++++++++++ src/crypto/kdf/Kdf.cpp | 86 +++++++++++++++ src/crypto/kdf/Kdf.h | 69 ++++++++++++ src/crypto/kdf/Kdf_p.h | 43 ++++++++ src/format/KeePass1Reader.cpp | 18 +-- src/format/KeePass2Reader.cpp | 32 ++++-- src/format/KeePass2Reader.h | 1 - src/format/KeePass2Writer.cpp | 9 +- src/gui/DatabaseSettingsWidget.cpp | 10 +- src/keys/CompositeKey.cpp | 112 +------------------ src/keys/CompositeKey.h | 9 +- src/keys/CompositeKey_p.h | 39 ------- tests/TestKeePass1Reader.cpp | 3 +- tests/TestKeys.cpp | 27 +++-- 18 files changed, 555 insertions(+), 265 deletions(-) create mode 100644 src/crypto/kdf/AesKdf.cpp create mode 100644 src/crypto/kdf/AesKdf.h create mode 100644 src/crypto/kdf/Kdf.cpp create mode 100644 src/crypto/kdf/Kdf.h create mode 100644 src/crypto/kdf/Kdf_p.h delete mode 100644 src/keys/CompositeKey_p.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2dca9e606..5ee71a28a 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -76,6 +76,9 @@ set(keepassx_SOURCES crypto/SymmetricCipher.cpp crypto/SymmetricCipherBackend.h crypto/SymmetricCipherGcrypt.cpp + crypto/kdf/Kdf.cpp + crypto/kdf/Kdf_p.h + crypto/kdf/AesKdf.cpp format/CsvExporter.cpp format/KeePass1.h format/KeePass1Reader.cpp @@ -139,7 +142,6 @@ set(keepassx_SOURCES gui/group/GroupModel.cpp gui/group/GroupView.cpp keys/CompositeKey.cpp - keys/CompositeKey_p.h keys/drivers/YubiKey.h keys/FileKey.cpp keys/Key.h diff --git a/src/core/Database.cpp b/src/core/Database.cpp index cb28ee211..45f2b479f 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -27,7 +27,7 @@ #include "cli/Utils.h" #include "core/Group.h" #include "core/Metadata.h" -#include "crypto/Random.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass2.h" #include "format/KeePass2Reader.h" #include "format/KeePass2Writer.h" @@ -45,7 +45,8 @@ Database::Database() { m_data.cipher = KeePass2::CIPHER_AES; m_data.compressionAlgo = CompressionGZip; - m_data.transformRounds = 100000; + m_data.kdf = new AesKdf(); + m_data.kdf->randomizeTransformSalt(); m_data.hasKey = false; setRootGroup(new Group()); @@ -225,16 +226,6 @@ Database::CompressionAlgorithm Database::compressionAlgo() const return m_data.compressionAlgo; } -QByteArray Database::transformSeed() const -{ - return m_data.transformSeed; -} - -quint64 Database::transformRounds() const -{ - return m_data.transformRounds; -} - QByteArray Database::transformedMasterKey() const { return m_data.transformedMasterKey; @@ -265,36 +256,18 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo) m_data.compressionAlgo = algo; } -bool Database::setTransformRounds(quint64 rounds) +bool Database::setKey(const CompositeKey& key, bool updateChangedTime, bool updateTransformSalt) { - if (m_data.transformRounds != rounds) { - quint64 oldRounds = m_data.transformRounds; - - m_data.transformRounds = rounds; - - if (m_data.hasKey) { - if (!setKey(m_data.key)) { - m_data.transformRounds = oldRounds; - return false; - } - } + if (updateTransformSalt) { + m_data.kdf->randomizeTransformSalt(); } - return true; -} - -bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, bool updateChangedTime) -{ - bool ok; - QString errorString; - - QByteArray transformedMasterKey = key.transform(transformSeed, transformRounds(), &ok, &errorString); - if (!ok) { + QByteArray transformedMasterKey; + if (!key.transform(*m_data.kdf, transformedMasterKey)) { return false; } m_data.key = key; - m_data.transformSeed = transformSeed; m_data.transformedMasterKey = transformedMasterKey; m_data.hasKey = true; if (updateChangedTime) { @@ -305,35 +278,11 @@ bool Database::setKey(const CompositeKey& key, const QByteArray& transformSeed, return true; } -bool Database::setKey(const CompositeKey& key) -{ - return setKey(key, randomGen()->randomArray(32)); -} - bool Database::hasKey() const { return m_data.hasKey; } -bool Database::transformKeyWithSeed(const QByteArray& transformSeed) -{ - Q_ASSERT(hasKey()); - - bool ok; - QString errorString; - - QByteArray transformedMasterKey = - m_data.key.transform(transformSeed, transformRounds(), &ok, &errorString); - if (!ok) { - return false; - } - - m_data.transformSeed = transformSeed; - m_data.transformedMasterKey = transformedMasterKey; - - return true; -} - bool Database::verifyKey(const CompositeKey& key) const { Q_ASSERT(hasKey()); @@ -429,6 +378,7 @@ void Database::setEmitModified(bool value) void Database::copyAttributesFrom(const Database* other) { m_data = other->m_data; + m_data.kdf = m_data.kdf->clone(); m_metadata->copyAttributesFrom(other->m_metadata); } @@ -534,3 +484,38 @@ QString Database::saveToFile(QString filePath) return saveFile.errorString(); } } + +Kdf* Database::kdf() { + return m_data.kdf; +} + +void Database::setKdf(Kdf* kdf) { + if (m_data.kdf == kdf) { + return; + } + + if (m_data.kdf != nullptr) { + delete m_data.kdf; + } + + m_data.kdf = kdf; +} + +bool Database::changeKdf(Kdf* kdf) { + kdf->randomizeTransformSalt(); + QByteArray transformedMasterKey; + if (!m_data.key.transform(*kdf, transformedMasterKey)) { + return false; + } + + setKdf(kdf); + m_data.transformedMasterKey = transformedMasterKey; + emit modifiedImmediate(); + + return true; +} + +Database::DatabaseData::~DatabaseData() +{ + delete kdf; +} diff --git a/src/core/Database.h b/src/core/Database.h index b20f897fe..dbb48718e 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -23,6 +23,7 @@ #include #include +#include "crypto/kdf/Kdf.h" #include "core/Uuid.h" #include "keys/CompositeKey.h" @@ -56,13 +57,14 @@ public: { Uuid cipher; CompressionAlgorithm compressionAlgo; - QByteArray transformSeed; - quint64 transformRounds; QByteArray transformedMasterKey; + Kdf* kdf; CompositeKey key; bool hasKey; QByteArray masterSeed; QByteArray challengeResponseKey; + + ~DatabaseData(); }; Database(); @@ -90,8 +92,7 @@ public: Uuid cipher() const; Database::CompressionAlgorithm compressionAlgo() const; - QByteArray transformSeed() const; - quint64 transformRounds() const; + Kdf* kdf(); QByteArray transformedMasterKey() const; const CompositeKey& key() const; QByteArray challengeResponseKey() const; @@ -99,16 +100,10 @@ public: void setCipher(const Uuid& cipher); void setCompressionAlgo(Database::CompressionAlgorithm algo); - bool setTransformRounds(quint64 rounds); - bool setKey(const CompositeKey& key, const QByteArray& transformSeed, - bool updateChangedTime = true); - - /** - * Sets the database key and generates a random transform seed. - */ - bool setKey(const CompositeKey& key); + void setKdf(Kdf* kdf); + bool setKey(const CompositeKey& key, bool updateChangedTime = true, + bool updateTransformSalt = false); bool hasKey() const; - bool transformKeyWithSeed(const QByteArray& transformSeed); bool verifyKey(const CompositeKey& key) const; void recycleEntry(Entry* entry); void recycleGroup(Group* group); @@ -122,6 +117,7 @@ public: * Returns a unique id that is only valid as long as the Database exists. */ Uuid uuid(); + bool changeKdf(Kdf* kdf); static Database* databaseByUuid(const Uuid& uuid); static Database* openDatabaseFile(QString fileName, CompositeKey key); diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp new file mode 100644 index 000000000..4380a782e --- /dev/null +++ b/src/crypto/kdf/AesKdf.cpp @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "format/KeePass2.h" +#include "crypto/SymmetricCipher.h" +#include "crypto/CryptoHash.h" +#include "crypto/Random.h" +#include "AesKdf.h" + +const QList AesKdf::FIELDS = AesKdf::initFields(); + +QList AesKdf::initFields() +{ + return QList { + Kdf::Field(static_cast(Fields::ROUNDS), "Transform rounds", 1, UINT64_MAX, true), + }; +} + +bool AesKdf::transform(const QByteArray& raw, QByteArray& result) const +{ + QByteArray resultLeft; + QByteArray resultRight; + + QFuture future = QtConcurrent::run(transformKeyRaw, raw.left(16), m_seed, m_rounds, &resultLeft); + + bool rightResult = transformKeyRaw(raw.right(16), m_seed, m_rounds, &resultRight); + bool leftResult = future.result(); + + if (!rightResult || !leftResult) { + return false; + } + + QByteArray transformed; + transformed.append(resultLeft); + transformed.append(resultRight); + + result = CryptoHash::hash(transformed, CryptoHash::Sha256); + return true; +} + +bool AesKdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, quint64 rounds, QByteArray* result) +{ + QByteArray iv(16, 0); + SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, + SymmetricCipher::Encrypt); + if (!cipher.init(seed, iv)) { + qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::init: %s", cipher.errorString().toUtf8().data()); + return false; + } + + *result = key; + + if (!cipher.processInPlace(*result, rounds)) { + qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::processInPlace: %s", cipher.errorString().toUtf8().data()); + return false; + } + + return true; +} + +AesKdf::AesKdf() + : m_rounds(100000ull) + , m_seed(QByteArray(32, 0)) +{ +} + +quint64 AesKdf::rounds() const +{ + return m_rounds; +} + +QByteArray AesKdf::seed() const +{ + return m_seed; +} + +bool AesKdf::setRounds(quint64 rounds) +{ + m_rounds = rounds; + return true; +} + +bool AesKdf::setSeed(const QByteArray& seed) +{ + if (seed.size() != 32) { + return false; + } + + m_seed = seed; + return true; +} + +void AesKdf::randomizeTransformSalt() +{ + setSeed(randomGen()->randomArray(32)); +} + +Kdf::Type AesKdf::type() const +{ + return Kdf::Type::AES; +} + +Kdf* AesKdf::clone() const +{ + return static_cast(new AesKdf(*this)); +} + +const QList AesKdf::fields() const +{ + return FIELDS; +} + +quint64 AesKdf::field(quint32 id) const +{ + switch (static_cast(id)) { + case Fields::ROUNDS: + return m_rounds; + default: + return 0; + } +} + +bool AesKdf::setField(quint32 id, quint64 val) +{ + switch (static_cast(id)) { + case Fields::ROUNDS: + return setRounds(val); + default: + return false; + } +} + +int AesKdf::benchmarkImpl(int msec) const +{ + QByteArray key = QByteArray(16, '\x7E'); + QByteArray seed = QByteArray(32, '\x4B'); + QByteArray iv(16, 0); + + SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, + SymmetricCipher::Encrypt); + cipher.init(seed, iv); + + int rounds = 0; + QElapsedTimer t; + t.start(); + do { + if (!cipher.processInPlace(key, 10000)) { + rounds = -1; + break; + } + rounds += 10000; + } while (!t.hasExpired(msec)); + + return rounds; +} \ No newline at end of file diff --git a/src/crypto/kdf/AesKdf.h b/src/crypto/kdf/AesKdf.h new file mode 100644 index 000000000..3aebc8d50 --- /dev/null +++ b/src/crypto/kdf/AesKdf.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_AESKDF_H +#define KEEPASSX_AESKDF_H + +#include "Kdf.h" + +class AesKdf : public Kdf +{ +public: + AesKdf(); + + bool transform(const QByteArray& raw, QByteArray& result) const override; + void randomizeTransformSalt() override; + Kdf::Type type() const override; + Kdf* clone() const override; + + const QList fields() const override; + quint64 field(quint32 id) const override; + bool setField(quint32 id, quint64 val) override; + + quint64 rounds() const; + QByteArray seed() const; + + bool setRounds(quint64 rounds); + bool setSeed(const QByteArray& seed); + + enum class Fields : quint32 + { + ROUNDS, + SEED + }; + + static const QList FIELDS; + +protected: + int benchmarkImpl(int msec) const override; + +private: + quint64 m_rounds; + QByteArray m_seed; + + static bool transformKeyRaw(const QByteArray& key, const QByteArray& seed, quint64 rounds, QByteArray* result) Q_REQUIRED_RESULT; + static QList initFields(); +}; + +#endif // KEEPASSX_AESKDF_H diff --git a/src/crypto/kdf/Kdf.cpp b/src/crypto/kdf/Kdf.cpp new file mode 100644 index 000000000..99f12645c --- /dev/null +++ b/src/crypto/kdf/Kdf.cpp @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Kdf.h" +#include "Kdf_p.h" + +#include +#include +#include + +Kdf::Field::Field(quint32 id, const QString& name, quint64 min, quint64 max, bool benchmark) + : m_id(id) + , m_name(name) + , m_min(min) + , m_max(max) + , m_benchmark(benchmark) +{ +} + +quint32 Kdf::Field::id() const +{ + return m_id; +} + +QString Kdf::Field::name() const +{ + return m_name; +} + +quint64 Kdf::Field::min() const +{ + return m_min; +} + +quint64 Kdf::Field::max() const +{ + return m_max; +} + +bool Kdf::Field::benchmarked() const +{ + return m_benchmark; +} + +int Kdf::benchmark(int msec) const +{ + BenchmarkThread thread1(msec, this); + BenchmarkThread thread2(msec, this); + + thread1.start(); + thread2.start(); + + thread1.wait(); + thread2.wait(); + + return qMin(thread1.rounds(), thread2.rounds()); +} + +Kdf::BenchmarkThread::BenchmarkThread(int msec, const Kdf* kdf) + : m_msec(msec) + , m_kdf(kdf) +{ +} + +int Kdf::BenchmarkThread::rounds() +{ + return m_rounds; +} + +void Kdf::BenchmarkThread::run() { + m_rounds = m_kdf->benchmarkImpl(m_msec); +} diff --git a/src/crypto/kdf/Kdf.h b/src/crypto/kdf/Kdf.h new file mode 100644 index 000000000..c032a030c --- /dev/null +++ b/src/crypto/kdf/Kdf.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_KDF_H +#define KEEPASSX_KDF_H + +#include + +#include "core/Uuid.h" + +class Kdf +{ +public: + enum class Type { + AES + }; + + class Field { + public: + Field(quint32 id, const QString& name, quint64 min, quint64 max, bool benchmark = false); + + quint32 id() const; + QString name() const; + quint64 min() const; + quint64 max() const; + bool benchmarked() const; + + private: + quint32 m_id; + QString m_name; + quint64 m_min; + quint64 m_max; + bool m_benchmark; + }; + + virtual ~Kdf() {} + virtual bool transform(const QByteArray& raw, QByteArray& result) const = 0; + virtual void randomizeTransformSalt() = 0; + virtual Type type() const = 0; + virtual Kdf* clone() const = 0; + + virtual const QList fields() const = 0; + virtual quint64 field(quint32 id) const = 0; + virtual bool setField(quint32 id, quint64 val) = 0; + virtual int benchmark(int msec) const; + +protected: + virtual int benchmarkImpl(int msec) const = 0; + +private: + class BenchmarkThread; + +}; + +#endif // KEEPASSX_KDF_H diff --git a/src/crypto/kdf/Kdf_p.h b/src/crypto/kdf/Kdf_p.h new file mode 100644 index 000000000..c74837522 --- /dev/null +++ b/src/crypto/kdf/Kdf_p.h @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Kdf.h" + +#include + +#ifndef KEEPASSXC_KDF_P_H +#define KEEPASSXC_KDF_P_H + +class Kdf::BenchmarkThread : public QThread +{ + Q_OBJECT + +public: + explicit BenchmarkThread(int msec, const Kdf* kdf); + + int rounds(); + +protected: + void run(); + +private: + int m_rounds; + int m_msec; + const Kdf* m_kdf; +}; + +#endif // KEEPASSXC_KDF_P_H diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp index 4747d7f87..512b0b58b 100644 --- a/src/format/KeePass1Reader.cpp +++ b/src/format/KeePass1Reader.cpp @@ -21,6 +21,7 @@ #include #include +#include "crypto/kdf/AesKdf.h" #include "core/Database.h" #include "core/Endian.h" #include "core/Entry.h" @@ -159,10 +160,10 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor raiseError("Invalid number of transform rounds"); return nullptr; } - if (!m_db->setTransformRounds(m_transformRounds)) { - raiseError(tr("Unable to calculate master key")); - return nullptr; - } + AesKdf* kdf = new AesKdf(); + kdf->setRounds(m_transformRounds); + kdf->setSeed(m_transformSeed); + db->setKdf(kdf); qint64 contentPos = m_device->pos(); @@ -397,12 +398,11 @@ QByteArray KeePass1Reader::key(const QByteArray& password, const QByteArray& key key.setPassword(password); key.setKeyfileData(keyfileData); - bool ok; - QString errorString; - QByteArray transformedKey = key.transform(m_transformSeed, m_transformRounds, &ok, &errorString); + QByteArray transformedKey; + bool result = key.transform(*m_db->kdf(), transformedKey); - if (!ok) { - raiseError(errorString); + if (!result) { + raiseError("Key transformation failed"); return QByteArray(); } diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index bb737beda..60ebfdc82 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -24,6 +24,7 @@ #include "core/Database.h" #include "core/Endian.h" #include "crypto/CryptoHash.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass1.h" #include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" @@ -53,7 +54,6 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke m_headerEnd = false; m_xmlData.clear(); m_masterSeed.clear(); - m_transformSeed.clear(); m_encryptionIV.clear(); m_streamStartBytes.clear(); m_protectedStreamKey.clear(); @@ -101,14 +101,14 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke } // check if all required headers were present - if (m_masterSeed.isEmpty() || m_transformSeed.isEmpty() || m_encryptionIV.isEmpty() + if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty() || m_db->cipher().isNull()) { raiseError("missing database headers"); return nullptr; } - if (!m_db->setKey(key, m_transformSeed, false)) { + if (!m_db->setKey(key, false)) { raiseError(tr("Unable to calculate master key")); return nullptr; } @@ -378,7 +378,15 @@ void KeePass2Reader::setTransformSeed(const QByteArray& data) raiseError("Invalid transform seed size"); } else { - m_transformSeed = data; + AesKdf* aesKdf; + if (m_db->kdf()->type() == Kdf::Type::AES) { + aesKdf = static_cast(m_db->kdf()); + } else { + aesKdf = new AesKdf(); + m_db->setKdf(aesKdf); + } + + aesKdf->setSeed(data); } } @@ -388,10 +396,18 @@ void KeePass2Reader::setTransformRounds(const QByteArray& data) raiseError("Invalid transform rounds size"); } else { - if (!m_db->setTransformRounds(Endian::bytesToUInt64(data, KeePass2::BYTEORDER))) { - raiseError(tr("Unable to calculate master key")); - } - } + quint64 rounds = Endian::bytesToUInt64(data, KeePass2::BYTEORDER); + + AesKdf* aesKdf; + if (m_db->kdf()->type() == Kdf::Type::AES) { + aesKdf = static_cast(m_db->kdf()); + } else { + aesKdf = new AesKdf(); + m_db->setKdf(aesKdf); + } + + aesKdf->setRounds(rounds); + } } void KeePass2Reader::setEncryptionIV(const QByteArray& data) diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h index f8b962535..f82b4464a 100644 --- a/src/format/KeePass2Reader.h +++ b/src/format/KeePass2Reader.h @@ -64,7 +64,6 @@ private: Database* m_db; QByteArray m_masterSeed; - QByteArray m_transformSeed; QByteArray m_encryptionIV; QByteArray m_streamStartBytes; QByteArray m_protectedStreamKey; diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index f8f60f11e..0d78dc9a8 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -24,6 +24,7 @@ #include "core/Database.h" #include "core/Endian.h" #include "crypto/CryptoHash.h" +#include "crypto/kdf/AesKdf.h" #include "crypto/Random.h" #include "format/KeePass2RandomStream.h" #include "format/KeePass2XmlWriter.h" @@ -45,7 +46,6 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) m_error = false; m_errorStr.clear(); - QByteArray transformSeed = randomGen()->randomArray(32); QByteArray masterSeed = randomGen()->randomArray(32); QByteArray encryptionIV = randomGen()->randomArray(16); QByteArray protectedStreamKey = randomGen()->randomArray(32); @@ -57,7 +57,7 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) return; } - if (!db->transformKeyWithSeed(transformSeed)) { + if (!db->setKey(db->key(), false, true)) { raiseError(tr("Unable to calculate master key")); return; } @@ -81,10 +81,11 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) CHECK_RETURN(writeHeaderField(KeePass2::CompressionFlags, Endian::int32ToBytes(db->compressionAlgo(), KeePass2::BYTEORDER))); + AesKdf* kdf = static_cast(db->kdf()); CHECK_RETURN(writeHeaderField(KeePass2::MasterSeed, masterSeed)); - CHECK_RETURN(writeHeaderField(KeePass2::TransformSeed, db->transformSeed())); + CHECK_RETURN(writeHeaderField(KeePass2::TransformSeed, kdf->seed())); CHECK_RETURN(writeHeaderField(KeePass2::TransformRounds, - Endian::int64ToBytes(db->transformRounds(), + Endian::int64ToBytes(kdf->rounds(), KeePass2::BYTEORDER))); CHECK_RETURN(writeHeaderField(KeePass2::EncryptionIV, encryptionIV)); CHECK_RETURN(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey)); diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index 7c51edfd4..dd18f3e0e 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -22,6 +22,7 @@ #include "core/Group.h" #include "core/Metadata.h" #include "crypto/SymmetricCipher.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass2.h" #include "keys/CompositeKey.h" @@ -56,7 +57,7 @@ void DatabaseSettingsWidget::load(Database* db) m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled()); m_ui->defaultUsernameEdit->setText(meta->defaultUserName()); m_ui->AlgorithmComboBox->setCurrentIndex(SymmetricCipher::cipherToAlgorithm(m_db->cipher())); - m_ui->transformRoundsSpinBox->setValue(m_db->transformRounds()); + m_ui->transformRoundsSpinBox->setValue(static_cast(m_db->kdf())->rounds()); if (meta->historyMaxItems() > -1) { m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems()); m_ui->historyMaxItemsCheckBox->setChecked(true); @@ -88,9 +89,10 @@ void DatabaseSettingsWidget::save() m_db->setCipher(SymmetricCipher::algorithmToCipher(static_cast (m_ui->AlgorithmComboBox->currentIndex()))); meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked()); - if (static_cast(m_ui->transformRoundsSpinBox->value()) != m_db->transformRounds()) { + AesKdf* kdf = static_cast(m_db->kdf()); + if (static_cast(m_ui->transformRoundsSpinBox->value()) != kdf->rounds()) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - m_db->setTransformRounds(m_ui->transformRoundsSpinBox->value()); + kdf->setRounds(m_ui->transformRoundsSpinBox->value()); QApplication::restoreOverrideCursor(); } @@ -135,7 +137,7 @@ void DatabaseSettingsWidget::reject() void DatabaseSettingsWidget::transformRoundsBenchmark() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - int rounds = CompositeKey::transformKeyBenchmark(1000); + int rounds = m_db->kdf()->benchmark(1000); if (rounds != -1) { m_ui->transformRoundsSpinBox->setValue(rounds); } diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 83a6725c9..88ac4cf6a 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -17,13 +17,14 @@ */ #include "CompositeKey.h" -#include "CompositeKey_p.h" #include "ChallengeResponseKey.h" #include #include #include +#include "crypto/kdf/Kdf.h" +#include "format/KeePass2.h" #include "core/Global.h" #include "crypto/CryptoHash.h" #include "crypto/SymmetricCipher.h" @@ -91,64 +92,9 @@ QByteArray CompositeKey::rawKey() const return cryptoHash.result(); } -QByteArray CompositeKey::transform(const QByteArray& seed, quint64 rounds, - bool* ok, QString* errorString) const +bool CompositeKey::transform(const Kdf& kdf, QByteArray& result) const { - Q_ASSERT(seed.size() == 32); - Q_ASSERT(rounds > 0); - - bool okLeft; - QString errorStringLeft; - bool okRight; - QString errorStringRight; - - QByteArray key = rawKey(); - - QFuture future = QtConcurrent::run(transformKeyRaw, key.left(16), seed, rounds, - &okLeft, &errorStringLeft); - QByteArray result2 = transformKeyRaw(key.right(16), seed, rounds, &okRight, &errorStringRight); - - QByteArray transformed; - transformed.append(future.result()); - transformed.append(result2); - - *ok = (okLeft && okRight); - - if (!okLeft) { - *errorString = errorStringLeft; - return QByteArray(); - } - - if (!okRight) { - *errorString = errorStringRight; - return QByteArray(); - } - - return CryptoHash::hash(transformed, CryptoHash::Sha256); -} - -QByteArray CompositeKey::transformKeyRaw(const QByteArray& key, const QByteArray& seed, - quint64 rounds, bool* ok, QString* errorString) -{ - QByteArray iv(16, 0); - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, - SymmetricCipher::Encrypt); - if (!cipher.init(seed, iv)) { - *ok = false; - *errorString = cipher.errorString(); - return QByteArray(); - } - - QByteArray result = key; - - if (!cipher.processInPlace(result, rounds)) { - *ok = false; - *errorString = cipher.errorString(); - return QByteArray(); - } - - *ok = true; - return result; + return kdf.transform(rawKey(), result); } bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const @@ -183,53 +129,3 @@ void CompositeKey::addChallengeResponseKey(QSharedPointer { m_challengeResponseKeys.append(key); } - - -int CompositeKey::transformKeyBenchmark(int msec) -{ - TransformKeyBenchmarkThread thread1(msec); - TransformKeyBenchmarkThread thread2(msec); - - thread1.start(); - thread2.start(); - - thread1.wait(); - thread2.wait(); - - return qMin(thread1.rounds(), thread2.rounds()); -} - - -TransformKeyBenchmarkThread::TransformKeyBenchmarkThread(int msec) - : m_msec(msec) - , m_rounds(0) -{ - Q_ASSERT(msec > 0); -} - -int TransformKeyBenchmarkThread::rounds() -{ - return m_rounds; -} - -void TransformKeyBenchmarkThread::run() -{ - QByteArray key = QByteArray(16, '\x7E'); - QByteArray seed = QByteArray(32, '\x4B'); - QByteArray iv(16, 0); - - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, - SymmetricCipher::Encrypt); - cipher.init(seed, iv); - - QElapsedTimer t; - t.start(); - - do { - if (!cipher.processInPlace(key, 10000)) { - m_rounds = -1; - return; - } - m_rounds += 10000; - } while (!t.hasExpired(m_msec)); -} diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h index d9c4e3a9e..35bc309fa 100644 --- a/src/keys/CompositeKey.h +++ b/src/keys/CompositeKey.h @@ -23,6 +23,7 @@ #include #include +#include "crypto/kdf/Kdf.h" #include "keys/Key.h" #include "keys/ChallengeResponseKey.h" @@ -38,19 +39,13 @@ public: CompositeKey& operator=(const CompositeKey& key); QByteArray rawKey() const; - QByteArray transform(const QByteArray& seed, quint64 rounds, - bool* ok, QString* errorString) const; + bool transform(const Kdf& kdf, QByteArray& result) const Q_REQUIRED_RESULT; bool challenge(const QByteArray& seed, QByteArray &result) const; void addKey(const Key& key); void addChallengeResponseKey(QSharedPointer key); - static int transformKeyBenchmark(int msec); - private: - static QByteArray transformKeyRaw(const QByteArray& key, const QByteArray& seed, - quint64 rounds, bool* ok, QString* errorString); - QList m_keys; QList> m_challengeResponseKeys; }; diff --git a/src/keys/CompositeKey_p.h b/src/keys/CompositeKey_p.h deleted file mode 100644 index 27fa7abc4..000000000 --- a/src/keys/CompositeKey_p.h +++ /dev/null @@ -1,39 +0,0 @@ -/* -* Copyright (C) 2012 Felix Geyer -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 2 or (at your option) -* version 3 of the License. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#ifndef KEEPASSX_COMPOSITEKEY_P_H -#define KEEPASSX_COMPOSITEKEY_P_H - -#include - -class TransformKeyBenchmarkThread : public QThread -{ - Q_OBJECT - -public: - explicit TransformKeyBenchmarkThread(int msec); - int rounds(); - -protected: - void run(); - -private: - int m_msec; - int m_rounds; -}; - -#endif // KEEPASSX_COMPOSITEKEY_P_H diff --git a/tests/TestKeePass1Reader.cpp b/tests/TestKeePass1Reader.cpp index f60846d26..7e015c2bc 100644 --- a/tests/TestKeePass1Reader.cpp +++ b/tests/TestKeePass1Reader.cpp @@ -26,6 +26,7 @@ #include "core/Group.h" #include "core/Metadata.h" #include "crypto/Crypto.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass1Reader.h" #include "format/KeePass2Reader.h" #include "format/KeePass2Writer.h" @@ -110,7 +111,7 @@ void TestKeePass1Reader::testBasic() void TestKeePass1Reader::testMasterKey() { QVERIFY(m_db->hasKey()); - QCOMPARE(m_db->transformRounds(), static_cast(713)); + QCOMPARE(static_cast(m_db->kdf())->rounds(), static_cast(713)); } void TestKeePass1Reader::testCustomIcons() diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index 95a72a49f..990878e2d 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -27,6 +27,7 @@ #include "core/Metadata.h" #include "core/Tools.h" #include "crypto/Crypto.h" +#include "crypto/kdf/AesKdf.h" #include "crypto/CryptoHash.h" #include "format/KeePass2Reader.h" #include "format/KeePass2Writer.h" @@ -45,21 +46,23 @@ void TestKeys::testComposite() QScopedPointer compositeKey1(new CompositeKey()); QScopedPointer passwordKey1(new PasswordKey()); QScopedPointer passwordKey2(new PasswordKey("test")); - bool ok; - QString errorString; // make sure that addKey() creates a copy of the keys compositeKey1->addKey(*passwordKey1); compositeKey1->addKey(*passwordKey2); - QByteArray transformed = compositeKey1->transform(QByteArray(32, '\0'), 1, &ok, &errorString); - QVERIFY(ok); - QCOMPARE(transformed.size(), 32); + AesKdf kdf; + kdf.setRounds(1); + QByteArray transformed1; + QVERIFY(compositeKey1->transform(kdf, transformed1)); + QCOMPARE(transformed1.size(), 32); // make sure the subkeys are copied QScopedPointer compositeKey2(compositeKey1->clone()); - QCOMPARE(compositeKey2->transform(QByteArray(32, '\0'), 1, &ok, &errorString), transformed); - QVERIFY(ok); + QByteArray transformed2; + QVERIFY(compositeKey2->transform(kdf, transformed2)); + QCOMPARE(transformed2.size(), 32); + QCOMPARE(transformed1, transformed2); QScopedPointer compositeKey3(new CompositeKey()); QScopedPointer compositeKey4(new CompositeKey()); @@ -208,10 +211,12 @@ void TestKeys::benchmarkTransformKey() QByteArray seed(32, '\x4B'); - bool ok; - QString errorString; + QByteArray result; + AesKdf kdf; + kdf.setSeed(seed); + kdf.setRounds(1e6); QBENCHMARK { - compositeKey.transform(seed, 1e6, &ok, &errorString); - } + Q_UNUSED(compositeKey.transform(kdf, result)); + }; } From 23347b392f0c15a3a2fc2ca19601049d433315d2 Mon Sep 17 00:00:00 2001 From: angelsl Date: Sun, 12 Nov 2017 20:58:41 +0800 Subject: [PATCH 02/39] Fix typo in Endian and add uint{16,32,64}ToBytes --- src/core/Endian.cpp | 47 ++++++++++++++++++++++++++++++++++++++++++++- src/core/Endian.h | 3 +++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/core/Endian.cpp b/src/core/Endian.cpp index bf838abcb..3944bba2d 100644 --- a/src/core/Endian.cpp +++ b/src/core/Endian.cpp @@ -139,7 +139,7 @@ QByteArray int16ToBytes(qint16 num, QSysInfo::Endian byteOrder) qToLittleEndian(num, reinterpret_cast(ba.data())); } else { - qToBigEndian(num, reinterpret_cast(ba.data())); + qToBigEndian(num, reinterpret_cast(ba.data())); } return ba; @@ -175,6 +175,51 @@ QByteArray int64ToBytes(qint64 num, QSysInfo::Endian byteOrder) return ba; } +QByteArray uint16ToBytes(quint16 num, QSysInfo::Endian byteOrder) +{ + QByteArray ba; + ba.resize(2); + + if (byteOrder == QSysInfo::LittleEndian) { + qToLittleEndian(num, reinterpret_cast(ba.data())); + } + else { + qToBigEndian(num, reinterpret_cast(ba.data())); + } + + return ba; +} + +QByteArray uint32ToBytes(quint32 num, QSysInfo::Endian byteOrder) +{ + QByteArray ba; + ba.resize(4); + + if (byteOrder == QSysInfo::LittleEndian) { + qToLittleEndian(num, reinterpret_cast(ba.data())); + } + else { + qToBigEndian(num, reinterpret_cast(ba.data())); + } + + return ba; +} + +QByteArray uint64ToBytes(quint64 num, QSysInfo::Endian byteOrder) +{ + QByteArray ba; + ba.resize(8); + + if (byteOrder == QSysInfo::LittleEndian) { + qToLittleEndian(num, reinterpret_cast(ba.data())); + } + else { + qToBigEndian(num, reinterpret_cast(ba.data())); + } + + return ba; +} + bool writeInt16(qint16 num, QIODevice* device, QSysInfo::Endian byteOrder) { QByteArray ba = int16ToBytes(num, byteOrder); diff --git a/src/core/Endian.h b/src/core/Endian.h index 0cea617c6..0b969b3ed 100644 --- a/src/core/Endian.h +++ b/src/core/Endian.h @@ -42,6 +42,9 @@ namespace Endian { QByteArray int16ToBytes(qint16 num, QSysInfo::Endian byteOrder); QByteArray int32ToBytes(qint32 num, QSysInfo::Endian byteOrder); QByteArray int64ToBytes(qint64 num, QSysInfo::Endian byteOrder); + QByteArray uint16ToBytes(quint16 num, QSysInfo::Endian byteOrder); + QByteArray uint32ToBytes(quint32 num, QSysInfo::Endian byteOrder); + QByteArray uint64ToBytes(quint64 num, QSysInfo::Endian byteOrder); bool writeInt16(qint16 num, QIODevice* device, QSysInfo::Endian byteOrder); bool writeInt32(qint32 num, QIODevice* device, QSysInfo::Endian byteOrder); From 656836950ef6e03b8ae4d132694386e966df0b11 Mon Sep 17 00:00:00 2001 From: angelsl Date: Sun, 12 Nov 2017 21:02:23 +0800 Subject: [PATCH 03/39] Add support for stream cipher subclasses in SymmetricCipherStream --- src/streams/SymmetricCipherStream.cpp | 82 ++++++++++++++------------- src/streams/SymmetricCipherStream.h | 4 +- 2 files changed, 47 insertions(+), 39 deletions(-) diff --git a/src/streams/SymmetricCipherStream.cpp b/src/streams/SymmetricCipherStream.cpp index 643aa0339..ece2642b5 100644 --- a/src/streams/SymmetricCipherStream.cpp +++ b/src/streams/SymmetricCipherStream.cpp @@ -24,7 +24,7 @@ SymmetricCipherStream::SymmetricCipherStream(QIODevice* baseDevice, SymmetricCip , m_bufferPos(0) , m_bufferFilling(false) , m_error(false) - , m_isInitalized(false) + , m_isInitialized(false) , m_dataWritten(false) { } @@ -36,12 +36,12 @@ SymmetricCipherStream::~SymmetricCipherStream() bool SymmetricCipherStream::init(const QByteArray& key, const QByteArray& iv) { - m_isInitalized = m_cipher->init(key, iv); - if (!m_isInitalized) { + m_isInitialized = m_cipher->init(key, iv); + if (!m_isInitialized) { setErrorString(m_cipher->errorString()); } - - return m_isInitalized; + m_streamCipher = m_cipher->blockSize() == 1; + return m_isInitialized; } void SymmetricCipherStream::resetInternalState() @@ -56,11 +56,8 @@ void SymmetricCipherStream::resetInternalState() bool SymmetricCipherStream::open(QIODevice::OpenMode mode) { - if (!m_isInitalized) { - return false; - } + return m_isInitialized && LayeredStream::open(mode); - return LayeredStream::open(mode); } bool SymmetricCipherStream::reset() @@ -127,11 +124,11 @@ bool SymmetricCipherStream::readBlock() QByteArray newData; if (m_bufferFilling) { - newData.resize(m_cipher->blockSize() - m_buffer.size()); + newData.resize(blockSize() - m_buffer.size()); } else { m_buffer.clear(); - newData.resize(m_cipher->blockSize()); + newData.resize(blockSize()); } int readResult = m_baseDevice->read(newData.data(), newData.size()); @@ -140,12 +137,11 @@ bool SymmetricCipherStream::readBlock() m_error = true; setErrorString(m_baseDevice->errorString()); return false; - } - else { + } else { m_buffer.append(newData.left(readResult)); } - if (m_buffer.size() != m_cipher->blockSize()) { + if (!m_streamCipher && m_buffer.size() != blockSize()) { m_bufferFilling = true; return false; } @@ -159,26 +155,28 @@ bool SymmetricCipherStream::readBlock() m_bufferFilling = false; if (m_baseDevice->atEnd()) { - // PKCS7 padding - quint8 padLength = m_buffer.at(m_buffer.size() - 1); + if (!m_streamCipher) { + // PKCS7 padding + quint8 padLength = m_buffer.at(m_buffer.size() - 1); - if (padLength == m_cipher->blockSize()) { - Q_ASSERT(m_buffer == QByteArray(m_cipher->blockSize(), m_cipher->blockSize())); - // full block with just padding: discard - m_buffer.clear(); - return false; - } - else if (padLength > m_cipher->blockSize()) { - // invalid padding - m_error = true; - setErrorString("Invalid padding."); - return false; - } - else { - Q_ASSERT(m_buffer.right(padLength) == QByteArray(padLength, padLength)); - // resize buffer to strip padding - m_buffer.resize(m_cipher->blockSize() - padLength); - return true; + if (padLength == blockSize()) { + Q_ASSERT(m_buffer == QByteArray(blockSize(), blockSize())); + // full block with just padding: discard + m_buffer.clear(); + return false; + } else if (padLength > blockSize()) { + // invalid padding + m_error = true; + setErrorString("Invalid padding."); + return false; + } else { + Q_ASSERT(m_buffer.right(padLength) == QByteArray(padLength, padLength)); + // resize buffer to strip padding + m_buffer.resize(blockSize() - padLength); + return true; + } + } else { + return m_buffer.size() > 0; } } else { @@ -200,14 +198,14 @@ qint64 SymmetricCipherStream::writeData(const char* data, qint64 maxSize) qint64 offset = 0; while (bytesRemaining > 0) { - int bytesToCopy = qMin(bytesRemaining, static_cast(m_cipher->blockSize() - m_buffer.size())); + int bytesToCopy = qMin(bytesRemaining, static_cast(blockSize() - m_buffer.size())); m_buffer.append(data + offset, bytesToCopy); offset += bytesToCopy; bytesRemaining -= bytesToCopy; - if (m_buffer.size() == m_cipher->blockSize()) { + if (m_buffer.size() == blockSize()) { if (!writeBlock(false)) { if (m_error) { return -1; @@ -224,11 +222,11 @@ qint64 SymmetricCipherStream::writeData(const char* data, qint64 maxSize) bool SymmetricCipherStream::writeBlock(bool lastBlock) { - Q_ASSERT(lastBlock || (m_buffer.size() == m_cipher->blockSize())); + Q_ASSERT(m_streamCipher || lastBlock || (m_buffer.size() == blockSize())); - if (lastBlock) { + if (lastBlock && !m_streamCipher) { // PKCS7 padding - int padLen = m_cipher->blockSize() - m_buffer.size(); + int padLen = blockSize() - m_buffer.size(); for (int i = 0; i < padLen; i++) { m_buffer.append(static_cast(padLen)); } @@ -250,3 +248,11 @@ bool SymmetricCipherStream::writeBlock(bool lastBlock) return true; } } + +int SymmetricCipherStream::blockSize() const { + if (m_streamCipher) { + return 1024; + } else { + return m_cipher->blockSize(); + } +} diff --git a/src/streams/SymmetricCipherStream.h b/src/streams/SymmetricCipherStream.h index b6228e1b8..b68dba01f 100644 --- a/src/streams/SymmetricCipherStream.h +++ b/src/streams/SymmetricCipherStream.h @@ -45,14 +45,16 @@ private: void resetInternalState(); bool readBlock(); bool writeBlock(bool lastBlock); + int blockSize() const; const QScopedPointer m_cipher; QByteArray m_buffer; int m_bufferPos; bool m_bufferFilling; bool m_error; - bool m_isInitalized; + bool m_isInitialized; bool m_dataWritten; + bool m_streamCipher; }; #endif // KEEPASSX_SYMMETRICCIPHERSTREAM_H From 663b8dcb0826295a701b5891924658f870b5cabc Mon Sep 17 00:00:00 2001 From: angelsl Date: Sun, 12 Nov 2017 21:21:26 +0800 Subject: [PATCH 04/39] Remove unused SymmetricCipherSalsa20 --- src/crypto/SymmetricCipherSalsa20.cpp | 112 -------------------------- src/crypto/SymmetricCipherSalsa20.h | 53 ------------ 2 files changed, 165 deletions(-) delete mode 100644 src/crypto/SymmetricCipherSalsa20.cpp delete mode 100644 src/crypto/SymmetricCipherSalsa20.h diff --git a/src/crypto/SymmetricCipherSalsa20.cpp b/src/crypto/SymmetricCipherSalsa20.cpp deleted file mode 100644 index 7e477656a..000000000 --- a/src/crypto/SymmetricCipherSalsa20.cpp +++ /dev/null @@ -1,112 +0,0 @@ -/* -* Copyright (C) 2010 Felix Geyer -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 2 or (at your option) -* version 3 of the License. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#include "SymmetricCipherSalsa20.h" - -SymmetricCipherSalsa20::SymmetricCipherSalsa20(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction) -{ - Q_ASSERT(algo == SymmetricCipher::Salsa20); - Q_UNUSED(algo); - - Q_ASSERT(mode == SymmetricCipher::Stream); - Q_UNUSED(mode); - - Q_UNUSED(direction); -} - -SymmetricCipherSalsa20::~SymmetricCipherSalsa20() -{ -} - -bool SymmetricCipherSalsa20::init() -{ - return true; -} - -bool SymmetricCipherSalsa20::setKey(const QByteArray& key) -{ - Q_ASSERT((key.size() == 16) || (key.size() == 32)); - - m_key = key; - ECRYPT_keysetup(&m_ctx, reinterpret_cast(m_key.constData()), m_key.size()*8, 64); - - return true; -} - -bool SymmetricCipherSalsa20::setIv(const QByteArray& iv) -{ - Q_ASSERT(iv.size() == 8); - - m_iv = iv; - ECRYPT_ivsetup(&m_ctx, reinterpret_cast(m_iv.constData())); - - return true; -} - -QByteArray SymmetricCipherSalsa20::process(const QByteArray& data, bool* ok) -{ - Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0)); - - QByteArray result; - result.resize(data.size()); - - ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast(data.constData()), - reinterpret_cast(result.data()), data.size()); - - *ok = true; - return result; -} - -bool SymmetricCipherSalsa20::processInPlace(QByteArray& data) -{ - Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0)); - - ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast(data.constData()), - reinterpret_cast(data.data()), data.size()); - - return true; -} - -bool SymmetricCipherSalsa20::processInPlace(QByteArray& data, quint64 rounds) -{ - Q_ASSERT((data.size() < blockSize()) || ((data.size() % blockSize()) == 0)); - - for (quint64 i = 0; i != rounds; ++i) { - ECRYPT_encrypt_bytes(&m_ctx, reinterpret_cast(data.constData()), - reinterpret_cast(data.data()), data.size()); - } - - return true; -} - -bool SymmetricCipherSalsa20::reset() -{ - ECRYPT_ivsetup(&m_ctx, reinterpret_cast(m_iv.constData())); - - return true; -} - -int SymmetricCipherSalsa20::blockSize() const -{ - return 64; -} - -QString SymmetricCipherSalsa20::errorString() const -{ - return QString(); -} diff --git a/src/crypto/SymmetricCipherSalsa20.h b/src/crypto/SymmetricCipherSalsa20.h deleted file mode 100644 index 443d4ec8b..000000000 --- a/src/crypto/SymmetricCipherSalsa20.h +++ /dev/null @@ -1,53 +0,0 @@ -/* -* Copyright (C) 2010 Felix Geyer -* -* This program is free software: you can redistribute it and/or modify -* it under the terms of the GNU General Public License as published by -* the Free Software Foundation, either version 2 or (at your option) -* version 3 of the License. -* -* This program is distributed in the hope that it will be useful, -* but WITHOUT ANY WARRANTY; without even the implied warranty of -* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -* GNU General Public License for more details. -* -* You should have received a copy of the GNU General Public License -* along with this program. If not, see . -*/ - -#ifndef KEEPASSX_SYMMETRICCIPHERSALSA20_H -#define KEEPASSX_SYMMETRICCIPHERSALSA20_H - -#include "crypto/SymmetricCipher.h" -#include "crypto/SymmetricCipherBackend.h" -#include "crypto/salsa20/ecrypt-sync.h" - -class SymmetricCipherSalsa20 : public SymmetricCipherBackend -{ -public: - SymmetricCipherSalsa20(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction); - ~SymmetricCipherSalsa20(); - bool init(); - void setAlgorithm(SymmetricCipher::Algorithm algo); - void setMode(SymmetricCipher::Mode mode); - void setDirection(SymmetricCipher::Direction direction); - bool setKey(const QByteArray& key); - bool setIv(const QByteArray& iv); - - QByteArray process(const QByteArray& data, bool* ok); - bool processInPlace(QByteArray& data); - bool processInPlace(QByteArray& data, quint64 rounds); - - bool reset(); - int blockSize() const; - - QString errorString() const; - -private: - ECRYPT_ctx m_ctx; - QByteArray m_key; - QByteArray m_iv; -}; - -#endif // KEEPASSX_SYMMETRICCIPHERSALSA20_H From a5ec7fc704a1271d34534e8976e494598565b2fc Mon Sep 17 00:00:00 2001 From: angelsl Date: Sun, 12 Nov 2017 21:48:53 +0800 Subject: [PATCH 05/39] Make HashedBlockStream::atEnd report EOF correctly --- src/streams/HashedBlockStream.cpp | 4 ++++ src/streams/HashedBlockStream.h | 2 ++ 2 files changed, 6 insertions(+) diff --git a/src/streams/HashedBlockStream.cpp b/src/streams/HashedBlockStream.cpp index ec59769aa..0b11a9ca0 100644 --- a/src/streams/HashedBlockStream.cpp +++ b/src/streams/HashedBlockStream.cpp @@ -256,3 +256,7 @@ bool HashedBlockStream::writeHashedBlock() return true; } + +bool HashedBlockStream::atEnd() const { + return m_eof; +} diff --git a/src/streams/HashedBlockStream.h b/src/streams/HashedBlockStream.h index 93e4af59c..60a15a8c9 100644 --- a/src/streams/HashedBlockStream.h +++ b/src/streams/HashedBlockStream.h @@ -34,6 +34,8 @@ public: bool reset() override; void close() override; + bool atEnd() const override; + protected: qint64 readData(char* data, qint64 maxSize) override; qint64 writeData(const char* data, qint64 maxSize) override; From 33974d710a2e93ab39878a8d827c722bffa610b5 Mon Sep 17 00:00:00 2001 From: angelsl Date: Mon, 13 Nov 2017 02:02:32 +0800 Subject: [PATCH 06/39] Move constants in KeePass2.h to KeePass2.cpp and add a list of KDFs and ciphers --- src/CMakeLists.txt | 2 +- src/crypto/SymmetricCipher.h | 1 + src/format/KeePass2.cpp | 70 ++++++++++++++++++++++++++++++++++++ src/format/KeePass2.h | 29 +++++++++++++-- 4 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 src/format/KeePass2.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5ee71a28a..c04901a11 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -82,7 +82,7 @@ set(keepassx_SOURCES format/CsvExporter.cpp format/KeePass1.h format/KeePass1Reader.cpp - format/KeePass2.h + format/KeePass2.cpp format/KeePass2RandomStream.cpp format/KeePass2Reader.cpp format/KeePass2Repair.cpp diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h index 81e13f385..31d10466b 100644 --- a/src/crypto/SymmetricCipher.h +++ b/src/crypto/SymmetricCipher.h @@ -24,6 +24,7 @@ #include "crypto/SymmetricCipherBackend.h" #include "format/KeePass2.h" +#include "core/Uuid.h" class SymmetricCipher { diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp new file mode 100644 index 000000000..01c15a871 --- /dev/null +++ b/src/format/KeePass2.cpp @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "KeePass2.h" +#include "crypto/CryptoHash.h" +#include "crypto/kdf/AesKdf.h" +#include "core/Uuid.h" + +const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff")); +const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); + +const Uuid KeePass2::KDF_AES = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA")); + +const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); + +const QList KeePass2::CIPHERS { + KeePass2::UuidNamePair(KeePass2::CIPHER_AES, "AES: 256-bit"), + KeePass2::UuidNamePair(KeePass2::CIPHER_TWOFISH, "Twofish: 256-bit"), +}; +const QList KeePass2::KDFS { + KeePass2::UuidNamePair(KeePass2::KDF_AES, "AES-KDF"), +}; + +Kdf* KeePass2::uuidToKdf(const Uuid& uuid) { + if (uuid == KDF_AES) { + return static_cast(new AesKdf()); + } + + return nullptr; +} + +Uuid KeePass2::kdfToUuid(const Kdf& kdf) +{ + switch (kdf.type()) { + case Kdf::Type::AES: + return KDF_AES; + default: + return Uuid(); + } +} + +KeePass2::UuidNamePair::UuidNamePair(const Uuid& uuid, const QString& name) + : m_uuid(uuid) + , m_name(name) +{ +} + +Uuid KeePass2::UuidNamePair::uuid() const +{ + return m_uuid; +} + +QString KeePass2::UuidNamePair::name() const +{ + return m_name; +} diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index 91ee48293..b3c7ee559 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -19,7 +19,10 @@ #define KEEPASSX_KEEPASS2_H #include +#include +#include "crypto/SymmetricCipher.h" +#include "crypto/kdf/Kdf.h" #include "core/Uuid.h" namespace KeePass2 @@ -32,10 +35,27 @@ namespace KeePass2 const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian; - const Uuid CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff")); - const Uuid CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); + extern const Uuid CIPHER_AES; + extern const Uuid CIPHER_TWOFISH; - const QByteArray INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); + extern const Uuid KDF_AES; + + extern const QByteArray INNER_STREAM_SALSA20_IV; + + class UuidNamePair + { + public: + UuidNamePair(const Uuid& uuid, const QString& name); + Uuid uuid() const; + QString name() const; + + private: + Uuid m_uuid; + QString m_name; + }; + + extern const QList CIPHERS; + extern const QList KDFS; enum HeaderFieldID { @@ -57,6 +77,9 @@ namespace KeePass2 ArcFourVariant = 1, Salsa20 = 2 }; + + Kdf* uuidToKdf(const Uuid& uuid); + Uuid kdfToUuid(const Kdf& kdf); } #endif // KEEPASSX_KEEPASS2_H From 4532108678e5b86cd064c9d234db55055a86a17c Mon Sep 17 00:00:00 2001 From: angelsl Date: Mon, 13 Nov 2017 01:56:01 +0800 Subject: [PATCH 07/39] Add support for KDFs and cyphers to the db settings widget --- src/gui/DatabaseSettingsWidget.cpp | 151 +++++++++++++-- src/gui/DatabaseSettingsWidget.h | 11 ++ src/gui/DatabaseSettingsWidget.ui | 295 ++++++++++++----------------- tests/gui/TestGui.cpp | 5 +- 4 files changed, 265 insertions(+), 197 deletions(-) diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index dd18f3e0e..5f1dfeedf 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -18,17 +18,28 @@ #include "DatabaseSettingsWidget.h" #include "ui_DatabaseSettingsWidget.h" +#include +#include +#include +#include +#include +#include +#include + #include "core/Database.h" #include "core/Group.h" #include "core/Metadata.h" #include "crypto/SymmetricCipher.h" -#include "crypto/kdf/AesKdf.h" #include "format/KeePass2.h" #include "keys/CompositeKey.h" +#include "format/KeePass2.h" +#include "crypto/kdf/Kdf.h" +#include "MessageBox.h" DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) : DialogyWidget(parent) , m_ui(new Ui::DatabaseSettingsWidget()) + , m_benchmarkField(nullptr) , m_db(nullptr) { m_ui->setupUi(this); @@ -39,7 +50,7 @@ DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) m_ui->historyMaxItemsSpinBox, SLOT(setEnabled(bool))); connect(m_ui->historyMaxSizeCheckBox, SIGNAL(toggled(bool)), m_ui->historyMaxSizeSpinBox, SLOT(setEnabled(bool))); - connect(m_ui->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark())); + connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeKdf(int))); } DatabaseSettingsWidget::~DatabaseSettingsWidget() @@ -56,8 +67,6 @@ void DatabaseSettingsWidget::load(Database* db) m_ui->dbDescriptionEdit->setText(meta->description()); m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled()); m_ui->defaultUsernameEdit->setText(meta->defaultUserName()); - m_ui->AlgorithmComboBox->setCurrentIndex(SymmetricCipher::cipherToAlgorithm(m_db->cipher())); - m_ui->transformRoundsSpinBox->setValue(static_cast(m_db->kdf())->rounds()); if (meta->historyMaxItems() > -1) { m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems()); m_ui->historyMaxItemsCheckBox->setChecked(true); @@ -76,6 +85,33 @@ void DatabaseSettingsWidget::load(Database* db) m_ui->historyMaxSizeCheckBox->setChecked(false); } + m_ui->algorithmComboBox->clear(); + for (QList::const_iterator ciphers = KeePass2::CIPHERS.constBegin(); + ciphers != KeePass2::CIPHERS.constEnd(); ++ciphers) { + KeePass2::UuidNamePair cipher = *ciphers; + m_ui->algorithmComboBox->addItem(cipher.name(), cipher.uuid().toByteArray()); + } + int cipherIndex = m_ui->algorithmComboBox->findData(m_db->cipher().toByteArray()); + if (cipherIndex > -1) { + m_ui->algorithmComboBox->setCurrentIndex(cipherIndex); + } + + bool blockSignals = m_ui->kdfComboBox->signalsBlocked(); + m_ui->kdfComboBox->blockSignals(true); + m_kdf.reset(m_db->kdf()->clone()); + m_ui->kdfComboBox->clear(); + for (QList::const_iterator kdfs = KeePass2::KDFS.constBegin(); + kdfs != KeePass2::KDFS.constEnd(); ++kdfs) { + KeePass2::UuidNamePair kdf = *kdfs; + m_ui->kdfComboBox->addItem(kdf.name(), kdf.uuid().toByteArray()); + } + int kdfIndex = m_ui->kdfComboBox->findData(KeePass2::kdfToUuid(*m_kdf).toByteArray()); + if (kdfIndex > -1) { + m_ui->kdfComboBox->setCurrentIndex(kdfIndex); + } + displayKdf(*m_kdf); + m_ui->kdfComboBox->blockSignals(blockSignals); + m_ui->dbNameEdit->setFocus(); } @@ -86,15 +122,7 @@ void DatabaseSettingsWidget::save() meta->setName(m_ui->dbNameEdit->text()); meta->setDescription(m_ui->dbDescriptionEdit->text()); meta->setDefaultUserName(m_ui->defaultUsernameEdit->text()); - m_db->setCipher(SymmetricCipher::algorithmToCipher(static_cast - (m_ui->AlgorithmComboBox->currentIndex()))); meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked()); - AesKdf* kdf = static_cast(m_db->kdf()); - if (static_cast(m_ui->transformRoundsSpinBox->value()) != kdf->rounds()) { - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - kdf->setRounds(m_ui->transformRoundsSpinBox->value()); - QApplication::restoreOverrideCursor(); - } bool truncate = false; @@ -126,6 +154,33 @@ void DatabaseSettingsWidget::save() truncateHistories(); } + m_db->setCipher(Uuid(m_ui->algorithmComboBox->currentData().toByteArray())); + + bool kdfValid = true; + for (int i = 0; i < m_kdfFields.size(); ++i) { + QPair field = m_kdfFields.at(i); + kdfValid &= m_kdf->setField(field.first, static_cast(qMax(0, field.second->value()))); + if (!kdfValid) { + break; + } + } + + if (kdfValid) { + Kdf* kdf = m_kdf.take(); + bool ok = m_db->changeKdf(kdf); + if (!ok) { + MessageBox::warning(this, tr("KDF unchanged"), + tr("Failed to transform key with new KDF parameters; KDF unchanged."), + QMessageBox::Ok); + delete kdf; // m_db has not taken ownership + } + } else { + MessageBox::warning(this, tr("KDF unchanged"), + tr("Invalid KDF parameters; KDF unchanged."), + QMessageBox::Ok); + } + clearKdfWidgets(); + emit editFinished(true); } @@ -136,11 +191,12 @@ void DatabaseSettingsWidget::reject() void DatabaseSettingsWidget::transformRoundsBenchmark() { - QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - int rounds = m_db->kdf()->benchmark(1000); - if (rounds != -1) { - m_ui->transformRoundsSpinBox->setValue(rounds); + if (m_benchmarkField == nullptr) { + Q_ASSERT(false); + return; } + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + m_benchmarkField->setValue(m_kdf->benchmark(1000)); QApplication::restoreOverrideCursor(); } @@ -151,3 +207,66 @@ void DatabaseSettingsWidget::truncateHistories() entry->truncateHistory(); } } + +void DatabaseSettingsWidget::changeKdf(int index) { + QByteArray uuidBytes = m_ui->kdfComboBox->itemData(index).toByteArray(); + if (uuidBytes.size() != Uuid::Length) { + return; + } + Kdf* newKdf = KeePass2::uuidToKdf(Uuid(uuidBytes)); + if (newKdf != nullptr) { + m_kdf.reset(newKdf); + displayKdf(*m_kdf); + } +} + +void DatabaseSettingsWidget::displayKdf(const Kdf& kdf) +{ + clearKdfWidgets(); + + QWidget* lastWidget = nullptr; + int columnStart = m_ui->gridLayout->columnCount(); + int rowStart = m_ui->gridLayout->rowCount(); + QList fields = kdf.fields(); + for (int i = 0; i < fields.size(); i++) { + const Kdf::Field& field = fields.at(i); + QLabel* label = new QLabel(QString("%1:").arg(field.name())); + QSpinBox* spinBox = new QSpinBox(); + m_kdfWidgets.append(label); + m_kdfWidgets.append(spinBox); + m_kdfFields.append(QPair(field.id(), spinBox)); + spinBox->setMinimum(static_cast(qMin(qMax(0ull, field.min()), 0x7FFFFFFFull))); + spinBox->setMaximum(static_cast(qMin(qMax(0ull, field.max()), 0x7FFFFFFFull))); + spinBox->setValue(static_cast(qMin(qMax(0ull, kdf.field(field.id())), 0x7FFFFFFFull))); + spinBox->setObjectName(QString("kdfParams%1").arg(i)); + m_ui->gridLayout->addWidget(label, rowStart + i, columnStart - 3, Qt::AlignRight); + if (field.benchmarked()) { + Q_ASSERT(m_benchmarkField == nullptr); + QPushButton* benchBtn = new QPushButton("Benchmark"); + connect(benchBtn, &QPushButton::clicked, this, &DatabaseSettingsWidget::transformRoundsBenchmark); + m_kdfWidgets.append(benchBtn); + m_ui->gridLayout->addWidget(spinBox, rowStart + i, columnStart - 2); + m_ui->gridLayout->addWidget(benchBtn, rowStart + i, columnStart - 1); + m_benchmarkField = spinBox; + lastWidget = benchBtn; + } else { + m_ui->gridLayout->addWidget(spinBox, rowStart + i, columnStart - 2, 1, 2); + lastWidget = spinBox; + } + } + if (lastWidget != nullptr) { + QWidget::setTabOrder(lastWidget, m_ui->buttonBox->button(QDialogButtonBox::StandardButton::Cancel)); + QWidget::setTabOrder(m_ui->buttonBox->button(QDialogButtonBox::StandardButton::Cancel), m_ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)); + } +} + +void DatabaseSettingsWidget::clearKdfWidgets() +{ + m_benchmarkField = nullptr; + for (int i = 0; i < m_kdfWidgets.size(); ++i) { + m_ui->gridLayout->removeWidget(m_kdfWidgets.at(i)); + m_kdfWidgets.at(i)->deleteLater(); + } + m_kdfWidgets.clear(); + m_kdfFields.clear(); +} diff --git a/src/gui/DatabaseSettingsWidget.h b/src/gui/DatabaseSettingsWidget.h index 733b32f87..a6001b892 100644 --- a/src/gui/DatabaseSettingsWidget.h +++ b/src/gui/DatabaseSettingsWidget.h @@ -19,8 +19,12 @@ #define KEEPASSX_DATABASESETTINGSWIDGET_H #include +#include +#include +#include #include "gui/DialogyWidget.h" +#include "crypto/kdf/Kdf.h" class Database; @@ -45,11 +49,18 @@ private slots: void save(); void reject(); void transformRoundsBenchmark(); + void changeKdf(int index); private: void truncateHistories(); + void displayKdf(const Kdf& kdf); + void clearKdfWidgets(); const QScopedPointer m_ui; + QList m_kdfWidgets; + QList> m_kdfFields; + QSpinBox* m_benchmarkField; + QScopedPointer m_kdf; Database* m_db; Q_DISABLE_COPY(DatabaseSettingsWidget) diff --git a/src/gui/DatabaseSettingsWidget.ui b/src/gui/DatabaseSettingsWidget.ui index c38aebc8c..701df42a0 100644 --- a/src/gui/DatabaseSettingsWidget.ui +++ b/src/gui/DatabaseSettingsWidget.ui @@ -12,7 +12,7 @@ - + Qt::Vertical @@ -25,9 +25,9 @@ - + - + Qt::Horizontal @@ -40,181 +40,118 @@ - - - - 800 - 16777215 - - - - - - - - - - 0 - 0 - - - - 1 - - - 1000000000 - - - - - - - - 0 - 0 - - - - - 25 - 0 - - - - Benchmark - - - - - - - - - Database name: - - - - - - - Max. history size: - - - - - - - Transform rounds: - - - - - - - Max. history items: - - - - - - - - - - - - - 0 - 0 - - - - 2000000000 - - - - - - - - - Default username: - - - - - - - - - - - - - 0 - 0 - - - - MiB - - - 1 - - - 2000000000 - - - - - - - - - Use recycle bin - - - - - - - true - - - - - - - Database description: - - - - - - - - AES: 256 Bit (default) - - - - - Twofish: 256 Bit - - - - - - - - Algorithm: - - - - - + + + + + + + + Database name: + + + + + + + Max. history size: + + + + + + + Key derivation function: + + + + + + + Max. history items: + + + + + + + + + + + 0 + 0 + + + + 2000000000 + + + + + + + Default username: + + + + + + + + + + + 0 + 0 + + + + MiB + + + 1 + + + 2000000000 + + + + + + + Use recycle bin + + + + + + + true + + + + + + + Database description: + + + + + + + + + + Algorithm: + + + + - + Qt::Horizontal @@ -229,7 +166,7 @@ - + Qt::Vertical @@ -253,14 +190,14 @@ dbNameEdit dbDescriptionEdit - transformRoundsSpinBox - transformBenchmarkButton defaultUsernameEdit recycleBinEnabledCheckBox historyMaxItemsCheckBox historyMaxItemsSpinBox historyMaxSizeCheckBox historyMaxSizeSpinBox + algorithmComboBox + kdfComboBox buttonBox diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 17b2736cc..92b7a6a5f 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -45,6 +45,7 @@ #include "core/Metadata.h" #include "core/Tools.h" #include "crypto/Crypto.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass2Reader.h" #include "gui/DatabaseTabWidget.h" #include "gui/DatabaseWidget.h" @@ -897,12 +898,12 @@ void TestGui::testDatabaseSettings() m_db->metadata()->setName("Save"); triggerAction("actionChangeDatabaseSettings"); QWidget* dbSettingsWidget = m_dbWidget->findChild("databaseSettingsWidget"); - QSpinBox* transformRoundsSpinBox = dbSettingsWidget->findChild("transformRoundsSpinBox"); + QSpinBox* transformRoundsSpinBox = dbSettingsWidget->findChild("kdfParams0"); transformRoundsSpinBox->setValue(100); QTest::keyClick(transformRoundsSpinBox, Qt::Key_Enter); // wait for modified timer QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save*")); - QCOMPARE(m_db->transformRounds(), Q_UINT64_C(100)); + QCOMPARE(static_cast(m_db->kdf())->rounds(), Q_UINT64_C(100)); triggerAction("actionDatabaseSave"); QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save")); From 6a0d05e1ef6c15a77c543ecba927ad77fb2f395c Mon Sep 17 00:00:00 2001 From: angelsl Date: Mon, 13 Nov 2017 02:23:01 +0800 Subject: [PATCH 08/39] Add support for various algorithms for kdbx4 * Add SHA512 support to CryptoHash * Add ChaCha20 support * Add HMAC support * Add new HmacBlockStream, used in KDBX 4 * Add support for ChaCha20 protected stream --- CMakeLists.txt | 2 +- src/CMakeLists.txt | 1 + src/crypto/Crypto.cpp | 52 +++++- src/crypto/Crypto.h | 2 + src/crypto/CryptoHash.cpp | 42 ++++- src/crypto/CryptoHash.h | 6 +- src/crypto/SymmetricCipher.cpp | 47 ++++- src/crypto/SymmetricCipher.h | 15 +- src/crypto/SymmetricCipherGcrypt.cpp | 3 + src/format/KeePass2.cpp | 16 ++ src/format/KeePass2.h | 6 +- src/format/KeePass2RandomStream.cpp | 30 ++- src/format/KeePass2RandomStream.h | 6 +- src/format/KeePass2Reader.cpp | 12 +- src/format/KeePass2Reader.h | 3 + src/format/KeePass2Repair.cpp | 2 +- src/format/KeePass2Writer.cpp | 2 +- src/streams/HmacBlockStream.cpp | 267 +++++++++++++++++++++++++++ src/streams/HmacBlockStream.h | 61 ++++++ tests/TestCryptoHash.cpp | 13 ++ tests/TestKeePass2RandomStream.cpp | 2 +- tests/TestSymmetricCipher.cpp | 50 +++++ tests/TestSymmetricCipher.h | 1 + 23 files changed, 616 insertions(+), 25 deletions(-) create mode 100644 src/streams/HmacBlockStream.cpp create mode 100644 src/streams/HmacBlockStream.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 1d5c198ee..34ac87f04 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -262,7 +262,7 @@ set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG) find_package(LibGPGError REQUIRED) -find_package(Gcrypt 1.6.0 REQUIRED) +find_package(Gcrypt 1.7.0 REQUIRED) find_package(ZLIB REQUIRED) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c04901a11..2e10cdcf4 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -148,6 +148,7 @@ set(keepassx_SOURCES keys/PasswordKey.cpp keys/YkChallengeResponseKey.cpp streams/HashedBlockStream.cpp + streams/HmacBlockStream.cpp streams/LayeredStream.cpp streams/qtiocompressor.cpp streams/StoreDataStream.cpp diff --git a/src/crypto/Crypto.cpp b/src/crypto/Crypto.cpp index d00be720b..7ba78a6b3 100644 --- a/src/crypto/Crypto.cpp +++ b/src/crypto/Crypto.cpp @@ -95,18 +95,28 @@ bool Crypto::checkAlgorithms() qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr)); return false; } + if (gcry_cipher_algo_info(GCRY_CIPHER_CHACHA20, GCRYCTL_TEST_ALGO, nullptr, nullptr) != 0) { + m_errorStr = "GCRY_CIPHER_CHACHA20 not found."; + qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr)); + return false; + } if (gcry_md_test_algo(GCRY_MD_SHA256) != 0) { m_errorStr = "GCRY_MD_SHA256 not found."; qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr)); return false; } + if (gcry_md_test_algo(GCRY_MD_SHA512) != 0) { + m_errorStr = "GCRY_MD_SHA512 not found."; + qWarning("Crypto::checkAlgorithms: %s", qPrintable(m_errorStr)); + return false; + } return true; } bool Crypto::selfTest() { - return testSha256() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20(); + return testSha256() && testSha512() && testAes256Cbc() && testAes256Ecb() && testTwofish() && testSalsa20() && testChaCha20(); } void Crypto::raiseError(const QString& str) @@ -128,6 +138,19 @@ bool Crypto::testSha256() return true; } +bool Crypto::testSha512() +{ + QByteArray sha512Test = CryptoHash::hash("abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq", + CryptoHash::Sha512); + + if (sha512Test != QByteArray::fromHex("204a8fc6dda82f0a0ced7beb8e08a41657c16ef468b228a8279be331a703c33596fd15c13b1b07f9aa1d3bea57789ca031ad85c7a71dd70354ec631238ca3445")) { + raiseError("SHA-512 mismatch."); + return false; + } + + return true; +} + bool Crypto::testAes256Cbc() { QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); @@ -285,3 +308,30 @@ bool Crypto::testSalsa20() return true; } + +bool Crypto::testChaCha20() { + QByteArray chacha20Key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); + QByteArray chacha20iv = QByteArray::fromHex("0000000000000000"); + QByteArray chacha20Plain = QByteArray::fromHex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); + QByteArray chacha20Cipher = QByteArray::fromHex("76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586"); + bool ok; + + SymmetricCipher chacha20Stream(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, + SymmetricCipher::Encrypt); + if (!chacha20Stream.init(chacha20Key, chacha20iv)) { + raiseError(chacha20Stream.errorString()); + return false; + } + + QByteArray chacha20Processed = chacha20Stream.process(chacha20Plain, &ok); + if (!ok) { + raiseError(chacha20Stream.errorString()); + return false; + } + if (chacha20Processed != chacha20Cipher) { + raiseError("ChaCha20 stream cipher mismatch."); + return false; + } + + return true; +} \ No newline at end of file diff --git a/src/crypto/Crypto.h b/src/crypto/Crypto.h index 0ce2903c6..379068eb4 100644 --- a/src/crypto/Crypto.h +++ b/src/crypto/Crypto.h @@ -35,10 +35,12 @@ private: static bool selfTest(); static void raiseError(const QString& str); static bool testSha256(); + static bool testSha512(); static bool testAes256Cbc(); static bool testAes256Ecb(); static bool testTwofish(); static bool testSalsa20(); + static bool testChaCha20(); static bool m_initalized; static QString m_errorStr; diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp index d116451fc..a3ffb648e 100644 --- a/src/crypto/CryptoHash.cpp +++ b/src/crypto/CryptoHash.cpp @@ -29,27 +29,42 @@ public: }; CryptoHash::CryptoHash(CryptoHash::Algorithm algo) + : CryptoHash::CryptoHash(algo, false) {} + +CryptoHash::CryptoHash(CryptoHash::Algorithm algo, bool hmac) : d_ptr(new CryptoHashPrivate()) { Q_D(CryptoHash); Q_ASSERT(Crypto::initalized()); - int algoGcrypt; + int algoGcrypt = -1; + unsigned int flagsGcrypt = 0; switch (algo) { case CryptoHash::Sha256: algoGcrypt = GCRY_MD_SHA256; break; + case CryptoHash::Sha512: + algoGcrypt = GCRY_MD_SHA512; + break; + default: Q_ASSERT(false); break; } - gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, 0); + if (hmac) { + flagsGcrypt |= GCRY_MD_FLAG_HMAC; + } + + gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, flagsGcrypt); + if (error) { + qWarning("Gcrypt error (ctor): %s", gcry_strerror(error)); + qWarning("Gcrypt error (ctor): %s", gcry_strsource(error)); + } Q_ASSERT(error == 0); // TODO: error handling - Q_UNUSED(error); d->hashLen = gcry_md_get_algo_dlen(algoGcrypt); } @@ -74,6 +89,18 @@ void CryptoHash::addData(const QByteArray& data) gcry_md_write(d->ctx, data.constData(), data.size()); } +void CryptoHash::setKey(const QByteArray& data) +{ + Q_D(CryptoHash); + + gcry_error_t error = gcry_md_setkey(d->ctx, data.constData(), data.size()); + if (error) { + qWarning("Gcrypt error (setKey): %s", gcry_strerror(error)); + qWarning("Gcrypt error (setKey): %s", gcry_strsource(error)); + } + Q_ASSERT(error == 0); +} + void CryptoHash::reset() { Q_D(CryptoHash); @@ -96,3 +123,12 @@ QByteArray CryptoHash::hash(const QByteArray& data, CryptoHash::Algorithm algo) cryptoHash.addData(data); return cryptoHash.result(); } + +QByteArray CryptoHash::hmac(const QByteArray& data, const QByteArray& key, CryptoHash::Algorithm algo) +{ + // replace with gcry_md_hash_buffer()? + CryptoHash cryptoHash(algo, true); + cryptoHash.setKey(key); + cryptoHash.addData(data); + return cryptoHash.result(); +} diff --git a/src/crypto/CryptoHash.h b/src/crypto/CryptoHash.h index 80df056f1..cf027e0f3 100644 --- a/src/crypto/CryptoHash.h +++ b/src/crypto/CryptoHash.h @@ -27,16 +27,20 @@ class CryptoHash public: enum Algorithm { - Sha256 + Sha256, + Sha512 }; explicit CryptoHash(CryptoHash::Algorithm algo); + explicit CryptoHash(CryptoHash::Algorithm algo, bool hmac); ~CryptoHash(); void addData(const QByteArray& data); void reset(); QByteArray result() const; + void setKey(const QByteArray& data); static QByteArray hash(const QByteArray& data, CryptoHash::Algorithm algo); + static QByteArray hmac(const QByteArray& data, const QByteArray& key, Algorithm algo); private: CryptoHashPrivate* const d_ptr; diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp index 016103b27..9bd605b43 100644 --- a/src/crypto/SymmetricCipher.cpp +++ b/src/crypto/SymmetricCipher.cpp @@ -24,6 +24,7 @@ SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCiphe SymmetricCipher::Direction direction) : m_backend(createBackend(algo, mode, direction)) , m_initialized(false) + , m_algo(algo) { } @@ -61,6 +62,7 @@ SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorith case SymmetricCipher::Aes256: case SymmetricCipher::Twofish: case SymmetricCipher::Salsa20: + case SymmetricCipher::ChaCha20: return new SymmetricCipherGcrypt(algo, mode, direction); default: @@ -93,10 +95,14 @@ SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(Uuid cipher) { if (cipher == KeePass2::CIPHER_AES) { return SymmetricCipher::Aes256; - } - else { + } else if (cipher == KeePass2::CIPHER_CHACHA20) { + return SymmetricCipher::ChaCha20; + } else if (cipher == KeePass2::CIPHER_TWOFISH) { return SymmetricCipher::Twofish; } + + qWarning("SymmetricCipher::cipherToAlgorithm: invalid Uuid %s", cipher.toByteArray().toHex().data()); + return InvalidAlgorithm; } Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo) @@ -104,7 +110,42 @@ Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo) switch (algo) { case SymmetricCipher::Aes256: return KeePass2::CIPHER_AES; - default: + case SymmetricCipher::ChaCha20: + return KeePass2::CIPHER_CHACHA20; + case SymmetricCipher::Twofish: return KeePass2::CIPHER_TWOFISH; + default: + qWarning("SymmetricCipher::algorithmToCipher: invalid algorithm %d", algo); + return Uuid(); } } + +int SymmetricCipher::algorithmIvSize(SymmetricCipher::Algorithm algo) { + switch (algo) { + case SymmetricCipher::ChaCha20: + return 12; + case SymmetricCipher::Aes256: + case SymmetricCipher::Twofish: + return 16; + default: + qWarning("SymmetricCipher::algorithmIvSize: invalid algorithm %d", algo); + return -1; + } +} + +SymmetricCipher::Mode SymmetricCipher::algorithmMode(SymmetricCipher::Algorithm algo) { + switch (algo) { + case SymmetricCipher::ChaCha20: + return SymmetricCipher::Stream; + case SymmetricCipher::Aes256: + case SymmetricCipher::Twofish: + return SymmetricCipher::Cbc; + default: + qWarning("SymmetricCipher::algorithmMode: invalid algorithm %d", algo); + return SymmetricCipher::InvalidMode; + } +} + +SymmetricCipher::Algorithm SymmetricCipher::algorithm() const { + return m_algo; +} diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h index 31d10466b..981ef320e 100644 --- a/src/crypto/SymmetricCipher.h +++ b/src/crypto/SymmetricCipher.h @@ -33,7 +33,9 @@ public: { Aes256, Twofish, - Salsa20 + Salsa20, + ChaCha20, + InvalidAlgorithm = -1 }; enum Mode @@ -41,7 +43,8 @@ public: Cbc, Ctr, Ecb, - Stream + Stream, + InvalidMode = -1 }; enum Direction @@ -74,9 +77,12 @@ public: int keySize() const; int blockSize() const; QString errorString() const; + Algorithm algorithm() const; - static SymmetricCipher::Algorithm cipherToAlgorithm(Uuid cipher); - static Uuid algorithmToCipher(SymmetricCipher::Algorithm algo); + static Algorithm cipherToAlgorithm(Uuid cipher); + static Uuid algorithmToCipher(Algorithm algo); + static int algorithmIvSize(Algorithm algo); + static Mode algorithmMode(Algorithm algo); private: static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, @@ -84,6 +90,7 @@ private: const QScopedPointer m_backend; bool m_initialized; + Algorithm m_algo; Q_DISABLE_COPY(SymmetricCipher) }; diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp index 0b291e693..bbb80bf60 100644 --- a/src/crypto/SymmetricCipherGcrypt.cpp +++ b/src/crypto/SymmetricCipherGcrypt.cpp @@ -46,6 +46,9 @@ int SymmetricCipherGcrypt::gcryptAlgo(SymmetricCipher::Algorithm algo) case SymmetricCipher::Salsa20: return GCRY_CIPHER_SALSA20; + case SymmetricCipher::ChaCha20: + return GCRY_CIPHER_CHACHA20; + default: Q_ASSERT(false); return -1; diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index 01c15a871..8cf3df555 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -22,6 +22,7 @@ const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff")); const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); +const Uuid KeePass2::CIPHER_CHACHA20 = Uuid(QByteArray::fromHex("D6038A2B8B6F4CB5A524339A31DBB59A")); const Uuid KeePass2::KDF_AES = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA")); @@ -30,6 +31,7 @@ const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D const QList KeePass2::CIPHERS { KeePass2::UuidNamePair(KeePass2::CIPHER_AES, "AES: 256-bit"), KeePass2::UuidNamePair(KeePass2::CIPHER_TWOFISH, "Twofish: 256-bit"), + KeePass2::UuidNamePair(KeePass2::CIPHER_CHACHA20, "ChaCha20: 256-bit") }; const QList KeePass2::KDFS { KeePass2::UuidNamePair(KeePass2::KDF_AES, "AES-KDF"), @@ -53,6 +55,20 @@ Uuid KeePass2::kdfToUuid(const Kdf& kdf) } } +KeePass2::ProtectedStreamAlgo KeePass2::idToProtectedStreamAlgo(quint32 id) +{ + switch (id) { + case static_cast(KeePass2::ArcFourVariant): + return KeePass2::ArcFourVariant; + case static_cast(KeePass2::Salsa20): + return KeePass2::Salsa20; + case static_cast(KeePass2::ChaCha20): + return KeePass2::ChaCha20; + default: + return KeePass2::InvalidProtectedStreamAlgo; + } +} + KeePass2::UuidNamePair::UuidNamePair(const Uuid& uuid, const QString& name) : m_uuid(uuid) , m_name(name) diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index b3c7ee559..6356e15da 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -37,6 +37,7 @@ namespace KeePass2 extern const Uuid CIPHER_AES; extern const Uuid CIPHER_TWOFISH; + extern const Uuid CIPHER_CHACHA20; extern const Uuid KDF_AES; @@ -75,11 +76,14 @@ namespace KeePass2 enum ProtectedStreamAlgo { ArcFourVariant = 1, - Salsa20 = 2 + Salsa20 = 2, + ChaCha20 = 3, + InvalidProtectedStreamAlgo = -1 }; Kdf* uuidToKdf(const Uuid& uuid); Uuid kdfToUuid(const Kdf& kdf); + ProtectedStreamAlgo idToProtectedStreamAlgo(quint32 id); } #endif // KEEPASSX_KEEPASS2_H diff --git a/src/format/KeePass2RandomStream.cpp b/src/format/KeePass2RandomStream.cpp index 1944e5d5b..5f80fc151 100644 --- a/src/format/KeePass2RandomStream.cpp +++ b/src/format/KeePass2RandomStream.cpp @@ -20,16 +20,27 @@ #include "crypto/CryptoHash.h" #include "format/KeePass2.h" -KeePass2RandomStream::KeePass2RandomStream() - : m_cipher(SymmetricCipher::Salsa20, SymmetricCipher::Stream, SymmetricCipher::Encrypt) +KeePass2RandomStream::KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo) + : m_cipher(mapAlgo(algo), SymmetricCipher::Stream, SymmetricCipher::Encrypt) , m_offset(0) { } bool KeePass2RandomStream::init(const QByteArray& key) { - return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), - KeePass2::INNER_STREAM_SALSA20_IV); + switch (m_cipher.algorithm()) { + case SymmetricCipher::Salsa20: + return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), + KeePass2::INNER_STREAM_SALSA20_IV); + case SymmetricCipher::ChaCha20: { + QByteArray keyIv = CryptoHash::hash(key, CryptoHash::Sha512); + return m_cipher.init(keyIv.left(32), keyIv.mid(32, 12)); + } + default: + qWarning("Invalid stream algorithm (%d)", m_cipher.algorithm()); + break; + } + return false; } QByteArray KeePass2RandomStream::randomBytes(int size, bool* ok) @@ -109,3 +120,14 @@ bool KeePass2RandomStream::loadBlock() return true; } + +SymmetricCipher::Algorithm KeePass2RandomStream::mapAlgo(KeePass2::ProtectedStreamAlgo algo) { + switch (algo) { + case KeePass2::ChaCha20: + return SymmetricCipher::ChaCha20; + case KeePass2::Salsa20: + return SymmetricCipher::Salsa20; + default: + return SymmetricCipher::InvalidAlgorithm; + } +} \ No newline at end of file diff --git a/src/format/KeePass2RandomStream.h b/src/format/KeePass2RandomStream.h index 584d738b3..1e341bacc 100644 --- a/src/format/KeePass2RandomStream.h +++ b/src/format/KeePass2RandomStream.h @@ -21,11 +21,13 @@ #include #include "crypto/SymmetricCipher.h" +#include "KeePass2.h" class KeePass2RandomStream { public: - KeePass2RandomStream(); + KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo); + bool init(const QByteArray& key); QByteArray randomBytes(int size, bool* ok); QByteArray process(const QByteArray& data, bool* ok); @@ -38,6 +40,8 @@ private: SymmetricCipher m_cipher; QByteArray m_buffer; int m_offset; + + static SymmetricCipher::Algorithm mapAlgo(KeePass2::ProtectedStreamAlgo algo); }; #endif // KEEPASSX_KEEPASS2RANDOMSTREAM_H diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 60ebfdc82..f42e7b9bb 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -41,6 +41,7 @@ KeePass2Reader::KeePass2Reader() , m_headerEnd(false) , m_saveXml(false) , m_db(nullptr) + , m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo) { } @@ -164,7 +165,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke xmlDevice = ioCompressor.data(); } - KeePass2RandomStream randomStream; + KeePass2RandomStream randomStream(m_irsAlgo); if (!randomStream.init(m_protectedStreamKey)) { raiseError(randomStream.errorString()); return nullptr; @@ -447,9 +448,14 @@ void KeePass2Reader::setInnerRandomStreamID(const QByteArray& data) } else { quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); - - if (id != KeePass2::Salsa20) { + m_irsAlgo = KeePass2::idToProtectedStreamAlgo(id); + if (m_irsAlgo == KeePass2::ArcFourVariant || m_irsAlgo == KeePass2::InvalidProtectedStreamAlgo) { raiseError("Unsupported random stream algorithm"); } } } + +KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const +{ + return m_irsAlgo; +} diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h index f82b4464a..e9a947284 100644 --- a/src/format/KeePass2Reader.h +++ b/src/format/KeePass2Reader.h @@ -21,6 +21,7 @@ #include #include "keys/CompositeKey.h" +#include "format/KeePass2.h" class Database; class QIODevice; @@ -38,6 +39,7 @@ public: void setSaveXml(bool save); QByteArray xmlData(); QByteArray streamKey(); + KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const; private: void raiseError(const QString& errorMessage); @@ -67,6 +69,7 @@ private: QByteArray m_encryptionIV; QByteArray m_streamStartBytes; QByteArray m_protectedStreamKey; + KeePass2::ProtectedStreamAlgo m_irsAlgo; }; #endif // KEEPASSX_KEEPASS2READER_H diff --git a/src/format/KeePass2Repair.cpp b/src/format/KeePass2Repair.cpp index 8d18457d4..5f2732e2a 100644 --- a/src/format/KeePass2Repair.cpp +++ b/src/format/KeePass2Repair.cpp @@ -71,7 +71,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, return qMakePair(RepairFailed, nullptr); } - KeePass2RandomStream randomStream; + KeePass2RandomStream randomStream(reader.protectedStreamAlgo()); randomStream.init(reader.streamKey()); KeePass2XmlReader xmlReader; QBuffer buffer(&xmlData); diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index 0d78dc9a8..6dc89c36d 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -131,7 +131,7 @@ void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) m_device = ioCompressor.data(); } - KeePass2RandomStream randomStream; + KeePass2RandomStream randomStream(KeePass2::Salsa20); if (!randomStream.init(protectedStreamKey)) { raiseError(randomStream.errorString()); return; diff --git a/src/streams/HmacBlockStream.cpp b/src/streams/HmacBlockStream.cpp new file mode 100644 index 000000000..c01c973b1 --- /dev/null +++ b/src/streams/HmacBlockStream.cpp @@ -0,0 +1,267 @@ +/* +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#include "HmacBlockStream.h" + +#include "core/Endian.h" +#include "crypto/CryptoHash.h" + +const QSysInfo::Endian HmacBlockStream::ByteOrder = QSysInfo::LittleEndian; + +HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key) + : LayeredStream(baseDevice) + , m_blockSize(1024*1024) + , m_key(key) +{ + init(); +} + +HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key, qint32 blockSize) + : LayeredStream(baseDevice) + , m_blockSize(blockSize) + , m_key(key) +{ + init(); +} + +HmacBlockStream::~HmacBlockStream() +{ + close(); +} + +void HmacBlockStream::init() +{ + m_buffer.clear(); + m_bufferPos = 0; + m_blockIndex = 0; + m_eof = false; + m_error = false; +} + +bool HmacBlockStream::reset() +{ + // Write final block(s) only if device is writable and we haven't + // already written a final block. + if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) { + if (!m_buffer.isEmpty()) { + if (!writeHashedBlock()) { + return false; + } + } + + // write empty final block + if (!writeHashedBlock()) { + return false; + } + } + + init(); + + return true; +} + +void HmacBlockStream::close() +{ + // Write final block(s) only if device is writable and we haven't + // already written a final block. + if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) { + if (!m_buffer.isEmpty()) { + writeHashedBlock(); + } + + // write empty final block + writeHashedBlock(); + } + + LayeredStream::close(); +} + +qint64 HmacBlockStream::readData(char* data, qint64 maxSize) +{ + if (m_error) { + return -1; + } + else if (m_eof) { + return 0; + } + + qint64 bytesRemaining = maxSize; + qint64 offset = 0; + + while (bytesRemaining > 0) { + if (m_bufferPos == m_buffer.size()) { + if (!readHashedBlock()) { + if (m_error) { + return -1; + } + else { + return maxSize - bytesRemaining; + } + } + } + + int bytesToCopy = qMin(bytesRemaining, static_cast(m_buffer.size() - m_bufferPos)); + + memcpy(data + offset, m_buffer.constData() + m_bufferPos, bytesToCopy); + + offset += bytesToCopy; + m_bufferPos += bytesToCopy; + bytesRemaining -= bytesToCopy; + } + + return maxSize; +} + +bool HmacBlockStream::readHashedBlock() +{ + if (m_eof) { + return false; + } + QByteArray hmac = m_baseDevice->read(32); + if (hmac.size() != 32) { + m_error = true; + setErrorString("Invalid HMAC size."); + return false; + } + + QByteArray blockSizeBytes = m_baseDevice->read(4); + if (blockSizeBytes.size() != 4) { + m_error = true; + setErrorString("Invalid block size size."); + return false; + } + qint32 blockSize = Endian::bytesToInt32(blockSizeBytes, ByteOrder); + if (blockSize < 0) { + m_error = true; + setErrorString("Invalid block size."); + return false; + } + + m_buffer = m_baseDevice->read(blockSize); + if (m_buffer.size() != blockSize) { + m_error = true; + setErrorString("Block too short."); + return false; + } + + CryptoHash hasher(CryptoHash::Sha256, true); + hasher.setKey(getCurrentHmacKey()); + hasher.addData(Endian::uint64ToBytes(m_blockIndex, ByteOrder)); + hasher.addData(blockSizeBytes); + hasher.addData(m_buffer); + + if (hmac != hasher.result()) { + m_error = true; + setErrorString("Mismatch between hash and data."); + return false; + } + + m_bufferPos = 0; + m_blockIndex++; + + if (blockSize == 0) { + m_eof = true; + return false; + } + + return true; +} + +qint64 HmacBlockStream::writeData(const char* data, qint64 maxSize) +{ + Q_ASSERT(maxSize >= 0); + + if (m_error) { + return 0; + } + + qint64 bytesRemaining = maxSize; + qint64 offset = 0; + + while (bytesRemaining > 0) { + int bytesToCopy = qMin(bytesRemaining, static_cast(m_blockSize - m_buffer.size())); + + m_buffer.append(data + offset, bytesToCopy); + + offset += bytesToCopy; + bytesRemaining -= bytesToCopy; + + if (m_buffer.size() == m_blockSize) { + if (!writeHashedBlock()) { + if (m_error) { + return -1; + } + else { + return maxSize - bytesRemaining; + } + } + } + } + + return maxSize; +} + +bool HmacBlockStream::writeHashedBlock() +{ + CryptoHash hasher(CryptoHash::Sha256, true); + hasher.setKey(getCurrentHmacKey()); + hasher.addData(Endian::uint64ToBytes(m_blockIndex, ByteOrder)); + hasher.addData(Endian::int32ToBytes(m_buffer.size(), ByteOrder)); + hasher.addData(m_buffer); + QByteArray hash = hasher.result(); + + if (m_baseDevice->write(hash) != hash.size()) { + m_error = true; + setErrorString(m_baseDevice->errorString()); + return false; + } + + if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) { + m_error = true; + setErrorString(m_baseDevice->errorString()); + return false; + } + + if (!m_buffer.isEmpty()) { + if (m_baseDevice->write(m_buffer) != m_buffer.size()) { + m_error = true; + setErrorString(m_baseDevice->errorString()); + return false; + } + + m_buffer.clear(); + } + m_blockIndex++; + return true; +} + +QByteArray HmacBlockStream::getCurrentHmacKey() const { + return getHmacKey(m_blockIndex, m_key); +} + +QByteArray HmacBlockStream::getHmacKey(quint64 blockIndex, QByteArray key) { + Q_ASSERT(key.size() == 64); + QByteArray indexBytes = Endian::uint64ToBytes(blockIndex, ByteOrder); + CryptoHash hasher(CryptoHash::Sha512); + hasher.addData(indexBytes); + hasher.addData(key); + return hasher.result(); +} + +bool HmacBlockStream::atEnd() const { + return m_eof; +} diff --git a/src/streams/HmacBlockStream.h b/src/streams/HmacBlockStream.h new file mode 100644 index 000000000..eecd8fe92 --- /dev/null +++ b/src/streams/HmacBlockStream.h @@ -0,0 +1,61 @@ +/* +* Copyright (C) 2017 KeePassXC Team +* +* This program is free software: you can redistribute it and/or modify +* it under the terms of the GNU General Public License as published by +* the Free Software Foundation, either version 2 or (at your option) +* version 3 of the License. +* +* This program is distributed in the hope that it will be useful, +* but WITHOUT ANY WARRANTY; without even the implied warranty of +* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +* GNU General Public License for more details. +* +* You should have received a copy of the GNU General Public License +* along with this program. If not, see . +*/ + +#ifndef KEEPASSX_HMACBLOCKSTREAM_H +#define KEEPASSX_HMACBLOCKSTREAM_H + +#include + +#include "streams/LayeredStream.h" + +class HmacBlockStream : public LayeredStream +{ + Q_OBJECT + +public: + explicit HmacBlockStream(QIODevice* baseDevice, QByteArray key); + HmacBlockStream(QIODevice* baseDevice, QByteArray key, qint32 blockSize); + ~HmacBlockStream(); + + bool reset() override; + void close() override; + + static QByteArray getHmacKey(quint64 blockIndex, QByteArray key); + + bool atEnd() const override; + +protected: + qint64 readData(char* data, qint64 maxSize) override; + qint64 writeData(const char* data, qint64 maxSize) override; + +private: + void init(); + bool readHashedBlock(); + bool writeHashedBlock(); + QByteArray getCurrentHmacKey() const; + + static const QSysInfo::Endian ByteOrder; + qint32 m_blockSize; + QByteArray m_buffer; + QByteArray m_key; + int m_bufferPos; + quint64 m_blockIndex; + bool m_eof; + bool m_error; +}; + +#endif // KEEPASSX_HMACBLOCKSTREAM_H diff --git a/tests/TestCryptoHash.cpp b/tests/TestCryptoHash.cpp index c166f5595..469ce8192 100644 --- a/tests/TestCryptoHash.cpp +++ b/tests/TestCryptoHash.cpp @@ -44,4 +44,17 @@ void TestCryptoHash::test() cryptoHash3.addData(QString("ssX").toLatin1()); QCOMPARE(cryptoHash3.result(), QByteArray::fromHex("0b56e5f65263e747af4a833bd7dd7ad26a64d7a4de7c68e52364893dca0766b4")); + + CryptoHash cryptoHash2(CryptoHash::Sha512); + QCOMPARE(cryptoHash2.result(), + QByteArray::fromHex("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e")); + + QByteArray result3 = CryptoHash::hash(source2, CryptoHash::Sha512); + QCOMPARE(result3, QByteArray::fromHex("0d41b612584ed39ff72944c29494573e40f4bb95283455fae2e0be1e3565aa9f48057d59e6ffd777970e282871c25a549a2763e5b724794f312c97021c42f91d")); + + CryptoHash cryptoHash4(CryptoHash::Sha512); + cryptoHash4.addData(QString("KeePa").toLatin1()); + cryptoHash4.addData(QString("ssX").toLatin1()); + QCOMPARE(cryptoHash4.result(), + QByteArray::fromHex("0d41b612584ed39ff72944c29494573e40f4bb95283455fae2e0be1e3565aa9f48057d59e6ffd777970e282871c25a549a2763e5b724794f312c97021c42f91d")); } diff --git a/tests/TestKeePass2RandomStream.cpp b/tests/TestKeePass2RandomStream.cpp index 03dfbe507..bef7af540 100644 --- a/tests/TestKeePass2RandomStream.cpp +++ b/tests/TestKeePass2RandomStream.cpp @@ -58,7 +58,7 @@ void TestKeePass2RandomStream::test() } - KeePass2RandomStream randomStream; + KeePass2RandomStream randomStream(KeePass2::Salsa20); bool ok; QVERIFY(randomStream.init(key)); QByteArray randomStreamData; diff --git a/tests/TestSymmetricCipher.cpp b/tests/TestSymmetricCipher.cpp index c1e947063..bfa3c3db8 100644 --- a/tests/TestSymmetricCipher.cpp +++ b/tests/TestSymmetricCipher.cpp @@ -342,6 +342,56 @@ void TestSymmetricCipher::testSalsa20() QCOMPARE(cipherTextB.mid(448, 64), expectedCipherText4); } +void TestSymmetricCipher::testChaCha20() +{ + // https://tools.ietf.org/html/draft-agl-tls-chacha20poly1305-04#section-7 + bool ok; + + { + QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); + QByteArray iv = QByteArray::fromHex("0000000000000000"); + SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); + QVERIFY(cipher.init(key, iv)); + QCOMPARE(cipher.process(QByteArray(64, 0), &ok), + QByteArray::fromHex( + "76b8e0ada0f13d90405d6ae55386bd28bdd219b8a08ded1aa836efcc8b770dc7da41597c5157488d7724e03fb8d84a376a43b8f41518a11cc387b669b2ee6586")); + QVERIFY(ok); + } + + { + QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000001"); + QByteArray iv = QByteArray::fromHex("0000000000000000"); + SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); + QVERIFY(cipher.init(key, iv)); + QCOMPARE(cipher.process(QByteArray(64, 0), &ok), + QByteArray::fromHex( + "4540f05a9f1fb296d7736e7b208e3c96eb4fe1834688d2604f450952ed432d41bbe2a0b6ea7566d2a5d1e7e20d42af2c53d792b1c43fea817e9ad275ae546963")); + QVERIFY(ok); + } + + { + QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); + QByteArray iv = QByteArray::fromHex("0000000000000001"); + SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); + QVERIFY(cipher.init(key, iv)); + QCOMPARE(cipher.process(QByteArray(60, 0), &ok), + QByteArray::fromHex( + "de9cba7bf3d69ef5e786dc63973f653a0b49e015adbff7134fcb7df137821031e85a050278a7084527214f73efc7fa5b5277062eb7a0433e445f41e3")); + QVERIFY(ok); + } + + { + QByteArray key = QByteArray::fromHex("0000000000000000000000000000000000000000000000000000000000000000"); + QByteArray iv = QByteArray::fromHex("0100000000000000"); + SymmetricCipher cipher(SymmetricCipher::ChaCha20, SymmetricCipher::Stream, SymmetricCipher::Encrypt); + QVERIFY(cipher.init(key, iv)); + QCOMPARE(cipher.process(QByteArray(64, 0), &ok), + QByteArray::fromHex( + "ef3fdfd6c61578fbf5cf35bd3dd33b8009631634d21e42ac33960bd138e50d32111e4caf237ee53ca8ad6426194a88545ddc497a0b466e7d6bbdb0041b2f586b")); + QVERIFY(ok); + } +} + void TestSymmetricCipher::testPadding() { QByteArray key = QByteArray::fromHex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4"); diff --git a/tests/TestSymmetricCipher.h b/tests/TestSymmetricCipher.h index cad13841a..40e3b49cf 100644 --- a/tests/TestSymmetricCipher.h +++ b/tests/TestSymmetricCipher.h @@ -34,6 +34,7 @@ private slots: void testTwofish256CbcEncryption(); void testTwofish256CbcDecryption(); void testSalsa20(); + void testChaCha20(); void testPadding(); void testStreamReset(); }; From e5ec585f983d17fba5db23fa510776d65bceb9e6 Mon Sep 17 00:00:00 2001 From: angelsl Date: Mon, 13 Nov 2017 15:43:38 +0800 Subject: [PATCH 09/39] Use GCRY_MD_FLAG_SECURE in CryptoHash --- src/crypto/CryptoHash.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp index a3ffb648e..907140606 100644 --- a/src/crypto/CryptoHash.cpp +++ b/src/crypto/CryptoHash.cpp @@ -39,7 +39,7 @@ CryptoHash::CryptoHash(CryptoHash::Algorithm algo, bool hmac) Q_ASSERT(Crypto::initalized()); int algoGcrypt = -1; - unsigned int flagsGcrypt = 0; + unsigned int flagsGcrypt = GCRY_MD_FLAG_SECURE; switch (algo) { case CryptoHash::Sha256: From 3461cbfb060012dbdaaea5af418eccbeb0fedc04 Mon Sep 17 00:00:00 2001 From: angelsl Date: Mon, 13 Nov 2017 02:55:03 +0800 Subject: [PATCH 10/39] Rename KeePass2{,Xml}{R,W} to Kdbx3{,Xml}{R,W}, and add a redirection class This class will in future select Kdbx4{R,W} as appropriate. --- src/CMakeLists.txt | 8 +- src/format/Kdbx3Reader.cpp | 394 ++++++++++++++++ src/format/Kdbx3Reader.h | 62 +++ src/format/Kdbx3Writer.cpp | 187 ++++++++ src/format/Kdbx3Writer.h | 47 ++ ...ePass2XmlReader.cpp => Kdbx3XmlReader.cpp} | 92 ++-- .../{KeePass2XmlReader.h => Kdbx3XmlReader.h} | 12 +- ...ePass2XmlWriter.cpp => Kdbx3XmlWriter.cpp} | 72 +-- .../{KeePass2XmlWriter.h => Kdbx3XmlWriter.h} | 10 +- src/format/KeePass2Reader.cpp | 437 +++--------------- src/format/KeePass2Reader.h | 83 ++-- src/format/KeePass2Repair.cpp | 5 +- src/format/KeePass2Writer.cpp | 209 ++------- src/format/KeePass2Writer.h | 51 +- tests/CMakeLists.txt | 2 +- tests/TestDeletedObjects.cpp | 4 +- tests/TestKdbx3XmlReader.cpp | 22 + tests/TestKeePass2Writer.cpp | 1 - tests/TestKeePass2XmlReader.cpp | 112 +++-- tests/TestKeePass2XmlReader.h | 23 +- 20 files changed, 1099 insertions(+), 734 deletions(-) create mode 100644 src/format/Kdbx3Reader.cpp create mode 100644 src/format/Kdbx3Reader.h create mode 100644 src/format/Kdbx3Writer.cpp create mode 100644 src/format/Kdbx3Writer.h rename src/format/{KeePass2XmlReader.cpp => Kdbx3XmlReader.cpp} (92%) rename src/format/{KeePass2XmlReader.h => Kdbx3XmlReader.h} (92%) rename src/format/{KeePass2XmlWriter.cpp => Kdbx3XmlWriter.cpp} (86%) rename src/format/{KeePass2XmlWriter.h => Kdbx3XmlWriter.h} (94%) create mode 100644 tests/TestKdbx3XmlReader.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2e10cdcf4..35ea97817 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -84,11 +84,13 @@ set(keepassx_SOURCES format/KeePass1Reader.cpp format/KeePass2.cpp format/KeePass2RandomStream.cpp - format/KeePass2Reader.cpp format/KeePass2Repair.cpp + format/KeePass2Reader.cpp format/KeePass2Writer.cpp - format/KeePass2XmlReader.cpp - format/KeePass2XmlWriter.cpp + format/Kdbx3Reader.cpp + format/Kdbx3Writer.cpp + format/Kdbx3XmlReader.cpp + format/Kdbx3XmlWriter.cpp gui/AboutDialog.cpp gui/Application.cpp gui/CategoryListWidget.cpp diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp new file mode 100644 index 000000000..fc3dcbd64 --- /dev/null +++ b/src/format/Kdbx3Reader.cpp @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Kdbx3Reader.h" + +#include +#include +#include + +#include "core/Database.h" +#include "core/Endian.h" +#include "crypto/CryptoHash.h" +#include "crypto/kdf/AesKdf.h" +#include "format/KeePass1.h" +#include "format/KeePass2.h" +#include "format/KeePass2RandomStream.h" +#include "format/Kdbx3XmlReader.h" +#include "streams/HashedBlockStream.h" +#include "streams/QtIOCompressor" +#include "streams/StoreDataStream.h" +#include "streams/SymmetricCipherStream.h" + +Kdbx3Reader::Kdbx3Reader() + : m_device(nullptr) + , m_headerStream(nullptr) + , m_headerEnd(false) + , m_db(nullptr) +{ +} + +Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) +{ + QScopedPointer db(new Database()); + m_db = db.data(); + m_device = device; + m_error = false; + m_errorStr.clear(); + m_headerEnd = false; + m_xmlData.clear(); + m_masterSeed.clear(); + m_encryptionIV.clear(); + m_streamStartBytes.clear(); + m_protectedStreamKey.clear(); + + StoreDataStream headerStream(m_device); + headerStream.open(QIODevice::ReadOnly); + m_headerStream = &headerStream; + + bool ok; + + quint32 signature1 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); + if (!ok || signature1 != KeePass2::SIGNATURE_1) { + raiseError(tr("Not a KeePass database.")); + return nullptr; + } + + quint32 signature2 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); + if (ok && signature2 == KeePass1::SIGNATURE_2) { + raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" + "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; + } + else if (!ok || signature2 != KeePass2::SIGNATURE_2) { + raiseError(tr("Not a KeePass database.")); + return nullptr; + } + + quint32 version = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok) + & KeePass2::FILE_VERSION_CRITICAL_MASK; + quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; + if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) { + raiseError(tr("Unsupported KeePass KDBX 2 or 3 database version.")); + return nullptr; + } + + while (readHeaderField() && !hasError()) { + } + + headerStream.close(); + + if (hasError()) { + return nullptr; + } + + // check if all required headers were present + if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() + || m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty() + || m_db->cipher().isNull()) { + raiseError("missing database headers"); + return nullptr; + } + + if (!m_db->setKey(key, false)) { + raiseError(tr("Unable to calculate master key")); + return nullptr; + } + + if (m_db->challengeMasterSeed(m_masterSeed) == false) { + raiseError(tr("Unable to issue challenge-response.")); + return nullptr; + } + + CryptoHash hash(CryptoHash::Sha256); + hash.addData(m_masterSeed); + hash.addData(m_db->challengeResponseKey()); + hash.addData(m_db->transformedMasterKey()); + QByteArray finalKey = hash.result(); + + SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher()); + SymmetricCipherStream cipherStream(m_device, cipher, + SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt); + if (!cipherStream.init(finalKey, m_encryptionIV)) { + raiseError(cipherStream.errorString()); + return nullptr; + } + if (!cipherStream.open(QIODevice::ReadOnly)) { + raiseError(cipherStream.errorString()); + return nullptr; + } + + QByteArray realStart = cipherStream.read(32); + + if (realStart != m_streamStartBytes) { + raiseError(tr("Wrong key or database file is corrupt.")); + return nullptr; + } + + HashedBlockStream hashedStream(&cipherStream); + if (!hashedStream.open(QIODevice::ReadOnly)) { + raiseError(hashedStream.errorString()); + return nullptr; + } + + QIODevice* xmlDevice; + QScopedPointer ioCompressor; + + if (m_db->compressionAlgo() == Database::CompressionNone) { + xmlDevice = &hashedStream; + } + else { + ioCompressor.reset(new QtIOCompressor(&hashedStream)); + ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); + if (!ioCompressor->open(QIODevice::ReadOnly)) { + raiseError(ioCompressor->errorString()); + return nullptr; + } + xmlDevice = ioCompressor.data(); + } + + KeePass2RandomStream randomStream(KeePass2::Salsa20); + if (!randomStream.init(m_protectedStreamKey)) { + raiseError(randomStream.errorString()); + return nullptr; + } + + QScopedPointer buffer; + + if (m_saveXml) { + m_xmlData = xmlDevice->readAll(); + buffer.reset(new QBuffer(&m_xmlData)); + buffer->open(QIODevice::ReadOnly); + xmlDevice = buffer.data(); + } + + Kdbx3XmlReader xmlReader; + xmlReader.readDatabase(xmlDevice, m_db, &randomStream); + + if (xmlReader.hasError()) { + raiseError(xmlReader.errorString()); + if (keepDatabase) { + return db.take(); + } + else { + return nullptr; + } + } + + Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty()); + + if (!xmlReader.headerHash().isEmpty()) { + QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256); + if (headerHash != xmlReader.headerHash()) { + raiseError("Header doesn't match hash"); + return nullptr; + } + } + + return db.take(); +} + +bool Kdbx3Reader::readHeaderField() +{ + QByteArray fieldIDArray = m_headerStream->read(1); + if (fieldIDArray.size() != 1) { + raiseError("Invalid header id size"); + return false; + } + quint8 fieldID = fieldIDArray.at(0); + + bool ok; + quint16 fieldLen = Endian::readUInt16(m_headerStream, KeePass2::BYTEORDER, &ok); + if (!ok) { + raiseError("Invalid header field length"); + return false; + } + + QByteArray fieldData; + if (fieldLen != 0) { + fieldData = m_headerStream->read(fieldLen); + if (fieldData.size() != fieldLen) { + raiseError("Invalid header data length"); + return false; + } + } + + switch (fieldID) { + case KeePass2::EndOfHeader: + m_headerEnd = true; + break; + + case KeePass2::CipherID: + setCipher(fieldData); + break; + + case KeePass2::CompressionFlags: + setCompressionFlags(fieldData); + break; + + case KeePass2::MasterSeed: + setMasterSeed(fieldData); + break; + + case KeePass2::TransformSeed: + setTransformSeed(fieldData); + break; + + case KeePass2::TransformRounds: + setTransformRounds(fieldData); + break; + + case KeePass2::EncryptionIV: + setEncryptionIV(fieldData); + break; + + case KeePass2::ProtectedStreamKey: + setProtectedStreamKey(fieldData); + break; + + case KeePass2::StreamStartBytes: + setStreamStartBytes(fieldData); + break; + + case KeePass2::InnerRandomStreamID: + setInnerRandomStreamID(fieldData); + break; + + default: + qWarning("Unknown header field read: id=%d", fieldID); + break; + } + + return !m_headerEnd; +} + +void Kdbx3Reader::setCipher(const QByteArray& data) +{ + if (data.size() != Uuid::Length) { + raiseError("Invalid cipher uuid length"); + } else { + Uuid uuid(data); + + if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { + raiseError("Unsupported cipher"); + } else { + m_db->setCipher(uuid); + } + } +} + +void Kdbx3Reader::setCompressionFlags(const QByteArray& data) +{ + if (data.size() != 4) { + raiseError("Invalid compression flags length"); + } + else { + quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); + + if (id > Database::CompressionAlgorithmMax) { + raiseError("Unsupported compression algorithm"); + } + else { + m_db->setCompressionAlgo(static_cast(id)); + } + } +} + +void Kdbx3Reader::setMasterSeed(const QByteArray& data) +{ + if (data.size() != 32) { + raiseError("Invalid master seed size"); + } + else { + m_masterSeed = data; + } +} + +void Kdbx3Reader::setTransformSeed(const QByteArray& data) +{ + if (data.size() != 32) { + raiseError("Invalid transform seed size"); + } + else { + AesKdf* aesKdf; + if (m_db->kdf()->type() == Kdf::Type::AES) { + aesKdf = static_cast(m_db->kdf()); + } else { + aesKdf = new AesKdf(); + m_db->setKdf(aesKdf); + } + + aesKdf->setSeed(data); + } +} + +void Kdbx3Reader::setTransformRounds(const QByteArray& data) +{ + if (data.size() != 8) { + raiseError("Invalid transform rounds size"); + } + else { + quint64 rounds = Endian::bytesToUInt64(data, KeePass2::BYTEORDER); + + AesKdf* aesKdf; + if (m_db->kdf()->type() == Kdf::Type::AES) { + aesKdf = static_cast(m_db->kdf()); + } else { + aesKdf = new AesKdf(); + m_db->setKdf(aesKdf); + } + + aesKdf->setRounds(rounds); + } +} + +void Kdbx3Reader::setEncryptionIV(const QByteArray& data) +{ + m_encryptionIV = data; +} + +void Kdbx3Reader::setProtectedStreamKey(const QByteArray& data) +{ + m_protectedStreamKey = data; +} + +void Kdbx3Reader::setStreamStartBytes(const QByteArray& data) +{ + if (data.size() != 32) { + raiseError("Invalid start bytes size"); + } + else { + m_streamStartBytes = data; + } +} + +void Kdbx3Reader::setInnerRandomStreamID(const QByteArray& data) +{ + if (data.size() != 4) { + raiseError("Invalid random stream id size"); + } else { + quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); + KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id); + if (irsAlgo == KeePass2::InvalidProtectedStreamAlgo || irsAlgo == KeePass2::ArcFourVariant) { + raiseError("Invalid inner random stream cipher"); + } else { + m_irsAlgo = irsAlgo; + } + } +} diff --git a/src/format/Kdbx3Reader.h b/src/format/Kdbx3Reader.h new file mode 100644 index 000000000..1083b4bb4 --- /dev/null +++ b/src/format/Kdbx3Reader.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_KDBX3READER_H +#define KEEPASSX_KDBX3READER_H + +#include + +#include "format/KeePass2Reader.h" +#include "keys/CompositeKey.h" + +class Database; +class QIODevice; + +class Kdbx3Reader : public BaseKeePass2Reader +{ + Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader) + +public: + Kdbx3Reader(); + + using BaseKeePass2Reader::readDatabase; + virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override; + +private: + bool readHeaderField(); + + void setCipher(const QByteArray& data); + void setCompressionFlags(const QByteArray& data); + void setMasterSeed(const QByteArray& data); + void setTransformSeed(const QByteArray& data); + void setTransformRounds(const QByteArray& data); + void setEncryptionIV(const QByteArray& data); + void setProtectedStreamKey(const QByteArray& data); + void setStreamStartBytes(const QByteArray& data); + void setInnerRandomStreamID(const QByteArray& data); + + QIODevice* m_device; + QIODevice* m_headerStream; + bool m_headerEnd; + + Database* m_db; + QByteArray m_masterSeed; + QByteArray m_encryptionIV; + QByteArray m_streamStartBytes; +}; + +#endif // KEEPASSX_KDBX3READER_H diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp new file mode 100644 index 000000000..d07ff9663 --- /dev/null +++ b/src/format/Kdbx3Writer.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Kdbx3Writer.h" + +#include +#include +#include + +#include "core/Database.h" +#include "core/Endian.h" +#include "crypto/CryptoHash.h" +#include "crypto/kdf/AesKdf.h" +#include "crypto/Random.h" +#include "format/KeePass2RandomStream.h" +#include "format/Kdbx3XmlWriter.h" +#include "streams/HashedBlockStream.h" +#include "streams/QtIOCompressor" +#include "streams/SymmetricCipherStream.h" + +#define CHECK_RETURN(x) if (!(x)) return; +#define CHECK_RETURN_FALSE(x) if (!(x)) return false; + +Kdbx3Writer::Kdbx3Writer() + : m_device(0) +{ +} + +bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) +{ + m_error = false; + m_errorStr.clear(); + + QByteArray masterSeed = randomGen()->randomArray(32); + QByteArray encryptionIV = randomGen()->randomArray(16); + QByteArray protectedStreamKey = randomGen()->randomArray(32); + QByteArray startBytes = randomGen()->randomArray(32); + QByteArray endOfHeader = "\r\n\r\n"; + + if (db->challengeMasterSeed(masterSeed) == false) { + raiseError(tr("Unable to issue challenge-response.")); + return false; + } + + if (!db->setKey(db->key(), false, true)) { + raiseError(tr("Unable to calculate master key")); + return false; + } + + CryptoHash hash(CryptoHash::Sha256); + hash.addData(masterSeed); + hash.addData(db->challengeResponseKey()); + Q_ASSERT(!db->transformedMasterKey().isEmpty()); + hash.addData(db->transformedMasterKey()); + QByteArray finalKey = hash.result(); + + QBuffer header; + header.open(QIODevice::WriteOnly); + m_device = &header; + + CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER))); + + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray())); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CompressionFlags, + Endian::int32ToBytes(db->compressionAlgo(), + KeePass2::BYTEORDER))); + AesKdf* kdf = static_cast(db->kdf()); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::MasterSeed, masterSeed)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformSeed, kdf->seed())); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformRounds, + Endian::int64ToBytes(kdf->rounds(), + KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EncryptionIV, encryptionIV)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::StreamStartBytes, startBytes)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::InnerRandomStreamID, + Endian::int32ToBytes(KeePass2::Salsa20, + KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EndOfHeader, endOfHeader)); + + header.close(); + m_device = device; + QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256); + CHECK_RETURN_FALSE(writeData(header.data())); + + SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher()); + SymmetricCipherStream cipherStream(device, algo, + SymmetricCipher::algorithmMode(algo), SymmetricCipher::Encrypt); + cipherStream.init(finalKey, encryptionIV); + if (!cipherStream.open(QIODevice::WriteOnly)) { + raiseError(cipherStream.errorString()); + return false; + } + m_device = &cipherStream; + CHECK_RETURN_FALSE(writeData(startBytes)); + + HashedBlockStream hashedStream(&cipherStream); + if (!hashedStream.open(QIODevice::WriteOnly)) { + raiseError(hashedStream.errorString()); + return false; + } + + QScopedPointer ioCompressor; + + if (db->compressionAlgo() == Database::CompressionNone) { + m_device = &hashedStream; + } + else { + ioCompressor.reset(new QtIOCompressor(&hashedStream)); + ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); + if (!ioCompressor->open(QIODevice::WriteOnly)) { + raiseError(ioCompressor->errorString()); + return false; + } + m_device = ioCompressor.data(); + } + + KeePass2RandomStream randomStream(KeePass2::Salsa20); + if (!randomStream.init(protectedStreamKey)) { + raiseError(randomStream.errorString()); + return false; + } + + Kdbx3XmlWriter xmlWriter; + xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash); + + // Explicitly close/reset streams so they are flushed and we can detect + // errors. QIODevice::close() resets errorString() etc. + if (ioCompressor) { + ioCompressor->close(); + } + if (!hashedStream.reset()) { + raiseError(hashedStream.errorString()); + return false; + } + if (!cipherStream.reset()) { + raiseError(cipherStream.errorString()); + return false; + } + + if (xmlWriter.hasError()) { + raiseError(xmlWriter.errorString()); + } + + return true; +} + +bool Kdbx3Writer::writeData(const QByteArray& data) +{ + if (m_device->write(data) != data.size()) { + raiseError(m_device->errorString()); + return false; + } + else { + return true; + } +} + +bool Kdbx3Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data) +{ + Q_ASSERT(data.size() <= 65535); + + QByteArray fieldIdArr; + fieldIdArr[0] = fieldId; + CHECK_RETURN_FALSE(writeData(fieldIdArr)); + CHECK_RETURN_FALSE(writeData(Endian::int16ToBytes(static_cast(data.size()), + KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(data)); + + return true; +} diff --git a/src/format/Kdbx3Writer.h b/src/format/Kdbx3Writer.h new file mode 100644 index 000000000..6b5b8f28f --- /dev/null +++ b/src/format/Kdbx3Writer.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_KDBX3WRITER_H +#define KEEPASSX_KDBX3WRITER_H + +#include + +#include "format/KeePass2.h" +#include "format/KeePass2Writer.h" +#include "keys/CompositeKey.h" + +class Database; +class QIODevice; + +class Kdbx3Writer : public BaseKeePass2Writer +{ + Q_DECLARE_TR_FUNCTIONS(Kdbx3Writer) + +public: + Kdbx3Writer(); + + using BaseKeePass2Writer::writeDatabase; + bool writeDatabase(QIODevice* device, Database* db); + +private: + bool writeData(const QByteArray& data); + bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data); + + QIODevice* m_device; +}; + +#endif // KEEPASSX_KDBX3WRITER_H diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/Kdbx3XmlReader.cpp similarity index 92% rename from src/format/KeePass2XmlReader.cpp rename to src/format/Kdbx3XmlReader.cpp index de7ca6d79..6f3745553 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/Kdbx3XmlReader.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -#include "KeePass2XmlReader.h" +#include "Kdbx3XmlReader.h" #include #include @@ -30,7 +30,7 @@ typedef QPair StringPair; -KeePass2XmlReader::KeePass2XmlReader() +Kdbx3XmlReader::Kdbx3XmlReader() : m_randomStream(nullptr) , m_db(nullptr) , m_meta(nullptr) @@ -40,12 +40,12 @@ KeePass2XmlReader::KeePass2XmlReader() { } -void KeePass2XmlReader::setStrictMode(bool strictMode) +void Kdbx3XmlReader::setStrictMode(bool strictMode) { m_strictMode = strictMode; } -void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) +void Kdbx3XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) { m_error = false; m_errorStr.clear(); @@ -76,12 +76,12 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra if (!m_xml.error()) { if (!m_tmpParent->children().isEmpty()) { - qWarning("KeePass2XmlReader::readDatabase: found %d invalid group reference(s)", + qWarning("Kdbx3XmlReader::readDatabase: found %d invalid group reference(s)", m_tmpParent->children().size()); } if (!m_tmpParent->entries().isEmpty()) { - qWarning("KeePass2XmlReader::readDatabase: found %d invalid entry reference(s)", + qWarning("Kdbx3XmlReader::readDatabase: found %d invalid entry reference(s)", m_tmpParent->children().size()); } } @@ -97,7 +97,7 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra if (!m_xml.error()) { for (const QString& key : unusedKeys) { - qWarning("KeePass2XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); + qWarning("Kdbx3XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); } } @@ -127,26 +127,26 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra delete m_tmpParent; } -Database* KeePass2XmlReader::readDatabase(QIODevice* device) +Database* Kdbx3XmlReader::readDatabase(QIODevice* device) { Database* db = new Database(); readDatabase(device, db); return db; } -Database* KeePass2XmlReader::readDatabase(const QString& filename) +Database* Kdbx3XmlReader::readDatabase(const QString& filename) { QFile file(filename); file.open(QIODevice::ReadOnly); return readDatabase(&file); } -bool KeePass2XmlReader::hasError() +bool Kdbx3XmlReader::hasError() { return m_error || m_xml.hasError(); } -QString KeePass2XmlReader::errorString() +QString Kdbx3XmlReader::errorString() { if (m_error) { return m_errorStr; @@ -162,18 +162,18 @@ QString KeePass2XmlReader::errorString() } } -void KeePass2XmlReader::raiseError(const QString& errorMessage) +void Kdbx3XmlReader::raiseError(const QString& errorMessage) { m_error = true; m_errorStr = errorMessage; } -QByteArray KeePass2XmlReader::headerHash() +QByteArray Kdbx3XmlReader::headerHash() { return m_headerHash; } -bool KeePass2XmlReader::parseKeePassFile() +bool Kdbx3XmlReader::parseKeePassFile() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile"); @@ -202,7 +202,7 @@ bool KeePass2XmlReader::parseKeePassFile() return rootParsedSuccessfully; } -void KeePass2XmlReader::parseMeta() +void Kdbx3XmlReader::parseMeta() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Meta"); @@ -303,7 +303,7 @@ void KeePass2XmlReader::parseMeta() } } -void KeePass2XmlReader::parseMemoryProtection() +void Kdbx3XmlReader::parseMemoryProtection() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "MemoryProtection"); @@ -329,7 +329,7 @@ void KeePass2XmlReader::parseMemoryProtection() } } -void KeePass2XmlReader::parseCustomIcons() +void Kdbx3XmlReader::parseCustomIcons() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomIcons"); @@ -343,7 +343,7 @@ void KeePass2XmlReader::parseCustomIcons() } } -void KeePass2XmlReader::parseIcon() +void Kdbx3XmlReader::parseIcon() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Icon"); @@ -374,7 +374,7 @@ void KeePass2XmlReader::parseIcon() } } -void KeePass2XmlReader::parseBinaries() +void Kdbx3XmlReader::parseBinaries() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries"); @@ -393,7 +393,7 @@ void KeePass2XmlReader::parseBinaries() } if (m_binaryPool.contains(id)) { - qWarning("KeePass2XmlReader::parseBinaries: overwriting binary item \"%s\"", + qWarning("Kdbx3XmlReader::parseBinaries: overwriting binary item \"%s\"", qPrintable(id)); } @@ -405,7 +405,7 @@ void KeePass2XmlReader::parseBinaries() } } -void KeePass2XmlReader::parseCustomData() +void Kdbx3XmlReader::parseCustomData() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData"); @@ -419,7 +419,7 @@ void KeePass2XmlReader::parseCustomData() } } -void KeePass2XmlReader::parseCustomDataItem() +void Kdbx3XmlReader::parseCustomDataItem() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item"); @@ -450,7 +450,7 @@ void KeePass2XmlReader::parseCustomDataItem() } } -bool KeePass2XmlReader::parseRoot() +bool Kdbx3XmlReader::parseRoot() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Root"); @@ -486,7 +486,7 @@ bool KeePass2XmlReader::parseRoot() return groupParsedSuccessfully; } -Group* KeePass2XmlReader::parseGroup() +Group* Kdbx3XmlReader::parseGroup() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group"); @@ -525,7 +525,7 @@ Group* KeePass2XmlReader::parseGroup() } else { if (iconId >= DatabaseIcons::IconCount) { - qWarning("KeePass2XmlReader::parseGroup: icon id \"%d\" not supported", iconId); + qWarning("Kdbx3XmlReader::parseGroup: icon id \"%d\" not supported", iconId); } group->setIcon(iconId); } @@ -623,7 +623,7 @@ Group* KeePass2XmlReader::parseGroup() return group; } -void KeePass2XmlReader::parseDeletedObjects() +void Kdbx3XmlReader::parseDeletedObjects() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObjects"); @@ -637,7 +637,7 @@ void KeePass2XmlReader::parseDeletedObjects() } } -void KeePass2XmlReader::parseDeletedObject() +void Kdbx3XmlReader::parseDeletedObject() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject"); @@ -671,7 +671,7 @@ void KeePass2XmlReader::parseDeletedObject() } } -Entry* KeePass2XmlReader::parseEntry(bool history) +Entry* Kdbx3XmlReader::parseEntry(bool history) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry"); @@ -793,7 +793,7 @@ Entry* KeePass2XmlReader::parseEntry(bool history) return entry; } -void KeePass2XmlReader::parseEntryString(Entry* entry) +void Kdbx3XmlReader::parseEntryString(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "String"); @@ -855,7 +855,7 @@ void KeePass2XmlReader::parseEntryString(Entry* entry) } } -QPair KeePass2XmlReader::parseEntryBinary(Entry* entry) +QPair Kdbx3XmlReader::parseEntryBinary(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binary"); @@ -913,7 +913,7 @@ QPair KeePass2XmlReader::parseEntryBinary(Entry* entry) return poolRef; } -void KeePass2XmlReader::parseAutoType(Entry* entry) +void Kdbx3XmlReader::parseAutoType(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "AutoType"); @@ -936,7 +936,7 @@ void KeePass2XmlReader::parseAutoType(Entry* entry) } } -void KeePass2XmlReader::parseAutoTypeAssoc(Entry* entry) +void Kdbx3XmlReader::parseAutoTypeAssoc(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Association"); @@ -966,7 +966,7 @@ void KeePass2XmlReader::parseAutoTypeAssoc(Entry* entry) } } -QList KeePass2XmlReader::parseEntryHistory() +QList Kdbx3XmlReader::parseEntryHistory() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "History"); @@ -984,7 +984,7 @@ QList KeePass2XmlReader::parseEntryHistory() return historyItems; } -TimeInfo KeePass2XmlReader::parseTimes() +TimeInfo Kdbx3XmlReader::parseTimes() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times"); @@ -1019,12 +1019,12 @@ TimeInfo KeePass2XmlReader::parseTimes() return timeInfo; } -QString KeePass2XmlReader::readString() +QString Kdbx3XmlReader::readString() { return m_xml.readElementText(); } -bool KeePass2XmlReader::readBool() +bool Kdbx3XmlReader::readBool() { QString str = readString(); @@ -1043,7 +1043,7 @@ bool KeePass2XmlReader::readBool() } } -QDateTime KeePass2XmlReader::readDateTime() +QDateTime Kdbx3XmlReader::readDateTime() { QString str = readString(); QDateTime dt = QDateTime::fromString(str, Qt::ISODate); @@ -1060,7 +1060,7 @@ QDateTime KeePass2XmlReader::readDateTime() return dt; } -QColor KeePass2XmlReader::readColor() +QColor Kdbx3XmlReader::readColor() { QString colorStr = readString(); @@ -1101,7 +1101,7 @@ QColor KeePass2XmlReader::readColor() return color; } -int KeePass2XmlReader::readNumber() +int Kdbx3XmlReader::readNumber() { bool ok; int result = readString().toInt(&ok); @@ -1111,7 +1111,7 @@ int KeePass2XmlReader::readNumber() return result; } -Uuid KeePass2XmlReader::readUuid() +Uuid Kdbx3XmlReader::readUuid() { QByteArray uuidBin = readBinary(); if (uuidBin.isEmpty()) { @@ -1128,12 +1128,12 @@ Uuid KeePass2XmlReader::readUuid() } } -QByteArray KeePass2XmlReader::readBinary() +QByteArray Kdbx3XmlReader::readBinary() { return QByteArray::fromBase64(readString().toLatin1()); } -QByteArray KeePass2XmlReader::readCompressedBinary() +QByteArray Kdbx3XmlReader::readCompressedBinary() { QByteArray rawData = readBinary(); @@ -1151,7 +1151,7 @@ QByteArray KeePass2XmlReader::readCompressedBinary() return result; } -Group* KeePass2XmlReader::getGroup(const Uuid& uuid) +Group* Kdbx3XmlReader::getGroup(const Uuid& uuid) { if (uuid.isNull()) { return nullptr; @@ -1170,7 +1170,7 @@ Group* KeePass2XmlReader::getGroup(const Uuid& uuid) } } -Entry* KeePass2XmlReader::getEntry(const Uuid& uuid) +Entry* Kdbx3XmlReader::getEntry(const Uuid& uuid) { if (uuid.isNull()) { return nullptr; @@ -1189,8 +1189,8 @@ Entry* KeePass2XmlReader::getEntry(const Uuid& uuid) } } -void KeePass2XmlReader::skipCurrentElement() +void Kdbx3XmlReader::skipCurrentElement() { - qWarning("KeePass2XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString())); + qWarning("Kdbx3XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString())); m_xml.skipCurrentElement(); } diff --git a/src/format/KeePass2XmlReader.h b/src/format/Kdbx3XmlReader.h similarity index 92% rename from src/format/KeePass2XmlReader.h rename to src/format/Kdbx3XmlReader.h index d2e0e0025..1837aaab6 100644 --- a/src/format/KeePass2XmlReader.h +++ b/src/format/Kdbx3XmlReader.h @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#ifndef KEEPASSX_KEEPASS2XMLREADER_H -#define KEEPASSX_KEEPASS2XMLREADER_H +#ifndef KEEPASSX_KDBX3XMLREADER_H +#define KEEPASSX_KDBX3XMLREADER_H #include #include @@ -34,12 +34,12 @@ class Group; class KeePass2RandomStream; class Metadata; -class KeePass2XmlReader +class Kdbx3XmlReader { - Q_DECLARE_TR_FUNCTIONS(KeePass2XmlReader) + Q_DECLARE_TR_FUNCTIONS(Kdbx3XmlReader) public: - KeePass2XmlReader(); + Kdbx3XmlReader(); Database* readDatabase(QIODevice* device); void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr); Database* readDatabase(const QString& filename); @@ -98,4 +98,4 @@ private: bool m_strictMode; }; -#endif // KEEPASSX_KEEPASS2XMLREADER_H +#endif // KEEPASSX_KDBX3XMLREADER_H diff --git a/src/format/KeePass2XmlWriter.cpp b/src/format/Kdbx3XmlWriter.cpp similarity index 86% rename from src/format/KeePass2XmlWriter.cpp rename to src/format/Kdbx3XmlWriter.cpp index fa67ece93..ff9262a01 100644 --- a/src/format/KeePass2XmlWriter.cpp +++ b/src/format/Kdbx3XmlWriter.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -#include "KeePass2XmlWriter.h" +#include "Kdbx3XmlWriter.h" #include #include @@ -24,7 +24,7 @@ #include "format/KeePass2RandomStream.h" #include "streams/QtIOCompressor" -KeePass2XmlWriter::KeePass2XmlWriter() +Kdbx3XmlWriter::Kdbx3XmlWriter() : m_db(nullptr) , m_meta(nullptr) , m_randomStream(nullptr) @@ -35,7 +35,7 @@ KeePass2XmlWriter::KeePass2XmlWriter() m_xml.setCodec("UTF-8"); } -void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, +void Kdbx3XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, const QByteArray& headerHash) { m_db = db; @@ -63,24 +63,24 @@ void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2R } } -void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db) +void Kdbx3XmlWriter::writeDatabase(const QString& filename, Database* db) { QFile file(filename); file.open(QIODevice::WriteOnly|QIODevice::Truncate); writeDatabase(&file, db); } -bool KeePass2XmlWriter::hasError() +bool Kdbx3XmlWriter::hasError() { return m_error; } -QString KeePass2XmlWriter::errorString() +QString Kdbx3XmlWriter::errorString() { return m_errorStr; } -void KeePass2XmlWriter::generateIdMap() +void Kdbx3XmlWriter::generateIdMap() { const QList allEntries = m_db->rootGroup()->entriesRecursive(true); int nextId = 0; @@ -96,7 +96,7 @@ void KeePass2XmlWriter::generateIdMap() } } -void KeePass2XmlWriter::writeMetadata() +void Kdbx3XmlWriter::writeMetadata() { m_xml.writeStartElement("Meta"); @@ -132,7 +132,7 @@ void KeePass2XmlWriter::writeMetadata() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeMemoryProtection() +void Kdbx3XmlWriter::writeMemoryProtection() { m_xml.writeStartElement("MemoryProtection"); @@ -145,7 +145,7 @@ void KeePass2XmlWriter::writeMemoryProtection() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeCustomIcons() +void Kdbx3XmlWriter::writeCustomIcons() { m_xml.writeStartElement("CustomIcons"); @@ -157,7 +157,7 @@ void KeePass2XmlWriter::writeCustomIcons() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) +void Kdbx3XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) { m_xml.writeStartElement("Icon"); @@ -174,7 +174,7 @@ void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeBinaries() +void Kdbx3XmlWriter::writeBinaries() { m_xml.writeStartElement("Binaries"); @@ -216,7 +216,7 @@ void KeePass2XmlWriter::writeBinaries() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeCustomData() +void Kdbx3XmlWriter::writeCustomData() { m_xml.writeStartElement("CustomData"); @@ -229,7 +229,7 @@ void KeePass2XmlWriter::writeCustomData() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeCustomDataItem(const QString& key, const QString& value) +void Kdbx3XmlWriter::writeCustomDataItem(const QString& key, const QString& value) { m_xml.writeStartElement("Item"); @@ -239,7 +239,7 @@ void KeePass2XmlWriter::writeCustomDataItem(const QString& key, const QString& v m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeRoot() +void Kdbx3XmlWriter::writeRoot() { Q_ASSERT(m_db->rootGroup()); @@ -251,7 +251,7 @@ void KeePass2XmlWriter::writeRoot() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeGroup(const Group* group) +void Kdbx3XmlWriter::writeGroup(const Group* group) { Q_ASSERT(!group->uuid().isNull()); @@ -288,7 +288,7 @@ void KeePass2XmlWriter::writeGroup(const Group* group) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeTimes(const TimeInfo& ti) +void Kdbx3XmlWriter::writeTimes(const TimeInfo& ti) { m_xml.writeStartElement("Times"); @@ -303,7 +303,7 @@ void KeePass2XmlWriter::writeTimes(const TimeInfo& ti) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeDeletedObjects() +void Kdbx3XmlWriter::writeDeletedObjects() { m_xml.writeStartElement("DeletedObjects"); @@ -315,7 +315,7 @@ void KeePass2XmlWriter::writeDeletedObjects() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeDeletedObject(const DeletedObject& delObj) +void Kdbx3XmlWriter::writeDeletedObject(const DeletedObject& delObj) { m_xml.writeStartElement("DeletedObject"); @@ -325,7 +325,7 @@ void KeePass2XmlWriter::writeDeletedObject(const DeletedObject& delObj) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeEntry(const Entry* entry) +void Kdbx3XmlWriter::writeEntry(const Entry* entry) { Q_ASSERT(!entry->uuid().isNull()); @@ -407,7 +407,7 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeAutoType(const Entry* entry) +void Kdbx3XmlWriter::writeAutoType(const Entry* entry) { m_xml.writeStartElement("AutoType"); @@ -423,7 +423,7 @@ void KeePass2XmlWriter::writeAutoType(const Entry* entry) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc) +void Kdbx3XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc) { m_xml.writeStartElement("Association"); @@ -433,7 +433,7 @@ void KeePass2XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Associati m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeEntryHistory(const Entry* entry) +void Kdbx3XmlWriter::writeEntryHistory(const Entry* entry) { m_xml.writeStartElement("History"); @@ -445,7 +445,7 @@ void KeePass2XmlWriter::writeEntryHistory(const Entry* entry) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeString(const QString& qualifiedName, const QString& string) +void Kdbx3XmlWriter::writeString(const QString& qualifiedName, const QString& string) { if (string.isEmpty()) { m_xml.writeEmptyElement(qualifiedName); @@ -455,12 +455,12 @@ void KeePass2XmlWriter::writeString(const QString& qualifiedName, const QString& } } -void KeePass2XmlWriter::writeNumber(const QString& qualifiedName, int number) +void Kdbx3XmlWriter::writeNumber(const QString& qualifiedName, int number) { writeString(qualifiedName, QString::number(number)); } -void KeePass2XmlWriter::writeBool(const QString& qualifiedName, bool b) +void Kdbx3XmlWriter::writeBool(const QString& qualifiedName, bool b) { if (b) { writeString(qualifiedName, "True"); @@ -470,7 +470,7 @@ void KeePass2XmlWriter::writeBool(const QString& qualifiedName, bool b) } } -void KeePass2XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime) +void Kdbx3XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime) { Q_ASSERT(dateTime.isValid()); Q_ASSERT(dateTime.timeSpec() == Qt::UTC); @@ -485,12 +485,12 @@ void KeePass2XmlWriter::writeDateTime(const QString& qualifiedName, const QDateT writeString(qualifiedName, dateTimeStr); } -void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid) +void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid) { writeString(qualifiedName, uuid.toBase64()); } -void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) +void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) { if (group) { writeUuid(qualifiedName, group->uuid()); @@ -500,7 +500,7 @@ void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Group* gro } } -void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) +void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) { if (entry) { writeUuid(qualifiedName, entry->uuid()); @@ -510,12 +510,12 @@ void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Entry* ent } } -void KeePass2XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba) +void Kdbx3XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba) { writeString(qualifiedName, QString::fromLatin1(ba.toBase64())); } -void KeePass2XmlWriter::writeColor(const QString& qualifiedName, const QColor& color) +void Kdbx3XmlWriter::writeColor(const QString& qualifiedName, const QColor& color) { QString colorStr; @@ -528,7 +528,7 @@ void KeePass2XmlWriter::writeColor(const QString& qualifiedName, const QColor& c writeString(qualifiedName, colorStr); } -void KeePass2XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState) +void Kdbx3XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState) { QString value; @@ -545,7 +545,7 @@ void KeePass2XmlWriter::writeTriState(const QString& qualifiedName, Group::TriSt writeString(qualifiedName, value); } -QString KeePass2XmlWriter::colorPartToString(int value) +QString Kdbx3XmlWriter::colorPartToString(int value) { QString str = QString::number(value, 16).toUpper(); if (str.length() == 1) { @@ -555,7 +555,7 @@ QString KeePass2XmlWriter::colorPartToString(int value) return str; } -QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str) +QString Kdbx3XmlWriter::stripInvalidXml10Chars(QString str) { for (int i = str.size() - 1; i >= 0; i--) { const QChar ch = str.at(i); @@ -580,7 +580,7 @@ QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str) return str; } -void KeePass2XmlWriter::raiseError(const QString& errorMessage) +void Kdbx3XmlWriter::raiseError(const QString& errorMessage) { m_error = true; m_errorStr = errorMessage; diff --git a/src/format/KeePass2XmlWriter.h b/src/format/Kdbx3XmlWriter.h similarity index 94% rename from src/format/KeePass2XmlWriter.h rename to src/format/Kdbx3XmlWriter.h index 23e148dbb..2bb130566 100644 --- a/src/format/KeePass2XmlWriter.h +++ b/src/format/Kdbx3XmlWriter.h @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#ifndef KEEPASSX_KEEPASS2XMLWRITER_H -#define KEEPASSX_KEEPASS2XMLWRITER_H +#ifndef KEEPASSX_KDBX3XMLWRITER_H +#define KEEPASSX_KDBX3XMLWRITER_H #include #include @@ -32,10 +32,10 @@ class KeePass2RandomStream; class Metadata; -class KeePass2XmlWriter +class Kdbx3XmlWriter { public: - KeePass2XmlWriter(); + Kdbx3XmlWriter(); void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr, const QByteArray& headerHash = QByteArray()); void writeDatabase(const QString& filename, Database* db); @@ -87,4 +87,4 @@ private: QString m_errorStr; }; -#endif // KEEPASSX_KEEPASS2XMLWRITER_H +#endif // KEEPASSX_KDBX3XMLWRITER_H diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index f42e7b9bb..a09f0adce 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,198 +15,30 @@ * along with this program. If not, see . */ -#include "KeePass2Reader.h" - -#include +#include +#include #include -#include -#include "core/Database.h" #include "core/Endian.h" -#include "crypto/CryptoHash.h" -#include "crypto/kdf/AesKdf.h" +#include "keys/CompositeKey.h" +#include "format/KeePass2Reader.h" #include "format/KeePass1.h" #include "format/KeePass2.h" -#include "format/KeePass2RandomStream.h" -#include "format/KeePass2XmlReader.h" -#include "streams/HashedBlockStream.h" -#include "streams/QtIOCompressor" -#include "streams/StoreDataStream.h" -#include "streams/SymmetricCipherStream.h" +#include "format/Kdbx3Reader.h" -KeePass2Reader::KeePass2Reader() - : m_device(nullptr) - , m_headerStream(nullptr) - , m_error(false) - , m_headerEnd(false) - , m_saveXml(false) - , m_db(nullptr) - , m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo) +BaseKeePass2Reader::BaseKeePass2Reader() : + m_error(false), + m_saveXml(false), + m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo) { -} - -Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) -{ - QScopedPointer db(new Database()); - m_db = db.data(); - m_device = device; - m_error = false; m_errorStr.clear(); - m_headerEnd = false; m_xmlData.clear(); - m_masterSeed.clear(); - m_encryptionIV.clear(); - m_streamStartBytes.clear(); m_protectedStreamKey.clear(); - - StoreDataStream headerStream(m_device); - headerStream.open(QIODevice::ReadOnly); - m_headerStream = &headerStream; - - bool ok; - - quint32 signature1 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); - if (!ok || signature1 != KeePass2::SIGNATURE_1) { - raiseError(tr("Not a KeePass database.")); - return nullptr; - } - - quint32 signature2 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); - if (ok && signature2 == KeePass1::SIGNATURE_2) { - raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" - "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; - } - else if (!ok || signature2 != KeePass2::SIGNATURE_2) { - raiseError(tr("Not a KeePass database.")); - return nullptr; - } - - quint32 version = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok) - & KeePass2::FILE_VERSION_CRITICAL_MASK; - quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; - if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) { - raiseError(tr("Unsupported KeePass database version.")); - return nullptr; - } - - while (readHeaderField() && !hasError()) { - } - - headerStream.close(); - - if (hasError()) { - return nullptr; - } - - // check if all required headers were present - if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() - || m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty() - || m_db->cipher().isNull()) { - raiseError("missing database headers"); - return nullptr; - } - - if (!m_db->setKey(key, false)) { - raiseError(tr("Unable to calculate master key")); - return nullptr; - } - - if (m_db->challengeMasterSeed(m_masterSeed) == false) { - raiseError(tr("Unable to issue challenge-response.")); - return nullptr; - } - - CryptoHash hash(CryptoHash::Sha256); - hash.addData(m_masterSeed); - hash.addData(m_db->challengeResponseKey()); - hash.addData(m_db->transformedMasterKey()); - QByteArray finalKey = hash.result(); - - SymmetricCipherStream cipherStream(m_device, SymmetricCipher::cipherToAlgorithm(m_db->cipher()), - SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - if (!cipherStream.init(finalKey, m_encryptionIV)) { - raiseError(cipherStream.errorString()); - return nullptr; - } - if (!cipherStream.open(QIODevice::ReadOnly)) { - raiseError(cipherStream.errorString()); - return nullptr; - } - - QByteArray realStart = cipherStream.read(32); - - if (realStart != m_streamStartBytes) { - raiseError(tr("Wrong key or database file is corrupt.")); - return nullptr; - } - - HashedBlockStream hashedStream(&cipherStream); - if (!hashedStream.open(QIODevice::ReadOnly)) { - raiseError(hashedStream.errorString()); - return nullptr; - } - - QIODevice* xmlDevice; - QScopedPointer ioCompressor; - - if (m_db->compressionAlgo() == Database::CompressionNone) { - xmlDevice = &hashedStream; - } - else { - ioCompressor.reset(new QtIOCompressor(&hashedStream)); - ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); - if (!ioCompressor->open(QIODevice::ReadOnly)) { - raiseError(ioCompressor->errorString()); - return nullptr; - } - xmlDevice = ioCompressor.data(); - } - - KeePass2RandomStream randomStream(m_irsAlgo); - if (!randomStream.init(m_protectedStreamKey)) { - raiseError(randomStream.errorString()); - return nullptr; - } - - QScopedPointer buffer; - - if (m_saveXml) { - m_xmlData = xmlDevice->readAll(); - buffer.reset(new QBuffer(&m_xmlData)); - buffer->open(QIODevice::ReadOnly); - xmlDevice = buffer.data(); - } - - KeePass2XmlReader xmlReader; - xmlReader.readDatabase(xmlDevice, m_db, &randomStream); - - if (xmlReader.hasError()) { - raiseError(xmlReader.errorString()); - if (keepDatabase) { - return db.take(); - } - else { - return nullptr; - } - } - - Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty()); - - if (!xmlReader.headerHash().isEmpty()) { - QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256); - if (headerHash != xmlReader.headerHash()) { - raiseError("Header doesn't match hash"); - return nullptr; - } - } - - return db.take(); } -Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key) +BaseKeePass2Reader::~BaseKeePass2Reader() {} + +Database* BaseKeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key) { QFile file(filename); if (!file.open(QFile::ReadOnly)) { @@ -224,238 +56,113 @@ Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeK return db.take(); } -bool KeePass2Reader::hasError() +bool BaseKeePass2Reader::hasError() { return m_error; } -QString KeePass2Reader::errorString() +QString BaseKeePass2Reader::errorString() { return m_errorStr; } -void KeePass2Reader::setSaveXml(bool save) +void BaseKeePass2Reader::setSaveXml(bool save) { m_saveXml = save; } -QByteArray KeePass2Reader::xmlData() +QByteArray BaseKeePass2Reader::xmlData() { return m_xmlData; } -QByteArray KeePass2Reader::streamKey() +QByteArray BaseKeePass2Reader::streamKey() { return m_protectedStreamKey; } -void KeePass2Reader::raiseError(const QString& errorMessage) +KeePass2::ProtectedStreamAlgo BaseKeePass2Reader::protectedStreamAlgo() const { + return m_irsAlgo; +} + + +void BaseKeePass2Reader::raiseError(const QString& errorMessage) { m_error = true; m_errorStr = errorMessage; } -bool KeePass2Reader::readHeaderField() +Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) { - QByteArray fieldIDArray = m_headerStream->read(1); - if (fieldIDArray.size() != 1) { - raiseError("Invalid header id size"); - return false; - } - quint8 fieldID = fieldIDArray.at(0); + m_error = false; + m_errorStr.clear(); bool ok; - quint16 fieldLen = Endian::readUInt16(m_headerStream, KeePass2::BYTEORDER, &ok); - if (!ok) { - raiseError("Invalid header field length"); - return false; + + quint32 signature1 = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok); + if (!ok || signature1 != KeePass2::SIGNATURE_1) { + raiseError(tr("Not a KeePass database.")); + return nullptr; } - QByteArray fieldData; - if (fieldLen != 0) { - fieldData = m_headerStream->read(fieldLen); - if (fieldData.size() != fieldLen) { - raiseError("Invalid header data length"); - return false; - } + quint32 signature2 = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok); + if (ok && signature2 == KeePass1::SIGNATURE_2) { + raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" + "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; + } + else if (!ok || signature2 != KeePass2::SIGNATURE_2) { + raiseError(tr("Not a KeePass database.")); + return nullptr; } - switch (fieldID) { - case KeePass2::EndOfHeader: - m_headerEnd = true; - break; - - case KeePass2::CipherID: - setCipher(fieldData); - break; - - case KeePass2::CompressionFlags: - setCompressionFlags(fieldData); - break; - - case KeePass2::MasterSeed: - setMasterSeed(fieldData); - break; - - case KeePass2::TransformSeed: - setTransformSeed(fieldData); - break; - - case KeePass2::TransformRounds: - setTransformRounds(fieldData); - break; - - case KeePass2::EncryptionIV: - setEncryptionIV(fieldData); - break; - - case KeePass2::ProtectedStreamKey: - setProtectedStreamKey(fieldData); - break; - - case KeePass2::StreamStartBytes: - setStreamStartBytes(fieldData); - break; - - case KeePass2::InnerRandomStreamID: - setInnerRandomStreamID(fieldData); - break; - - default: - qWarning("Unknown header field read: id=%d", fieldID); - break; + m_version = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok) + & KeePass2::FILE_VERSION_CRITICAL_MASK; + quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; + if (!ok || (m_version < KeePass2::FILE_VERSION_MIN) || (m_version > maxVersion)) { + raiseError(tr("Unsupported KeePass 2 database version.")); + return nullptr; } - return !m_headerEnd; + device->seek(0); + m_reader.reset(static_cast(new Kdbx3Reader())); + m_reader->setSaveXml(m_saveXml); + return m_reader->readDatabase(device, key, keepDatabase); } -void KeePass2Reader::setCipher(const QByteArray& data) +bool KeePass2Reader::hasError() { - if (data.size() != Uuid::Length) { - raiseError("Invalid cipher uuid length"); - } - else { - Uuid uuid(data); - - if (uuid != KeePass2::CIPHER_AES && uuid != KeePass2::CIPHER_TWOFISH) { - raiseError("Unsupported cipher"); - } - else { - m_db->setCipher(uuid); - } - } + return m_error || (m_reader && m_reader->hasError()); } -void KeePass2Reader::setCompressionFlags(const QByteArray& data) +QString KeePass2Reader::errorString() { - if (data.size() != 4) { - raiseError("Invalid compression flags length"); - } - else { - quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); - - if (id > Database::CompressionAlgorithmMax) { - raiseError("Unsupported compression algorithm"); - } - else { - m_db->setCompressionAlgo(static_cast(id)); - } - } + return m_reader ? m_reader->errorString() : m_errorStr; } -void KeePass2Reader::setMasterSeed(const QByteArray& data) +QByteArray KeePass2Reader::xmlData() { - if (data.size() != 32) { - raiseError("Invalid master seed size"); - } - else { - m_masterSeed = data; - } + return m_reader ? m_reader->xmlData() : m_xmlData; } -void KeePass2Reader::setTransformSeed(const QByteArray& data) +QByteArray KeePass2Reader::streamKey() { - if (data.size() != 32) { - raiseError("Invalid transform seed size"); - } - else { - AesKdf* aesKdf; - if (m_db->kdf()->type() == Kdf::Type::AES) { - aesKdf = static_cast(m_db->kdf()); - } else { - aesKdf = new AesKdf(); - m_db->setKdf(aesKdf); - } - - aesKdf->setSeed(data); - } -} - -void KeePass2Reader::setTransformRounds(const QByteArray& data) -{ - if (data.size() != 8) { - raiseError("Invalid transform rounds size"); - } - else { - quint64 rounds = Endian::bytesToUInt64(data, KeePass2::BYTEORDER); - - AesKdf* aesKdf; - if (m_db->kdf()->type() == Kdf::Type::AES) { - aesKdf = static_cast(m_db->kdf()); - } else { - aesKdf = new AesKdf(); - m_db->setKdf(aesKdf); - } - - aesKdf->setRounds(rounds); - } -} - -void KeePass2Reader::setEncryptionIV(const QByteArray& data) -{ - if (data.size() != 16) { - raiseError("Invalid encryption iv size"); - } - else { - m_encryptionIV = data; - } -} - -void KeePass2Reader::setProtectedStreamKey(const QByteArray& data) -{ - if (data.size() != 32) { - raiseError("Invalid stream key size"); - } - else { - m_protectedStreamKey = data; - } -} - -void KeePass2Reader::setStreamStartBytes(const QByteArray& data) -{ - if (data.size() != 32) { - raiseError("Invalid start bytes size"); - } - else { - m_streamStartBytes = data; - } -} - -void KeePass2Reader::setInnerRandomStreamID(const QByteArray& data) -{ - if (data.size() != 4) { - raiseError("Invalid random stream id size"); - } - else { - quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); - m_irsAlgo = KeePass2::idToProtectedStreamAlgo(id); - if (m_irsAlgo == KeePass2::ArcFourVariant || m_irsAlgo == KeePass2::InvalidProtectedStreamAlgo) { - raiseError("Unsupported random stream algorithm"); - } - } + return m_reader ? m_reader->streamKey() : m_protectedStreamKey; } KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const { - return m_irsAlgo; + return m_reader ? m_reader->protectedStreamAlgo() : m_irsAlgo; +} + +quint32 KeePass2Reader::version() const +{ + return m_version; +} + +BaseKeePass2Reader* KeePass2Reader::reader() const +{ + return m_reader.data(); } diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h index e9a947284..21c317023 100644 --- a/src/format/KeePass2Reader.h +++ b/src/format/KeePass2Reader.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,58 +18,65 @@ #ifndef KEEPASSX_KEEPASS2READER_H #define KEEPASSX_KEEPASS2READER_H +#include +#include +#include #include +#include +#include -#include "keys/CompositeKey.h" #include "format/KeePass2.h" +#include "core/Database.h" +#include "keys/CompositeKey.h" -class Database; -class QIODevice; - -class KeePass2Reader +class BaseKeePass2Reader { - Q_DECLARE_TR_FUNCTIONS(KeePass2Reader) - public: - KeePass2Reader(); - Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false); - Database* readDatabase(const QString& filename, const CompositeKey& key); - bool hasError(); - QString errorString(); - void setSaveXml(bool save); - QByteArray xmlData(); - QByteArray streamKey(); - KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const; + BaseKeePass2Reader(); -private: + virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) = 0; + virtual Database* readDatabase(const QString& filename, const CompositeKey& key); + + virtual bool hasError(); + virtual QString errorString(); + virtual void setSaveXml(bool save); + virtual QByteArray xmlData(); + virtual QByteArray streamKey(); + virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const; + + virtual ~BaseKeePass2Reader(); + +protected: void raiseError(const QString& errorMessage); - bool readHeaderField(); - - void setCipher(const QByteArray& data); - void setCompressionFlags(const QByteArray& data); - void setMasterSeed(const QByteArray& data); - void setTransformSeed(const QByteArray& data); - void setTransformRounds(const QByteArray& data); - void setEncryptionIV(const QByteArray& data); - void setProtectedStreamKey(const QByteArray& data); - void setStreamStartBytes(const QByteArray& data); - void setInnerRandomStreamID(const QByteArray& data); - - QIODevice* m_device; - QIODevice* m_headerStream; bool m_error; QString m_errorStr; - bool m_headerEnd; + bool m_saveXml; QByteArray m_xmlData; - - Database* m_db; - QByteArray m_masterSeed; - QByteArray m_encryptionIV; - QByteArray m_streamStartBytes; QByteArray m_protectedStreamKey; KeePass2::ProtectedStreamAlgo m_irsAlgo; }; +class KeePass2Reader : public BaseKeePass2Reader +{ + Q_DECLARE_TR_FUNCTIONS(KeePass2Reader) + +public: + virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override; + using BaseKeePass2Reader::readDatabase; + + virtual bool hasError() override; + virtual QString errorString() override; + virtual QByteArray xmlData() override; + virtual QByteArray streamKey() override; + virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const override; + + quint32 version() const; + BaseKeePass2Reader* reader() const; +private: + QScopedPointer m_reader; + quint32 m_version; +}; + #endif // KEEPASSX_KEEPASS2READER_H diff --git a/src/format/KeePass2Repair.cpp b/src/format/KeePass2Repair.cpp index 5f2732e2a..0e79fa8ba 100644 --- a/src/format/KeePass2Repair.cpp +++ b/src/format/KeePass2Repair.cpp @@ -22,9 +22,10 @@ #include #include +#include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" #include "format/KeePass2Reader.h" -#include "format/KeePass2XmlReader.h" +#include "format/Kdbx3XmlReader.h" KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key) { @@ -73,7 +74,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, KeePass2RandomStream randomStream(reader.protectedStreamAlgo()); randomStream.init(reader.streamKey()); - KeePass2XmlReader xmlReader; + Kdbx3XmlReader xmlReader; QBuffer buffer(&xmlData); buffer.open(QIODevice::ReadOnly); xmlReader.readDatabase(&buffer, db.data(), &randomStream); diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index 6dc89c36d..00392dc05 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,197 +15,58 @@ * along with this program. If not, see . */ -#include "KeePass2Writer.h" - -#include -#include #include +#include +#include +#include "format/KeePass2Writer.h" #include "core/Database.h" -#include "core/Endian.h" -#include "crypto/CryptoHash.h" -#include "crypto/kdf/AesKdf.h" -#include "crypto/Random.h" -#include "format/KeePass2RandomStream.h" -#include "format/KeePass2XmlWriter.h" -#include "streams/HashedBlockStream.h" -#include "streams/QtIOCompressor" -#include "streams/SymmetricCipherStream.h" +#include "format/Kdbx3Writer.h" -#define CHECK_RETURN(x) if (!(x)) return; -#define CHECK_RETURN_FALSE(x) if (!(x)) return false; - -KeePass2Writer::KeePass2Writer() - : m_device(0) - , m_error(false) +BaseKeePass2Writer::BaseKeePass2Writer() : m_error(false) { -} - -void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) -{ - m_error = false; m_errorStr.clear(); - - QByteArray masterSeed = randomGen()->randomArray(32); - QByteArray encryptionIV = randomGen()->randomArray(16); - QByteArray protectedStreamKey = randomGen()->randomArray(32); - QByteArray startBytes = randomGen()->randomArray(32); - QByteArray endOfHeader = "\r\n\r\n"; - - if (db->challengeMasterSeed(masterSeed) == false) { - raiseError(tr("Unable to issue challenge-response.")); - return; - } - - if (!db->setKey(db->key(), false, true)) { - raiseError(tr("Unable to calculate master key")); - return; - } - - CryptoHash hash(CryptoHash::Sha256); - hash.addData(masterSeed); - hash.addData(db->challengeResponseKey()); - Q_ASSERT(!db->transformedMasterKey().isEmpty()); - hash.addData(db->transformedMasterKey()); - QByteArray finalKey = hash.result(); - - QBuffer header; - header.open(QIODevice::WriteOnly); - m_device = &header; - - CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER))); - CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); - CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER))); - - CHECK_RETURN(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray())); - CHECK_RETURN(writeHeaderField(KeePass2::CompressionFlags, - Endian::int32ToBytes(db->compressionAlgo(), - KeePass2::BYTEORDER))); - AesKdf* kdf = static_cast(db->kdf()); - CHECK_RETURN(writeHeaderField(KeePass2::MasterSeed, masterSeed)); - CHECK_RETURN(writeHeaderField(KeePass2::TransformSeed, kdf->seed())); - CHECK_RETURN(writeHeaderField(KeePass2::TransformRounds, - Endian::int64ToBytes(kdf->rounds(), - KeePass2::BYTEORDER))); - CHECK_RETURN(writeHeaderField(KeePass2::EncryptionIV, encryptionIV)); - CHECK_RETURN(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey)); - CHECK_RETURN(writeHeaderField(KeePass2::StreamStartBytes, startBytes)); - CHECK_RETURN(writeHeaderField(KeePass2::InnerRandomStreamID, - Endian::int32ToBytes(KeePass2::Salsa20, - KeePass2::BYTEORDER))); - CHECK_RETURN(writeHeaderField(KeePass2::EndOfHeader, endOfHeader)); - - header.close(); - m_device = device; - QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256); - CHECK_RETURN(writeData(header.data())); - - SymmetricCipherStream cipherStream(device, SymmetricCipher::cipherToAlgorithm(db->cipher()), - SymmetricCipher::Cbc, SymmetricCipher::Encrypt); - cipherStream.init(finalKey, encryptionIV); - if (!cipherStream.open(QIODevice::WriteOnly)) { - raiseError(cipherStream.errorString()); - return; - } - m_device = &cipherStream; - CHECK_RETURN(writeData(startBytes)); - - HashedBlockStream hashedStream(&cipherStream); - if (!hashedStream.open(QIODevice::WriteOnly)) { - raiseError(hashedStream.errorString()); - return; - } - - QScopedPointer ioCompressor; - - if (db->compressionAlgo() == Database::CompressionNone) { - m_device = &hashedStream; - } - else { - ioCompressor.reset(new QtIOCompressor(&hashedStream)); - ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); - if (!ioCompressor->open(QIODevice::WriteOnly)) { - raiseError(ioCompressor->errorString()); - return; - } - m_device = ioCompressor.data(); - } - - KeePass2RandomStream randomStream(KeePass2::Salsa20); - if (!randomStream.init(protectedStreamKey)) { - raiseError(randomStream.errorString()); - return; - } - - KeePass2XmlWriter xmlWriter; - xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash); - - // Explicitly close/reset streams so they are flushed and we can detect - // errors. QIODevice::close() resets errorString() etc. - if (ioCompressor) { - ioCompressor->close(); - } - if (!hashedStream.reset()) { - raiseError(hashedStream.errorString()); - return; - } - if (!cipherStream.reset()) { - raiseError(cipherStream.errorString()); - return; - } - - if (xmlWriter.hasError()) { - raiseError(xmlWriter.errorString()); - } } -bool KeePass2Writer::writeData(const QByteArray& data) -{ - if (m_device->write(data) != data.size()) { - raiseError(m_device->errorString()); - return false; - } - else { - return true; - } -} +BaseKeePass2Writer::~BaseKeePass2Writer() {} -bool KeePass2Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data) -{ - Q_ASSERT(data.size() <= 65535); - - QByteArray fieldIdArr; - fieldIdArr[0] = fieldId; - CHECK_RETURN_FALSE(writeData(fieldIdArr)); - CHECK_RETURN_FALSE(writeData(Endian::int16ToBytes(static_cast(data.size()), - KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(data)); - - return true; -} - -void KeePass2Writer::writeDatabase(const QString& filename, Database* db) -{ - QFile file(filename); - if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) { - raiseError(file.errorString()); - return; - } - writeDatabase(&file, db); -} - -bool KeePass2Writer::hasError() +bool BaseKeePass2Writer::hasError() { return m_error; } -QString KeePass2Writer::errorString() +QString BaseKeePass2Writer::errorString() { return m_errorStr; } -void KeePass2Writer::raiseError(const QString& errorMessage) +void BaseKeePass2Writer::raiseError(const QString& errorMessage) { m_error = true; m_errorStr = errorMessage; } + +bool BaseKeePass2Writer::writeDatabase(const QString& filename, Database* db) +{ + QFile file(filename); + if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) { + raiseError(file.errorString()); + return false; + } + return writeDatabase(&file, db); +} + +bool KeePass2Writer::hasError() +{ + return m_error || (m_writer && m_writer->hasError()); +} + +QString KeePass2Writer::errorString() +{ + return m_writer ? m_writer->errorString() : m_errorStr; +} + +bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) { + m_writer.reset(static_cast(new Kdbx3Writer())); + return m_writer->writeDatabase(device, db); +} diff --git a/src/format/KeePass2Writer.h b/src/format/KeePass2Writer.h index 184aa1a71..85802eb44 100644 --- a/src/format/KeePass2Writer.h +++ b/src/format/KeePass2Writer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,33 +18,48 @@ #ifndef KEEPASSX_KEEPASS2WRITER_H #define KEEPASSX_KEEPASS2WRITER_H +#include +#include +#include #include +#include +#include "core/Database.h" #include "format/KeePass2.h" -#include "keys/CompositeKey.h" -class Database; -class QIODevice; - -class KeePass2Writer +class BaseKeePass2Writer { - Q_DECLARE_TR_FUNCTIONS(KeePass2Writer) - public: - KeePass2Writer(); - void writeDatabase(QIODevice* device, Database* db); - void writeDatabase(const QString& filename, Database* db); - bool hasError(); - QString errorString(); + BaseKeePass2Writer(); -private: - bool writeData(const QByteArray& data); - bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data); + virtual bool writeDatabase(QIODevice* device, Database* db) = 0; + virtual bool writeDatabase(const QString& filename, Database* db); + + virtual bool hasError(); + virtual QString errorString(); + + virtual ~BaseKeePass2Writer(); + +protected: void raiseError(const QString& errorMessage); - QIODevice* m_device; bool m_error; QString m_errorStr; }; -#endif // KEEPASSX_KEEPASS2WRITER_H +class KeePass2Writer : public BaseKeePass2Writer +{ + Q_DECLARE_TR_FUNCTIONS(KeePass2Writer) + +public: + virtual bool writeDatabase(QIODevice* device, Database* db) override; + using BaseKeePass2Writer::writeDatabase; + + virtual bool hasError() override; + virtual QString errorString() override; + +private: + QScopedPointer m_writer; +}; + +#endif // KEEPASSX_KEEPASS2READER_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c36eefd4a..bdf0e6f86 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -107,7 +107,7 @@ endif() add_unit_test(NAME testgroup SOURCES TestGroup.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testkeepass2xmlreader SOURCES TestKeePass2XmlReader.cpp +add_unit_test(NAME testkdbx3xmlreader SOURCES TestKeePass2XmlReader.cpp TestKdbx3XmlReader.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testkeys SOURCES TestKeys.cpp diff --git a/tests/TestDeletedObjects.cpp b/tests/TestDeletedObjects.cpp index 5af017885..371d318db 100644 --- a/tests/TestDeletedObjects.cpp +++ b/tests/TestDeletedObjects.cpp @@ -22,7 +22,7 @@ #include "core/Database.h" #include "core/Group.h" #include "crypto/Crypto.h" -#include "format/KeePass2XmlReader.h" +#include "format/Kdbx3XmlReader.h" #include "config-keepassx-tests.h" QTEST_GUILESS_MAIN(TestDeletedObjects) @@ -88,7 +88,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize) void TestDeletedObjects::testDeletedObjectsFromFile() { - KeePass2XmlReader reader; + Kdbx3XmlReader reader; reader.setStrictMode(true); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); Database* db = reader.readDatabase(xmlFile); diff --git a/tests/TestKdbx3XmlReader.cpp b/tests/TestKdbx3XmlReader.cpp new file mode 100644 index 000000000..d4ce58542 --- /dev/null +++ b/tests/TestKdbx3XmlReader.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "TestKeePass2XmlReader.h" + +QTEST_GUILESS_MAIN(TestKdbx3XmlReader) diff --git a/tests/TestKeePass2Writer.cpp b/tests/TestKeePass2Writer.cpp index f6d3f58ad..049c3e66e 100644 --- a/tests/TestKeePass2Writer.cpp +++ b/tests/TestKeePass2Writer.cpp @@ -30,7 +30,6 @@ #include "format/KeePass2Reader.h" #include "format/KeePass2Repair.h" #include "format/KeePass2Writer.h" -#include "format/KeePass2XmlWriter.h" #include "keys/PasswordKey.h" QTEST_GUILESS_MAIN(TestKeePass2Writer) diff --git a/tests/TestKeePass2XmlReader.cpp b/tests/TestKeePass2XmlReader.cpp index 495b39acf..c1e3b4b63 100644 --- a/tests/TestKeePass2XmlReader.cpp +++ b/tests/TestKeePass2XmlReader.cpp @@ -25,12 +25,10 @@ #include "core/Group.h" #include "core/Metadata.h" #include "crypto/Crypto.h" -#include "format/KeePass2XmlReader.h" -#include "format/KeePass2XmlWriter.h" +#include "format/Kdbx3XmlReader.h" +#include "format/Kdbx3XmlWriter.h" #include "config-keepassx-tests.h" -QTEST_GUILESS_MAIN(TestKeePass2XmlReader) - namespace QTest { template<> char* toString(const Uuid& uuid) @@ -79,11 +77,11 @@ QByteArray TestKeePass2XmlReader::strToBytes(const QString& str) return result; } -void TestKeePass2XmlReader::initTestCase() +void TestKdbx3XmlReader::initTestCase() { QVERIFY(Crypto::init()); - KeePass2XmlReader reader; + Kdbx3XmlReader reader; reader.setStrictMode(true); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); m_db = reader.readDatabase(xmlFile); @@ -91,6 +89,32 @@ void TestKeePass2XmlReader::initTestCase() QVERIFY(!reader.hasError()); } +void TestKdbx3XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) +{ + Kdbx3XmlReader reader; + reader.setStrictMode(strictMode); + db = reader.readDatabase(path); + hasError = reader.hasError(); + errorString = reader.errorString(); +} + +void TestKdbx3XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) +{ + Kdbx3XmlReader reader; + reader.setStrictMode(strictMode); + db = reader.readDatabase(buf); + hasError = reader.hasError(); + errorString = reader.errorString(); +} + +void TestKdbx3XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) +{ + Kdbx3XmlWriter writer; + writer.writeDatabase(buf, db); + hasError = writer.hasError(); + errorString = writer.errorString(); +} + void TestKeePass2XmlReader::testMetadata() { QCOMPARE(m_db->metadata()->generator(), QString("KeePass")); @@ -374,15 +398,20 @@ void TestKeePass2XmlReader::testBroken() QFETCH(bool, strictMode); QFETCH(bool, expectError); - KeePass2XmlReader reader; - reader.setStrictMode(strictMode); + QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName); QVERIFY(QFile::exists(xmlFile)); - QScopedPointer db(reader.readDatabase(xmlFile)); - if (reader.hasError()) { - qWarning("Reader error: %s", qPrintable(reader.errorString())); + bool hasError; + QString errorString; + Database* db; + readDatabase(xmlFile, strictMode, db, hasError, errorString); + if (hasError) { + qWarning("Reader error: %s", qPrintable(errorString)); + } + QCOMPARE(hasError, expectError); + if (db) { + delete db; } - QCOMPARE(reader.hasError(), expectError); } void TestKeePass2XmlReader::testBroken_data() @@ -412,15 +441,20 @@ void TestKeePass2XmlReader::testBroken_data() void TestKeePass2XmlReader::testEmptyUuids() { - KeePass2XmlReader reader; - reader.setStrictMode(true); + QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "EmptyUuids"); QVERIFY(QFile::exists(xmlFile)); - QScopedPointer db(reader.readDatabase(xmlFile)); - if (reader.hasError()) { - qWarning("Reader error: %s", qPrintable(reader.errorString())); + Database* dbp; + bool hasError; + QString errorString; + readDatabase(xmlFile, true, dbp, hasError, errorString); + if (hasError) { + qWarning("Reader error: %s", qPrintable(errorString)); + } + QVERIFY(!hasError); + if (dbp) { + delete dbp; } - QVERIFY(!reader.hasError()); } void TestKeePass2XmlReader::testInvalidXmlChars() @@ -459,19 +493,19 @@ void TestKeePass2XmlReader::testInvalidXmlChars() QBuffer buffer; buffer.open(QIODevice::ReadWrite); - KeePass2XmlWriter writer; - writer.writeDatabase(&buffer, dbWrite.data()); - QVERIFY(!writer.hasError()); + bool hasError; + QString errorString; + writeDatabase(&buffer, dbWrite.data(), hasError, errorString); + QVERIFY(!hasError); buffer.seek(0); - KeePass2XmlReader reader; - reader.setStrictMode(true); - QScopedPointer dbRead(reader.readDatabase(&buffer)); - if (reader.hasError()) { - qWarning("Database read error: %s", qPrintable(reader.errorString())); + Database* dbRead; + readDatabase(&buffer, true, dbRead, hasError, errorString); + if (hasError) { + qWarning("Database read error: %s", qPrintable(errorString)); } - QVERIFY(!reader.hasError()); - QVERIFY(!dbRead.isNull()); + QVERIFY(!hasError); + QVERIFY(dbRead); QCOMPARE(dbRead->rootGroup()->entries().size(), 1); Entry* entryRead = dbRead->rootGroup()->entries().at(0); EntryAttributes* attrRead = entryRead->attributes(); @@ -486,22 +520,28 @@ void TestKeePass2XmlReader::testInvalidXmlChars() QCOMPARE(strToBytes(attrRead->value("LowLowSurrogate")), QByteArray()); QCOMPARE(strToBytes(attrRead->value("SurrogateValid1")), strToBytes(strSurrogateValid1)); QCOMPARE(strToBytes(attrRead->value("SurrogateValid2")), strToBytes(strSurrogateValid2)); + + if (dbRead) { + delete dbRead; + } } void TestKeePass2XmlReader::testRepairUuidHistoryItem() { - KeePass2XmlReader reader; QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid"); QVERIFY(QFile::exists(xmlFile)); - QScopedPointer db(reader.readDatabase(xmlFile)); - if (reader.hasError()) { - qWarning("Database read error: %s", qPrintable(reader.errorString())); + Database* db; + bool hasError; + QString errorString; + readDatabase(xmlFile, true, db, hasError, errorString); + if (hasError) { + qWarning("Database read error: %s", qPrintable(errorString)); } - QVERIFY(!reader.hasError()); + QVERIFY(!hasError); - QList entries = db.data()->rootGroup()->entries(); + QList entries = db->rootGroup()->entries(); QCOMPARE(entries.size(), 1); Entry* entry = entries.at(0); @@ -512,6 +552,10 @@ void TestKeePass2XmlReader::testRepairUuidHistoryItem() QVERIFY(!entry->uuid().isNull()); QVERIFY(!historyItem->uuid().isNull()); QCOMPARE(historyItem->uuid(), entry->uuid()); + + if (db) { + delete db; + } } void TestKeePass2XmlReader::cleanupTestCase() diff --git a/tests/TestKeePass2XmlReader.h b/tests/TestKeePass2XmlReader.h index 628964b46..2ce122235 100644 --- a/tests/TestKeePass2XmlReader.h +++ b/tests/TestKeePass2XmlReader.h @@ -20,6 +20,7 @@ #include #include +#include class Database; @@ -27,8 +28,8 @@ class TestKeePass2XmlReader : public QObject { Q_OBJECT -private slots: - void initTestCase(); +protected slots: + virtual void initTestCase() = 0; void testMetadata(); void testCustomIcons(); void testCustomData(); @@ -46,11 +47,27 @@ private slots: void testRepairUuidHistoryItem(); void cleanupTestCase(); -private: +protected: + virtual void readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) = 0; + virtual void readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) = 0; + virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) = 0; static QDateTime genDT(int year, int month, int day, int hour, int min, int second); static QByteArray strToBytes(const QString& str); Database* m_db; }; +class TestKdbx3XmlReader : public TestKeePass2XmlReader +{ + Q_OBJECT + +private slots: + virtual void initTestCase() override; + +protected: + virtual void readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) override; + virtual void readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) override; + virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; +}; + #endif // KEEPASSX_TESTKEEPASS2XMLREADER_H From d1a19a1009fe237406d35431c75b01362d5a7aee Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 16 Dec 2017 17:32:39 +0100 Subject: [PATCH 11/39] Refactor Endian namespace --- src/CMakeLists.txt | 1 - src/core/Endian.cpp | 244 ------------------------------ src/core/Endian.h | 70 ++++++--- src/format/Kdbx3Reader.cpp | 14 +- src/format/Kdbx3Writer.cpp | 14 +- src/format/KeePass1Reader.cpp | 50 +++--- src/format/KeePass2Reader.cpp | 6 +- src/streams/HashedBlockStream.cpp | 8 +- src/streams/HmacBlockStream.cpp | 12 +- 9 files changed, 99 insertions(+), 320 deletions(-) delete mode 100644 src/core/Endian.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 35ea97817..5172ea0ba 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,7 +42,6 @@ set(keepassx_SOURCES core/CsvParser.cpp core/Database.cpp core/DatabaseIcons.cpp - core/Endian.cpp core/Entry.cpp core/EntryAttachments.cpp core/EntryAttributes.cpp diff --git a/src/core/Endian.cpp b/src/core/Endian.cpp deleted file mode 100644 index 3944bba2d..000000000 --- a/src/core/Endian.cpp +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (C) 2010 Felix Geyer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "Endian.h" - -#include -#include - -namespace Endian { - -qint16 bytesToInt16(const QByteArray& ba, QSysInfo::Endian byteOrder) -{ - Q_ASSERT(ba.size() == 2); - - if (byteOrder == QSysInfo::LittleEndian) { - return qFromLittleEndian(reinterpret_cast(ba.constData())); - } - else { - return qFromBigEndian(reinterpret_cast(ba.constData())); - } -} - -qint32 bytesToInt32(const QByteArray& ba, QSysInfo::Endian byteOrder) -{ - Q_ASSERT(ba.size() == 4); - - if (byteOrder == QSysInfo::LittleEndian) { - return qFromLittleEndian(reinterpret_cast(ba.constData())); - } - else { - return qFromBigEndian(reinterpret_cast(ba.constData())); - } -} - -qint64 bytesToInt64(const QByteArray& ba, QSysInfo::Endian byteOrder) -{ - Q_ASSERT(ba.size() == 8); - - if (byteOrder == QSysInfo::LittleEndian) { - return qFromLittleEndian(reinterpret_cast(ba.constData())); - } - else { - return qFromBigEndian(reinterpret_cast(ba.constData())); - } -} - -quint16 bytesToUInt16(const QByteArray& ba, QSysInfo::Endian byteOrder) -{ - return static_cast(bytesToInt16(ba, byteOrder)); -} - -quint32 bytesToUInt32(const QByteArray& ba, QSysInfo::Endian byteOrder) -{ - return static_cast(bytesToInt32(ba, byteOrder)); -} - -quint64 bytesToUInt64(const QByteArray& ba, QSysInfo::Endian byteOrder) -{ - return static_cast(bytesToInt64(ba, byteOrder)); -} - -qint16 readInt16(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok) -{ - QByteArray ba = device->read(2); - - if (ba.size() != 2) { - *ok = false; - return 0; - } - else { - *ok = true; - return bytesToInt16(ba, byteOrder); - } -} - -qint32 readInt32(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok) -{ - QByteArray ba = device->read(4); - - if (ba.size() != 4) { - *ok = false; - return 0; - } - else { - *ok = true; - return bytesToInt32(ba, byteOrder); - } -} - -qint64 readInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok) -{ - QByteArray ba = device->read(8); - - if (ba.size() != 8) { - *ok = false; - return 0; - } - else { - *ok = true; - return bytesToInt64(ba, byteOrder); - } -} - -quint16 readUInt16(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok) -{ - return static_cast(readInt16(device, byteOrder, ok)); -} - -quint32 readUInt32(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok) -{ - return static_cast(readInt32(device, byteOrder, ok)); -} - -quint64 readUInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok) -{ - return static_cast(readInt64(device, byteOrder, ok)); -} - -QByteArray int16ToBytes(qint16 num, QSysInfo::Endian byteOrder) -{ - QByteArray ba; - ba.resize(2); - - if (byteOrder == QSysInfo::LittleEndian) { - qToLittleEndian(num, reinterpret_cast(ba.data())); - } - else { - qToBigEndian(num, reinterpret_cast(ba.data())); - } - - return ba; -} - -QByteArray int32ToBytes(qint32 num, QSysInfo::Endian byteOrder) -{ - QByteArray ba; - ba.resize(4); - - if (byteOrder == QSysInfo::LittleEndian) { - qToLittleEndian(num, reinterpret_cast(ba.data())); - } - else { - qToBigEndian(num, reinterpret_cast(ba.data())); - } - - return ba; -} - -QByteArray int64ToBytes(qint64 num, QSysInfo::Endian byteOrder) -{ - QByteArray ba; - ba.resize(8); - - if (byteOrder == QSysInfo::LittleEndian) { - qToLittleEndian(num, reinterpret_cast(ba.data())); - } - else { - qToBigEndian(num, reinterpret_cast(ba.data())); - } - - return ba; -} - -QByteArray uint16ToBytes(quint16 num, QSysInfo::Endian byteOrder) -{ - QByteArray ba; - ba.resize(2); - - if (byteOrder == QSysInfo::LittleEndian) { - qToLittleEndian(num, reinterpret_cast(ba.data())); - } - else { - qToBigEndian(num, reinterpret_cast(ba.data())); - } - - return ba; -} - -QByteArray uint32ToBytes(quint32 num, QSysInfo::Endian byteOrder) -{ - QByteArray ba; - ba.resize(4); - - if (byteOrder == QSysInfo::LittleEndian) { - qToLittleEndian(num, reinterpret_cast(ba.data())); - } - else { - qToBigEndian(num, reinterpret_cast(ba.data())); - } - - return ba; -} - -QByteArray uint64ToBytes(quint64 num, QSysInfo::Endian byteOrder) -{ - QByteArray ba; - ba.resize(8); - - if (byteOrder == QSysInfo::LittleEndian) { - qToLittleEndian(num, reinterpret_cast(ba.data())); - } - else { - qToBigEndian(num, reinterpret_cast(ba.data())); - } - - return ba; -} - -bool writeInt16(qint16 num, QIODevice* device, QSysInfo::Endian byteOrder) -{ - QByteArray ba = int16ToBytes(num, byteOrder); - int bytesWritten = device->write(ba); - return (bytesWritten == ba.size()); -} - -bool writeInt32(qint32 num, QIODevice* device, QSysInfo::Endian byteOrder) -{ - QByteArray ba = int32ToBytes(num, byteOrder); - int bytesWritten = device->write(ba); - return (bytesWritten == ba.size()); -} - -bool writeInt64(qint64 num, QIODevice* device, QSysInfo::Endian byteOrder) -{ - QByteArray ba = int64ToBytes(num, byteOrder); - int bytesWritten = device->write(ba); - return (bytesWritten == ba.size()); -} - -} // namespace Endian diff --git a/src/core/Endian.h b/src/core/Endian.h index 0b969b3ed..cd01eb483 100644 --- a/src/core/Endian.h +++ b/src/core/Endian.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2017 KeePassXC Team * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -20,35 +21,58 @@ #include #include +#include +#include -class QIODevice; +namespace Endian +{ -namespace Endian { +template +SizedQInt bytesToSizedInt(const QByteArray& ba, QSysInfo::Endian byteOrder) +{ + Q_ASSERT(ba.size() == sizeof(SizedQInt)); - qint16 bytesToInt16(const QByteArray& ba, QSysInfo::Endian byteOrder); - quint16 bytesToUInt16(const QByteArray& ba, QSysInfo::Endian byteOrder); - qint32 bytesToInt32(const QByteArray& ba, QSysInfo::Endian byteOrder); - quint32 bytesToUInt32(const QByteArray& ba, QSysInfo::Endian byteOrder); - qint64 bytesToInt64(const QByteArray& ba, QSysInfo::Endian byteOrder); - quint64 bytesToUInt64(const QByteArray& ba, QSysInfo::Endian byteOrder); + if (byteOrder == QSysInfo::LittleEndian) { + return qFromLittleEndian(reinterpret_cast(ba.constData())); + } + return qFromBigEndian(reinterpret_cast(ba.constData())); +} - qint16 readInt16(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok); - quint16 readUInt16(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok); - qint32 readInt32(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok); - quint32 readUInt32(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok); - qint64 readInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok); - quint64 readUInt64(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok); +template +SizedQInt readSizedInt(QIODevice* device, QSysInfo::Endian byteOrder, bool* ok) +{ + QByteArray ba = device->read(sizeof(SizedQInt)); - QByteArray int16ToBytes(qint16 num, QSysInfo::Endian byteOrder); - QByteArray int32ToBytes(qint32 num, QSysInfo::Endian byteOrder); - QByteArray int64ToBytes(qint64 num, QSysInfo::Endian byteOrder); - QByteArray uint16ToBytes(quint16 num, QSysInfo::Endian byteOrder); - QByteArray uint32ToBytes(quint32 num, QSysInfo::Endian byteOrder); - QByteArray uint64ToBytes(quint64 num, QSysInfo::Endian byteOrder); + if (ba.size() != sizeof(SizedQInt)) { + *ok = false; + return 0; + } + *ok = true; + return bytesToSizedInt(ba, byteOrder); +} - bool writeInt16(qint16 num, QIODevice* device, QSysInfo::Endian byteOrder); - bool writeInt32(qint32 num, QIODevice* device, QSysInfo::Endian byteOrder); - bool writeInt64(qint64 num, QIODevice* device, QSysInfo::Endian byteOrder); +template +QByteArray sizedIntToBytes(SizedQInt num, QSysInfo::Endian byteOrder) +{ + QByteArray ba; + ba.resize(sizeof(SizedQInt)); + + if (byteOrder == QSysInfo::LittleEndian) { + qToLittleEndian(num, reinterpret_cast(ba.data())); + } else { + qToBigEndian(num, reinterpret_cast(ba.data())); + } + + return ba; +} + +template +bool writeSizedInt(SizedQInt num, QIODevice* device, QSysInfo::Endian byteOrder) +{ + QByteArray ba = sizedIntToBytes(num, byteOrder); + qint64 bytesWritten = device->write(ba); + return (bytesWritten == ba.size()); +} } // namespace Endian diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index fc3dcbd64..fdf3c5997 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -62,13 +62,13 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool ok; - quint32 signature1 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); + quint32 signature1 = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok); if (!ok || signature1 != KeePass2::SIGNATURE_1) { raiseError(tr("Not a KeePass database.")); return nullptr; } - quint32 signature2 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); + quint32 signature2 = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok); if (ok && signature2 == KeePass1::SIGNATURE_2) { raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" "You can import it by clicking on Database > 'Import KeePass 1 database...'.\n" @@ -81,7 +81,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, return nullptr; } - quint32 version = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok) + quint32 version = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok) & KeePass2::FILE_VERSION_CRITICAL_MASK; quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) { @@ -214,7 +214,7 @@ bool Kdbx3Reader::readHeaderField() quint8 fieldID = fieldIDArray.at(0); bool ok; - quint16 fieldLen = Endian::readUInt16(m_headerStream, KeePass2::BYTEORDER, &ok); + quint16 fieldLen = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok); if (!ok) { raiseError("Invalid header field length"); return false; @@ -299,7 +299,7 @@ void Kdbx3Reader::setCompressionFlags(const QByteArray& data) raiseError("Invalid compression flags length"); } else { - quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); + quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); if (id > Database::CompressionAlgorithmMax) { raiseError("Unsupported compression algorithm"); @@ -344,7 +344,7 @@ void Kdbx3Reader::setTransformRounds(const QByteArray& data) raiseError("Invalid transform rounds size"); } else { - quint64 rounds = Endian::bytesToUInt64(data, KeePass2::BYTEORDER); + quint64 rounds = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); AesKdf* aesKdf; if (m_db->kdf()->type() == Kdf::Type::AES) { @@ -383,7 +383,7 @@ void Kdbx3Reader::setInnerRandomStreamID(const QByteArray& data) if (data.size() != 4) { raiseError("Invalid random stream id size"); } else { - quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); + quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id); if (irsAlgo == KeePass2::InvalidProtectedStreamAlgo || irsAlgo == KeePass2::ArcFourVariant) { raiseError("Invalid inner random stream cipher"); diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp index d07ff9663..de04ad686 100644 --- a/src/format/Kdbx3Writer.cpp +++ b/src/format/Kdbx3Writer.cpp @@ -72,25 +72,25 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) header.open(QIODevice::WriteOnly); m_device = &header; - CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray())); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CompressionFlags, - Endian::int32ToBytes(db->compressionAlgo(), + Endian::sizedIntToBytes(db->compressionAlgo(), KeePass2::BYTEORDER))); AesKdf* kdf = static_cast(db->kdf()); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::MasterSeed, masterSeed)); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformSeed, kdf->seed())); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformRounds, - Endian::int64ToBytes(kdf->rounds(), + Endian::sizedIntToBytes(kdf->rounds(), KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EncryptionIV, encryptionIV)); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey)); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::StreamStartBytes, startBytes)); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::InnerRandomStreamID, - Endian::int32ToBytes(KeePass2::Salsa20, + Endian::sizedIntToBytes(KeePass2::Salsa20, KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EndOfHeader, endOfHeader)); @@ -179,7 +179,7 @@ bool Kdbx3Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteA QByteArray fieldIdArr; fieldIdArr[0] = fieldId; CHECK_RETURN_FALSE(writeData(fieldIdArr)); - CHECK_RETURN_FALSE(writeData(Endian::int16ToBytes(static_cast(data.size()), + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size()), KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeData(data)); diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp index 512b0b58b..ccb3d1ad1 100644 --- a/src/format/KeePass1Reader.cpp +++ b/src/format/KeePass1Reader.cpp @@ -94,25 +94,25 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor bool ok; - quint32 signature1 = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok); + quint32 signature1 = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok || signature1 != KeePass1::SIGNATURE_1) { raiseError(tr("Not a KeePass database.")); return nullptr; } - quint32 signature2 = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok); + quint32 signature2 = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok || signature2 != KeePass1::SIGNATURE_2) { raiseError(tr("Not a KeePass database.")); return nullptr; } - m_encryptionFlags = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok); + m_encryptionFlags = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok || !(m_encryptionFlags & KeePass1::Rijndael || m_encryptionFlags & KeePass1::Twofish)) { raiseError(tr("Unsupported encryption algorithm.")); return nullptr; } - quint32 version = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok); + quint32 version = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok || (version & KeePass1::FILE_VERSION_CRITICAL_MASK) != (KeePass1::FILE_VERSION & KeePass1::FILE_VERSION_CRITICAL_MASK)) { raiseError(tr("Unsupported KeePass database version.")); @@ -131,13 +131,13 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor return nullptr; } - quint32 numGroups = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok); + quint32 numGroups = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok) { raiseError("Invalid number of groups"); return nullptr; } - quint32 numEntries = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok); + quint32 numEntries = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok) { raiseError("Invalid number of entries"); return nullptr; @@ -155,7 +155,7 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor return nullptr; } - m_transformRounds = Endian::readUInt32(m_device, KeePass1::BYTEORDER, &ok); + m_transformRounds = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok) { raiseError("Invalid number of transform rounds"); return nullptr; @@ -444,13 +444,13 @@ Group* KeePass1Reader::readGroup(QIODevice* cipherStream) bool reachedEnd = false; do { - quint16 fieldType = Endian::readUInt16(cipherStream, KeePass1::BYTEORDER, &ok); + quint16 fieldType = Endian::readSizedInt(cipherStream, KeePass1::BYTEORDER, &ok); if (!ok) { raiseError("Invalid group field type number"); return nullptr; } - int fieldSize = static_cast(Endian::readUInt32(cipherStream, KeePass1::BYTEORDER, &ok)); + int fieldSize = static_cast(Endian::readSizedInt(cipherStream, KeePass1::BYTEORDER, &ok)); if (!ok) { raiseError("Invalid group field size"); return nullptr; @@ -471,7 +471,7 @@ Group* KeePass1Reader::readGroup(QIODevice* cipherStream) raiseError("Incorrect group id field size"); return nullptr; } - groupId = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER); + groupId = Endian::bytesToSizedInt(fieldData, KeePass1::BYTEORDER); groupIdSet = true; break; case 0x0002: @@ -530,7 +530,7 @@ Group* KeePass1Reader::readGroup(QIODevice* cipherStream) raiseError("Incorrect group icon field size"); return nullptr; } - quint32 iconNumber = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER); + quint32 iconNumber = Endian::bytesToSizedInt(fieldData, KeePass1::BYTEORDER); group->setIcon(iconNumber); break; } @@ -540,7 +540,7 @@ Group* KeePass1Reader::readGroup(QIODevice* cipherStream) raiseError("Incorrect group level field size"); return nullptr; } - groupLevel = Endian::bytesToUInt16(fieldData, KeePass1::BYTEORDER); + groupLevel = Endian::bytesToSizedInt(fieldData, KeePass1::BYTEORDER); groupLevelSet = true; break; } @@ -582,13 +582,13 @@ Entry* KeePass1Reader::readEntry(QIODevice* cipherStream) bool reachedEnd = false; do { - quint16 fieldType = Endian::readUInt16(cipherStream, KeePass1::BYTEORDER, &ok); + quint16 fieldType = Endian::readSizedInt(cipherStream, KeePass1::BYTEORDER, &ok); if (!ok) { raiseError("Missing entry field type number"); return nullptr; } - int fieldSize = static_cast(Endian::readUInt32(cipherStream, KeePass1::BYTEORDER, &ok)); + int fieldSize = static_cast(Endian::readSizedInt(cipherStream, KeePass1::BYTEORDER, &ok)); if (!ok) { raiseError("Invalid entry field size"); return nullptr; @@ -617,7 +617,7 @@ Entry* KeePass1Reader::readEntry(QIODevice* cipherStream) raiseError("Invalid entry group id field size"); return nullptr; } - quint32 groupId = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER); + quint32 groupId = Endian::bytesToSizedInt(fieldData, KeePass1::BYTEORDER); m_entryGroupIds.insert(entry.data(), groupId); break; } @@ -627,7 +627,7 @@ Entry* KeePass1Reader::readEntry(QIODevice* cipherStream) raiseError("Invalid entry icon field size"); return nullptr; } - quint32 iconNumber = Endian::bytesToUInt32(fieldData, KeePass1::BYTEORDER); + quint32 iconNumber = Endian::bytesToSizedInt(fieldData, KeePass1::BYTEORDER); entry->setIcon(iconNumber); break; } @@ -837,7 +837,7 @@ bool KeePass1Reader::parseGroupTreeState(const QByteArray& data) } int pos = 0; - quint32 num = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER); + quint32 num = Endian::bytesToSizedInt(data.mid(pos, 4), KeePass1::BYTEORDER); pos += 4; if (static_cast(data.size() - 4) != (num * 5)) { @@ -845,7 +845,7 @@ bool KeePass1Reader::parseGroupTreeState(const QByteArray& data) } for (quint32 i = 0; i < num; i++) { - quint32 groupId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER); + quint32 groupId = Endian::bytesToSizedInt(data.mid(pos, 4), KeePass1::BYTEORDER); pos += 4; bool expanded = data.at(pos); @@ -867,13 +867,13 @@ bool KeePass1Reader::parseCustomIcons4(const QByteArray& data) int pos = 0; - quint32 numIcons = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER); + quint32 numIcons = Endian::bytesToSizedInt(data.mid(pos, 4), KeePass1::BYTEORDER); pos += 4; - quint32 numEntries = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER); + quint32 numEntries = Endian::bytesToSizedInt(data.mid(pos, 4), KeePass1::BYTEORDER); pos += 4; - quint32 numGroups = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER); + quint32 numGroups = Endian::bytesToSizedInt(data.mid(pos, 4), KeePass1::BYTEORDER); pos += 4; QList iconUuids; @@ -882,7 +882,7 @@ bool KeePass1Reader::parseCustomIcons4(const QByteArray& data) if (data.size() < (pos + 4)) { return false; } - quint32 iconSize = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER); + quint32 iconSize = Endian::bytesToSizedInt(data.mid(pos, 4), KeePass1::BYTEORDER); pos += 4; if (static_cast(data.size()) < (pos + iconSize)) { @@ -908,7 +908,7 @@ bool KeePass1Reader::parseCustomIcons4(const QByteArray& data) QByteArray entryUuid = data.mid(pos, 16); pos += 16; - quint32 iconId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER); + quint32 iconId = Endian::bytesToSizedInt(data.mid(pos, 4), KeePass1::BYTEORDER); pos += 4; if (m_entryUuids.contains(entryUuid) && (iconId < static_cast(iconUuids.size()))) { @@ -921,10 +921,10 @@ bool KeePass1Reader::parseCustomIcons4(const QByteArray& data) } for (quint32 i = 0; i < numGroups; i++) { - quint32 groupId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER); + quint32 groupId = Endian::bytesToSizedInt(data.mid(pos, 4), KeePass1::BYTEORDER); pos += 4; - quint32 iconId = Endian::bytesToUInt32(data.mid(pos, 4), KeePass1::BYTEORDER); + quint32 iconId = Endian::bytesToSizedInt(data.mid(pos, 4), KeePass1::BYTEORDER); pos += 4; if (m_groupIds.contains(groupId) && (iconId < static_cast(iconUuids.size()))) { diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index a09f0adce..5d62794d4 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -99,13 +99,13 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke bool ok; - quint32 signature1 = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok); + quint32 signature1 = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); if (!ok || signature1 != KeePass2::SIGNATURE_1) { raiseError(tr("Not a KeePass database.")); return nullptr; } - quint32 signature2 = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok); + quint32 signature2 = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); if (ok && signature2 == KeePass1::SIGNATURE_2) { raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" "You can import it by clicking on Database > 'Import KeePass 1 database...'.\n" @@ -118,7 +118,7 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke return nullptr; } - m_version = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok) + m_version = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok) & KeePass2::FILE_VERSION_CRITICAL_MASK; quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; if (!ok || (m_version < KeePass2::FILE_VERSION_MIN) || (m_version > maxVersion)) { diff --git a/src/streams/HashedBlockStream.cpp b/src/streams/HashedBlockStream.cpp index 0b11a9ca0..dd323a6d5 100644 --- a/src/streams/HashedBlockStream.cpp +++ b/src/streams/HashedBlockStream.cpp @@ -130,7 +130,7 @@ bool HashedBlockStream::readHashedBlock() { bool ok; - quint32 index = Endian::readUInt32(m_baseDevice, ByteOrder, &ok); + quint32 index = Endian::readSizedInt(m_baseDevice, ByteOrder, &ok); if (!ok || index != m_blockIndex) { m_error = true; setErrorString("Invalid block index."); @@ -144,7 +144,7 @@ bool HashedBlockStream::readHashedBlock() return false; } - m_blockSize = Endian::readInt32(m_baseDevice, ByteOrder, &ok); + m_blockSize = Endian::readSizedInt(m_baseDevice, ByteOrder, &ok); if (!ok || m_blockSize < 0) { m_error = true; setErrorString("Invalid block size."); @@ -217,7 +217,7 @@ qint64 HashedBlockStream::writeData(const char* data, qint64 maxSize) bool HashedBlockStream::writeHashedBlock() { - if (!Endian::writeInt32(m_blockIndex, m_baseDevice, ByteOrder)) { + if (!Endian::writeSizedInt(m_blockIndex, m_baseDevice, ByteOrder)) { m_error = true; setErrorString(m_baseDevice->errorString()); return false; @@ -238,7 +238,7 @@ bool HashedBlockStream::writeHashedBlock() return false; } - if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) { + if (!Endian::writeSizedInt(m_buffer.size(), m_baseDevice, ByteOrder)) { m_error = true; setErrorString(m_baseDevice->errorString()); return false; diff --git a/src/streams/HmacBlockStream.cpp b/src/streams/HmacBlockStream.cpp index c01c973b1..40c5fc547 100644 --- a/src/streams/HmacBlockStream.cpp +++ b/src/streams/HmacBlockStream.cpp @@ -144,7 +144,7 @@ bool HmacBlockStream::readHashedBlock() setErrorString("Invalid block size size."); return false; } - qint32 blockSize = Endian::bytesToInt32(blockSizeBytes, ByteOrder); + qint32 blockSize = Endian::bytesToSizedInt(blockSizeBytes, ByteOrder); if (blockSize < 0) { m_error = true; setErrorString("Invalid block size."); @@ -160,7 +160,7 @@ bool HmacBlockStream::readHashedBlock() CryptoHash hasher(CryptoHash::Sha256, true); hasher.setKey(getCurrentHmacKey()); - hasher.addData(Endian::uint64ToBytes(m_blockIndex, ByteOrder)); + hasher.addData(Endian::sizedIntToBytes(m_blockIndex, ByteOrder)); hasher.addData(blockSizeBytes); hasher.addData(m_buffer); @@ -219,8 +219,8 @@ bool HmacBlockStream::writeHashedBlock() { CryptoHash hasher(CryptoHash::Sha256, true); hasher.setKey(getCurrentHmacKey()); - hasher.addData(Endian::uint64ToBytes(m_blockIndex, ByteOrder)); - hasher.addData(Endian::int32ToBytes(m_buffer.size(), ByteOrder)); + hasher.addData(Endian::sizedIntToBytes(m_blockIndex, ByteOrder)); + hasher.addData(Endian::sizedIntToBytes(m_buffer.size(), ByteOrder)); hasher.addData(m_buffer); QByteArray hash = hasher.result(); @@ -230,7 +230,7 @@ bool HmacBlockStream::writeHashedBlock() return false; } - if (!Endian::writeInt32(m_buffer.size(), m_baseDevice, ByteOrder)) { + if (!Endian::writeSizedInt(m_buffer.size(), m_baseDevice, ByteOrder)) { m_error = true; setErrorString(m_baseDevice->errorString()); return false; @@ -255,7 +255,7 @@ QByteArray HmacBlockStream::getCurrentHmacKey() const { QByteArray HmacBlockStream::getHmacKey(quint64 blockIndex, QByteArray key) { Q_ASSERT(key.size() == 64); - QByteArray indexBytes = Endian::uint64ToBytes(blockIndex, ByteOrder); + QByteArray indexBytes = Endian::sizedIntToBytes(blockIndex, ByteOrder); CryptoHash hasher(CryptoHash::Sha512); hasher.addData(indexBytes); hasher.addData(key); From 0d6ca0945bf1425decf5516420fe3973743e47ab Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 16 Dec 2017 17:36:33 +0100 Subject: [PATCH 12/39] Reformat code, fix minor style issues, make kdf() getter const --- src/core/Database.cpp | 2 +- src/core/Database.h | 2 +- src/crypto/CryptoHash.cpp | 15 +- src/crypto/CryptoHash.h | 5 +- src/crypto/SymmetricCipher.cpp | 56 ++-- src/crypto/SymmetricCipher.h | 18 +- src/crypto/SymmetricCipherGcrypt.cpp | 11 +- src/crypto/SymmetricCipherGcrypt.h | 2 +- src/crypto/kdf/AesKdf.cpp | 26 +- src/crypto/kdf/AesKdf.h | 11 +- src/crypto/kdf/Kdf.cpp | 18 +- src/crypto/kdf/Kdf.h | 12 +- src/crypto/kdf/Kdf_p.h | 6 +- src/format/Kdbx3Reader.cpp | 43 ++- src/format/Kdbx3Reader.h | 4 +- src/format/Kdbx3Writer.cpp | 20 +- src/format/Kdbx3Writer.h | 4 +- src/format/Kdbx3XmlReader.cpp | 406 +++++++++------------------ src/format/Kdbx3XmlReader.h | 2 +- src/format/Kdbx3XmlWriter.cpp | 62 ++-- src/format/KeePass2.cpp | 24 +- src/format/KeePass2RandomStream.cpp | 20 +- src/streams/HmacBlockStream.cpp | 22 +- src/streams/HmacBlockStream.h | 6 +- 24 files changed, 324 insertions(+), 473 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 45f2b479f..ae22603f3 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -485,7 +485,7 @@ QString Database::saveToFile(QString filePath) } } -Kdf* Database::kdf() { +Kdf* Database::kdf() const { return m_data.kdf; } diff --git a/src/core/Database.h b/src/core/Database.h index dbb48718e..44854b854 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -92,7 +92,7 @@ public: Uuid cipher() const; Database::CompressionAlgorithm compressionAlgo() const; - Kdf* kdf(); + Kdf* kdf() const; QByteArray transformedMasterKey() const; const CompositeKey& key() const; QByteArray challengeResponseKey() const; diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp index 907140606..8d5b56e76 100644 --- a/src/crypto/CryptoHash.cpp +++ b/src/crypto/CryptoHash.cpp @@ -28,10 +28,7 @@ public: int hashLen; }; -CryptoHash::CryptoHash(CryptoHash::Algorithm algo) - : CryptoHash::CryptoHash(algo, false) {} - -CryptoHash::CryptoHash(CryptoHash::Algorithm algo, bool hmac) +CryptoHash::CryptoHash(Algorithm algo, bool hmac) : d_ptr(new CryptoHashPrivate()) { Q_D(CryptoHash); @@ -86,14 +83,14 @@ void CryptoHash::addData(const QByteArray& data) return; } - gcry_md_write(d->ctx, data.constData(), data.size()); + gcry_md_write(d->ctx, data.constData(), static_cast(data.size())); } void CryptoHash::setKey(const QByteArray& data) { Q_D(CryptoHash); - gcry_error_t error = gcry_md_setkey(d->ctx, data.constData(), data.size()); + gcry_error_t error = gcry_md_setkey(d->ctx, data.constData(), static_cast(data.size())); if (error) { qWarning("Gcrypt error (setKey): %s", gcry_strerror(error)); qWarning("Gcrypt error (setKey): %s", gcry_strsource(error)); @@ -112,11 +109,11 @@ QByteArray CryptoHash::result() const { Q_D(const CryptoHash); - const char* result = reinterpret_cast(gcry_md_read(d->ctx, 0)); + const auto* result = reinterpret_cast(gcry_md_read(d->ctx, 0)); return QByteArray(result, d->hashLen); } -QByteArray CryptoHash::hash(const QByteArray& data, CryptoHash::Algorithm algo) +QByteArray CryptoHash::hash(const QByteArray& data, Algorithm algo) { // replace with gcry_md_hash_buffer()? CryptoHash cryptoHash(algo); @@ -124,7 +121,7 @@ QByteArray CryptoHash::hash(const QByteArray& data, CryptoHash::Algorithm algo) return cryptoHash.result(); } -QByteArray CryptoHash::hmac(const QByteArray& data, const QByteArray& key, CryptoHash::Algorithm algo) +QByteArray CryptoHash::hmac(const QByteArray& data, const QByteArray& key, Algorithm algo) { // replace with gcry_md_hash_buffer()? CryptoHash cryptoHash(algo, true); diff --git a/src/crypto/CryptoHash.h b/src/crypto/CryptoHash.h index cf027e0f3..bd312121a 100644 --- a/src/crypto/CryptoHash.h +++ b/src/crypto/CryptoHash.h @@ -31,15 +31,14 @@ public: Sha512 }; - explicit CryptoHash(CryptoHash::Algorithm algo); - explicit CryptoHash(CryptoHash::Algorithm algo, bool hmac); + explicit CryptoHash(Algorithm algo, bool hmac = false); ~CryptoHash(); void addData(const QByteArray& data); void reset(); QByteArray result() const; void setKey(const QByteArray& data); - static QByteArray hash(const QByteArray& data, CryptoHash::Algorithm algo); + static QByteArray hash(const QByteArray& data, Algorithm algo); static QByteArray hmac(const QByteArray& data, const QByteArray& key, Algorithm algo); private: diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp index 9bd605b43..1ec8a2cf6 100644 --- a/src/crypto/SymmetricCipher.cpp +++ b/src/crypto/SymmetricCipher.cpp @@ -20,8 +20,7 @@ #include "config-keepassx.h" #include "crypto/SymmetricCipherGcrypt.h" -SymmetricCipher::SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction) +SymmetricCipher::SymmetricCipher(Algorithm algo, Mode mode, Direction direction) : m_backend(createBackend(algo, mode, direction)) , m_initialized(false) , m_algo(algo) @@ -55,14 +54,13 @@ bool SymmetricCipher::isInitalized() const return m_initialized; } -SymmetricCipherBackend* SymmetricCipher::createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction) +SymmetricCipherBackend* SymmetricCipher::createBackend(Algorithm algo, Mode mode, Direction direction) { switch (algo) { - case SymmetricCipher::Aes256: - case SymmetricCipher::Twofish: - case SymmetricCipher::Salsa20: - case SymmetricCipher::ChaCha20: + case Aes256: + case Twofish: + case Salsa20: + case ChaCha20: return new SymmetricCipherGcrypt(algo, mode, direction); default: @@ -94,25 +92,25 @@ QString SymmetricCipher::errorString() const SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(Uuid cipher) { if (cipher == KeePass2::CIPHER_AES) { - return SymmetricCipher::Aes256; + return Aes256; } else if (cipher == KeePass2::CIPHER_CHACHA20) { - return SymmetricCipher::ChaCha20; + return ChaCha20; } else if (cipher == KeePass2::CIPHER_TWOFISH) { - return SymmetricCipher::Twofish; + return Twofish; } qWarning("SymmetricCipher::cipherToAlgorithm: invalid Uuid %s", cipher.toByteArray().toHex().data()); return InvalidAlgorithm; } -Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo) +Uuid SymmetricCipher::algorithmToCipher(Algorithm algo) { switch (algo) { - case SymmetricCipher::Aes256: + case Aes256: return KeePass2::CIPHER_AES; - case SymmetricCipher::ChaCha20: + case ChaCha20: return KeePass2::CIPHER_CHACHA20; - case SymmetricCipher::Twofish: + case Twofish: return KeePass2::CIPHER_TWOFISH; default: qWarning("SymmetricCipher::algorithmToCipher: invalid algorithm %d", algo); @@ -120,12 +118,14 @@ Uuid SymmetricCipher::algorithmToCipher(SymmetricCipher::Algorithm algo) } } -int SymmetricCipher::algorithmIvSize(SymmetricCipher::Algorithm algo) { +int SymmetricCipher::algorithmIvSize(Algorithm algo) +{ switch (algo) { - case SymmetricCipher::ChaCha20: + case ChaCha20: return 12; - case SymmetricCipher::Aes256: - case SymmetricCipher::Twofish: + case Aes256: + return 16; + case Twofish: return 16; default: qWarning("SymmetricCipher::algorithmIvSize: invalid algorithm %d", algo); @@ -133,19 +133,21 @@ int SymmetricCipher::algorithmIvSize(SymmetricCipher::Algorithm algo) { } } -SymmetricCipher::Mode SymmetricCipher::algorithmMode(SymmetricCipher::Algorithm algo) { +SymmetricCipher::Mode SymmetricCipher::algorithmMode(Algorithm algo) +{ switch (algo) { - case SymmetricCipher::ChaCha20: - return SymmetricCipher::Stream; - case SymmetricCipher::Aes256: - case SymmetricCipher::Twofish: - return SymmetricCipher::Cbc; + case ChaCha20: + return Stream; + case Aes256: + case Twofish: + return Cbc; default: qWarning("SymmetricCipher::algorithmMode: invalid algorithm %d", algo); - return SymmetricCipher::InvalidMode; + return InvalidMode; } } -SymmetricCipher::Algorithm SymmetricCipher::algorithm() const { +SymmetricCipher::Algorithm SymmetricCipher::algorithm() const +{ return m_algo; } diff --git a/src/crypto/SymmetricCipher.h b/src/crypto/SymmetricCipher.h index 981ef320e..eab834956 100644 --- a/src/crypto/SymmetricCipher.h +++ b/src/crypto/SymmetricCipher.h @@ -53,22 +53,25 @@ public: Encrypt }; - SymmetricCipher(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction); + SymmetricCipher(Algorithm algo, Mode mode, Direction direction); ~SymmetricCipher(); + Q_DISABLE_COPY(SymmetricCipher) bool init(const QByteArray& key, const QByteArray& iv); bool isInitalized() const; - inline QByteArray process(const QByteArray& data, bool* ok) { + inline QByteArray process(const QByteArray& data, bool* ok) + { return m_backend->process(data, ok); } - Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data) { + Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data) + { return m_backend->processInPlace(data); } - Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data, quint64 rounds) { + Q_REQUIRED_RESULT inline bool processInPlace(QByteArray& data, quint64 rounds) + { Q_ASSERT(rounds > 0); return m_backend->processInPlace(data, rounds); } @@ -85,14 +88,11 @@ public: static Mode algorithmMode(Algorithm algo); private: - static SymmetricCipherBackend* createBackend(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, - SymmetricCipher::Direction direction); + static SymmetricCipherBackend* createBackend(Algorithm algo, Mode mode, Direction direction); const QScopedPointer m_backend; bool m_initialized; Algorithm m_algo; - - Q_DISABLE_COPY(SymmetricCipher) }; #endif // KEEPASSX_SYMMETRICCIPHER_H diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp index bbb80bf60..b1abd5250 100644 --- a/src/crypto/SymmetricCipherGcrypt.cpp +++ b/src/crypto/SymmetricCipherGcrypt.cpp @@ -145,8 +145,7 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok) if (m_direction == SymmetricCipher::Decrypt) { error = gcry_cipher_decrypt(m_ctx, result.data(), data.size(), data.constData(), data.size()); - } - else { + } else { error = gcry_cipher_encrypt(m_ctx, result.data(), data.size(), data.constData(), data.size()); } @@ -154,7 +153,7 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok) setErrorString(error); *ok = false; } else { - *ok = true; + *ok = true; } return result; @@ -168,8 +167,7 @@ bool SymmetricCipherGcrypt::processInPlace(QByteArray& data) if (m_direction == SymmetricCipher::Decrypt) { error = gcry_cipher_decrypt(m_ctx, data.data(), data.size(), nullptr, 0); - } - else { + } else { error = gcry_cipher_encrypt(m_ctx, data.data(), data.size(), nullptr, 0); } @@ -199,8 +197,7 @@ bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds) return false; } } - } - else { + } else { for (quint64 i = 0; i != rounds; ++i) { error = gcry_cipher_encrypt(m_ctx, rawData, size, nullptr, 0); diff --git a/src/crypto/SymmetricCipherGcrypt.h b/src/crypto/SymmetricCipherGcrypt.h index 108bc14e4..2436c3be1 100644 --- a/src/crypto/SymmetricCipherGcrypt.h +++ b/src/crypto/SymmetricCipherGcrypt.h @@ -23,7 +23,7 @@ #include "crypto/SymmetricCipher.h" #include "crypto/SymmetricCipherBackend.h" -class SymmetricCipherGcrypt : public SymmetricCipherBackend +class SymmetricCipherGcrypt: public SymmetricCipherBackend { public: SymmetricCipherGcrypt(SymmetricCipher::Algorithm algo, SymmetricCipher::Mode mode, diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp index 4380a782e..a4dd75650 100644 --- a/src/crypto/kdf/AesKdf.cpp +++ b/src/crypto/kdf/AesKdf.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -28,7 +28,7 @@ const QList AesKdf::FIELDS = AesKdf::initFields(); QList AesKdf::initFields() { return QList { - Kdf::Field(static_cast(Fields::ROUNDS), "Transform rounds", 1, UINT64_MAX, true), + Kdf::Field(static_cast(Fields::ROUNDS), "Transform rounds", 1, UINT64_MAX, true), }; } @@ -67,7 +67,8 @@ bool AesKdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, quin *result = key; if (!cipher.processInPlace(*result, rounds)) { - qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::processInPlace: %s", cipher.errorString().toUtf8().data()); + qWarning("AesKdf::transformKeyRaw: error in SymmetricCipher::processInPlace: %s", + cipher.errorString().toUtf8().data()); return false; } @@ -129,20 +130,20 @@ const QList AesKdf::fields() const quint64 AesKdf::field(quint32 id) const { switch (static_cast(id)) { - case Fields::ROUNDS: - return m_rounds; - default: - return 0; + case Fields::ROUNDS: + return m_rounds; + default: + return 0; } } bool AesKdf::setField(quint32 id, quint64 val) { switch (static_cast(id)) { - case Fields::ROUNDS: - return setRounds(val); - default: - return false; + case Fields::ROUNDS: + return setRounds(val); + default: + return false; } } @@ -165,7 +166,8 @@ int AesKdf::benchmarkImpl(int msec) const break; } rounds += 10000; - } while (!t.hasExpired(msec)); + } + while (!t.hasExpired(msec)); return rounds; } \ No newline at end of file diff --git a/src/crypto/kdf/AesKdf.h b/src/crypto/kdf/AesKdf.h index 3aebc8d50..660c022ed 100644 --- a/src/crypto/kdf/AesKdf.h +++ b/src/crypto/kdf/AesKdf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -20,7 +20,7 @@ #include "Kdf.h" -class AesKdf : public Kdf +class AesKdf: public Kdf { public: AesKdf(); @@ -40,7 +40,7 @@ public: bool setRounds(quint64 rounds); bool setSeed(const QByteArray& seed); - enum class Fields : quint32 + enum class Fields: quint32 { ROUNDS, SEED @@ -55,7 +55,10 @@ private: quint64 m_rounds; QByteArray m_seed; - static bool transformKeyRaw(const QByteArray& key, const QByteArray& seed, quint64 rounds, QByteArray* result) Q_REQUIRED_RESULT; + static bool transformKeyRaw(const QByteArray& key, + const QByteArray& seed, + quint64 rounds, + QByteArray* result) Q_REQUIRED_RESULT; static QList initFields(); }; diff --git a/src/crypto/kdf/Kdf.cpp b/src/crypto/kdf/Kdf.cpp index 99f12645c..c1bfdf7e1 100644 --- a/src/crypto/kdf/Kdf.cpp +++ b/src/crypto/kdf/Kdf.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -23,11 +23,11 @@ #include Kdf::Field::Field(quint32 id, const QString& name, quint64 min, quint64 max, bool benchmark) - : m_id(id) - , m_name(name) - , m_min(min) - , m_max(max) - , m_benchmark(benchmark) + : m_id(id) + , m_name(name) + , m_min(min) + , m_max(max) + , m_benchmark(benchmark) { } @@ -71,8 +71,7 @@ int Kdf::benchmark(int msec) const } Kdf::BenchmarkThread::BenchmarkThread(int msec, const Kdf* kdf) - : m_msec(msec) - , m_kdf(kdf) + : m_msec(msec), m_kdf(kdf) { } @@ -81,6 +80,7 @@ int Kdf::BenchmarkThread::rounds() return m_rounds; } -void Kdf::BenchmarkThread::run() { +void Kdf::BenchmarkThread::run() +{ m_rounds = m_kdf->benchmarkImpl(m_msec); } diff --git a/src/crypto/kdf/Kdf.h b/src/crypto/kdf/Kdf.h index c032a030c..14ce24501 100644 --- a/src/crypto/kdf/Kdf.h +++ b/src/crypto/kdf/Kdf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -25,11 +25,13 @@ class Kdf { public: - enum class Type { + enum class Type + { AES }; - class Field { + class Field + { public: Field(quint32 id, const QString& name, quint64 min, quint64 max, bool benchmark = false); @@ -48,9 +50,11 @@ public: }; virtual ~Kdf() {} + + virtual QByteArray seed() const = 0; + virtual Type type() const = 0; virtual bool transform(const QByteArray& raw, QByteArray& result) const = 0; virtual void randomizeTransformSalt() = 0; - virtual Type type() const = 0; virtual Kdf* clone() const = 0; virtual const QList fields() const = 0; diff --git a/src/crypto/kdf/Kdf_p.h b/src/crypto/kdf/Kdf_p.h index c74837522..5606c0bf5 100644 --- a/src/crypto/kdf/Kdf_p.h +++ b/src/crypto/kdf/Kdf_p.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,9 +22,9 @@ #ifndef KEEPASSXC_KDF_P_H #define KEEPASSXC_KDF_P_H -class Kdf::BenchmarkThread : public QThread +class Kdf::BenchmarkThread: public QThread { - Q_OBJECT +Q_OBJECT public: explicit BenchmarkThread(int msec, const Kdf* kdf); diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index fdf3c5997..0ea6f817e 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -71,18 +71,17 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, quint32 signature2 = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok); if (ok && signature2 == KeePass1::SIGNATURE_2) { raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" - "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.")); + "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; - } - else if (!ok || signature2 != KeePass2::SIGNATURE_2) { + } else if (!ok || signature2 != KeePass2::SIGNATURE_2) { raiseError(tr("Not a KeePass database.")); return nullptr; } quint32 version = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok) - & KeePass2::FILE_VERSION_CRITICAL_MASK; + & KeePass2::FILE_VERSION_CRITICAL_MASK; quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) { raiseError(tr("Unsupported KeePass KDBX 2 or 3 database version.")); @@ -100,8 +99,8 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, // check if all required headers were present if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() - || m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty() - || m_db->cipher().isNull()) { + || m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty() + || m_db->cipher().isNull()) { raiseError("missing database headers"); return nullptr; } @@ -152,8 +151,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, if (m_db->compressionAlgo() == Database::CompressionNone) { xmlDevice = &hashedStream; - } - else { + } else { ioCompressor.reset(new QtIOCompressor(&hashedStream)); ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); if (!ioCompressor->open(QIODevice::ReadOnly)) { @@ -185,8 +183,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, raiseError(xmlReader.errorString()); if (keepDatabase) { return db.take(); - } - else { + } else { return nullptr; } } @@ -297,14 +294,12 @@ void Kdbx3Reader::setCompressionFlags(const QByteArray& data) { if (data.size() != 4) { raiseError("Invalid compression flags length"); - } - else { + } else { quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); if (id > Database::CompressionAlgorithmMax) { raiseError("Unsupported compression algorithm"); - } - else { + } else { m_db->setCompressionAlgo(static_cast(id)); } } @@ -314,8 +309,7 @@ void Kdbx3Reader::setMasterSeed(const QByteArray& data) { if (data.size() != 32) { raiseError("Invalid master seed size"); - } - else { + } else { m_masterSeed = data; } } @@ -324,8 +318,7 @@ void Kdbx3Reader::setTransformSeed(const QByteArray& data) { if (data.size() != 32) { raiseError("Invalid transform seed size"); - } - else { + } else { AesKdf* aesKdf; if (m_db->kdf()->type() == Kdf::Type::AES) { aesKdf = static_cast(m_db->kdf()); @@ -342,8 +335,7 @@ void Kdbx3Reader::setTransformRounds(const QByteArray& data) { if (data.size() != 8) { raiseError("Invalid transform rounds size"); - } - else { + } else { quint64 rounds = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); AesKdf* aesKdf; @@ -352,10 +344,10 @@ void Kdbx3Reader::setTransformRounds(const QByteArray& data) } else { aesKdf = new AesKdf(); m_db->setKdf(aesKdf); - } + } aesKdf->setRounds(rounds); - } + } } void Kdbx3Reader::setEncryptionIV(const QByteArray& data) @@ -372,8 +364,7 @@ void Kdbx3Reader::setStreamStartBytes(const QByteArray& data) { if (data.size() != 32) { raiseError("Invalid start bytes size"); - } - else { + } else { m_streamStartBytes = data; } } diff --git a/src/format/Kdbx3Reader.h b/src/format/Kdbx3Reader.h index 1083b4bb4..24a1ef100 100644 --- a/src/format/Kdbx3Reader.h +++ b/src/format/Kdbx3Reader.h @@ -26,9 +26,9 @@ class Database; class QIODevice; -class Kdbx3Reader : public BaseKeePass2Reader +class Kdbx3Reader: public BaseKeePass2Reader { - Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader) +Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader) public: Kdbx3Reader(); diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp index de04ad686..990aa71ee 100644 --- a/src/format/Kdbx3Writer.cpp +++ b/src/format/Kdbx3Writer.cpp @@ -78,20 +78,20 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray())); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CompressionFlags, - Endian::sizedIntToBytes(db->compressionAlgo(), - KeePass2::BYTEORDER))); + Endian::sizedIntToBytes(db->compressionAlgo(), + KeePass2::BYTEORDER))); AesKdf* kdf = static_cast(db->kdf()); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::MasterSeed, masterSeed)); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformSeed, kdf->seed())); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformRounds, - Endian::sizedIntToBytes(kdf->rounds(), - KeePass2::BYTEORDER))); + Endian::sizedIntToBytes(kdf->rounds(), + KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EncryptionIV, encryptionIV)); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey)); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::StreamStartBytes, startBytes)); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::InnerRandomStreamID, - Endian::sizedIntToBytes(KeePass2::Salsa20, - KeePass2::BYTEORDER))); + Endian::sizedIntToBytes(KeePass2::Salsa20, + KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EndOfHeader, endOfHeader)); header.close(); @@ -120,8 +120,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) if (db->compressionAlgo() == Database::CompressionNone) { m_device = &hashedStream; - } - else { + } else { ioCompressor.reset(new QtIOCompressor(&hashedStream)); ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); if (!ioCompressor->open(QIODevice::WriteOnly)) { @@ -166,8 +165,7 @@ bool Kdbx3Writer::writeData(const QByteArray& data) if (m_device->write(data) != data.size()) { raiseError(m_device->errorString()); return false; - } - else { + } else { return true; } } @@ -180,7 +178,7 @@ bool Kdbx3Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteA fieldIdArr[0] = fieldId; CHECK_RETURN_FALSE(writeData(fieldIdArr)); CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size()), - KeePass2::BYTEORDER))); + KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeData(data)); return true; diff --git a/src/format/Kdbx3Writer.h b/src/format/Kdbx3Writer.h index 6b5b8f28f..f3368eebb 100644 --- a/src/format/Kdbx3Writer.h +++ b/src/format/Kdbx3Writer.h @@ -27,9 +27,9 @@ class Database; class QIODevice; -class Kdbx3Writer : public BaseKeePass2Writer +class Kdbx3Writer: public BaseKeePass2Writer { - Q_DECLARE_TR_FUNCTIONS(Kdbx3Writer) +Q_DECLARE_TR_FUNCTIONS(Kdbx3Writer) public: Kdbx3Writer(); diff --git a/src/format/Kdbx3XmlReader.cpp b/src/format/Kdbx3XmlReader.cpp index 6f3745553..3a4bdbd9d 100644 --- a/src/format/Kdbx3XmlReader.cpp +++ b/src/format/Kdbx3XmlReader.cpp @@ -150,14 +150,12 @@ QString Kdbx3XmlReader::errorString() { if (m_error) { return m_errorStr; - } - else if (m_xml.hasError()) { + } else if (m_xml.hasError()) { return QString("XML error:\n%1\nLine %2, column %3") - .arg(m_xml.errorString()) - .arg(m_xml.lineNumber()) - .arg(m_xml.columnNumber()); - } - else { + .arg(m_xml.errorString()) + .arg(m_xml.lineNumber()) + .arg(m_xml.columnNumber()); + } else { return QString(); } } @@ -183,18 +181,15 @@ bool Kdbx3XmlReader::parseKeePassFile() while (!m_xml.error() && m_xml.readNextStartElement()) { if (m_xml.name() == "Meta") { parseMeta(); - } - else if (m_xml.name() == "Root") { + } else if (m_xml.name() == "Root") { if (rootElementFound) { rootParsedSuccessfully = false; raiseError("Multiple root elements"); - } - else { + } else { rootParsedSuccessfully = parseRoot(); rootElementFound = true; } - } - else { + } else { skipCurrentElement(); } } @@ -209,95 +204,67 @@ void Kdbx3XmlReader::parseMeta() while (!m_xml.error() && m_xml.readNextStartElement()) { if (m_xml.name() == "Generator") { m_meta->setGenerator(readString()); - } - else if (m_xml.name() == "HeaderHash") { + } else if (m_xml.name() == "HeaderHash") { m_headerHash = readBinary(); - } - else if (m_xml.name() == "DatabaseName") { + } else if (m_xml.name() == "DatabaseName") { m_meta->setName(readString()); - } - else if (m_xml.name() == "DatabaseNameChanged") { + } else if (m_xml.name() == "DatabaseNameChanged") { m_meta->setNameChanged(readDateTime()); - } - else if (m_xml.name() == "DatabaseDescription") { + } else if (m_xml.name() == "DatabaseDescription") { m_meta->setDescription(readString()); - } - else if (m_xml.name() == "DatabaseDescriptionChanged") { + } else if (m_xml.name() == "DatabaseDescriptionChanged") { m_meta->setDescriptionChanged(readDateTime()); - } - else if (m_xml.name() == "DefaultUserName") { + } else if (m_xml.name() == "DefaultUserName") { m_meta->setDefaultUserName(readString()); - } - else if (m_xml.name() == "DefaultUserNameChanged") { + } else if (m_xml.name() == "DefaultUserNameChanged") { m_meta->setDefaultUserNameChanged(readDateTime()); - } - else if (m_xml.name() == "MaintenanceHistoryDays") { + } else if (m_xml.name() == "MaintenanceHistoryDays") { m_meta->setMaintenanceHistoryDays(readNumber()); - } - else if (m_xml.name() == "Color") { + } else if (m_xml.name() == "Color") { m_meta->setColor(readColor()); - } - else if (m_xml.name() == "MasterKeyChanged") { + } else if (m_xml.name() == "MasterKeyChanged") { m_meta->setMasterKeyChanged(readDateTime()); - } - else if (m_xml.name() == "MasterKeyChangeRec") { + } else if (m_xml.name() == "MasterKeyChangeRec") { m_meta->setMasterKeyChangeRec(readNumber()); - } - else if (m_xml.name() == "MasterKeyChangeForce") { + } else if (m_xml.name() == "MasterKeyChangeForce") { m_meta->setMasterKeyChangeForce(readNumber()); - } - else if (m_xml.name() == "MemoryProtection") { + } else if (m_xml.name() == "MemoryProtection") { parseMemoryProtection(); - } - else if (m_xml.name() == "CustomIcons") { + } else if (m_xml.name() == "CustomIcons") { parseCustomIcons(); - } - else if (m_xml.name() == "RecycleBinEnabled") { + } else if (m_xml.name() == "RecycleBinEnabled") { m_meta->setRecycleBinEnabled(readBool()); - } - else if (m_xml.name() == "RecycleBinUUID") { + } else if (m_xml.name() == "RecycleBinUUID") { m_meta->setRecycleBin(getGroup(readUuid())); - } - else if (m_xml.name() == "RecycleBinChanged") { + } else if (m_xml.name() == "RecycleBinChanged") { m_meta->setRecycleBinChanged(readDateTime()); - } - else if (m_xml.name() == "EntryTemplatesGroup") { + } else if (m_xml.name() == "EntryTemplatesGroup") { m_meta->setEntryTemplatesGroup(getGroup(readUuid())); - } - else if (m_xml.name() == "EntryTemplatesGroupChanged") { + } else if (m_xml.name() == "EntryTemplatesGroupChanged") { m_meta->setEntryTemplatesGroupChanged(readDateTime()); - } - else if (m_xml.name() == "LastSelectedGroup") { + } else if (m_xml.name() == "LastSelectedGroup") { m_meta->setLastSelectedGroup(getGroup(readUuid())); - } - else if (m_xml.name() == "LastTopVisibleGroup") { + } else if (m_xml.name() == "LastTopVisibleGroup") { m_meta->setLastTopVisibleGroup(getGroup(readUuid())); - } - else if (m_xml.name() == "HistoryMaxItems") { + } else if (m_xml.name() == "HistoryMaxItems") { int value = readNumber(); if (value >= -1) { m_meta->setHistoryMaxItems(value); - } - else { + } else { raiseError("HistoryMaxItems invalid number"); } - } - else if (m_xml.name() == "HistoryMaxSize") { + } else if (m_xml.name() == "HistoryMaxSize") { int value = readNumber(); if (value >= -1) { m_meta->setHistoryMaxSize(value); - } - else { + } else { raiseError("HistoryMaxSize invalid number"); } - } - else if (m_xml.name() == "Binaries") { + } else if (m_xml.name() == "Binaries") { parseBinaries(); - } - else if (m_xml.name() == "CustomData") { + } else if (m_xml.name() == "CustomData") { parseCustomData(); - } - else { + } else { skipCurrentElement(); } } @@ -310,20 +277,15 @@ void Kdbx3XmlReader::parseMemoryProtection() while (!m_xml.error() && m_xml.readNextStartElement()) { if (m_xml.name() == "ProtectTitle") { m_meta->setProtectTitle(readBool()); - } - else if (m_xml.name() == "ProtectUserName") { + } else if (m_xml.name() == "ProtectUserName") { m_meta->setProtectUsername(readBool()); - } - else if (m_xml.name() == "ProtectPassword") { + } else if (m_xml.name() == "ProtectPassword") { m_meta->setProtectPassword(readBool()); - } - else if (m_xml.name() == "ProtectURL") { + } else if (m_xml.name() == "ProtectURL") { m_meta->setProtectUrl(readBool()); - } - else if (m_xml.name() == "ProtectNotes") { + } else if (m_xml.name() == "ProtectNotes") { m_meta->setProtectNotes(readBool()); - } - else { + } else { skipCurrentElement(); } } @@ -336,8 +298,7 @@ void Kdbx3XmlReader::parseCustomIcons() while (!m_xml.error() && m_xml.readNextStartElement()) { if (m_xml.name() == "Icon") { parseIcon(); - } - else { + } else { skipCurrentElement(); } } @@ -356,20 +317,17 @@ void Kdbx3XmlReader::parseIcon() if (m_xml.name() == "UUID") { uuid = readUuid(); uuidSet = !uuid.isNull(); - } - else if (m_xml.name() == "Data") { + } else if (m_xml.name() == "Data") { icon.loadFromData(readBinary()); iconSet = true; - } - else { + } else { skipCurrentElement(); } } if (uuidSet && iconSet) { m_meta->addCustomIcon(uuid, icon); - } - else { + } else { raiseError("Missing icon uuid or data"); } } @@ -387,8 +345,7 @@ void Kdbx3XmlReader::parseBinaries() QByteArray data; if (attr.value("Compressed").compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) { data = readCompressedBinary(); - } - else { + } else { data = readBinary(); } @@ -398,8 +355,7 @@ void Kdbx3XmlReader::parseBinaries() } m_binaryPool.insert(id, data); - } - else { + } else { skipCurrentElement(); } } @@ -412,8 +368,7 @@ void Kdbx3XmlReader::parseCustomData() while (!m_xml.error() && m_xml.readNextStartElement()) { if (m_xml.name() == "Item") { parseCustomDataItem(); - } - else { + } else { skipCurrentElement(); } } @@ -432,20 +387,17 @@ void Kdbx3XmlReader::parseCustomDataItem() if (m_xml.name() == "Key") { key = readString(); keySet = true; - } - else if (m_xml.name() == "Value") { + } else if (m_xml.name() == "Value") { value = readString(); valueSet = true; - } - else { + } else { skipCurrentElement(); } } if (keySet && valueSet) { m_meta->addCustomField(key, value); - } - else { + } else { raiseError("Missing custom data key or value"); } } @@ -474,11 +426,9 @@ bool Kdbx3XmlReader::parseRoot() } groupElementFound = true; - } - else if (m_xml.name() == "DeletedObjects") { + } else if (m_xml.name() == "DeletedObjects") { parseDeletedObjects(); - } - else { + } else { skipCurrentElement(); } } @@ -500,99 +450,77 @@ Group* Kdbx3XmlReader::parseGroup() if (uuid.isNull()) { if (m_strictMode) { raiseError("Null group uuid"); - } - else { + } else { group->setUuid(Uuid::random()); } - } - else { + } else { group->setUuid(uuid); } - } - else if (m_xml.name() == "Name") { + } else if (m_xml.name() == "Name") { group->setName(readString()); - } - else if (m_xml.name() == "Notes") { + } else if (m_xml.name() == "Notes") { group->setNotes(readString()); - } - else if (m_xml.name() == "IconID") { + } else if (m_xml.name() == "IconID") { int iconId = readNumber(); if (iconId < 0) { if (m_strictMode) { raiseError("Invalid group icon number"); } iconId = 0; - } - else { + } else { if (iconId >= DatabaseIcons::IconCount) { qWarning("Kdbx3XmlReader::parseGroup: icon id \"%d\" not supported", iconId); } group->setIcon(iconId); } - } - else if (m_xml.name() == "CustomIconUUID") { + } else if (m_xml.name() == "CustomIconUUID") { Uuid uuid = readUuid(); if (!uuid.isNull()) { group->setIcon(uuid); } - } - else if (m_xml.name() == "Times") { + } else if (m_xml.name() == "Times") { group->setTimeInfo(parseTimes()); - } - else if (m_xml.name() == "IsExpanded") { + } else if (m_xml.name() == "IsExpanded") { group->setExpanded(readBool()); - } - else if (m_xml.name() == "DefaultAutoTypeSequence") { + } else if (m_xml.name() == "DefaultAutoTypeSequence") { group->setDefaultAutoTypeSequence(readString()); - } - else if (m_xml.name() == "EnableAutoType") { + } else if (m_xml.name() == "EnableAutoType") { QString str = readString(); if (str.compare("null", Qt::CaseInsensitive) == 0) { group->setAutoTypeEnabled(Group::Inherit); - } - else if (str.compare("true", Qt::CaseInsensitive) == 0) { + } else if (str.compare("true", Qt::CaseInsensitive) == 0) { group->setAutoTypeEnabled(Group::Enable); - } - else if (str.compare("false", Qt::CaseInsensitive) == 0) { + } else if (str.compare("false", Qt::CaseInsensitive) == 0) { group->setAutoTypeEnabled(Group::Disable); - } - else { + } else { raiseError("Invalid EnableAutoType value"); } - } - else if (m_xml.name() == "EnableSearching") { + } else if (m_xml.name() == "EnableSearching") { QString str = readString(); if (str.compare("null", Qt::CaseInsensitive) == 0) { group->setSearchingEnabled(Group::Inherit); - } - else if (str.compare("true", Qt::CaseInsensitive) == 0) { + } else if (str.compare("true", Qt::CaseInsensitive) == 0) { group->setSearchingEnabled(Group::Enable); - } - else if (str.compare("false", Qt::CaseInsensitive) == 0) { + } else if (str.compare("false", Qt::CaseInsensitive) == 0) { group->setSearchingEnabled(Group::Disable); - } - else { + } else { raiseError("Invalid EnableSearching value"); } - } - else if (m_xml.name() == "LastTopVisibleEntry") { + } else if (m_xml.name() == "LastTopVisibleEntry") { group->setLastTopVisibleEntry(getEntry(readUuid())); - } - else if (m_xml.name() == "Group") { + } else if (m_xml.name() == "Group") { Group* newGroup = parseGroup(); if (newGroup) { children.append(newGroup); } - } - else if (m_xml.name() == "Entry") { + } else if (m_xml.name() == "Entry") { Entry* newEntry = parseEntry(false); if (newEntry) { entries.append(newEntry); } - } - else { + } else { skipCurrentElement(); } } @@ -607,8 +535,7 @@ Group* Kdbx3XmlReader::parseGroup() group->copyDataFrom(tmpGroup); group->setUpdateTimeinfo(false); delete tmpGroup; - } - else if (!hasError()) { + } else if (!hasError()) { raiseError("No group uuid found"); } @@ -630,8 +557,7 @@ void Kdbx3XmlReader::parseDeletedObjects() while (!m_xml.error() && m_xml.readNextStartElement()) { if (m_xml.name() == "DeletedObject") { parseDeletedObject(); - } - else { + } else { skipCurrentElement(); } } @@ -650,23 +576,19 @@ void Kdbx3XmlReader::parseDeletedObject() if (m_strictMode) { raiseError("Null DeleteObject uuid"); } - } - else { + } else { delObj.uuid = uuid; } - } - else if (m_xml.name() == "DeletionTime") { + } else if (m_xml.name() == "DeletionTime") { delObj.deletionTime = readDateTime(); - } - else { + } else { skipCurrentElement(); } } if (!delObj.uuid.isNull() && !delObj.deletionTime.isNull()) { m_db->addDeletedObject(delObj); - } - else if (m_strictMode) { + } else if (m_strictMode) { raiseError("Missing DeletedObject uuid or time"); } } @@ -686,69 +608,53 @@ Entry* Kdbx3XmlReader::parseEntry(bool history) if (uuid.isNull()) { if (m_strictMode) { raiseError("Null entry uuid"); - } - else { + } else { entry->setUuid(Uuid::random()); } - } - else { + } else { entry->setUuid(uuid); } - } - else if (m_xml.name() == "IconID") { + } else if (m_xml.name() == "IconID") { int iconId = readNumber(); if (iconId < 0) { if (m_strictMode) { raiseError("Invalid entry icon number"); } iconId = 0; - } - else { + } else { entry->setIcon(iconId); } - } - else if (m_xml.name() == "CustomIconUUID") { + } else if (m_xml.name() == "CustomIconUUID") { Uuid uuid = readUuid(); if (!uuid.isNull()) { entry->setIcon(uuid); } - } - else if (m_xml.name() == "ForegroundColor") { + } else if (m_xml.name() == "ForegroundColor") { entry->setForegroundColor(readColor()); - } - else if (m_xml.name() == "BackgroundColor") { + } else if (m_xml.name() == "BackgroundColor") { entry->setBackgroundColor(readColor()); - } - else if (m_xml.name() == "OverrideURL") { + } else if (m_xml.name() == "OverrideURL") { entry->setOverrideUrl(readString()); - } - else if (m_xml.name() == "Tags") { + } else if (m_xml.name() == "Tags") { entry->setTags(readString()); - } - else if (m_xml.name() == "Times") { + } else if (m_xml.name() == "Times") { entry->setTimeInfo(parseTimes()); - } - else if (m_xml.name() == "String") { + } else if (m_xml.name() == "String") { parseEntryString(entry); - } - else if (m_xml.name() == "Binary") { + } else if (m_xml.name() == "Binary") { QPair ref = parseEntryBinary(entry); if (!ref.first.isNull() && !ref.second.isNull()) { binaryRefs.append(ref); } - } - else if (m_xml.name() == "AutoType") { + } else if (m_xml.name() == "AutoType") { parseAutoType(entry); - } - else if (m_xml.name() == "History") { + } else if (m_xml.name() == "History") { if (history) { raiseError("History element in history entry"); - } - else { + } else { historyItems = parseEntryHistory(); } - } - else { + } else { skipCurrentElement(); } } @@ -760,8 +666,7 @@ Entry* Kdbx3XmlReader::parseEntry(bool history) if (!entry->uuid().isNull()) { if (history) { entry->setUpdateTimeinfo(false); - } - else { + } else { Entry* tmpEntry = entry; entry = getEntry(tmpEntry->uuid()); @@ -770,8 +675,7 @@ Entry* Kdbx3XmlReader::parseEntry(bool history) delete tmpEntry; } - } - else if (!hasError()) { + } else if (!hasError()) { raiseError("No entry uuid found"); } @@ -807,8 +711,7 @@ void Kdbx3XmlReader::parseEntryString(Entry* entry) if (m_xml.name() == "Key") { key = readString(); keySet = true; - } - else if (m_xml.name() == "Value") { + } else if (m_xml.name() == "Value") { QXmlStreamAttributes attr = m_xml.attributes(); value = readString(); @@ -823,20 +726,17 @@ void Kdbx3XmlReader::parseEntryString(Entry* entry) if (!ok) { value.clear(); raiseError(m_randomStream->errorString()); - } - else { + } else { value = QString::fromUtf8(plaintext); } - } - else { + } else { raiseError("Unable to decrypt entry string"); } } protect = isProtected || protectInMemory; valueSet = true; - } - else { + } else { skipCurrentElement(); } } @@ -845,12 +745,10 @@ void Kdbx3XmlReader::parseEntryString(Entry* entry) // the default attributes are always there so additionally check if it's empty if (entry->attributes()->hasKey(key) && !entry->attributes()->value(key).isEmpty()) { raiseError("Duplicate custom attribute found"); - } - else { + } else { entry->attributes()->set(key, value, protect); } - } - else { + } else { raiseError("Entry string key or value missing"); } } @@ -870,19 +768,17 @@ QPair Kdbx3XmlReader::parseEntryBinary(Entry* entry) if (m_xml.name() == "Key") { key = readString(); keySet = true; - } - else if (m_xml.name() == "Value") { + } else if (m_xml.name() == "Value") { QXmlStreamAttributes attr = m_xml.attributes(); if (attr.hasAttribute("Ref")) { poolRef = qMakePair(attr.value("Ref").toString(), key); m_xml.skipCurrentElement(); - } - else { + } else { // format compatibility value = readBinary(); bool isProtected = attr.hasAttribute("Protected") - && (attr.value("Protected") == "True"); + && (attr.value("Protected") == "True"); if (isProtected && !value.isEmpty()) { if (!m_randomStream->processInPlace(value)) { @@ -892,8 +788,7 @@ QPair Kdbx3XmlReader::parseEntryBinary(Entry* entry) } valueSet = true; - } - else { + } else { skipCurrentElement(); } } @@ -901,12 +796,10 @@ QPair Kdbx3XmlReader::parseEntryBinary(Entry* entry) if (keySet && valueSet) { if (entry->attachments()->hasKey(key)) { raiseError("Duplicate attachment found"); - } - else { + } else { entry->attachments()->set(key, value); } - } - else { + } else { raiseError("Entry binary key or value missing"); } @@ -920,17 +813,13 @@ void Kdbx3XmlReader::parseAutoType(Entry* entry) while (!m_xml.error() && m_xml.readNextStartElement()) { if (m_xml.name() == "Enabled") { entry->setAutoTypeEnabled(readBool()); - } - else if (m_xml.name() == "DataTransferObfuscation") { + } else if (m_xml.name() == "DataTransferObfuscation") { entry->setAutoTypeObfuscation(readNumber()); - } - else if (m_xml.name() == "DefaultSequence") { + } else if (m_xml.name() == "DefaultSequence") { entry->setDefaultAutoTypeSequence(readString()); - } - else if (m_xml.name() == "Association") { + } else if (m_xml.name() == "Association") { parseAutoTypeAssoc(entry); - } - else { + } else { skipCurrentElement(); } } @@ -948,20 +837,17 @@ void Kdbx3XmlReader::parseAutoTypeAssoc(Entry* entry) if (m_xml.name() == "Window") { assoc.window = readString(); windowSet = true; - } - else if (m_xml.name() == "KeystrokeSequence") { + } else if (m_xml.name() == "KeystrokeSequence") { assoc.sequence = readString(); sequenceSet = true; - } - else { + } else { skipCurrentElement(); } } if (windowSet && sequenceSet) { entry->autoTypeAssociations()->add(assoc); - } - else { + } else { raiseError("Auto-type association window or sequence missing"); } } @@ -975,8 +861,7 @@ QList Kdbx3XmlReader::parseEntryHistory() while (!m_xml.error() && m_xml.readNextStartElement()) { if (m_xml.name() == "Entry") { historyItems.append(parseEntry(true)); - } - else { + } else { skipCurrentElement(); } } @@ -992,26 +877,19 @@ TimeInfo Kdbx3XmlReader::parseTimes() while (!m_xml.error() && m_xml.readNextStartElement()) { if (m_xml.name() == "LastModificationTime") { timeInfo.setLastModificationTime(readDateTime()); - } - else if (m_xml.name() == "CreationTime") { + } else if (m_xml.name() == "CreationTime") { timeInfo.setCreationTime(readDateTime()); - } - else if (m_xml.name() == "LastAccessTime") { + } else if (m_xml.name() == "LastAccessTime") { timeInfo.setLastAccessTime(readDateTime()); - } - else if (m_xml.name() == "ExpiryTime") { + } else if (m_xml.name() == "ExpiryTime") { timeInfo.setExpiryTime(readDateTime()); - } - else if (m_xml.name() == "Expires") { + } else if (m_xml.name() == "Expires") { timeInfo.setExpires(readBool()); - } - else if (m_xml.name() == "UsageCount") { + } else if (m_xml.name() == "UsageCount") { timeInfo.setUsageCount(readNumber()); - } - else if (m_xml.name() == "LocationChanged") { + } else if (m_xml.name() == "LocationChanged") { timeInfo.setLocationChanged(readDateTime()); - } - else { + } else { skipCurrentElement(); } } @@ -1030,14 +908,11 @@ bool Kdbx3XmlReader::readBool() if (str.compare("True", Qt::CaseInsensitive) == 0) { return true; - } - else if (str.compare("False", Qt::CaseInsensitive) == 0) { + } else if (str.compare("False", Qt::CaseInsensitive) == 0) { return false; - } - else if (str.length() == 0) { + } else if (str.length() == 0) { return false; - } - else { + } else { raiseError("Invalid bool value"); return false; } @@ -1051,8 +926,7 @@ QDateTime Kdbx3XmlReader::readDateTime() if (!dt.isValid()) { if (m_strictMode) { raiseError("Invalid date time value"); - } - else { + } else { dt = QDateTime::currentDateTimeUtc(); } } @@ -1077,7 +951,7 @@ QColor Kdbx3XmlReader::readColor() QColor color; for (int i = 0; i <= 2; i++) { - QString rgbPartStr = colorStr.mid(1 + 2*i, 2); + QString rgbPartStr = colorStr.mid(1 + 2 * i, 2); bool ok; int rgbPart = rgbPartStr.toInt(&ok, 16); if (!ok || rgbPart > 255) { @@ -1089,11 +963,9 @@ QColor Kdbx3XmlReader::readColor() if (i == 0) { color.setRed(rgbPart); - } - else if (i == 1) { + } else if (i == 1) { color.setGreen(rgbPart); - } - else { + } else { color.setBlue(rgbPart); } } @@ -1116,14 +988,12 @@ Uuid Kdbx3XmlReader::readUuid() QByteArray uuidBin = readBinary(); if (uuidBin.isEmpty()) { return Uuid(); - } - else if (uuidBin.length() != Uuid::Length) { + } else if (uuidBin.length() != Uuid::Length) { if (m_strictMode) { raiseError("Invalid uuid value"); } return Uuid(); - } - else { + } else { return Uuid(uuidBin); } } @@ -1159,8 +1029,7 @@ Group* Kdbx3XmlReader::getGroup(const Uuid& uuid) if (m_groups.contains(uuid)) { return m_groups.value(uuid); - } - else { + } else { Group* group = new Group(); group->setUpdateTimeinfo(false); group->setUuid(uuid); @@ -1178,8 +1047,7 @@ Entry* Kdbx3XmlReader::getEntry(const Uuid& uuid) if (m_entries.contains(uuid)) { return m_entries.value(uuid); - } - else { + } else { Entry* entry = new Entry(); entry->setUpdateTimeinfo(false); entry->setUuid(uuid); diff --git a/src/format/Kdbx3XmlReader.h b/src/format/Kdbx3XmlReader.h index 1837aaab6..477eb3865 100644 --- a/src/format/Kdbx3XmlReader.h +++ b/src/format/Kdbx3XmlReader.h @@ -36,7 +36,7 @@ class Metadata; class Kdbx3XmlReader { - Q_DECLARE_TR_FUNCTIONS(Kdbx3XmlReader) +Q_DECLARE_TR_FUNCTIONS(Kdbx3XmlReader) public: Kdbx3XmlReader(); diff --git a/src/format/Kdbx3XmlWriter.cpp b/src/format/Kdbx3XmlWriter.cpp index ff9262a01..40c20e65c 100644 --- a/src/format/Kdbx3XmlWriter.cpp +++ b/src/format/Kdbx3XmlWriter.cpp @@ -36,7 +36,7 @@ Kdbx3XmlWriter::Kdbx3XmlWriter() } void Kdbx3XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, - const QByteArray& headerHash) + const QByteArray& headerHash) { m_db = db; m_meta = db->metadata(); @@ -66,7 +66,7 @@ void Kdbx3XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2Rand void Kdbx3XmlWriter::writeDatabase(const QString& filename, Database* db) { QFile file(filename); - file.open(QIODevice::WriteOnly|QIODevice::Truncate); + file.open(QIODevice::WriteOnly | QIODevice::Truncate); writeDatabase(&file, db); } @@ -202,8 +202,7 @@ void Kdbx3XmlWriter::writeBinaries() buffer.seek(0); data = buffer.readAll(); - } - else { + } else { data = i.key(); } @@ -346,12 +345,12 @@ void Kdbx3XmlWriter::writeEntry(const Entry* entry) for (const QString& key : attributesKeyList) { m_xml.writeStartElement("String"); - bool protect = ( ((key == "Title") && m_meta->protectTitle()) || - ((key == "UserName") && m_meta->protectUsername()) || - ((key == "Password") && m_meta->protectPassword()) || - ((key == "URL") && m_meta->protectUrl()) || - ((key == "Notes") && m_meta->protectNotes()) || - entry->attributes()->isProtected(key) ); + bool protect = (((key == "Title") && m_meta->protectTitle()) || + ((key == "UserName") && m_meta->protectUsername()) || + ((key == "Password") && m_meta->protectPassword()) || + ((key == "URL") && m_meta->protectUrl()) || + ((key == "Notes") && m_meta->protectNotes()) || + entry->attributes()->isProtected(key)); writeString("Key", key); @@ -367,13 +366,11 @@ void Kdbx3XmlWriter::writeEntry(const Entry* entry) raiseError(m_randomStream->errorString()); } value = QString::fromLatin1(rawData.toBase64()); - } - else { + } else { m_xml.writeAttribute("ProtectInMemory", "True"); value = entry->attributes()->value(key); } - } - else { + } else { value = entry->attributes()->value(key); } @@ -449,8 +446,7 @@ void Kdbx3XmlWriter::writeString(const QString& qualifiedName, const QString& st { if (string.isEmpty()) { m_xml.writeEmptyElement(qualifiedName); - } - else { + } else { m_xml.writeTextElement(qualifiedName, stripInvalidXml10Chars(string)); } } @@ -464,8 +460,7 @@ void Kdbx3XmlWriter::writeBool(const QString& qualifiedName, bool b) { if (b) { writeString(qualifiedName, "True"); - } - else { + } else { writeString(qualifiedName, "False"); } } @@ -494,8 +489,7 @@ void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) { if (group) { writeUuid(qualifiedName, group->uuid()); - } - else { + } else { writeUuid(qualifiedName, Uuid()); } } @@ -504,8 +498,7 @@ void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) { if (entry) { writeUuid(qualifiedName, entry->uuid()); - } - else { + } else { writeUuid(qualifiedName, Uuid()); } } @@ -520,9 +513,9 @@ void Kdbx3XmlWriter::writeColor(const QString& qualifiedName, const QColor& colo QString colorStr; if (color.isValid()) { - colorStr = QString("#%1%2%3").arg(colorPartToString(color.red()), - colorPartToString(color.green()), - colorPartToString(color.blue())); + colorStr = QString("#%1%2%3").arg(colorPartToString(color.red()), + colorPartToString(color.green()), + colorPartToString(color.blue())); } writeString(qualifiedName, colorStr); @@ -534,11 +527,9 @@ void Kdbx3XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState if (triState == Group::Inherit) { value = "null"; - } - else if (triState == Group::Enable) { + } else if (triState == Group::Enable) { value = "true"; - } - else { + } else { value = "false"; } @@ -564,13 +555,12 @@ QString Kdbx3XmlWriter::stripInvalidXml10Chars(QString str) if (ch.isLowSurrogate() && i != 0 && str.at(i - 1).isHighSurrogate()) { // keep valid surrogate pair i--; - } - else if ((uc < 0x20 && uc != 0x09 && uc != 0x0A && uc != 0x0D) // control characters - || (uc >= 0x7F && uc <= 0x84) // control characters, valid but discouraged by XML - || (uc >= 0x86 && uc <= 0x9F) // control characters, valid but discouraged by XML - || (uc > 0xFFFD) // noncharacter - || ch.isLowSurrogate() // single low surrogate - || ch.isHighSurrogate()) // single high surrogate + } else if ((uc < 0x20 && uc != 0x09 && uc != 0x0A && uc != 0x0D) // control characters + || (uc >= 0x7F && uc <= 0x84) // control characters, valid but discouraged by XML + || (uc >= 0x86 && uc <= 0x9F) // control characters, valid but discouraged by XML + || (uc > 0xFFFD) // noncharacter + || ch.isLowSurrogate() // single low surrogate + || ch.isHighSurrogate()) // single high surrogate { qWarning("Stripping invalid XML 1.0 codepoint %x", uc); str.remove(i, 1); diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index 8cf3df555..e79dff513 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -48,24 +48,24 @@ Kdf* KeePass2::uuidToKdf(const Uuid& uuid) { Uuid KeePass2::kdfToUuid(const Kdf& kdf) { switch (kdf.type()) { - case Kdf::Type::AES: - return KDF_AES; - default: - return Uuid(); + case Kdf::Type::AES: + return KDF_AES; + default: + return Uuid(); } } KeePass2::ProtectedStreamAlgo KeePass2::idToProtectedStreamAlgo(quint32 id) { switch (id) { - case static_cast(KeePass2::ArcFourVariant): - return KeePass2::ArcFourVariant; - case static_cast(KeePass2::Salsa20): - return KeePass2::Salsa20; - case static_cast(KeePass2::ChaCha20): - return KeePass2::ChaCha20; - default: - return KeePass2::InvalidProtectedStreamAlgo; + case static_cast(KeePass2::ArcFourVariant): + return KeePass2::ArcFourVariant; + case static_cast(KeePass2::Salsa20): + return KeePass2::Salsa20; + case static_cast(KeePass2::ChaCha20): + return KeePass2::ChaCha20; + default: + return KeePass2::InvalidProtectedStreamAlgo; } } diff --git a/src/format/KeePass2RandomStream.cpp b/src/format/KeePass2RandomStream.cpp index 5f80fc151..9e3d4cbce 100644 --- a/src/format/KeePass2RandomStream.cpp +++ b/src/format/KeePass2RandomStream.cpp @@ -29,16 +29,16 @@ KeePass2RandomStream::KeePass2RandomStream(KeePass2::ProtectedStreamAlgo algo) bool KeePass2RandomStream::init(const QByteArray& key) { switch (m_cipher.algorithm()) { - case SymmetricCipher::Salsa20: - return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), - KeePass2::INNER_STREAM_SALSA20_IV); - case SymmetricCipher::ChaCha20: { - QByteArray keyIv = CryptoHash::hash(key, CryptoHash::Sha512); - return m_cipher.init(keyIv.left(32), keyIv.mid(32, 12)); - } - default: - qWarning("Invalid stream algorithm (%d)", m_cipher.algorithm()); - break; + case SymmetricCipher::Salsa20: + return m_cipher.init(CryptoHash::hash(key, CryptoHash::Sha256), + KeePass2::INNER_STREAM_SALSA20_IV); + case SymmetricCipher::ChaCha20: { + QByteArray keyIv = CryptoHash::hash(key, CryptoHash::Sha512); + return m_cipher.init(keyIv.left(32), keyIv.mid(32, 12)); + } + default: + qWarning("Invalid stream algorithm (%d)", m_cipher.algorithm()); + break; } return false; } diff --git a/src/streams/HmacBlockStream.cpp b/src/streams/HmacBlockStream.cpp index 40c5fc547..677921a60 100644 --- a/src/streams/HmacBlockStream.cpp +++ b/src/streams/HmacBlockStream.cpp @@ -1,5 +1,5 @@ /* -* Copyright (C) 2017 KeePassXC Team +* Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -24,7 +24,7 @@ const QSysInfo::Endian HmacBlockStream::ByteOrder = QSysInfo::LittleEndian; HmacBlockStream::HmacBlockStream(QIODevice* baseDevice, QByteArray key) : LayeredStream(baseDevice) - , m_blockSize(1024*1024) + , m_blockSize(1024 * 1024) , m_key(key) { init(); @@ -94,8 +94,7 @@ qint64 HmacBlockStream::readData(char* data, qint64 maxSize) { if (m_error) { return -1; - } - else if (m_eof) { + } else if (m_eof) { return 0; } @@ -107,8 +106,7 @@ qint64 HmacBlockStream::readData(char* data, qint64 maxSize) if (!readHashedBlock()) { if (m_error) { return -1; - } - else { + } else { return maxSize - bytesRemaining; } } @@ -204,8 +202,7 @@ qint64 HmacBlockStream::writeData(const char* data, qint64 maxSize) if (!writeHashedBlock()) { if (m_error) { return -1; - } - else { + } else { return maxSize - bytesRemaining; } } @@ -249,11 +246,13 @@ bool HmacBlockStream::writeHashedBlock() return true; } -QByteArray HmacBlockStream::getCurrentHmacKey() const { +QByteArray HmacBlockStream::getCurrentHmacKey() const +{ return getHmacKey(m_blockIndex, m_key); } -QByteArray HmacBlockStream::getHmacKey(quint64 blockIndex, QByteArray key) { +QByteArray HmacBlockStream::getHmacKey(quint64 blockIndex, QByteArray key) +{ Q_ASSERT(key.size() == 64); QByteArray indexBytes = Endian::sizedIntToBytes(blockIndex, ByteOrder); CryptoHash hasher(CryptoHash::Sha512); @@ -262,6 +261,7 @@ QByteArray HmacBlockStream::getHmacKey(quint64 blockIndex, QByteArray key) { return hasher.result(); } -bool HmacBlockStream::atEnd() const { +bool HmacBlockStream::atEnd() const +{ return m_eof; } diff --git a/src/streams/HmacBlockStream.h b/src/streams/HmacBlockStream.h index eecd8fe92..592cf844c 100644 --- a/src/streams/HmacBlockStream.h +++ b/src/streams/HmacBlockStream.h @@ -1,5 +1,5 @@ /* -* Copyright (C) 2017 KeePassXC Team +* Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -22,9 +22,9 @@ #include "streams/LayeredStream.h" -class HmacBlockStream : public LayeredStream +class HmacBlockStream: public LayeredStream { - Q_OBJECT +Q_OBJECT public: explicit HmacBlockStream(QIODevice* baseDevice, QByteArray key); From d00ccd2eb5016dec4be962385e61d5bccd606ce1 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 17 Dec 2017 14:49:46 +0100 Subject: [PATCH 13/39] Add AsyncTask helper functions --- src/CMakeLists.txt | 1 + src/core/AsyncTask.h | 63 ++++++++++++++++++++++++++++++ src/gui/DatabaseSettingsWidget.cpp | 25 ++++-------- src/gui/DatabaseSettingsWidget.h | 11 +++--- 4 files changed, 77 insertions(+), 23 deletions(-) create mode 100644 src/core/AsyncTask.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5172ea0ba..1908cea09 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -38,6 +38,7 @@ configure_file(version.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version.h @ONLY) set(keepassx_SOURCES core/AutoTypeAssociations.cpp + core/AsyncTask.h core/Config.cpp core/CsvParser.cpp core/Database.cpp diff --git a/src/core/AsyncTask.h b/src/core/AsyncTask.h new file mode 100644 index 000000000..67cab1609 --- /dev/null +++ b/src/core/AsyncTask.h @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . +*/ + +#ifndef KEEPASSXC_ASYNCTASK_HPP +#define KEEPASSXC_ASYNCTASK_HPP + +#include +#include +#include + + +/** + * Asynchronously run computations outside the GUI thread. + */ +namespace AsyncTask +{ + +/** + * Wait for the given future without blocking the event loop. + * + * @param future future to wait for + * @return async task result + */ +template +typename std::result_of::type waitForFuture(QFuture::type> future) +{ + QEventLoop loop; + QFutureWatcher::type> watcher; + QObject::connect(&watcher, SIGNAL(finished()), &loop, SLOT(quit())); + watcher.setFuture(future); + loop.exec(); + return future.result(); +} + +/** + * Run a given task and wait for it to finish without blocking the event loop. + * + * @param task std::function object to run + * @return async task result + */ +template +typename std::result_of::type runAndWaitForFuture(FunctionObject task) +{ + return waitForFuture(QtConcurrent::run(task)); +} + +}; // namespace AsyncTask + +#endif //KEEPASSXC_ASYNCTASK_HPP diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index 5f1dfeedf..ce1fb8dbb 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -18,28 +18,18 @@ #include "DatabaseSettingsWidget.h" #include "ui_DatabaseSettingsWidget.h" -#include -#include -#include -#include -#include #include -#include +#include "core/AsyncTask.h" #include "core/Database.h" #include "core/Group.h" #include "core/Metadata.h" #include "crypto/SymmetricCipher.h" -#include "format/KeePass2.h" -#include "keys/CompositeKey.h" -#include "format/KeePass2.h" -#include "crypto/kdf/Kdf.h" #include "MessageBox.h" DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) : DialogyWidget(parent) , m_ui(new Ui::DatabaseSettingsWidget()) - , m_benchmarkField(nullptr) , m_db(nullptr) { m_ui->setupUi(this); @@ -51,6 +41,7 @@ DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) connect(m_ui->historyMaxSizeCheckBox, SIGNAL(toggled(bool)), m_ui->historyMaxSizeSpinBox, SLOT(setEnabled(bool))); connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeKdf(int))); + connect(m_ui->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark())); } DatabaseSettingsWidget::~DatabaseSettingsWidget() @@ -111,6 +102,7 @@ void DatabaseSettingsWidget::load(Database* db) } displayKdf(*m_kdf); m_ui->kdfComboBox->blockSignals(blockSignals); + m_ui->transformRoundsSpinBox->setValue(static_cast(m_kdf->rounds())); m_ui->dbNameEdit->setFocus(); } @@ -191,13 +183,12 @@ void DatabaseSettingsWidget::reject() void DatabaseSettingsWidget::transformRoundsBenchmark() { - if (m_benchmarkField == nullptr) { - Q_ASSERT(false); - return; - } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - m_benchmarkField->setValue(m_kdf->benchmark(1000)); - QApplication::restoreOverrideCursor(); + m_ui->transformRoundsSpinBox->setValue(AsyncTask::runAndWaitForFuture([this]() { + int rounds = m_kdf->benchmark(1000); + QApplication::restoreOverrideCursor(); + return rounds; + })); } void DatabaseSettingsWidget::truncateHistories() diff --git a/src/gui/DatabaseSettingsWidget.h b/src/gui/DatabaseSettingsWidget.h index a6001b892..fe8e25c6b 100644 --- a/src/gui/DatabaseSettingsWidget.h +++ b/src/gui/DatabaseSettingsWidget.h @@ -28,13 +28,14 @@ class Database; -namespace Ui { - class DatabaseSettingsWidget; +namespace Ui +{ +class DatabaseSettingsWidget; } -class DatabaseSettingsWidget : public DialogyWidget +class DatabaseSettingsWidget: public DialogyWidget { - Q_OBJECT +Q_OBJECT public: explicit DatabaseSettingsWidget(QWidget* parent = nullptr); @@ -58,8 +59,6 @@ private: const QScopedPointer m_ui; QList m_kdfWidgets; - QList> m_kdfFields; - QSpinBox* m_benchmarkField; QScopedPointer m_kdf; Database* m_db; From 15648991fc0636320804804000066a27973fb7e5 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 16 Dec 2017 18:36:42 +0100 Subject: [PATCH 14/39] Refactor Kdf class, remove fields concept --- src/core/Database.cpp | 27 +-- src/core/Database.h | 12 +- src/crypto/kdf/AesKdf.cpp | 49 +---- src/crypto/kdf/AesKdf.h | 24 +-- src/crypto/kdf/Kdf.cpp | 34 +--- src/crypto/kdf/Kdf.h | 37 +--- src/format/Kdbx3Reader.cpp | 41 ++-- src/format/Kdbx3Writer.cpp | 2 +- src/format/KeePass1Reader.cpp | 2 +- src/format/KeePass2.cpp | 52 ++--- src/format/KeePass2.h | 19 +- src/gui/DatabaseSettingsWidget.cpp | 129 +++--------- src/gui/DatabaseSettingsWidget.h | 8 +- src/gui/DatabaseSettingsWidget.ui | 307 +++++++++++++++++------------ src/keys/CompositeKey.cpp | 8 - tests/TestKeePass1Reader.cpp | 3 +- tests/gui/TestGui.cpp | 2 +- 17 files changed, 282 insertions(+), 474 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index ae22603f3..3aeaa3d26 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -33,7 +33,6 @@ #include "format/KeePass2Writer.h" #include "keys/PasswordKey.h" #include "keys/FileKey.h" -#include "keys/CompositeKey.h" QHash Database::m_uuidMap; @@ -45,7 +44,7 @@ Database::Database() { m_data.cipher = KeePass2::CIPHER_AES; m_data.compressionAlgo = CompressionGZip; - m_data.kdf = new AesKdf(); + m_data.kdf = QSharedPointer::create(); m_data.kdf->randomizeTransformSalt(); m_data.hasKey = false; @@ -485,23 +484,18 @@ QString Database::saveToFile(QString filePath) } } -Kdf* Database::kdf() const { +QSharedPointer Database::kdf() const +{ return m_data.kdf; } -void Database::setKdf(Kdf* kdf) { - if (m_data.kdf == kdf) { - return; - } - - if (m_data.kdf != nullptr) { - delete m_data.kdf; - } - - m_data.kdf = kdf; +void Database::setKdf(QSharedPointer kdf) +{ + m_data.kdf = std::move(kdf); } -bool Database::changeKdf(Kdf* kdf) { +bool Database::changeKdf(QSharedPointer kdf) +{ kdf->randomizeTransformSalt(); QByteArray transformedMasterKey; if (!m_data.key.transform(*kdf, transformedMasterKey)) { @@ -514,8 +508,3 @@ bool Database::changeKdf(Kdf* kdf) { return true; } - -Database::DatabaseData::~DatabaseData() -{ - delete kdf; -} diff --git a/src/core/Database.h b/src/core/Database.h index 44854b854..c199069d3 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -58,17 +58,15 @@ public: Uuid cipher; CompressionAlgorithm compressionAlgo; QByteArray transformedMasterKey; - Kdf* kdf; + QSharedPointer kdf; CompositeKey key; bool hasKey; QByteArray masterSeed; QByteArray challengeResponseKey; - - ~DatabaseData(); }; Database(); - ~Database(); + ~Database() override; Group* rootGroup(); const Group* rootGroup() const; @@ -92,7 +90,7 @@ public: Uuid cipher() const; Database::CompressionAlgorithm compressionAlgo() const; - Kdf* kdf() const; + QSharedPointer kdf() const; QByteArray transformedMasterKey() const; const CompositeKey& key() const; QByteArray challengeResponseKey() const; @@ -100,7 +98,7 @@ public: void setCipher(const Uuid& cipher); void setCompressionAlgo(Database::CompressionAlgorithm algo); - void setKdf(Kdf* kdf); + void setKdf(QSharedPointer kdf); bool setKey(const CompositeKey& key, bool updateChangedTime = true, bool updateTransformSalt = false); bool hasKey() const; @@ -117,7 +115,7 @@ public: * Returns a unique id that is only valid as long as the Database exists. */ Uuid uuid(); - bool changeKdf(Kdf* kdf); + bool changeKdf(QSharedPointer kdf); static Database* databaseByUuid(const Uuid& uuid); static Database* openDatabaseFile(QString fileName, CompositeKey key); diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp index a4dd75650..8d3801a5f 100644 --- a/src/crypto/kdf/AesKdf.cpp +++ b/src/crypto/kdf/AesKdf.cpp @@ -18,20 +18,10 @@ #include #include "format/KeePass2.h" -#include "crypto/SymmetricCipher.h" #include "crypto/CryptoHash.h" #include "crypto/Random.h" #include "AesKdf.h" -const QList AesKdf::FIELDS = AesKdf::initFields(); - -QList AesKdf::initFields() -{ - return QList { - Kdf::Field(static_cast(Fields::ROUNDS), "Transform rounds", 1, UINT64_MAX, true), - }; -} - bool AesKdf::transform(const QByteArray& raw, QByteArray& result) const { QByteArray resultLeft; @@ -76,7 +66,8 @@ bool AesKdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, quin } AesKdf::AesKdf() - : m_rounds(100000ull) + : Kdf::Kdf(KeePass2::KDF_AES) + , m_rounds(100000ull) , m_seed(QByteArray(32, 0)) { } @@ -112,39 +103,9 @@ void AesKdf::randomizeTransformSalt() setSeed(randomGen()->randomArray(32)); } -Kdf::Type AesKdf::type() const +QSharedPointer AesKdf::clone() const { - return Kdf::Type::AES; -} - -Kdf* AesKdf::clone() const -{ - return static_cast(new AesKdf(*this)); -} - -const QList AesKdf::fields() const -{ - return FIELDS; -} - -quint64 AesKdf::field(quint32 id) const -{ - switch (static_cast(id)) { - case Fields::ROUNDS: - return m_rounds; - default: - return 0; - } -} - -bool AesKdf::setField(quint32 id, quint64 val) -{ - switch (static_cast(id)) { - case Fields::ROUNDS: - return setRounds(val); - default: - return false; - } + return QSharedPointer::create(*this); } int AesKdf::benchmarkImpl(int msec) const @@ -170,4 +131,4 @@ int AesKdf::benchmarkImpl(int msec) const while (!t.hasExpired(msec)); return rounds; -} \ No newline at end of file +} diff --git a/src/crypto/kdf/AesKdf.h b/src/crypto/kdf/AesKdf.h index 660c022ed..28206a8d6 100644 --- a/src/crypto/kdf/AesKdf.h +++ b/src/crypto/kdf/AesKdf.h @@ -27,26 +27,13 @@ public: bool transform(const QByteArray& raw, QByteArray& result) const override; void randomizeTransformSalt() override; - Kdf::Type type() const override; - Kdf* clone() const override; + QSharedPointer clone() const override; - const QList fields() const override; - quint64 field(quint32 id) const override; - bool setField(quint32 id, quint64 val) override; + quint64 rounds() const override; + QByteArray seed() const override; - quint64 rounds() const; - QByteArray seed() const; - - bool setRounds(quint64 rounds); - bool setSeed(const QByteArray& seed); - - enum class Fields: quint32 - { - ROUNDS, - SEED - }; - - static const QList FIELDS; + bool setRounds(quint64 rounds) override; + bool setSeed(const QByteArray& seed) override; protected: int benchmarkImpl(int msec) const override; @@ -59,7 +46,6 @@ private: const QByteArray& seed, quint64 rounds, QByteArray* result) Q_REQUIRED_RESULT; - static QList initFields(); }; #endif // KEEPASSX_AESKDF_H diff --git a/src/crypto/kdf/Kdf.cpp b/src/crypto/kdf/Kdf.cpp index c1bfdf7e1..6829b650a 100644 --- a/src/crypto/kdf/Kdf.cpp +++ b/src/crypto/kdf/Kdf.cpp @@ -18,42 +18,16 @@ #include "Kdf.h" #include "Kdf_p.h" -#include -#include #include -Kdf::Field::Field(quint32 id, const QString& name, quint64 min, quint64 max, bool benchmark) - : m_id(id) - , m_name(name) - , m_min(min) - , m_max(max) - , m_benchmark(benchmark) +Kdf::Kdf(Uuid uuid) + : m_uuid(uuid) { } -quint32 Kdf::Field::id() const +Uuid Kdf::uuid() const { - return m_id; -} - -QString Kdf::Field::name() const -{ - return m_name; -} - -quint64 Kdf::Field::min() const -{ - return m_min; -} - -quint64 Kdf::Field::max() const -{ - return m_max; -} - -bool Kdf::Field::benchmarked() const -{ - return m_benchmark; + return m_uuid; } int Kdf::benchmark(int msec) const diff --git a/src/crypto/kdf/Kdf.h b/src/crypto/kdf/Kdf.h index 14ce24501..ba171dcba 100644 --- a/src/crypto/kdf/Kdf.h +++ b/src/crypto/kdf/Kdf.h @@ -25,41 +25,19 @@ class Kdf { public: - enum class Type - { - AES - }; + explicit Kdf(Uuid uuid); + virtual ~Kdf() = default; - class Field - { - public: - Field(quint32 id, const QString& name, quint64 min, quint64 max, bool benchmark = false); - - quint32 id() const; - QString name() const; - quint64 min() const; - quint64 max() const; - bool benchmarked() const; - - private: - quint32 m_id; - QString m_name; - quint64 m_min; - quint64 m_max; - bool m_benchmark; - }; - - virtual ~Kdf() {} + Uuid uuid() const; + virtual quint64 rounds() const = 0; + virtual bool setRounds(quint64 rounds) = 0; virtual QByteArray seed() const = 0; - virtual Type type() const = 0; + virtual bool setSeed(const QByteArray& seed) = 0; virtual bool transform(const QByteArray& raw, QByteArray& result) const = 0; virtual void randomizeTransformSalt() = 0; - virtual Kdf* clone() const = 0; + virtual QSharedPointer clone() const = 0; - virtual const QList fields() const = 0; - virtual quint64 field(quint32 id) const = 0; - virtual bool setField(quint32 id, quint64 val) = 0; virtual int benchmark(int msec) const; protected: @@ -67,6 +45,7 @@ protected: private: class BenchmarkThread; + const Uuid m_uuid; }; diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index 0ea6f817e..3c3529f95 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -318,36 +318,33 @@ void Kdbx3Reader::setTransformSeed(const QByteArray& data) { if (data.size() != 32) { raiseError("Invalid transform seed size"); - } else { - AesKdf* aesKdf; - if (m_db->kdf()->type() == Kdf::Type::AES) { - aesKdf = static_cast(m_db->kdf()); - } else { - aesKdf = new AesKdf(); - m_db->setKdf(aesKdf); - } - - aesKdf->setSeed(data); + return; } + + auto kdf = m_db->kdf(); + if (!kdf) { + kdf = QSharedPointer::create(); + m_db->setKdf(kdf); + } + + kdf->setSeed(data); } void Kdbx3Reader::setTransformRounds(const QByteArray& data) { if (data.size() != 8) { raiseError("Invalid transform rounds size"); - } else { - quint64 rounds = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); - - AesKdf* aesKdf; - if (m_db->kdf()->type() == Kdf::Type::AES) { - aesKdf = static_cast(m_db->kdf()); - } else { - aesKdf = new AesKdf(); - m_db->setKdf(aesKdf); - } - - aesKdf->setRounds(rounds); + return; } + + auto rounds = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); + auto kdf = m_db->kdf(); + if (!kdf) { + kdf = QSharedPointer::create(); + m_db->setKdf(kdf); + } + + kdf->setRounds(rounds); } void Kdbx3Reader::setEncryptionIV(const QByteArray& data) diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp index 990aa71ee..770e7270e 100644 --- a/src/format/Kdbx3Writer.cpp +++ b/src/format/Kdbx3Writer.cpp @@ -80,7 +80,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CompressionFlags, Endian::sizedIntToBytes(db->compressionAlgo(), KeePass2::BYTEORDER))); - AesKdf* kdf = static_cast(db->kdf()); + auto kdf = db->kdf(); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::MasterSeed, masterSeed)); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformSeed, kdf->seed())); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformRounds, diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp index ccb3d1ad1..c6e2ed03c 100644 --- a/src/format/KeePass1Reader.cpp +++ b/src/format/KeePass1Reader.cpp @@ -160,7 +160,7 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor raiseError("Invalid number of transform rounds"); return nullptr; } - AesKdf* kdf = new AesKdf(); + auto kdf = QSharedPointer::create(); kdf->setRounds(m_transformRounds); kdf->setSeed(m_transformSeed); db->setKdf(kdf); diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index e79dff513..61bd383df 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -16,9 +16,9 @@ */ #include "KeePass2.h" -#include "crypto/CryptoHash.h" #include "crypto/kdf/AesKdf.h" -#include "core/Uuid.h" +#include + const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff")); const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); @@ -28,31 +28,23 @@ const Uuid KeePass2::KDF_AES = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D0 const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); -const QList KeePass2::CIPHERS { - KeePass2::UuidNamePair(KeePass2::CIPHER_AES, "AES: 256-bit"), - KeePass2::UuidNamePair(KeePass2::CIPHER_TWOFISH, "Twofish: 256-bit"), - KeePass2::UuidNamePair(KeePass2::CIPHER_CHACHA20, "ChaCha20: 256-bit") +const QList> KeePass2::CIPHERS { + qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")), + qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")), + qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit")) }; -const QList KeePass2::KDFS { - KeePass2::UuidNamePair(KeePass2::KDF_AES, "AES-KDF"), +const QList> KeePass2::KDFS { + qMakePair(KeePass2::KDF_AES, QObject::tr("AES-KDF")), }; -Kdf* KeePass2::uuidToKdf(const Uuid& uuid) { - if (uuid == KDF_AES) { - return static_cast(new AesKdf()); - } - - return nullptr; -} - -Uuid KeePass2::kdfToUuid(const Kdf& kdf) +QSharedPointer KeePass2::uuidToKdf(const Uuid& uuid) { - switch (kdf.type()) { - case Kdf::Type::AES: - return KDF_AES; - default: - return Uuid(); + if (uuid == KDF_AES) { + return QSharedPointer::create(); } + + Q_ASSERT_X(false, "uuidToKdf", "Invalid UUID"); + return nullptr; } KeePass2::ProtectedStreamAlgo KeePass2::idToProtectedStreamAlgo(quint32 id) @@ -68,19 +60,3 @@ KeePass2::ProtectedStreamAlgo KeePass2::idToProtectedStreamAlgo(quint32 id) return KeePass2::InvalidProtectedStreamAlgo; } } - -KeePass2::UuidNamePair::UuidNamePair(const Uuid& uuid, const QString& name) - : m_uuid(uuid) - , m_name(name) -{ -} - -Uuid KeePass2::UuidNamePair::uuid() const -{ - return m_uuid; -} - -QString KeePass2::UuidNamePair::name() const -{ - return m_name; -} diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index 6356e15da..c7945e907 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -43,20 +43,8 @@ namespace KeePass2 extern const QByteArray INNER_STREAM_SALSA20_IV; - class UuidNamePair - { - public: - UuidNamePair(const Uuid& uuid, const QString& name); - Uuid uuid() const; - QString name() const; - - private: - Uuid m_uuid; - QString m_name; - }; - - extern const QList CIPHERS; - extern const QList KDFS; + extern const QList> CIPHERS; + extern const QList> KDFS; enum HeaderFieldID { @@ -81,8 +69,7 @@ namespace KeePass2 InvalidProtectedStreamAlgo = -1 }; - Kdf* uuidToKdf(const Uuid& uuid); - Uuid kdfToUuid(const Kdf& kdf); + QSharedPointer uuidToKdf(const Uuid& uuid); ProtectedStreamAlgo idToProtectedStreamAlgo(quint32 id); } diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index ce1fb8dbb..51c0f4183 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -20,6 +20,7 @@ #include +#include "core/Global.h" #include "core/AsyncTask.h" #include "core/Database.h" #include "core/Group.h" @@ -40,7 +41,6 @@ DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) m_ui->historyMaxItemsSpinBox, SLOT(setEnabled(bool))); connect(m_ui->historyMaxSizeCheckBox, SIGNAL(toggled(bool)), m_ui->historyMaxSizeSpinBox, SLOT(setEnabled(bool))); - connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(changeKdf(int))); connect(m_ui->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark())); } @@ -61,8 +61,7 @@ void DatabaseSettingsWidget::load(Database* db) if (meta->historyMaxItems() > -1) { m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems()); m_ui->historyMaxItemsCheckBox->setChecked(true); - } - else { + } else { m_ui->historyMaxItemsSpinBox->setValue(Metadata::DefaultHistoryMaxItems); m_ui->historyMaxItemsCheckBox->setChecked(false); } @@ -70,17 +69,14 @@ void DatabaseSettingsWidget::load(Database* db) if (historyMaxSizeMiB > 0) { m_ui->historyMaxSizeSpinBox->setValue(historyMaxSizeMiB); m_ui->historyMaxSizeCheckBox->setChecked(true); - } - else { + } else { m_ui->historyMaxSizeSpinBox->setValue(Metadata::DefaultHistoryMaxSize); m_ui->historyMaxSizeCheckBox->setChecked(false); } m_ui->algorithmComboBox->clear(); - for (QList::const_iterator ciphers = KeePass2::CIPHERS.constBegin(); - ciphers != KeePass2::CIPHERS.constEnd(); ++ciphers) { - KeePass2::UuidNamePair cipher = *ciphers; - m_ui->algorithmComboBox->addItem(cipher.name(), cipher.uuid().toByteArray()); + for (auto& cipher: asConst(KeePass2::CIPHERS)) { + m_ui->algorithmComboBox->addItem(cipher.second, cipher.first.toByteArray()); } int cipherIndex = m_ui->algorithmComboBox->findData(m_db->cipher().toByteArray()); if (cipherIndex > -1) { @@ -89,20 +85,17 @@ void DatabaseSettingsWidget::load(Database* db) bool blockSignals = m_ui->kdfComboBox->signalsBlocked(); m_ui->kdfComboBox->blockSignals(true); - m_kdf.reset(m_db->kdf()->clone()); + m_ui->kdfComboBox->clear(); - for (QList::const_iterator kdfs = KeePass2::KDFS.constBegin(); - kdfs != KeePass2::KDFS.constEnd(); ++kdfs) { - KeePass2::UuidNamePair kdf = *kdfs; - m_ui->kdfComboBox->addItem(kdf.name(), kdf.uuid().toByteArray()); + for (auto& kdf: asConst(KeePass2::KDFS)) { + m_ui->kdfComboBox->addItem(kdf.second, kdf.first.toByteArray()); } - int kdfIndex = m_ui->kdfComboBox->findData(KeePass2::kdfToUuid(*m_kdf).toByteArray()); + int kdfIndex = m_ui->kdfComboBox->findData(m_db->kdf()->uuid().toByteArray()); if (kdfIndex > -1) { m_ui->kdfComboBox->setCurrentIndex(kdfIndex); } - displayKdf(*m_kdf); m_ui->kdfComboBox->blockSignals(blockSignals); - m_ui->transformRoundsSpinBox->setValue(static_cast(m_kdf->rounds())); + m_ui->transformRoundsSpinBox->setValue(static_cast(m_db->kdf()->rounds())); m_ui->dbNameEdit->setFocus(); } @@ -121,8 +114,7 @@ void DatabaseSettingsWidget::save() int historyMaxItems; if (m_ui->historyMaxItemsCheckBox->isChecked()) { historyMaxItems = m_ui->historyMaxItemsSpinBox->value(); - } - else { + } else { historyMaxItems = -1; } if (historyMaxItems != meta->historyMaxItems()) { @@ -133,8 +125,7 @@ void DatabaseSettingsWidget::save() int historyMaxSize; if (m_ui->historyMaxSizeCheckBox->isChecked()) { historyMaxSize = m_ui->historyMaxSizeSpinBox->value() * 1048576; - } - else { + } else { historyMaxSize = -1; } if (historyMaxSize != meta->historyMaxSize()) { @@ -148,31 +139,20 @@ void DatabaseSettingsWidget::save() m_db->setCipher(Uuid(m_ui->algorithmComboBox->currentData().toByteArray())); - bool kdfValid = true; - for (int i = 0; i < m_kdfFields.size(); ++i) { - QPair field = m_kdfFields.at(i); - kdfValid &= m_kdf->setField(field.first, static_cast(qMax(0, field.second->value()))); - if (!kdfValid) { - break; - } - } + auto kdf = KeePass2::uuidToKdf(Uuid(m_ui->kdfComboBox->currentData().toByteArray())); + kdf->setRounds(static_cast(qMax(0, m_ui->transformRoundsSpinBox->value()))); - if (kdfValid) { - Kdf* kdf = m_kdf.take(); - bool ok = m_db->changeKdf(kdf); - if (!ok) { - MessageBox::warning(this, tr("KDF unchanged"), - tr("Failed to transform key with new KDF parameters; KDF unchanged."), - QMessageBox::Ok); - delete kdf; // m_db has not taken ownership - } - } else { + QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); + // TODO: we should probably use AsyncTask::runAndWaitForFuture() here, + // but not without making Database thread-safe + bool ok = m_db->changeKdf(kdf); + QApplication::restoreOverrideCursor(); + + if (!ok) { MessageBox::warning(this, tr("KDF unchanged"), - tr("Invalid KDF parameters; KDF unchanged."), + tr("Failed to transform key with new KDF parameters; KDF unchanged."), QMessageBox::Ok); } - clearKdfWidgets(); - emit editFinished(true); } @@ -185,7 +165,7 @@ void DatabaseSettingsWidget::transformRoundsBenchmark() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); m_ui->transformRoundsSpinBox->setValue(AsyncTask::runAndWaitForFuture([this]() { - int rounds = m_kdf->benchmark(1000); + int rounds = m_db->kdf()->benchmark(1000); QApplication::restoreOverrideCursor(); return rounds; })); @@ -198,66 +178,3 @@ void DatabaseSettingsWidget::truncateHistories() entry->truncateHistory(); } } - -void DatabaseSettingsWidget::changeKdf(int index) { - QByteArray uuidBytes = m_ui->kdfComboBox->itemData(index).toByteArray(); - if (uuidBytes.size() != Uuid::Length) { - return; - } - Kdf* newKdf = KeePass2::uuidToKdf(Uuid(uuidBytes)); - if (newKdf != nullptr) { - m_kdf.reset(newKdf); - displayKdf(*m_kdf); - } -} - -void DatabaseSettingsWidget::displayKdf(const Kdf& kdf) -{ - clearKdfWidgets(); - - QWidget* lastWidget = nullptr; - int columnStart = m_ui->gridLayout->columnCount(); - int rowStart = m_ui->gridLayout->rowCount(); - QList fields = kdf.fields(); - for (int i = 0; i < fields.size(); i++) { - const Kdf::Field& field = fields.at(i); - QLabel* label = new QLabel(QString("%1:").arg(field.name())); - QSpinBox* spinBox = new QSpinBox(); - m_kdfWidgets.append(label); - m_kdfWidgets.append(spinBox); - m_kdfFields.append(QPair(field.id(), spinBox)); - spinBox->setMinimum(static_cast(qMin(qMax(0ull, field.min()), 0x7FFFFFFFull))); - spinBox->setMaximum(static_cast(qMin(qMax(0ull, field.max()), 0x7FFFFFFFull))); - spinBox->setValue(static_cast(qMin(qMax(0ull, kdf.field(field.id())), 0x7FFFFFFFull))); - spinBox->setObjectName(QString("kdfParams%1").arg(i)); - m_ui->gridLayout->addWidget(label, rowStart + i, columnStart - 3, Qt::AlignRight); - if (field.benchmarked()) { - Q_ASSERT(m_benchmarkField == nullptr); - QPushButton* benchBtn = new QPushButton("Benchmark"); - connect(benchBtn, &QPushButton::clicked, this, &DatabaseSettingsWidget::transformRoundsBenchmark); - m_kdfWidgets.append(benchBtn); - m_ui->gridLayout->addWidget(spinBox, rowStart + i, columnStart - 2); - m_ui->gridLayout->addWidget(benchBtn, rowStart + i, columnStart - 1); - m_benchmarkField = spinBox; - lastWidget = benchBtn; - } else { - m_ui->gridLayout->addWidget(spinBox, rowStart + i, columnStart - 2, 1, 2); - lastWidget = spinBox; - } - } - if (lastWidget != nullptr) { - QWidget::setTabOrder(lastWidget, m_ui->buttonBox->button(QDialogButtonBox::StandardButton::Cancel)); - QWidget::setTabOrder(m_ui->buttonBox->button(QDialogButtonBox::StandardButton::Cancel), m_ui->buttonBox->button(QDialogButtonBox::StandardButton::Ok)); - } -} - -void DatabaseSettingsWidget::clearKdfWidgets() -{ - m_benchmarkField = nullptr; - for (int i = 0; i < m_kdfWidgets.size(); ++i) { - m_ui->gridLayout->removeWidget(m_kdfWidgets.at(i)); - m_kdfWidgets.at(i)->deleteLater(); - } - m_kdfWidgets.clear(); - m_kdfFields.clear(); -} diff --git a/src/gui/DatabaseSettingsWidget.h b/src/gui/DatabaseSettingsWidget.h index fe8e25c6b..ec3d6415a 100644 --- a/src/gui/DatabaseSettingsWidget.h +++ b/src/gui/DatabaseSettingsWidget.h @@ -40,6 +40,7 @@ Q_OBJECT public: explicit DatabaseSettingsWidget(QWidget* parent = nullptr); ~DatabaseSettingsWidget(); + Q_DISABLE_COPY(DatabaseSettingsWidget) void load(Database* db); @@ -50,19 +51,12 @@ private slots: void save(); void reject(); void transformRoundsBenchmark(); - void changeKdf(int index); private: void truncateHistories(); - void displayKdf(const Kdf& kdf); - void clearKdfWidgets(); const QScopedPointer m_ui; - QList m_kdfWidgets; - QScopedPointer m_kdf; Database* m_db; - - Q_DISABLE_COPY(DatabaseSettingsWidget) }; #endif // KEEPASSX_DATABASESETTINGSWIDGET_H diff --git a/src/gui/DatabaseSettingsWidget.ui b/src/gui/DatabaseSettingsWidget.ui index 701df42a0..8aaab19e7 100644 --- a/src/gui/DatabaseSettingsWidget.ui +++ b/src/gui/DatabaseSettingsWidget.ui @@ -2,17 +2,9 @@ DatabaseSettingsWidget - - - 0 - 0 - 600 - 340 - - - + Qt::Vertical @@ -25,9 +17,9 @@ - + - + Qt::Horizontal @@ -40,118 +32,185 @@ - - - - - - - - Database name: - - - - - - - Max. history size: - - - - - - - Key derivation function: - - - - - - - Max. history items: - - - - - - - - - - - 0 - 0 - - - - 2000000000 - - - - - - - Default username: - - - - - - - - - - - 0 - 0 - - - - MiB - - - 1 - - - 2000000000 - - - - - - - Use recycle bin - - - - - - - true - - - - - - - Database description: - - - - - - - - - - Algorithm: - - - - + + + + 800 + 16777215 + + + + + + + + + + 0 + 0 + + + + 1 + + + 1000000000 + + + + + + + + 0 + 0 + + + + Benchmark + + + + + + + + + Database name: + + + + + + + Max. history size: + + + + + + + Transform rounds: + + + + + + + Max. history items: + + + + + + + + + + + + + 0 + 0 + + + + 2000000000 + + + + + + + + + Default username: + + + + + + + + + + + + + 0 + 0 + + + + MiB + + + 1 + + + 2000000000 + + + + + + + + + Use recycle bin + + + + + + + true + + + + + + + Database description: + + + + + + + + AES: 256 Bit (default) + + + + + Twofish: 256 Bit + + + + + + + + Encryption Algorithm: + + + + + + + + + + Key Derivation Function + + + + + - + Qt::Horizontal @@ -166,7 +225,7 @@ - + Qt::Vertical @@ -190,14 +249,14 @@ dbNameEdit dbDescriptionEdit + transformRoundsSpinBox + transformBenchmarkButton defaultUsernameEdit recycleBinEnabledCheckBox historyMaxItemsCheckBox historyMaxItemsSpinBox historyMaxSizeCheckBox historyMaxSizeSpinBox - algorithmComboBox - kdfComboBox buttonBox diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 88ac4cf6a..8f6917afc 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -17,19 +17,11 @@ */ #include "CompositeKey.h" -#include "ChallengeResponseKey.h" - -#include #include #include -#include "crypto/kdf/Kdf.h" -#include "format/KeePass2.h" #include "core/Global.h" #include "crypto/CryptoHash.h" -#include "crypto/SymmetricCipher.h" -#include "keys/FileKey.h" -#include "keys/PasswordKey.h" CompositeKey::CompositeKey() { diff --git a/tests/TestKeePass1Reader.cpp b/tests/TestKeePass1Reader.cpp index 7e015c2bc..c372c8715 100644 --- a/tests/TestKeePass1Reader.cpp +++ b/tests/TestKeePass1Reader.cpp @@ -26,7 +26,6 @@ #include "core/Group.h" #include "core/Metadata.h" #include "crypto/Crypto.h" -#include "crypto/kdf/AesKdf.h" #include "format/KeePass1Reader.h" #include "format/KeePass2Reader.h" #include "format/KeePass2Writer.h" @@ -111,7 +110,7 @@ void TestKeePass1Reader::testBasic() void TestKeePass1Reader::testMasterKey() { QVERIFY(m_db->hasKey()); - QCOMPARE(static_cast(m_db->kdf())->rounds(), static_cast(713)); + QCOMPARE(m_db->kdf()->rounds(), static_cast(713)); } void TestKeePass1Reader::testCustomIcons() diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 92b7a6a5f..9f1e7abc2 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -903,7 +903,7 @@ void TestGui::testDatabaseSettings() QTest::keyClick(transformRoundsSpinBox, Qt::Key_Enter); // wait for modified timer QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save*")); - QCOMPARE(static_cast(m_db->kdf())->rounds(), Q_UINT64_C(100)); + QCOMPARE(m_db->kdf()->rounds(), Q_UINT64_C(100)); triggerAction("actionDatabaseSave"); QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save")); From 4592de8fb6c6bc7ff722ae3a78d821acec6ebaa9 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 17 Dec 2017 19:17:22 +0100 Subject: [PATCH 15/39] Make benchmark() method non-virtual --- src/crypto/kdf/Kdf.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/kdf/Kdf.h b/src/crypto/kdf/Kdf.h index ba171dcba..5330e71d0 100644 --- a/src/crypto/kdf/Kdf.h +++ b/src/crypto/kdf/Kdf.h @@ -38,7 +38,7 @@ public: virtual void randomizeTransformSalt() = 0; virtual QSharedPointer clone() const = 0; - virtual int benchmark(int msec) const; + int benchmark(int msec) const; protected: virtual int benchmarkImpl(int msec) const = 0; From 61c4fe899242d487549d482c6154d327c1656c26 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Wed, 20 Dec 2017 09:57:17 -0500 Subject: [PATCH 16/39] Formatting and consistency fixes --- src/crypto/CryptoHash.cpp | 2 +- src/crypto/kdf/AesKdf.cpp | 9 ++++----- src/crypto/kdf/Kdf.cpp | 3 ++- src/format/Kdbx3Reader.cpp | 14 ++++---------- src/format/KeePass2Reader.cpp | 25 +++++++++---------------- src/format/KeePass2Reader.h | 19 +++++++++---------- 6 files changed, 29 insertions(+), 43 deletions(-) diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp index 8d5b56e76..70633da15 100644 --- a/src/crypto/CryptoHash.cpp +++ b/src/crypto/CryptoHash.cpp @@ -57,7 +57,7 @@ CryptoHash::CryptoHash(Algorithm algo, bool hmac) } gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, flagsGcrypt); - if (error) { + if (error != GPG_ERR_NO_ERROR) { qWarning("Gcrypt error (ctor): %s", gcry_strerror(error)); qWarning("Gcrypt error (ctor): %s", gcry_strsource(error)); } diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp index 8d3801a5f..7056698d7 100644 --- a/src/crypto/kdf/AesKdf.cpp +++ b/src/crypto/kdf/AesKdf.cpp @@ -114,13 +114,12 @@ int AesKdf::benchmarkImpl(int msec) const QByteArray seed = QByteArray(32, '\x4B'); QByteArray iv(16, 0); - SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, - SymmetricCipher::Encrypt); + SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); cipher.init(seed, iv); int rounds = 0; - QElapsedTimer t; - t.start(); + QElapsedTimer timer; + timer.start(); do { if (!cipher.processInPlace(key, 10000)) { rounds = -1; @@ -128,7 +127,7 @@ int AesKdf::benchmarkImpl(int msec) const } rounds += 10000; } - while (!t.hasExpired(msec)); + while (!timer.hasExpired(msec)); return rounds; } diff --git a/src/crypto/kdf/Kdf.cpp b/src/crypto/kdf/Kdf.cpp index 6829b650a..cfc3025c1 100644 --- a/src/crypto/kdf/Kdf.cpp +++ b/src/crypto/kdf/Kdf.cpp @@ -45,7 +45,8 @@ int Kdf::benchmark(int msec) const } Kdf::BenchmarkThread::BenchmarkThread(int msec, const Kdf* kdf) - : m_msec(msec), m_kdf(kdf) + : m_msec(msec) + , m_kdf(kdf) { } diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index 3c3529f95..b5e5e2df8 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -322,12 +322,9 @@ void Kdbx3Reader::setTransformSeed(const QByteArray& data) } auto kdf = m_db->kdf(); - if (!kdf) { - kdf = QSharedPointer::create(); - m_db->setKdf(kdf); + if (!kdf.isNull()) { + kdf->setSeed(data); } - - kdf->setSeed(data); } void Kdbx3Reader::setTransformRounds(const QByteArray& data) @@ -339,12 +336,9 @@ void Kdbx3Reader::setTransformRounds(const QByteArray& data) auto rounds = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); auto kdf = m_db->kdf(); - if (!kdf) { - kdf = QSharedPointer::create(); - m_db->setKdf(kdf); + if (!kdf.isNull()) { + kdf->setRounds(rounds); } - - kdf->setRounds(rounds); } void Kdbx3Reader::setEncryptionIV(const QByteArray& data) diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 5d62794d4..daa6c9aa8 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -26,18 +26,16 @@ #include "format/KeePass2.h" #include "format/Kdbx3Reader.h" -BaseKeePass2Reader::BaseKeePass2Reader() : - m_error(false), - m_saveXml(false), - m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo) +BaseKeePass2Reader::BaseKeePass2Reader() + : m_error(false) + , m_saveXml(false) + , m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo) { m_errorStr.clear(); m_xmlData.clear(); m_protectedStreamKey.clear(); } -BaseKeePass2Reader::~BaseKeePass2Reader() {} - Database* BaseKeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key) { QFile file(filename); @@ -134,35 +132,30 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke bool KeePass2Reader::hasError() { - return m_error || (m_reader && m_reader->hasError()); + return m_error || (!m_reader.isNull() && m_reader->hasError()); } QString KeePass2Reader::errorString() { - return m_reader ? m_reader->errorString() : m_errorStr; + return !m_reader.isNull() ? m_reader->errorString() : m_errorStr; } QByteArray KeePass2Reader::xmlData() { - return m_reader ? m_reader->xmlData() : m_xmlData; + return !m_reader.isNull() ? m_reader->xmlData() : m_xmlData; } QByteArray KeePass2Reader::streamKey() { - return m_reader ? m_reader->streamKey() : m_protectedStreamKey; + return !m_reader.isNull() ? m_reader->streamKey() : m_protectedStreamKey; } KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const { - return m_reader ? m_reader->protectedStreamAlgo() : m_irsAlgo; + return !m_reader.isNull() ? m_reader->protectedStreamAlgo() : m_irsAlgo; } quint32 KeePass2Reader::version() const { return m_version; } - -BaseKeePass2Reader* KeePass2Reader::reader() const -{ - return m_reader.data(); -} diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h index 21c317023..93348f565 100644 --- a/src/format/KeePass2Reader.h +++ b/src/format/KeePass2Reader.h @@ -31,6 +31,8 @@ class BaseKeePass2Reader { + Q_DECLARE_TR_FUNCTIONS(BaseKeePass2Reader) + public: BaseKeePass2Reader(); @@ -44,7 +46,7 @@ public: virtual QByteArray streamKey(); virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const; - virtual ~BaseKeePass2Reader(); + virtual ~BaseKeePass2Reader() = default; protected: void raiseError(const QString& errorMessage); @@ -60,20 +62,17 @@ protected: class KeePass2Reader : public BaseKeePass2Reader { - Q_DECLARE_TR_FUNCTIONS(KeePass2Reader) - public: - virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override; + Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override; using BaseKeePass2Reader::readDatabase; - virtual bool hasError() override; - virtual QString errorString() override; - virtual QByteArray xmlData() override; - virtual QByteArray streamKey() override; - virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const override; + bool hasError() override; + QString errorString() override; + QByteArray xmlData() override; + QByteArray streamKey() override; + KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const override; quint32 version() const; - BaseKeePass2Reader* reader() const; private: QScopedPointer m_reader; quint32 m_version; From 2866bc626a02f7d0f2943dad3725200f318850cf Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Wed, 20 Dec 2017 09:58:02 -0500 Subject: [PATCH 17/39] Removed dead code --- src/CMakeLists.txt | 1 - src/core/Database.cpp | 6 ------ src/core/Database.h | 1 - src/core/ToDbExporter.cpp | 39 --------------------------------------- src/core/ToDbExporter.h | 33 --------------------------------- 5 files changed, 80 deletions(-) delete mode 100644 src/core/ToDbExporter.cpp delete mode 100644 src/core/ToDbExporter.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1908cea09..c04d7110f 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -62,7 +62,6 @@ set(keepassx_SOURCES core/ScreenLockListenerPrivate.cpp core/TimeDelta.cpp core/TimeInfo.cpp - core/ToDbExporter.cpp core/Tools.cpp core/Translator.cpp core/Uuid.cpp diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 3aeaa3d26..2f2f2967d 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -374,12 +374,6 @@ void Database::setEmitModified(bool value) m_emitModified = value; } -void Database::copyAttributesFrom(const Database* other) -{ - m_data = other->m_data; - m_data.kdf = m_data.kdf->clone(); - m_metadata->copyAttributesFrom(other->m_metadata); -} Uuid Database::uuid() { diff --git a/src/core/Database.h b/src/core/Database.h index c199069d3..b293c760d 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -107,7 +107,6 @@ public: void recycleGroup(Group* group); void emptyRecycleBin(); void setEmitModified(bool value); - void copyAttributesFrom(const Database* other); void merge(const Database* other); QString saveToFile(QString filePath); diff --git a/src/core/ToDbExporter.cpp b/src/core/ToDbExporter.cpp deleted file mode 100644 index 1f76fb744..000000000 --- a/src/core/ToDbExporter.cpp +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (C) 2014 Felix Geyer - * Copyright (C) 2014 Florian Geyer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "ToDbExporter.h" -#include "core/Database.h" -#include "core/Group.h" -#include "core/Metadata.h" - -Database* ToDbExporter::exportGroup(Group* group) -{ - Database* oldDb = group->database(); - Q_ASSERT(oldDb); - - Database* db = new Database(); - Group* clonedGroup = group->clone(Entry::CloneNewUuid | Entry::CloneIncludeHistory); - clonedGroup->setParent(db->rootGroup()); - - QSet customIcons = group->customIconsRecursive(); - db->metadata()->copyCustomIcons(customIcons, oldDb->metadata()); - - db->copyAttributesFrom(oldDb); - - return db; -} diff --git a/src/core/ToDbExporter.h b/src/core/ToDbExporter.h deleted file mode 100644 index 58c5efeb3..000000000 --- a/src/core/ToDbExporter.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2014 Felix Geyer - * Copyright (C) 2014 Florian Geyer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef KEEPASSX_TODBEXPORTER_H -#define KEEPASSX_TODBEXPORTER_H - -#include "core/Exporter.h" - -class Database; -class Group; - -class ToDbExporter : Exporter -{ -public: - Database* exportGroup(Group* group); -}; - -#endif // KEEPASSX_TODBEXPORTER_H From 9140893cd3e7658cd5ecda2fed4207fda6893f81 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Mon, 1 Jan 2018 13:20:31 -0500 Subject: [PATCH 18/39] Correct Endian function use in Random tests --- tests/CMakeLists.txt | 3 --- tests/TestRandom.cpp | 16 ++++++++-------- 2 files changed, 8 insertions(+), 11 deletions(-) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index bdf0e6f86..91c6b2e2e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -181,9 +181,6 @@ add_unit_test(NAME testrandom SOURCES TestRandom.cpp add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testexporter SOURCES TestExporter.cpp - LIBS ${TEST_LIBRARIES}) - add_unit_test(NAME testcsvexporter SOURCES TestCsvExporter.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/TestRandom.cpp b/tests/TestRandom.cpp index 6c5b1f724..69f0fcb7c 100644 --- a/tests/TestRandom.cpp +++ b/tests/TestRandom.cpp @@ -35,29 +35,29 @@ void TestRandom::testUInt() { QByteArray nextBytes; - nextBytes = Endian::int32ToBytes(42, QSysInfo::ByteOrder); + nextBytes = Endian::sizedIntToBytes(42, QSysInfo::ByteOrder); m_backend->setNextBytes(nextBytes); QCOMPARE(randomGen()->randomUInt(100), 42U); - nextBytes = Endian::int32ToBytes(117, QSysInfo::ByteOrder); + nextBytes = Endian::sizedIntToBytes(117, QSysInfo::ByteOrder); m_backend->setNextBytes(nextBytes); QCOMPARE(randomGen()->randomUInt(100), 17U); - nextBytes = Endian::int32ToBytes(1001, QSysInfo::ByteOrder); + nextBytes = Endian::sizedIntToBytes(1001, QSysInfo::ByteOrder); m_backend->setNextBytes(nextBytes); QCOMPARE(randomGen()->randomUInt(1), 0U); nextBytes.clear(); - nextBytes.append(Endian::int32ToBytes(QUINT32_MAX, QSysInfo::ByteOrder)); - nextBytes.append(Endian::int32ToBytes(QUINT32_MAX - 70000U, QSysInfo::ByteOrder)); + nextBytes.append(Endian::sizedIntToBytes(QUINT32_MAX, QSysInfo::ByteOrder)); + nextBytes.append(Endian::sizedIntToBytes(QUINT32_MAX - 70000U, QSysInfo::ByteOrder)); m_backend->setNextBytes(nextBytes); QCOMPARE(randomGen()->randomUInt(100000U), (QUINT32_MAX - 70000U) % 100000U); nextBytes.clear(); for (int i = 0; i < 10000; i++) { - nextBytes.append(Endian::int32ToBytes((QUINT32_MAX / 2U) + 1U + i, QSysInfo::ByteOrder)); + nextBytes.append(Endian::sizedIntToBytes((QUINT32_MAX / 2U) + 1U + i, QSysInfo::ByteOrder)); } - nextBytes.append(Endian::int32ToBytes(QUINT32_MAX / 2U, QSysInfo::ByteOrder)); + nextBytes.append(Endian::sizedIntToBytes(QUINT32_MAX / 2U, QSysInfo::ByteOrder)); m_backend->setNextBytes(nextBytes); QCOMPARE(randomGen()->randomUInt((QUINT32_MAX / 2U) + 1U), QUINT32_MAX / 2U); } @@ -66,7 +66,7 @@ void TestRandom::testUIntRange() { QByteArray nextBytes; - nextBytes = Endian::int32ToBytes(42, QSysInfo::ByteOrder); + nextBytes = Endian::sizedIntToBytes(42, QSysInfo::ByteOrder); m_backend->setNextBytes(nextBytes); QCOMPARE(randomGen()->randomUIntRange(100, 200), 142U); } From 542ee42313f16d7f6522c746b0403da0369a4e99 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Mon, 1 Jan 2018 13:21:02 -0500 Subject: [PATCH 19/39] Add Argon2Kdf and enable parameters in db settings Note: This implementation is not yet connected to the database itself and will corrupt existing kdbx3 db's. * Implemented memory and parallelism parameters for Argon2Kdf * Using libargon2; libsodium does not support Argon2d algorithm * Moved basic rounds parameter into Kdf class * Reimplemented benchmark algorithm; previous was utterly broken --- CMakeLists.txt | 2 +- INSTALL.md | 1 + cmake/FindArgon2.cmake | 21 +++ src/CMakeLists.txt | 2 + src/cli/CMakeLists.txt | 1 + src/core/Database.cpp | 6 +- src/crypto/kdf/AesKdf.cpp | 65 ++------ src/crypto/kdf/AesKdf.h | 12 +- src/crypto/kdf/Argon2Kdf.cpp | 116 +++++++++++++++ src/crypto/kdf/Argon2Kdf.h | 50 +++++++ src/crypto/kdf/Kdf.cpp | 39 ++++- src/crypto/kdf/Kdf.h | 17 ++- src/format/KeePass2.cpp | 8 +- src/format/KeePass2.h | 1 + src/gui/DatabaseSettingsWidget.cpp | 67 +++++++-- src/gui/DatabaseSettingsWidget.h | 1 + src/gui/DatabaseSettingsWidget.ui | 228 ++++++++++++++++++----------- src/main.cpp | 1 - 18 files changed, 468 insertions(+), 170 deletions(-) create mode 100644 cmake/FindArgon2.cmake create mode 100644 src/crypto/kdf/Argon2Kdf.cpp create mode 100644 src/crypto/kdf/Argon2Kdf.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 34ac87f04..e6d4270ca 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -261,8 +261,8 @@ endif() set_property(DIRECTORY APPEND PROPERTY COMPILE_DEFINITIONS_NONE QT_NO_DEBUG) find_package(LibGPGError REQUIRED) - find_package(Gcrypt 1.7.0 REQUIRED) +find_package(Argon2 REQUIRED) find_package(ZLIB REQUIRED) diff --git a/INSTALL.md b/INSTALL.md index 0bfa86b2c..2690e6091 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -26,6 +26,7 @@ The following libraries are required: * libmicrohttpd * libxi, libxtst, qtx11extras (optional for auto-type on X11) * libsodium (>= 1.0.12, optional for keepassxc-browser support) +* libargon2 Prepare the Building Environment diff --git a/cmake/FindArgon2.cmake b/cmake/FindArgon2.cmake new file mode 100644 index 000000000..8378ebd54 --- /dev/null +++ b/cmake/FindArgon2.cmake @@ -0,0 +1,21 @@ +# Copyright (C) 2017 KeePassXC Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 or (at your option) +# version 3 of the License. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +find_path(ARGON2_INCLUDE_DIR argon2.h) +find_library(ARGON2_LIBRARIES argon2) +mark_as_advanced(ARGON2_LIBRARIES ARGON2_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Argon2 DEFAULT_MSG ARGON2_LIBRARIES ARGON2_INCLUDE_DIR) \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c04d7110f..1e927ee38 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -78,6 +78,7 @@ set(keepassx_SOURCES crypto/kdf/Kdf.cpp crypto/kdf/Kdf_p.h crypto/kdf/AesKdf.cpp + crypto/kdf/Argon2Kdf.cpp format/CsvExporter.cpp format/KeePass1.h format/KeePass1Reader.cpp @@ -252,6 +253,7 @@ target_link_libraries(keepassx_core Qt5::Network Qt5::Concurrent Qt5::Widgets + ${ARGON2_LIBRARIES} ${GCRYPT_LIBRARIES} ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES}) diff --git a/src/cli/CMakeLists.txt b/src/cli/CMakeLists.txt index 4c8620d55..225ce47dc 100644 --- a/src/cli/CMakeLists.txt +++ b/src/cli/CMakeLists.txt @@ -46,6 +46,7 @@ target_link_libraries(keepassxc-cli keepassx_core Qt5::Core ${GCRYPT_LIBRARIES} + ${ARGON2_LIBRARIES} ${GPGERROR_LIBRARIES} ${ZLIB_LIBRARIES} ${ZXCVBN_LIBRARIES}) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 2f2f2967d..ae1664118 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -45,7 +45,7 @@ Database::Database() m_data.cipher = KeePass2::CIPHER_AES; m_data.compressionAlgo = CompressionGZip; m_data.kdf = QSharedPointer::create(); - m_data.kdf->randomizeTransformSalt(); + m_data.kdf->randomizeSeed(); m_data.hasKey = false; setRootGroup(new Group()); @@ -258,7 +258,7 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo) bool Database::setKey(const CompositeKey& key, bool updateChangedTime, bool updateTransformSalt) { if (updateTransformSalt) { - m_data.kdf->randomizeTransformSalt(); + m_data.kdf->randomizeSeed(); } QByteArray transformedMasterKey; @@ -490,7 +490,7 @@ void Database::setKdf(QSharedPointer kdf) bool Database::changeKdf(QSharedPointer kdf) { - kdf->randomizeTransformSalt(); + kdf->randomizeSeed(); QByteArray transformedMasterKey; if (!m_data.key.transform(*kdf, transformedMasterKey)) { return false; diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp index 7056698d7..3177506fc 100644 --- a/src/crypto/kdf/AesKdf.cpp +++ b/src/crypto/kdf/AesKdf.cpp @@ -15,12 +15,17 @@ * along with this program. If not, see . */ +#include "AesKdf.h" + #include #include "format/KeePass2.h" #include "crypto/CryptoHash.h" -#include "crypto/Random.h" -#include "AesKdf.h" + +AesKdf::AesKdf() + : Kdf::Kdf(KeePass2::KDF_AES) +{ +} bool AesKdf::transform(const QByteArray& raw, QByteArray& result) const { @@ -44,7 +49,7 @@ bool AesKdf::transform(const QByteArray& raw, QByteArray& result) const return true; } -bool AesKdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, quint64 rounds, QByteArray* result) +bool AesKdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, int rounds, QByteArray* result) { QByteArray iv(16, 0); SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, @@ -65,44 +70,6 @@ bool AesKdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, quin return true; } -AesKdf::AesKdf() - : Kdf::Kdf(KeePass2::KDF_AES) - , m_rounds(100000ull) - , m_seed(QByteArray(32, 0)) -{ -} - -quint64 AesKdf::rounds() const -{ - return m_rounds; -} - -QByteArray AesKdf::seed() const -{ - return m_seed; -} - -bool AesKdf::setRounds(quint64 rounds) -{ - m_rounds = rounds; - return true; -} - -bool AesKdf::setSeed(const QByteArray& seed) -{ - if (seed.size() != 32) { - return false; - } - - m_seed = seed; - return true; -} - -void AesKdf::randomizeTransformSalt() -{ - setSeed(randomGen()->randomArray(32)); -} - QSharedPointer AesKdf::clone() const { return QSharedPointer::create(*this); @@ -117,17 +84,13 @@ int AesKdf::benchmarkImpl(int msec) const SymmetricCipher cipher(SymmetricCipher::Aes256, SymmetricCipher::Ecb, SymmetricCipher::Encrypt); cipher.init(seed, iv); - int rounds = 0; + quint64 rounds = 1000000; QElapsedTimer timer; timer.start(); - do { - if (!cipher.processInPlace(key, 10000)) { - rounds = -1; - break; - } - rounds += 10000; - } - while (!timer.hasExpired(msec)); - return rounds; + if (!cipher.processInPlace(key, rounds)) { + return -1; + } + + return static_cast(rounds * (static_cast(msec) / timer.elapsed())); } diff --git a/src/crypto/kdf/AesKdf.h b/src/crypto/kdf/AesKdf.h index 28206a8d6..3e2c8ada6 100644 --- a/src/crypto/kdf/AesKdf.h +++ b/src/crypto/kdf/AesKdf.h @@ -26,25 +26,15 @@ public: AesKdf(); bool transform(const QByteArray& raw, QByteArray& result) const override; - void randomizeTransformSalt() override; QSharedPointer clone() const override; - quint64 rounds() const override; - QByteArray seed() const override; - - bool setRounds(quint64 rounds) override; - bool setSeed(const QByteArray& seed) override; - protected: int benchmarkImpl(int msec) const override; private: - quint64 m_rounds; - QByteArray m_seed; - static bool transformKeyRaw(const QByteArray& key, const QByteArray& seed, - quint64 rounds, + int rounds, QByteArray* result) Q_REQUIRED_RESULT; }; diff --git a/src/crypto/kdf/Argon2Kdf.cpp b/src/crypto/kdf/Argon2Kdf.cpp new file mode 100644 index 000000000..fa410dc93 --- /dev/null +++ b/src/crypto/kdf/Argon2Kdf.cpp @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Argon2Kdf.h" + +#include +#include + +#include "format/KeePass2.h" +#include "crypto/CryptoHash.h" + +/** + * KeePass' Argon2 implementation supports all parameters that are defined in the official specification, + * but only the number of iterations, the memory size and the degree of parallelism can be configured by + * the user in the database settings dialog. For the other parameters, KeePass chooses reasonable defaults: + * a 256-bit salt is generated each time the database is saved, the tag length is 256 bits, no secret key + * or associated data. KeePass uses the latest version of Argon2, v1.3. + */ +Argon2Kdf::Argon2Kdf() + : Kdf::Kdf(KeePass2::KDF_ARGON2) + , m_memory(1<<16) + , m_parallelism(2) +{ + m_rounds = 1; +} + +quint32 Argon2Kdf::memory() const +{ + // Convert to Megabytes + return m_memory / (1<<10); +} + +bool Argon2Kdf::setMemory(quint32 memoryMegabytes) +{ + // TODO: add bounds check + // Convert to Kibibytes + m_memory = (1<<10) * memoryMegabytes; + return true; +} + +quint32 Argon2Kdf::parallelism() const +{ + return m_parallelism; +} + +bool Argon2Kdf::setParallelism(quint32 threads) +{ + // TODO: add bounds check + m_parallelism = threads; + return true; +} + +bool Argon2Kdf::transform(const QByteArray& raw, QByteArray& result) const +{ + result.clear(); + result.resize(32); + + if (!transformKeyRaw(raw, seed(), rounds(), memory(), parallelism(), result)) { + return false; + } + + result = CryptoHash::hash(result, CryptoHash::Sha256); + return true; +} + +bool Argon2Kdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, int rounds, + quint32 memory, quint32 parallelism, QByteArray& result) +{ + // Time Cost, Mem Cost, Threads/Lanes, Password, length, Salt, length, out, length + int rc = argon2d_hash_raw(rounds, memory, parallelism, key.data(), key.size(), + seed.data(), seed.size(), result.data(), result.size()); + if (rc != ARGON2_OK) { + qWarning("Argon2 error: %s", argon2_error_message(rc)); + return false; + } + + return true; +} + +QSharedPointer Argon2Kdf::clone() const +{ + return QSharedPointer::create(*this); +} + +int Argon2Kdf::benchmarkImpl(int msec) const +{ + QByteArray key = QByteArray(16, '\x7E'); + QByteArray seed = QByteArray(32, '\x4B'); + + QElapsedTimer timer; + timer.start(); + + int rounds = 4; + + int rc = argon2d_hash_raw(rounds, m_memory, m_parallelism, key.data(), key.size(), seed.data(), seed.size(), key.data(), key.size()); + if (rc != ARGON2_OK) { + qWarning("Argon2 error: %s", argon2_error_message(rc)); + return -1; + } + + return static_cast(rounds * (static_cast(msec) / timer.elapsed())); +} \ No newline at end of file diff --git a/src/crypto/kdf/Argon2Kdf.h b/src/crypto/kdf/Argon2Kdf.h new file mode 100644 index 000000000..c01698120 --- /dev/null +++ b/src/crypto/kdf/Argon2Kdf.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_ARGON2KDF_H +#define KEEPASSX_ARGON2KDF_H + +#include "Kdf.h" + +class Argon2Kdf : public Kdf { +public: + Argon2Kdf(); + + bool transform(const QByteArray& raw, QByteArray& result) const override; + QSharedPointer clone() const override; + + quint32 memory() const; + bool setMemory(quint32 memory_kb); + quint32 parallelism() const; + bool setParallelism(quint32 threads); + +protected: + int benchmarkImpl(int msec) const override; + + quint32 m_memory; + quint32 m_parallelism; + +private: + static bool transformKeyRaw(const QByteArray& key, + const QByteArray& seed, + int rounds, + quint32 memory, + quint32 parallelism, + QByteArray& result) Q_REQUIRED_RESULT; +}; + +#endif // KEEPASSX_ARGON2KDF_H diff --git a/src/crypto/kdf/Kdf.cpp b/src/crypto/kdf/Kdf.cpp index cfc3025c1..5134adc5f 100644 --- a/src/crypto/kdf/Kdf.cpp +++ b/src/crypto/kdf/Kdf.cpp @@ -20,8 +20,12 @@ #include +#include "crypto/Random.h" + Kdf::Kdf(Uuid uuid) - : m_uuid(uuid) + : m_rounds(KDF_DEFAULT_ROUNDS) + , m_seed(QByteArray(KDF_DEFAULT_SEED_SIZE, 0)) + , m_uuid(uuid) { } @@ -30,6 +34,37 @@ Uuid Kdf::uuid() const return m_uuid; } +int Kdf::rounds() const +{ + return m_rounds; +} + +QByteArray Kdf::seed() const +{ + return m_seed; +} + +bool Kdf::setRounds(int rounds) +{ + m_rounds = rounds; + return true; +} + +bool Kdf::setSeed(const QByteArray& seed) +{ + if (seed.size() != m_seed.size()) { + return false; + } + + m_seed = seed; + return true; +} + +void Kdf::randomizeSeed() +{ + setSeed(randomGen()->randomArray(m_seed.size())); +} + int Kdf::benchmark(int msec) const { BenchmarkThread thread1(msec, this); @@ -41,7 +76,7 @@ int Kdf::benchmark(int msec) const thread1.wait(); thread2.wait(); - return qMin(thread1.rounds(), thread2.rounds()); + return qMax(1, qMin(thread1.rounds(), thread2.rounds())); } Kdf::BenchmarkThread::BenchmarkThread(int msec, const Kdf* kdf) diff --git a/src/crypto/kdf/Kdf.h b/src/crypto/kdf/Kdf.h index 5330e71d0..cb0bcc364 100644 --- a/src/crypto/kdf/Kdf.h +++ b/src/crypto/kdf/Kdf.h @@ -22,6 +22,9 @@ #include "core/Uuid.h" +#define KDF_DEFAULT_SEED_SIZE 32 +#define KDF_DEFAULT_ROUNDS 100000ull + class Kdf { public: @@ -30,12 +33,13 @@ public: Uuid uuid() const; - virtual quint64 rounds() const = 0; - virtual bool setRounds(quint64 rounds) = 0; - virtual QByteArray seed() const = 0; - virtual bool setSeed(const QByteArray& seed) = 0; + int rounds() const; + virtual bool setRounds(int rounds); + QByteArray seed() const; + virtual bool setSeed(const QByteArray& seed); + virtual void randomizeSeed(); + virtual bool transform(const QByteArray& raw, QByteArray& result) const = 0; - virtual void randomizeTransformSalt() = 0; virtual QSharedPointer clone() const = 0; int benchmark(int msec) const; @@ -43,6 +47,9 @@ public: protected: virtual int benchmarkImpl(int msec) const = 0; + int m_rounds; + QByteArray m_seed; + private: class BenchmarkThread; const Uuid m_uuid; diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index 61bd383df..fd57148d0 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -16,15 +16,16 @@ */ #include "KeePass2.h" -#include "crypto/kdf/AesKdf.h" #include - +#include "crypto/kdf/AesKdf.h" +#include "crypto/kdf/Argon2Kdf.h" const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff")); const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); const Uuid KeePass2::CIPHER_CHACHA20 = Uuid(QByteArray::fromHex("D6038A2B8B6F4CB5A524339A31DBB59A")); const Uuid KeePass2::KDF_AES = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA")); +const Uuid KeePass2::KDF_ARGON2 = Uuid(QByteArray::fromHex("EF636DDF8C29444B91F7A9A403E30A0C")); const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); @@ -35,12 +36,15 @@ const QList> KeePass2::CIPHERS { }; const QList> KeePass2::KDFS { qMakePair(KeePass2::KDF_AES, QObject::tr("AES-KDF")), + qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2")), }; QSharedPointer KeePass2::uuidToKdf(const Uuid& uuid) { if (uuid == KDF_AES) { return QSharedPointer::create(); + } else if (uuid == KDF_ARGON2) { + return QSharedPointer::create(); } Q_ASSERT_X(false, "uuidToKdf", "Invalid UUID"); diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index c7945e907..99bc5a0b0 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -40,6 +40,7 @@ namespace KeePass2 extern const Uuid CIPHER_CHACHA20; extern const Uuid KDF_AES; + extern const Uuid KDF_ARGON2; extern const QByteArray INNER_STREAM_SALSA20_IV; diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index 51c0f4183..2f4fe177d 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -26,6 +26,7 @@ #include "core/Group.h" #include "core/Metadata.h" #include "crypto/SymmetricCipher.h" +#include "crypto/kdf/Argon2Kdf.h" #include "MessageBox.h" DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) @@ -42,6 +43,7 @@ DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) connect(m_ui->historyMaxSizeCheckBox, SIGNAL(toggled(bool)), m_ui->historyMaxSizeSpinBox, SLOT(setEnabled(bool))); connect(m_ui->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark())); + connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(kdfChanged(int))); } DatabaseSettingsWidget::~DatabaseSettingsWidget() @@ -83,19 +85,29 @@ void DatabaseSettingsWidget::load(Database* db) m_ui->algorithmComboBox->setCurrentIndex(cipherIndex); } - bool blockSignals = m_ui->kdfComboBox->signalsBlocked(); + // Setup kdf combo box m_ui->kdfComboBox->blockSignals(true); - m_ui->kdfComboBox->clear(); for (auto& kdf: asConst(KeePass2::KDFS)) { m_ui->kdfComboBox->addItem(kdf.second, kdf.first.toByteArray()); } - int kdfIndex = m_ui->kdfComboBox->findData(m_db->kdf()->uuid().toByteArray()); + m_ui->kdfComboBox->blockSignals(false); + + auto kdfUuid = m_db->kdf()->uuid(); + int kdfIndex = m_ui->kdfComboBox->findData(kdfUuid.toByteArray()); if (kdfIndex > -1) { m_ui->kdfComboBox->setCurrentIndex(kdfIndex); + kdfChanged(kdfIndex); + } + + // Setup kdf parameters + auto kdf = m_db->kdf(); + m_ui->transformRoundsSpinBox->setValue(kdf->rounds()); + if (kdfUuid == KeePass2::KDF_ARGON2) { + auto argon2Kdf = kdf.staticCast(); + m_ui->memorySpinBox->setValue(argon2Kdf->memory()); + m_ui->parallelismSpinBox->setValue(argon2Kdf->parallelism()); } - m_ui->kdfComboBox->blockSignals(blockSignals); - m_ui->transformRoundsSpinBox->setValue(static_cast(m_db->kdf()->rounds())); m_ui->dbNameEdit->setFocus(); } @@ -139,8 +151,14 @@ void DatabaseSettingsWidget::save() m_db->setCipher(Uuid(m_ui->algorithmComboBox->currentData().toByteArray())); + // Save kdf parameters auto kdf = KeePass2::uuidToKdf(Uuid(m_ui->kdfComboBox->currentData().toByteArray())); - kdf->setRounds(static_cast(qMax(0, m_ui->transformRoundsSpinBox->value()))); + kdf->setRounds(m_ui->transformRoundsSpinBox->value()); + if (kdf->uuid() == KeePass2::KDF_ARGON2) { + auto argon2Kdf = kdf.staticCast(); + argon2Kdf->setMemory(m_ui->memorySpinBox->value()); + argon2Kdf->setParallelism(m_ui->parallelismSpinBox->value()); + } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); // TODO: we should probably use AsyncTask::runAndWaitForFuture() here, @@ -164,11 +182,26 @@ void DatabaseSettingsWidget::reject() void DatabaseSettingsWidget::transformRoundsBenchmark() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - m_ui->transformRoundsSpinBox->setValue(AsyncTask::runAndWaitForFuture([this]() { - int rounds = m_db->kdf()->benchmark(1000); - QApplication::restoreOverrideCursor(); - return rounds; - })); + m_ui->transformBenchmarkButton->setEnabled(false); + + // Create a new kdf with the current parameters + auto kdf = KeePass2::uuidToKdf(Uuid(m_ui->kdfComboBox->currentData().toByteArray())); + kdf->setRounds(m_ui->transformRoundsSpinBox->value()); + if (kdf->uuid() == KeePass2::KDF_ARGON2) { + auto argon2Kdf = kdf.staticCast(); + argon2Kdf->setMemory(m_ui->memorySpinBox->value()); + argon2Kdf->setParallelism(m_ui->parallelismSpinBox->value()); + } + + // Determine the number of rounds required to meet 1 second delay + int rounds = AsyncTask::runAndWaitForFuture([this, kdf]() { + return kdf->benchmark(1000); + }); + + m_ui->transformRoundsSpinBox->setValue(rounds); + + QApplication::restoreOverrideCursor(); + m_ui->transformBenchmarkButton->setEnabled(true); } void DatabaseSettingsWidget::truncateHistories() @@ -178,3 +211,15 @@ void DatabaseSettingsWidget::truncateHistories() entry->truncateHistory(); } } + +void DatabaseSettingsWidget::kdfChanged(int index) +{ + Uuid id(m_ui->kdfComboBox->itemData(index).toByteArray()); + if (id == KeePass2::KDF_ARGON2) { + m_ui->memorySpinBox->setEnabled(true); + m_ui->parallelismSpinBox->setEnabled(true); + } else { + m_ui->memorySpinBox->setEnabled(false); + m_ui->parallelismSpinBox->setEnabled(false); + } +} diff --git a/src/gui/DatabaseSettingsWidget.h b/src/gui/DatabaseSettingsWidget.h index ec3d6415a..83a5eb098 100644 --- a/src/gui/DatabaseSettingsWidget.h +++ b/src/gui/DatabaseSettingsWidget.h @@ -51,6 +51,7 @@ private slots: void save(); void reject(); void transformRoundsBenchmark(); + void kdfChanged(int index); private: void truncateHistories(); diff --git a/src/gui/DatabaseSettingsWidget.ui b/src/gui/DatabaseSettingsWidget.ui index 8aaab19e7..5a6778678 100644 --- a/src/gui/DatabaseSettingsWidget.ui +++ b/src/gui/DatabaseSettingsWidget.ui @@ -2,6 +2,14 @@ DatabaseSettingsWidget + + + 0 + 0 + 1082 + 506 + + @@ -35,39 +43,36 @@ - 800 + 400 16777215 - + + + + Max. history size: + + + + + + + - - - - 0 - 0 - + + + MB 1 - 1000000000 + 1048576 - - - - - - - 0 - 0 - - - - Benchmark + + 64 @@ -80,31 +85,55 @@ - - - - Max. history size: - - - - - - - Transform rounds: - - - - + Max. history items: - - + + + + Key Derivation Function + + - + + + + + AES: 256 Bit (default) + + + + + Twofish: 256 Bit + + + + + + + + Encryption Algorithm: + + + + + + + Database description: + + + + + + + + + + @@ -121,17 +150,7 @@ - - - - Default username: - - - - - - - + @@ -155,54 +174,101 @@ - - - Use recycle bin + + + + 0 + 0 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 1 + + + 1000000000 + + + + 0 + 0 + + + + Benchmark 1-second delay + + + + true - - + + - Database description: + Default username: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - AES: 256 Bit (default) - - - - - Twofish: 256 Bit - - - - - - + + - Encryption Algorithm: + Transform rounds: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - + + - Key Derivation Function + Memory Usage: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Parallelism: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + thread + + + 1 + + + 128 + + + + + + + Use recycle bin @@ -249,10 +315,6 @@ dbNameEdit dbDescriptionEdit - transformRoundsSpinBox - transformBenchmarkButton - defaultUsernameEdit - recycleBinEnabledCheckBox historyMaxItemsCheckBox historyMaxItemsSpinBox historyMaxSizeCheckBox diff --git a/src/main.cpp b/src/main.cpp index deabeabb7..da71739c4 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -27,7 +27,6 @@ #include "crypto/Crypto.h" #include "gui/Application.h" #include "gui/MainWindow.h" -#include "gui/csvImport/CsvImportWizard.h" #include "gui/MessageBox.h" #if defined(WITH_ASAN) && defined(WITH_LSAN) From 738f870e70b34620b5e441a97854710142319812 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Mon, 1 Jan 2018 13:59:57 -0500 Subject: [PATCH 20/39] Correct regression in database modification signal --- src/core/Database.cpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index ae1664118..e3fa5a269 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -261,6 +261,7 @@ bool Database::setKey(const CompositeKey& key, bool updateChangedTime, bool upda m_data.kdf->randomizeSeed(); } + QByteArray oldTransformedMasterKey = m_data.transformedMasterKey; QByteArray transformedMasterKey; if (!key.transform(*m_data.kdf, transformedMasterKey)) { return false; @@ -272,7 +273,10 @@ bool Database::setKey(const CompositeKey& key, bool updateChangedTime, bool upda if (updateChangedTime) { m_metadata->setMasterKeyChanged(QDateTime::currentDateTimeUtc()); } - emit modifiedImmediate(); + + if (oldTransformedMasterKey != m_data.transformedMasterKey) { + emit modifiedImmediate(); + } return true; } @@ -461,7 +465,9 @@ QString Database::saveToFile(QString filePath) if (saveFile.open(QIODevice::WriteOnly)) { // write the database to the file + setEmitModified(false); writer.writeDatabase(&saveFile, this); + setEmitModified(true); if (writer.hasError()) { return writer.errorString(); From 7dba788d09b3333087961e6fa2af4b02579999b4 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Mon, 1 Jan 2018 14:24:37 -0500 Subject: [PATCH 21/39] Correct failure in GUI tests due to widget name --- tests/gui/TestGui.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 9f1e7abc2..449f13a06 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -898,7 +898,8 @@ void TestGui::testDatabaseSettings() m_db->metadata()->setName("Save"); triggerAction("actionChangeDatabaseSettings"); QWidget* dbSettingsWidget = m_dbWidget->findChild("databaseSettingsWidget"); - QSpinBox* transformRoundsSpinBox = dbSettingsWidget->findChild("kdfParams0"); + QSpinBox* transformRoundsSpinBox = dbSettingsWidget->findChild("transformRoundsSpinBox"); + QVERIFY(transformRoundsSpinBox != nullptr); transformRoundsSpinBox->setValue(100); QTest::keyClick(transformRoundsSpinBox, Qt::Key_Enter); // wait for modified timer From bef7ba2cfed00b88175473ae68481cebb7746098 Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Fri, 5 Jan 2018 10:41:29 -0500 Subject: [PATCH 22/39] Implements KDBX4 format with Argon2 KDF * Adds KDBX4 reader/writer interfaces * Adds KDBX4 XML reader/write interfaces * Implements test cases for KDBX4 * Fully compatible with KeePass2 * Corrects minor issues with Argon2 KDF --- src/CMakeLists.txt | 4 + src/core/Database.cpp | 8 + src/core/Database.h | 3 + src/core/Metadata.cpp | 10 + src/core/Metadata.h | 4 + src/crypto/kdf/AesKdf.cpp | 25 + src/crypto/kdf/AesKdf.h | 2 + src/crypto/kdf/Argon2Kdf.cpp | 139 +++- src/crypto/kdf/Argon2Kdf.h | 16 +- src/crypto/kdf/Kdf.cpp | 8 +- src/crypto/kdf/Kdf.h | 2 + src/format/Kdbx3Reader.cpp | 2 +- src/format/Kdbx3Writer.cpp | 2 +- src/format/Kdbx4Reader.cpp | 522 ++++++++++++++ src/format/Kdbx4Reader.h | 66 ++ src/format/Kdbx4Writer.cpp | 326 +++++++++ src/format/Kdbx4Writer.h | 52 ++ src/format/Kdbx4XmlReader.cpp | 1080 ++++++++++++++++++++++++++++ src/format/Kdbx4XmlReader.h | 102 +++ src/format/Kdbx4XmlWriter.cpp | 611 ++++++++++++++++ src/format/Kdbx4XmlWriter.h | 93 +++ src/format/KeePass2.cpp | 46 ++ src/format/KeePass2.h | 58 +- src/format/KeePass2Reader.cpp | 17 +- src/format/KeePass2Reader.h | 3 +- src/format/KeePass2Repair.cpp | 19 +- src/format/KeePass2Writer.cpp | 19 +- src/gui/DatabaseSettingsWidget.cpp | 13 +- src/gui/DatabaseWidget.cpp | 2 +- tests/CMakeLists.txt | 3 + tests/TestKdbx4XmlReader.cpp | 22 + tests/TestKeePass2Reader.cpp | 23 + tests/TestKeePass2Reader.h | 1 + tests/TestKeePass2XmlReader.cpp | 40 ++ tests/TestKeePass2XmlReader.h | 13 + tests/data/Format400.kdbx | Bin 0 -> 1801 bytes 36 files changed, 3305 insertions(+), 51 deletions(-) create mode 100644 src/format/Kdbx4Reader.cpp create mode 100644 src/format/Kdbx4Reader.h create mode 100644 src/format/Kdbx4Writer.cpp create mode 100644 src/format/Kdbx4Writer.h create mode 100644 src/format/Kdbx4XmlReader.cpp create mode 100644 src/format/Kdbx4XmlReader.h create mode 100644 src/format/Kdbx4XmlWriter.cpp create mode 100644 src/format/Kdbx4XmlWriter.h create mode 100644 tests/TestKdbx4XmlReader.cpp create mode 100644 tests/data/Format400.kdbx diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1e927ee38..6ab5308dc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -91,6 +91,10 @@ set(keepassx_SOURCES format/Kdbx3Writer.cpp format/Kdbx3XmlReader.cpp format/Kdbx3XmlWriter.cpp + format/Kdbx4Reader.cpp + format/Kdbx4Writer.cpp + format/Kdbx4XmlReader.cpp + format/Kdbx4XmlWriter.cpp gui/AboutDialog.cpp gui/Application.cpp gui/CategoryListWidget.cpp diff --git a/src/core/Database.cpp b/src/core/Database.cpp index e3fa5a269..98a4fc817 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -494,6 +494,14 @@ void Database::setKdf(QSharedPointer kdf) m_data.kdf = std::move(kdf); } +void Database::setPublicCustomData(QByteArray data) { + m_data.publicCustomData = data; +} + +QByteArray Database::publicCustomData() const { + return m_data.publicCustomData; +} + bool Database::changeKdf(QSharedPointer kdf) { kdf->randomizeSeed(); diff --git a/src/core/Database.h b/src/core/Database.h index b293c760d..3bf43f62d 100644 --- a/src/core/Database.h +++ b/src/core/Database.h @@ -58,6 +58,7 @@ public: Uuid cipher; CompressionAlgorithm compressionAlgo; QByteArray transformedMasterKey; + QByteArray publicCustomData; QSharedPointer kdf; CompositeKey key; bool hasKey; @@ -91,6 +92,7 @@ public: Uuid cipher() const; Database::CompressionAlgorithm compressionAlgo() const; QSharedPointer kdf() const; + QByteArray publicCustomData() const; QByteArray transformedMasterKey() const; const CompositeKey& key() const; QByteArray challengeResponseKey() const; @@ -99,6 +101,7 @@ public: void setCipher(const Uuid& cipher); void setCompressionAlgo(Database::CompressionAlgorithm algo); void setKdf(QSharedPointer kdf); + void setPublicCustomData(QByteArray data); bool setKey(const CompositeKey& key, bool updateChangedTime = true, bool updateTransformSalt = false); bool hasKey() const; diff --git a/src/core/Metadata.cpp b/src/core/Metadata.cpp index 46b0a0b5e..ab56dab7f 100644 --- a/src/core/Metadata.cpp +++ b/src/core/Metadata.cpp @@ -49,6 +49,7 @@ Metadata::Metadata(QObject* parent) m_recycleBinChanged = now; m_entryTemplatesGroupChanged = now; m_masterKeyChanged = now; + m_settingsChanged = now; } template bool Metadata::set(P& property, const V& value) @@ -525,3 +526,12 @@ void Metadata::removeCustomField(const QString& key) m_customFields.remove(key); emit modified(); } + +QDateTime Metadata::settingsChanged() const { + return m_settingsChanged; +} + +void Metadata::setSettingsChanged(const QDateTime& value) { + Q_ASSERT(value.timeSpec() == Qt::UTC); + m_settingsChanged = value; +} diff --git a/src/core/Metadata.h b/src/core/Metadata.h index 1e972fd5a..7791b0387 100644 --- a/src/core/Metadata.h +++ b/src/core/Metadata.h @@ -69,6 +69,7 @@ public: QDateTime descriptionChanged() const; QString defaultUserName() const; QDateTime defaultUserNameChanged() const; + QDateTime settingsChanged() const; int maintenanceHistoryDays() const; QColor color() const; bool protectTitle() const; @@ -108,6 +109,7 @@ public: void setDescriptionChanged(const QDateTime& value); void setDefaultUserName(const QString& value); void setDefaultUserNameChanged(const QDateTime& value); + void setSettingsChanged(const QDateTime& value); void setMaintenanceHistoryDays(int value); void setColor(const QColor& value); void setProtectTitle(bool value); @@ -141,6 +143,7 @@ public: * - Master key changed date * - Custom icons * - Custom fields + * - Settings changed date */ void copyAttributesFrom(const Metadata* other); @@ -170,6 +173,7 @@ private: QPointer m_lastTopVisibleGroup; QDateTime m_masterKeyChanged; + QDateTime m_settingsChanged; QHash m_customFields; diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp index 3177506fc..d668652aa 100644 --- a/src/crypto/kdf/AesKdf.cpp +++ b/src/crypto/kdf/AesKdf.cpp @@ -27,6 +27,31 @@ AesKdf::AesKdf() { } +bool AesKdf::processParameters(const QVariantMap &p) +{ + bool ok; + int rounds = p.value(KeePass2::KDFPARAM_AES_ROUNDS).toInt(&ok); + if (!ok || !setRounds(rounds)) { + return false; + } + + QByteArray seed = p.value(KeePass2::KDFPARAM_AES_SEED).toByteArray(); + if (!setSeed(seed)) { + return false; + } + + return true; +} + +QVariantMap AesKdf::writeParameters() +{ + QVariantMap p; + p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_AES.toByteArray()); + p.insert(KeePass2::KDFPARAM_AES_ROUNDS, rounds()); + p.insert(KeePass2::KDFPARAM_AES_SEED, seed()); + return p; +} + bool AesKdf::transform(const QByteArray& raw, QByteArray& result) const { QByteArray resultLeft; diff --git a/src/crypto/kdf/AesKdf.h b/src/crypto/kdf/AesKdf.h index 3e2c8ada6..69c15b8af 100644 --- a/src/crypto/kdf/AesKdf.h +++ b/src/crypto/kdf/AesKdf.h @@ -25,6 +25,8 @@ class AesKdf: public Kdf public: AesKdf(); + bool processParameters(const QVariantMap& p) override; + QVariantMap writeParameters() override; bool transform(const QByteArray& raw, QByteArray& result) const override; QSharedPointer clone() const override; diff --git a/src/crypto/kdf/Argon2Kdf.cpp b/src/crypto/kdf/Argon2Kdf.cpp index fa410dc93..12e9135af 100644 --- a/src/crypto/kdf/Argon2Kdf.cpp +++ b/src/crypto/kdf/Argon2Kdf.cpp @@ -32,24 +32,43 @@ */ Argon2Kdf::Argon2Kdf() : Kdf::Kdf(KeePass2::KDF_ARGON2) + , m_version(0x13) , m_memory(1<<16) , m_parallelism(2) { m_rounds = 1; } -quint32 Argon2Kdf::memory() const +quint32 Argon2Kdf::version() const { - // Convert to Megabytes - return m_memory / (1<<10); + return m_version; } -bool Argon2Kdf::setMemory(quint32 memoryMegabytes) +bool Argon2Kdf::setVersion(quint32 version) { - // TODO: add bounds check - // Convert to Kibibytes - m_memory = (1<<10) * memoryMegabytes; - return true; + // MIN=0x10; MAX=0x13) + if (version >= 0x10 && version <= 0x13) { + m_version = version; + return true; + } + m_version = 0x13; + return false; +} + +quint64 Argon2Kdf::memory() const +{ + return m_memory; +} + +bool Argon2Kdf::setMemory(quint64 kibibytes) +{ + // MIN=8KB; MAX=2,147,483,648KB + if (kibibytes >= 8 && kibibytes < (1ULL<<32)) { + m_memory = kibibytes; + return true; + } + m_memory = 16; + return false; } quint32 Argon2Kdf::parallelism() const @@ -59,30 +78,97 @@ quint32 Argon2Kdf::parallelism() const bool Argon2Kdf::setParallelism(quint32 threads) { - // TODO: add bounds check - m_parallelism = threads; + // MIN=1; MAX=16,777,215 + if (threads >= 1 && threads < (1<<24)) { + m_parallelism = threads; + return true; + } + m_parallelism = 1; + return false; +} + +bool Argon2Kdf::processParameters(const QVariantMap &p) +{ + QByteArray salt = p.value(KeePass2::KDFPARAM_ARGON2_SALT).toByteArray(); + if (!setSeed(salt)) { + return false; + } + + bool ok; + quint32 version = p.value(KeePass2::KDFPARAM_ARGON2_VERSION).toUInt(&ok); + if (!ok || !setVersion(version)) { + return false; + } + + quint32 lanes = p.value(KeePass2::KDFPARAM_ARGON2_PARALLELISM).toUInt(&ok); + if (!ok || !setParallelism(lanes)) { + return false; + } + + quint64 memory = p.value(KeePass2::KDFPARAM_ARGON2_MEMORY).toULongLong(&ok) / 1024ULL; + if (!ok || !setMemory(memory)) { + return false; + } + + quint64 iterations = p.value(KeePass2::KDFPARAM_ARGON2_ITERATIONS).toULongLong(&ok); + if (!ok || !setRounds(iterations)) { + return false; + } + + /* KeePass2 does not currently implement these parameters + * + QByteArray secret = p.value(KeePass2::KDFPARAM_ARGON2_SECRET).toByteArray(); + if (!argon2Kdf->setSecret(secret)) { + return nullptr; + } + + QByteArray ad = p.value(KeePass2::KDFPARAM_ARGON2_ASSOCDATA).toByteArray(); + if (!argon2Kdf->setAssocData(ad)) { + return nullptr; + } + */ + return true; } +QVariantMap Argon2Kdf::writeParameters() +{ + QVariantMap p; + p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_ARGON2.toByteArray()); + p.insert(KeePass2::KDFPARAM_ARGON2_VERSION, version()); + p.insert(KeePass2::KDFPARAM_ARGON2_PARALLELISM, parallelism()); + p.insert(KeePass2::KDFPARAM_ARGON2_MEMORY, memory() * 1024); + p.insert(KeePass2::KDFPARAM_ARGON2_ITERATIONS, static_cast(rounds())); + p.insert(KeePass2::KDFPARAM_ARGON2_SALT, seed()); + + /* KeePass2 does not currently implement these + * + if (!assocData().isEmpty()) { + p.insert(KeePass2::KDFPARAM_ARGON2_ASSOCDATA, argon2Kdf.assocData()); + } + + if (!secret().isEmpty()) { + p.insert(KeePass2::KDFPARAM_ARGON2_SECRET, argon2Kdf.secret()); + } + */ + + return p; +} + bool Argon2Kdf::transform(const QByteArray& raw, QByteArray& result) const { result.clear(); result.resize(32); - - if (!transformKeyRaw(raw, seed(), rounds(), memory(), parallelism(), result)) { - return false; - } - - result = CryptoHash::hash(result, CryptoHash::Sha256); - return true; + return transformKeyRaw(raw, seed(), version(), rounds(), memory(), parallelism(), result); } -bool Argon2Kdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, int rounds, - quint32 memory, quint32 parallelism, QByteArray& result) +bool Argon2Kdf::transformKeyRaw(const QByteArray& key, const QByteArray& seed, quint32 version, + quint32 rounds, quint64 memory, quint32 parallelism, QByteArray& result) { // Time Cost, Mem Cost, Threads/Lanes, Password, length, Salt, length, out, length - int rc = argon2d_hash_raw(rounds, memory, parallelism, key.data(), key.size(), - seed.data(), seed.size(), result.data(), result.size()); + int rc = argon2_hash(rounds, memory, parallelism, key.data(), key.size(), + seed.data(), seed.size(), result.data(), result.size(), + nullptr, 0, Argon2_d, version); if (rc != ARGON2_OK) { qWarning("Argon2 error: %s", argon2_error_message(rc)); return false; @@ -105,12 +191,9 @@ int Argon2Kdf::benchmarkImpl(int msec) const timer.start(); int rounds = 4; - - int rc = argon2d_hash_raw(rounds, m_memory, m_parallelism, key.data(), key.size(), seed.data(), seed.size(), key.data(), key.size()); - if (rc != ARGON2_OK) { - qWarning("Argon2 error: %s", argon2_error_message(rc)); - return -1; + if (transformKeyRaw(key, seed, version(), rounds, memory(), parallelism(), key)) { + return static_cast(rounds * (static_cast(msec) / timer.elapsed())); } - return static_cast(rounds * (static_cast(msec) / timer.elapsed())); -} \ No newline at end of file + return 1; +} diff --git a/src/crypto/kdf/Argon2Kdf.h b/src/crypto/kdf/Argon2Kdf.h index c01698120..345ca279c 100644 --- a/src/crypto/kdf/Argon2Kdf.h +++ b/src/crypto/kdf/Argon2Kdf.h @@ -24,25 +24,31 @@ class Argon2Kdf : public Kdf { public: Argon2Kdf(); + bool processParameters(const QVariantMap& p) override; + QVariantMap writeParameters() override; bool transform(const QByteArray& raw, QByteArray& result) const override; QSharedPointer clone() const override; - quint32 memory() const; - bool setMemory(quint32 memory_kb); + quint32 version() const; + bool setVersion(quint32 version); + quint64 memory() const; + bool setMemory(quint64 kibibytes); quint32 parallelism() const; bool setParallelism(quint32 threads); protected: int benchmarkImpl(int msec) const override; - quint32 m_memory; + quint32 m_version; + quint64 m_memory; quint32 m_parallelism; private: static bool transformKeyRaw(const QByteArray& key, const QByteArray& seed, - int rounds, - quint32 memory, + quint32 version, + quint32 rounds, + quint64 memory, quint32 parallelism, QByteArray& result) Q_REQUIRED_RESULT; }; diff --git a/src/crypto/kdf/Kdf.cpp b/src/crypto/kdf/Kdf.cpp index 5134adc5f..e500dbe6f 100644 --- a/src/crypto/kdf/Kdf.cpp +++ b/src/crypto/kdf/Kdf.cpp @@ -46,8 +46,12 @@ QByteArray Kdf::seed() const bool Kdf::setRounds(int rounds) { - m_rounds = rounds; - return true; + if (rounds >= 1 && rounds < INT_MAX) { + m_rounds = rounds; + return true; + } + m_rounds = 1; + return false; } bool Kdf::setSeed(const QByteArray& seed) diff --git a/src/crypto/kdf/Kdf.h b/src/crypto/kdf/Kdf.h index cb0bcc364..e45d23bcd 100644 --- a/src/crypto/kdf/Kdf.h +++ b/src/crypto/kdf/Kdf.h @@ -39,6 +39,8 @@ public: virtual bool setSeed(const QByteArray& seed); virtual void randomizeSeed(); + virtual bool processParameters(const QVariantMap& p) = 0; + virtual QVariantMap writeParameters() = 0; virtual bool transform(const QByteArray& raw, QByteArray& result) const = 0; virtual QSharedPointer clone() const = 0; diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index b5e5e2df8..3187442be 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -82,7 +82,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, quint32 version = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok) & KeePass2::FILE_VERSION_CRITICAL_MASK; - quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; + quint32 maxVersion = KeePass2::FILE_VERSION_3 & KeePass2::FILE_VERSION_CRITICAL_MASK; if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) { raiseError(tr("Unsupported KeePass KDBX 2 or 3 database version.")); return nullptr; diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp index 770e7270e..2fedf273c 100644 --- a/src/format/Kdbx3Writer.cpp +++ b/src/format/Kdbx3Writer.cpp @@ -74,7 +74,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::FILE_VERSION_3, KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray())); CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CompressionFlags, diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp new file mode 100644 index 000000000..0a69cbf2d --- /dev/null +++ b/src/format/Kdbx4Reader.cpp @@ -0,0 +1,522 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Kdbx4Reader.h" + +#include +#include + +#include "crypto/kdf/AesKdf.h" +#include "streams/HmacBlockStream.h" +#include "core/Database.h" +#include "core/Endian.h" +#include "crypto/CryptoHash.h" +#include "format/KeePass1.h" +#include "format/KeePass2.h" +#include "format/KeePass2RandomStream.h" +#include "format/Kdbx4XmlReader.h" +#include "streams/HashedBlockStream.h" +#include "streams/QtIOCompressor" +#include "streams/StoreDataStream.h" +#include "streams/SymmetricCipherStream.h" + +Kdbx4Reader::Kdbx4Reader() + : m_device(nullptr) + , m_db(nullptr) +{ +} + +Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) +{ + QScopedPointer db(new Database()); + m_db = db.data(); + m_device = device; + m_error = false; + m_errorStr.clear(); + m_xmlData.clear(); + m_masterSeed.clear(); + m_encryptionIV.clear(); + m_protectedStreamKey.clear(); + m_binaryPool.clear(); + + StoreDataStream headerStream(m_device); + headerStream.open(QIODevice::ReadOnly); + QIODevice* headerIo = &headerStream; + + bool ok; + + quint32 signature1 = Endian::readSizedInt(headerIo, KeePass2::BYTEORDER, &ok); + if (!ok || signature1 != KeePass2::SIGNATURE_1) { + raiseError(tr("Not a KeePass database.")); + return nullptr; + } + + quint32 signature2 = Endian::readSizedInt(headerIo, KeePass2::BYTEORDER, &ok); + if (ok && signature2 == KeePass1::SIGNATURE_2) { + raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" + "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; + } + else if (!ok || signature2 != KeePass2::SIGNATURE_2) { + raiseError(tr("Not a KeePass database.")); + return nullptr; + } + + quint32 version = Endian::readSizedInt(headerIo, KeePass2::BYTEORDER, &ok) + & KeePass2::FILE_VERSION_CRITICAL_MASK; + if (!ok || version != (KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK)) { + raiseError(tr("Unsupported KeePass KDBX 4 database version.")); + return nullptr; + } + + while (readHeaderField(headerIo) && !hasError()) { + } + + headerStream.close(); + + if (hasError()) { + return nullptr; + } + + // check if all required headers were present + if (m_masterSeed.isEmpty() + || m_encryptionIV.isEmpty() + || m_db->cipher().isNull()) { + raiseError("missing database headers"); + return nullptr; + } + + if (!m_db->setKey(key, false)) { + raiseError(tr("Unable to calculate master key")); + return nullptr; + } + + if (m_db->challengeMasterSeed(m_masterSeed) == false) { + raiseError(tr("Unable to issue challenge-response.")); + return nullptr; + } + + CryptoHash hash(CryptoHash::Sha256); + hash.addData(m_masterSeed); + hash.addData(m_db->challengeResponseKey()); + hash.addData(m_db->transformedMasterKey()); + QByteArray finalKey = hash.result(); + + QByteArray headerSha256 = m_device->read(32); + QByteArray headerHmac = m_device->read(32); + if (headerSha256.size() != 32 || headerHmac.size() != 32) { + raiseError("Invalid header checksum size"); + return nullptr; + } + if (headerSha256 != CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256)) { + raiseError("Header SHA256 mismatch"); + return nullptr; + } + + QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, m_db->transformedMasterKey()); + if (headerHmac != CryptoHash::hmac(headerStream.storedData(), + HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) { + raiseError(tr("Wrong key or database file is corrupt. (HMAC mismatch)")); + return nullptr; + } + HmacBlockStream hmacStream(m_device, hmacKey); + if (!hmacStream.open(QIODevice::ReadOnly)) { + raiseError(hmacStream.errorString()); + return nullptr; + } + + SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher()); + if (cipher == SymmetricCipher::InvalidAlgorithm) { + raiseError("Unknown cipher"); + return nullptr; + } + SymmetricCipherStream cipherStream(&hmacStream, cipher, + SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt); + if (!cipherStream.init(finalKey, m_encryptionIV)) { + raiseError(cipherStream.errorString()); + return nullptr; + } + if (!cipherStream.open(QIODevice::ReadOnly)) { + raiseError(cipherStream.errorString()); + return nullptr; + } + + QIODevice* xmlDevice; + QScopedPointer ioCompressor; + + if (m_db->compressionAlgo() == Database::CompressionNone) { + xmlDevice = &cipherStream; + } else { + ioCompressor.reset(new QtIOCompressor(&cipherStream)); + ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); + if (!ioCompressor->open(QIODevice::ReadOnly)) { + raiseError(ioCompressor->errorString()); + return nullptr; + } + xmlDevice = ioCompressor.data(); + } + + + while (readInnerHeaderField(xmlDevice) && !hasError()) { + } + + if (hasError()) { + return nullptr; + } + + KeePass2RandomStream randomStream(m_irsAlgo); + if (!randomStream.init(m_protectedStreamKey)) { + raiseError(randomStream.errorString()); + return nullptr; + } + + QScopedPointer buffer; + + if (m_saveXml) { + m_xmlData = xmlDevice->readAll(); + buffer.reset(new QBuffer(&m_xmlData)); + buffer->open(QIODevice::ReadOnly); + xmlDevice = buffer.data(); + } + + Kdbx4XmlReader xmlReader(m_binaryPool); + xmlReader.readDatabase(xmlDevice, m_db, &randomStream); + + if (xmlReader.hasError()) { + raiseError(xmlReader.errorString()); + if (keepDatabase) { + return db.take(); + } + else { + return nullptr; + } + } + + return db.take(); +} + +bool Kdbx4Reader::readHeaderField(QIODevice* device) +{ + QByteArray fieldIDArray = device->read(1); + if (fieldIDArray.size() != 1) { + raiseError("Invalid header id size"); + return false; + } + quint8 fieldID = fieldIDArray.at(0); + + bool ok; + quint32 fieldLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + if (!ok) { + raiseError("Invalid header field length"); + return false; + } + + QByteArray fieldData; + if (fieldLen != 0) { + fieldData = device->read(fieldLen); + if (static_cast(fieldData.size()) != fieldLen) { + raiseError("Invalid header data length"); + return false; + } + } + + switch (fieldID) { + case KeePass2::EndOfHeader: + return false; + + case KeePass2::CipherID: + setCipher(fieldData); + break; + + case KeePass2::CompressionFlags: + setCompressionFlags(fieldData); + break; + + case KeePass2::MasterSeed: + setMasterSeed(fieldData); + break; + + case KeePass2::EncryptionIV: + setEncryptionIV(fieldData); + break; + + case KeePass2::KdfParameters: { + QBuffer bufIoDevice(&fieldData); + if (!bufIoDevice.open(QIODevice::ReadOnly)) { + raiseError("Failed to open buffer for KDF parameters in header"); + return false; + } + QVariantMap kdfParams = readVariantMap(&bufIoDevice); + QSharedPointer kdf = KeePass2::kdfFromParameters(kdfParams); + if (kdf == nullptr) { + raiseError("Invalid KDF parameters"); + return false; + } + m_db->setKdf(kdf); + break; + } + + case KeePass2::PublicCustomData: + m_db->setPublicCustomData(fieldData); + break; + + case KeePass2::ProtectedStreamKey: + case KeePass2::TransformRounds: + case KeePass2::TransformSeed: + case KeePass2::StreamStartBytes: + case KeePass2::InnerRandomStreamID: + raiseError("Legacy header fields found in KDBX4 file."); + return false; + + default: + qWarning("Unknown header field read: id=%d", fieldID); + break; + } + + return true; +} + +bool Kdbx4Reader::readInnerHeaderField(QIODevice* device) +{ + QByteArray fieldIDArray = device->read(1); + if (fieldIDArray.size() != 1) { + raiseError("Invalid inner header id size"); + return false; + } + KeePass2::InnerHeaderFieldID fieldID = static_cast(fieldIDArray.at(0)); + + bool ok; + quint32 fieldLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + if (!ok) { + raiseError("Invalid inner header field length"); + return false; + } + + QByteArray fieldData; + if (fieldLen != 0) { + fieldData = device->read(fieldLen); + if (static_cast(fieldData.size()) != fieldLen) { + raiseError("Invalid header data length"); + return false; + } + } + + switch (fieldID) { + case KeePass2::InnerHeaderFieldID::End: + return false; + + case KeePass2::InnerHeaderFieldID::InnerRandomStreamID: + setInnerRandomStreamID(fieldData); + break; + + case KeePass2::InnerHeaderFieldID::InnerRandomStreamKey: + setProtectedStreamKey(fieldData); + break; + + case KeePass2::InnerHeaderFieldID::Binary: + if (fieldLen < 1) { + raiseError("Invalid inner header binary size"); + return false; + } + m_binaryPool.insert(QString::number(m_binaryPool.size()), fieldData.mid(1)); + break; + + default: + qWarning("Unknown inner header field read: id=%hhu", static_cast(fieldID)); + break; + } + + return true; +} + +QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device) +{ + bool ok; + quint16 version = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok) + & KeePass2::VARIANTMAP_CRITICAL_MASK; + quint16 maxVersion = KeePass2::VARIANTMAP_VERSION & KeePass2::VARIANTMAP_CRITICAL_MASK; + if (!ok || (version > maxVersion)) { + raiseError(tr("Unsupported KeePass variant map version.")); + return QVariantMap(); + } + + QVariantMap vm; + QByteArray fieldTypeArray; + KeePass2::VariantMapFieldType fieldType; + while (((fieldTypeArray = device->read(1)).size() == 1) + && ((fieldType = static_cast(fieldTypeArray.at(0))) + != KeePass2::VariantMapFieldType::End)) { + quint32 nameLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + if (!ok) { + raiseError("Invalid variant map entry name length"); + return QVariantMap(); + } + QByteArray nameBytes; + if (nameLen != 0) { + nameBytes = device->read(nameLen); + if (static_cast(nameBytes.size()) != nameLen) { + raiseError("Invalid variant map entry name data"); + return QVariantMap(); + } + } + QString name = QString::fromUtf8(nameBytes); + + quint32 valueLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + if (!ok) { + raiseError("Invalid variant map entry value length"); + return QVariantMap(); + } + QByteArray valueBytes; + if (valueLen != 0) { + valueBytes = device->read(valueLen); + if (static_cast(valueBytes.size()) != valueLen) { + raiseError("Invalid variant map entry value data"); + return QVariantMap(); + } + } + + switch (fieldType) { + case KeePass2::VariantMapFieldType::Bool: + if (valueLen == 1) { + vm.insert(name, QVariant(valueBytes.at(0) != 0)); + } else { + raiseError("Invalid variant map Bool entry value length"); + return QVariantMap(); + } + break; + case KeePass2::VariantMapFieldType::Int32: + if (valueLen == 4) { + vm.insert(name, QVariant(Endian::bytesToSizedInt(valueBytes, KeePass2::BYTEORDER))); + } else { + raiseError("Invalid variant map Int32 entry value length"); + return QVariantMap(); + } + break; + case KeePass2::VariantMapFieldType::UInt32: + if (valueLen == 4) { + vm.insert(name, QVariant(Endian::bytesToSizedInt(valueBytes, KeePass2::BYTEORDER))); + } else { + raiseError("Invalid variant map UInt32 entry value length"); + return QVariantMap(); + } + break; + case KeePass2::VariantMapFieldType::Int64: + if (valueLen == 8) { + vm.insert(name, QVariant(Endian::bytesToSizedInt(valueBytes, KeePass2::BYTEORDER))); + } else { + raiseError("Invalid variant map Int64 entry value length"); + return QVariantMap(); + } + break; + case KeePass2::VariantMapFieldType::UInt64: + if (valueLen == 8) { + vm.insert(name, QVariant(Endian::bytesToSizedInt(valueBytes, KeePass2::BYTEORDER))); + } else { + raiseError("Invalid variant map UInt64 entry value length"); + return QVariantMap(); + } + break; + case KeePass2::VariantMapFieldType::String: + vm.insert(name, QVariant(QString::fromUtf8(valueBytes))); + break; + case KeePass2::VariantMapFieldType::ByteArray: + vm.insert(name, QVariant(valueBytes)); + break; + default: + raiseError("Invalid variant map entry type"); + return QVariantMap(); + } + } + + if (fieldTypeArray.size() != 1) { + raiseError("Invalid variant map field type size"); + return QVariantMap(); + } + + return vm; +} + +void Kdbx4Reader::setCipher(const QByteArray& data) +{ + if (data.size() != Uuid::Length) { + raiseError("Invalid cipher uuid length"); + } else { + Uuid uuid(data); + + if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { + raiseError("Unsupported cipher"); + } else { + m_db->setCipher(uuid); + } + } +} + +void Kdbx4Reader::setCompressionFlags(const QByteArray& data) +{ + if (data.size() != 4) { + raiseError("Invalid compression flags length"); + } else { + quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); + + if (id > Database::CompressionAlgorithmMax) { + raiseError("Unsupported compression algorithm"); + } else { + m_db->setCompressionAlgo(static_cast(id)); + } + } +} + +void Kdbx4Reader::setMasterSeed(const QByteArray& data) +{ + if (data.size() != 32) { + raiseError("Invalid master seed size"); + } else { + m_masterSeed = data; + } +} + +void Kdbx4Reader::setEncryptionIV(const QByteArray& data) +{ + m_encryptionIV = data; +} + +void Kdbx4Reader::setProtectedStreamKey(const QByteArray& data) +{ + m_protectedStreamKey = data; +} + +void Kdbx4Reader::setInnerRandomStreamID(const QByteArray& data) +{ + if (data.size() != 4) { + raiseError("Invalid random stream id size"); + } else { + quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); + KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id); + if (irsAlgo == KeePass2::InvalidProtectedStreamAlgo || irsAlgo == KeePass2::ArcFourVariant) { + raiseError("Invalid inner random stream cipher"); + } else { + m_irsAlgo = irsAlgo; + } + } +} + +QHash Kdbx4Reader::binaryPool() +{ + return m_binaryPool; +} diff --git a/src/format/Kdbx4Reader.h b/src/format/Kdbx4Reader.h new file mode 100644 index 000000000..0375209c4 --- /dev/null +++ b/src/format/Kdbx4Reader.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_KDBX4READER_H +#define KEEPASSX_KDBX4READER_H + +#include +#include +#include +#include + +#include "format/KeePass2.h" +#include "format/KeePass2Reader.h" +#include "crypto/SymmetricCipher.h" +#include "keys/CompositeKey.h" + +class Database; +class QIODevice; + +class Kdbx4Reader : public BaseKeePass2Reader +{ + Q_DECLARE_TR_FUNCTIONS(Kdbx4Reader) + +public: + Kdbx4Reader(); + + using BaseKeePass2Reader::readDatabase; + virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override; + + QHash binaryPool(); + +private: + bool readHeaderField(QIODevice* device); + bool readInnerHeaderField(QIODevice* device); + QVariantMap readVariantMap(QIODevice* device); + + void setCipher(const QByteArray& data); + void setCompressionFlags(const QByteArray& data); + void setMasterSeed(const QByteArray& data); + void setEncryptionIV(const QByteArray& data); + void setProtectedStreamKey(const QByteArray& data); + void setInnerRandomStreamID(const QByteArray& data); + + QIODevice* m_device; + + Database* m_db; + QByteArray m_masterSeed; + QByteArray m_encryptionIV; + QHash m_binaryPool; +}; + +#endif // KEEPASSX_KDBX4READER_H diff --git a/src/format/Kdbx4Writer.cpp b/src/format/Kdbx4Writer.cpp new file mode 100644 index 000000000..49d04c853 --- /dev/null +++ b/src/format/Kdbx4Writer.cpp @@ -0,0 +1,326 @@ +/* + * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Kdbx4Writer.h" + +#include +#include +#include +#include +#include + +#include "streams/HmacBlockStream.h" +#include "core/Database.h" +#include "core/Endian.h" +#include "crypto/CryptoHash.h" +#include "crypto/Random.h" +#include "format/KeePass2RandomStream.h" +#include "format/Kdbx4XmlWriter.h" +#include "streams/QtIOCompressor" +#include "streams/SymmetricCipherStream.h" + +#define CHECK_RETURN_FALSE(x) if (!(x)) return false; + +Kdbx4Writer::Kdbx4Writer() + : m_device(nullptr) +{ +} + +bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) +{ + m_error = false; + m_errorStr.clear(); + + SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher()); + if (algo == SymmetricCipher::InvalidAlgorithm) { + raiseError("Invalid symmetric cipher algorithm."); + return false; + } + int ivSize = SymmetricCipher::algorithmIvSize(algo); + if (ivSize < 0) { + raiseError("Invalid symmetric cipher IV size."); + return false; + } + + QByteArray masterSeed = randomGen()->randomArray(32); + QByteArray encryptionIV = randomGen()->randomArray(ivSize); + QByteArray protectedStreamKey = randomGen()->randomArray(64); + QByteArray startBytes; + QByteArray endOfHeader = "\r\n\r\n"; + + if (db->challengeMasterSeed(masterSeed) == false) { + raiseError(tr("Unable to issue challenge-response.")); + return false; + } + + if (!db->setKey(db->key(), false, true)) { + raiseError(tr("Unable to calculate master key")); + return false; + } + + CryptoHash hash(CryptoHash::Sha256); + hash.addData(masterSeed); + hash.addData(db->challengeResponseKey()); + Q_ASSERT(!db->transformedMasterKey().isEmpty()); + hash.addData(db->transformedMasterKey()); + QByteArray finalKey = hash.result(); + + QByteArray headerData; + { + QBuffer header; + header.open(QIODevice::WriteOnly); + m_device = &header; + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::FILE_VERSION_4, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray())); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CompressionFlags, + Endian::sizedIntToBytes(static_cast(db->compressionAlgo()), + KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::MasterSeed, masterSeed)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EncryptionIV, encryptionIV)); + + // Convert current Kdf to basic parameters + QVariantMap kdfParams = KeePass2::kdfToParameters(db->kdf()); + + QByteArray kdfParamBytes; + if (!serializeVariantMap(kdfParams, kdfParamBytes)) { + raiseError("Failed to serialise KDF parameters variant map"); + return false; + } + QByteArray publicCustomData = db->publicCustomData(); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::KdfParameters, kdfParamBytes)); + if (!publicCustomData.isEmpty()) { + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::PublicCustomData, publicCustomData)); + } + + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EndOfHeader, endOfHeader)); + header.close(); + m_device = device; + headerData = header.data(); + } + CHECK_RETURN_FALSE(writeData(headerData)); + QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256); + + QScopedPointer firstLayer, secondLayer; + + QByteArray hmacKey = KeePass2::hmacKey(masterSeed, db->transformedMasterKey()); + QByteArray headerHmac = CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), + CryptoHash::Sha256); + CHECK_RETURN_FALSE(writeData(headerHash)); + CHECK_RETURN_FALSE(writeData(headerHmac)); + + HmacBlockStream* hmacStream = new HmacBlockStream(device, hmacKey); + if (!hmacStream->open(QIODevice::WriteOnly)) { + raiseError(hmacStream->errorString()); + return false; + } + firstLayer.reset(static_cast(hmacStream)); + + SymmetricCipherStream* cipherStream = new SymmetricCipherStream(hmacStream, algo, + SymmetricCipher::algorithmMode(algo), + SymmetricCipher::Encrypt); + if (!cipherStream->init(finalKey, encryptionIV)) { + raiseError(cipherStream->errorString()); + return false; + } + if (!cipherStream->open(QIODevice::WriteOnly)) { + raiseError(cipherStream->errorString()); + return false; + } + secondLayer.reset(static_cast(cipherStream)); + + QScopedPointer ioCompressor; + if (db->compressionAlgo() == Database::CompressionNone) { + m_device = secondLayer.data(); + } else { + ioCompressor.reset(new QtIOCompressor(secondLayer.data())); + ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); + if (!ioCompressor->open(QIODevice::WriteOnly)) { + raiseError(ioCompressor->errorString()); + return false; + } + m_device = ioCompressor.data(); + } + + QHash idMap; + + CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::InnerRandomStreamID, + Endian::sizedIntToBytes(static_cast(KeePass2::ChaCha20), + KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::InnerRandomStreamKey, + protectedStreamKey)); + const QList allEntries = db->rootGroup()->entriesRecursive(true); + int nextId = 0; + + for (Entry* entry : allEntries) { + const QList attachmentKeys = entry->attachments()->keys(); + for (const QString& key : attachmentKeys) { + QByteArray data = entry->attachments()->value(key); + if (!idMap.contains(data)) { + CHECK_RETURN_FALSE(writeBinary(data)); + idMap.insert(data, nextId++); + } + } + } + CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::End, QByteArray())); + + KeePass2RandomStream randomStream(KeePass2::ChaCha20); + if (!randomStream.init(protectedStreamKey)) { + raiseError(randomStream.errorString()); + return false; + } + + Kdbx4XmlWriter xmlWriter(KeePass2::FILE_VERSION_4, idMap); + xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash); + + // Explicitly close/reset streams so they are flushed and we can detect + // errors. QIODevice::close() resets errorString() etc. + if (ioCompressor) { + ioCompressor->close(); + } + if (!secondLayer->reset()) { + raiseError(secondLayer->errorString()); + return false; + } + if (!firstLayer->reset()) { + raiseError(firstLayer->errorString()); + return false; + } + + if (xmlWriter.hasError()) { + raiseError(xmlWriter.errorString()); + return false; + } + + return true; +} + +bool Kdbx4Writer::writeData(const QByteArray& data) +{ + if (m_device->write(data) != data.size()) { + raiseError(m_device->errorString()); + return false; + } + else { + return true; + } +} + +bool Kdbx4Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data) +{ + QByteArray fieldIdArr; + fieldIdArr[0] = fieldId; + CHECK_RETURN_FALSE(writeData(fieldIdArr)); + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size()), KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(data)); + + return true; +} + +bool Kdbx4Writer::writeInnerHeaderField(KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data) +{ + QByteArray fieldIdArr; + fieldIdArr[0] = static_cast(fieldId); + CHECK_RETURN_FALSE(writeData(fieldIdArr)); + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size()), KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(data)); + + return true; +} + +bool Kdbx4Writer::writeBinary(const QByteArray& data) +{ + QByteArray fieldIdArr; + fieldIdArr[0] = static_cast(KeePass2::InnerHeaderFieldID::Binary); + CHECK_RETURN_FALSE(writeData(fieldIdArr)); + CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size() + 1), KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(QByteArray(1, '\1'))); + CHECK_RETURN_FALSE(writeData(data)); + + return true; +} + +bool Kdbx4Writer::serializeVariantMap(const QVariantMap& p, QByteArray& o) +{ + QBuffer buf(&o); + buf.open(QIODevice::WriteOnly); + CHECK_RETURN_FALSE(buf.write(Endian::sizedIntToBytes(KeePass2::VARIANTMAP_VERSION, KeePass2::BYTEORDER)) == 2); + + bool ok; + QList keys = p.keys(); + for (int i = 0; i < keys.size(); ++i) { + QString k = keys.at(i); + KeePass2::VariantMapFieldType fieldType; + QByteArray data; + QVariant v = p.value(k); + switch (static_cast(v.type())) { + case QMetaType::Type::Int: + fieldType = KeePass2::VariantMapFieldType::Int32; + data = Endian::sizedIntToBytes(v.toInt(&ok), KeePass2::BYTEORDER); + CHECK_RETURN_FALSE(ok); + break; + case QMetaType::Type::UInt: + fieldType = KeePass2::VariantMapFieldType::UInt32; + data = Endian::sizedIntToBytes(v.toUInt(&ok), KeePass2::BYTEORDER); + CHECK_RETURN_FALSE(ok); + break; + case QMetaType::Type::LongLong: + fieldType = KeePass2::VariantMapFieldType::Int64; + data = Endian::sizedIntToBytes(v.toLongLong(&ok), KeePass2::BYTEORDER); + CHECK_RETURN_FALSE(ok); + break; + case QMetaType::Type::ULongLong: + fieldType = KeePass2::VariantMapFieldType::UInt64; + data = Endian::sizedIntToBytes(v.toULongLong(&ok), KeePass2::BYTEORDER); + CHECK_RETURN_FALSE(ok); + break; + case QMetaType::Type::QString: + fieldType = KeePass2::VariantMapFieldType::String; + data = v.toString().toUtf8(); + break; + case QMetaType::Type::Bool: + fieldType = KeePass2::VariantMapFieldType::Bool; + data = QByteArray(1, (v.toBool() ? '\1' : '\0')); + break; + case QMetaType::Type::QByteArray: + fieldType = KeePass2::VariantMapFieldType::ByteArray; + data = v.toByteArray(); + break; + default: + qWarning("Unknown object type %d in QVariantMap", v.type()); + return false; + } + QByteArray typeBytes; + typeBytes[0] = static_cast(fieldType); + QByteArray nameBytes = k.toUtf8(); + QByteArray nameLenBytes = Endian::sizedIntToBytes(nameBytes.size(), KeePass2::BYTEORDER); + QByteArray dataLenBytes = Endian::sizedIntToBytes(data.size(), KeePass2::BYTEORDER); + + CHECK_RETURN_FALSE(buf.write(typeBytes) == 1); + CHECK_RETURN_FALSE(buf.write(nameLenBytes) == 4); + CHECK_RETURN_FALSE(buf.write(nameBytes) == nameBytes.size()); + CHECK_RETURN_FALSE(buf.write(dataLenBytes) == 4); + CHECK_RETURN_FALSE(buf.write(data) == data.size()); + } + + QByteArray endBytes; + endBytes[0] = static_cast(KeePass2::VariantMapFieldType::End); + CHECK_RETURN_FALSE(buf.write(endBytes) == 1); + return true; +} diff --git a/src/format/Kdbx4Writer.h b/src/format/Kdbx4Writer.h new file mode 100644 index 000000000..4e703324d --- /dev/null +++ b/src/format/Kdbx4Writer.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_KDBX4WRITER_H +#define KEEPASSX_KDBX4WRITER_H + +#include + +#include "format/KeePass2.h" +#include "format/KeePass2Writer.h" +#include "keys/CompositeKey.h" + +class Database; +class QIODevice; + +class Kdbx4Writer : public BaseKeePass2Writer +{ + Q_DECLARE_TR_FUNCTIONS(Kdbx4Writer) + +public: + Kdbx4Writer(); + + using BaseKeePass2Writer::writeDatabase; + bool writeDatabase(QIODevice* device, Database* db); + +private: + bool writeData(const QByteArray& data); + bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data); + bool writeInnerHeaderField(KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data); + + QIODevice* m_device; + + bool writeBinary(const QByteArray& data); + + static bool serializeVariantMap(const QVariantMap& p, QByteArray& o); +}; + +#endif // KEEPASSX_KDBX4WRITER_H diff --git a/src/format/Kdbx4XmlReader.cpp b/src/format/Kdbx4XmlReader.cpp new file mode 100644 index 000000000..10dfe6475 --- /dev/null +++ b/src/format/Kdbx4XmlReader.cpp @@ -0,0 +1,1080 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Kdbx4XmlReader.h" + +#include +#include +#include + +#include "core/Endian.h" +#include "core/Database.h" +#include "core/DatabaseIcons.h" +#include "core/Group.h" +#include "core/Metadata.h" +#include "core/Tools.h" +#include "format/KeePass2RandomStream.h" +#include "streams/QtIOCompressor" + +typedef QPair StringPair; + +Kdbx4XmlReader::Kdbx4XmlReader() + : m_randomStream(nullptr) + , m_db(nullptr) + , m_meta(nullptr) + , m_tmpParent(nullptr) + , m_error(false) + , m_strictMode(false) +{ +} + +Kdbx4XmlReader::Kdbx4XmlReader(QHash& binaryPool) + : Kdbx4XmlReader() +{ + m_binaryPool = binaryPool; +} + +void Kdbx4XmlReader::setStrictMode(bool strictMode) +{ + m_strictMode = strictMode; +} + +void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) +{ + m_error = false; + m_errorStr.clear(); + + m_xml.clear(); + m_xml.setDevice(device); + + m_db = db; + m_meta = m_db->metadata(); + m_meta->setUpdateDatetime(false); + + m_randomStream = randomStream; + m_headerHash.clear(); + + m_tmpParent = new Group(); + + bool rootGroupParsed = false; + + if (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "KeePassFile") { + rootGroupParsed = parseKeePassFile(); + } + } + + if (!m_xml.hasError() && !rootGroupParsed) { + raiseError("No root group"); + } + + if (!m_xml.hasError()) { + if (!m_tmpParent->children().isEmpty()) { + qWarning("Kdbx4XmlReader::readDatabase: found %d invalid group reference(s)", + m_tmpParent->children().size()); + } + + if (!m_tmpParent->entries().isEmpty()) { + qWarning("Kdbx4XmlReader::readDatabase: found %d invalid entry reference(s)", + m_tmpParent->children().size()); + } + } + + const QSet poolKeys = m_binaryPool.keys().toSet(); + const QSet entryKeys = m_binaryMap.keys().toSet(); + const QSet unmappedKeys = entryKeys - poolKeys; + const QSet unusedKeys = poolKeys - entryKeys; + + if (!unmappedKeys.isEmpty()) { + raiseError("Unmapped keys left."); + } + + if (!m_xml.hasError()) { + for (const QString& key : unusedKeys) { + qWarning("Kdbx4XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); + } + } + + QHash >::const_iterator i; + for (i = m_binaryMap.constBegin(); i != m_binaryMap.constEnd(); ++i) { + const QPair& target = i.value(); + target.first->attachments()->set(target.second, m_binaryPool[i.key()]); + } + + m_meta->setUpdateDatetime(true); + + QHash::const_iterator iGroup; + for (iGroup = m_groups.constBegin(); iGroup != m_groups.constEnd(); ++iGroup) { + iGroup.value()->setUpdateTimeinfo(true); + } + + QHash::const_iterator iEntry; + for (iEntry = m_entries.constBegin(); iEntry != m_entries.constEnd(); ++iEntry) { + iEntry.value()->setUpdateTimeinfo(true); + + const QList historyItems = iEntry.value()->historyItems(); + for (Entry* histEntry : historyItems) { + histEntry->setUpdateTimeinfo(true); + } + } + + delete m_tmpParent; +} + +Database* Kdbx4XmlReader::readDatabase(QIODevice* device) +{ + Database* db = new Database(); + readDatabase(device, db); + return db; +} + +Database* Kdbx4XmlReader::readDatabase(const QString& filename) +{ + QFile file(filename); + file.open(QIODevice::ReadOnly); + return readDatabase(&file); +} + +bool Kdbx4XmlReader::hasError() +{ + return m_error || m_xml.hasError(); +} + +QString Kdbx4XmlReader::errorString() +{ + if (m_error) { + return m_errorStr; + } else if (m_xml.hasError()) { + return QString("XML error:\n%1\nLine %2, column %3") + .arg(m_xml.errorString()) + .arg(m_xml.lineNumber()) + .arg(m_xml.columnNumber()); + } else { + return QString(); + } +} + +void Kdbx4XmlReader::raiseError(const QString& errorMessage) +{ + m_error = true; + m_errorStr = errorMessage; +} + +QByteArray Kdbx4XmlReader::headerHash() +{ + return m_headerHash; +} + +bool Kdbx4XmlReader::parseKeePassFile() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile"); + + bool rootElementFound = false; + bool rootParsedSuccessfully = false; + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Meta") { + parseMeta(); + } else if (m_xml.name() == "Root") { + if (rootElementFound) { + rootParsedSuccessfully = false; + raiseError("Multiple root elements"); + } else { + rootParsedSuccessfully = parseRoot(); + rootElementFound = true; + } + } else { + skipCurrentElement(); + } + } + + return rootParsedSuccessfully; +} + +void Kdbx4XmlReader::parseMeta() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Meta"); + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Generator") { + m_meta->setGenerator(readString()); + } else if (m_xml.name() == "HeaderHash") { + m_headerHash = readBinary(); + } else if (m_xml.name() == "DatabaseName") { + m_meta->setName(readString()); + } else if (m_xml.name() == "DatabaseNameChanged") { + m_meta->setNameChanged(readDateTime()); + } else if (m_xml.name() == "DatabaseDescription") { + m_meta->setDescription(readString()); + } else if (m_xml.name() == "DatabaseDescriptionChanged") { + m_meta->setDescriptionChanged(readDateTime()); + } else if (m_xml.name() == "DefaultUserName") { + m_meta->setDefaultUserName(readString()); + } else if (m_xml.name() == "DefaultUserNameChanged") { + m_meta->setDefaultUserNameChanged(readDateTime()); + } else if (m_xml.name() == "MaintenanceHistoryDays") { + m_meta->setMaintenanceHistoryDays(readNumber()); + } else if (m_xml.name() == "Color") { + m_meta->setColor(readColor()); + } else if (m_xml.name() == "MasterKeyChanged") { + m_meta->setMasterKeyChanged(readDateTime()); + } else if (m_xml.name() == "MasterKeyChangeRec") { + m_meta->setMasterKeyChangeRec(readNumber()); + } else if (m_xml.name() == "MasterKeyChangeForce") { + m_meta->setMasterKeyChangeForce(readNumber()); + } else if (m_xml.name() == "MemoryProtection") { + parseMemoryProtection(); + } else if (m_xml.name() == "CustomIcons") { + parseCustomIcons(); + } else if (m_xml.name() == "RecycleBinEnabled") { + m_meta->setRecycleBinEnabled(readBool()); + } else if (m_xml.name() == "RecycleBinUUID") { + m_meta->setRecycleBin(getGroup(readUuid())); + } else if (m_xml.name() == "RecycleBinChanged") { + m_meta->setRecycleBinChanged(readDateTime()); + } else if (m_xml.name() == "EntryTemplatesGroup") { + m_meta->setEntryTemplatesGroup(getGroup(readUuid())); + } else if (m_xml.name() == "EntryTemplatesGroupChanged") { + m_meta->setEntryTemplatesGroupChanged(readDateTime()); + } else if (m_xml.name() == "LastSelectedGroup") { + m_meta->setLastSelectedGroup(getGroup(readUuid())); + } else if (m_xml.name() == "LastTopVisibleGroup") { + m_meta->setLastTopVisibleGroup(getGroup(readUuid())); + } else if (m_xml.name() == "HistoryMaxItems") { + int value = readNumber(); + if (value >= -1) { + m_meta->setHistoryMaxItems(value); + } else { + raiseError("HistoryMaxItems invalid number"); + } + } else if (m_xml.name() == "HistoryMaxSize") { + int value = readNumber(); + if (value >= -1) { + m_meta->setHistoryMaxSize(value); + } else { + raiseError("HistoryMaxSize invalid number"); + } + } else if (m_xml.name() == "Binaries") { + parseBinaries(); + } else if (m_xml.name() == "CustomData") { + parseCustomData(); + } else if (m_xml.name() == "SettingsChanged") { + m_meta->setSettingsChanged(readDateTime()); + } else { + skipCurrentElement(); + } + } +} + +void Kdbx4XmlReader::parseMemoryProtection() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "MemoryProtection"); + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "ProtectTitle") { + m_meta->setProtectTitle(readBool()); + } else if (m_xml.name() == "ProtectUserName") { + m_meta->setProtectUsername(readBool()); + } else if (m_xml.name() == "ProtectPassword") { + m_meta->setProtectPassword(readBool()); + } else if (m_xml.name() == "ProtectURL") { + m_meta->setProtectUrl(readBool()); + } else if (m_xml.name() == "ProtectNotes") { + m_meta->setProtectNotes(readBool()); + } else { + skipCurrentElement(); + } + } +} + +void Kdbx4XmlReader::parseCustomIcons() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomIcons"); + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Icon") { + parseIcon(); + } else { + skipCurrentElement(); + } + } +} + +void Kdbx4XmlReader::parseIcon() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Icon"); + + Uuid uuid; + QImage icon; + bool uuidSet = false; + bool iconSet = false; + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "UUID") { + uuid = readUuid(); + uuidSet = !uuid.isNull(); + } else if (m_xml.name() == "Data") { + icon.loadFromData(readBinary()); + iconSet = true; + } else { + skipCurrentElement(); + } + } + + if (uuidSet && iconSet) { + m_meta->addCustomIcon(uuid, icon); + } else { + raiseError("Missing icon uuid or data"); + } +} + +void Kdbx4XmlReader::parseBinaries() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries"); + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Binary") { + QXmlStreamAttributes attr = m_xml.attributes(); + + QString id = attr.value("ID").toString(); + + QByteArray data; + if (attr.value("Compressed").compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) { + data = readCompressedBinary(); + } else { + data = readBinary(); + } + + if (m_binaryPool.contains(id)) { + qWarning("Kdbx4XmlReader::parseBinaries: overwriting binary item \"%s\"", + qPrintable(id)); + } + + m_binaryPool.insert(id, data); + } else { + skipCurrentElement(); + } + } +} + +void Kdbx4XmlReader::parseCustomData() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData"); + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Item") { + parseCustomDataItem(); + } else { + skipCurrentElement(); + } + } +} + +void Kdbx4XmlReader::parseCustomDataItem() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item"); + + QString key; + QString value; + bool keySet = false; + bool valueSet = false; + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Key") { + key = readString(); + keySet = true; + } else if (m_xml.name() == "Value") { + value = readString(); + valueSet = true; + } else { + skipCurrentElement(); + } + } + + if (keySet && valueSet) { + m_meta->addCustomField(key, value); + } else { + raiseError("Missing custom data key or value"); + } +} + +bool Kdbx4XmlReader::parseRoot() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Root"); + + bool groupElementFound = false; + bool groupParsedSuccessfully = false; + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Group") { + if (groupElementFound) { + groupParsedSuccessfully = false; + raiseError("Multiple group elements"); + continue; + } + + Group* rootGroup = parseGroup(); + if (rootGroup) { + Group* oldRoot = m_db->rootGroup(); + m_db->setRootGroup(rootGroup); + delete oldRoot; + groupParsedSuccessfully = true; + } + + groupElementFound = true; + } else if (m_xml.name() == "DeletedObjects") { + parseDeletedObjects(); + } else { + skipCurrentElement(); + } + } + + return groupParsedSuccessfully; +} + +Group* Kdbx4XmlReader::parseGroup() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group"); + + Group* group = new Group(); + group->setUpdateTimeinfo(false); + QList children; + QList entries; + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "UUID") { + Uuid uuid = readUuid(); + if (uuid.isNull()) { + if (m_strictMode) { + raiseError("Null group uuid"); + } else { + group->setUuid(Uuid::random()); + } + } else { + group->setUuid(uuid); + } + } else if (m_xml.name() == "Name") { + group->setName(readString()); + } else if (m_xml.name() == "Notes") { + group->setNotes(readString()); + } else if (m_xml.name() == "IconID") { + int iconId = readNumber(); + if (iconId < 0) { + if (m_strictMode) { + raiseError("Invalid group icon number"); + } + iconId = 0; + } else if (iconId >= DatabaseIcons::IconCount) { + qWarning("Kdbx4XmlReader::parseGroup: icon id \"%d\" not supported", iconId); + iconId = DatabaseIcons::IconCount - 1; + } + + group->setIcon(iconId); + } else if (m_xml.name() == "CustomIconUUID") { + Uuid uuid = readUuid(); + if (!uuid.isNull()) { + group->setIcon(uuid); + } + } else if (m_xml.name() == "Times") { + group->setTimeInfo(parseTimes()); + } else if (m_xml.name() == "IsExpanded") { + group->setExpanded(readBool()); + } else if (m_xml.name() == "DefaultAutoTypeSequence") { + group->setDefaultAutoTypeSequence(readString()); + } else if (m_xml.name() == "EnableAutoType") { + QString str = readString(); + + if (str.compare("null", Qt::CaseInsensitive) == 0) { + group->setAutoTypeEnabled(Group::Inherit); + } else if (str.compare("true", Qt::CaseInsensitive) == 0) { + group->setAutoTypeEnabled(Group::Enable); + } else if (str.compare("false", Qt::CaseInsensitive) == 0) { + group->setAutoTypeEnabled(Group::Disable); + } else { + raiseError("Invalid EnableAutoType value"); + } + } else if (m_xml.name() == "EnableSearching") { + QString str = readString(); + + if (str.compare("null", Qt::CaseInsensitive) == 0) { + group->setSearchingEnabled(Group::Inherit); + } else if (str.compare("true", Qt::CaseInsensitive) == 0) { + group->setSearchingEnabled(Group::Enable); + } else if (str.compare("false", Qt::CaseInsensitive) == 0) { + group->setSearchingEnabled(Group::Disable); + } else { + raiseError("Invalid EnableSearching value"); + } + } else if (m_xml.name() == "LastTopVisibleEntry") { + group->setLastTopVisibleEntry(getEntry(readUuid())); + } else if (m_xml.name() == "Group") { + Group* newGroup = parseGroup(); + if (newGroup) { + children.append(newGroup); + } + } else if (m_xml.name() == "Entry") { + Entry* newEntry = parseEntry(false); + if (newEntry) { + entries.append(newEntry); + } + } else { + skipCurrentElement(); + } + } + + if (group->uuid().isNull() && !m_strictMode) { + group->setUuid(Uuid::random()); + } + + if (!group->uuid().isNull()) { + Group* tmpGroup = group; + group = getGroup(tmpGroup->uuid()); + group->copyDataFrom(tmpGroup); + group->setUpdateTimeinfo(false); + delete tmpGroup; + } else if (!hasError()) { + raiseError("No group uuid found"); + } + + for (Group* child : asConst(children)) { + child->setParent(group); + } + + for (Entry* entry : asConst(entries)) { + entry->setGroup(group); + } + + return group; +} + +void Kdbx4XmlReader::parseDeletedObjects() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObjects"); + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "DeletedObject") { + parseDeletedObject(); + } else { + skipCurrentElement(); + } + } +} + +void Kdbx4XmlReader::parseDeletedObject() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject"); + + DeletedObject delObj; + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "UUID") { + Uuid uuid = readUuid(); + if (uuid.isNull()) { + if (m_strictMode) { + raiseError("Null DeleteObject uuid"); + } + } else { + delObj.uuid = uuid; + } + } else if (m_xml.name() == "DeletionTime") { + delObj.deletionTime = readDateTime(); + } else { + skipCurrentElement(); + } + } + + if (!delObj.uuid.isNull() && !delObj.deletionTime.isNull()) { + m_db->addDeletedObject(delObj); + } else if (m_strictMode) { + raiseError("Missing DeletedObject uuid or time"); + } +} + +Entry* Kdbx4XmlReader::parseEntry(bool history) +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry"); + + Entry* entry = new Entry(); + entry->setUpdateTimeinfo(false); + QList historyItems; + QList binaryRefs; + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "UUID") { + Uuid uuid = readUuid(); + if (uuid.isNull()) { + if (m_strictMode) { + raiseError("Null entry uuid"); + } else { + entry->setUuid(Uuid::random()); + } + } else { + entry->setUuid(uuid); + } + } else if (m_xml.name() == "IconID") { + int iconId = readNumber(); + if (iconId < 0) { + if (m_strictMode) { + raiseError("Invalid entry icon number"); + } + iconId = 0; + } + entry->setIcon(iconId); + } else if (m_xml.name() == "CustomIconUUID") { + Uuid uuid = readUuid(); + if (!uuid.isNull()) { + entry->setIcon(uuid); + } + } else if (m_xml.name() == "ForegroundColor") { + entry->setForegroundColor(readColor()); + } else if (m_xml.name() == "BackgroundColor") { + entry->setBackgroundColor(readColor()); + } else if (m_xml.name() == "OverrideURL") { + entry->setOverrideUrl(readString()); + } else if (m_xml.name() == "Tags") { + entry->setTags(readString()); + } else if (m_xml.name() == "Times") { + entry->setTimeInfo(parseTimes()); + } else if (m_xml.name() == "String") { + parseEntryString(entry); + } else if (m_xml.name() == "Binary") { + QPair ref = parseEntryBinary(entry); + if (!ref.first.isNull() && !ref.second.isNull()) { + binaryRefs.append(ref); + } + } else if (m_xml.name() == "AutoType") { + parseAutoType(entry); + } else if (m_xml.name() == "History") { + if (history) { + raiseError("History element in history entry"); + } else { + historyItems = parseEntryHistory(); + } + } else { + skipCurrentElement(); + } + } + + if (entry->uuid().isNull() && !m_strictMode) { + entry->setUuid(Uuid::random()); + } + + if (!entry->uuid().isNull()) { + if (history) { + entry->setUpdateTimeinfo(false); + } else { + Entry* tmpEntry = entry; + + entry = getEntry(tmpEntry->uuid()); + entry->copyDataFrom(tmpEntry); + entry->setUpdateTimeinfo(false); + + delete tmpEntry; + } + } else if (!hasError()) { + raiseError("No entry uuid found"); + } + + for (Entry* historyItem : asConst(historyItems)) { + if (historyItem->uuid() != entry->uuid()) { + if (m_strictMode) { + raiseError("History element with different uuid"); + } else { + historyItem->setUuid(entry->uuid()); + } + } + entry->addHistoryItem(historyItem); + } + + for (const StringPair& ref : asConst(binaryRefs)) { + m_binaryMap.insertMulti(ref.first, qMakePair(entry, ref.second)); + } + + return entry; +} + +void Kdbx4XmlReader::parseEntryString(Entry* entry) +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "String"); + + QString key; + QString value; + bool protect = false; + bool keySet = false; + bool valueSet = false; + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Key") { + key = readString(); + keySet = true; + } else if (m_xml.name() == "Value") { + QXmlStreamAttributes attr = m_xml.attributes(); + value = readString(); + + bool isProtected = attr.value("Protected") == "True"; + bool protectInMemory = attr.value("ProtectInMemory") == "True"; + + if (isProtected && !value.isEmpty()) { + if (m_randomStream) { + QByteArray ciphertext = QByteArray::fromBase64(value.toLatin1()); + bool ok; + QByteArray plaintext = m_randomStream->process(ciphertext, &ok); + if (!ok) { + value.clear(); + raiseError(m_randomStream->errorString()); + } else { + value = QString::fromUtf8(plaintext); + } + } else { + raiseError("Unable to decrypt entry string"); + } + } + + protect = isProtected || protectInMemory; + valueSet = true; + } else { + skipCurrentElement(); + } + } + + if (keySet && valueSet) { + // the default attributes are always there so additionally check if it's empty + if (entry->attributes()->hasKey(key) && !entry->attributes()->value(key).isEmpty()) { + raiseError("Duplicate custom attribute found"); + } else { + entry->attributes()->set(key, value, protect); + } + } else { + raiseError("Entry string key or value missing"); + } +} + +QPair Kdbx4XmlReader::parseEntryBinary(Entry* entry) +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binary"); + + QPair poolRef; + + QString key; + QByteArray value; + bool keySet = false; + bool valueSet = false; + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Key") { + key = readString(); + keySet = true; + } else if (m_xml.name() == "Value") { + QXmlStreamAttributes attr = m_xml.attributes(); + + if (attr.hasAttribute("Ref")) { + poolRef = qMakePair(attr.value("Ref").toString(), key); + m_xml.skipCurrentElement(); + } else { + // format compatibility + value = readBinary(); + bool isProtected = attr.hasAttribute("Protected") + && (attr.value("Protected") == "True"); + + if (isProtected && !value.isEmpty()) { + if (!m_randomStream->processInPlace(value)) { + raiseError(m_randomStream->errorString()); + } + } + } + + valueSet = true; + } else { + skipCurrentElement(); + } + } + + if (keySet && valueSet) { + if (entry->attachments()->hasKey(key)) { + raiseError("Duplicate attachment found"); + } else { + entry->attachments()->set(key, value); + } + } else { + raiseError("Entry binary key or value missing"); + } + + return poolRef; +} + +void Kdbx4XmlReader::parseAutoType(Entry* entry) +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "AutoType"); + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Enabled") { + entry->setAutoTypeEnabled(readBool()); + } else if (m_xml.name() == "DataTransferObfuscation") { + entry->setAutoTypeObfuscation(readNumber()); + } else if (m_xml.name() == "DefaultSequence") { + entry->setDefaultAutoTypeSequence(readString()); + } else if (m_xml.name() == "Association") { + parseAutoTypeAssoc(entry); + } else { + skipCurrentElement(); + } + } +} + +void Kdbx4XmlReader::parseAutoTypeAssoc(Entry* entry) +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Association"); + + AutoTypeAssociations::Association assoc; + bool windowSet = false; + bool sequenceSet = false; + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Window") { + assoc.window = readString(); + windowSet = true; + } else if (m_xml.name() == "KeystrokeSequence") { + assoc.sequence = readString(); + sequenceSet = true; + } else { + skipCurrentElement(); + } + } + + if (windowSet && sequenceSet) { + entry->autoTypeAssociations()->add(assoc); + } else { + raiseError("Auto-type association window or sequence missing"); + } +} + +QList Kdbx4XmlReader::parseEntryHistory() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "History"); + + QList historyItems; + + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "Entry") { + historyItems.append(parseEntry(true)); + } else { + skipCurrentElement(); + } + } + + return historyItems; +} + +TimeInfo Kdbx4XmlReader::parseTimes() +{ + Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times"); + + TimeInfo timeInfo; + while (!m_xml.hasError() && m_xml.readNextStartElement()) { + if (m_xml.name() == "LastModificationTime") { + timeInfo.setLastModificationTime(readDateTime()); + } else if (m_xml.name() == "CreationTime") { + timeInfo.setCreationTime(readDateTime()); + } else if (m_xml.name() == "LastAccessTime") { + timeInfo.setLastAccessTime(readDateTime()); + } else if (m_xml.name() == "ExpiryTime") { + timeInfo.setExpiryTime(readDateTime()); + } else if (m_xml.name() == "Expires") { + timeInfo.setExpires(readBool()); + } else if (m_xml.name() == "UsageCount") { + timeInfo.setUsageCount(readNumber()); + } else if (m_xml.name() == "LocationChanged") { + timeInfo.setLocationChanged(readDateTime()); + } else { + skipCurrentElement(); + } + } + + return timeInfo; +} + +QString Kdbx4XmlReader::readString() +{ + return m_xml.readElementText(); +} + +bool Kdbx4XmlReader::readBool() +{ + QString str = readString(); + + if (str.compare("True", Qt::CaseInsensitive) == 0) { + return true; + } else if (str.compare("False", Qt::CaseInsensitive) == 0) { + return false; + } else if (str.length() == 0) { + return false; + } else { + raiseError("Invalid bool value"); + return false; + } +} + +QDateTime Kdbx4XmlReader::readDateTime() +{ + static QRegularExpression b64regex("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"); + QString str = readString(); + + if (b64regex.match(str).hasMatch()) { + QByteArray secsBytes = QByteArray::fromBase64(str.toUtf8()).leftJustified(8, '\0', true).left(8); + qint64 secs = Endian::bytesToSizedInt(secsBytes, KeePass2::BYTEORDER); + return QDateTime(QDate(1, 1, 1), QTime(0, 0, 0, 0), Qt::UTC).addSecs(secs); + } else { + QDateTime dt = QDateTime::fromString(str, Qt::ISODate); + if (dt.isValid()) { + return dt; + } else { + if (m_strictMode) { + raiseError("Invalid date time value"); + } + + return QDateTime::currentDateTimeUtc(); + } + } +} + +QColor Kdbx4XmlReader::readColor() +{ + QString colorStr = readString(); + + if (colorStr.isEmpty()) { + return QColor(); + } + + if (colorStr.length() != 7 || colorStr[0] != '#') { + if (m_strictMode) { + raiseError("Invalid color value"); + } + return QColor(); + } + + QColor color; + for (int i = 0; i <= 2; i++) { + QString rgbPartStr = colorStr.mid(1 + 2*i, 2); + bool ok; + int rgbPart = rgbPartStr.toInt(&ok, 16); + if (!ok || rgbPart > 255) { + if (m_strictMode) { + raiseError("Invalid color rgb part"); + } + return QColor(); + } + + if (i == 0) { + color.setRed(rgbPart); + } else if (i == 1) { + color.setGreen(rgbPart); + } else { + color.setBlue(rgbPart); + } + } + + return color; +} + +int Kdbx4XmlReader::readNumber() +{ + bool ok; + int result = readString().toInt(&ok); + if (!ok) { + raiseError("Invalid number value"); + } + return result; +} + +Uuid Kdbx4XmlReader::readUuid() +{ + QByteArray uuidBin = readBinary(); + if (uuidBin.isEmpty()) { + return Uuid(); + } else if (uuidBin.length() != Uuid::Length) { + if (m_strictMode) { + raiseError("Invalid uuid value"); + } + return Uuid(); + } else { + return Uuid(uuidBin); + } +} + +QByteArray Kdbx4XmlReader::readBinary() +{ + return QByteArray::fromBase64(readString().toLatin1()); +} + +QByteArray Kdbx4XmlReader::readCompressedBinary() +{ + QByteArray rawData = readBinary(); + + QBuffer buffer(&rawData); + buffer.open(QIODevice::ReadOnly); + + QtIOCompressor compressor(&buffer); + compressor.setStreamFormat(QtIOCompressor::GzipFormat); + compressor.open(QIODevice::ReadOnly); + + QByteArray result; + if (!Tools::readAllFromDevice(&compressor, result)) { + raiseError("Unable to decompress binary"); + } + return result; +} + +Group* Kdbx4XmlReader::getGroup(const Uuid& uuid) +{ + if (uuid.isNull()) { + return nullptr; + } + + if (m_groups.contains(uuid)) { + return m_groups.value(uuid); + } else { + Group* group = new Group(); + group->setUpdateTimeinfo(false); + group->setUuid(uuid); + group->setParent(m_tmpParent); + m_groups.insert(uuid, group); + return group; + } +} + +Entry* Kdbx4XmlReader::getEntry(const Uuid& uuid) +{ + if (uuid.isNull()) { + return nullptr; + } + + if (m_entries.contains(uuid)) { + return m_entries.value(uuid); + } else { + Entry* entry = new Entry(); + entry->setUpdateTimeinfo(false); + entry->setUuid(uuid); + entry->setGroup(m_tmpParent); + m_entries.insert(uuid, entry); + return entry; + } +} + +void Kdbx4XmlReader::skipCurrentElement() +{ + qWarning("Kdbx4XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString())); + m_xml.skipCurrentElement(); +} diff --git a/src/format/Kdbx4XmlReader.h b/src/format/Kdbx4XmlReader.h new file mode 100644 index 000000000..6a0a6d4f4 --- /dev/null +++ b/src/format/Kdbx4XmlReader.h @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_KDBX4XMLREADER_H +#define KEEPASSX_KDBX4XMLREADER_H + +#include +#include +#include +#include +#include +#include + +#include "core/TimeInfo.h" +#include "core/Uuid.h" + +class Database; +class Entry; +class Group; +class KeePass2RandomStream; +class Metadata; + +class Kdbx4XmlReader +{ + Q_DECLARE_TR_FUNCTIONS(Kdbx4XmlReader) + +public: + Kdbx4XmlReader(); + Kdbx4XmlReader(QHash& binaryPool); + Database* readDatabase(QIODevice* device); + void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr); + Database* readDatabase(const QString& filename); + bool hasError(); + QString errorString(); + QByteArray headerHash(); + void setStrictMode(bool strictMode); + +private: + bool parseKeePassFile(); + void parseMeta(); + void parseMemoryProtection(); + void parseCustomIcons(); + void parseIcon(); + void parseBinaries(); + void parseCustomData(); + void parseCustomDataItem(); + bool parseRoot(); + Group* parseGroup(); + void parseDeletedObjects(); + void parseDeletedObject(); + Entry* parseEntry(bool history); + void parseEntryString(Entry* entry); + QPair parseEntryBinary(Entry* entry); + void parseAutoType(Entry* entry); + void parseAutoTypeAssoc(Entry* entry); + QList parseEntryHistory(); + TimeInfo parseTimes(); + + QString readString(); + bool readBool(); + QDateTime readDateTime(); + QColor readColor(); + int readNumber(); + Uuid readUuid(); + QByteArray readBinary(); + QByteArray readCompressedBinary(); + + Group* getGroup(const Uuid& uuid); + Entry* getEntry(const Uuid& uuid); + void raiseError(const QString& errorMessage); + void skipCurrentElement(); + + QXmlStreamReader m_xml; + KeePass2RandomStream* m_randomStream; + Database* m_db; + Metadata* m_meta; + Group* m_tmpParent; + QHash m_groups; + QHash m_entries; + QHash m_binaryPool; + QHash > m_binaryMap; + QByteArray m_headerHash; + bool m_error; + QString m_errorStr; + bool m_strictMode; +}; + +#endif // KEEPASSX_KDBX4XMLREADER_H diff --git a/src/format/Kdbx4XmlWriter.cpp b/src/format/Kdbx4XmlWriter.cpp new file mode 100644 index 000000000..374744563 --- /dev/null +++ b/src/format/Kdbx4XmlWriter.cpp @@ -0,0 +1,611 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Kdbx4XmlWriter.h" + +#include +#include + +#include "core/Endian.h" +#include "core/Metadata.h" +#include "format/KeePass2RandomStream.h" +#include "streams/QtIOCompressor" + +Kdbx4XmlWriter::Kdbx4XmlWriter() + : Kdbx4XmlWriter(KeePass2::FILE_VERSION_3) +{ +} + +Kdbx4XmlWriter::Kdbx4XmlWriter(quint32 version) + : Kdbx4XmlWriter(version, QHash()) +{ +} + +Kdbx4XmlWriter::Kdbx4XmlWriter(quint32 version, QHash idMap) + : m_db(nullptr) + , m_meta(nullptr) + , m_randomStream(nullptr) + , m_idMap(idMap) + , m_error(false) + , m_version(version) +{ + m_xml.setAutoFormatting(true); + m_xml.setAutoFormattingIndent(-1); // 1 tab + m_xml.setCodec("UTF-8"); +} + +void Kdbx4XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, const QByteArray& headerHash) +{ + m_db = db; + m_meta = db->metadata(); + m_randomStream = randomStream; + m_headerHash = headerHash; + + if (m_version < KeePass2::FILE_VERSION_4 && m_idMap.isEmpty()) { + generateIdMap(); + } + + m_xml.setDevice(device); + + m_xml.writeStartDocument("1.0", true); + + m_xml.writeStartElement("KeePassFile"); + + writeMetadata(); + writeRoot(); + + m_xml.writeEndElement(); + + m_xml.writeEndDocument(); + + if (m_xml.hasError()) { + raiseError(device->errorString()); + } +} + +void Kdbx4XmlWriter::writeDatabase(const QString& filename, Database* db) +{ + QFile file(filename); + file.open(QIODevice::WriteOnly|QIODevice::Truncate); + writeDatabase(&file, db); +} + +bool Kdbx4XmlWriter::hasError() +{ + return m_error; +} + +QString Kdbx4XmlWriter::errorString() +{ + return m_errorStr; +} + +void Kdbx4XmlWriter::generateIdMap() +{ + const QList allEntries = m_db->rootGroup()->entriesRecursive(true); + int nextId = 0; + + for (Entry* entry : allEntries) { + const QList attachmentKeys = entry->attachments()->keys(); + for (const QString& key : attachmentKeys) { + QByteArray data = entry->attachments()->value(key); + if (!m_idMap.contains(data)) { + m_idMap.insert(data, nextId++); + } + } + } +} + +void Kdbx4XmlWriter::writeMetadata() +{ + m_xml.writeStartElement("Meta"); + writeString("Generator", m_meta->generator()); + if (m_version < KeePass2::FILE_VERSION_4 && !m_headerHash.isEmpty()) { + writeBinary("HeaderHash", m_headerHash); + } + writeString("DatabaseName", m_meta->name()); + writeDateTime("DatabaseNameChanged", m_meta->nameChanged()); + writeString("DatabaseDescription", m_meta->description()); + writeDateTime("DatabaseDescriptionChanged", m_meta->descriptionChanged()); + writeString("DefaultUserName", m_meta->defaultUserName()); + writeDateTime("DefaultUserNameChanged", m_meta->defaultUserNameChanged()); + writeNumber("MaintenanceHistoryDays", m_meta->maintenanceHistoryDays()); + writeColor("Color", m_meta->color()); + writeDateTime("MasterKeyChanged", m_meta->masterKeyChanged()); + writeNumber("MasterKeyChangeRec", m_meta->masterKeyChangeRec()); + writeNumber("MasterKeyChangeForce", m_meta->masterKeyChangeForce()); + writeMemoryProtection(); + writeCustomIcons(); + writeBool("RecycleBinEnabled", m_meta->recycleBinEnabled()); + writeUuid("RecycleBinUUID", m_meta->recycleBin()); + writeDateTime("RecycleBinChanged", m_meta->recycleBinChanged()); + writeUuid("EntryTemplatesGroup", m_meta->entryTemplatesGroup()); + writeDateTime("EntryTemplatesGroupChanged", m_meta->entryTemplatesGroupChanged()); + writeUuid("LastSelectedGroup", m_meta->lastSelectedGroup()); + writeUuid("LastTopVisibleGroup", m_meta->lastTopVisibleGroup()); + writeNumber("HistoryMaxItems", m_meta->historyMaxItems()); + writeNumber("HistoryMaxSize", m_meta->historyMaxSize()); + if (m_version >= KeePass2::FILE_VERSION_4) { + writeDateTime("SettingsChanged", m_meta->settingsChanged()); + } + if (m_version < KeePass2::FILE_VERSION_4) { + writeBinaries(); + } + writeCustomData(); + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeMemoryProtection() +{ + m_xml.writeStartElement("MemoryProtection"); + + writeBool("ProtectTitle", m_meta->protectTitle()); + writeBool("ProtectUserName", m_meta->protectUsername()); + writeBool("ProtectPassword", m_meta->protectPassword()); + writeBool("ProtectURL", m_meta->protectUrl()); + writeBool("ProtectNotes", m_meta->protectNotes()); + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeCustomIcons() +{ + m_xml.writeStartElement("CustomIcons"); + + const QList customIconsOrder = m_meta->customIconsOrder(); + for (const Uuid& uuid : customIconsOrder) { + writeIcon(uuid, m_meta->customIcon(uuid)); + } + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) +{ + m_xml.writeStartElement("Icon"); + + writeUuid("UUID", uuid); + + QByteArray ba; + QBuffer buffer(&ba); + buffer.open(QIODevice::WriteOnly); + // TODO: check !icon.save() + icon.save(&buffer, "PNG"); + buffer.close(); + writeBinary("Data", ba); + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeBinaries() +{ + m_xml.writeStartElement("Binaries"); + + QHash::const_iterator i; + for (i = m_idMap.constBegin(); i != m_idMap.constEnd(); ++i) { + m_xml.writeStartElement("Binary"); + + m_xml.writeAttribute("ID", QString::number(i.value())); + + QByteArray data; + if (m_db->compressionAlgo() == Database::CompressionGZip) { + m_xml.writeAttribute("Compressed", "True"); + + QBuffer buffer; + buffer.open(QIODevice::ReadWrite); + + QtIOCompressor compressor(&buffer); + compressor.setStreamFormat(QtIOCompressor::GzipFormat); + compressor.open(QIODevice::WriteOnly); + + qint64 bytesWritten = compressor.write(i.key()); + Q_ASSERT(bytesWritten == i.key().size()); + Q_UNUSED(bytesWritten); + compressor.close(); + + buffer.seek(0); + data = buffer.readAll(); + } + else { + data = i.key(); + } + + if (!data.isEmpty()) { + m_xml.writeCharacters(QString::fromLatin1(data.toBase64())); + } + m_xml.writeEndElement(); + } + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeCustomData() +{ + m_xml.writeStartElement("CustomData"); + + QHash customFields = m_meta->customFields(); + const QList keyList = customFields.keys(); + for (const QString& key : keyList) { + writeCustomDataItem(key, customFields.value(key)); + } + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeCustomDataItem(const QString& key, const QString& value) +{ + m_xml.writeStartElement("Item"); + + writeString("Key", key); + writeString("Value", value); + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeRoot() +{ + Q_ASSERT(m_db->rootGroup()); + + m_xml.writeStartElement("Root"); + + writeGroup(m_db->rootGroup()); + writeDeletedObjects(); + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeGroup(const Group* group) +{ + Q_ASSERT(!group->uuid().isNull()); + + m_xml.writeStartElement("Group"); + + writeUuid("UUID", group->uuid()); + writeString("Name", group->name()); + writeString("Notes", group->notes()); + writeNumber("IconID", group->iconNumber()); + + if (!group->iconUuid().isNull()) { + writeUuid("CustomIconUUID", group->iconUuid()); + } + writeTimes(group->timeInfo()); + writeBool("IsExpanded", group->isExpanded()); + writeString("DefaultAutoTypeSequence", group->defaultAutoTypeSequence()); + + writeTriState("EnableAutoType", group->autoTypeEnabled()); + + writeTriState("EnableSearching", group->searchingEnabled()); + + writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry()); + + const QList entryList = group->entries(); + for (const Entry* entry : entryList) { + writeEntry(entry); + } + + const QList children = group->children(); + for (const Group* child : children) { + writeGroup(child); + } + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeTimes(const TimeInfo& ti) +{ + m_xml.writeStartElement("Times"); + + writeDateTime("LastModificationTime", ti.lastModificationTime()); + writeDateTime("CreationTime", ti.creationTime()); + writeDateTime("LastAccessTime", ti.lastAccessTime()); + writeDateTime("ExpiryTime", ti.expiryTime()); + writeBool("Expires", ti.expires()); + writeNumber("UsageCount", ti.usageCount()); + writeDateTime("LocationChanged", ti.locationChanged()); + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeDeletedObjects() +{ + m_xml.writeStartElement("DeletedObjects"); + + const QList delObjList = m_db->deletedObjects(); + for (const DeletedObject& delObj : delObjList) { + writeDeletedObject(delObj); + } + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeDeletedObject(const DeletedObject& delObj) +{ + m_xml.writeStartElement("DeletedObject"); + + writeUuid("UUID", delObj.uuid); + writeDateTime("DeletionTime", delObj.deletionTime); + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeEntry(const Entry* entry) +{ + Q_ASSERT(!entry->uuid().isNull()); + + m_xml.writeStartElement("Entry"); + + writeUuid("UUID", entry->uuid()); + writeNumber("IconID", entry->iconNumber()); + if (!entry->iconUuid().isNull()) { + writeUuid("CustomIconUUID", entry->iconUuid()); + } + writeColor("ForegroundColor", entry->foregroundColor()); + writeColor("BackgroundColor", entry->backgroundColor()); + writeString("OverrideURL", entry->overrideUrl()); + writeString("Tags", entry->tags()); + writeTimes(entry->timeInfo()); + + const QList attributesKeyList = entry->attributes()->keys(); + for (const QString& key : attributesKeyList) { + m_xml.writeStartElement("String"); + + bool protect = ( ((key == "Title") && m_meta->protectTitle()) || + ((key == "UserName") && m_meta->protectUsername()) || + ((key == "Password") && m_meta->protectPassword()) || + ((key == "URL") && m_meta->protectUrl()) || + ((key == "Notes") && m_meta->protectNotes()) || + entry->attributes()->isProtected(key) ); + + writeString("Key", key); + + m_xml.writeStartElement("Value"); + QString value; + + if (protect) { + if (m_randomStream) { + m_xml.writeAttribute("Protected", "True"); + bool ok; + QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok); + if (!ok) { + raiseError(m_randomStream->errorString()); + } + value = QString::fromLatin1(rawData.toBase64()); + } + else { + m_xml.writeAttribute("ProtectInMemory", "True"); + value = entry->attributes()->value(key); + } + } + else { + value = entry->attributes()->value(key); + } + + if (!value.isEmpty()) { + m_xml.writeCharacters(stripInvalidXml10Chars(value)); + } + m_xml.writeEndElement(); + + m_xml.writeEndElement(); + } + + const QList attachmentsKeyList = entry->attachments()->keys(); + for (const QString& key : attachmentsKeyList) { + m_xml.writeStartElement("Binary"); + + writeString("Key", key); + + m_xml.writeStartElement("Value"); + m_xml.writeAttribute("Ref", QString::number(m_idMap[entry->attachments()->value(key)])); + m_xml.writeEndElement(); + + m_xml.writeEndElement(); + } + + writeAutoType(entry); + // write history only for entries that are not history items + if (entry->parent()) { + writeEntryHistory(entry); + } + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeAutoType(const Entry* entry) +{ + m_xml.writeStartElement("AutoType"); + + writeBool("Enabled", entry->autoTypeEnabled()); + writeNumber("DataTransferObfuscation", entry->autoTypeObfuscation()); + writeString("DefaultSequence", entry->defaultAutoTypeSequence()); + + const QList autoTypeAssociations = entry->autoTypeAssociations()->getAll(); + for (const AutoTypeAssociations::Association& assoc : autoTypeAssociations) { + writeAutoTypeAssoc(assoc); + } + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc) +{ + m_xml.writeStartElement("Association"); + + writeString("Window", assoc.window); + writeString("KeystrokeSequence", assoc.sequence); + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeEntryHistory(const Entry* entry) +{ + m_xml.writeStartElement("History"); + + const QList& historyItems = entry->historyItems(); + for (const Entry* item : historyItems) { + writeEntry(item); + } + + m_xml.writeEndElement(); +} + +void Kdbx4XmlWriter::writeString(const QString& qualifiedName, const QString& string) +{ + if (string.isEmpty()) { + m_xml.writeEmptyElement(qualifiedName); + } + else { + m_xml.writeTextElement(qualifiedName, stripInvalidXml10Chars(string)); + } +} + +void Kdbx4XmlWriter::writeNumber(const QString& qualifiedName, int number) +{ + writeString(qualifiedName, QString::number(number)); +} + +void Kdbx4XmlWriter::writeBool(const QString& qualifiedName, bool b) +{ + if (b) { + writeString(qualifiedName, "True"); + } + else { + writeString(qualifiedName, "False"); + } +} + +void Kdbx4XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime) +{ + Q_ASSERT(dateTime.isValid()); + Q_ASSERT(dateTime.timeSpec() == Qt::UTC); + + QString dateTimeStr; + if (m_version < KeePass2::FILE_VERSION_4) { + dateTimeStr = dateTime.toString(Qt::ISODate); + + // Qt < 4.8 doesn't append a 'Z' at the end + if (!dateTimeStr.isEmpty() && dateTimeStr[dateTimeStr.size() - 1] != 'Z') { + dateTimeStr.append('Z'); + } + } else { + qint64 secs = QDateTime(QDate(1, 1, 1), QTime(0, 0, 0, 0), Qt::UTC).secsTo(dateTime); + QByteArray secsBytes = Endian::sizedIntToBytes(secs, KeePass2::BYTEORDER); + dateTimeStr = QString::fromLatin1(secsBytes.toBase64()); + } + writeString(qualifiedName, dateTimeStr); +} + +void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid) +{ + writeString(qualifiedName, uuid.toBase64()); +} + +void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) +{ + if (group) { + writeUuid(qualifiedName, group->uuid()); + } + else { + writeUuid(qualifiedName, Uuid()); + } +} + +void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) +{ + if (entry) { + writeUuid(qualifiedName, entry->uuid()); + } + else { + writeUuid(qualifiedName, Uuid()); + } +} + +void Kdbx4XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba) +{ + writeString(qualifiedName, QString::fromLatin1(ba.toBase64())); +} + +void Kdbx4XmlWriter::writeColor(const QString& qualifiedName, const QColor& color) +{ + QString colorStr; + + if (color.isValid()) { + colorStr = QString("#%1%2%3").arg(colorPartToString(color.red()), + colorPartToString(color.green()), + colorPartToString(color.blue())); + } + + writeString(qualifiedName, colorStr); +} + +void Kdbx4XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState) +{ + QString value; + + if (triState == Group::Inherit) { + value = "null"; + } + else if (triState == Group::Enable) { + value = "true"; + } + else { + value = "false"; + } + + writeString(qualifiedName, value); +} + +QString Kdbx4XmlWriter::colorPartToString(int value) +{ + QString str = QString::number(value, 16).toUpper(); + if (str.length() == 1) { + str.prepend("0"); + } + + return str; +} + +QString Kdbx4XmlWriter::stripInvalidXml10Chars(QString str) +{ + for (int i = str.size() - 1; i >= 0; i--) { + const QChar ch = str.at(i); + const ushort uc = ch.unicode(); + + if (ch.isLowSurrogate() && i != 0 && str.at(i - 1).isHighSurrogate()) { + // keep valid surrogate pair + i--; + } + else if ((uc < 0x20 && uc != 0x09 && uc != 0x0A && uc != 0x0D) // control characters + || (uc >= 0x7F && uc <= 0x84) // control characters, valid but discouraged by XML + || (uc >= 0x86 && uc <= 0x9F) // control characters, valid but discouraged by XML + || (uc > 0xFFFD) // noncharacter + || ch.isLowSurrogate() // single low surrogate + || ch.isHighSurrogate()) // single high surrogate + { + qWarning("Stripping invalid XML 1.0 codepoint %x", uc); + str.remove(i, 1); + } + } + + return str; +} + +void Kdbx4XmlWriter::raiseError(const QString& errorMessage) +{ + m_error = true; + m_errorStr = errorMessage; +} diff --git a/src/format/Kdbx4XmlWriter.h b/src/format/Kdbx4XmlWriter.h new file mode 100644 index 000000000..79f27c98b --- /dev/null +++ b/src/format/Kdbx4XmlWriter.h @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSX_KDBX4XMLWRITER_H +#define KEEPASSX_KDBX4XMLWRITER_H + +#include +#include +#include +#include + +#include "core/Database.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "core/TimeInfo.h" +#include "core/Uuid.h" + +class KeePass2RandomStream; +class Metadata; + +class Kdbx4XmlWriter +{ +public: + Kdbx4XmlWriter(); + Kdbx4XmlWriter(quint32 version); + Kdbx4XmlWriter(quint32 version, QHash idMap); + void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr, + const QByteArray& headerHash = QByteArray()); + void writeDatabase(const QString& filename, Database* db); + bool hasError(); + QString errorString(); + +private: + void generateIdMap(); + + void writeMetadata(); + void writeMemoryProtection(); + void writeCustomIcons(); + void writeIcon(const Uuid& uuid, const QImage& icon); + void writeBinaries(); + void writeCustomData(); + void writeCustomDataItem(const QString& key, const QString& value); + void writeRoot(); + void writeGroup(const Group* group); + void writeTimes(const TimeInfo& ti); + void writeDeletedObjects(); + void writeDeletedObject(const DeletedObject& delObj); + void writeEntry(const Entry* entry); + void writeAutoType(const Entry* entry); + void writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc); + void writeEntryHistory(const Entry* entry); + + void writeString(const QString& qualifiedName, const QString& string); + void writeNumber(const QString& qualifiedName, int number); + void writeBool(const QString& qualifiedName, bool b); + void writeDateTime(const QString& qualifiedName, const QDateTime& dateTime); + void writeUuid(const QString& qualifiedName, const Uuid& uuid); + void writeUuid(const QString& qualifiedName, const Group* group); + void writeUuid(const QString& qualifiedName, const Entry* entry); + void writeBinary(const QString& qualifiedName, const QByteArray& ba); + void writeColor(const QString& qualifiedName, const QColor& color); + void writeTriState(const QString& qualifiedName, Group::TriState triState); + QString colorPartToString(int value); + QString stripInvalidXml10Chars(QString str); + + void raiseError(const QString& errorMessage); + + QXmlStreamWriter m_xml; + Database* m_db; + Metadata* m_meta; + KeePass2RandomStream* m_randomStream; + QHash m_idMap; + bool m_error; + QString m_errorStr; + quint32 m_version; + QByteArray m_headerHash; +}; + +#endif // KEEPASSX_KDBX4XMLWRITER_H diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index fd57148d0..f89e828a1 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -19,6 +19,7 @@ #include #include "crypto/kdf/AesKdf.h" #include "crypto/kdf/Argon2Kdf.h" +#include "crypto/CryptoHash.h" const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be5805216afc5aff")); const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); @@ -29,6 +30,19 @@ const Uuid KeePass2::KDF_ARGON2 = Uuid(QByteArray::fromHex("EF636DDF8C29444B91F7 const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); +const QString KeePass2::KDFPARAM_UUID("$UUID"); +// AES parameters +const QString KeePass2::KDFPARAM_AES_ROUNDS("R"); +const QString KeePass2::KDFPARAM_AES_SEED("S"); +// Argon2 parameters +const QString KeePass2::KDFPARAM_ARGON2_SALT("S"); +const QString KeePass2::KDFPARAM_ARGON2_PARALLELISM("P"); +const QString KeePass2::KDFPARAM_ARGON2_MEMORY("M"); +const QString KeePass2::KDFPARAM_ARGON2_ITERATIONS("I"); +const QString KeePass2::KDFPARAM_ARGON2_VERSION("V"); +const QString KeePass2::KDFPARAM_ARGON2_SECRET("K"); +const QString KeePass2::KDFPARAM_ARGON2_ASSOCDATA("A"); + const QList> KeePass2::CIPHERS { qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")), qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")), @@ -39,6 +53,38 @@ const QList> KeePass2::KDFS { qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2")), }; +QByteArray KeePass2::hmacKey(QByteArray masterSeed, QByteArray transformedMasterKey) { + CryptoHash hmacKeyHash(CryptoHash::Sha512); + hmacKeyHash.addData(masterSeed); + hmacKeyHash.addData(transformedMasterKey); + hmacKeyHash.addData(QByteArray(1, '\x01')); + return hmacKeyHash.result(); +} + +QSharedPointer KeePass2::kdfFromParameters(const QVariantMap &p) +{ + QByteArray uuidBytes = p.value(KDFPARAM_UUID).toByteArray(); + if (uuidBytes.size() != Uuid::Length) { + return nullptr; + } + + QSharedPointer kdf(uuidToKdf(Uuid(uuidBytes))); + if (kdf.isNull()) { + return nullptr; + } + + if (!kdf->processParameters(p)) { + return nullptr; + } + + return kdf; +} + +QVariantMap KeePass2::kdfToParameters(QSharedPointer kdf) +{ + return kdf->writeParameters(); +} + QSharedPointer KeePass2::uuidToKdf(const Uuid& uuid) { if (uuid == KDF_AES) { diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index 99bc5a0b0..cdc594f5a 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -19,6 +19,8 @@ #define KEEPASSX_KEEPASS2_H #include +#include +#include #include #include "crypto/SymmetricCipher.h" @@ -29,9 +31,14 @@ namespace KeePass2 { const quint32 SIGNATURE_1 = 0x9AA2D903; const quint32 SIGNATURE_2 = 0xB54BFB67; - const quint32 FILE_VERSION = 0x00030001; + const quint32 FILE_VERSION_MIN = 0x00020000; const quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000; + const quint32 FILE_VERSION_4 = 0x00040000; + const quint32 FILE_VERSION_3 = 0x00030001; + + const quint16 VARIANTMAP_VERSION = 0x0100; + const quint16 VARIANTMAP_CRITICAL_MASK = 0xFF00; const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian; @@ -44,6 +51,17 @@ namespace KeePass2 extern const QByteArray INNER_STREAM_SALSA20_IV; + extern const QString KDFPARAM_UUID; + extern const QString KDFPARAM_AES_ROUNDS; + extern const QString KDFPARAM_AES_SEED; + extern const QString KDFPARAM_ARGON2_SALT; + extern const QString KDFPARAM_ARGON2_PARALLELISM; + extern const QString KDFPARAM_ARGON2_MEMORY; + extern const QString KDFPARAM_ARGON2_ITERATIONS; + extern const QString KDFPARAM_ARGON2_VERSION; + extern const QString KDFPARAM_ARGON2_SECRET; + extern const QString KDFPARAM_ARGON2_ASSOCDATA; + extern const QList> CIPHERS; extern const QList> KDFS; @@ -59,7 +77,17 @@ namespace KeePass2 EncryptionIV = 7, ProtectedStreamKey = 8, StreamStartBytes = 9, - InnerRandomStreamID = 10 + InnerRandomStreamID = 10, + KdfParameters = 11, + PublicCustomData = 12 + }; + + enum class InnerHeaderFieldID : quint8 + { + End = 0, + InnerRandomStreamID = 1, + InnerRandomStreamKey = 2, + Binary = 3 }; enum ProtectedStreamAlgo @@ -70,7 +98,33 @@ namespace KeePass2 InvalidProtectedStreamAlgo = -1 }; + enum class VariantMapFieldType : quint8 + { + End = 0, + // Byte = 0x02, + // UInt16 = 0x03, + UInt32 = 0x04, + UInt64 = 0x05, + // Signed mask: 0x08 + Bool = 0x08, + // SByte = 0x0A, + // Int16 = 0x0B, + Int32 = 0x0C, + Int64 = 0x0D, + // Float = 0x10, + // Double = 0x11, + // Decimal = 0x12, + // Char = 0x17, // 16-bit Unicode character + String = 0x18, + // Array mask: 0x40 + ByteArray = 0x42 + }; + + QByteArray hmacKey(QByteArray masterSeed, QByteArray transformedMasterKey); + QSharedPointer kdfFromParameters(const QVariantMap &p); + QVariantMap kdfToParameters(QSharedPointer kdf); QSharedPointer uuidToKdf(const Uuid& uuid); + Uuid kdfToUuid(QSharedPointer kdf); ProtectedStreamAlgo idToProtectedStreamAlgo(quint32 id); } diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index daa6c9aa8..0a04c79c6 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -25,6 +25,7 @@ #include "format/KeePass1.h" #include "format/KeePass2.h" #include "format/Kdbx3Reader.h" +#include "format/Kdbx4Reader.h" BaseKeePass2Reader::BaseKeePass2Reader() : m_error(false) @@ -118,14 +119,21 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke m_version = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok) & KeePass2::FILE_VERSION_CRITICAL_MASK; - quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; + quint32 maxVersion = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK; if (!ok || (m_version < KeePass2::FILE_VERSION_MIN) || (m_version > maxVersion)) { raiseError(tr("Unsupported KeePass 2 database version.")); return nullptr; } device->seek(0); - m_reader.reset(static_cast(new Kdbx3Reader())); + + // Determine KDBX3 vs KDBX4 + if (m_version < KeePass2::FILE_VERSION_4) { + m_reader.reset(new Kdbx3Reader()); + } else { + m_reader.reset(new Kdbx4Reader()); + } + m_reader->setSaveXml(m_saveXml); return m_reader->readDatabase(device, key, keepDatabase); } @@ -159,3 +167,8 @@ quint32 KeePass2Reader::version() const { return m_version; } + +QSharedPointer KeePass2Reader::reader() +{ + return m_reader; +} \ No newline at end of file diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h index 93348f565..fd28db2b7 100644 --- a/src/format/KeePass2Reader.h +++ b/src/format/KeePass2Reader.h @@ -70,11 +70,12 @@ public: QString errorString() override; QByteArray xmlData() override; QByteArray streamKey() override; + QSharedPointer reader(); KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const override; quint32 version() const; private: - QScopedPointer m_reader; + QSharedPointer m_reader; quint32 m_version; }; diff --git a/src/format/KeePass2Repair.cpp b/src/format/KeePass2Repair.cpp index 0e79fa8ba..fdaa45d62 100644 --- a/src/format/KeePass2Repair.cpp +++ b/src/format/KeePass2Repair.cpp @@ -25,7 +25,9 @@ #include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" #include "format/KeePass2Reader.h" +#include "format/Kdbx4Reader.h" #include "format/Kdbx3XmlReader.h" +#include "format/Kdbx4XmlReader.h" KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key) { @@ -74,12 +76,23 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, KeePass2RandomStream randomStream(reader.protectedStreamAlgo()); randomStream.init(reader.streamKey()); - Kdbx3XmlReader xmlReader; + bool hasError; + QBuffer buffer(&xmlData); buffer.open(QIODevice::ReadOnly); - xmlReader.readDatabase(&buffer, db.data(), &randomStream); + if ((reader.version() & KeePass2::FILE_VERSION_CRITICAL_MASK) < KeePass2::FILE_VERSION_4) { + Kdbx3XmlReader xmlReader; + xmlReader.readDatabase(&buffer, db.data(), &randomStream); + hasError = xmlReader.hasError(); + } else { + auto reader4 = reader.reader().staticCast(); + QHash pool = reader4->binaryPool(); + Kdbx4XmlReader xmlReader(pool); + xmlReader.readDatabase(&buffer, db.data(), &randomStream); + hasError = xmlReader.hasError(); + } - if (xmlReader.hasError()) { + if (hasError) { return qMakePair(RepairFailed, nullptr); } else { diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index 00392dc05..baea9968a 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -22,6 +22,7 @@ #include "format/KeePass2Writer.h" #include "core/Database.h" #include "format/Kdbx3Writer.h" +#include "format/Kdbx4Writer.h" BaseKeePass2Writer::BaseKeePass2Writer() : m_error(false) { @@ -67,6 +68,22 @@ QString KeePass2Writer::errorString() } bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) { - m_writer.reset(static_cast(new Kdbx3Writer())); + bool useKdbx4 = false; + + if (db->kdf()->uuid() != KeePass2::KDF_AES) { + useKdbx4 = true; + } + + if (db->publicCustomData().size() > 0) { + useKdbx4 = true; + } + + // Determine KDBX3 vs KDBX4 + if (useKdbx4) { + m_writer.reset(new Kdbx4Writer()); + } else { + m_writer.reset(new Kdbx3Writer()); + } + return m_writer->writeDatabase(device, db); } diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index 2f4fe177d..b5c1ad459 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -105,7 +105,7 @@ void DatabaseSettingsWidget::load(Database* db) m_ui->transformRoundsSpinBox->setValue(kdf->rounds()); if (kdfUuid == KeePass2::KDF_ARGON2) { auto argon2Kdf = kdf.staticCast(); - m_ui->memorySpinBox->setValue(argon2Kdf->memory()); + m_ui->memorySpinBox->setValue(argon2Kdf->memory() / (1<<10)); m_ui->parallelismSpinBox->setValue(argon2Kdf->parallelism()); } @@ -120,6 +120,7 @@ void DatabaseSettingsWidget::save() meta->setDescription(m_ui->dbDescriptionEdit->text()); meta->setDefaultUserName(m_ui->defaultUsernameEdit->text()); meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked()); + meta->setSettingsChanged(QDateTime::currentDateTimeUtc()); bool truncate = false; @@ -156,7 +157,7 @@ void DatabaseSettingsWidget::save() kdf->setRounds(m_ui->transformRoundsSpinBox->value()); if (kdf->uuid() == KeePass2::KDF_ARGON2) { auto argon2Kdf = kdf.staticCast(); - argon2Kdf->setMemory(m_ui->memorySpinBox->value()); + argon2Kdf->setMemory(m_ui->memorySpinBox->value() * (1<<10)); argon2Kdf->setParallelism(m_ui->parallelismSpinBox->value()); } @@ -189,8 +190,12 @@ void DatabaseSettingsWidget::transformRoundsBenchmark() kdf->setRounds(m_ui->transformRoundsSpinBox->value()); if (kdf->uuid() == KeePass2::KDF_ARGON2) { auto argon2Kdf = kdf.staticCast(); - argon2Kdf->setMemory(m_ui->memorySpinBox->value()); - argon2Kdf->setParallelism(m_ui->parallelismSpinBox->value()); + if (!argon2Kdf->setMemory(m_ui->memorySpinBox->value() * (1<<10))) { + m_ui->memorySpinBox->setValue(argon2Kdf->memory() / (1<<10)); + } + if (!argon2Kdf->setParallelism(m_ui->parallelismSpinBox->value())) { + m_ui->parallelismSpinBox->setValue(argon2Kdf->parallelism()); + } } // Determine the number of rounds required to meet 1 second delay diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index 5325ed3de..6ea82b330 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -809,7 +809,7 @@ void DatabaseWidget::updateMasterKey(bool accepted) if (accepted) { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey()); + bool result = m_db->setKey(m_changeMasterKeyWidget->newMasterKey(), true, true); QApplication::restoreOverrideCursor(); if (!result) { diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 91c6b2e2e..4472fc27a 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -110,6 +110,9 @@ add_unit_test(NAME testgroup SOURCES TestGroup.cpp add_unit_test(NAME testkdbx3xmlreader SOURCES TestKeePass2XmlReader.cpp TestKdbx3XmlReader.cpp LIBS ${TEST_LIBRARIES}) +add_unit_test(NAME testkdbx4xmlreader SOURCES TestKeePass2XmlReader.cpp TestKdbx4XmlReader.cpp + LIBS ${TEST_LIBRARIES}) + add_unit_test(NAME testkeys SOURCES TestKeys.cpp LIBS ${TEST_LIBRARIES}) diff --git a/tests/TestKdbx4XmlReader.cpp b/tests/TestKdbx4XmlReader.cpp new file mode 100644 index 000000000..c1a0b42ee --- /dev/null +++ b/tests/TestKdbx4XmlReader.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include "TestKeePass2XmlReader.h" + +QTEST_GUILESS_MAIN(TestKdbx4XmlReader) diff --git a/tests/TestKeePass2Reader.cpp b/tests/TestKeePass2Reader.cpp index 22973ee00..86dc6db2c 100644 --- a/tests/TestKeePass2Reader.cpp +++ b/tests/TestKeePass2Reader.cpp @@ -155,3 +155,26 @@ void TestKeePass2Reader::testFormat300() delete db; } + +void TestKeePass2Reader::testFormat400() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format400.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("t")); + KeePass2Reader reader; + Database* db = reader.readDatabase(filename, key); + QVERIFY(db); + QVERIFY(!reader.hasError()); + + QCOMPARE(db->rootGroup()->name(), QString("Format400")); + QCOMPARE(db->metadata()->name(), QString("Format400")); + QCOMPARE(db->rootGroup()->entries().size(), 1); + Entry* entry = db->rootGroup()->entries().at(0); + + QCOMPARE(entry->title(), QString("Format400")); + QCOMPARE(entry->username(), QString("Format400")); + QCOMPARE(entry->attributes()->keys().size(), 6); + QCOMPARE(entry->attributes()->value("Format400"), QString("Format400")); + QCOMPARE(entry->attachments()->keys().size(), 1); + QCOMPARE(entry->attachments()->value("Format400"), QByteArray("Format400\n")); +} diff --git a/tests/TestKeePass2Reader.h b/tests/TestKeePass2Reader.h index 76ffe0297..6ba9b0dc1 100644 --- a/tests/TestKeePass2Reader.h +++ b/tests/TestKeePass2Reader.h @@ -32,6 +32,7 @@ private slots: void testBrokenHeaderHash(); void testFormat200(); void testFormat300(); + void testFormat400(); }; #endif // KEEPASSX_TESTKEEPASS2READER_H diff --git a/tests/TestKeePass2XmlReader.cpp b/tests/TestKeePass2XmlReader.cpp index c1e3b4b63..fda8fffd6 100644 --- a/tests/TestKeePass2XmlReader.cpp +++ b/tests/TestKeePass2XmlReader.cpp @@ -27,6 +27,8 @@ #include "crypto/Crypto.h" #include "format/Kdbx3XmlReader.h" #include "format/Kdbx3XmlWriter.h" +#include "format/Kdbx4XmlReader.h" +#include "format/Kdbx4XmlWriter.h" #include "config-keepassx-tests.h" namespace QTest { @@ -89,6 +91,18 @@ void TestKdbx3XmlReader::initTestCase() QVERIFY(!reader.hasError()); } +void TestKdbx4XmlReader::initTestCase() +{ + QVERIFY(Crypto::init()); + + Kdbx4XmlReader reader; + reader.setStrictMode(true); + QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); + m_db = reader.readDatabase(xmlFile); + QVERIFY(m_db); + QVERIFY(!reader.hasError()); +} + void TestKdbx3XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) { Kdbx3XmlReader reader; @@ -115,6 +129,32 @@ void TestKdbx3XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasErro errorString = writer.errorString(); } +void TestKdbx4XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) +{ + Kdbx4XmlReader reader; + reader.setStrictMode(strictMode); + db = reader.readDatabase(path); + hasError = reader.hasError(); + errorString = reader.errorString(); +} + +void TestKdbx4XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) +{ + Kdbx4XmlReader reader; + reader.setStrictMode(strictMode); + db = reader.readDatabase(buf); + hasError = reader.hasError(); + errorString = reader.errorString(); +} + +void TestKdbx4XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) +{ + Kdbx4XmlWriter writer; + writer.writeDatabase(buf, db); + hasError = writer.hasError(); + errorString = writer.errorString(); +} + void TestKeePass2XmlReader::testMetadata() { QCOMPARE(m_db->metadata()->generator(), QString("KeePass")); diff --git a/tests/TestKeePass2XmlReader.h b/tests/TestKeePass2XmlReader.h index 2ce122235..e07f575b3 100644 --- a/tests/TestKeePass2XmlReader.h +++ b/tests/TestKeePass2XmlReader.h @@ -70,4 +70,17 @@ protected: virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; }; +class TestKdbx4XmlReader : public TestKeePass2XmlReader +{ + Q_OBJECT + +private slots: + virtual void initTestCase() override; + +protected: + virtual void readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) override; + virtual void readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) override; + virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; +}; + #endif // KEEPASSX_TESTKEEPASS2XMLREADER_H diff --git a/tests/data/Format400.kdbx b/tests/data/Format400.kdbx new file mode 100644 index 0000000000000000000000000000000000000000..1a877508e838a68870fc8ba6420b0e6b55aead72 GIT binary patch literal 1801 zcmV+k2ln^_*`k_f`%AR|00aO65C8xG)&q(wi*HP|r6e<&G26A80|Wp70096100bZa z002AsdBAy7CEGE7ne=tFS@D(EGicgUEE1q$pu)ND%M%NW000000YU`;001OaRY^n; z0002*V{PAzDMU+=_o<`<;|dG}0RR91Rs;Y5022TJ00jX6002n{000020000000005 z0RR91O$Y!00000G0000000aR5002+~000020000&0RR91Qy>5U09LF9Z7qf>u*D1j z_7plao%BKS=8+32eW;I6!@~mEr2q#E0000pKZj78Z*q{0Xp}Gj1ONa44GIkk(A|W+ z?(w4C8xGa`1D~vE0Is>2-H*l&(4ZVrZRd%*bGLJjo?PbTu0158zkZaan9Ml=_3!qI=EUp(86)Bl)HqqwaowA@b;H9Og{7ytKFz7hPjH#4HK`QSqQwU z<9DYO&?^Lo>q3uNhMAc^iZR($z@AZTWMH3{Jg#^VK8hC3Kjz7ZO~zbjx)`(aL0*K2x~r{FV!qevR=3qX z6;QycGP6WhmBUS%kdz+iHp9=DT`WN8!H-9?B!B&zN8N_e=n^OyRGAtHr&g@vZ|_yu z%zFCg9UR7*{GFN~ur@Z;zI~3CkX7UVUK|$LSZZU7jf3s%I~!}K=m%<|Q>GK-$hRzz zEAfz;xzo?gf~4o9-!*~IVP$Ug@hJ(0HLMI1Y7kza2F9!!?H_Xtc&1|4>KN&;zr|WG zkve*EA+CmsOKnflHj5hO?kAUre)_)CgSvdYx-$&_lQs0<%BrP@_(?hxq}6w;u32;a zaa*cXHV=h?su|2nHJHpFKIbTQH1`(a!VY-57+N|%u@8=VVmgspcQ@r@IQ`jwn-Zahgw^+(auwgmDM zuuEm^;qFyP^A6n^GDRaKZ>48O(Bt5d_J{5b_P6~tva>QjaOhR%dQijo1#7gv{hZ0I zS>o1g=+gn_WcPArddeiCyS{Krs+A03fjgrxI%sxF5Gypbz-M4Mjhwl@&m)5vx6l`s z__Dcap5oC*o>MwknTZ+qBHve(N0%Ge&ytVuRDmNB{-yzcV!~9S0(ueg3Sr2;07YSc zCcWsX(@fxVg{@W>x~?)2c6q8!w+zdt&ZRL2e5tbNKjqHjLHP-K77?yk=*Z2f2e+w| zYb=U1(tbR7iS)NwJK ztA8GJ{_4<>x9dPzpN$xc13a(2T`V^Sjazwc8;C$=GskV#7;1mt-_n>>RNnl?(L(=E zO0~7BeBlb}(SW>~mB!6tnLX>2z8HoLV&`|7jcsm@mbInY^iAL1mEpH<3l7c?%tj2G zi^Z_m_bZ{?mhXA3$%W<(FYy=@$_o49eslC+z$9t%$?RZ2ni+u z{s+ywuTdHA83ti&`1xR?j!j5Ad>GqTNjGORo>Ae=(^drWz^h_)IY-;Ii9nKd!3Qz#~>hZeE2&w~|aQ;2#`i~@qnL;Gu&&Tx8O<^y56H%`h*nRRFP*%@Q0CbHs! zZ8GIVEyoYYL7@@68%6m2u*cf2Ovmdr%z+OT_6{rUYk?q+Cf>{MPP%vPZ=7;k(su)O zEo_<24kzHat5>PP+zLbE^eh?4Pe3z2owR|cBd?Cx;|K#ODZg7|Nm~+|)x?Zay*5~$ r`H}nGlUTlv#AmSXdYJo0x{`4F%hdC3e3OW`R%IFyI;%Ph00000Hm_K2 literal 0 HcmV?d00001 From 564188b0d3ef80e436a66c4f53f0393b1cfcfd08 Mon Sep 17 00:00:00 2001 From: angelsl Date: Mon, 13 Nov 2017 03:20:37 +0800 Subject: [PATCH 23/39] Report database read failure reason in tests --- tests/TestKeePass2Writer.cpp | 7 +++++-- tests/TestKeys.cpp | 9 ++++++++- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/tests/TestKeePass2Writer.cpp b/tests/TestKeePass2Writer.cpp index 049c3e66e..30d0cbf5a 100644 --- a/tests/TestKeePass2Writer.cpp +++ b/tests/TestKeePass2Writer.cpp @@ -65,12 +65,15 @@ void TestKeePass2Writer::initTestCase() buffer.open(QBuffer::ReadWrite); KeePass2Writer writer; - writer.writeDatabase(&buffer, m_dbOrg); + bool writeSuccess = writer.writeDatabase(&buffer, m_dbOrg); + QVERIFY(writeSuccess); QVERIFY(!writer.hasError()); buffer.seek(0); KeePass2Reader reader; m_dbTest = reader.readDatabase(&buffer, key); - QVERIFY(!reader.hasError()); + if (reader.hasError()) { + QFAIL(reader.errorString().toUtf8().constData()); + } QVERIFY(m_dbTest); } diff --git a/tests/TestKeys.cpp b/tests/TestKeys.cpp index 990878e2d..a97287f39 100644 --- a/tests/TestKeys.cpp +++ b/tests/TestKeys.cpp @@ -153,12 +153,19 @@ void TestKeys::testCreateAndOpenFileKey() KeePass2Writer writer; writer.writeDatabase(&dbBuffer, dbOrg.data()); + bool writeSuccess = writer.writeDatabase(&dbBuffer, dbOrg.data()); + if (writer.hasError()) { + QFAIL(writer.errorString().toUtf8().constData()); + } + QVERIFY(writeSuccess); dbBuffer.reset(); KeePass2Reader reader; QScopedPointer dbRead(reader.readDatabase(&dbBuffer, compositeKey)); + if (reader.hasError()) { + QFAIL(reader.errorString().toUtf8().constData()); + } QVERIFY(dbRead); - QVERIFY(!reader.hasError()); QCOMPARE(dbRead->metadata()->name(), dbName); } From f7d3c902185250c8db910f2e1b5f3457ab4a4a05 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 6 Jan 2018 13:11:47 +0100 Subject: [PATCH 24/39] Fix compilation error --- src/crypto/CryptoHash.cpp | 2 +- src/gui/DatabaseSettingsWidget.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp index 70633da15..12c6bf791 100644 --- a/src/crypto/CryptoHash.cpp +++ b/src/crypto/CryptoHash.cpp @@ -109,7 +109,7 @@ QByteArray CryptoHash::result() const { Q_D(const CryptoHash); - const auto* result = reinterpret_cast(gcry_md_read(d->ctx, 0)); + const auto result = reinterpret_cast(gcry_md_read(d->ctx, 0)); return QByteArray(result, d->hashLen); } diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index b5c1ad459..5d1764cd6 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -199,7 +199,7 @@ void DatabaseSettingsWidget::transformRoundsBenchmark() } // Determine the number of rounds required to meet 1 second delay - int rounds = AsyncTask::runAndWaitForFuture([this, kdf]() { + int rounds = AsyncTask::runAndWaitForFuture([kdf]() { return kdf->benchmark(1000); }); From 995d6646beb5352acd79f1204c3cbdb23aad0dd8 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 6 Jan 2018 15:27:44 +0100 Subject: [PATCH 25/39] Redesign DatabaseSettingsWidget --- .../32x32/actions/document-encrypt.png | Bin 0 -> 1052 bytes src/gui/DatabaseSettingsWidget.cpp | 145 ++++---- src/gui/DatabaseSettingsWidget.h | 6 + src/gui/DatabaseSettingsWidget.ui | 325 ++---------------- src/gui/DatabaseSettingsWidgetEncryption.ui | 192 +++++++++++ src/gui/DatabaseSettingsWidgetGeneral.ui | 154 +++++++++ 6 files changed, 458 insertions(+), 364 deletions(-) create mode 100644 share/icons/application/32x32/actions/document-encrypt.png create mode 100644 src/gui/DatabaseSettingsWidgetEncryption.ui create mode 100644 src/gui/DatabaseSettingsWidgetGeneral.ui diff --git a/share/icons/application/32x32/actions/document-encrypt.png b/share/icons/application/32x32/actions/document-encrypt.png new file mode 100644 index 0000000000000000000000000000000000000000..353a22ca259a4bcfda30193e8e360870d3b0c10f GIT binary patch literal 1052 zcmV+%1mpXOP)gwtX*L9!eayjqx^b|)&M^H+kP$-1h-Q5icFDEA_p99cI5|C?aYahw^(8x3?EM@;q;goQ?zN zqvyWk0+^nbUOWdVq#(j!1))& z!otEUaz4XK3=R(Zq}IdX;o*&z6sXl|^R)Z)!NCDb{4;ib!OYy;+$<@wO=S5!55RoO zA)vVTbF4Y(Iy*a`o^yVI1g)EuQleU|a&Gh|LEskc1`H~*_qfCy$NAv&MQoyGYWinlfQ~`y6lMh!kR*DIOEfE47Q#$oLdvWTe)=w*kl74*JWsUp5Zr@ZbYtw3r~<>_!>FDE0pNMs=0FU)dpq9ZZP-kCT3pK{60?7kfc+kbGn@%|7H-rw zz_E}Pjlvs(won#91N^9EkY9}`357jlBd6z(vTd6CjC12?aCT #include "core/Global.h" +#include "core/FilePath.h" #include "core/AsyncTask.h" #include "core/Database.h" #include "core/Group.h" @@ -32,18 +35,31 @@ DatabaseSettingsWidget::DatabaseSettingsWidget(QWidget* parent) : DialogyWidget(parent) , m_ui(new Ui::DatabaseSettingsWidget()) + , m_uiGeneral(new Ui::DatabaseSettingsWidgetGeneral()) + , m_uiEncryption(new Ui::DatabaseSettingsWidgetEncryption()) + , m_uiGeneralPage(new QWidget()) + , m_uiEncryptionPage(new QWidget()) , m_db(nullptr) { m_ui->setupUi(this); + m_uiGeneral->setupUi(m_uiGeneralPage); + m_uiEncryption->setupUi(m_uiEncryptionPage); connect(m_ui->buttonBox, SIGNAL(accepted()), SLOT(save())); connect(m_ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); - connect(m_ui->historyMaxItemsCheckBox, SIGNAL(toggled(bool)), - m_ui->historyMaxItemsSpinBox, SLOT(setEnabled(bool))); - connect(m_ui->historyMaxSizeCheckBox, SIGNAL(toggled(bool)), - m_ui->historyMaxSizeSpinBox, SLOT(setEnabled(bool))); - connect(m_ui->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark())); - connect(m_ui->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(kdfChanged(int))); + connect(m_uiGeneral->historyMaxItemsCheckBox, SIGNAL(toggled(bool)), + m_uiGeneral->historyMaxItemsSpinBox, SLOT(setEnabled(bool))); + connect(m_uiGeneral->historyMaxSizeCheckBox, SIGNAL(toggled(bool)), + m_uiGeneral->historyMaxSizeSpinBox, SLOT(setEnabled(bool))); + connect(m_uiEncryption->transformBenchmarkButton, SIGNAL(clicked()), SLOT(transformRoundsBenchmark())); + connect(m_uiEncryption->kdfComboBox, SIGNAL(currentIndexChanged(int)), SLOT(kdfChanged(int))); + + m_ui->categoryList->addCategory(tr("General"), FilePath::instance()->icon("categories", "preferences-other")); + m_ui->categoryList->addCategory(tr("Encryption"), FilePath::instance()->icon("actions", "document-encrypt")); + m_ui->stackedWidget->addWidget(m_uiGeneralPage); + m_ui->stackedWidget->addWidget(m_uiEncryptionPage); + + connect(m_ui->categoryList, SIGNAL(categoryChanged(int)), m_ui->stackedWidget, SLOT(setCurrentIndex(int))); } DatabaseSettingsWidget::~DatabaseSettingsWidget() @@ -56,77 +72,77 @@ void DatabaseSettingsWidget::load(Database* db) Metadata* meta = m_db->metadata(); - m_ui->dbNameEdit->setText(meta->name()); - m_ui->dbDescriptionEdit->setText(meta->description()); - m_ui->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled()); - m_ui->defaultUsernameEdit->setText(meta->defaultUserName()); + m_uiGeneral->dbNameEdit->setText(meta->name()); + m_uiGeneral->dbDescriptionEdit->setText(meta->description()); + m_uiGeneral->recycleBinEnabledCheckBox->setChecked(meta->recycleBinEnabled()); + m_uiGeneral->defaultUsernameEdit->setText(meta->defaultUserName()); if (meta->historyMaxItems() > -1) { - m_ui->historyMaxItemsSpinBox->setValue(meta->historyMaxItems()); - m_ui->historyMaxItemsCheckBox->setChecked(true); + m_uiGeneral->historyMaxItemsSpinBox->setValue(meta->historyMaxItems()); + m_uiGeneral->historyMaxItemsCheckBox->setChecked(true); } else { - m_ui->historyMaxItemsSpinBox->setValue(Metadata::DefaultHistoryMaxItems); - m_ui->historyMaxItemsCheckBox->setChecked(false); + m_uiGeneral->historyMaxItemsSpinBox->setValue(Metadata::DefaultHistoryMaxItems); + m_uiGeneral->historyMaxItemsCheckBox->setChecked(false); } int historyMaxSizeMiB = qRound(meta->historyMaxSize() / qreal(1048576)); if (historyMaxSizeMiB > 0) { - m_ui->historyMaxSizeSpinBox->setValue(historyMaxSizeMiB); - m_ui->historyMaxSizeCheckBox->setChecked(true); + m_uiGeneral->historyMaxSizeSpinBox->setValue(historyMaxSizeMiB); + m_uiGeneral->historyMaxSizeCheckBox->setChecked(true); } else { - m_ui->historyMaxSizeSpinBox->setValue(Metadata::DefaultHistoryMaxSize); - m_ui->historyMaxSizeCheckBox->setChecked(false); + m_uiGeneral->historyMaxSizeSpinBox->setValue(Metadata::DefaultHistoryMaxSize); + m_uiGeneral->historyMaxSizeCheckBox->setChecked(false); } - m_ui->algorithmComboBox->clear(); + m_uiEncryption->algorithmComboBox->clear(); for (auto& cipher: asConst(KeePass2::CIPHERS)) { - m_ui->algorithmComboBox->addItem(cipher.second, cipher.first.toByteArray()); + m_uiEncryption->algorithmComboBox->addItem(cipher.second, cipher.first.toByteArray()); } - int cipherIndex = m_ui->algorithmComboBox->findData(m_db->cipher().toByteArray()); + int cipherIndex = m_uiEncryption->algorithmComboBox->findData(m_db->cipher().toByteArray()); if (cipherIndex > -1) { - m_ui->algorithmComboBox->setCurrentIndex(cipherIndex); + m_uiEncryption->algorithmComboBox->setCurrentIndex(cipherIndex); } // Setup kdf combo box - m_ui->kdfComboBox->blockSignals(true); - m_ui->kdfComboBox->clear(); + m_uiEncryption->kdfComboBox->blockSignals(true); + m_uiEncryption->kdfComboBox->clear(); for (auto& kdf: asConst(KeePass2::KDFS)) { - m_ui->kdfComboBox->addItem(kdf.second, kdf.first.toByteArray()); + m_uiEncryption->kdfComboBox->addItem(kdf.second, kdf.first.toByteArray()); } - m_ui->kdfComboBox->blockSignals(false); + m_uiEncryption->kdfComboBox->blockSignals(false); auto kdfUuid = m_db->kdf()->uuid(); - int kdfIndex = m_ui->kdfComboBox->findData(kdfUuid.toByteArray()); + int kdfIndex = m_uiEncryption->kdfComboBox->findData(kdfUuid.toByteArray()); if (kdfIndex > -1) { - m_ui->kdfComboBox->setCurrentIndex(kdfIndex); + m_uiEncryption->kdfComboBox->setCurrentIndex(kdfIndex); kdfChanged(kdfIndex); } // Setup kdf parameters auto kdf = m_db->kdf(); - m_ui->transformRoundsSpinBox->setValue(kdf->rounds()); + m_uiEncryption->transformRoundsSpinBox->setValue(kdf->rounds()); if (kdfUuid == KeePass2::KDF_ARGON2) { auto argon2Kdf = kdf.staticCast(); - m_ui->memorySpinBox->setValue(argon2Kdf->memory() / (1<<10)); - m_ui->parallelismSpinBox->setValue(argon2Kdf->parallelism()); + m_uiEncryption->memorySpinBox->setValue(static_cast(argon2Kdf->memory()) / (1 << 10)); + m_uiEncryption->parallelismSpinBox->setValue(argon2Kdf->parallelism()); } - m_ui->dbNameEdit->setFocus(); + m_uiGeneral->dbNameEdit->setFocus(); } void DatabaseSettingsWidget::save() { Metadata* meta = m_db->metadata(); - meta->setName(m_ui->dbNameEdit->text()); - meta->setDescription(m_ui->dbDescriptionEdit->text()); - meta->setDefaultUserName(m_ui->defaultUsernameEdit->text()); - meta->setRecycleBinEnabled(m_ui->recycleBinEnabledCheckBox->isChecked()); + meta->setName(m_uiGeneral->dbNameEdit->text()); + meta->setDescription(m_uiGeneral->dbDescriptionEdit->text()); + meta->setDefaultUserName(m_uiGeneral->defaultUsernameEdit->text()); + meta->setRecycleBinEnabled(m_uiGeneral->recycleBinEnabledCheckBox->isChecked()); meta->setSettingsChanged(QDateTime::currentDateTimeUtc()); bool truncate = false; int historyMaxItems; - if (m_ui->historyMaxItemsCheckBox->isChecked()) { - historyMaxItems = m_ui->historyMaxItemsSpinBox->value(); + if (m_uiGeneral->historyMaxItemsCheckBox->isChecked()) { + historyMaxItems = m_uiGeneral->historyMaxItemsSpinBox->value(); } else { historyMaxItems = -1; } @@ -136,8 +152,8 @@ void DatabaseSettingsWidget::save() } int historyMaxSize; - if (m_ui->historyMaxSizeCheckBox->isChecked()) { - historyMaxSize = m_ui->historyMaxSizeSpinBox->value() * 1048576; + if (m_uiGeneral->historyMaxSizeCheckBox->isChecked()) { + historyMaxSize = m_uiGeneral->historyMaxSizeSpinBox->value() * 1048576; } else { historyMaxSize = -1; } @@ -150,15 +166,15 @@ void DatabaseSettingsWidget::save() truncateHistories(); } - m_db->setCipher(Uuid(m_ui->algorithmComboBox->currentData().toByteArray())); + m_db->setCipher(Uuid(m_uiEncryption->algorithmComboBox->currentData().toByteArray())); // Save kdf parameters - auto kdf = KeePass2::uuidToKdf(Uuid(m_ui->kdfComboBox->currentData().toByteArray())); - kdf->setRounds(m_ui->transformRoundsSpinBox->value()); + auto kdf = KeePass2::uuidToKdf(Uuid(m_uiEncryption->kdfComboBox->currentData().toByteArray())); + kdf->setRounds(m_uiEncryption->transformRoundsSpinBox->value()); if (kdf->uuid() == KeePass2::KDF_ARGON2) { auto argon2Kdf = kdf.staticCast(); - argon2Kdf->setMemory(m_ui->memorySpinBox->value() * (1<<10)); - argon2Kdf->setParallelism(m_ui->parallelismSpinBox->value()); + argon2Kdf->setMemory(static_cast(m_uiEncryption->memorySpinBox->value()) * (1 << 10)); + argon2Kdf->setParallelism(static_cast(m_uiEncryption->parallelismSpinBox->value())); } QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); @@ -183,30 +199,30 @@ void DatabaseSettingsWidget::reject() void DatabaseSettingsWidget::transformRoundsBenchmark() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); - m_ui->transformBenchmarkButton->setEnabled(false); + m_uiEncryption->transformBenchmarkButton->setEnabled(false); + m_uiEncryption->transformRoundsSpinBox->setFocus(); // Create a new kdf with the current parameters - auto kdf = KeePass2::uuidToKdf(Uuid(m_ui->kdfComboBox->currentData().toByteArray())); - kdf->setRounds(m_ui->transformRoundsSpinBox->value()); + auto kdf = KeePass2::uuidToKdf(Uuid(m_uiEncryption->kdfComboBox->currentData().toByteArray())); + kdf->setRounds(m_uiEncryption->transformRoundsSpinBox->value()); if (kdf->uuid() == KeePass2::KDF_ARGON2) { auto argon2Kdf = kdf.staticCast(); - if (!argon2Kdf->setMemory(m_ui->memorySpinBox->value() * (1<<10))) { - m_ui->memorySpinBox->setValue(argon2Kdf->memory() / (1<<10)); + if (!argon2Kdf->setMemory(static_cast(m_uiEncryption->memorySpinBox->value()) * (1 << 10))) { + m_uiEncryption->memorySpinBox->setValue(static_cast(argon2Kdf->memory() / (1 << 10))); } - if (!argon2Kdf->setParallelism(m_ui->parallelismSpinBox->value())) { - m_ui->parallelismSpinBox->setValue(argon2Kdf->parallelism()); + if (!argon2Kdf->setParallelism(static_cast(m_uiEncryption->parallelismSpinBox->value()))) { + m_uiEncryption->parallelismSpinBox->setValue(argon2Kdf->parallelism()); } } // Determine the number of rounds required to meet 1 second delay - int rounds = AsyncTask::runAndWaitForFuture([kdf]() { + int rounds = AsyncTask::runAndWaitForFuture([&kdf]() { return kdf->benchmark(1000); }); - m_ui->transformRoundsSpinBox->setValue(rounds); - + m_uiEncryption->transformRoundsSpinBox->setValue(rounds); + m_uiEncryption->transformBenchmarkButton->setEnabled(true); QApplication::restoreOverrideCursor(); - m_ui->transformBenchmarkButton->setEnabled(true); } void DatabaseSettingsWidget::truncateHistories() @@ -219,12 +235,13 @@ void DatabaseSettingsWidget::truncateHistories() void DatabaseSettingsWidget::kdfChanged(int index) { - Uuid id(m_ui->kdfComboBox->itemData(index).toByteArray()); - if (id == KeePass2::KDF_ARGON2) { - m_ui->memorySpinBox->setEnabled(true); - m_ui->parallelismSpinBox->setEnabled(true); - } else { - m_ui->memorySpinBox->setEnabled(false); - m_ui->parallelismSpinBox->setEnabled(false); - } + Uuid id(m_uiEncryption->kdfComboBox->itemData(index).toByteArray()); + + bool memoryEnabled = id == KeePass2::KDF_ARGON2; + m_uiEncryption->memoryUsageLabel->setEnabled(memoryEnabled); + m_uiEncryption->memorySpinBox->setEnabled(memoryEnabled); + + bool parallelismEnabled = id == KeePass2::KDF_ARGON2; + m_uiEncryption->parallelismLabel->setEnabled(parallelismEnabled); + m_uiEncryption->parallelismSpinBox->setEnabled(parallelismEnabled); } diff --git a/src/gui/DatabaseSettingsWidget.h b/src/gui/DatabaseSettingsWidget.h index 83a5eb098..8410af37e 100644 --- a/src/gui/DatabaseSettingsWidget.h +++ b/src/gui/DatabaseSettingsWidget.h @@ -31,6 +31,8 @@ class Database; namespace Ui { class DatabaseSettingsWidget; +class DatabaseSettingsWidgetGeneral; +class DatabaseSettingsWidgetEncryption; } class DatabaseSettingsWidget: public DialogyWidget @@ -57,6 +59,10 @@ private: void truncateHistories(); const QScopedPointer m_ui; + const QScopedPointer m_uiGeneral; + const QScopedPointer m_uiEncryption; + QWidget* m_uiGeneralPage; + QWidget* m_uiEncryptionPage; Database* m_db; }; diff --git a/src/gui/DatabaseSettingsWidget.ui b/src/gui/DatabaseSettingsWidget.ui index 5a6778678..9b45feaa7 100644 --- a/src/gui/DatabaseSettingsWidget.ui +++ b/src/gui/DatabaseSettingsWidget.ui @@ -7,320 +7,45 @@ 0 0 1082 - 506 + 578 - + - - - Qt::Vertical - - - - 0 - 0 - - - - - - + - - - Qt::Horizontal - - - - 0 - 0 - - - + - - - - 400 - 16777215 - + + + -1 - - - - - Max. history size: - - - - - - - - - - - - MB - - - 1 - - - 1048576 - - - 64 - - - - - - - - - Database name: - - - - - - - Max. history items: - - - - - - - Key Derivation Function - - - - - - - - AES: 256 Bit (default) - - - - - Twofish: 256 Bit - - - - - - - - Encryption Algorithm: - - - - - - - Database description: - - - - - - - - - - - - - - - - 0 - 0 - - - - 2000000000 - - - - - - - - - - - - 0 - 0 - - - - MiB - - - 1 - - - 2000000000 - - - - - - - - - - 0 - 0 - - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - 1 - - - 1000000000 - - - - - - - - 0 - 0 - - - - Benchmark 1-second delay - - - - - - - true - - - - - - - Default username: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Transform rounds: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Memory Usage: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - Parallelism: - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - thread - - - 1 - - - 128 - - - - - - - Use recycle bin - - - - - - - - Qt::Horizontal - - - - 0 - 0 - - - - - - - Qt::Vertical - - - - 20 - 40 - - - - - - - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok - - + + + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + - - dbNameEdit - dbDescriptionEdit - historyMaxItemsCheckBox - historyMaxItemsSpinBox - historyMaxSizeCheckBox - historyMaxSizeSpinBox - buttonBox - + + + CategoryListWidget + QWidget +
gui/CategoryListWidget.h
+ 1 +
+
-
+
\ No newline at end of file diff --git a/src/gui/DatabaseSettingsWidgetEncryption.ui b/src/gui/DatabaseSettingsWidgetEncryption.ui new file mode 100644 index 000000000..4c4d2aed2 --- /dev/null +++ b/src/gui/DatabaseSettingsWidgetEncryption.ui @@ -0,0 +1,192 @@ + + + DatabaseSettingsWidgetEncryption + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Encryption Algorithm: + + + + + + + + 0 + 0 + + + + + AES: 256 Bit (default) + + + + + Twofish: 256 Bit + + + + + + + + Key Derivation Function: + + + + + + + + 0 + 0 + + + + + + + + Transform rounds: + + + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + 1 + + + 1000000000 + + + + + + + Qt::WheelFocus + + + Benchmark 1-second delay + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Memory Usage: + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + MB + + + 1 + + + 1048576 + + + 64 + + + + + + + Parallelism: + + + + + + + + 150 + 0 + + + + + 150 + 16777215 + + + + thread + + + 1 + + + 128 + + + + + + + + diff --git a/src/gui/DatabaseSettingsWidgetGeneral.ui b/src/gui/DatabaseSettingsWidgetGeneral.ui new file mode 100644 index 000000000..c072c1d8d --- /dev/null +++ b/src/gui/DatabaseSettingsWidgetGeneral.ui @@ -0,0 +1,154 @@ + + + DatabaseSettingsWidgetGeneral + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Database Meta Data + + + + + + Database name: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Database description: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + Default username: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + true + + + + + + + + + + History Settings + + + + + + + + Max. history items: + + + + + + + Max. history size: + + + + + + + MiB + + + 1 + + + 2000000000 + + + + + + + 2000000000 + + + + + + + Use recycle bin + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + + From 54fb0d9bd331d6f5567360e772a4e1b44587a75b Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 6 Jan 2018 16:59:41 +0100 Subject: [PATCH 26/39] Show warning when using inappropriate transform round number Increase default AES-KDF rounds to 100k --- src/crypto/kdf/Kdf.h | 2 +- src/gui/DatabaseSettingsWidget.cpp | 36 +++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/crypto/kdf/Kdf.h b/src/crypto/kdf/Kdf.h index e45d23bcd..216224a6f 100644 --- a/src/crypto/kdf/Kdf.h +++ b/src/crypto/kdf/Kdf.h @@ -23,7 +23,7 @@ #include "core/Uuid.h" #define KDF_DEFAULT_SEED_SIZE 32 -#define KDF_DEFAULT_ROUNDS 100000ull +#define KDF_DEFAULT_ROUNDS 1000000ull class Kdf { diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index 9335b0ab8..0c2eba796 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -1,4 +1,5 @@ /* + * Copyright (C) 2018 KeePassXC Team * Copyright (C) 2012 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -21,6 +22,7 @@ #include "ui_DatabaseSettingsWidgetEncryption.h" #include +#include #include "core/Global.h" #include "core/FilePath.h" @@ -130,6 +132,36 @@ void DatabaseSettingsWidget::load(Database* db) void DatabaseSettingsWidget::save() { + // first perform safety check for KDF rounds + auto kdf = KeePass2::uuidToKdf(Uuid(m_uiEncryption->kdfComboBox->currentData().toByteArray())); + if (kdf->uuid() == KeePass2::KDF_ARGON2 and m_uiEncryption->transformRoundsSpinBox->value() > 1000) { + QMessageBox warning; + warning.setIcon(QMessageBox::Warning); + warning.setWindowTitle(tr("Number of rounds too high")); + warning.setText(tr("You are using a very high number of key transform rounds with Argon2.\n\n" + "If you keep this number, your database may take hours or days (or even longer) to open!")); + auto ok = warning.addButton(tr("Understood, keep number"), QMessageBox::ButtonRole::AcceptRole); + auto cancel = warning.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole); + warning.setDefaultButton(cancel); + warning.exec(); + if (warning.clickedButton() != ok) { + return; + } + } else if (kdf->uuid() == KeePass2::KDF_AES and m_uiEncryption->transformRoundsSpinBox->value() < 100000) { + QMessageBox warning; + warning.setIcon(QMessageBox::Warning); + warning.setWindowTitle(tr("Number of rounds too low")); + warning.setText(tr("You are using a very low number of key transform rounds with AES-KDF.\n\n" + "If you keep this number, your database may be too easy to crack!")); + auto ok = warning.addButton(tr("Understood, keep number"), QMessageBox::ButtonRole::AcceptRole); + auto cancel = warning.addButton(tr("Cancel"), QMessageBox::ButtonRole::RejectRole); + warning.setDefaultButton(cancel); + warning.exec(); + if (warning.clickedButton() != ok) { + return; + } + } + Metadata* meta = m_db->metadata(); meta->setName(m_uiGeneral->dbNameEdit->text()); @@ -169,7 +201,6 @@ void DatabaseSettingsWidget::save() m_db->setCipher(Uuid(m_uiEncryption->algorithmComboBox->currentData().toByteArray())); // Save kdf parameters - auto kdf = KeePass2::uuidToKdf(Uuid(m_uiEncryption->kdfComboBox->currentData().toByteArray())); kdf->setRounds(m_uiEncryption->transformRoundsSpinBox->value()); if (kdf->uuid() == KeePass2::KDF_ARGON2) { auto argon2Kdf = kdf.staticCast(); @@ -188,6 +219,7 @@ void DatabaseSettingsWidget::save() tr("Failed to transform key with new KDF parameters; KDF unchanged."), QMessageBox::Ok); } + emit editFinished(true); } @@ -244,4 +276,6 @@ void DatabaseSettingsWidget::kdfChanged(int index) bool parallelismEnabled = id == KeePass2::KDF_ARGON2; m_uiEncryption->parallelismLabel->setEnabled(parallelismEnabled); m_uiEncryption->parallelismSpinBox->setEnabled(parallelismEnabled); + + transformRoundsBenchmark(); } From ccfd7a065c821b3075144f497ddff6b4c80598f1 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 6 Jan 2018 17:06:51 +0100 Subject: [PATCH 27/39] Fix coding style and GUI test --- src/crypto/kdf/Argon2Kdf.cpp | 8 +- src/crypto/kdf/Argon2Kdf.h | 2 +- src/format/Kdbx3Reader.cpp | 87 +++--- src/format/Kdbx3Reader.h | 1 + src/format/Kdbx3Writer.cpp | 32 ++- src/format/Kdbx3Writer.h | 1 + src/format/Kdbx3XmlReader.cpp | 1 + src/format/Kdbx3XmlReader.h | 1 + src/format/Kdbx3XmlWriter.cpp | 19 +- src/format/Kdbx3XmlWriter.h | 1 + src/format/Kdbx4Reader.cpp | 93 ++++--- src/format/Kdbx4Reader.h | 2 +- src/format/Kdbx4Writer.cpp | 102 ++++--- src/format/Kdbx4Writer.h | 2 +- src/format/Kdbx4XmlReader.cpp | 367 +++++++++++++++----------- src/format/Kdbx4XmlReader.h | 6 +- src/format/Kdbx4XmlWriter.cpp | 2 +- src/format/Kdbx4XmlWriter.h | 2 +- src/format/KeePass2.cpp | 17 +- src/format/KeePass2.h | 4 +- src/format/KeePass2RandomStream.cpp | 4 +- src/format/KeePass2Reader.cpp | 2 +- src/format/KeePass2Writer.h | 2 + src/gui/DatabaseSettingsWidget.cpp | 4 +- src/streams/HmacBlockStream.cpp | 34 +-- src/streams/SymmetricCipherStream.cpp | 3 +- tests/TestKeePass2RandomStream.cpp | 2 +- tests/gui/TestGui.cpp | 4 +- 28 files changed, 426 insertions(+), 379 deletions(-) diff --git a/src/crypto/kdf/Argon2Kdf.cpp b/src/crypto/kdf/Argon2Kdf.cpp index 12e9135af..154f2ffe4 100644 --- a/src/crypto/kdf/Argon2Kdf.cpp +++ b/src/crypto/kdf/Argon2Kdf.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -33,7 +33,7 @@ Argon2Kdf::Argon2Kdf() : Kdf::Kdf(KeePass2::KDF_ARGON2) , m_version(0x13) - , m_memory(1<<16) + , m_memory(1 << 16) , m_parallelism(2) { m_rounds = 1; @@ -63,7 +63,7 @@ quint64 Argon2Kdf::memory() const bool Argon2Kdf::setMemory(quint64 kibibytes) { // MIN=8KB; MAX=2,147,483,648KB - if (kibibytes >= 8 && kibibytes < (1ULL<<32)) { + if (kibibytes >= 8 && kibibytes < (1ULL << 32)) { m_memory = kibibytes; return true; } @@ -79,7 +79,7 @@ quint32 Argon2Kdf::parallelism() const bool Argon2Kdf::setParallelism(quint32 threads) { // MIN=1; MAX=16,777,215 - if (threads >= 1 && threads < (1<<24)) { + if (threads >= 1 && threads < (1 << 24)) { m_parallelism = threads; return true; } diff --git a/src/crypto/kdf/Argon2Kdf.h b/src/crypto/kdf/Argon2Kdf.h index 345ca279c..fe62b2953 100644 --- a/src/crypto/kdf/Argon2Kdf.h +++ b/src/crypto/kdf/Argon2Kdf.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index 3187442be..5fb72a23e 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -1,4 +1,5 @@ /* + * Copyright (C) 2017 KeePassXC Team * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -161,7 +162,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, xmlDevice = ioCompressor.data(); } - KeePass2RandomStream randomStream(KeePass2::Salsa20); + KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20); if (!randomStream.init(m_protectedStreamKey)) { raiseError(randomStream.errorString()); return nullptr; @@ -208,10 +209,10 @@ bool Kdbx3Reader::readHeaderField() raiseError("Invalid header id size"); return false; } - quint8 fieldID = fieldIDArray.at(0); + char fieldID = fieldIDArray.at(0); bool ok; - quint16 fieldLen = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok); + auto fieldLen = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok); if (!ok) { raiseError("Invalid header field length"); return false; @@ -226,44 +227,44 @@ bool Kdbx3Reader::readHeaderField() } } - switch (fieldID) { - case KeePass2::EndOfHeader: + switch (static_cast(fieldID)) { + case KeePass2::HeaderFieldID::EndOfHeader: m_headerEnd = true; break; - case KeePass2::CipherID: + case KeePass2::HeaderFieldID::CipherID: setCipher(fieldData); break; - case KeePass2::CompressionFlags: + case KeePass2::HeaderFieldID::CompressionFlags: setCompressionFlags(fieldData); break; - case KeePass2::MasterSeed: + case KeePass2::HeaderFieldID::MasterSeed: setMasterSeed(fieldData); break; - case KeePass2::TransformSeed: + case KeePass2::HeaderFieldID::TransformSeed: setTransformSeed(fieldData); break; - case KeePass2::TransformRounds: + case KeePass2::HeaderFieldID::TransformRounds: setTransformRounds(fieldData); break; - case KeePass2::EncryptionIV: + case KeePass2::HeaderFieldID::EncryptionIV: setEncryptionIV(fieldData); break; - case KeePass2::ProtectedStreamKey: + case KeePass2::HeaderFieldID::ProtectedStreamKey: setProtectedStreamKey(fieldData); break; - case KeePass2::StreamStartBytes: + case KeePass2::HeaderFieldID::StreamStartBytes: setStreamStartBytes(fieldData); break; - case KeePass2::InnerRandomStreamID: + case KeePass2::HeaderFieldID::InnerRandomStreamID: setInnerRandomStreamID(fieldData); break; @@ -279,39 +280,40 @@ void Kdbx3Reader::setCipher(const QByteArray& data) { if (data.size() != Uuid::Length) { raiseError("Invalid cipher uuid length"); - } else { - Uuid uuid(data); - - if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { - raiseError("Unsupported cipher"); - } else { - m_db->setCipher(uuid); - } + return; } + + Uuid uuid(data); + + if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { + raiseError("Unsupported cipher"); + return; + } + m_db->setCipher(uuid); } void Kdbx3Reader::setCompressionFlags(const QByteArray& data) { if (data.size() != 4) { raiseError("Invalid compression flags length"); - } else { - quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); - - if (id > Database::CompressionAlgorithmMax) { - raiseError("Unsupported compression algorithm"); - } else { - m_db->setCompressionAlgo(static_cast(id)); - } + return; } + auto id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); + + if (id > Database::CompressionAlgorithmMax) { + raiseError("Unsupported compression algorithm"); + return; + } + m_db->setCompressionAlgo(static_cast(id)); } void Kdbx3Reader::setMasterSeed(const QByteArray& data) { if (data.size() != 32) { raiseError("Invalid master seed size"); - } else { - m_masterSeed = data; + return; } + m_masterSeed = data; } void Kdbx3Reader::setTransformSeed(const QByteArray& data) @@ -355,22 +357,23 @@ void Kdbx3Reader::setStreamStartBytes(const QByteArray& data) { if (data.size() != 32) { raiseError("Invalid start bytes size"); - } else { - m_streamStartBytes = data; + return; } + m_streamStartBytes = data; } void Kdbx3Reader::setInnerRandomStreamID(const QByteArray& data) { if (data.size() != 4) { raiseError("Invalid random stream id size"); - } else { - quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); - KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id); - if (irsAlgo == KeePass2::InvalidProtectedStreamAlgo || irsAlgo == KeePass2::ArcFourVariant) { - raiseError("Invalid inner random stream cipher"); - } else { - m_irsAlgo = irsAlgo; - } + return; } + quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); + KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id); + if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo || + irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) { + raiseError("Invalid inner random stream cipher"); + return; + } + m_irsAlgo = irsAlgo; } diff --git a/src/format/Kdbx3Reader.h b/src/format/Kdbx3Reader.h index 24a1ef100..024189eda 100644 --- a/src/format/Kdbx3Reader.h +++ b/src/format/Kdbx3Reader.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2017 KeePassXC Team * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp index 2fedf273c..ca02fb7ac 100644 --- a/src/format/Kdbx3Writer.cpp +++ b/src/format/Kdbx3Writer.cpp @@ -1,4 +1,5 @@ /* + * Copyright (C) 2017 KeePassXC Team * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -32,9 +33,6 @@ #include "streams/QtIOCompressor" #include "streams/SymmetricCipherStream.h" -#define CHECK_RETURN(x) if (!(x)) return; -#define CHECK_RETURN_FALSE(x) if (!(x)) return false; - Kdbx3Writer::Kdbx3Writer() : m_device(0) { @@ -51,7 +49,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) QByteArray startBytes = randomGen()->randomArray(32); QByteArray endOfHeader = "\r\n\r\n"; - if (db->challengeMasterSeed(masterSeed) == false) { + if (!db->challengeMasterSeed(masterSeed)) { raiseError(tr("Unable to issue challenge-response.")); return false; } @@ -76,23 +74,23 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::FILE_VERSION_3, KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray())); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CompressionFlags, + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray())); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CompressionFlags, Endian::sizedIntToBytes(db->compressionAlgo(), KeePass2::BYTEORDER))); auto kdf = db->kdf(); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::MasterSeed, masterSeed)); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformSeed, kdf->seed())); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformRounds, + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::MasterSeed, masterSeed)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::TransformSeed, kdf->seed())); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::TransformRounds, Endian::sizedIntToBytes(kdf->rounds(), KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EncryptionIV, encryptionIV)); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey)); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::StreamStartBytes, startBytes)); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::InnerRandomStreamID, - Endian::sizedIntToBytes(KeePass2::Salsa20, + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EncryptionIV, encryptionIV)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::ProtectedStreamKey, protectedStreamKey)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::StreamStartBytes, startBytes)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::InnerRandomStreamID, + Endian::sizedIntToBytes(static_cast(KeePass2::ProtectedStreamAlgo::Salsa20), KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EndOfHeader, endOfHeader)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EndOfHeader, endOfHeader)); header.close(); m_device = device; @@ -130,7 +128,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) m_device = ioCompressor.data(); } - KeePass2RandomStream randomStream(KeePass2::Salsa20); + KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20); if (!randomStream.init(protectedStreamKey)) { raiseError(randomStream.errorString()); return false; @@ -175,7 +173,7 @@ bool Kdbx3Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteA Q_ASSERT(data.size() <= 65535); QByteArray fieldIdArr; - fieldIdArr[0] = fieldId; + fieldIdArr[0] = static_cast(fieldId); CHECK_RETURN_FALSE(writeData(fieldIdArr)); CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size()), KeePass2::BYTEORDER))); diff --git a/src/format/Kdbx3Writer.h b/src/format/Kdbx3Writer.h index f3368eebb..1b9fe7563 100644 --- a/src/format/Kdbx3Writer.h +++ b/src/format/Kdbx3Writer.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2017 KeePassXC Team * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify diff --git a/src/format/Kdbx3XmlReader.cpp b/src/format/Kdbx3XmlReader.cpp index 3a4bdbd9d..18dd0914d 100644 --- a/src/format/Kdbx3XmlReader.cpp +++ b/src/format/Kdbx3XmlReader.cpp @@ -1,4 +1,5 @@ /* + * Copyright (C) 2017 KeePassXC Team * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify diff --git a/src/format/Kdbx3XmlReader.h b/src/format/Kdbx3XmlReader.h index 477eb3865..213b21c92 100644 --- a/src/format/Kdbx3XmlReader.h +++ b/src/format/Kdbx3XmlReader.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2017 KeePassXC Team * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify diff --git a/src/format/Kdbx3XmlWriter.cpp b/src/format/Kdbx3XmlWriter.cpp index 40c20e65c..bfa94a77c 100644 --- a/src/format/Kdbx3XmlWriter.cpp +++ b/src/format/Kdbx3XmlWriter.cpp @@ -1,4 +1,5 @@ /* + * Copyright (C) 2017 KeePassXC Team * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify @@ -458,11 +459,7 @@ void Kdbx3XmlWriter::writeNumber(const QString& qualifiedName, int number) void Kdbx3XmlWriter::writeBool(const QString& qualifiedName, bool b) { - if (b) { - writeString(qualifiedName, "True"); - } else { - writeString(qualifiedName, "False"); - } + writeString(qualifiedName, b ? "True" : "False"); } void Kdbx3XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime) @@ -487,20 +484,12 @@ void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid) void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) { - if (group) { - writeUuid(qualifiedName, group->uuid()); - } else { - writeUuid(qualifiedName, Uuid()); - } + writeUuid(qualifiedName, group ? group->uuid() : Uuid()); } void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) { - if (entry) { - writeUuid(qualifiedName, entry->uuid()); - } else { - writeUuid(qualifiedName, Uuid()); - } + writeUuid(qualifiedName, entry ? entry->uuid() : Uuid()); } void Kdbx3XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba) diff --git a/src/format/Kdbx3XmlWriter.h b/src/format/Kdbx3XmlWriter.h index 2bb130566..6eaf32f35 100644 --- a/src/format/Kdbx3XmlWriter.h +++ b/src/format/Kdbx3XmlWriter.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2017 KeePassXC Team * Copyright (C) 2010 Felix Geyer * * This program is free software: you can redistribute it and/or modify diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index 0a69cbf2d..5bac5d005 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -72,8 +72,7 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, "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; - } - else if (!ok || signature2 != KeePass2::SIGNATURE_2) { + } else if (!ok || signature2 != KeePass2::SIGNATURE_2) { raiseError(tr("Not a KeePass database.")); return nullptr; } @@ -203,9 +202,7 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, if (keepDatabase) { return db.take(); } - else { - return nullptr; - } + return nullptr; } return db.take(); @@ -218,10 +215,10 @@ bool Kdbx4Reader::readHeaderField(QIODevice* device) raiseError("Invalid header id size"); return false; } - quint8 fieldID = fieldIDArray.at(0); + char fieldID = fieldIDArray.at(0); bool ok; - quint32 fieldLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + auto fieldLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); if (!ok) { raiseError("Invalid header field length"); return false; @@ -236,27 +233,27 @@ bool Kdbx4Reader::readHeaderField(QIODevice* device) } } - switch (fieldID) { - case KeePass2::EndOfHeader: + switch (static_cast(fieldID)) { + case KeePass2::HeaderFieldID::EndOfHeader: return false; - case KeePass2::CipherID: + case KeePass2::HeaderFieldID::CipherID: setCipher(fieldData); break; - case KeePass2::CompressionFlags: + case KeePass2::HeaderFieldID::CompressionFlags: setCompressionFlags(fieldData); break; - case KeePass2::MasterSeed: + case KeePass2::HeaderFieldID::MasterSeed: setMasterSeed(fieldData); break; - case KeePass2::EncryptionIV: + case KeePass2::HeaderFieldID::EncryptionIV: setEncryptionIV(fieldData); break; - case KeePass2::KdfParameters: { + case KeePass2::HeaderFieldID::KdfParameters: { QBuffer bufIoDevice(&fieldData); if (!bufIoDevice.open(QIODevice::ReadOnly)) { raiseError("Failed to open buffer for KDF parameters in header"); @@ -264,7 +261,7 @@ bool Kdbx4Reader::readHeaderField(QIODevice* device) } QVariantMap kdfParams = readVariantMap(&bufIoDevice); QSharedPointer kdf = KeePass2::kdfFromParameters(kdfParams); - if (kdf == nullptr) { + if (!kdf) { raiseError("Invalid KDF parameters"); return false; } @@ -272,15 +269,15 @@ bool Kdbx4Reader::readHeaderField(QIODevice* device) break; } - case KeePass2::PublicCustomData: + case KeePass2::HeaderFieldID::PublicCustomData: m_db->setPublicCustomData(fieldData); break; - case KeePass2::ProtectedStreamKey: - case KeePass2::TransformRounds: - case KeePass2::TransformSeed: - case KeePass2::StreamStartBytes: - case KeePass2::InnerRandomStreamID: + case KeePass2::HeaderFieldID::ProtectedStreamKey: + case KeePass2::HeaderFieldID::TransformRounds: + case KeePass2::HeaderFieldID::TransformSeed: + case KeePass2::HeaderFieldID::StreamStartBytes: + case KeePass2::HeaderFieldID::InnerRandomStreamID: raiseError("Legacy header fields found in KDBX4 file."); return false; @@ -456,39 +453,39 @@ void Kdbx4Reader::setCipher(const QByteArray& data) { if (data.size() != Uuid::Length) { raiseError("Invalid cipher uuid length"); - } else { - Uuid uuid(data); - - if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { - raiseError("Unsupported cipher"); - } else { - m_db->setCipher(uuid); - } + return; } + Uuid uuid(data); + + if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { + raiseError("Unsupported cipher"); + return; + } + m_db->setCipher(uuid); } void Kdbx4Reader::setCompressionFlags(const QByteArray& data) { if (data.size() != 4) { raiseError("Invalid compression flags length"); - } else { - quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); - - if (id > Database::CompressionAlgorithmMax) { - raiseError("Unsupported compression algorithm"); - } else { - m_db->setCompressionAlgo(static_cast(id)); - } + return; } + auto id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); + + if (id > Database::CompressionAlgorithmMax) { + raiseError("Unsupported compression algorithm"); + return; + } + m_db->setCompressionAlgo(static_cast(id)); } void Kdbx4Reader::setMasterSeed(const QByteArray& data) { if (data.size() != 32) { raiseError("Invalid master seed size"); - } else { - m_masterSeed = data; + return; } + m_masterSeed = data; } void Kdbx4Reader::setEncryptionIV(const QByteArray& data) @@ -505,15 +502,15 @@ void Kdbx4Reader::setInnerRandomStreamID(const QByteArray& data) { if (data.size() != 4) { raiseError("Invalid random stream id size"); - } else { - quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); - KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id); - if (irsAlgo == KeePass2::InvalidProtectedStreamAlgo || irsAlgo == KeePass2::ArcFourVariant) { - raiseError("Invalid inner random stream cipher"); - } else { - m_irsAlgo = irsAlgo; - } + return; } + auto id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); + KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id); + if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo || irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) { + raiseError("Invalid inner random stream cipher"); + return; + } + m_irsAlgo = irsAlgo; } QHash Kdbx4Reader::binaryPool() diff --git a/src/format/Kdbx4Reader.h b/src/format/Kdbx4Reader.h index 0375209c4..9d8c531ef 100644 --- a/src/format/Kdbx4Reader.h +++ b/src/format/Kdbx4Reader.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/format/Kdbx4Writer.cpp b/src/format/Kdbx4Writer.cpp index 49d04c853..8b503c73a 100644 --- a/src/format/Kdbx4Writer.cpp +++ b/src/format/Kdbx4Writer.cpp @@ -1,5 +1,4 @@ /* - * Copyright (C) 2010 Felix Geyer * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify @@ -34,8 +33,6 @@ #include "streams/QtIOCompressor" #include "streams/SymmetricCipherStream.h" -#define CHECK_RETURN_FALSE(x) if (!(x)) return false; - Kdbx4Writer::Kdbx4Writer() : m_device(nullptr) { @@ -63,7 +60,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) QByteArray startBytes; QByteArray endOfHeader = "\r\n\r\n"; - if (db->challengeMasterSeed(masterSeed) == false) { + if (!db->challengeMasterSeed(masterSeed)) { raiseError(tr("Unable to issue challenge-response.")); return false; } @@ -88,12 +85,12 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::FILE_VERSION_4, KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray())); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CompressionFlags, + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray())); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CompressionFlags, Endian::sizedIntToBytes(static_cast(db->compressionAlgo()), KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::MasterSeed, masterSeed)); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EncryptionIV, encryptionIV)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::MasterSeed, masterSeed)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EncryptionIV, encryptionIV)); // Convert current Kdf to basic parameters QVariantMap kdfParams = KeePass2::kdfToParameters(db->kdf()); @@ -104,12 +101,12 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) return false; } QByteArray publicCustomData = db->publicCustomData(); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::KdfParameters, kdfParamBytes)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::KdfParameters, kdfParamBytes)); if (!publicCustomData.isEmpty()) { - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::PublicCustomData, publicCustomData)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::PublicCustomData, publicCustomData)); } - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EndOfHeader, endOfHeader)); + CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EndOfHeader, endOfHeader)); header.close(); m_device = device; headerData = header.data(); @@ -161,7 +158,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) QHash idMap; CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::InnerRandomStreamID, - Endian::sizedIntToBytes(static_cast(KeePass2::ChaCha20), + Endian::sizedIntToBytes(static_cast(KeePass2::ProtectedStreamAlgo::ChaCha20), KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::InnerRandomStreamKey, protectedStreamKey)); @@ -180,7 +177,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) } CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::End, QByteArray())); - KeePass2RandomStream randomStream(KeePass2::ChaCha20); + KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::ChaCha20); if (!randomStream.init(protectedStreamKey)) { raiseError(randomStream.errorString()); return false; @@ -217,15 +214,13 @@ bool Kdbx4Writer::writeData(const QByteArray& data) raiseError(m_device->errorString()); return false; } - else { - return true; - } + return true; } bool Kdbx4Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data) { QByteArray fieldIdArr; - fieldIdArr[0] = fieldId; + fieldIdArr[0] = static_cast(fieldId); CHECK_RETURN_FALSE(writeData(fieldIdArr)); CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size()), KeePass2::BYTEORDER))); CHECK_RETURN_FALSE(writeData(data)); @@ -264,47 +259,46 @@ bool Kdbx4Writer::serializeVariantMap(const QVariantMap& p, QByteArray& o) bool ok; QList keys = p.keys(); - for (int i = 0; i < keys.size(); ++i) { - QString k = keys.at(i); + for (const auto& k : keys) { KeePass2::VariantMapFieldType fieldType; QByteArray data; QVariant v = p.value(k); switch (static_cast(v.type())) { - case QMetaType::Type::Int: - fieldType = KeePass2::VariantMapFieldType::Int32; - data = Endian::sizedIntToBytes(v.toInt(&ok), KeePass2::BYTEORDER); - CHECK_RETURN_FALSE(ok); - break; - case QMetaType::Type::UInt: - fieldType = KeePass2::VariantMapFieldType::UInt32; - data = Endian::sizedIntToBytes(v.toUInt(&ok), KeePass2::BYTEORDER); - CHECK_RETURN_FALSE(ok); - break; - case QMetaType::Type::LongLong: - fieldType = KeePass2::VariantMapFieldType::Int64; - data = Endian::sizedIntToBytes(v.toLongLong(&ok), KeePass2::BYTEORDER); - CHECK_RETURN_FALSE(ok); - break; - case QMetaType::Type::ULongLong: - fieldType = KeePass2::VariantMapFieldType::UInt64; - data = Endian::sizedIntToBytes(v.toULongLong(&ok), KeePass2::BYTEORDER); - CHECK_RETURN_FALSE(ok); - break; - case QMetaType::Type::QString: - fieldType = KeePass2::VariantMapFieldType::String; - data = v.toString().toUtf8(); - break; - case QMetaType::Type::Bool: - fieldType = KeePass2::VariantMapFieldType::Bool; - data = QByteArray(1, (v.toBool() ? '\1' : '\0')); - break; - case QMetaType::Type::QByteArray: - fieldType = KeePass2::VariantMapFieldType::ByteArray; - data = v.toByteArray(); - break; - default: - qWarning("Unknown object type %d in QVariantMap", v.type()); - return false; + case QMetaType::Type::Int: + fieldType = KeePass2::VariantMapFieldType::Int32; + data = Endian::sizedIntToBytes(v.toInt(&ok), KeePass2::BYTEORDER); + CHECK_RETURN_FALSE(ok); + break; + case QMetaType::Type::UInt: + fieldType = KeePass2::VariantMapFieldType::UInt32; + data = Endian::sizedIntToBytes(v.toUInt(&ok), KeePass2::BYTEORDER); + CHECK_RETURN_FALSE(ok); + break; + case QMetaType::Type::LongLong: + fieldType = KeePass2::VariantMapFieldType::Int64; + data = Endian::sizedIntToBytes(v.toLongLong(&ok), KeePass2::BYTEORDER); + CHECK_RETURN_FALSE(ok); + break; + case QMetaType::Type::ULongLong: + fieldType = KeePass2::VariantMapFieldType::UInt64; + data = Endian::sizedIntToBytes(v.toULongLong(&ok), KeePass2::BYTEORDER); + CHECK_RETURN_FALSE(ok); + break; + case QMetaType::Type::QString: + fieldType = KeePass2::VariantMapFieldType::String; + data = v.toString().toUtf8(); + break; + case QMetaType::Type::Bool: + fieldType = KeePass2::VariantMapFieldType::Bool; + data = QByteArray(1, static_cast(v.toBool() ? '\1' : '\0')); + break; + case QMetaType::Type::QByteArray: + fieldType = KeePass2::VariantMapFieldType::ByteArray; + data = v.toByteArray(); + break; + default: + qWarning("Unknown object type %d in QVariantMap", v.type()); + return false; } QByteArray typeBytes; typeBytes[0] = static_cast(fieldType); diff --git a/src/format/Kdbx4Writer.h b/src/format/Kdbx4Writer.h index 4e703324d..7bb20e0f1 100644 --- a/src/format/Kdbx4Writer.h +++ b/src/format/Kdbx4Writer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/format/Kdbx4XmlReader.cpp b/src/format/Kdbx4XmlReader.cpp index 10dfe6475..9f6ce02c3 100644 --- a/src/format/Kdbx4XmlReader.cpp +++ b/src/format/Kdbx4XmlReader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,12 +19,9 @@ #include #include -#include #include "core/Endian.h" -#include "core/Database.h" #include "core/DatabaseIcons.h" -#include "core/Group.h" #include "core/Metadata.h" #include "core/Tools.h" #include "format/KeePass2RandomStream.h" @@ -68,30 +65,32 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando m_randomStream = randomStream; m_headerHash.clear(); - m_tmpParent = new Group(); + m_tmpParent.reset(new Group()); bool rootGroupParsed = false; - if (!m_xml.hasError() && m_xml.readNextStartElement()) { - if (m_xml.name() == "KeePassFile") { - rootGroupParsed = parseKeePassFile(); - } + if (m_xml.hasError()) { + raiseError(QString("XML parsing failure: %1").arg(m_xml.error())); + return; } - if (!m_xml.hasError() && !rootGroupParsed) { + if (m_xml.readNextStartElement() && m_xml.name() == "KeePassFile") { + rootGroupParsed = parseKeePassFile(); + } + + if (!rootGroupParsed) { raiseError("No root group"); + return; } - if (!m_xml.hasError()) { - if (!m_tmpParent->children().isEmpty()) { - qWarning("Kdbx4XmlReader::readDatabase: found %d invalid group reference(s)", - m_tmpParent->children().size()); - } + if (!m_tmpParent->children().isEmpty()) { + qWarning("Kdbx4XmlReader::readDatabase: found %d invalid group reference(s)", + m_tmpParent->children().size()); + } - if (!m_tmpParent->entries().isEmpty()) { - qWarning("Kdbx4XmlReader::readDatabase: found %d invalid entry reference(s)", - m_tmpParent->children().size()); - } + if (!m_tmpParent->entries().isEmpty()) { + qWarning("Kdbx4XmlReader::readDatabase: found %d invalid entry reference(s)", + m_tmpParent->children().size()); } const QSet poolKeys = m_binaryPool.keys().toSet(); @@ -100,13 +99,11 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando const QSet unusedKeys = poolKeys - entryKeys; if (!unmappedKeys.isEmpty()) { - raiseError("Unmapped keys left."); + qWarning("Unmapped keys left."); } - if (!m_xml.hasError()) { - for (const QString& key : unusedKeys) { - qWarning("Kdbx4XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); - } + for (const QString& key : unusedKeys) { + qWarning("Kdbx4XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); } QHash >::const_iterator i; @@ -131,13 +128,11 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando histEntry->setUpdateTimeinfo(true); } } - - delete m_tmpParent; } Database* Kdbx4XmlReader::readDatabase(QIODevice* device) { - Database* db = new Database(); + auto db = new Database(); readDatabase(device, db); return db; } @@ -158,14 +153,16 @@ QString Kdbx4XmlReader::errorString() { if (m_error) { return m_errorStr; - } else if (m_xml.hasError()) { + } + + if (m_xml.hasError()) { return QString("XML error:\n%1\nLine %2, column %3") .arg(m_xml.errorString()) .arg(m_xml.lineNumber()) .arg(m_xml.columnNumber()); - } else { - return QString(); } + + return QString(); } void Kdbx4XmlReader::raiseError(const QString& errorMessage) @@ -189,17 +186,21 @@ bool Kdbx4XmlReader::parseKeePassFile() while (!m_xml.hasError() && m_xml.readNextStartElement()) { if (m_xml.name() == "Meta") { parseMeta(); - } else if (m_xml.name() == "Root") { + continue; + } + + if (m_xml.name() == "Root") { if (rootElementFound) { rootParsedSuccessfully = false; - raiseError("Multiple root elements"); + qWarning("Multiple root elements"); } else { rootParsedSuccessfully = parseRoot(); rootElementFound = true; } - } else { - skipCurrentElement(); + continue; } + + skipCurrentElement(); } return rootParsedSuccessfully; @@ -259,14 +260,14 @@ void Kdbx4XmlReader::parseMeta() if (value >= -1) { m_meta->setHistoryMaxItems(value); } else { - raiseError("HistoryMaxItems invalid number"); + qWarning("HistoryMaxItems invalid number"); } } else if (m_xml.name() == "HistoryMaxSize") { int value = readNumber(); if (value >= -1) { m_meta->setHistoryMaxSize(value); } else { - raiseError("HistoryMaxSize invalid number"); + qWarning("HistoryMaxSize invalid number"); } } else if (m_xml.name() == "Binaries") { parseBinaries(); @@ -337,9 +338,10 @@ void Kdbx4XmlReader::parseIcon() if (uuidSet && iconSet) { m_meta->addCustomIcon(uuid, icon); - } else { - raiseError("Missing icon uuid or data"); + return; } + + raiseError("Missing icon uuid or data"); } void Kdbx4XmlReader::parseBinaries() @@ -347,27 +349,28 @@ void Kdbx4XmlReader::parseBinaries() Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries"); while (!m_xml.hasError() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Binary") { - QXmlStreamAttributes attr = m_xml.attributes(); - - QString id = attr.value("ID").toString(); - - QByteArray data; - if (attr.value("Compressed").compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) { - data = readCompressedBinary(); - } else { - data = readBinary(); - } - - if (m_binaryPool.contains(id)) { - qWarning("Kdbx4XmlReader::parseBinaries: overwriting binary item \"%s\"", - qPrintable(id)); - } - - m_binaryPool.insert(id, data); - } else { + if (m_xml.name() != "Binary") { skipCurrentElement(); + continue; } + + QXmlStreamAttributes attr = m_xml.attributes(); + + QString id = attr.value("ID").toString(); + + QByteArray data; + if (attr.value("Compressed").compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) { + data = readCompressedBinary(); + } else { + data = readBinary(); + } + + if (m_binaryPool.contains(id)) { + qWarning("Kdbx4XmlReader::parseBinaries: overwriting binary item \"%s\"", + qPrintable(id)); + } + + m_binaryPool.insert(id, data); } } @@ -378,9 +381,9 @@ void Kdbx4XmlReader::parseCustomData() while (!m_xml.hasError() && m_xml.readNextStartElement()) { if (m_xml.name() == "Item") { parseCustomDataItem(); - } else { - skipCurrentElement(); + continue; } + skipCurrentElement(); } } @@ -407,9 +410,10 @@ void Kdbx4XmlReader::parseCustomDataItem() if (keySet && valueSet) { m_meta->addCustomField(key, value); - } else { - raiseError("Missing custom data key or value"); + return; } + + raiseError("Missing custom data key or value"); } bool Kdbx4XmlReader::parseRoot() @@ -450,7 +454,7 @@ Group* Kdbx4XmlReader::parseGroup() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group"); - Group* group = new Group(); + auto group = new Group(); group->setUpdateTimeinfo(false); QList children; QList entries; @@ -466,11 +470,17 @@ Group* Kdbx4XmlReader::parseGroup() } else { group->setUuid(uuid); } - } else if (m_xml.name() == "Name") { + continue; + } + if (m_xml.name() == "Name") { group->setName(readString()); - } else if (m_xml.name() == "Notes") { + continue; + } + if (m_xml.name() == "Notes") { group->setNotes(readString()); - } else if (m_xml.name() == "IconID") { + continue; + } + if (m_xml.name() == "IconID") { int iconId = readNumber(); if (iconId < 0) { if (m_strictMode) { @@ -483,18 +493,28 @@ Group* Kdbx4XmlReader::parseGroup() } group->setIcon(iconId); - } else if (m_xml.name() == "CustomIconUUID") { + continue; + } + if (m_xml.name() == "CustomIconUUID") { Uuid uuid = readUuid(); if (!uuid.isNull()) { group->setIcon(uuid); } - } else if (m_xml.name() == "Times") { + continue; + } + if (m_xml.name() == "Times") { group->setTimeInfo(parseTimes()); - } else if (m_xml.name() == "IsExpanded") { + continue; + } + if (m_xml.name() == "IsExpanded") { group->setExpanded(readBool()); - } else if (m_xml.name() == "DefaultAutoTypeSequence") { + continue; + } + if (m_xml.name() == "DefaultAutoTypeSequence") { group->setDefaultAutoTypeSequence(readString()); - } else if (m_xml.name() == "EnableAutoType") { + continue; + } + if (m_xml.name() == "EnableAutoType") { QString str = readString(); if (str.compare("null", Qt::CaseInsensitive) == 0) { @@ -506,7 +526,9 @@ Group* Kdbx4XmlReader::parseGroup() } else { raiseError("Invalid EnableAutoType value"); } - } else if (m_xml.name() == "EnableSearching") { + continue; + } + if (m_xml.name() == "EnableSearching") { QString str = readString(); if (str.compare("null", Qt::CaseInsensitive) == 0) { @@ -518,21 +540,28 @@ Group* Kdbx4XmlReader::parseGroup() } else { raiseError("Invalid EnableSearching value"); } - } else if (m_xml.name() == "LastTopVisibleEntry") { + continue; + } + if (m_xml.name() == "LastTopVisibleEntry") { group->setLastTopVisibleEntry(getEntry(readUuid())); - } else if (m_xml.name() == "Group") { + continue; + } + if (m_xml.name() == "Group") { Group* newGroup = parseGroup(); if (newGroup) { children.append(newGroup); } - } else if (m_xml.name() == "Entry") { + continue; + } + if (m_xml.name() == "Entry") { Entry* newEntry = parseEntry(false); if (newEntry) { entries.append(newEntry); } - } else { - skipCurrentElement(); + continue; } + + skipCurrentElement(); } if (group->uuid().isNull() && !m_strictMode) { @@ -577,7 +606,7 @@ void Kdbx4XmlReader::parseDeletedObject() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject"); - DeletedObject delObj; + DeletedObject delObj{}; while (!m_xml.hasError() && m_xml.readNextStartElement()) { if (m_xml.name() == "UUID") { @@ -586,19 +615,24 @@ void Kdbx4XmlReader::parseDeletedObject() if (m_strictMode) { raiseError("Null DeleteObject uuid"); } - } else { - delObj.uuid = uuid; + continue; } - } else if (m_xml.name() == "DeletionTime") { - delObj.deletionTime = readDateTime(); - } else { - skipCurrentElement(); + delObj.uuid = uuid; + continue; } + if (m_xml.name() == "DeletionTime") { + delObj.deletionTime = readDateTime(); + continue; + } + skipCurrentElement(); } if (!delObj.uuid.isNull() && !delObj.deletionTime.isNull()) { m_db->addDeletedObject(delObj); - } else if (m_strictMode) { + return; + } + + if (m_strictMode) { raiseError("Missing DeletedObject uuid or time"); } } @@ -607,7 +641,7 @@ Entry* Kdbx4XmlReader::parseEntry(bool history) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry"); - Entry* entry = new Entry(); + auto entry = new Entry(); entry->setUpdateTimeinfo(false); QList historyItems; QList binaryRefs; @@ -624,7 +658,9 @@ Entry* Kdbx4XmlReader::parseEntry(bool history) } else { entry->setUuid(uuid); } - } else if (m_xml.name() == "IconID") { + continue; + } + if (m_xml.name() == "IconID") { int iconId = readNumber(); if (iconId < 0) { if (m_strictMode) { @@ -633,39 +669,59 @@ Entry* Kdbx4XmlReader::parseEntry(bool history) iconId = 0; } entry->setIcon(iconId); - } else if (m_xml.name() == "CustomIconUUID") { + continue; + } + if (m_xml.name() == "CustomIconUUID") { Uuid uuid = readUuid(); if (!uuid.isNull()) { entry->setIcon(uuid); } - } else if (m_xml.name() == "ForegroundColor") { + continue; + }if (m_xml.name() == "ForegroundColor") { entry->setForegroundColor(readColor()); - } else if (m_xml.name() == "BackgroundColor") { + continue; + } + if (m_xml.name() == "BackgroundColor") { entry->setBackgroundColor(readColor()); - } else if (m_xml.name() == "OverrideURL") { + continue; + } + if (m_xml.name() == "OverrideURL") { entry->setOverrideUrl(readString()); - } else if (m_xml.name() == "Tags") { + continue; + } + if (m_xml.name() == "Tags") { entry->setTags(readString()); - } else if (m_xml.name() == "Times") { + continue; + } + if (m_xml.name() == "Times") { entry->setTimeInfo(parseTimes()); - } else if (m_xml.name() == "String") { + continue; + } + if (m_xml.name() == "String") { parseEntryString(entry); - } else if (m_xml.name() == "Binary") { + continue; + } + if (m_xml.name() == "Binary") { QPair ref = parseEntryBinary(entry); if (!ref.first.isNull() && !ref.second.isNull()) { binaryRefs.append(ref); } - } else if (m_xml.name() == "AutoType") { + continue; + } + if (m_xml.name() == "AutoType") { parseAutoType(entry); - } else if (m_xml.name() == "History") { + continue; + } + if (m_xml.name() == "History") { if (history) { raiseError("History element in history entry"); } else { historyItems = parseEntryHistory(); } - } else { - skipCurrentElement(); + continue; } + + skipCurrentElement(); } if (entry->uuid().isNull() && !m_strictMode) { @@ -720,7 +776,10 @@ void Kdbx4XmlReader::parseEntryString(Entry* entry) if (m_xml.name() == "Key") { key = readString(); keySet = true; - } else if (m_xml.name() == "Value") { + continue; + } + + if (m_xml.name() == "Value") { QXmlStreamAttributes attr = m_xml.attributes(); value = readString(); @@ -740,26 +799,29 @@ void Kdbx4XmlReader::parseEntryString(Entry* entry) } } else { raiseError("Unable to decrypt entry string"); + continue; } } protect = isProtected || protectInMemory; valueSet = true; - } else { - skipCurrentElement(); + continue; } + + skipCurrentElement(); } if (keySet && valueSet) { // the default attributes are always there so additionally check if it's empty if (entry->attributes()->hasKey(key) && !entry->attributes()->value(key).isEmpty()) { raiseError("Duplicate custom attribute found"); - } else { - entry->attributes()->set(key, value, protect); + return; } - } else { - raiseError("Entry string key or value missing"); + entry->attributes()->set(key, value, protect); + return; } + + raiseError("Entry string key or value missing"); } QPair Kdbx4XmlReader::parseEntryBinary(Entry* entry) @@ -777,7 +839,9 @@ QPair Kdbx4XmlReader::parseEntryBinary(Entry* entry) if (m_xml.name() == "Key") { key = readString(); keySet = true; - } else if (m_xml.name() == "Value") { + continue; + } + if (m_xml.name() == "Value") { QXmlStreamAttributes attr = m_xml.attributes(); if (attr.hasAttribute("Ref")) { @@ -797,9 +861,9 @@ QPair Kdbx4XmlReader::parseEntryBinary(Entry* entry) } valueSet = true; - } else { - skipCurrentElement(); + continue; } + skipCurrentElement(); } if (keySet && valueSet) { @@ -856,9 +920,9 @@ void Kdbx4XmlReader::parseAutoTypeAssoc(Entry* entry) if (windowSet && sequenceSet) { entry->autoTypeAssociations()->add(assoc); - } else { - raiseError("Auto-type association window or sequence missing"); + return; } + raiseError("Auto-type association window or sequence missing"); } QList Kdbx4XmlReader::parseEntryHistory() @@ -917,14 +981,15 @@ bool Kdbx4XmlReader::readBool() if (str.compare("True", Qt::CaseInsensitive) == 0) { return true; - } else if (str.compare("False", Qt::CaseInsensitive) == 0) { - return false; - } else if (str.length() == 0) { - return false; - } else { - raiseError("Invalid bool value"); + } + if (str.compare("False", Qt::CaseInsensitive) == 0) { return false; } + if (str.length() == 0) { + return false; + } + raiseError("Invalid bool value"); + return false; } QDateTime Kdbx4XmlReader::readDateTime() @@ -936,18 +1001,18 @@ QDateTime Kdbx4XmlReader::readDateTime() QByteArray secsBytes = QByteArray::fromBase64(str.toUtf8()).leftJustified(8, '\0', true).left(8); qint64 secs = Endian::bytesToSizedInt(secsBytes, KeePass2::BYTEORDER); return QDateTime(QDate(1, 1, 1), QTime(0, 0, 0, 0), Qt::UTC).addSecs(secs); - } else { - QDateTime dt = QDateTime::fromString(str, Qt::ISODate); - if (dt.isValid()) { - return dt; - } else { - if (m_strictMode) { - raiseError("Invalid date time value"); - } - - return QDateTime::currentDateTimeUtc(); - } } + + QDateTime dt = QDateTime::fromString(str, Qt::ISODate); + if (dt.isValid()) { + return dt; + } + + if (m_strictMode) { + raiseError("Invalid date time value"); + } + + return QDateTime::currentDateTimeUtc(); } QColor Kdbx4XmlReader::readColor() @@ -955,26 +1020,26 @@ QColor Kdbx4XmlReader::readColor() QString colorStr = readString(); if (colorStr.isEmpty()) { - return QColor(); + return {}; } if (colorStr.length() != 7 || colorStr[0] != '#') { if (m_strictMode) { raiseError("Invalid color value"); } - return QColor(); + return {}; } QColor color; - for (int i = 0; i <= 2; i++) { - QString rgbPartStr = colorStr.mid(1 + 2*i, 2); + for (int i = 0; i <= 2; ++i) { + QString rgbPartStr = colorStr.mid(1 + 2 * i, 2); bool ok; int rgbPart = rgbPartStr.toInt(&ok, 16); if (!ok || rgbPart > 255) { if (m_strictMode) { raiseError("Invalid color rgb part"); } - return QColor(); + return {}; } if (i == 0) { @@ -1003,15 +1068,15 @@ Uuid Kdbx4XmlReader::readUuid() { QByteArray uuidBin = readBinary(); if (uuidBin.isEmpty()) { - return Uuid(); - } else if (uuidBin.length() != Uuid::Length) { + return {}; + } + if (uuidBin.length() != Uuid::Length) { if (m_strictMode) { raiseError("Invalid uuid value"); } - return Uuid(); - } else { - return Uuid(uuidBin); + return {}; } + return Uuid(uuidBin); } QByteArray Kdbx4XmlReader::readBinary() @@ -1045,14 +1110,14 @@ Group* Kdbx4XmlReader::getGroup(const Uuid& uuid) if (m_groups.contains(uuid)) { return m_groups.value(uuid); - } else { - Group* group = new Group(); - group->setUpdateTimeinfo(false); - group->setUuid(uuid); - group->setParent(m_tmpParent); - m_groups.insert(uuid, group); - return group; } + + auto group = new Group(); + group->setUpdateTimeinfo(false); + group->setUuid(uuid); + group->setParent(m_tmpParent.data()); + m_groups.insert(uuid, group); + return group; } Entry* Kdbx4XmlReader::getEntry(const Uuid& uuid) @@ -1063,14 +1128,14 @@ Entry* Kdbx4XmlReader::getEntry(const Uuid& uuid) if (m_entries.contains(uuid)) { return m_entries.value(uuid); - } else { - Entry* entry = new Entry(); - entry->setUpdateTimeinfo(false); - entry->setUuid(uuid); - entry->setGroup(m_tmpParent); - m_entries.insert(uuid, entry); - return entry; } + + auto entry = new Entry(); + entry->setUpdateTimeinfo(false); + entry->setUuid(uuid); + entry->setGroup(m_tmpParent.data()); + m_entries.insert(uuid, entry); + return entry; } void Kdbx4XmlReader::skipCurrentElement() diff --git a/src/format/Kdbx4XmlReader.h b/src/format/Kdbx4XmlReader.h index 6a0a6d4f4..229ac7425 100644 --- a/src/format/Kdbx4XmlReader.h +++ b/src/format/Kdbx4XmlReader.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -27,10 +27,10 @@ #include "core/TimeInfo.h" #include "core/Uuid.h" +#include "core/Group.h" class Database; class Entry; -class Group; class KeePass2RandomStream; class Metadata; @@ -88,7 +88,7 @@ private: KeePass2RandomStream* m_randomStream; Database* m_db; Metadata* m_meta; - Group* m_tmpParent; + QScopedPointer m_tmpParent; QHash m_groups; QHash m_entries; QHash m_binaryPool; diff --git a/src/format/Kdbx4XmlWriter.cpp b/src/format/Kdbx4XmlWriter.cpp index 374744563..5c99186ca 100644 --- a/src/format/Kdbx4XmlWriter.cpp +++ b/src/format/Kdbx4XmlWriter.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/format/Kdbx4XmlWriter.h b/src/format/Kdbx4XmlWriter.h index 79f27c98b..17d872580 100644 --- a/src/format/Kdbx4XmlWriter.h +++ b/src/format/Kdbx4XmlWriter.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index f89e828a1..88286a051 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -89,7 +89,8 @@ QSharedPointer KeePass2::uuidToKdf(const Uuid& uuid) { if (uuid == KDF_AES) { return QSharedPointer::create(); - } else if (uuid == KDF_ARGON2) { + } + if (uuid == KDF_ARGON2) { return QSharedPointer::create(); } @@ -100,13 +101,13 @@ QSharedPointer KeePass2::uuidToKdf(const Uuid& uuid) KeePass2::ProtectedStreamAlgo KeePass2::idToProtectedStreamAlgo(quint32 id) { switch (id) { - case static_cast(KeePass2::ArcFourVariant): - return KeePass2::ArcFourVariant; - case static_cast(KeePass2::Salsa20): - return KeePass2::Salsa20; - case static_cast(KeePass2::ChaCha20): - return KeePass2::ChaCha20; + case static_cast(KeePass2::ProtectedStreamAlgo::ArcFourVariant): + return KeePass2::ProtectedStreamAlgo::ArcFourVariant; + case static_cast(KeePass2::ProtectedStreamAlgo::Salsa20): + return KeePass2::ProtectedStreamAlgo::Salsa20; + case static_cast(KeePass2::ProtectedStreamAlgo::ChaCha20): + return KeePass2::ProtectedStreamAlgo::ChaCha20; default: - return KeePass2::InvalidProtectedStreamAlgo; + return KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo; } } diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index cdc594f5a..f7fa0d397 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -65,7 +65,7 @@ namespace KeePass2 extern const QList> CIPHERS; extern const QList> KDFS; - enum HeaderFieldID + enum class HeaderFieldID { EndOfHeader = 0, Comment = 1, @@ -90,7 +90,7 @@ namespace KeePass2 Binary = 3 }; - enum ProtectedStreamAlgo + enum class ProtectedStreamAlgo { ArcFourVariant = 1, Salsa20 = 2, diff --git a/src/format/KeePass2RandomStream.cpp b/src/format/KeePass2RandomStream.cpp index 9e3d4cbce..26824b7e5 100644 --- a/src/format/KeePass2RandomStream.cpp +++ b/src/format/KeePass2RandomStream.cpp @@ -123,9 +123,9 @@ bool KeePass2RandomStream::loadBlock() SymmetricCipher::Algorithm KeePass2RandomStream::mapAlgo(KeePass2::ProtectedStreamAlgo algo) { switch (algo) { - case KeePass2::ChaCha20: + case KeePass2::ProtectedStreamAlgo::ChaCha20: return SymmetricCipher::ChaCha20; - case KeePass2::Salsa20: + case KeePass2::ProtectedStreamAlgo::Salsa20: return SymmetricCipher::Salsa20; default: return SymmetricCipher::InvalidAlgorithm; diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index 0a04c79c6..c213b4a18 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -30,7 +30,7 @@ BaseKeePass2Reader::BaseKeePass2Reader() : m_error(false) , m_saveXml(false) - , m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo) + , m_irsAlgo(KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo) { m_errorStr.clear(); m_xmlData.clear(); diff --git a/src/format/KeePass2Writer.h b/src/format/KeePass2Writer.h index 85802eb44..f6ec129f2 100644 --- a/src/format/KeePass2Writer.h +++ b/src/format/KeePass2Writer.h @@ -27,6 +27,8 @@ #include "core/Database.h" #include "format/KeePass2.h" +#define CHECK_RETURN_FALSE(x) if (!(x)) return false; + class BaseKeePass2Writer { public: diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index 0c2eba796..c59f97423 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -134,7 +134,7 @@ void DatabaseSettingsWidget::save() { // first perform safety check for KDF rounds auto kdf = KeePass2::uuidToKdf(Uuid(m_uiEncryption->kdfComboBox->currentData().toByteArray())); - if (kdf->uuid() == KeePass2::KDF_ARGON2 and m_uiEncryption->transformRoundsSpinBox->value() > 1000) { + if (kdf->uuid() == KeePass2::KDF_ARGON2 && m_uiEncryption->transformRoundsSpinBox->value() > 1000) { QMessageBox warning; warning.setIcon(QMessageBox::Warning); warning.setWindowTitle(tr("Number of rounds too high")); @@ -147,7 +147,7 @@ void DatabaseSettingsWidget::save() if (warning.clickedButton() != ok) { return; } - } else if (kdf->uuid() == KeePass2::KDF_AES and m_uiEncryption->transformRoundsSpinBox->value() < 100000) { + } else if (kdf->uuid() == KeePass2::KDF_AES && m_uiEncryption->transformRoundsSpinBox->value() < 100000) { QMessageBox warning; warning.setIcon(QMessageBox::Warning); warning.setWindowTitle(tr("Number of rounds too low")); diff --git a/src/streams/HmacBlockStream.cpp b/src/streams/HmacBlockStream.cpp index 677921a60..780db98c1 100644 --- a/src/streams/HmacBlockStream.cpp +++ b/src/streams/HmacBlockStream.cpp @@ -57,10 +57,8 @@ bool HmacBlockStream::reset() // Write final block(s) only if device is writable and we haven't // already written a final block. if (isWritable() && (!m_buffer.isEmpty() || m_blockIndex != 0)) { - if (!m_buffer.isEmpty()) { - if (!writeHashedBlock()) { - return false; - } + if (!m_buffer.isEmpty() && !writeHashedBlock()) { + return false; } // write empty final block @@ -106,15 +104,14 @@ qint64 HmacBlockStream::readData(char* data, qint64 maxSize) if (!readHashedBlock()) { if (m_error) { return -1; - } else { - return maxSize - bytesRemaining; } + return maxSize - bytesRemaining; } } - int bytesToCopy = qMin(bytesRemaining, static_cast(m_buffer.size() - m_bufferPos)); + qint64 bytesToCopy = qMin(bytesRemaining, static_cast(m_buffer.size() - m_bufferPos)); - memcpy(data + offset, m_buffer.constData() + m_bufferPos, bytesToCopy); + memcpy(data + offset, m_buffer.constData() + m_bufferPos, static_cast(bytesToCopy)); offset += bytesToCopy; m_bufferPos += bytesToCopy; @@ -142,7 +139,7 @@ bool HmacBlockStream::readHashedBlock() setErrorString("Invalid block size size."); return false; } - qint32 blockSize = Endian::bytesToSizedInt(blockSizeBytes, ByteOrder); + auto blockSize = Endian::bytesToSizedInt(blockSizeBytes, ByteOrder); if (blockSize < 0) { m_error = true; setErrorString("Invalid block size."); @@ -169,7 +166,7 @@ bool HmacBlockStream::readHashedBlock() } m_bufferPos = 0; - m_blockIndex++; + ++m_blockIndex; if (blockSize == 0) { m_eof = true; @@ -191,21 +188,18 @@ qint64 HmacBlockStream::writeData(const char* data, qint64 maxSize) qint64 offset = 0; while (bytesRemaining > 0) { - int bytesToCopy = qMin(bytesRemaining, static_cast(m_blockSize - m_buffer.size())); + qint64 bytesToCopy = qMin(bytesRemaining, static_cast(m_blockSize - m_buffer.size())); - m_buffer.append(data + offset, bytesToCopy); + m_buffer.append(data + offset, static_cast(bytesToCopy)); offset += bytesToCopy; bytesRemaining -= bytesToCopy; - if (m_buffer.size() == m_blockSize) { - if (!writeHashedBlock()) { - if (m_error) { - return -1; - } else { - return maxSize - bytesRemaining; - } + if (m_buffer.size() == m_blockSize && !writeHashedBlock()) { + if (m_error) { + return -1; } + return maxSize - bytesRemaining; } } @@ -242,7 +236,7 @@ bool HmacBlockStream::writeHashedBlock() m_buffer.clear(); } - m_blockIndex++; + ++m_blockIndex; return true; } diff --git a/src/streams/SymmetricCipherStream.cpp b/src/streams/SymmetricCipherStream.cpp index ece2642b5..78476c618 100644 --- a/src/streams/SymmetricCipherStream.cpp +++ b/src/streams/SymmetricCipherStream.cpp @@ -252,7 +252,6 @@ bool SymmetricCipherStream::writeBlock(bool lastBlock) int SymmetricCipherStream::blockSize() const { if (m_streamCipher) { return 1024; - } else { - return m_cipher->blockSize(); } + return m_cipher->blockSize(); } diff --git a/tests/TestKeePass2RandomStream.cpp b/tests/TestKeePass2RandomStream.cpp index bef7af540..53852e82d 100644 --- a/tests/TestKeePass2RandomStream.cpp +++ b/tests/TestKeePass2RandomStream.cpp @@ -58,7 +58,7 @@ void TestKeePass2RandomStream::test() } - KeePass2RandomStream randomStream(KeePass2::Salsa20); + KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20); bool ok; QVERIFY(randomStream.init(key)); QByteArray randomStreamData; diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index 449f13a06..bf237a854 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -900,11 +900,11 @@ void TestGui::testDatabaseSettings() QWidget* dbSettingsWidget = m_dbWidget->findChild("databaseSettingsWidget"); QSpinBox* transformRoundsSpinBox = dbSettingsWidget->findChild("transformRoundsSpinBox"); QVERIFY(transformRoundsSpinBox != nullptr); - transformRoundsSpinBox->setValue(100); + transformRoundsSpinBox->setValue(123456); QTest::keyClick(transformRoundsSpinBox, Qt::Key_Enter); // wait for modified timer QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save*")); - QCOMPARE(m_db->kdf()->rounds(), Q_UINT64_C(100)); + QCOMPARE(m_db->kdf()->rounds(), Q_UINT64_C(123456)); triggerAction("actionDatabaseSave"); QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save")); From 72a1c65d003789fa9ad6330b8c6e6fe3ccb5eb42 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 7 Jan 2018 00:30:18 +0100 Subject: [PATCH 28/39] Fix memory leaks in tests --- src/format/Kdbx3XmlReader.cpp | 2 +- src/format/Kdbx4Writer.cpp | 32 ++++++++++++++++---------------- tests/gui/TestGui.cpp | 9 ++++++++- tests/gui/TestGui.h | 9 +++++---- 4 files changed, 30 insertions(+), 22 deletions(-) diff --git a/src/format/Kdbx3XmlReader.cpp b/src/format/Kdbx3XmlReader.cpp index 18dd0914d..fdc9cb416 100644 --- a/src/format/Kdbx3XmlReader.cpp +++ b/src/format/Kdbx3XmlReader.cpp @@ -1031,7 +1031,7 @@ Group* Kdbx3XmlReader::getGroup(const Uuid& uuid) if (m_groups.contains(uuid)) { return m_groups.value(uuid); } else { - Group* group = new Group(); + auto group = new Group(); group->setUpdateTimeinfo(false); group->setUuid(uuid); group->setParent(m_tmpParent); diff --git a/src/format/Kdbx4Writer.cpp b/src/format/Kdbx4Writer.cpp index 8b503c73a..fbd590563 100644 --- a/src/format/Kdbx4Writer.cpp +++ b/src/format/Kdbx4Writer.cpp @@ -114,24 +114,25 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) CHECK_RETURN_FALSE(writeData(headerData)); QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256); - QScopedPointer firstLayer, secondLayer; - QByteArray hmacKey = KeePass2::hmacKey(masterSeed, db->transformedMasterKey()); QByteArray headerHmac = CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256); CHECK_RETURN_FALSE(writeData(headerHash)); CHECK_RETURN_FALSE(writeData(headerHmac)); - HmacBlockStream* hmacStream = new HmacBlockStream(device, hmacKey); - if (!hmacStream->open(QIODevice::WriteOnly)) { - raiseError(hmacStream->errorString()); + QScopedPointer hmacBlockStream; + QScopedPointer cipherStream; + + hmacBlockStream.reset(new HmacBlockStream(device, hmacKey)); + if (!hmacBlockStream->open(QIODevice::WriteOnly)) { + raiseError(hmacBlockStream->errorString()); return false; } - firstLayer.reset(static_cast(hmacStream)); - SymmetricCipherStream* cipherStream = new SymmetricCipherStream(hmacStream, algo, - SymmetricCipher::algorithmMode(algo), - SymmetricCipher::Encrypt); + cipherStream.reset(new SymmetricCipherStream(hmacBlockStream.data(), algo, + SymmetricCipher::algorithmMode(algo), + SymmetricCipher::Encrypt)); + if (!cipherStream->init(finalKey, encryptionIV)) { raiseError(cipherStream->errorString()); return false; @@ -140,13 +141,12 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) raiseError(cipherStream->errorString()); return false; } - secondLayer.reset(static_cast(cipherStream)); QScopedPointer ioCompressor; if (db->compressionAlgo() == Database::CompressionNone) { - m_device = secondLayer.data(); + m_device = cipherStream.data(); } else { - ioCompressor.reset(new QtIOCompressor(secondLayer.data())); + ioCompressor.reset(new QtIOCompressor(cipherStream.data())); ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); if (!ioCompressor->open(QIODevice::WriteOnly)) { raiseError(ioCompressor->errorString()); @@ -191,12 +191,12 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) if (ioCompressor) { ioCompressor->close(); } - if (!secondLayer->reset()) { - raiseError(secondLayer->errorString()); + if (!cipherStream->reset()) { + raiseError(cipherStream->errorString()); return false; } - if (!firstLayer->reset()) { - raiseError(firstLayer->errorString()); + if (!hmacBlockStream->reset()) { + raiseError(hmacBlockStream->errorString()); return false; } diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index bf237a854..d591165c4 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -117,7 +117,14 @@ void TestGui::cleanup() triggerAction("actionDatabaseClose"); Tools::wait(100); + if (m_db) { + delete m_db; + } m_db = nullptr; + + if (m_dbWidget) { + delete m_dbWidget; + } m_dbWidget = nullptr; } @@ -1060,7 +1067,7 @@ void TestGui::dragAndDropGroup(const QModelIndex& sourceIndex, const QModelIndex QVERIFY(sourceIndex.isValid()); QVERIFY(targetIndex.isValid()); - GroupModel* groupModel = qobject_cast(m_dbWidget->findChild("groupView")->model()); + auto groupModel = qobject_cast(m_dbWidget->findChild("groupView")->model()); QMimeData mimeData; QByteArray encoded; diff --git a/tests/gui/TestGui.h b/tests/gui/TestGui.h index 1a2b24e74..b8d3ce1fc 100644 --- a/tests/gui/TestGui.h +++ b/tests/gui/TestGui.h @@ -23,6 +23,7 @@ #include #include +#include class Database; class DatabaseTabWidget; @@ -71,14 +72,14 @@ private: void clickIndex(const QModelIndex& index, QAbstractItemView* view, Qt::MouseButton button, Qt::KeyboardModifiers stateKey = 0); - MainWindow* m_mainWindow; - DatabaseTabWidget* m_tabWidget; - DatabaseWidget* m_dbWidget; + QPointer m_mainWindow; + QPointer m_tabWidget; + QPointer m_dbWidget; + QPointer m_db; QByteArray m_dbData; TemporaryFile m_dbFile; QString m_dbFileName; QString m_dbFilePath; - Database* m_db; }; #endif // KEEPASSX_TESTGUI_H From a6ddc22fb8f2bd760dfd8ce3a3d1bf0502d94d0d Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 7 Jan 2018 04:08:32 +0100 Subject: [PATCH 29/39] Refactor database readers/writers and XML handling * Refactor Kdbx*Reader * Refactor KdbxWriter * Refactor KdbxXmlReader * Refactor KdbxXmlWriter --- src/CMakeLists.txt | 8 +- src/cli/Extract.cpp | 2 +- src/format/Kdbx3Reader.cpp | 223 +--- src/format/Kdbx3Reader.h | 44 +- src/format/Kdbx3Writer.cpp | 98 +- src/format/Kdbx3Writer.h | 30 +- src/format/Kdbx3XmlReader.cpp | 1065 ----------------- src/format/Kdbx3XmlReader.h | 102 -- src/format/Kdbx3XmlWriter.cpp | 566 --------- src/format/Kdbx4Reader.cpp | 282 ++--- src/format/Kdbx4Reader.h | 43 +- src/format/Kdbx4Writer.cpp | 160 ++- src/format/Kdbx4Writer.h | 33 +- src/format/Kdbx4XmlReader.h | 102 -- src/format/KdbxReader.cpp | 267 +++++ src/format/KdbxReader.h | 107 ++ src/format/KdbxWriter.cpp | 74 ++ src/format/KdbxWriter.h | 90 ++ .../{Kdbx4XmlReader.cpp => KdbxXmlReader.cpp} | 264 ++-- src/format/KdbxXmlReader.h | 120 ++ .../{Kdbx4XmlWriter.cpp => KdbxXmlWriter.cpp} | 118 +- .../{Kdbx4XmlWriter.h => KdbxXmlWriter.h} | 28 +- src/format/KeePass2Reader.cpp | 132 +- src/format/KeePass2Reader.h | 66 +- src/format/KeePass2Repair.cpp | 18 +- src/format/KeePass2Writer.cpp | 106 +- src/format/KeePass2Writer.h | 54 +- tests/TestDeletedObjects.cpp | 5 +- tests/TestKeePass2XmlReader.cpp | 23 +- 29 files changed, 1313 insertions(+), 2917 deletions(-) delete mode 100644 src/format/Kdbx3XmlReader.cpp delete mode 100644 src/format/Kdbx3XmlReader.h delete mode 100644 src/format/Kdbx3XmlWriter.cpp delete mode 100644 src/format/Kdbx4XmlReader.h create mode 100644 src/format/KdbxReader.cpp create mode 100644 src/format/KdbxReader.h create mode 100644 src/format/KdbxWriter.cpp create mode 100644 src/format/KdbxWriter.h rename src/format/{Kdbx4XmlReader.cpp => KdbxXmlReader.cpp} (82%) create mode 100644 src/format/KdbxXmlReader.h rename src/format/{Kdbx4XmlWriter.cpp => KdbxXmlWriter.cpp} (82%) rename src/format/{Kdbx4XmlWriter.h => KdbxXmlWriter.h} (88%) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6ab5308dc..b007f0f93 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -85,16 +85,16 @@ set(keepassx_SOURCES format/KeePass2.cpp format/KeePass2RandomStream.cpp format/KeePass2Repair.cpp + format/KdbxReader.cpp + format/KdbxWriter.cpp + format/KdbxXmlReader.cpp format/KeePass2Reader.cpp format/KeePass2Writer.cpp format/Kdbx3Reader.cpp format/Kdbx3Writer.cpp - format/Kdbx3XmlReader.cpp - format/Kdbx3XmlWriter.cpp format/Kdbx4Reader.cpp format/Kdbx4Writer.cpp - format/Kdbx4XmlReader.cpp - format/Kdbx4XmlWriter.cpp + format/KdbxXmlWriter.cpp gui/AboutDialog.cpp gui/Application.cpp gui/CategoryListWidget.cpp diff --git a/src/cli/Extract.cpp b/src/cli/Extract.cpp index 73879f67d..54c8a45ee 100644 --- a/src/cli/Extract.cpp +++ b/src/cli/Extract.cpp @@ -101,7 +101,7 @@ int Extract::execute(QStringList arguments) Database* db = reader.readDatabase(&dbFile, compositeKey); delete db; - QByteArray xmlData = reader.xmlData(); + QByteArray xmlData = reader.reader()->xmlData(); if (reader.hasError()) { if (xmlData.isEmpty()) { diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index 5fb72a23e..638f84063 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -18,81 +18,21 @@ #include "Kdbx3Reader.h" -#include -#include -#include - -#include "core/Database.h" +#include "core/Group.h" #include "core/Endian.h" #include "crypto/CryptoHash.h" -#include "crypto/kdf/AesKdf.h" -#include "format/KeePass1.h" -#include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" -#include "format/Kdbx3XmlReader.h" +#include "format/KdbxXmlReader.h" #include "streams/HashedBlockStream.h" #include "streams/QtIOCompressor" -#include "streams/StoreDataStream.h" #include "streams/SymmetricCipherStream.h" -Kdbx3Reader::Kdbx3Reader() - : m_device(nullptr) - , m_headerStream(nullptr) - , m_headerEnd(false) - , m_db(nullptr) +#include + +Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData, + const CompositeKey& key, bool keepDatabase) { -} - -Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) -{ - QScopedPointer db(new Database()); - m_db = db.data(); - m_device = device; - m_error = false; - m_errorStr.clear(); - m_headerEnd = false; - m_xmlData.clear(); - m_masterSeed.clear(); - m_encryptionIV.clear(); - m_streamStartBytes.clear(); - m_protectedStreamKey.clear(); - - StoreDataStream headerStream(m_device); - headerStream.open(QIODevice::ReadOnly); - m_headerStream = &headerStream; - - bool ok; - - quint32 signature1 = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok); - if (!ok || signature1 != KeePass2::SIGNATURE_1) { - raiseError(tr("Not a KeePass database.")); - return nullptr; - } - - quint32 signature2 = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok); - if (ok && signature2 == KeePass1::SIGNATURE_2) { - raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" - "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; - } else if (!ok || signature2 != KeePass2::SIGNATURE_2) { - raiseError(tr("Not a KeePass database.")); - return nullptr; - } - - quint32 version = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok) - & KeePass2::FILE_VERSION_CRITICAL_MASK; - quint32 maxVersion = KeePass2::FILE_VERSION_3 & KeePass2::FILE_VERSION_CRITICAL_MASK; - if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) { - raiseError(tr("Unsupported KeePass KDBX 2 or 3 database version.")); - return nullptr; - } - - while (readHeaderField() && !hasError()) { - } - - headerStream.close(); + Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3); if (hasError()) { return nullptr; @@ -111,7 +51,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, return nullptr; } - if (m_db->challengeMasterSeed(m_masterSeed) == false) { + if (!m_db->challengeMasterSeed(m_masterSeed)) { raiseError(tr("Unable to issue challenge-response.")); return nullptr; } @@ -123,7 +63,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, QByteArray finalKey = hash.result(); SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher()); - SymmetricCipherStream cipherStream(m_device, cipher, + SymmetricCipherStream cipherStream(device, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt); if (!cipherStream.init(finalKey, m_encryptionIV)) { raiseError(cipherStream.errorString()); @@ -147,7 +87,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, return nullptr; } - QIODevice* xmlDevice; + QIODevice* xmlDevice = nullptr; QScopedPointer ioCompressor; if (m_db->compressionAlgo() == Database::CompressionNone) { @@ -168,43 +108,43 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, return nullptr; } - QScopedPointer buffer; - - if (m_saveXml) { + QBuffer buffer; + if (saveXml()) { m_xmlData = xmlDevice->readAll(); - buffer.reset(new QBuffer(&m_xmlData)); - buffer->open(QIODevice::ReadOnly); - xmlDevice = buffer.data(); + buffer.setBuffer(&m_xmlData); + buffer.open(QIODevice::ReadOnly); + xmlDevice = &buffer; } - Kdbx3XmlReader xmlReader; - xmlReader.readDatabase(xmlDevice, m_db, &randomStream); + Q_ASSERT(xmlDevice); + + KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3); + xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream); if (xmlReader.hasError()) { raiseError(xmlReader.errorString()); if (keepDatabase) { - return db.take(); - } else { - return nullptr; + return m_db.take(); } + return nullptr; } - Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty()); + Q_ASSERT(!xmlReader.headerHash().isEmpty() || m_kdbxVersion < KeePass2::FILE_VERSION_3); if (!xmlReader.headerHash().isEmpty()) { - QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256); + QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256); if (headerHash != xmlReader.headerHash()) { raiseError("Header doesn't match hash"); return nullptr; } } - return db.take(); + return m_db.take(); } -bool Kdbx3Reader::readHeaderField() +bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream) { - QByteArray fieldIDArray = m_headerStream->read(1); + QByteArray fieldIDArray = headerStream.read(1); if (fieldIDArray.size() != 1) { raiseError("Invalid header id size"); return false; @@ -212,7 +152,7 @@ bool Kdbx3Reader::readHeaderField() char fieldID = fieldIDArray.at(0); bool ok; - auto fieldLen = Endian::readSizedInt(m_headerStream, KeePass2::BYTEORDER, &ok); + auto fieldLen = Endian::readSizedInt(&headerStream, KeePass2::BYTEORDER, &ok); if (!ok) { raiseError("Invalid header field length"); return false; @@ -220,16 +160,17 @@ bool Kdbx3Reader::readHeaderField() QByteArray fieldData; if (fieldLen != 0) { - fieldData = m_headerStream->read(fieldLen); + fieldData = headerStream.read(fieldLen); if (fieldData.size() != fieldLen) { raiseError("Invalid header data length"); return false; } } + bool headerEnd = false; switch (static_cast(fieldID)) { case KeePass2::HeaderFieldID::EndOfHeader: - m_headerEnd = true; + headerEnd = true; break; case KeePass2::HeaderFieldID::CipherID: @@ -273,107 +214,5 @@ bool Kdbx3Reader::readHeaderField() break; } - return !m_headerEnd; -} - -void Kdbx3Reader::setCipher(const QByteArray& data) -{ - if (data.size() != Uuid::Length) { - raiseError("Invalid cipher uuid length"); - return; - } - - Uuid uuid(data); - - if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { - raiseError("Unsupported cipher"); - return; - } - m_db->setCipher(uuid); -} - -void Kdbx3Reader::setCompressionFlags(const QByteArray& data) -{ - if (data.size() != 4) { - raiseError("Invalid compression flags length"); - return; - } - auto id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); - - if (id > Database::CompressionAlgorithmMax) { - raiseError("Unsupported compression algorithm"); - return; - } - m_db->setCompressionAlgo(static_cast(id)); -} - -void Kdbx3Reader::setMasterSeed(const QByteArray& data) -{ - if (data.size() != 32) { - raiseError("Invalid master seed size"); - return; - } - m_masterSeed = data; -} - -void Kdbx3Reader::setTransformSeed(const QByteArray& data) -{ - if (data.size() != 32) { - raiseError("Invalid transform seed size"); - return; - } - - auto kdf = m_db->kdf(); - if (!kdf.isNull()) { - kdf->setSeed(data); - } -} - -void Kdbx3Reader::setTransformRounds(const QByteArray& data) -{ - if (data.size() != 8) { - raiseError("Invalid transform rounds size"); - return; - } - - auto rounds = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); - auto kdf = m_db->kdf(); - if (!kdf.isNull()) { - kdf->setRounds(rounds); - } -} - -void Kdbx3Reader::setEncryptionIV(const QByteArray& data) -{ - m_encryptionIV = data; -} - -void Kdbx3Reader::setProtectedStreamKey(const QByteArray& data) -{ - m_protectedStreamKey = data; -} - -void Kdbx3Reader::setStreamStartBytes(const QByteArray& data) -{ - if (data.size() != 32) { - raiseError("Invalid start bytes size"); - return; - } - m_streamStartBytes = data; -} - -void Kdbx3Reader::setInnerRandomStreamID(const QByteArray& data) -{ - if (data.size() != 4) { - raiseError("Invalid random stream id size"); - return; - } - quint32 id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); - KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id); - if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo || - irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) { - raiseError("Invalid inner random stream cipher"); - return; - } - m_irsAlgo = irsAlgo; + return !headerEnd; } diff --git a/src/format/Kdbx3Reader.h b/src/format/Kdbx3Reader.h index 024189eda..bd6a794d1 100644 --- a/src/format/Kdbx3Reader.h +++ b/src/format/Kdbx3Reader.h @@ -19,45 +19,19 @@ #ifndef KEEPASSX_KDBX3READER_H #define KEEPASSX_KDBX3READER_H -#include +#include "format/KdbxReader.h" -#include "format/KeePass2Reader.h" -#include "keys/CompositeKey.h" - -class Database; -class QIODevice; - -class Kdbx3Reader: public BaseKeePass2Reader +/** + * KDBX 2/3 reader implementation. + */ +class Kdbx3Reader: public KdbxReader { -Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader) - public: - Kdbx3Reader(); + Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData, + const CompositeKey& key, bool keepDatabase) override; - using BaseKeePass2Reader::readDatabase; - virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override; - -private: - bool readHeaderField(); - - void setCipher(const QByteArray& data); - void setCompressionFlags(const QByteArray& data); - void setMasterSeed(const QByteArray& data); - void setTransformSeed(const QByteArray& data); - void setTransformRounds(const QByteArray& data); - void setEncryptionIV(const QByteArray& data); - void setProtectedStreamKey(const QByteArray& data); - void setStreamStartBytes(const QByteArray& data); - void setInnerRandomStreamID(const QByteArray& data); - - QIODevice* m_device; - QIODevice* m_headerStream; - bool m_headerEnd; - - Database* m_db; - QByteArray m_masterSeed; - QByteArray m_encryptionIV; - QByteArray m_streamStartBytes; +protected: + bool readHeaderField(StoreDataStream& headerStream) override; }; #endif // KEEPASSX_KDBX3READER_H diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp index ca02fb7ac..b0b44c6b2 100644 --- a/src/format/Kdbx3Writer.cpp +++ b/src/format/Kdbx3Writer.cpp @@ -19,25 +19,17 @@ #include "Kdbx3Writer.h" #include -#include -#include #include "core/Database.h" -#include "core/Endian.h" #include "crypto/CryptoHash.h" -#include "crypto/kdf/AesKdf.h" #include "crypto/Random.h" +#include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" -#include "format/Kdbx3XmlWriter.h" +#include "format/KdbxXmlWriter.h" #include "streams/HashedBlockStream.h" #include "streams/QtIOCompressor" #include "streams/SymmetricCipherStream.h" -Kdbx3Writer::Kdbx3Writer() - : m_device(0) -{ -} - bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) { m_error = false; @@ -59,6 +51,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) return false; } + // generate transformed master key CryptoHash hash(CryptoHash::Sha256); hash.addData(masterSeed); hash.addData(db->challengeResponseKey()); @@ -66,37 +59,39 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) hash.addData(db->transformedMasterKey()); QByteArray finalKey = hash.result(); + // write header QBuffer header; header.open(QIODevice::WriteOnly); - m_device = &header; - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::FILE_VERSION_3, KeePass2::BYTEORDER))); + writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_3); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray())); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CompressionFlags, - Endian::sizedIntToBytes(db->compressionAlgo(), - KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray())); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::CompressionFlags, + Endian::sizedIntToBytes(db->compressionAlgo(), + KeePass2::BYTEORDER))); auto kdf = db->kdf(); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::MasterSeed, masterSeed)); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::TransformSeed, kdf->seed())); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::TransformRounds, - Endian::sizedIntToBytes(kdf->rounds(), + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed)); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::TransformSeed, kdf->seed())); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::TransformRounds, + Endian::sizedIntToBytes(kdf->rounds(), + KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV)); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::ProtectedStreamKey, protectedStreamKey)); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::StreamStartBytes, startBytes)); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::InnerRandomStreamID, + Endian::sizedIntToBytes(static_cast( + KeePass2::ProtectedStreamAlgo::Salsa20), KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EncryptionIV, encryptionIV)); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::ProtectedStreamKey, protectedStreamKey)); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::StreamStartBytes, startBytes)); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::InnerRandomStreamID, - Endian::sizedIntToBytes(static_cast(KeePass2::ProtectedStreamAlgo::Salsa20), - KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EndOfHeader, endOfHeader)); - + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::EndOfHeader, endOfHeader)); header.close(); - m_device = device; - QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256); - CHECK_RETURN_FALSE(writeData(header.data())); + // write header data + CHECK_RETURN_FALSE(writeData(device, header.data())); + + // hash header + const QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256); + + // write cipher stream SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher()); SymmetricCipherStream cipherStream(device, algo, SymmetricCipher::algorithmMode(algo), SymmetricCipher::Encrypt); @@ -105,8 +100,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) raiseError(cipherStream.errorString()); return false; } - m_device = &cipherStream; - CHECK_RETURN_FALSE(writeData(startBytes)); + CHECK_RETURN_FALSE(writeData(&cipherStream, startBytes)); HashedBlockStream hashedStream(&cipherStream); if (!hashedStream.open(QIODevice::WriteOnly)) { @@ -114,10 +108,11 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) return false; } + QIODevice* outputDevice = nullptr; QScopedPointer ioCompressor; if (db->compressionAlgo() == Database::CompressionNone) { - m_device = &hashedStream; + outputDevice = &hashedStream; } else { ioCompressor.reset(new QtIOCompressor(&hashedStream)); ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); @@ -125,17 +120,19 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) raiseError(ioCompressor->errorString()); return false; } - m_device = ioCompressor.data(); + outputDevice = ioCompressor.data(); } + Q_ASSERT(outputDevice); + KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20); if (!randomStream.init(protectedStreamKey)) { raiseError(randomStream.errorString()); return false; } - Kdbx3XmlWriter xmlWriter; - xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash); + KdbxXmlWriter xmlWriter(KeePass2::FILE_VERSION_3); + xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash); // Explicitly close/reset streams so they are flushed and we can detect // errors. QIODevice::close() resets errorString() etc. @@ -153,31 +150,8 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) if (xmlWriter.hasError()) { raiseError(xmlWriter.errorString()); - } - - return true; -} - -bool Kdbx3Writer::writeData(const QByteArray& data) -{ - if (m_device->write(data) != data.size()) { - raiseError(m_device->errorString()); return false; - } else { - return true; } -} - -bool Kdbx3Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data) -{ - Q_ASSERT(data.size() <= 65535); - - QByteArray fieldIdArr; - fieldIdArr[0] = static_cast(fieldId); - CHECK_RETURN_FALSE(writeData(fieldIdArr)); - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size()), - KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(data)); return true; } diff --git a/src/format/Kdbx3Writer.h b/src/format/Kdbx3Writer.h index 1b9fe7563..88c4d16a4 100644 --- a/src/format/Kdbx3Writer.h +++ b/src/format/Kdbx3Writer.h @@ -1,6 +1,5 @@ /* - * Copyright (C) 2017 KeePassXC Team - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2018 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -19,30 +18,15 @@ #ifndef KEEPASSX_KDBX3WRITER_H #define KEEPASSX_KDBX3WRITER_H -#include +#include "KdbxWriter.h" -#include "format/KeePass2.h" -#include "format/KeePass2Writer.h" -#include "keys/CompositeKey.h" - -class Database; -class QIODevice; - -class Kdbx3Writer: public BaseKeePass2Writer +/** + * KDBX2/3 writer implementation. + */ +class Kdbx3Writer: public KdbxWriter { -Q_DECLARE_TR_FUNCTIONS(Kdbx3Writer) - public: - Kdbx3Writer(); - - using BaseKeePass2Writer::writeDatabase; - bool writeDatabase(QIODevice* device, Database* db); - -private: - bool writeData(const QByteArray& data); - bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data); - - QIODevice* m_device; + bool writeDatabase(QIODevice* device, Database* db) override; }; #endif // KEEPASSX_KDBX3WRITER_H diff --git a/src/format/Kdbx3XmlReader.cpp b/src/format/Kdbx3XmlReader.cpp deleted file mode 100644 index fdc9cb416..000000000 --- a/src/format/Kdbx3XmlReader.cpp +++ /dev/null @@ -1,1065 +0,0 @@ -/* - * Copyright (C) 2017 KeePassXC Team - * Copyright (C) 2010 Felix Geyer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "Kdbx3XmlReader.h" - -#include -#include - -#include "core/Database.h" -#include "core/DatabaseIcons.h" -#include "core/Group.h" -#include "core/Metadata.h" -#include "core/Tools.h" -#include "format/KeePass2RandomStream.h" -#include "streams/QtIOCompressor" - -typedef QPair StringPair; - -Kdbx3XmlReader::Kdbx3XmlReader() - : m_randomStream(nullptr) - , m_db(nullptr) - , m_meta(nullptr) - , m_tmpParent(nullptr) - , m_error(false) - , m_strictMode(false) -{ -} - -void Kdbx3XmlReader::setStrictMode(bool strictMode) -{ - m_strictMode = strictMode; -} - -void Kdbx3XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) -{ - m_error = false; - m_errorStr.clear(); - - m_xml.clear(); - m_xml.setDevice(device); - - m_db = db; - m_meta = m_db->metadata(); - m_meta->setUpdateDatetime(false); - - m_randomStream = randomStream; - m_headerHash.clear(); - - m_tmpParent = new Group(); - - bool rootGroupParsed = false; - - if (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "KeePassFile") { - rootGroupParsed = parseKeePassFile(); - } - } - - if (!m_xml.error() && !rootGroupParsed) { - raiseError("No root group"); - } - - if (!m_xml.error()) { - if (!m_tmpParent->children().isEmpty()) { - qWarning("Kdbx3XmlReader::readDatabase: found %d invalid group reference(s)", - m_tmpParent->children().size()); - } - - if (!m_tmpParent->entries().isEmpty()) { - qWarning("Kdbx3XmlReader::readDatabase: found %d invalid entry reference(s)", - m_tmpParent->children().size()); - } - } - - const QSet poolKeys = m_binaryPool.keys().toSet(); - const QSet entryKeys = m_binaryMap.keys().toSet(); - const QSet unmappedKeys = entryKeys - poolKeys; - const QSet unusedKeys = poolKeys - entryKeys; - - if (!unmappedKeys.isEmpty()) { - raiseError("Unmapped keys left."); - } - - if (!m_xml.error()) { - for (const QString& key : unusedKeys) { - qWarning("Kdbx3XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); - } - } - - QHash >::const_iterator i; - for (i = m_binaryMap.constBegin(); i != m_binaryMap.constEnd(); ++i) { - const QPair& target = i.value(); - target.first->attachments()->set(target.second, m_binaryPool[i.key()]); - } - - m_meta->setUpdateDatetime(true); - - QHash::const_iterator iGroup; - for (iGroup = m_groups.constBegin(); iGroup != m_groups.constEnd(); ++iGroup) { - iGroup.value()->setUpdateTimeinfo(true); - } - - QHash::const_iterator iEntry; - for (iEntry = m_entries.constBegin(); iEntry != m_entries.constEnd(); ++iEntry) { - iEntry.value()->setUpdateTimeinfo(true); - - const QList historyItems = iEntry.value()->historyItems(); - for (Entry* histEntry : historyItems) { - histEntry->setUpdateTimeinfo(true); - } - } - - delete m_tmpParent; -} - -Database* Kdbx3XmlReader::readDatabase(QIODevice* device) -{ - Database* db = new Database(); - readDatabase(device, db); - return db; -} - -Database* Kdbx3XmlReader::readDatabase(const QString& filename) -{ - QFile file(filename); - file.open(QIODevice::ReadOnly); - return readDatabase(&file); -} - -bool Kdbx3XmlReader::hasError() -{ - return m_error || m_xml.hasError(); -} - -QString Kdbx3XmlReader::errorString() -{ - if (m_error) { - return m_errorStr; - } else if (m_xml.hasError()) { - return QString("XML error:\n%1\nLine %2, column %3") - .arg(m_xml.errorString()) - .arg(m_xml.lineNumber()) - .arg(m_xml.columnNumber()); - } else { - return QString(); - } -} - -void Kdbx3XmlReader::raiseError(const QString& errorMessage) -{ - m_error = true; - m_errorStr = errorMessage; -} - -QByteArray Kdbx3XmlReader::headerHash() -{ - return m_headerHash; -} - -bool Kdbx3XmlReader::parseKeePassFile() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile"); - - bool rootElementFound = false; - bool rootParsedSuccessfully = false; - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Meta") { - parseMeta(); - } else if (m_xml.name() == "Root") { - if (rootElementFound) { - rootParsedSuccessfully = false; - raiseError("Multiple root elements"); - } else { - rootParsedSuccessfully = parseRoot(); - rootElementFound = true; - } - } else { - skipCurrentElement(); - } - } - - return rootParsedSuccessfully; -} - -void Kdbx3XmlReader::parseMeta() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Meta"); - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Generator") { - m_meta->setGenerator(readString()); - } else if (m_xml.name() == "HeaderHash") { - m_headerHash = readBinary(); - } else if (m_xml.name() == "DatabaseName") { - m_meta->setName(readString()); - } else if (m_xml.name() == "DatabaseNameChanged") { - m_meta->setNameChanged(readDateTime()); - } else if (m_xml.name() == "DatabaseDescription") { - m_meta->setDescription(readString()); - } else if (m_xml.name() == "DatabaseDescriptionChanged") { - m_meta->setDescriptionChanged(readDateTime()); - } else if (m_xml.name() == "DefaultUserName") { - m_meta->setDefaultUserName(readString()); - } else if (m_xml.name() == "DefaultUserNameChanged") { - m_meta->setDefaultUserNameChanged(readDateTime()); - } else if (m_xml.name() == "MaintenanceHistoryDays") { - m_meta->setMaintenanceHistoryDays(readNumber()); - } else if (m_xml.name() == "Color") { - m_meta->setColor(readColor()); - } else if (m_xml.name() == "MasterKeyChanged") { - m_meta->setMasterKeyChanged(readDateTime()); - } else if (m_xml.name() == "MasterKeyChangeRec") { - m_meta->setMasterKeyChangeRec(readNumber()); - } else if (m_xml.name() == "MasterKeyChangeForce") { - m_meta->setMasterKeyChangeForce(readNumber()); - } else if (m_xml.name() == "MemoryProtection") { - parseMemoryProtection(); - } else if (m_xml.name() == "CustomIcons") { - parseCustomIcons(); - } else if (m_xml.name() == "RecycleBinEnabled") { - m_meta->setRecycleBinEnabled(readBool()); - } else if (m_xml.name() == "RecycleBinUUID") { - m_meta->setRecycleBin(getGroup(readUuid())); - } else if (m_xml.name() == "RecycleBinChanged") { - m_meta->setRecycleBinChanged(readDateTime()); - } else if (m_xml.name() == "EntryTemplatesGroup") { - m_meta->setEntryTemplatesGroup(getGroup(readUuid())); - } else if (m_xml.name() == "EntryTemplatesGroupChanged") { - m_meta->setEntryTemplatesGroupChanged(readDateTime()); - } else if (m_xml.name() == "LastSelectedGroup") { - m_meta->setLastSelectedGroup(getGroup(readUuid())); - } else if (m_xml.name() == "LastTopVisibleGroup") { - m_meta->setLastTopVisibleGroup(getGroup(readUuid())); - } else if (m_xml.name() == "HistoryMaxItems") { - int value = readNumber(); - if (value >= -1) { - m_meta->setHistoryMaxItems(value); - } else { - raiseError("HistoryMaxItems invalid number"); - } - } else if (m_xml.name() == "HistoryMaxSize") { - int value = readNumber(); - if (value >= -1) { - m_meta->setHistoryMaxSize(value); - } else { - raiseError("HistoryMaxSize invalid number"); - } - } else if (m_xml.name() == "Binaries") { - parseBinaries(); - } else if (m_xml.name() == "CustomData") { - parseCustomData(); - } else { - skipCurrentElement(); - } - } -} - -void Kdbx3XmlReader::parseMemoryProtection() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "MemoryProtection"); - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "ProtectTitle") { - m_meta->setProtectTitle(readBool()); - } else if (m_xml.name() == "ProtectUserName") { - m_meta->setProtectUsername(readBool()); - } else if (m_xml.name() == "ProtectPassword") { - m_meta->setProtectPassword(readBool()); - } else if (m_xml.name() == "ProtectURL") { - m_meta->setProtectUrl(readBool()); - } else if (m_xml.name() == "ProtectNotes") { - m_meta->setProtectNotes(readBool()); - } else { - skipCurrentElement(); - } - } -} - -void Kdbx3XmlReader::parseCustomIcons() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomIcons"); - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Icon") { - parseIcon(); - } else { - skipCurrentElement(); - } - } -} - -void Kdbx3XmlReader::parseIcon() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Icon"); - - Uuid uuid; - QImage icon; - bool uuidSet = false; - bool iconSet = false; - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "UUID") { - uuid = readUuid(); - uuidSet = !uuid.isNull(); - } else if (m_xml.name() == "Data") { - icon.loadFromData(readBinary()); - iconSet = true; - } else { - skipCurrentElement(); - } - } - - if (uuidSet && iconSet) { - m_meta->addCustomIcon(uuid, icon); - } else { - raiseError("Missing icon uuid or data"); - } -} - -void Kdbx3XmlReader::parseBinaries() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries"); - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Binary") { - QXmlStreamAttributes attr = m_xml.attributes(); - - QString id = attr.value("ID").toString(); - - QByteArray data; - if (attr.value("Compressed").compare(QLatin1String("True"), Qt::CaseInsensitive) == 0) { - data = readCompressedBinary(); - } else { - data = readBinary(); - } - - if (m_binaryPool.contains(id)) { - qWarning("Kdbx3XmlReader::parseBinaries: overwriting binary item \"%s\"", - qPrintable(id)); - } - - m_binaryPool.insert(id, data); - } else { - skipCurrentElement(); - } - } -} - -void Kdbx3XmlReader::parseCustomData() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData"); - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Item") { - parseCustomDataItem(); - } else { - skipCurrentElement(); - } - } -} - -void Kdbx3XmlReader::parseCustomDataItem() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item"); - - QString key; - QString value; - bool keySet = false; - bool valueSet = false; - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Key") { - key = readString(); - keySet = true; - } else if (m_xml.name() == "Value") { - value = readString(); - valueSet = true; - } else { - skipCurrentElement(); - } - } - - if (keySet && valueSet) { - m_meta->addCustomField(key, value); - } else { - raiseError("Missing custom data key or value"); - } -} - -bool Kdbx3XmlReader::parseRoot() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Root"); - - bool groupElementFound = false; - bool groupParsedSuccessfully = false; - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Group") { - if (groupElementFound) { - groupParsedSuccessfully = false; - raiseError("Multiple group elements"); - continue; - } - - Group* rootGroup = parseGroup(); - if (rootGroup) { - Group* oldRoot = m_db->rootGroup(); - m_db->setRootGroup(rootGroup); - delete oldRoot; - groupParsedSuccessfully = true; - } - - groupElementFound = true; - } else if (m_xml.name() == "DeletedObjects") { - parseDeletedObjects(); - } else { - skipCurrentElement(); - } - } - - return groupParsedSuccessfully; -} - -Group* Kdbx3XmlReader::parseGroup() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group"); - - Group* group = new Group(); - group->setUpdateTimeinfo(false); - QList children; - QList entries; - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "UUID") { - Uuid uuid = readUuid(); - if (uuid.isNull()) { - if (m_strictMode) { - raiseError("Null group uuid"); - } else { - group->setUuid(Uuid::random()); - } - } else { - group->setUuid(uuid); - } - } else if (m_xml.name() == "Name") { - group->setName(readString()); - } else if (m_xml.name() == "Notes") { - group->setNotes(readString()); - } else if (m_xml.name() == "IconID") { - int iconId = readNumber(); - if (iconId < 0) { - if (m_strictMode) { - raiseError("Invalid group icon number"); - } - iconId = 0; - } else { - if (iconId >= DatabaseIcons::IconCount) { - qWarning("Kdbx3XmlReader::parseGroup: icon id \"%d\" not supported", iconId); - } - group->setIcon(iconId); - } - } else if (m_xml.name() == "CustomIconUUID") { - Uuid uuid = readUuid(); - if (!uuid.isNull()) { - group->setIcon(uuid); - } - } else if (m_xml.name() == "Times") { - group->setTimeInfo(parseTimes()); - } else if (m_xml.name() == "IsExpanded") { - group->setExpanded(readBool()); - } else if (m_xml.name() == "DefaultAutoTypeSequence") { - group->setDefaultAutoTypeSequence(readString()); - } else if (m_xml.name() == "EnableAutoType") { - QString str = readString(); - - if (str.compare("null", Qt::CaseInsensitive) == 0) { - group->setAutoTypeEnabled(Group::Inherit); - } else if (str.compare("true", Qt::CaseInsensitive) == 0) { - group->setAutoTypeEnabled(Group::Enable); - } else if (str.compare("false", Qt::CaseInsensitive) == 0) { - group->setAutoTypeEnabled(Group::Disable); - } else { - raiseError("Invalid EnableAutoType value"); - } - } else if (m_xml.name() == "EnableSearching") { - QString str = readString(); - - if (str.compare("null", Qt::CaseInsensitive) == 0) { - group->setSearchingEnabled(Group::Inherit); - } else if (str.compare("true", Qt::CaseInsensitive) == 0) { - group->setSearchingEnabled(Group::Enable); - } else if (str.compare("false", Qt::CaseInsensitive) == 0) { - group->setSearchingEnabled(Group::Disable); - } else { - raiseError("Invalid EnableSearching value"); - } - } else if (m_xml.name() == "LastTopVisibleEntry") { - group->setLastTopVisibleEntry(getEntry(readUuid())); - } else if (m_xml.name() == "Group") { - Group* newGroup = parseGroup(); - if (newGroup) { - children.append(newGroup); - } - } else if (m_xml.name() == "Entry") { - Entry* newEntry = parseEntry(false); - if (newEntry) { - entries.append(newEntry); - } - } else { - skipCurrentElement(); - } - } - - if (group->uuid().isNull() && !m_strictMode) { - group->setUuid(Uuid::random()); - } - - if (!group->uuid().isNull()) { - Group* tmpGroup = group; - group = getGroup(tmpGroup->uuid()); - group->copyDataFrom(tmpGroup); - group->setUpdateTimeinfo(false); - delete tmpGroup; - } else if (!hasError()) { - raiseError("No group uuid found"); - } - - for (Group* child : asConst(children)) { - child->setParent(group); - } - - for (Entry* entry : asConst(entries)) { - entry->setGroup(group); - } - - return group; -} - -void Kdbx3XmlReader::parseDeletedObjects() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObjects"); - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "DeletedObject") { - parseDeletedObject(); - } else { - skipCurrentElement(); - } - } -} - -void Kdbx3XmlReader::parseDeletedObject() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject"); - - DeletedObject delObj; - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "UUID") { - Uuid uuid = readUuid(); - if (uuid.isNull()) { - if (m_strictMode) { - raiseError("Null DeleteObject uuid"); - } - } else { - delObj.uuid = uuid; - } - } else if (m_xml.name() == "DeletionTime") { - delObj.deletionTime = readDateTime(); - } else { - skipCurrentElement(); - } - } - - if (!delObj.uuid.isNull() && !delObj.deletionTime.isNull()) { - m_db->addDeletedObject(delObj); - } else if (m_strictMode) { - raiseError("Missing DeletedObject uuid or time"); - } -} - -Entry* Kdbx3XmlReader::parseEntry(bool history) -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry"); - - Entry* entry = new Entry(); - entry->setUpdateTimeinfo(false); - QList historyItems; - QList binaryRefs; - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "UUID") { - Uuid uuid = readUuid(); - if (uuid.isNull()) { - if (m_strictMode) { - raiseError("Null entry uuid"); - } else { - entry->setUuid(Uuid::random()); - } - } else { - entry->setUuid(uuid); - } - } else if (m_xml.name() == "IconID") { - int iconId = readNumber(); - if (iconId < 0) { - if (m_strictMode) { - raiseError("Invalid entry icon number"); - } - iconId = 0; - } else { - entry->setIcon(iconId); - } - } else if (m_xml.name() == "CustomIconUUID") { - Uuid uuid = readUuid(); - if (!uuid.isNull()) { - entry->setIcon(uuid); - } - } else if (m_xml.name() == "ForegroundColor") { - entry->setForegroundColor(readColor()); - } else if (m_xml.name() == "BackgroundColor") { - entry->setBackgroundColor(readColor()); - } else if (m_xml.name() == "OverrideURL") { - entry->setOverrideUrl(readString()); - } else if (m_xml.name() == "Tags") { - entry->setTags(readString()); - } else if (m_xml.name() == "Times") { - entry->setTimeInfo(parseTimes()); - } else if (m_xml.name() == "String") { - parseEntryString(entry); - } else if (m_xml.name() == "Binary") { - QPair ref = parseEntryBinary(entry); - if (!ref.first.isNull() && !ref.second.isNull()) { - binaryRefs.append(ref); - } - } else if (m_xml.name() == "AutoType") { - parseAutoType(entry); - } else if (m_xml.name() == "History") { - if (history) { - raiseError("History element in history entry"); - } else { - historyItems = parseEntryHistory(); - } - } else { - skipCurrentElement(); - } - } - - if (entry->uuid().isNull() && !m_strictMode) { - entry->setUuid(Uuid::random()); - } - - if (!entry->uuid().isNull()) { - if (history) { - entry->setUpdateTimeinfo(false); - } else { - Entry* tmpEntry = entry; - - entry = getEntry(tmpEntry->uuid()); - entry->copyDataFrom(tmpEntry); - entry->setUpdateTimeinfo(false); - - delete tmpEntry; - } - } else if (!hasError()) { - raiseError("No entry uuid found"); - } - - for (Entry* historyItem : asConst(historyItems)) { - if (historyItem->uuid() != entry->uuid()) { - if (m_strictMode) { - raiseError("History element with different uuid"); - } else { - historyItem->setUuid(entry->uuid()); - } - } - entry->addHistoryItem(historyItem); - } - - for (const StringPair& ref : asConst(binaryRefs)) { - m_binaryMap.insertMulti(ref.first, qMakePair(entry, ref.second)); - } - - return entry; -} - -void Kdbx3XmlReader::parseEntryString(Entry* entry) -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "String"); - - QString key; - QString value; - bool protect = false; - bool keySet = false; - bool valueSet = false; - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Key") { - key = readString(); - keySet = true; - } else if (m_xml.name() == "Value") { - QXmlStreamAttributes attr = m_xml.attributes(); - value = readString(); - - bool isProtected = attr.value("Protected") == "True"; - bool protectInMemory = attr.value("ProtectInMemory") == "True"; - - if (isProtected && !value.isEmpty()) { - if (m_randomStream) { - QByteArray ciphertext = QByteArray::fromBase64(value.toLatin1()); - bool ok; - QByteArray plaintext = m_randomStream->process(ciphertext, &ok); - if (!ok) { - value.clear(); - raiseError(m_randomStream->errorString()); - } else { - value = QString::fromUtf8(plaintext); - } - } else { - raiseError("Unable to decrypt entry string"); - } - } - - protect = isProtected || protectInMemory; - valueSet = true; - } else { - skipCurrentElement(); - } - } - - if (keySet && valueSet) { - // the default attributes are always there so additionally check if it's empty - if (entry->attributes()->hasKey(key) && !entry->attributes()->value(key).isEmpty()) { - raiseError("Duplicate custom attribute found"); - } else { - entry->attributes()->set(key, value, protect); - } - } else { - raiseError("Entry string key or value missing"); - } -} - -QPair Kdbx3XmlReader::parseEntryBinary(Entry* entry) -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binary"); - - QPair poolRef; - - QString key; - QByteArray value; - bool keySet = false; - bool valueSet = false; - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Key") { - key = readString(); - keySet = true; - } else if (m_xml.name() == "Value") { - QXmlStreamAttributes attr = m_xml.attributes(); - - if (attr.hasAttribute("Ref")) { - poolRef = qMakePair(attr.value("Ref").toString(), key); - m_xml.skipCurrentElement(); - } else { - // format compatibility - value = readBinary(); - bool isProtected = attr.hasAttribute("Protected") - && (attr.value("Protected") == "True"); - - if (isProtected && !value.isEmpty()) { - if (!m_randomStream->processInPlace(value)) { - raiseError(m_randomStream->errorString()); - } - } - } - - valueSet = true; - } else { - skipCurrentElement(); - } - } - - if (keySet && valueSet) { - if (entry->attachments()->hasKey(key)) { - raiseError("Duplicate attachment found"); - } else { - entry->attachments()->set(key, value); - } - } else { - raiseError("Entry binary key or value missing"); - } - - return poolRef; -} - -void Kdbx3XmlReader::parseAutoType(Entry* entry) -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "AutoType"); - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Enabled") { - entry->setAutoTypeEnabled(readBool()); - } else if (m_xml.name() == "DataTransferObfuscation") { - entry->setAutoTypeObfuscation(readNumber()); - } else if (m_xml.name() == "DefaultSequence") { - entry->setDefaultAutoTypeSequence(readString()); - } else if (m_xml.name() == "Association") { - parseAutoTypeAssoc(entry); - } else { - skipCurrentElement(); - } - } -} - -void Kdbx3XmlReader::parseAutoTypeAssoc(Entry* entry) -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Association"); - - AutoTypeAssociations::Association assoc; - bool windowSet = false; - bool sequenceSet = false; - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Window") { - assoc.window = readString(); - windowSet = true; - } else if (m_xml.name() == "KeystrokeSequence") { - assoc.sequence = readString(); - sequenceSet = true; - } else { - skipCurrentElement(); - } - } - - if (windowSet && sequenceSet) { - entry->autoTypeAssociations()->add(assoc); - } else { - raiseError("Auto-type association window or sequence missing"); - } -} - -QList Kdbx3XmlReader::parseEntryHistory() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "History"); - - QList historyItems; - - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "Entry") { - historyItems.append(parseEntry(true)); - } else { - skipCurrentElement(); - } - } - - return historyItems; -} - -TimeInfo Kdbx3XmlReader::parseTimes() -{ - Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times"); - - TimeInfo timeInfo; - while (!m_xml.error() && m_xml.readNextStartElement()) { - if (m_xml.name() == "LastModificationTime") { - timeInfo.setLastModificationTime(readDateTime()); - } else if (m_xml.name() == "CreationTime") { - timeInfo.setCreationTime(readDateTime()); - } else if (m_xml.name() == "LastAccessTime") { - timeInfo.setLastAccessTime(readDateTime()); - } else if (m_xml.name() == "ExpiryTime") { - timeInfo.setExpiryTime(readDateTime()); - } else if (m_xml.name() == "Expires") { - timeInfo.setExpires(readBool()); - } else if (m_xml.name() == "UsageCount") { - timeInfo.setUsageCount(readNumber()); - } else if (m_xml.name() == "LocationChanged") { - timeInfo.setLocationChanged(readDateTime()); - } else { - skipCurrentElement(); - } - } - - return timeInfo; -} - -QString Kdbx3XmlReader::readString() -{ - return m_xml.readElementText(); -} - -bool Kdbx3XmlReader::readBool() -{ - QString str = readString(); - - if (str.compare("True", Qt::CaseInsensitive) == 0) { - return true; - } else if (str.compare("False", Qt::CaseInsensitive) == 0) { - return false; - } else if (str.length() == 0) { - return false; - } else { - raiseError("Invalid bool value"); - return false; - } -} - -QDateTime Kdbx3XmlReader::readDateTime() -{ - QString str = readString(); - QDateTime dt = QDateTime::fromString(str, Qt::ISODate); - - if (!dt.isValid()) { - if (m_strictMode) { - raiseError("Invalid date time value"); - } else { - dt = QDateTime::currentDateTimeUtc(); - } - } - - return dt; -} - -QColor Kdbx3XmlReader::readColor() -{ - QString colorStr = readString(); - - if (colorStr.isEmpty()) { - return QColor(); - } - - if (colorStr.length() != 7 || colorStr[0] != '#') { - if (m_strictMode) { - raiseError("Invalid color value"); - } - return QColor(); - } - - QColor color; - for (int i = 0; i <= 2; i++) { - QString rgbPartStr = colorStr.mid(1 + 2 * i, 2); - bool ok; - int rgbPart = rgbPartStr.toInt(&ok, 16); - if (!ok || rgbPart > 255) { - if (m_strictMode) { - raiseError("Invalid color rgb part"); - } - return QColor(); - } - - if (i == 0) { - color.setRed(rgbPart); - } else if (i == 1) { - color.setGreen(rgbPart); - } else { - color.setBlue(rgbPart); - } - } - - return color; -} - -int Kdbx3XmlReader::readNumber() -{ - bool ok; - int result = readString().toInt(&ok); - if (!ok) { - raiseError("Invalid number value"); - } - return result; -} - -Uuid Kdbx3XmlReader::readUuid() -{ - QByteArray uuidBin = readBinary(); - if (uuidBin.isEmpty()) { - return Uuid(); - } else if (uuidBin.length() != Uuid::Length) { - if (m_strictMode) { - raiseError("Invalid uuid value"); - } - return Uuid(); - } else { - return Uuid(uuidBin); - } -} - -QByteArray Kdbx3XmlReader::readBinary() -{ - return QByteArray::fromBase64(readString().toLatin1()); -} - -QByteArray Kdbx3XmlReader::readCompressedBinary() -{ - QByteArray rawData = readBinary(); - - QBuffer buffer(&rawData); - buffer.open(QIODevice::ReadOnly); - - QtIOCompressor compressor(&buffer); - compressor.setStreamFormat(QtIOCompressor::GzipFormat); - compressor.open(QIODevice::ReadOnly); - - QByteArray result; - if (!Tools::readAllFromDevice(&compressor, result)) { - raiseError("Unable to decompress binary"); - } - return result; -} - -Group* Kdbx3XmlReader::getGroup(const Uuid& uuid) -{ - if (uuid.isNull()) { - return nullptr; - } - - if (m_groups.contains(uuid)) { - return m_groups.value(uuid); - } else { - auto group = new Group(); - group->setUpdateTimeinfo(false); - group->setUuid(uuid); - group->setParent(m_tmpParent); - m_groups.insert(uuid, group); - return group; - } -} - -Entry* Kdbx3XmlReader::getEntry(const Uuid& uuid) -{ - if (uuid.isNull()) { - return nullptr; - } - - if (m_entries.contains(uuid)) { - return m_entries.value(uuid); - } else { - Entry* entry = new Entry(); - entry->setUpdateTimeinfo(false); - entry->setUuid(uuid); - entry->setGroup(m_tmpParent); - m_entries.insert(uuid, entry); - return entry; - } -} - -void Kdbx3XmlReader::skipCurrentElement() -{ - qWarning("Kdbx3XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString())); - m_xml.skipCurrentElement(); -} diff --git a/src/format/Kdbx3XmlReader.h b/src/format/Kdbx3XmlReader.h deleted file mode 100644 index 213b21c92..000000000 --- a/src/format/Kdbx3XmlReader.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2017 KeePassXC Team - * Copyright (C) 2010 Felix Geyer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef KEEPASSX_KDBX3XMLREADER_H -#define KEEPASSX_KDBX3XMLREADER_H - -#include -#include -#include -#include -#include -#include - -#include "core/TimeInfo.h" -#include "core/Uuid.h" - -class Database; -class Entry; -class Group; -class KeePass2RandomStream; -class Metadata; - -class Kdbx3XmlReader -{ -Q_DECLARE_TR_FUNCTIONS(Kdbx3XmlReader) - -public: - Kdbx3XmlReader(); - Database* readDatabase(QIODevice* device); - void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr); - Database* readDatabase(const QString& filename); - bool hasError(); - QString errorString(); - QByteArray headerHash(); - void setStrictMode(bool strictMode); - -private: - bool parseKeePassFile(); - void parseMeta(); - void parseMemoryProtection(); - void parseCustomIcons(); - void parseIcon(); - void parseBinaries(); - void parseCustomData(); - void parseCustomDataItem(); - bool parseRoot(); - Group* parseGroup(); - void parseDeletedObjects(); - void parseDeletedObject(); - Entry* parseEntry(bool history); - void parseEntryString(Entry* entry); - QPair parseEntryBinary(Entry* entry); - void parseAutoType(Entry* entry); - void parseAutoTypeAssoc(Entry* entry); - QList parseEntryHistory(); - TimeInfo parseTimes(); - - QString readString(); - bool readBool(); - QDateTime readDateTime(); - QColor readColor(); - int readNumber(); - Uuid readUuid(); - QByteArray readBinary(); - QByteArray readCompressedBinary(); - - Group* getGroup(const Uuid& uuid); - Entry* getEntry(const Uuid& uuid); - void raiseError(const QString& errorMessage); - void skipCurrentElement(); - - QXmlStreamReader m_xml; - KeePass2RandomStream* m_randomStream; - Database* m_db; - Metadata* m_meta; - Group* m_tmpParent; - QHash m_groups; - QHash m_entries; - QHash m_binaryPool; - QHash > m_binaryMap; - QByteArray m_headerHash; - bool m_error; - QString m_errorStr; - bool m_strictMode; -}; - -#endif // KEEPASSX_KDBX3XMLREADER_H diff --git a/src/format/Kdbx3XmlWriter.cpp b/src/format/Kdbx3XmlWriter.cpp deleted file mode 100644 index bfa94a77c..000000000 --- a/src/format/Kdbx3XmlWriter.cpp +++ /dev/null @@ -1,566 +0,0 @@ -/* - * Copyright (C) 2017 KeePassXC Team - * Copyright (C) 2010 Felix Geyer - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#include "Kdbx3XmlWriter.h" - -#include -#include - -#include "core/Metadata.h" -#include "format/KeePass2RandomStream.h" -#include "streams/QtIOCompressor" - -Kdbx3XmlWriter::Kdbx3XmlWriter() - : m_db(nullptr) - , m_meta(nullptr) - , m_randomStream(nullptr) - , m_error(false) -{ - m_xml.setAutoFormatting(true); - m_xml.setAutoFormattingIndent(-1); // 1 tab - m_xml.setCodec("UTF-8"); -} - -void Kdbx3XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, - const QByteArray& headerHash) -{ - m_db = db; - m_meta = db->metadata(); - m_randomStream = randomStream; - m_headerHash = headerHash; - - generateIdMap(); - - m_xml.setDevice(device); - - m_xml.writeStartDocument("1.0", true); - - m_xml.writeStartElement("KeePassFile"); - - writeMetadata(); - writeRoot(); - - m_xml.writeEndElement(); - - m_xml.writeEndDocument(); - - if (m_xml.hasError()) { - raiseError(device->errorString()); - } -} - -void Kdbx3XmlWriter::writeDatabase(const QString& filename, Database* db) -{ - QFile file(filename); - file.open(QIODevice::WriteOnly | QIODevice::Truncate); - writeDatabase(&file, db); -} - -bool Kdbx3XmlWriter::hasError() -{ - return m_error; -} - -QString Kdbx3XmlWriter::errorString() -{ - return m_errorStr; -} - -void Kdbx3XmlWriter::generateIdMap() -{ - const QList allEntries = m_db->rootGroup()->entriesRecursive(true); - int nextId = 0; - - for (Entry* entry : allEntries) { - const QList attachmentKeys = entry->attachments()->keys(); - for (const QString& key : attachmentKeys) { - QByteArray data = entry->attachments()->value(key); - if (!m_idMap.contains(data)) { - m_idMap.insert(data, nextId++); - } - } - } -} - -void Kdbx3XmlWriter::writeMetadata() -{ - m_xml.writeStartElement("Meta"); - - writeString("Generator", m_meta->generator()); - if (!m_headerHash.isEmpty()) { - writeBinary("HeaderHash", m_headerHash); - } - writeString("DatabaseName", m_meta->name()); - writeDateTime("DatabaseNameChanged", m_meta->nameChanged()); - writeString("DatabaseDescription", m_meta->description()); - writeDateTime("DatabaseDescriptionChanged", m_meta->descriptionChanged()); - writeString("DefaultUserName", m_meta->defaultUserName()); - writeDateTime("DefaultUserNameChanged", m_meta->defaultUserNameChanged()); - writeNumber("MaintenanceHistoryDays", m_meta->maintenanceHistoryDays()); - writeColor("Color", m_meta->color()); - writeDateTime("MasterKeyChanged", m_meta->masterKeyChanged()); - writeNumber("MasterKeyChangeRec", m_meta->masterKeyChangeRec()); - writeNumber("MasterKeyChangeForce", m_meta->masterKeyChangeForce()); - writeMemoryProtection(); - writeCustomIcons(); - writeBool("RecycleBinEnabled", m_meta->recycleBinEnabled()); - writeUuid("RecycleBinUUID", m_meta->recycleBin()); - writeDateTime("RecycleBinChanged", m_meta->recycleBinChanged()); - writeUuid("EntryTemplatesGroup", m_meta->entryTemplatesGroup()); - writeDateTime("EntryTemplatesGroupChanged", m_meta->entryTemplatesGroupChanged()); - writeUuid("LastSelectedGroup", m_meta->lastSelectedGroup()); - writeUuid("LastTopVisibleGroup", m_meta->lastTopVisibleGroup()); - writeNumber("HistoryMaxItems", m_meta->historyMaxItems()); - writeNumber("HistoryMaxSize", m_meta->historyMaxSize()); - writeBinaries(); - writeCustomData(); - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeMemoryProtection() -{ - m_xml.writeStartElement("MemoryProtection"); - - writeBool("ProtectTitle", m_meta->protectTitle()); - writeBool("ProtectUserName", m_meta->protectUsername()); - writeBool("ProtectPassword", m_meta->protectPassword()); - writeBool("ProtectURL", m_meta->protectUrl()); - writeBool("ProtectNotes", m_meta->protectNotes()); - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeCustomIcons() -{ - m_xml.writeStartElement("CustomIcons"); - - const QList customIconsOrder = m_meta->customIconsOrder(); - for (const Uuid& uuid : customIconsOrder) { - writeIcon(uuid, m_meta->customIcon(uuid)); - } - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) -{ - m_xml.writeStartElement("Icon"); - - writeUuid("UUID", uuid); - - QByteArray ba; - QBuffer buffer(&ba); - buffer.open(QIODevice::WriteOnly); - // TODO: check !icon.save() - icon.save(&buffer, "PNG"); - buffer.close(); - writeBinary("Data", ba); - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeBinaries() -{ - m_xml.writeStartElement("Binaries"); - - QHash::const_iterator i; - for (i = m_idMap.constBegin(); i != m_idMap.constEnd(); ++i) { - m_xml.writeStartElement("Binary"); - - m_xml.writeAttribute("ID", QString::number(i.value())); - - QByteArray data; - if (m_db->compressionAlgo() == Database::CompressionGZip) { - m_xml.writeAttribute("Compressed", "True"); - - QBuffer buffer; - buffer.open(QIODevice::ReadWrite); - - QtIOCompressor compressor(&buffer); - compressor.setStreamFormat(QtIOCompressor::GzipFormat); - compressor.open(QIODevice::WriteOnly); - - qint64 bytesWritten = compressor.write(i.key()); - Q_ASSERT(bytesWritten == i.key().size()); - Q_UNUSED(bytesWritten); - compressor.close(); - - buffer.seek(0); - data = buffer.readAll(); - } else { - data = i.key(); - } - - if (!data.isEmpty()) { - m_xml.writeCharacters(QString::fromLatin1(data.toBase64())); - } - m_xml.writeEndElement(); - } - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeCustomData() -{ - m_xml.writeStartElement("CustomData"); - - QHash customFields = m_meta->customFields(); - const QList keyList = customFields.keys(); - for (const QString& key : keyList) { - writeCustomDataItem(key, customFields.value(key)); - } - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeCustomDataItem(const QString& key, const QString& value) -{ - m_xml.writeStartElement("Item"); - - writeString("Key", key); - writeString("Value", value); - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeRoot() -{ - Q_ASSERT(m_db->rootGroup()); - - m_xml.writeStartElement("Root"); - - writeGroup(m_db->rootGroup()); - writeDeletedObjects(); - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeGroup(const Group* group) -{ - Q_ASSERT(!group->uuid().isNull()); - - m_xml.writeStartElement("Group"); - - writeUuid("UUID", group->uuid()); - writeString("Name", group->name()); - writeString("Notes", group->notes()); - writeNumber("IconID", group->iconNumber()); - - if (!group->iconUuid().isNull()) { - writeUuid("CustomIconUUID", group->iconUuid()); - } - writeTimes(group->timeInfo()); - writeBool("IsExpanded", group->isExpanded()); - writeString("DefaultAutoTypeSequence", group->defaultAutoTypeSequence()); - - writeTriState("EnableAutoType", group->autoTypeEnabled()); - - writeTriState("EnableSearching", group->searchingEnabled()); - - writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry()); - - const QList entryList = group->entries(); - for (const Entry* entry : entryList) { - writeEntry(entry); - } - - const QList children = group->children(); - for (const Group* child : children) { - writeGroup(child); - } - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeTimes(const TimeInfo& ti) -{ - m_xml.writeStartElement("Times"); - - writeDateTime("LastModificationTime", ti.lastModificationTime()); - writeDateTime("CreationTime", ti.creationTime()); - writeDateTime("LastAccessTime", ti.lastAccessTime()); - writeDateTime("ExpiryTime", ti.expiryTime()); - writeBool("Expires", ti.expires()); - writeNumber("UsageCount", ti.usageCount()); - writeDateTime("LocationChanged", ti.locationChanged()); - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeDeletedObjects() -{ - m_xml.writeStartElement("DeletedObjects"); - - const QList delObjList = m_db->deletedObjects(); - for (const DeletedObject& delObj : delObjList) { - writeDeletedObject(delObj); - } - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeDeletedObject(const DeletedObject& delObj) -{ - m_xml.writeStartElement("DeletedObject"); - - writeUuid("UUID", delObj.uuid); - writeDateTime("DeletionTime", delObj.deletionTime); - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeEntry(const Entry* entry) -{ - Q_ASSERT(!entry->uuid().isNull()); - - m_xml.writeStartElement("Entry"); - - writeUuid("UUID", entry->uuid()); - writeNumber("IconID", entry->iconNumber()); - if (!entry->iconUuid().isNull()) { - writeUuid("CustomIconUUID", entry->iconUuid()); - } - writeColor("ForegroundColor", entry->foregroundColor()); - writeColor("BackgroundColor", entry->backgroundColor()); - writeString("OverrideURL", entry->overrideUrl()); - writeString("Tags", entry->tags()); - writeTimes(entry->timeInfo()); - - const QList attributesKeyList = entry->attributes()->keys(); - for (const QString& key : attributesKeyList) { - m_xml.writeStartElement("String"); - - bool protect = (((key == "Title") && m_meta->protectTitle()) || - ((key == "UserName") && m_meta->protectUsername()) || - ((key == "Password") && m_meta->protectPassword()) || - ((key == "URL") && m_meta->protectUrl()) || - ((key == "Notes") && m_meta->protectNotes()) || - entry->attributes()->isProtected(key)); - - writeString("Key", key); - - m_xml.writeStartElement("Value"); - QString value; - - if (protect) { - if (m_randomStream) { - m_xml.writeAttribute("Protected", "True"); - bool ok; - QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok); - if (!ok) { - raiseError(m_randomStream->errorString()); - } - value = QString::fromLatin1(rawData.toBase64()); - } else { - m_xml.writeAttribute("ProtectInMemory", "True"); - value = entry->attributes()->value(key); - } - } else { - value = entry->attributes()->value(key); - } - - if (!value.isEmpty()) { - m_xml.writeCharacters(stripInvalidXml10Chars(value)); - } - m_xml.writeEndElement(); - - m_xml.writeEndElement(); - } - - const QList attachmentsKeyList = entry->attachments()->keys(); - for (const QString& key : attachmentsKeyList) { - m_xml.writeStartElement("Binary"); - - writeString("Key", key); - - m_xml.writeStartElement("Value"); - m_xml.writeAttribute("Ref", QString::number(m_idMap[entry->attachments()->value(key)])); - m_xml.writeEndElement(); - - m_xml.writeEndElement(); - } - - writeAutoType(entry); - // write history only for entries that are not history items - if (entry->parent()) { - writeEntryHistory(entry); - } - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeAutoType(const Entry* entry) -{ - m_xml.writeStartElement("AutoType"); - - writeBool("Enabled", entry->autoTypeEnabled()); - writeNumber("DataTransferObfuscation", entry->autoTypeObfuscation()); - writeString("DefaultSequence", entry->defaultAutoTypeSequence()); - - const QList autoTypeAssociations = entry->autoTypeAssociations()->getAll(); - for (const AutoTypeAssociations::Association& assoc : autoTypeAssociations) { - writeAutoTypeAssoc(assoc); - } - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc) -{ - m_xml.writeStartElement("Association"); - - writeString("Window", assoc.window); - writeString("KeystrokeSequence", assoc.sequence); - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeEntryHistory(const Entry* entry) -{ - m_xml.writeStartElement("History"); - - const QList& historyItems = entry->historyItems(); - for (const Entry* item : historyItems) { - writeEntry(item); - } - - m_xml.writeEndElement(); -} - -void Kdbx3XmlWriter::writeString(const QString& qualifiedName, const QString& string) -{ - if (string.isEmpty()) { - m_xml.writeEmptyElement(qualifiedName); - } else { - m_xml.writeTextElement(qualifiedName, stripInvalidXml10Chars(string)); - } -} - -void Kdbx3XmlWriter::writeNumber(const QString& qualifiedName, int number) -{ - writeString(qualifiedName, QString::number(number)); -} - -void Kdbx3XmlWriter::writeBool(const QString& qualifiedName, bool b) -{ - writeString(qualifiedName, b ? "True" : "False"); -} - -void Kdbx3XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime) -{ - Q_ASSERT(dateTime.isValid()); - Q_ASSERT(dateTime.timeSpec() == Qt::UTC); - - QString dateTimeStr = dateTime.toString(Qt::ISODate); - - // Qt < 4.8 doesn't append a 'Z' at the end - if (!dateTimeStr.isEmpty() && dateTimeStr[dateTimeStr.size() - 1] != 'Z') { - dateTimeStr.append('Z'); - } - - writeString(qualifiedName, dateTimeStr); -} - -void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid) -{ - writeString(qualifiedName, uuid.toBase64()); -} - -void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) -{ - writeUuid(qualifiedName, group ? group->uuid() : Uuid()); -} - -void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) -{ - writeUuid(qualifiedName, entry ? entry->uuid() : Uuid()); -} - -void Kdbx3XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba) -{ - writeString(qualifiedName, QString::fromLatin1(ba.toBase64())); -} - -void Kdbx3XmlWriter::writeColor(const QString& qualifiedName, const QColor& color) -{ - QString colorStr; - - if (color.isValid()) { - colorStr = QString("#%1%2%3").arg(colorPartToString(color.red()), - colorPartToString(color.green()), - colorPartToString(color.blue())); - } - - writeString(qualifiedName, colorStr); -} - -void Kdbx3XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState) -{ - QString value; - - if (triState == Group::Inherit) { - value = "null"; - } else if (triState == Group::Enable) { - value = "true"; - } else { - value = "false"; - } - - writeString(qualifiedName, value); -} - -QString Kdbx3XmlWriter::colorPartToString(int value) -{ - QString str = QString::number(value, 16).toUpper(); - if (str.length() == 1) { - str.prepend("0"); - } - - return str; -} - -QString Kdbx3XmlWriter::stripInvalidXml10Chars(QString str) -{ - for (int i = str.size() - 1; i >= 0; i--) { - const QChar ch = str.at(i); - const ushort uc = ch.unicode(); - - if (ch.isLowSurrogate() && i != 0 && str.at(i - 1).isHighSurrogate()) { - // keep valid surrogate pair - i--; - } else if ((uc < 0x20 && uc != 0x09 && uc != 0x0A && uc != 0x0D) // control characters - || (uc >= 0x7F && uc <= 0x84) // control characters, valid but discouraged by XML - || (uc >= 0x86 && uc <= 0x9F) // control characters, valid but discouraged by XML - || (uc > 0xFFFD) // noncharacter - || ch.isLowSurrogate() // single low surrogate - || ch.isHighSurrogate()) // single high surrogate - { - qWarning("Stripping invalid XML 1.0 codepoint %x", uc); - str.remove(i, 1); - } - } - - return str; -} - -void Kdbx3XmlWriter::raiseError(const QString& errorMessage) -{ - m_error = true; - m_errorStr = errorMessage; -} diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index 5bac5d005..05ef71160 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -18,77 +18,24 @@ #include "Kdbx4Reader.h" #include -#include -#include "crypto/kdf/AesKdf.h" -#include "streams/HmacBlockStream.h" +#include "core/Group.h" #include "core/Database.h" #include "core/Endian.h" #include "crypto/CryptoHash.h" -#include "format/KeePass1.h" -#include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" -#include "format/Kdbx4XmlReader.h" -#include "streams/HashedBlockStream.h" +#include "format/KdbxXmlReader.h" +#include "streams/HmacBlockStream.h" #include "streams/QtIOCompressor" -#include "streams/StoreDataStream.h" #include "streams/SymmetricCipherStream.h" -Kdbx4Reader::Kdbx4Reader() - : m_device(nullptr) - , m_db(nullptr) +Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData, + const CompositeKey& key, bool keepDatabase) { -} + Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4); -Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) -{ - QScopedPointer db(new Database()); - m_db = db.data(); - m_device = device; - m_error = false; - m_errorStr.clear(); - m_xmlData.clear(); - m_masterSeed.clear(); - m_encryptionIV.clear(); - m_protectedStreamKey.clear(); m_binaryPool.clear(); - StoreDataStream headerStream(m_device); - headerStream.open(QIODevice::ReadOnly); - QIODevice* headerIo = &headerStream; - - bool ok; - - quint32 signature1 = Endian::readSizedInt(headerIo, KeePass2::BYTEORDER, &ok); - if (!ok || signature1 != KeePass2::SIGNATURE_1) { - raiseError(tr("Not a KeePass database.")); - return nullptr; - } - - quint32 signature2 = Endian::readSizedInt(headerIo, KeePass2::BYTEORDER, &ok); - if (ok && signature2 == KeePass1::SIGNATURE_2) { - raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" - "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; - } else if (!ok || signature2 != KeePass2::SIGNATURE_2) { - raiseError(tr("Not a KeePass database.")); - return nullptr; - } - - quint32 version = Endian::readSizedInt(headerIo, KeePass2::BYTEORDER, &ok) - & KeePass2::FILE_VERSION_CRITICAL_MASK; - if (!ok || version != (KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK)) { - raiseError(tr("Unsupported KeePass KDBX 4 database version.")); - return nullptr; - } - - while (readHeaderField(headerIo) && !hasError()) { - } - - headerStream.close(); - if (hasError()) { return nullptr; } @@ -97,7 +44,7 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() || m_db->cipher().isNull()) { - raiseError("missing database headers"); + raiseError(tr("missing database headers")); return nullptr; } @@ -106,7 +53,7 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, return nullptr; } - if (m_db->challengeMasterSeed(m_masterSeed) == false) { + if (!m_db->challengeMasterSeed(m_masterSeed)) { raiseError(tr("Unable to issue challenge-response.")); return nullptr; } @@ -117,24 +64,24 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, hash.addData(m_db->transformedMasterKey()); QByteArray finalKey = hash.result(); - QByteArray headerSha256 = m_device->read(32); - QByteArray headerHmac = m_device->read(32); + QByteArray headerSha256 = device->read(32); + QByteArray headerHmac = device->read(32); if (headerSha256.size() != 32 || headerHmac.size() != 32) { - raiseError("Invalid header checksum size"); + raiseError(tr("Invalid header checksum size")); return nullptr; } - if (headerSha256 != CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256)) { - raiseError("Header SHA256 mismatch"); + if (headerSha256 != CryptoHash::hash(headerData, CryptoHash::Sha256)) { + raiseError(tr("Header SHA256 mismatch")); return nullptr; } QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, m_db->transformedMasterKey()); - if (headerHmac != CryptoHash::hmac(headerStream.storedData(), + 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; } - HmacBlockStream hmacStream(m_device, hmacKey); + HmacBlockStream hmacStream(device, hmacKey); if (!hmacStream.open(QIODevice::ReadOnly)) { raiseError(hmacStream.errorString()); return nullptr; @@ -142,7 +89,7 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher()); if (cipher == SymmetricCipher::InvalidAlgorithm) { - raiseError("Unknown cipher"); + raiseError(tr("Unknown cipher")); return nullptr; } SymmetricCipherStream cipherStream(&hmacStream, cipher, @@ -156,7 +103,7 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, return nullptr; } - QIODevice* xmlDevice; + QIODevice* xmlDevice = nullptr; QScopedPointer ioCompressor; if (m_db->compressionAlgo() == Database::CompressionNone) { @@ -171,7 +118,6 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, xmlDevice = ioCompressor.data(); } - while (readInnerHeaderField(xmlDevice) && !hasError()) { } @@ -185,50 +131,51 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, return nullptr; } - QScopedPointer buffer; - - if (m_saveXml) { + QBuffer buffer; + if (saveXml()) { m_xmlData = xmlDevice->readAll(); - buffer.reset(new QBuffer(&m_xmlData)); - buffer->open(QIODevice::ReadOnly); - xmlDevice = buffer.data(); + buffer.setBuffer(&m_xmlData); + buffer.open(QIODevice::ReadOnly); + xmlDevice = &buffer; } - Kdbx4XmlReader xmlReader(m_binaryPool); - xmlReader.readDatabase(xmlDevice, m_db, &randomStream); + Q_ASSERT(xmlDevice); + + KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, m_binaryPool); + xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream); if (xmlReader.hasError()) { raiseError(xmlReader.errorString()); if (keepDatabase) { - return db.take(); + return m_db.take(); } return nullptr; } - return db.take(); + return m_db.take(); } -bool Kdbx4Reader::readHeaderField(QIODevice* device) +bool Kdbx4Reader::readHeaderField(StoreDataStream& device) { - QByteArray fieldIDArray = device->read(1); + QByteArray fieldIDArray = device.read(1); if (fieldIDArray.size() != 1) { - raiseError("Invalid header id size"); + raiseError(tr("Invalid header id size")); return false; } char fieldID = fieldIDArray.at(0); bool ok; - auto fieldLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + auto fieldLen = Endian::readSizedInt(&device, KeePass2::BYTEORDER, &ok); if (!ok) { - raiseError("Invalid header field length"); + raiseError(tr("Invalid header field length")); return false; } QByteArray fieldData; if (fieldLen != 0) { - fieldData = device->read(fieldLen); + fieldData = device.read(fieldLen); if (static_cast(fieldData.size()) != fieldLen) { - raiseError("Invalid header data length"); + raiseError(tr("Invalid header data length")); return false; } } @@ -256,13 +203,13 @@ bool Kdbx4Reader::readHeaderField(QIODevice* device) case KeePass2::HeaderFieldID::KdfParameters: { QBuffer bufIoDevice(&fieldData); if (!bufIoDevice.open(QIODevice::ReadOnly)) { - raiseError("Failed to open buffer for KDF parameters in header"); + raiseError(tr("Failed to open buffer for KDF parameters in header")); return false; } QVariantMap kdfParams = readVariantMap(&bufIoDevice); QSharedPointer kdf = KeePass2::kdfFromParameters(kdfParams); if (!kdf) { - raiseError("Invalid KDF parameters"); + raiseError(tr("Invalid KDF parameters")); return false; } m_db->setKdf(kdf); @@ -278,7 +225,7 @@ bool Kdbx4Reader::readHeaderField(QIODevice* device) case KeePass2::HeaderFieldID::TransformSeed: case KeePass2::HeaderFieldID::StreamStartBytes: case KeePass2::HeaderFieldID::InnerRandomStreamID: - raiseError("Legacy header fields found in KDBX4 file."); + raiseError(tr("Legacy header fields found in KDBX4 file.")); return false; default: @@ -289,19 +236,25 @@ bool Kdbx4Reader::readHeaderField(QIODevice* device) return true; } +/** + * Helper method for reading KDBX4 inner header fields. + * + * @param device input device + * @return true if there are more inner header fields + */ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device) { QByteArray fieldIDArray = device->read(1); if (fieldIDArray.size() != 1) { - raiseError("Invalid inner header id size"); + raiseError(tr("Invalid inner header id size")); return false; } - KeePass2::InnerHeaderFieldID fieldID = static_cast(fieldIDArray.at(0)); + auto fieldID = static_cast(fieldIDArray.at(0)); bool ok; - quint32 fieldLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + auto fieldLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); if (!ok) { - raiseError("Invalid inner header field length"); + raiseError(tr("Invalid inner header field length")); return false; } @@ -309,7 +262,7 @@ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device) if (fieldLen != 0) { fieldData = device->read(fieldLen); if (static_cast(fieldData.size()) != fieldLen) { - raiseError("Invalid header data length"); + raiseError(tr("Invalid header data length")); return false; } } @@ -328,20 +281,22 @@ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device) case KeePass2::InnerHeaderFieldID::Binary: if (fieldLen < 1) { - raiseError("Invalid inner header binary size"); + raiseError(tr("Invalid inner header binary size")); return false; } m_binaryPool.insert(QString::number(m_binaryPool.size()), fieldData.mid(1)); break; - - default: - qWarning("Unknown inner header field read: id=%hhu", static_cast(fieldID)); - break; } return true; } +/** + * Helper method for reading KDF parameters into variant map. + * + * @param device input device + * @return filled variant map + */ QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device) { bool ok; @@ -350,41 +305,41 @@ QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device) quint16 maxVersion = KeePass2::VARIANTMAP_VERSION & KeePass2::VARIANTMAP_CRITICAL_MASK; if (!ok || (version > maxVersion)) { raiseError(tr("Unsupported KeePass variant map version.")); - return QVariantMap(); + return {}; } QVariantMap vm; QByteArray fieldTypeArray; - KeePass2::VariantMapFieldType fieldType; + KeePass2::VariantMapFieldType fieldType = KeePass2::VariantMapFieldType::End; while (((fieldTypeArray = device->read(1)).size() == 1) && ((fieldType = static_cast(fieldTypeArray.at(0))) != KeePass2::VariantMapFieldType::End)) { - quint32 nameLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + auto nameLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); if (!ok) { - raiseError("Invalid variant map entry name length"); - return QVariantMap(); + raiseError(tr("Invalid variant map entry name length")); + return {}; } QByteArray nameBytes; if (nameLen != 0) { nameBytes = device->read(nameLen); if (static_cast(nameBytes.size()) != nameLen) { - raiseError("Invalid variant map entry name data"); - return QVariantMap(); + raiseError(tr("Invalid variant map entry name data")); + return {}; } } QString name = QString::fromUtf8(nameBytes); - quint32 valueLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + auto valueLen = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); if (!ok) { - raiseError("Invalid variant map entry value length"); - return QVariantMap(); + raiseError(tr("Invalid variant map entry value length")); + return {}; } QByteArray valueBytes; if (valueLen != 0) { valueBytes = device->read(valueLen); if (static_cast(valueBytes.size()) != valueLen) { - raiseError("Invalid variant map entry value data"); - return QVariantMap(); + raiseError(tr("Invalid variant map entry value data")); + return {}; } } @@ -393,127 +348,70 @@ QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device) if (valueLen == 1) { vm.insert(name, QVariant(valueBytes.at(0) != 0)); } else { - raiseError("Invalid variant map Bool entry value length"); - return QVariantMap(); + raiseError(tr("Invalid variant map Bool entry value length")); + return {}; } break; + case KeePass2::VariantMapFieldType::Int32: if (valueLen == 4) { vm.insert(name, QVariant(Endian::bytesToSizedInt(valueBytes, KeePass2::BYTEORDER))); } else { - raiseError("Invalid variant map Int32 entry value length"); - return QVariantMap(); + raiseError(tr("Invalid variant map Int32 entry value length")); + return {}; } break; + case KeePass2::VariantMapFieldType::UInt32: if (valueLen == 4) { vm.insert(name, QVariant(Endian::bytesToSizedInt(valueBytes, KeePass2::BYTEORDER))); } else { - raiseError("Invalid variant map UInt32 entry value length"); - return QVariantMap(); + raiseError(tr("Invalid variant map UInt32 entry value length")); + return {}; } break; + case KeePass2::VariantMapFieldType::Int64: if (valueLen == 8) { vm.insert(name, QVariant(Endian::bytesToSizedInt(valueBytes, KeePass2::BYTEORDER))); } else { - raiseError("Invalid variant map Int64 entry value length"); - return QVariantMap(); + raiseError(tr("Invalid variant map Int64 entry value length")); + return {}; } break; + case KeePass2::VariantMapFieldType::UInt64: if (valueLen == 8) { vm.insert(name, QVariant(Endian::bytesToSizedInt(valueBytes, KeePass2::BYTEORDER))); } else { - raiseError("Invalid variant map UInt64 entry value length"); - return QVariantMap(); + raiseError(tr("Invalid variant map UInt64 entry value length")); + return {}; } break; + case KeePass2::VariantMapFieldType::String: vm.insert(name, QVariant(QString::fromUtf8(valueBytes))); break; + case KeePass2::VariantMapFieldType::ByteArray: vm.insert(name, QVariant(valueBytes)); break; + default: - raiseError("Invalid variant map entry type"); - return QVariantMap(); + raiseError(tr("Invalid variant map entry type")); + return {}; } } if (fieldTypeArray.size() != 1) { - raiseError("Invalid variant map field type size"); - return QVariantMap(); + raiseError(tr("Invalid variant map field type size")); + return {}; } return vm; } -void Kdbx4Reader::setCipher(const QByteArray& data) -{ - if (data.size() != Uuid::Length) { - raiseError("Invalid cipher uuid length"); - return; - } - Uuid uuid(data); - - if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { - raiseError("Unsupported cipher"); - return; - } - m_db->setCipher(uuid); -} - -void Kdbx4Reader::setCompressionFlags(const QByteArray& data) -{ - if (data.size() != 4) { - raiseError("Invalid compression flags length"); - return; - } - auto id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); - - if (id > Database::CompressionAlgorithmMax) { - raiseError("Unsupported compression algorithm"); - return; - } - m_db->setCompressionAlgo(static_cast(id)); -} - -void Kdbx4Reader::setMasterSeed(const QByteArray& data) -{ - if (data.size() != 32) { - raiseError("Invalid master seed size"); - return; - } - m_masterSeed = data; -} - -void Kdbx4Reader::setEncryptionIV(const QByteArray& data) -{ - m_encryptionIV = data; -} - -void Kdbx4Reader::setProtectedStreamKey(const QByteArray& data) -{ - m_protectedStreamKey = data; -} - -void Kdbx4Reader::setInnerRandomStreamID(const QByteArray& data) -{ - if (data.size() != 4) { - raiseError("Invalid random stream id size"); - return; - } - auto id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); - KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id); - if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo || irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) { - raiseError("Invalid inner random stream cipher"); - return; - } - m_irsAlgo = irsAlgo; -} - -QHash Kdbx4Reader::binaryPool() +QHash Kdbx4Reader::binaryPool() const { return m_binaryPool; } diff --git a/src/format/Kdbx4Reader.h b/src/format/Kdbx4Reader.h index 9d8c531ef..175af9419 100644 --- a/src/format/Kdbx4Reader.h +++ b/src/format/Kdbx4Reader.h @@ -18,48 +18,27 @@ #ifndef KEEPASSX_KDBX4READER_H #define KEEPASSX_KDBX4READER_H -#include -#include -#include -#include +#include "format/KdbxReader.h" -#include "format/KeePass2.h" -#include "format/KeePass2Reader.h" -#include "crypto/SymmetricCipher.h" -#include "keys/CompositeKey.h" +#include -class Database; -class QIODevice; - -class Kdbx4Reader : public BaseKeePass2Reader +/** + * KDBX4 reader implementation. + */ +class Kdbx4Reader : public KdbxReader { - Q_DECLARE_TR_FUNCTIONS(Kdbx4Reader) - public: - Kdbx4Reader(); + Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData, + const CompositeKey& key, bool keepDatabase) override; + QHash binaryPool() const; - using BaseKeePass2Reader::readDatabase; - virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override; - - QHash binaryPool(); +protected: + bool readHeaderField(StoreDataStream& headerStream) override; private: - bool readHeaderField(QIODevice* device); bool readInnerHeaderField(QIODevice* device); QVariantMap readVariantMap(QIODevice* device); - void setCipher(const QByteArray& data); - void setCompressionFlags(const QByteArray& data); - void setMasterSeed(const QByteArray& data); - void setEncryptionIV(const QByteArray& data); - void setProtectedStreamKey(const QByteArray& data); - void setInnerRandomStreamID(const QByteArray& data); - - QIODevice* m_device; - - Database* m_db; - QByteArray m_masterSeed; - QByteArray m_encryptionIV; QHash m_binaryPool; }; diff --git a/src/format/Kdbx4Writer.cpp b/src/format/Kdbx4Writer.cpp index fbd590563..e151ab965 100644 --- a/src/format/Kdbx4Writer.cpp +++ b/src/format/Kdbx4Writer.cpp @@ -19,25 +19,16 @@ #include #include -#include -#include -#include #include "streams/HmacBlockStream.h" #include "core/Database.h" -#include "core/Endian.h" #include "crypto/CryptoHash.h" #include "crypto/Random.h" #include "format/KeePass2RandomStream.h" -#include "format/Kdbx4XmlWriter.h" +#include "format/KdbxXmlWriter.h" #include "streams/QtIOCompressor" #include "streams/SymmetricCipherStream.h" -Kdbx4Writer::Kdbx4Writer() - : m_device(nullptr) -{ -} - bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) { m_error = false; @@ -45,12 +36,12 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher()); if (algo == SymmetricCipher::InvalidAlgorithm) { - raiseError("Invalid symmetric cipher algorithm."); + raiseError(tr("Invalid symmetric cipher algorithm.")); return false; } int ivSize = SymmetricCipher::algorithmIvSize(algo); if (ivSize < 0) { - raiseError("Invalid symmetric cipher IV size."); + raiseError(tr("Invalid symmetric cipher IV size.")); return false; } @@ -70,6 +61,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) return false; } + // generate transformed master key CryptoHash hash(CryptoHash::Sha256); hash.addData(masterSeed); hash.addData(db->challengeResponseKey()); @@ -77,48 +69,49 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) hash.addData(db->transformedMasterKey()); QByteArray finalKey = hash.result(); + // write header QByteArray headerData; { QBuffer header; header.open(QIODevice::WriteOnly); - m_device = &header; - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::FILE_VERSION_4, KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray())); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CompressionFlags, - Endian::sizedIntToBytes(static_cast(db->compressionAlgo()), - KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::MasterSeed, masterSeed)); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EncryptionIV, encryptionIV)); - // Convert current Kdf to basic parameters + writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_4); + + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray())); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::CompressionFlags, + Endian::sizedIntToBytes(static_cast(db->compressionAlgo()), + KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed)); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV)); + + // convert current Kdf to basic parameters QVariantMap kdfParams = KeePass2::kdfToParameters(db->kdf()); - QByteArray kdfParamBytes; if (!serializeVariantMap(kdfParams, kdfParamBytes)) { - raiseError("Failed to serialise KDF parameters variant map"); + raiseError(tr("Failed to serialize KDF parameters variant map")); return false; } QByteArray publicCustomData = db->publicCustomData(); - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::KdfParameters, kdfParamBytes)); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::KdfParameters, kdfParamBytes)); if (!publicCustomData.isEmpty()) { - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::PublicCustomData, publicCustomData)); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::PublicCustomData, publicCustomData)); } - CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EndOfHeader, endOfHeader)); + CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::EndOfHeader, endOfHeader)); header.close(); - m_device = device; headerData = header.data(); } - CHECK_RETURN_FALSE(writeData(headerData)); + CHECK_RETURN_FALSE(writeData(device, headerData)); + + // hash header QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256); + // write HMAC-authenticated cipher stream QByteArray hmacKey = KeePass2::hmacKey(masterSeed, db->transformedMasterKey()); QByteArray headerHmac = CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256); - CHECK_RETURN_FALSE(writeData(headerHash)); - CHECK_RETURN_FALSE(writeData(headerHmac)); + CHECK_RETURN_FALSE(writeData(device, headerHash)); + CHECK_RETURN_FALSE(writeData(device, headerHmac)); QScopedPointer hmacBlockStream; QScopedPointer cipherStream; @@ -130,8 +123,8 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) } cipherStream.reset(new SymmetricCipherStream(hmacBlockStream.data(), algo, - SymmetricCipher::algorithmMode(algo), - SymmetricCipher::Encrypt)); + SymmetricCipher::algorithmMode(algo), + SymmetricCipher::Encrypt)); if (!cipherStream->init(finalKey, encryptionIV)) { raiseError(cipherStream->errorString()); @@ -142,9 +135,11 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) return false; } + QIODevice* outputDevice = nullptr; QScopedPointer ioCompressor; + if (db->compressionAlgo() == Database::CompressionNone) { - m_device = cipherStream.data(); + outputDevice = cipherStream.data(); } else { ioCompressor.reset(new QtIOCompressor(cipherStream.data())); ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); @@ -152,30 +147,18 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) raiseError(ioCompressor->errorString()); return false; } - m_device = ioCompressor.data(); + outputDevice = ioCompressor.data(); } - QHash idMap; + Q_ASSERT(outputDevice); - CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::InnerRandomStreamID, + CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::InnerRandomStreamID, Endian::sizedIntToBytes(static_cast(KeePass2::ProtectedStreamAlgo::ChaCha20), KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::InnerRandomStreamKey, + CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::InnerRandomStreamKey, protectedStreamKey)); - const QList allEntries = db->rootGroup()->entriesRecursive(true); - int nextId = 0; - for (Entry* entry : allEntries) { - const QList attachmentKeys = entry->attachments()->keys(); - for (const QString& key : attachmentKeys) { - QByteArray data = entry->attachments()->value(key); - if (!idMap.contains(data)) { - CHECK_RETURN_FALSE(writeBinary(data)); - idMap.insert(data, nextId++); - } - } - } - CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::End, QByteArray())); + CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::End, QByteArray())); KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::ChaCha20); if (!randomStream.init(protectedStreamKey)) { @@ -183,8 +166,8 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) return false; } - Kdbx4XmlWriter xmlWriter(KeePass2::FILE_VERSION_4, idMap); - xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash); + KdbxXmlWriter xmlWriter(KeePass2::FILE_VERSION_4); + xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash); // Explicitly close/reset streams so they are flushed and we can detect // errors. QIODevice::close() resets errorString() etc. @@ -208,61 +191,64 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) return true; } -bool Kdbx4Writer::writeData(const QByteArray& data) -{ - if (m_device->write(data) != data.size()) { - raiseError(m_device->errorString()); - return false; - } - return true; -} - -bool Kdbx4Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data) +/** + * Write KDBX4 inner header field. + * + * @param device output device + * @param fieldId field identifier + * @param data header payload + * @return true on success + */ +bool Kdbx4Writer::writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data) { QByteArray fieldIdArr; fieldIdArr[0] = static_cast(fieldId); - CHECK_RETURN_FALSE(writeData(fieldIdArr)); - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size()), KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(data)); + CHECK_RETURN_FALSE(writeData(device, fieldIdArr)); + CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes(static_cast(data.size()), KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(device, data)); return true; } -bool Kdbx4Writer::writeInnerHeaderField(KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data) -{ - QByteArray fieldIdArr; - fieldIdArr[0] = static_cast(fieldId); - CHECK_RETURN_FALSE(writeData(fieldIdArr)); - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size()), KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(data)); - - return true; -} - -bool Kdbx4Writer::writeBinary(const QByteArray& data) +/** + * Write binary header field.. + * + * @param device output device + * @param fieldId field identifier + * @param data header payload + * @return true on success + */ +bool Kdbx4Writer::writeBinary(QIODevice* device, const QByteArray& data) { QByteArray fieldIdArr; fieldIdArr[0] = static_cast(KeePass2::InnerHeaderFieldID::Binary); - CHECK_RETURN_FALSE(writeData(fieldIdArr)); - CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast(data.size() + 1), KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(QByteArray(1, '\1'))); - CHECK_RETURN_FALSE(writeData(data)); + CHECK_RETURN_FALSE(writeData(device, fieldIdArr)); + CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes(static_cast(data.size() + 1), KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(device, QByteArray(1, '\1'))); + CHECK_RETURN_FALSE(writeData(device, data)); return true; } -bool Kdbx4Writer::serializeVariantMap(const QVariantMap& p, QByteArray& o) +/** + * Serialize KDF parameter variant map to byte array. + * + * @param map input variant map + * @param outputBytes output byte array + * @return true on success + */ +bool Kdbx4Writer::serializeVariantMap(const QVariantMap& map, QByteArray& outputBytes) { - QBuffer buf(&o); + QBuffer buf(&outputBytes); buf.open(QIODevice::WriteOnly); CHECK_RETURN_FALSE(buf.write(Endian::sizedIntToBytes(KeePass2::VARIANTMAP_VERSION, KeePass2::BYTEORDER)) == 2); bool ok; - QList keys = p.keys(); + QList keys = map.keys(); for (const auto& k : keys) { KeePass2::VariantMapFieldType fieldType; QByteArray data; - QVariant v = p.value(k); + QVariant v = map.value(k); switch (static_cast(v.type())) { case QMetaType::Type::Int: fieldType = KeePass2::VariantMapFieldType::Int32; diff --git a/src/format/Kdbx4Writer.h b/src/format/Kdbx4Writer.h index 7bb20e0f1..097a7864a 100644 --- a/src/format/Kdbx4Writer.h +++ b/src/format/Kdbx4Writer.h @@ -18,35 +18,20 @@ #ifndef KEEPASSX_KDBX4WRITER_H #define KEEPASSX_KDBX4WRITER_H -#include +#include "KdbxWriter.h" -#include "format/KeePass2.h" -#include "format/KeePass2Writer.h" -#include "keys/CompositeKey.h" - -class Database; -class QIODevice; - -class Kdbx4Writer : public BaseKeePass2Writer +/** + * KDBX4 writer implementation. + */ +class Kdbx4Writer : public KdbxWriter { - Q_DECLARE_TR_FUNCTIONS(Kdbx4Writer) - public: - Kdbx4Writer(); - - using BaseKeePass2Writer::writeDatabase; - bool writeDatabase(QIODevice* device, Database* db); + bool writeDatabase(QIODevice* device, Database* db) override; private: - bool writeData(const QByteArray& data); - bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data); - bool writeInnerHeaderField(KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data); - - QIODevice* m_device; - - bool writeBinary(const QByteArray& data); - - static bool serializeVariantMap(const QVariantMap& p, QByteArray& o); + bool writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data); + bool writeBinary(QIODevice* device, const QByteArray& data); + static bool serializeVariantMap(const QVariantMap& map, QByteArray& outputBytes); }; #endif // KEEPASSX_KDBX4WRITER_H diff --git a/src/format/Kdbx4XmlReader.h b/src/format/Kdbx4XmlReader.h deleted file mode 100644 index 229ac7425..000000000 --- a/src/format/Kdbx4XmlReader.h +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2017 KeePassXC Team - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -#ifndef KEEPASSX_KDBX4XMLREADER_H -#define KEEPASSX_KDBX4XMLREADER_H - -#include -#include -#include -#include -#include -#include - -#include "core/TimeInfo.h" -#include "core/Uuid.h" -#include "core/Group.h" - -class Database; -class Entry; -class KeePass2RandomStream; -class Metadata; - -class Kdbx4XmlReader -{ - Q_DECLARE_TR_FUNCTIONS(Kdbx4XmlReader) - -public: - Kdbx4XmlReader(); - Kdbx4XmlReader(QHash& binaryPool); - Database* readDatabase(QIODevice* device); - void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr); - Database* readDatabase(const QString& filename); - bool hasError(); - QString errorString(); - QByteArray headerHash(); - void setStrictMode(bool strictMode); - -private: - bool parseKeePassFile(); - void parseMeta(); - void parseMemoryProtection(); - void parseCustomIcons(); - void parseIcon(); - void parseBinaries(); - void parseCustomData(); - void parseCustomDataItem(); - bool parseRoot(); - Group* parseGroup(); - void parseDeletedObjects(); - void parseDeletedObject(); - Entry* parseEntry(bool history); - void parseEntryString(Entry* entry); - QPair parseEntryBinary(Entry* entry); - void parseAutoType(Entry* entry); - void parseAutoTypeAssoc(Entry* entry); - QList parseEntryHistory(); - TimeInfo parseTimes(); - - QString readString(); - bool readBool(); - QDateTime readDateTime(); - QColor readColor(); - int readNumber(); - Uuid readUuid(); - QByteArray readBinary(); - QByteArray readCompressedBinary(); - - Group* getGroup(const Uuid& uuid); - Entry* getEntry(const Uuid& uuid); - void raiseError(const QString& errorMessage); - void skipCurrentElement(); - - QXmlStreamReader m_xml; - KeePass2RandomStream* m_randomStream; - Database* m_db; - Metadata* m_meta; - QScopedPointer m_tmpParent; - QHash m_groups; - QHash m_entries; - QHash m_binaryPool; - QHash > m_binaryMap; - QByteArray m_headerHash; - bool m_error; - QString m_errorStr; - bool m_strictMode; -}; - -#endif // KEEPASSX_KDBX4XMLREADER_H diff --git a/src/format/KdbxReader.cpp b/src/format/KdbxReader.cpp new file mode 100644 index 000000000..36ff6d197 --- /dev/null +++ b/src/format/KdbxReader.cpp @@ -0,0 +1,267 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "KdbxReader.h" +#include "core/Database.h" +#include "core/Endian.h" + +/** + * Read KDBX magic header numbers from a device. + * + * @param device input device + * @param sig1 KDBX signature 1 + * @param sig2 KDBX signature 2 + * @param version KDBX version + * @return true if magic numbers were read successfully + */ +bool KdbxReader::readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version) +{ + bool ok; + sig1 = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + if (!ok) { + return false; + } + + sig2 = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + if (!ok) { + return false; + } + + version = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); + + return ok; +} + +/** + * Read KDBX stream from device. + * The device will automatically be reset to 0 before reading. + * + * @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 + */ +Database* KdbxReader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) +{ + device->seek(0); + + m_db.reset(new Database()); + m_xmlData.clear(); + m_masterSeed.clear(); + m_encryptionIV.clear(); + m_streamStartBytes.clear(); + m_protectedStreamKey.clear(); + + StoreDataStream headerStream(device); + headerStream.open(QIODevice::ReadOnly); + + // read KDBX magic numbers + quint32 sig1, sig2; + readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion); + m_kdbxSignature = qMakePair(sig1, sig2); + + // mask out minor version + m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK; + + // read header fields + while (readHeaderField(headerStream) && !hasError()) { + } + + headerStream.close(); + + if (hasError()) { + return nullptr; + } + + // read payload + return readDatabaseImpl(device, headerStream.storedData(), key, keepDatabase); +} + +bool KdbxReader::hasError() const +{ + return m_error; +} + +QString KdbxReader::errorString() const +{ + return m_errorStr; +} + +bool KdbxReader::saveXml() const +{ + return m_saveXml; +} + +void KdbxReader::setSaveXml(bool save) +{ + m_saveXml = save; +} + +QByteArray KdbxReader::xmlData() const +{ + return m_xmlData; +} + +QByteArray KdbxReader::streamKey() const +{ + return m_protectedStreamKey; +} + +KeePass2::ProtectedStreamAlgo KdbxReader::protectedStreamAlgo() const +{ + return m_irsAlgo; +} + +/** + * @param data stream cipher UUID as bytes + */ +void KdbxReader::setCipher(const QByteArray& data) +{ + if (data.size() != Uuid::Length) { + raiseError(tr("Invalid cipher uuid length")); + return; + } + + Uuid uuid(data); + + if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) { + raiseError(tr("Unsupported cipher")); + return; + } + m_db->setCipher(uuid); +} + +/** + * @param data compression flags as bytes + */ +void KdbxReader::setCompressionFlags(const QByteArray& data) +{ + if (data.size() != 4) { + raiseError(tr("Invalid compression flags length")); + return; + } + auto id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); + + if (id > Database::CompressionAlgorithmMax) { + raiseError(tr("Unsupported compression algorithm")); + return; + } + m_db->setCompressionAlgo(static_cast(id)); +} + +/** + * @param data master seed as bytes + */ +void KdbxReader::setMasterSeed(const QByteArray& data) +{ + if (data.size() != 32) { + raiseError(tr("Invalid master seed size")); + return; + } + m_masterSeed = data; +} + +/** + * @param data KDF seed as bytes + */ +void KdbxReader::setTransformSeed(const QByteArray& data) +{ + if (data.size() != 32) { + raiseError(tr("Invalid transform seed size")); + return; + } + + auto kdf = m_db->kdf(); + if (!kdf.isNull()) { + kdf->setSeed(data); + } +} + +/** + * @param data KDF transform rounds as bytes + */ +void KdbxReader::setTransformRounds(const QByteArray& data) +{ + if (data.size() != 8) { + raiseError(tr("Invalid transform rounds size")); + return; + } + + auto rounds = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); + auto kdf = m_db->kdf(); + if (!kdf.isNull()) { + kdf->setRounds(static_cast(rounds)); + } +} + +/** + * @param data cipher stream IV as bytes + */ +void KdbxReader::setEncryptionIV(const QByteArray& data) +{ + m_encryptionIV = data; +} + +/** + * @param data key for random (inner) stream as bytes + */ +void KdbxReader::setProtectedStreamKey(const QByteArray& data) +{ + m_protectedStreamKey = data; +} + +/** + * @param data start bytes for cipher stream + */ +void KdbxReader::setStreamStartBytes(const QByteArray& data) +{ + if (data.size() != 32) { + raiseError(tr("Invalid start bytes size")); + return; + } + m_streamStartBytes = data; +} + +/** + * @param data id of inner cipher stream algorithm + */ +void KdbxReader::setInnerRandomStreamID(const QByteArray& data) +{ + if (data.size() != 4) { + raiseError(tr("Invalid random stream id size")); + return; + } + auto id = Endian::bytesToSizedInt(data, KeePass2::BYTEORDER); + KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id); + if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo || + irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) { + raiseError(tr("Invalid inner random stream cipher")); + return; + } + m_irsAlgo = irsAlgo; +} + +/** + * Raise an error. Use in case of an unexpected read error. + * + * @param errorMessage error message + */ +void KdbxReader::raiseError(const QString& errorMessage) +{ + m_error = true; + m_errorStr = errorMessage; +} diff --git a/src/format/KdbxReader.h b/src/format/KdbxReader.h new file mode 100644 index 000000000..994cfb7ef --- /dev/null +++ b/src/format/KdbxReader.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_KDBXREADER_H +#define KEEPASSXC_KDBXREADER_H + +#include "KeePass2.h" +#include "keys/CompositeKey.h" +#include "streams/StoreDataStream.h" + +#include +#include + +class Database; +class QIODevice; + +/** + * Abstract KDBX reader base class. + */ +class KdbxReader +{ +Q_DECLARE_TR_FUNCTIONS(KdbxReader) + +public: + KdbxReader() = default; + virtual ~KdbxReader() = default; + + static bool readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version); + Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false); + + bool hasError() const; + QString errorString() const; + + bool saveXml() const; + void setSaveXml(bool save); + QByteArray xmlData() const; + QByteArray streamKey() const; + KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const; + +protected: + /** + * Concrete reader implementation for reading database from device. + * + * @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 + */ + virtual Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData, + const CompositeKey& key, bool keepDatabase) = 0; + + /** + * Read next header field from stream. + * + * @param headerStream input header stream + * @return true if there are more header fields + */ + virtual bool readHeaderField(StoreDataStream& headerStream) = 0; + + virtual void setCipher(const QByteArray& data); + virtual void setCompressionFlags(const QByteArray& data); + virtual void setMasterSeed(const QByteArray& data); + virtual void setTransformSeed(const QByteArray& data); + virtual void setTransformRounds(const QByteArray& data); + virtual void setEncryptionIV(const QByteArray& data); + virtual void setProtectedStreamKey(const QByteArray& data); + virtual void setStreamStartBytes(const QByteArray& data); + virtual void setInnerRandomStreamID(const QByteArray& data); + + void raiseError(const QString& errorMessage); + + QScopedPointer m_db; + + QPair m_kdbxSignature; + quint32 m_kdbxVersion = 0; + + QByteArray m_masterSeed; + QByteArray m_encryptionIV; + QByteArray m_streamStartBytes; + QByteArray m_protectedStreamKey; + KeePass2::ProtectedStreamAlgo m_irsAlgo = KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo; + + QByteArray m_xmlData; + +private: + bool m_saveXml = false; + bool m_error = false; + QString m_errorStr = ""; +}; + + +#endif //KEEPASSXC_KDBXREADER_H diff --git a/src/format/KdbxWriter.cpp b/src/format/KdbxWriter.cpp new file mode 100644 index 000000000..6016cf3a6 --- /dev/null +++ b/src/format/KdbxWriter.cpp @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "KdbxWriter.h" + +bool KdbxWriter::hasError() const +{ + return m_error; +} + +QString KdbxWriter::errorString() const +{ + return m_errorStr; +} + +/** + * Write KDBX magic header numbers to a device. + * + * @param device output device + * @param sig1 KDBX signature 1 + * @param sig2 KDBX signature 2 + * @param version KDBX version + * @return true if magic numbers were written successfully + */ +bool KdbxWriter::writeMagicNumbers(QIODevice* device, quint32 sig1, quint32 sig2, quint32 version) +{ + CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes(sig1, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes(sig2, KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes(version, KeePass2::BYTEORDER))); + + return true; +} + +/** + * Helper method for writing bytes to the device and raising an error + * in case of write failure. + * + * @param device output device + * @param data byte contents + * @return true on success + */ +bool KdbxWriter::writeData(QIODevice* device, const QByteArray& data) +{ + if (device->write(data) != data.size()) { + raiseError(device->errorString()); + return false; + } + return true; +} + +/** + * Raise an error. Use in case of an unexpected write error. + * + * @param errorMessage error message + */ +void KdbxWriter::raiseError(const QString& errorMessage) +{ + m_error = true; + m_errorStr = errorMessage; +} diff --git a/src/format/KdbxWriter.h b/src/format/KdbxWriter.h new file mode 100644 index 000000000..5aa41766e --- /dev/null +++ b/src/format/KdbxWriter.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_KDBXWRITER_H +#define KEEPASSXC_KDBXWRITER_H + +#include "KeePass2.h" +#include "core/Endian.h" + +#include + +#define CHECK_RETURN_FALSE(x) if (!(x)) return false; + +class QIODevice; +class Database; + +/** + * Abstract KDBX writer base class. + */ +class KdbxWriter +{ +Q_DECLARE_TR_FUNCTIONS(KdbxWriter) + +public: + KdbxWriter() = default; + virtual ~KdbxWriter() = default; + + bool writeMagicNumbers(QIODevice* device, quint32 sig1, quint32 sig2, quint32 version); + + /** + * Write a database to a device in KDBX format. + * + * @param device output device + * @param db source database + * @return true on success + */ + virtual bool writeDatabase(QIODevice* device, Database* db) = 0; + + bool hasError() const; + QString errorString() const; + +protected: + + /** + * Helper method for writing a KDBX header field to a device. + * + * @tparam SizedQInt field width + * @param device output device + * @param fieldId field identifier + * @param data field contents + * @return true on success + */ + template + bool writeHeaderField(QIODevice* device, KeePass2::HeaderFieldID fieldId, const QByteArray& data) + { + Q_ASSERT(static_cast(data.size()) < (1ull << (sizeof(SizedQInt) * 8))); + + QByteArray fieldIdArr; + fieldIdArr[0] = static_cast(fieldId); + CHECK_RETURN_FALSE(writeData(device, fieldIdArr)); + CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes(static_cast(data.size()), + KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(device, data)); + + return true; + } + + bool writeData(QIODevice* device, const QByteArray& data); + void raiseError(const QString& errorMessage); + + bool m_error = false; + QString m_errorStr = ""; +}; + + +#endif //KEEPASSXC_KDBXWRITER_H diff --git a/src/format/Kdbx4XmlReader.cpp b/src/format/KdbxXmlReader.cpp similarity index 82% rename from src/format/Kdbx4XmlReader.cpp rename to src/format/KdbxXmlReader.cpp index 9f6ce02c3..be692abe3 100644 --- a/src/format/Kdbx4XmlReader.cpp +++ b/src/format/KdbxXmlReader.cpp @@ -1,56 +1,86 @@ /* - * Copyright (C) 2017 KeePassXC Team + * Copyright (C) 2018 KeePassXC Team * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 2 or (at your option) - * version 3 of the License. + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . */ -#include "Kdbx4XmlReader.h" - -#include -#include - -#include "core/Endian.h" -#include "core/DatabaseIcons.h" -#include "core/Metadata.h" +#include "KdbxXmlReader.h" +#include "KeePass2RandomStream.h" +#include "core/Global.h" #include "core/Tools.h" -#include "format/KeePass2RandomStream.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "core/DatabaseIcons.h" +#include "core/Endian.h" #include "streams/QtIOCompressor" -typedef QPair StringPair; +#include +#include -Kdbx4XmlReader::Kdbx4XmlReader() - : m_randomStream(nullptr) - , m_db(nullptr) - , m_meta(nullptr) - , m_tmpParent(nullptr) - , m_error(false) - , m_strictMode(false) +/** + * @param version KDBX version + */ +KdbxXmlReader::KdbxXmlReader(quint32 version) + : m_kdbxVersion(version) { } -Kdbx4XmlReader::Kdbx4XmlReader(QHash& binaryPool) - : Kdbx4XmlReader() +/** + * @param version KDBX version + * @param binaryPool binary pool + */ +KdbxXmlReader::KdbxXmlReader(quint32 version, QHash& binaryPool) + : m_kdbxVersion(version) + , m_binaryPool(binaryPool) { - m_binaryPool = binaryPool; } -void Kdbx4XmlReader::setStrictMode(bool strictMode) +/** + * Read XML contents from a file into a new database. + * + * @param device input file + * @return pointer to the new database + */ +Database* KdbxXmlReader::readDatabase(const QString& filename) { - m_strictMode = strictMode; + QFile file(filename); + file.open(QIODevice::ReadOnly); + return readDatabase(&file); } -void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) +/** + * Read XML stream from a device into a new database. + * + * @param device input device + * @return pointer to the new database + */ +Database* KdbxXmlReader::readDatabase(QIODevice* device) +{ + auto db = new Database(); + readDatabase(device, db); + return db; +} + +/** + * Read XML contents from a device into a given database using a \link KeePass2RandomStream. + * + * @param device input device + * @param db database to read into + * @param randomStream random stream to use for decryption + */ +#include "QDebug" +void KdbxXmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) { m_error = false; m_errorStr.clear(); @@ -70,7 +100,7 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando bool rootGroupParsed = false; if (m_xml.hasError()) { - raiseError(QString("XML parsing failure: %1").arg(m_xml.error())); + raiseError(tr("XML parsing failure: %1").arg(m_xml.error())); return; } @@ -79,17 +109,17 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando } if (!rootGroupParsed) { - raiseError("No root group"); + raiseError(tr("No root group")); return; } if (!m_tmpParent->children().isEmpty()) { - qWarning("Kdbx4XmlReader::readDatabase: found %d invalid group reference(s)", + qWarning("KdbxXmlReader::readDatabase: found %d invalid group reference(s)", m_tmpParent->children().size()); } if (!m_tmpParent->entries().isEmpty()) { - qWarning("Kdbx4XmlReader::readDatabase: found %d invalid entry reference(s)", + qWarning("KdbxXmlReader::readDatabase: found %d invalid entry reference(s)", m_tmpParent->children().size()); } @@ -103,7 +133,7 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando } for (const QString& key : unusedKeys) { - qWarning("Kdbx4XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); + qWarning("KdbxXmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); } QHash >::const_iterator i; @@ -130,53 +160,46 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando } } -Database* Kdbx4XmlReader::readDatabase(QIODevice* device) +bool KdbxXmlReader::strictMode() const { - auto db = new Database(); - readDatabase(device, db); - return db; + return m_strictMode; } -Database* Kdbx4XmlReader::readDatabase(const QString& filename) +void KdbxXmlReader::setStrictMode(bool strictMode) { - QFile file(filename); - file.open(QIODevice::ReadOnly); - return readDatabase(&file); + m_strictMode = strictMode; } -bool Kdbx4XmlReader::hasError() +bool KdbxXmlReader::hasError() const { return m_error || m_xml.hasError(); } -QString Kdbx4XmlReader::errorString() +QString KdbxXmlReader::errorString() const { if (m_error) { return m_errorStr; - } - - if (m_xml.hasError()) { + }if (m_xml.hasError()) { return QString("XML error:\n%1\nLine %2, column %3") - .arg(m_xml.errorString()) - .arg(m_xml.lineNumber()) - .arg(m_xml.columnNumber()); + .arg(m_xml.errorString()) + .arg(m_xml.lineNumber()) + .arg(m_xml.columnNumber()); } - return QString(); } -void Kdbx4XmlReader::raiseError(const QString& errorMessage) +void KdbxXmlReader::raiseError(const QString& errorMessage) { m_error = true; m_errorStr = errorMessage; } -QByteArray Kdbx4XmlReader::headerHash() +QByteArray KdbxXmlReader::headerHash() const { return m_headerHash; } -bool Kdbx4XmlReader::parseKeePassFile() +bool KdbxXmlReader::parseKeePassFile() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile"); @@ -206,7 +229,7 @@ bool Kdbx4XmlReader::parseKeePassFile() return rootParsedSuccessfully; } -void Kdbx4XmlReader::parseMeta() +void KdbxXmlReader::parseMeta() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Meta"); @@ -281,7 +304,7 @@ void Kdbx4XmlReader::parseMeta() } } -void Kdbx4XmlReader::parseMemoryProtection() +void KdbxXmlReader::parseMemoryProtection() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "MemoryProtection"); @@ -302,7 +325,7 @@ void Kdbx4XmlReader::parseMemoryProtection() } } -void Kdbx4XmlReader::parseCustomIcons() +void KdbxXmlReader::parseCustomIcons() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomIcons"); @@ -315,7 +338,7 @@ void Kdbx4XmlReader::parseCustomIcons() } } -void Kdbx4XmlReader::parseIcon() +void KdbxXmlReader::parseIcon() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Icon"); @@ -341,10 +364,10 @@ void Kdbx4XmlReader::parseIcon() return; } - raiseError("Missing icon uuid or data"); + raiseError(tr("Missing icon uuid or data")); } -void Kdbx4XmlReader::parseBinaries() +void KdbxXmlReader::parseBinaries() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries"); @@ -366,7 +389,7 @@ void Kdbx4XmlReader::parseBinaries() } if (m_binaryPool.contains(id)) { - qWarning("Kdbx4XmlReader::parseBinaries: overwriting binary item \"%s\"", + qWarning("KdbxXmlReader::parseBinaries: overwriting binary item \"%s\"", qPrintable(id)); } @@ -374,7 +397,7 @@ void Kdbx4XmlReader::parseBinaries() } } -void Kdbx4XmlReader::parseCustomData() +void KdbxXmlReader::parseCustomData() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData"); @@ -387,7 +410,7 @@ void Kdbx4XmlReader::parseCustomData() } } -void Kdbx4XmlReader::parseCustomDataItem() +void KdbxXmlReader::parseCustomDataItem() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item"); @@ -413,10 +436,10 @@ void Kdbx4XmlReader::parseCustomDataItem() return; } - raiseError("Missing custom data key or value"); + raiseError(tr("Missing custom data key or value")); } -bool Kdbx4XmlReader::parseRoot() +bool KdbxXmlReader::parseRoot() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Root"); @@ -427,7 +450,7 @@ bool Kdbx4XmlReader::parseRoot() if (m_xml.name() == "Group") { if (groupElementFound) { groupParsedSuccessfully = false; - raiseError("Multiple group elements"); + raiseError(tr("Multiple group elements")); continue; } @@ -450,7 +473,7 @@ bool Kdbx4XmlReader::parseRoot() return groupParsedSuccessfully; } -Group* Kdbx4XmlReader::parseGroup() +Group* KdbxXmlReader::parseGroup() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group"); @@ -463,7 +486,7 @@ Group* Kdbx4XmlReader::parseGroup() Uuid uuid = readUuid(); if (uuid.isNull()) { if (m_strictMode) { - raiseError("Null group uuid"); + raiseError(tr("Null group uuid")); } else { group->setUuid(Uuid::random()); } @@ -484,11 +507,11 @@ Group* Kdbx4XmlReader::parseGroup() int iconId = readNumber(); if (iconId < 0) { if (m_strictMode) { - raiseError("Invalid group icon number"); + raiseError(tr("Invalid group icon number")); } iconId = 0; } else if (iconId >= DatabaseIcons::IconCount) { - qWarning("Kdbx4XmlReader::parseGroup: icon id \"%d\" not supported", iconId); + qWarning("KdbxXmlReader::parseGroup: icon id \"%d\" not supported", iconId); iconId = DatabaseIcons::IconCount - 1; } @@ -524,7 +547,7 @@ Group* Kdbx4XmlReader::parseGroup() } else if (str.compare("false", Qt::CaseInsensitive) == 0) { group->setAutoTypeEnabled(Group::Disable); } else { - raiseError("Invalid EnableAutoType value"); + raiseError(tr("Invalid EnableAutoType value")); } continue; } @@ -538,7 +561,7 @@ Group* Kdbx4XmlReader::parseGroup() } else if (str.compare("false", Qt::CaseInsensitive) == 0) { group->setSearchingEnabled(Group::Disable); } else { - raiseError("Invalid EnableSearching value"); + raiseError(tr("Invalid EnableSearching value")); } continue; } @@ -575,7 +598,7 @@ Group* Kdbx4XmlReader::parseGroup() group->setUpdateTimeinfo(false); delete tmpGroup; } else if (!hasError()) { - raiseError("No group uuid found"); + raiseError(tr("No group uuid found")); } for (Group* child : asConst(children)) { @@ -589,7 +612,7 @@ Group* Kdbx4XmlReader::parseGroup() return group; } -void Kdbx4XmlReader::parseDeletedObjects() +void KdbxXmlReader::parseDeletedObjects() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObjects"); @@ -602,7 +625,7 @@ void Kdbx4XmlReader::parseDeletedObjects() } } -void Kdbx4XmlReader::parseDeletedObject() +void KdbxXmlReader::parseDeletedObject() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject"); @@ -613,7 +636,7 @@ void Kdbx4XmlReader::parseDeletedObject() Uuid uuid = readUuid(); if (uuid.isNull()) { if (m_strictMode) { - raiseError("Null DeleteObject uuid"); + raiseError(tr("Null DeleteObject uuid")); } continue; } @@ -633,11 +656,11 @@ void Kdbx4XmlReader::parseDeletedObject() } if (m_strictMode) { - raiseError("Missing DeletedObject uuid or time"); + raiseError(tr("Missing DeletedObject uuid or time")); } } -Entry* Kdbx4XmlReader::parseEntry(bool history) +Entry* KdbxXmlReader::parseEntry(bool history) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry"); @@ -651,7 +674,7 @@ Entry* Kdbx4XmlReader::parseEntry(bool history) Uuid uuid = readUuid(); if (uuid.isNull()) { if (m_strictMode) { - raiseError("Null entry uuid"); + raiseError(tr("Null entry uuid")); } else { entry->setUuid(Uuid::random()); } @@ -664,7 +687,7 @@ Entry* Kdbx4XmlReader::parseEntry(bool history) int iconId = readNumber(); if (iconId < 0) { if (m_strictMode) { - raiseError("Invalid entry icon number"); + raiseError(tr("Invalid entry icon number")); } iconId = 0; } @@ -714,7 +737,7 @@ Entry* Kdbx4XmlReader::parseEntry(bool history) } if (m_xml.name() == "History") { if (history) { - raiseError("History element in history entry"); + raiseError(tr("History element in history entry")); } else { historyItems = parseEntryHistory(); } @@ -741,13 +764,13 @@ Entry* Kdbx4XmlReader::parseEntry(bool history) delete tmpEntry; } } else if (!hasError()) { - raiseError("No entry uuid found"); + raiseError(tr("No entry uuid found")); } for (Entry* historyItem : asConst(historyItems)) { if (historyItem->uuid() != entry->uuid()) { if (m_strictMode) { - raiseError("History element with different uuid"); + raiseError(tr("History element with different uuid")); } else { historyItem->setUuid(entry->uuid()); } @@ -762,7 +785,7 @@ Entry* Kdbx4XmlReader::parseEntry(bool history) return entry; } -void Kdbx4XmlReader::parseEntryString(Entry* entry) +void KdbxXmlReader::parseEntryString(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "String"); @@ -798,7 +821,7 @@ void Kdbx4XmlReader::parseEntryString(Entry* entry) value = QString::fromUtf8(plaintext); } } else { - raiseError("Unable to decrypt entry string"); + raiseError(tr("Unable to decrypt entry string")); continue; } } @@ -814,17 +837,17 @@ void Kdbx4XmlReader::parseEntryString(Entry* entry) if (keySet && valueSet) { // the default attributes are always there so additionally check if it's empty if (entry->attributes()->hasKey(key) && !entry->attributes()->value(key).isEmpty()) { - raiseError("Duplicate custom attribute found"); + raiseError(tr("Duplicate custom attribute found")); return; } entry->attributes()->set(key, value, protect); return; } - raiseError("Entry string key or value missing"); + raiseError(tr("Entry string key or value missing")); } -QPair Kdbx4XmlReader::parseEntryBinary(Entry* entry) +QPair KdbxXmlReader::parseEntryBinary(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binary"); @@ -851,7 +874,7 @@ QPair Kdbx4XmlReader::parseEntryBinary(Entry* entry) // format compatibility value = readBinary(); bool isProtected = attr.hasAttribute("Protected") - && (attr.value("Protected") == "True"); + && (attr.value("Protected") == "True"); if (isProtected && !value.isEmpty()) { if (!m_randomStream->processInPlace(value)) { @@ -868,18 +891,18 @@ QPair Kdbx4XmlReader::parseEntryBinary(Entry* entry) if (keySet && valueSet) { if (entry->attachments()->hasKey(key)) { - raiseError("Duplicate attachment found"); + raiseError(tr("Duplicate attachment found")); } else { entry->attachments()->set(key, value); } } else { - raiseError("Entry binary key or value missing"); + raiseError(tr("Entry binary key or value missing")); } return poolRef; } -void Kdbx4XmlReader::parseAutoType(Entry* entry) +void KdbxXmlReader::parseAutoType(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "AutoType"); @@ -898,7 +921,7 @@ void Kdbx4XmlReader::parseAutoType(Entry* entry) } } -void Kdbx4XmlReader::parseAutoTypeAssoc(Entry* entry) +void KdbxXmlReader::parseAutoTypeAssoc(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Association"); @@ -922,10 +945,10 @@ void Kdbx4XmlReader::parseAutoTypeAssoc(Entry* entry) entry->autoTypeAssociations()->add(assoc); return; } - raiseError("Auto-type association window or sequence missing"); + raiseError(tr("Auto-type association window or sequence missing")); } -QList Kdbx4XmlReader::parseEntryHistory() +QList KdbxXmlReader::parseEntryHistory() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "History"); @@ -942,7 +965,7 @@ QList Kdbx4XmlReader::parseEntryHistory() return historyItems; } -TimeInfo Kdbx4XmlReader::parseTimes() +TimeInfo KdbxXmlReader::parseTimes() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times"); @@ -970,12 +993,12 @@ TimeInfo Kdbx4XmlReader::parseTimes() return timeInfo; } -QString Kdbx4XmlReader::readString() +QString KdbxXmlReader::readString() { return m_xml.readElementText(); } -bool Kdbx4XmlReader::readBool() +bool KdbxXmlReader::readBool() { QString str = readString(); @@ -988,11 +1011,11 @@ bool Kdbx4XmlReader::readBool() if (str.length() == 0) { return false; } - raiseError("Invalid bool value"); + raiseError(tr("Invalid bool value")); return false; } -QDateTime Kdbx4XmlReader::readDateTime() +QDateTime KdbxXmlReader::readDateTime() { static QRegularExpression b64regex("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$"); QString str = readString(); @@ -1009,13 +1032,13 @@ QDateTime Kdbx4XmlReader::readDateTime() } if (m_strictMode) { - raiseError("Invalid date time value"); + raiseError(tr("Invalid date time value")); } return QDateTime::currentDateTimeUtc(); } -QColor Kdbx4XmlReader::readColor() +QColor KdbxXmlReader::readColor() { QString colorStr = readString(); @@ -1025,7 +1048,7 @@ QColor Kdbx4XmlReader::readColor() if (colorStr.length() != 7 || colorStr[0] != '#') { if (m_strictMode) { - raiseError("Invalid color value"); + raiseError(tr("Invalid color value")); } return {}; } @@ -1037,7 +1060,7 @@ QColor Kdbx4XmlReader::readColor() int rgbPart = rgbPartStr.toInt(&ok, 16); if (!ok || rgbPart > 255) { if (m_strictMode) { - raiseError("Invalid color rgb part"); + raiseError(tr("Invalid color rgb part")); } return {}; } @@ -1054,17 +1077,17 @@ QColor Kdbx4XmlReader::readColor() return color; } -int Kdbx4XmlReader::readNumber() +int KdbxXmlReader::readNumber() { bool ok; int result = readString().toInt(&ok); if (!ok) { - raiseError("Invalid number value"); + raiseError(tr("Invalid number value")); } return result; } -Uuid Kdbx4XmlReader::readUuid() +Uuid KdbxXmlReader::readUuid() { QByteArray uuidBin = readBinary(); if (uuidBin.isEmpty()) { @@ -1072,19 +1095,19 @@ Uuid Kdbx4XmlReader::readUuid() } if (uuidBin.length() != Uuid::Length) { if (m_strictMode) { - raiseError("Invalid uuid value"); + raiseError(tr("Invalid uuid value")); } return {}; } return Uuid(uuidBin); } -QByteArray Kdbx4XmlReader::readBinary() +QByteArray KdbxXmlReader::readBinary() { return QByteArray::fromBase64(readString().toLatin1()); } -QByteArray Kdbx4XmlReader::readCompressedBinary() +QByteArray KdbxXmlReader::readCompressedBinary() { QByteArray rawData = readBinary(); @@ -1097,12 +1120,12 @@ QByteArray Kdbx4XmlReader::readCompressedBinary() QByteArray result; if (!Tools::readAllFromDevice(&compressor, result)) { - raiseError("Unable to decompress binary"); + raiseError(tr("Unable to decompress binary")); } return result; } -Group* Kdbx4XmlReader::getGroup(const Uuid& uuid) +Group* KdbxXmlReader::getGroup(const Uuid& uuid) { if (uuid.isNull()) { return nullptr; @@ -1120,7 +1143,7 @@ Group* Kdbx4XmlReader::getGroup(const Uuid& uuid) return group; } -Entry* Kdbx4XmlReader::getEntry(const Uuid& uuid) +Entry* KdbxXmlReader::getEntry(const Uuid& uuid) { if (uuid.isNull()) { return nullptr; @@ -1138,8 +1161,9 @@ Entry* Kdbx4XmlReader::getEntry(const Uuid& uuid) return entry; } -void Kdbx4XmlReader::skipCurrentElement() +void KdbxXmlReader::skipCurrentElement() { - qWarning("Kdbx4XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString())); + qWarning("KdbxXmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString())); m_xml.skipCurrentElement(); } + diff --git a/src/format/KdbxXmlReader.h b/src/format/KdbxXmlReader.h new file mode 100644 index 000000000..e31757ccf --- /dev/null +++ b/src/format/KdbxXmlReader.h @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2018 KeePassXC Team + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 or (at your option) + * version 3 of the License. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef KEEPASSXC_KDBXXMLREADER_H +#define KEEPASSXC_KDBXXMLREADER_H + +#include "core/Metadata.h" +#include "core/TimeInfo.h" +#include "core/Uuid.h" +#include "core/Database.h" + +#include +#include +#include +#include + +class QIODevice; +class Group; +class Entry; +class KeePass2RandomStream; + +/** + * KDBX XML payload reader. + */ +class KdbxXmlReader +{ +Q_DECLARE_TR_FUNCTIONS(KdbxXmlReader) + +public: + explicit KdbxXmlReader(quint32 version); + explicit KdbxXmlReader(quint32 version, QHash& binaryPool); + virtual ~KdbxXmlReader() = default; + + virtual Database* readDatabase(const QString& filename); + virtual Database* readDatabase(QIODevice* device); + virtual void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr); + + bool hasError() const; + QString errorString() const; + + QByteArray headerHash() const; + + bool strictMode() const; + void setStrictMode(bool strictMode); + +protected: + typedef QPair StringPair; + + virtual bool parseKeePassFile(); + virtual void parseMeta(); + virtual void parseMemoryProtection(); + virtual void parseCustomIcons(); + virtual void parseIcon(); + virtual void parseBinaries(); + virtual void parseCustomData(); + virtual void parseCustomDataItem(); + virtual bool parseRoot(); + virtual Group* parseGroup(); + virtual void parseDeletedObjects(); + virtual void parseDeletedObject(); + virtual Entry* parseEntry(bool history); + virtual void parseEntryString(Entry* entry); + virtual QPair parseEntryBinary(Entry* entry); + virtual void parseAutoType(Entry* entry); + virtual void parseAutoTypeAssoc(Entry* entry); + virtual QList parseEntryHistory(); + virtual TimeInfo parseTimes(); + + virtual QString readString(); + virtual bool readBool(); + virtual QDateTime readDateTime(); + virtual QColor readColor(); + virtual int readNumber(); + virtual Uuid readUuid(); + virtual QByteArray readBinary(); + virtual QByteArray readCompressedBinary(); + + virtual void skipCurrentElement(); + + virtual Group* getGroup(const Uuid& uuid); + virtual Entry* getEntry(const Uuid& uuid); + + virtual void raiseError(const QString& errorMessage); + + const quint32 m_kdbxVersion; + + bool m_strictMode = false; + + QPointer m_db; + QPointer m_meta; + KeePass2RandomStream* m_randomStream = nullptr; + QXmlStreamReader m_xml; + + QScopedPointer m_tmpParent; + QHash m_groups; + QHash m_entries; + + QHash m_binaryPool; + QHash > m_binaryMap; + QByteArray m_headerHash; + + bool m_error = false; + QString m_errorStr = ""; +}; + +#endif //KEEPASSXC_KDBXXMLREADER_H diff --git a/src/format/Kdbx4XmlWriter.cpp b/src/format/KdbxXmlWriter.cpp similarity index 82% rename from src/format/Kdbx4XmlWriter.cpp rename to src/format/KdbxXmlWriter.cpp index 5c99186ca..4fe202a9f 100644 --- a/src/format/Kdbx4XmlWriter.cpp +++ b/src/format/KdbxXmlWriter.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -#include "Kdbx4XmlWriter.h" +#include "KdbxXmlWriter.h" #include #include @@ -25,51 +25,35 @@ #include "format/KeePass2RandomStream.h" #include "streams/QtIOCompressor" -Kdbx4XmlWriter::Kdbx4XmlWriter() - : Kdbx4XmlWriter(KeePass2::FILE_VERSION_3) +/** + * @param version KDBX version + */ +KdbxXmlWriter::KdbxXmlWriter(quint32 version) + : m_kdbxVersion(version) { } -Kdbx4XmlWriter::Kdbx4XmlWriter(quint32 version) - : Kdbx4XmlWriter(version, QHash()) -{ -} - -Kdbx4XmlWriter::Kdbx4XmlWriter(quint32 version, QHash idMap) - : m_db(nullptr) - , m_meta(nullptr) - , m_randomStream(nullptr) - , m_idMap(idMap) - , m_error(false) - , m_version(version) -{ - m_xml.setAutoFormatting(true); - m_xml.setAutoFormattingIndent(-1); // 1 tab - m_xml.setCodec("UTF-8"); -} - -void Kdbx4XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, const QByteArray& headerHash) +void KdbxXmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, const QByteArray& headerHash) { m_db = db; m_meta = db->metadata(); m_randomStream = randomStream; m_headerHash = headerHash; - if (m_version < KeePass2::FILE_VERSION_4 && m_idMap.isEmpty()) { - generateIdMap(); - } + m_xml.setAutoFormatting(true); + m_xml.setAutoFormattingIndent(-1); // 1 tab + m_xml.setCodec("UTF-8"); + + generateIdMap(); m_xml.setDevice(device); - m_xml.writeStartDocument("1.0", true); - m_xml.writeStartElement("KeePassFile"); writeMetadata(); writeRoot(); m_xml.writeEndElement(); - m_xml.writeEndDocument(); if (m_xml.hasError()) { @@ -77,24 +61,24 @@ void Kdbx4XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2Rand } } -void Kdbx4XmlWriter::writeDatabase(const QString& filename, Database* db) +void KdbxXmlWriter::writeDatabase(const QString& filename, Database* db) { QFile file(filename); file.open(QIODevice::WriteOnly|QIODevice::Truncate); writeDatabase(&file, db); } -bool Kdbx4XmlWriter::hasError() +bool KdbxXmlWriter::hasError() { return m_error; } -QString Kdbx4XmlWriter::errorString() +QString KdbxXmlWriter::errorString() { return m_errorStr; } -void Kdbx4XmlWriter::generateIdMap() +void KdbxXmlWriter::generateIdMap() { const QList allEntries = m_db->rootGroup()->entriesRecursive(true); int nextId = 0; @@ -110,11 +94,11 @@ void Kdbx4XmlWriter::generateIdMap() } } -void Kdbx4XmlWriter::writeMetadata() +void KdbxXmlWriter::writeMetadata() { m_xml.writeStartElement("Meta"); writeString("Generator", m_meta->generator()); - if (m_version < KeePass2::FILE_VERSION_4 && !m_headerHash.isEmpty()) { + if (m_kdbxVersion < KeePass2::FILE_VERSION_4 && !m_headerHash.isEmpty()) { writeBinary("HeaderHash", m_headerHash); } writeString("DatabaseName", m_meta->name()); @@ -139,10 +123,10 @@ void Kdbx4XmlWriter::writeMetadata() writeUuid("LastTopVisibleGroup", m_meta->lastTopVisibleGroup()); writeNumber("HistoryMaxItems", m_meta->historyMaxItems()); writeNumber("HistoryMaxSize", m_meta->historyMaxSize()); - if (m_version >= KeePass2::FILE_VERSION_4) { + if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) { writeDateTime("SettingsChanged", m_meta->settingsChanged()); } - if (m_version < KeePass2::FILE_VERSION_4) { + if (m_kdbxVersion < KeePass2::FILE_VERSION_4) { writeBinaries(); } writeCustomData(); @@ -150,7 +134,7 @@ void Kdbx4XmlWriter::writeMetadata() m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeMemoryProtection() +void KdbxXmlWriter::writeMemoryProtection() { m_xml.writeStartElement("MemoryProtection"); @@ -163,7 +147,7 @@ void Kdbx4XmlWriter::writeMemoryProtection() m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeCustomIcons() +void KdbxXmlWriter::writeCustomIcons() { m_xml.writeStartElement("CustomIcons"); @@ -175,7 +159,7 @@ void Kdbx4XmlWriter::writeCustomIcons() m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) +void KdbxXmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) { m_xml.writeStartElement("Icon"); @@ -192,7 +176,7 @@ void Kdbx4XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeBinaries() +void KdbxXmlWriter::writeBinaries() { m_xml.writeStartElement("Binaries"); @@ -234,7 +218,7 @@ void Kdbx4XmlWriter::writeBinaries() m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeCustomData() +void KdbxXmlWriter::writeCustomData() { m_xml.writeStartElement("CustomData"); @@ -247,7 +231,7 @@ void Kdbx4XmlWriter::writeCustomData() m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeCustomDataItem(const QString& key, const QString& value) +void KdbxXmlWriter::writeCustomDataItem(const QString& key, const QString& value) { m_xml.writeStartElement("Item"); @@ -257,7 +241,7 @@ void Kdbx4XmlWriter::writeCustomDataItem(const QString& key, const QString& valu m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeRoot() +void KdbxXmlWriter::writeRoot() { Q_ASSERT(m_db->rootGroup()); @@ -269,7 +253,7 @@ void Kdbx4XmlWriter::writeRoot() m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeGroup(const Group* group) +void KdbxXmlWriter::writeGroup(const Group* group) { Q_ASSERT(!group->uuid().isNull()); @@ -293,12 +277,12 @@ void Kdbx4XmlWriter::writeGroup(const Group* group) writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry()); - const QList entryList = group->entries(); + const QList& entryList = group->entries(); for (const Entry* entry : entryList) { writeEntry(entry); } - const QList children = group->children(); + const QList& children = group->children(); for (const Group* child : children) { writeGroup(child); } @@ -306,7 +290,7 @@ void Kdbx4XmlWriter::writeGroup(const Group* group) m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeTimes(const TimeInfo& ti) +void KdbxXmlWriter::writeTimes(const TimeInfo& ti) { m_xml.writeStartElement("Times"); @@ -321,7 +305,7 @@ void Kdbx4XmlWriter::writeTimes(const TimeInfo& ti) m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeDeletedObjects() +void KdbxXmlWriter::writeDeletedObjects() { m_xml.writeStartElement("DeletedObjects"); @@ -333,7 +317,7 @@ void Kdbx4XmlWriter::writeDeletedObjects() m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeDeletedObject(const DeletedObject& delObj) +void KdbxXmlWriter::writeDeletedObject(const DeletedObject& delObj) { m_xml.writeStartElement("DeletedObject"); @@ -343,7 +327,7 @@ void Kdbx4XmlWriter::writeDeletedObject(const DeletedObject& delObj) m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeEntry(const Entry* entry) +void KdbxXmlWriter::writeEntry(const Entry* entry) { Q_ASSERT(!entry->uuid().isNull()); @@ -425,7 +409,7 @@ void Kdbx4XmlWriter::writeEntry(const Entry* entry) m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeAutoType(const Entry* entry) +void KdbxXmlWriter::writeAutoType(const Entry* entry) { m_xml.writeStartElement("AutoType"); @@ -441,7 +425,7 @@ void Kdbx4XmlWriter::writeAutoType(const Entry* entry) m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc) +void KdbxXmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc) { m_xml.writeStartElement("Association"); @@ -451,7 +435,7 @@ void Kdbx4XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeEntryHistory(const Entry* entry) +void KdbxXmlWriter::writeEntryHistory(const Entry* entry) { m_xml.writeStartElement("History"); @@ -463,7 +447,7 @@ void Kdbx4XmlWriter::writeEntryHistory(const Entry* entry) m_xml.writeEndElement(); } -void Kdbx4XmlWriter::writeString(const QString& qualifiedName, const QString& string) +void KdbxXmlWriter::writeString(const QString& qualifiedName, const QString& string) { if (string.isEmpty()) { m_xml.writeEmptyElement(qualifiedName); @@ -473,12 +457,12 @@ void Kdbx4XmlWriter::writeString(const QString& qualifiedName, const QString& st } } -void Kdbx4XmlWriter::writeNumber(const QString& qualifiedName, int number) +void KdbxXmlWriter::writeNumber(const QString& qualifiedName, int number) { writeString(qualifiedName, QString::number(number)); } -void Kdbx4XmlWriter::writeBool(const QString& qualifiedName, bool b) +void KdbxXmlWriter::writeBool(const QString& qualifiedName, bool b) { if (b) { writeString(qualifiedName, "True"); @@ -488,13 +472,13 @@ void Kdbx4XmlWriter::writeBool(const QString& qualifiedName, bool b) } } -void Kdbx4XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime) +void KdbxXmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime) { Q_ASSERT(dateTime.isValid()); Q_ASSERT(dateTime.timeSpec() == Qt::UTC); QString dateTimeStr; - if (m_version < KeePass2::FILE_VERSION_4) { + if (m_kdbxVersion < KeePass2::FILE_VERSION_4) { dateTimeStr = dateTime.toString(Qt::ISODate); // Qt < 4.8 doesn't append a 'Z' at the end @@ -509,12 +493,12 @@ void Kdbx4XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime writeString(qualifiedName, dateTimeStr); } -void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid) +void KdbxXmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid) { writeString(qualifiedName, uuid.toBase64()); } -void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) +void KdbxXmlWriter::writeUuid(const QString& qualifiedName, const Group* group) { if (group) { writeUuid(qualifiedName, group->uuid()); @@ -524,7 +508,7 @@ void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) } } -void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) +void KdbxXmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) { if (entry) { writeUuid(qualifiedName, entry->uuid()); @@ -534,12 +518,12 @@ void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) } } -void Kdbx4XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba) +void KdbxXmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba) { writeString(qualifiedName, QString::fromLatin1(ba.toBase64())); } -void Kdbx4XmlWriter::writeColor(const QString& qualifiedName, const QColor& color) +void KdbxXmlWriter::writeColor(const QString& qualifiedName, const QColor& color) { QString colorStr; @@ -552,7 +536,7 @@ void Kdbx4XmlWriter::writeColor(const QString& qualifiedName, const QColor& colo writeString(qualifiedName, colorStr); } -void Kdbx4XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState) +void KdbxXmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState) { QString value; @@ -569,7 +553,7 @@ void Kdbx4XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState writeString(qualifiedName, value); } -QString Kdbx4XmlWriter::colorPartToString(int value) +QString KdbxXmlWriter::colorPartToString(int value) { QString str = QString::number(value, 16).toUpper(); if (str.length() == 1) { @@ -579,7 +563,7 @@ QString Kdbx4XmlWriter::colorPartToString(int value) return str; } -QString Kdbx4XmlWriter::stripInvalidXml10Chars(QString str) +QString KdbxXmlWriter::stripInvalidXml10Chars(QString str) { for (int i = str.size() - 1; i >= 0; i--) { const QChar ch = str.at(i); @@ -604,7 +588,7 @@ QString Kdbx4XmlWriter::stripInvalidXml10Chars(QString str) return str; } -void Kdbx4XmlWriter::raiseError(const QString& errorMessage) +void KdbxXmlWriter::raiseError(const QString& errorMessage) { m_error = true; m_errorStr = errorMessage; diff --git a/src/format/Kdbx4XmlWriter.h b/src/format/KdbxXmlWriter.h similarity index 88% rename from src/format/Kdbx4XmlWriter.h rename to src/format/KdbxXmlWriter.h index 17d872580..6c1bd1d0b 100644 --- a/src/format/Kdbx4XmlWriter.h +++ b/src/format/KdbxXmlWriter.h @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#ifndef KEEPASSX_KDBX4XMLWRITER_H -#define KEEPASSX_KDBX4XMLWRITER_H +#ifndef KEEPASSX_KDBXXMLWRITER_H +#define KEEPASSX_KDBXXMLWRITER_H #include #include @@ -32,12 +32,11 @@ class KeePass2RandomStream; class Metadata; -class Kdbx4XmlWriter +class KdbxXmlWriter { public: - Kdbx4XmlWriter(); - Kdbx4XmlWriter(quint32 version); - Kdbx4XmlWriter(quint32 version, QHash idMap); + explicit KdbxXmlWriter(quint32 version); + void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr, const QByteArray& headerHash = QByteArray()); void writeDatabase(const QString& filename, Database* db); @@ -79,15 +78,18 @@ private: void raiseError(const QString& errorMessage); + const quint32 m_kdbxVersion; + QXmlStreamWriter m_xml; - Database* m_db; - Metadata* m_meta; - KeePass2RandomStream* m_randomStream; + QPointer m_db; + QPointer m_meta; + KeePass2RandomStream* m_randomStream = nullptr; QHash m_idMap; - bool m_error; - QString m_errorStr; - quint32 m_version; QByteArray m_headerHash; + + bool m_error = false; + + QString m_errorStr = ""; }; -#endif // KEEPASSX_KDBX4XMLWRITER_H +#endif // KEEPASSX_KDBXXMLWRITER_H diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index c213b4a18..abc7f54e1 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -15,29 +15,21 @@ * along with this program. If not, see . */ -#include -#include -#include - -#include "core/Endian.h" -#include "keys/CompositeKey.h" #include "format/KeePass2Reader.h" #include "format/KeePass1.h" -#include "format/KeePass2.h" #include "format/Kdbx3Reader.h" #include "format/Kdbx4Reader.h" -BaseKeePass2Reader::BaseKeePass2Reader() - : m_error(false) - , m_saveXml(false) - , m_irsAlgo(KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo) -{ - m_errorStr.clear(); - m_xmlData.clear(); - m_protectedStreamKey.clear(); -} +#include -Database* BaseKeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key) +/** + * 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 + */ +Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key) { QFile file(filename); if (!file.open(QFile::ReadOnly)) { @@ -55,79 +47,45 @@ Database* BaseKeePass2Reader::readDatabase(const QString& filename, const Compos return db.take(); } -bool BaseKeePass2Reader::hasError() -{ - return m_error; -} - -QString BaseKeePass2Reader::errorString() -{ - return m_errorStr; -} - -void BaseKeePass2Reader::setSaveXml(bool save) -{ - m_saveXml = save; -} - -QByteArray BaseKeePass2Reader::xmlData() -{ - return m_xmlData; -} - -QByteArray BaseKeePass2Reader::streamKey() -{ - return m_protectedStreamKey; -} - -KeePass2::ProtectedStreamAlgo BaseKeePass2Reader::protectedStreamAlgo() const { - return m_irsAlgo; -} - - -void BaseKeePass2Reader::raiseError(const QString& errorMessage) -{ - m_error = true; - m_errorStr = errorMessage; -} - +/** + * Read database from device and detect correct file format. + * + * @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 + */ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) { m_error = false; m_errorStr.clear(); - bool ok; + quint32 signature1, signature2; + bool ok = KdbxReader::readMagicNumbers(device, signature1, signature2, m_version); - quint32 signature1 = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); - if (!ok || signature1 != KeePass2::SIGNATURE_1) { + // mask out minor version + m_version &= KeePass2::FILE_VERSION_CRITICAL_MASK; + + if (!ok || signature1 != KeePass2::SIGNATURE_1 || signature2 != KeePass2::SIGNATURE_2) { raiseError(tr("Not a KeePass database.")); return nullptr; } - quint32 signature2 = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok); - if (ok && signature2 == KeePass1::SIGNATURE_2) { + if (signature2 == KeePass1::SIGNATURE_2) { raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" "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; } - else if (!ok || signature2 != KeePass2::SIGNATURE_2) { - raiseError(tr("Not a KeePass database.")); - return nullptr; - } - m_version = Endian::readSizedInt(device, KeePass2::BYTEORDER, &ok) - & KeePass2::FILE_VERSION_CRITICAL_MASK; quint32 maxVersion = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK; - if (!ok || (m_version < KeePass2::FILE_VERSION_MIN) || (m_version > maxVersion)) { + if (m_version < KeePass2::FILE_VERSION_MIN || m_version > maxVersion) { raiseError(tr("Unsupported KeePass 2 database version.")); return nullptr; } - device->seek(0); - - // Determine KDBX3 vs KDBX4 + // determine file format (KDBX 2/3 or 4) if (m_version < KeePass2::FILE_VERSION_4) { m_reader.reset(new Kdbx3Reader()); } else { @@ -138,37 +96,49 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke return m_reader->readDatabase(device, key, keepDatabase); } -bool KeePass2Reader::hasError() +bool KeePass2Reader::hasError() const { return m_error || (!m_reader.isNull() && m_reader->hasError()); } -QString KeePass2Reader::errorString() +QString KeePass2Reader::errorString() const { return !m_reader.isNull() ? m_reader->errorString() : m_errorStr; } -QByteArray KeePass2Reader::xmlData() +bool KeePass2Reader::saveXml() const { - return !m_reader.isNull() ? m_reader->xmlData() : m_xmlData; + return m_saveXml; } -QByteArray KeePass2Reader::streamKey() +void KeePass2Reader::setSaveXml(bool save) { - return !m_reader.isNull() ? m_reader->streamKey() : m_protectedStreamKey; -} - -KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const -{ - return !m_reader.isNull() ? m_reader->protectedStreamAlgo() : m_irsAlgo; + m_saveXml = save; } +/** + * @return detected KDBX version + */ quint32 KeePass2Reader::version() const { return m_version; } -QSharedPointer KeePass2Reader::reader() +/** + * @return KDBX reader used for reading the input file + */ +QSharedPointer KeePass2Reader::reader() const { return m_reader; -} \ No newline at end of file +} + +/** + * Raise an error. Use in case of an unexpected read error. + * + * @param errorMessage error message + */ +void KeePass2Reader::raiseError(const QString& errorMessage) +{ + m_error = true; + m_errorStr = errorMessage; +} diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h index fd28db2b7..1b91223ee 100644 --- a/src/format/KeePass2Reader.h +++ b/src/format/KeePass2Reader.h @@ -18,6 +18,11 @@ #ifndef KEEPASSX_KEEPASS2READER_H #define KEEPASSX_KEEPASS2READER_H +#include "format/KeePass2.h" +#include "core/Database.h" +#include "keys/CompositeKey.h" +#include "KdbxReader.h" + #include #include #include @@ -25,58 +30,31 @@ #include #include -#include "format/KeePass2.h" -#include "core/Database.h" -#include "keys/CompositeKey.h" - -class BaseKeePass2Reader +class KeePass2Reader { - Q_DECLARE_TR_FUNCTIONS(BaseKeePass2Reader) +Q_DECLARE_TR_FUNCTIONS(KdbxReader) public: - BaseKeePass2Reader(); + Database* readDatabase(const QString& filename, const CompositeKey& key); + Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false); - virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) = 0; - virtual Database* readDatabase(const QString& filename, const CompositeKey& key); + bool hasError() const; + QString errorString() const; - virtual bool hasError(); - virtual QString errorString(); - virtual void setSaveXml(bool save); - virtual QByteArray xmlData(); - virtual QByteArray streamKey(); - virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const; - - virtual ~BaseKeePass2Reader() = default; - -protected: - void raiseError(const QString& errorMessage); - - bool m_error; - QString m_errorStr; - - bool m_saveXml; - QByteArray m_xmlData; - QByteArray m_protectedStreamKey; - KeePass2::ProtectedStreamAlgo m_irsAlgo; -}; - -class KeePass2Reader : public BaseKeePass2Reader -{ -public: - Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override; - using BaseKeePass2Reader::readDatabase; - - bool hasError() override; - QString errorString() override; - QByteArray xmlData() override; - QByteArray streamKey() override; - QSharedPointer reader(); - KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const override; + bool saveXml() const; + void setSaveXml(bool save); + QSharedPointer reader() const; quint32 version() const; private: - QSharedPointer m_reader; - quint32 m_version; + void raiseError(const QString& errorMessage); + + bool m_saveXml = false; + bool m_error = false; + QString m_errorStr = ""; + + QSharedPointer m_reader; + quint32 m_version = 0; }; #endif // KEEPASSX_KEEPASS2READER_H diff --git a/src/format/KeePass2Repair.cpp b/src/format/KeePass2Repair.cpp index fdaa45d62..e2af16cea 100644 --- a/src/format/KeePass2Repair.cpp +++ b/src/format/KeePass2Repair.cpp @@ -19,15 +19,13 @@ #include "KeePass2Repair.h" #include -#include -#include +#include "core/Group.h" #include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" #include "format/KeePass2Reader.h" #include "format/Kdbx4Reader.h" -#include "format/Kdbx3XmlReader.h" -#include "format/Kdbx4XmlReader.h" +#include "format/KdbxXmlReader.h" KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key) { @@ -41,7 +39,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, return qMakePair(NothingTodo, nullptr); } - QByteArray xmlData = reader.xmlData(); + QByteArray xmlData = reader.reader()->xmlData(); if (!db || xmlData.isEmpty()) { m_errorStr = reader.errorString(); return qMakePair(UnableToOpen, nullptr); @@ -62,7 +60,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, // try to fix broken databases because of bug #392 for (int i = (xmlData.size() - 1); i >= 0; i--) { - quint8 ch = static_cast(xmlData.at(i)); + auto ch = static_cast(xmlData.at(i)); if (ch < 0x20 && ch != 0x09 && ch != 0x0A && ch != 0x0D) { xmlData.remove(i, 1); repairAction = true; @@ -74,20 +72,20 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, return qMakePair(RepairFailed, nullptr); } - KeePass2RandomStream randomStream(reader.protectedStreamAlgo()); - randomStream.init(reader.streamKey()); + KeePass2RandomStream randomStream(reader.reader()->protectedStreamAlgo()); + randomStream.init(reader.reader()->streamKey()); bool hasError; QBuffer buffer(&xmlData); buffer.open(QIODevice::ReadOnly); if ((reader.version() & KeePass2::FILE_VERSION_CRITICAL_MASK) < KeePass2::FILE_VERSION_4) { - Kdbx3XmlReader xmlReader; + KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3); xmlReader.readDatabase(&buffer, db.data(), &randomStream); hasError = xmlReader.hasError(); } else { auto reader4 = reader.reader().staticCast(); QHash pool = reader4->binaryPool(); - Kdbx4XmlReader xmlReader(pool); + KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, pool); xmlReader.readDatabase(&buffer, db.data(), &randomStream); hasError = xmlReader.hasError(); } diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index baea9968a..3e32eb96b 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -16,7 +16,6 @@ */ #include -#include #include #include "format/KeePass2Writer.h" @@ -24,66 +23,79 @@ #include "format/Kdbx3Writer.h" #include "format/Kdbx4Writer.h" -BaseKeePass2Writer::BaseKeePass2Writer() : m_error(false) -{ - m_errorStr.clear(); -} - -BaseKeePass2Writer::~BaseKeePass2Writer() {} - -bool BaseKeePass2Writer::hasError() -{ - return m_error; -} - -QString BaseKeePass2Writer::errorString() -{ - return m_errorStr; -} - -void BaseKeePass2Writer::raiseError(const QString& errorMessage) -{ - m_error = true; - m_errorStr = errorMessage; -} - -bool BaseKeePass2Writer::writeDatabase(const QString& filename, Database* db) +/** + * Write a database to a KDBX file. + * + * @param filename output filename + * @param db source database + * @return true on success + */ +bool KeePass2Writer::writeDatabase(const QString& filename, Database* db) { QFile file(filename); - if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) { + if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) { raiseError(file.errorString()); return false; } return writeDatabase(&file, db); } -bool KeePass2Writer::hasError() -{ - return m_error || (m_writer && m_writer->hasError()); -} - -QString KeePass2Writer::errorString() -{ - return m_writer ? m_writer->errorString() : m_errorStr; -} - +/** + * Write a database to a device in KDBX format. + * + * @param device output device + * @param db source database + * @return true on success + */ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) { - bool useKdbx4 = false; + m_error = false; + m_errorStr.clear(); - if (db->kdf()->uuid() != KeePass2::KDF_AES) { - useKdbx4 = true; - } - - if (db->publicCustomData().size() > 0) { - useKdbx4 = true; - } - - // Determine KDBX3 vs KDBX4 - if (useKdbx4) { + // determine KDBX3 vs KDBX4 + if (db->kdf()->uuid() != KeePass2::KDF_AES || db->publicCustomData().size() > 0) { + m_version = KeePass2::FILE_VERSION_4; m_writer.reset(new Kdbx4Writer()); } else { + m_version = KeePass2::FILE_VERSION_3; m_writer.reset(new Kdbx3Writer()); } return m_writer->writeDatabase(device, db); } + +bool KeePass2Writer::hasError() const +{ + return m_error || (m_writer && m_writer->hasError()); +} + +QString KeePass2Writer::errorString() const +{ + return m_writer ? m_writer->errorString() : m_errorStr; +} + +/** + * Raise an error. Use in case of an unexpected write error. + * + * @param errorMessage error message + */ +void KeePass2Writer::raiseError(const QString& errorMessage) +{ + m_error = true; + m_errorStr = errorMessage; +} + +/** + * @return KDBX writer used for writing the output file + */ +QSharedPointer KeePass2Writer::writer() const +{ + return QSharedPointer(); +} + +/** + * @return KDBX version used for writing the output file + */ +quint32 KeePass2Writer::version() const +{ + return m_version; +} diff --git a/src/format/KeePass2Writer.h b/src/format/KeePass2Writer.h index f6ec129f2..98daed5e3 100644 --- a/src/format/KeePass2Writer.h +++ b/src/format/KeePass2Writer.h @@ -18,50 +18,36 @@ #ifndef KEEPASSX_KEEPASS2WRITER_H #define KEEPASSX_KEEPASS2WRITER_H -#include -#include -#include +#include "KdbxWriter.h" + #include #include -#include "core/Database.h" -#include "format/KeePass2.h" +class QIODevice; +class Database; -#define CHECK_RETURN_FALSE(x) if (!(x)) return false; - -class BaseKeePass2Writer +class KeePass2Writer { -public: - BaseKeePass2Writer(); - - virtual bool writeDatabase(QIODevice* device, Database* db) = 0; - virtual bool writeDatabase(const QString& filename, Database* db); - - virtual bool hasError(); - virtual QString errorString(); - - virtual ~BaseKeePass2Writer(); - -protected: - void raiseError(const QString& errorMessage); - - bool m_error; - QString m_errorStr; -}; - -class KeePass2Writer : public BaseKeePass2Writer -{ - Q_DECLARE_TR_FUNCTIONS(KeePass2Writer) +Q_DECLARE_TR_FUNCTIONS(KeePass2Writer) public: - virtual bool writeDatabase(QIODevice* device, Database* db) override; - using BaseKeePass2Writer::writeDatabase; + bool writeDatabase(const QString& filename, Database* db); + bool writeDatabase(QIODevice* device, Database* db); - virtual bool hasError() override; - virtual QString errorString() override; + QSharedPointer writer() const; + quint32 version() const; + + bool hasError() const; + QString errorString() const; private: - QScopedPointer m_writer; + void raiseError(const QString& errorMessage); + + bool m_error = false; + QString m_errorStr = ""; + + QScopedPointer m_writer; + quint32 m_version = 0; }; #endif // KEEPASSX_KEEPASS2READER_H diff --git a/tests/TestDeletedObjects.cpp b/tests/TestDeletedObjects.cpp index 371d318db..c8236f05b 100644 --- a/tests/TestDeletedObjects.cpp +++ b/tests/TestDeletedObjects.cpp @@ -22,7 +22,8 @@ #include "core/Database.h" #include "core/Group.h" #include "crypto/Crypto.h" -#include "format/Kdbx3XmlReader.h" +#include "format/KeePass2.h" +#include "format/KdbxXmlReader.h" #include "config-keepassx-tests.h" QTEST_GUILESS_MAIN(TestDeletedObjects) @@ -88,7 +89,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize) void TestDeletedObjects::testDeletedObjectsFromFile() { - Kdbx3XmlReader reader; + KdbxXmlReader reader(KeePass2::FILE_VERSION_3); reader.setStrictMode(true); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); Database* db = reader.readDatabase(xmlFile); diff --git a/tests/TestKeePass2XmlReader.cpp b/tests/TestKeePass2XmlReader.cpp index fda8fffd6..4970fe927 100644 --- a/tests/TestKeePass2XmlReader.cpp +++ b/tests/TestKeePass2XmlReader.cpp @@ -20,15 +20,14 @@ #include #include #include +#include #include "core/Database.h" #include "core/Group.h" #include "core/Metadata.h" #include "crypto/Crypto.h" -#include "format/Kdbx3XmlReader.h" -#include "format/Kdbx3XmlWriter.h" -#include "format/Kdbx4XmlReader.h" -#include "format/Kdbx4XmlWriter.h" +#include "format/KdbxXmlReader.h" +#include "format/KdbxXmlWriter.h" #include "config-keepassx-tests.h" namespace QTest { @@ -83,7 +82,7 @@ void TestKdbx3XmlReader::initTestCase() { QVERIFY(Crypto::init()); - Kdbx3XmlReader reader; + KdbxXmlReader reader(KeePass2::FILE_VERSION_3); reader.setStrictMode(true); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); m_db = reader.readDatabase(xmlFile); @@ -95,7 +94,7 @@ void TestKdbx4XmlReader::initTestCase() { QVERIFY(Crypto::init()); - Kdbx4XmlReader reader; + KdbxXmlReader reader(KeePass2::FILE_VERSION_3); reader.setStrictMode(true); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); m_db = reader.readDatabase(xmlFile); @@ -105,7 +104,7 @@ void TestKdbx4XmlReader::initTestCase() void TestKdbx3XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) { - Kdbx3XmlReader reader; + KdbxXmlReader reader(KeePass2::FILE_VERSION_3); reader.setStrictMode(strictMode); db = reader.readDatabase(path); hasError = reader.hasError(); @@ -114,7 +113,7 @@ void TestKdbx3XmlReader::readDatabase(QString path, bool strictMode, Database*& void TestKdbx3XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) { - Kdbx3XmlReader reader; + KdbxXmlReader reader(KeePass2::FILE_VERSION_3); reader.setStrictMode(strictMode); db = reader.readDatabase(buf); hasError = reader.hasError(); @@ -123,7 +122,7 @@ void TestKdbx3XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& void TestKdbx3XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) { - Kdbx3XmlWriter writer; + KdbxXmlWriter writer(KeePass2::FILE_VERSION_3); writer.writeDatabase(buf, db); hasError = writer.hasError(); errorString = writer.errorString(); @@ -131,7 +130,7 @@ void TestKdbx3XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasErro void TestKdbx4XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) { - Kdbx4XmlReader reader; + KdbxXmlReader reader(KeePass2::FILE_VERSION_3); reader.setStrictMode(strictMode); db = reader.readDatabase(path); hasError = reader.hasError(); @@ -140,7 +139,7 @@ void TestKdbx4XmlReader::readDatabase(QString path, bool strictMode, Database*& void TestKdbx4XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) { - Kdbx4XmlReader reader; + KdbxXmlReader reader(KeePass2::FILE_VERSION_3); reader.setStrictMode(strictMode); db = reader.readDatabase(buf); hasError = reader.hasError(); @@ -149,7 +148,7 @@ void TestKdbx4XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& void TestKdbx4XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) { - Kdbx4XmlWriter writer; + KdbxXmlWriter writer(KeePass2::FILE_VERSION_3); writer.writeDatabase(buf, db); hasError = writer.hasError(); errorString = writer.errorString(); From df728083cc147f9669d11b6306999b36c4a79fae Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 7 Jan 2018 18:46:24 +0100 Subject: [PATCH 30/39] Add challenge response to key before transformation, resolves #1060 * Re-implement KDBX4 challenge-response key assembly with transform seed instead of master seed --- src/core/Database.cpp | 9 +++++++ src/format/Kdbx4Reader.cpp | 9 +------ src/format/Kdbx4Writer.cpp | 6 ----- src/keys/CompositeKey.cpp | 54 +++++++++++++++++++++++++++++++++++++- src/keys/CompositeKey.h | 7 ++--- 5 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index 98a4fc817..a579ed02d 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -255,10 +255,19 @@ void Database::setCompressionAlgo(Database::CompressionAlgorithm algo) m_data.compressionAlgo = algo; } +/** + * Set and transform a new encryption key. + * + * @param key key to set and transform + * @param updateChangedTime true to update database change time + * @param updateTransformSalt true to update the transform salt + * @return true on success + */ bool Database::setKey(const CompositeKey& key, bool updateChangedTime, bool updateTransformSalt) { if (updateTransformSalt) { m_data.kdf->randomizeSeed(); + Q_ASSERT(!m_data.kdf->seed().isEmpty()); } QByteArray oldTransformedMasterKey = m_data.transformedMasterKey; diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index 05ef71160..1cde6d18c 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -20,7 +20,6 @@ #include #include "core/Group.h" -#include "core/Database.h" #include "core/Endian.h" #include "crypto/CryptoHash.h" #include "format/KeePass2RandomStream.h" @@ -48,19 +47,13 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device, const QByteArray& hea return nullptr; } - if (!m_db->setKey(key, false)) { + if (!m_db->setKey(key, false, false)) { raiseError(tr("Unable to calculate master key")); return nullptr; } - if (!m_db->challengeMasterSeed(m_masterSeed)) { - raiseError(tr("Unable to issue challenge-response.")); - return nullptr; - } - CryptoHash hash(CryptoHash::Sha256); hash.addData(m_masterSeed); - hash.addData(m_db->challengeResponseKey()); hash.addData(m_db->transformedMasterKey()); QByteArray finalKey = hash.result(); diff --git a/src/format/Kdbx4Writer.cpp b/src/format/Kdbx4Writer.cpp index e151ab965..70bfa2d5b 100644 --- a/src/format/Kdbx4Writer.cpp +++ b/src/format/Kdbx4Writer.cpp @@ -51,11 +51,6 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) QByteArray startBytes; QByteArray endOfHeader = "\r\n\r\n"; - if (!db->challengeMasterSeed(masterSeed)) { - raiseError(tr("Unable to issue challenge-response.")); - return false; - } - if (!db->setKey(db->key(), false, true)) { raiseError(tr("Unable to calculate master key")); return false; @@ -64,7 +59,6 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db) // generate transformed master key CryptoHash hash(CryptoHash::Sha256); hash.addData(masterSeed); - hash.addData(db->challengeResponseKey()); Q_ASSERT(!db->transformedMasterKey().isEmpty()); hash.addData(db->transformedMasterKey()); QByteArray finalKey = hash.result(); diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 8f6917afc..7a9b2cbe0 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -19,6 +19,7 @@ #include "CompositeKey.h" #include #include +#include #include "core/Global.h" #include "crypto/CryptoHash.h" @@ -73,7 +74,31 @@ CompositeKey& CompositeKey::operator=(const CompositeKey& key) return *this; } +/** + * Get raw key hash as bytes. + * + * The key hash does not contain contributions by challenge-response components for + * backwards compatibility with KeePassXC's pre-KDBX4 challenge-response + * implementation. To include challenge-response in the raw key, + * use \link CompositeKey::rawKey(const QByteArray*) instead. + * + * @return key hash + */ QByteArray CompositeKey::rawKey() const +{ + return rawKey(nullptr); +} + +/** + * Get raw key hash as bytes. + * + * Challenge-response key components will use the provided transformSeed + * as a challenge to acquire their key contribution. + * + * @param transformSeed transform seed to challenge or nullptr to exclude challenge-response components + * @return key hash + */ +QByteArray CompositeKey::rawKey(const QByteArray* transformSeed) const { CryptoHash cryptoHash(CryptoHash::Sha256); @@ -81,12 +106,38 @@ QByteArray CompositeKey::rawKey() const cryptoHash.addData(key->rawKey()); } + if (transformSeed) { + QByteArray challengeResult; + challenge(*transformSeed, challengeResult); + cryptoHash.addData(challengeResult); + } + return cryptoHash.result(); } +/** + * Transform this composite key. + * + * If using AES-KDF as transform function, the transformed key will not include + * any challenge-response components. Only static key components will be hashed + * for backwards-compatibility with KeePassXC's KDBX3 implementation, which added + * challenge response key components after key transformation. + * KDBX4+ KDFs transform the whole key including challenge-response components. + * + * @param kdf key derivation function + * @param result transformed key hash + * @return true on success + */ bool CompositeKey::transform(const Kdf& kdf, QByteArray& result) const { - return kdf.transform(rawKey(), result); + if (kdf.uuid() == KeePass2::KDF_AES) { + // legacy KDBX3 AES-KDF, challenge response is added later to the hash + return kdf.transform(rawKey(), result); + } + + QByteArray seed = kdf.seed(); + Q_ASSERT(!seed.isEmpty()); + return kdf.transform(rawKey(&seed), result); } bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const @@ -103,6 +154,7 @@ bool CompositeKey::challenge(const QByteArray& seed, QByteArray& result) const for (const auto key : m_challengeResponseKeys) { // if the device isn't present or fails, return an error if (!key->challenge(seed)) { + qWarning("Failed to issue challenge"); return false; } cryptoHash.addData(key->rawKey()); diff --git a/src/keys/CompositeKey.h b/src/keys/CompositeKey.h index 35bc309fa..9018276c3 100644 --- a/src/keys/CompositeKey.h +++ b/src/keys/CompositeKey.h @@ -32,13 +32,14 @@ class CompositeKey : public Key public: CompositeKey(); CompositeKey(const CompositeKey& key); - ~CompositeKey(); + ~CompositeKey() override; void clear(); bool isEmpty() const; - CompositeKey* clone() const; + CompositeKey* clone() const override; CompositeKey& operator=(const CompositeKey& key); - QByteArray rawKey() const; + QByteArray rawKey() const override; + QByteArray rawKey(const QByteArray* transformSeed) const; bool transform(const Kdf& kdf, QByteArray& result) const Q_REQUIRED_RESULT; bool challenge(const QByteArray& seed, QByteArray &result) const; From c51585c2794b99b0f80cd12f7d556f2d9ed0d6d6 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sun, 7 Jan 2018 22:59:59 +0100 Subject: [PATCH 31/39] Fix type conversion error in older Qt versions --- src/format/KeePass2.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index 88286a051..aa081e7b5 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -65,16 +65,16 @@ QSharedPointer KeePass2::kdfFromParameters(const QVariantMap &p) { QByteArray uuidBytes = p.value(KDFPARAM_UUID).toByteArray(); if (uuidBytes.size() != Uuid::Length) { - return nullptr; + return {}; } QSharedPointer kdf(uuidToKdf(Uuid(uuidBytes))); if (kdf.isNull()) { - return nullptr; + return {}; } if (!kdf->processParameters(p)) { - return nullptr; + return {}; } return kdf; @@ -95,7 +95,7 @@ QSharedPointer KeePass2::uuidToKdf(const Uuid& uuid) } Q_ASSERT_X(false, "uuidToKdf", "Invalid UUID"); - return nullptr; + return {}; } KeePass2::ProtectedStreamAlgo KeePass2::idToProtectedStreamAlgo(quint32 id) From d327c169049c0b56df237e6d8b31812ba0ce9d8b Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 10 Jan 2018 00:49:04 +0100 Subject: [PATCH 32/39] Increase Argon2 warning threshold and set parallelism default value to number of available processors --- src/crypto/kdf/Argon2Kdf.cpp | 3 +-- src/gui/DatabaseSettingsWidget.cpp | 6 +++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/crypto/kdf/Argon2Kdf.cpp b/src/crypto/kdf/Argon2Kdf.cpp index 154f2ffe4..cd8474056 100644 --- a/src/crypto/kdf/Argon2Kdf.cpp +++ b/src/crypto/kdf/Argon2Kdf.cpp @@ -21,7 +21,6 @@ #include #include "format/KeePass2.h" -#include "crypto/CryptoHash.h" /** * KeePass' Argon2 implementation supports all parameters that are defined in the official specification, @@ -34,7 +33,7 @@ Argon2Kdf::Argon2Kdf() : Kdf::Kdf(KeePass2::KDF_ARGON2) , m_version(0x13) , m_memory(1 << 16) - , m_parallelism(2) + , m_parallelism(static_cast(QThread::idealThreadCount())) { m_rounds = 1; } diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index c59f97423..68b0d4ae6 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -23,6 +23,7 @@ #include #include +#include #include "core/Global.h" #include "core/FilePath.h" @@ -118,6 +119,9 @@ void DatabaseSettingsWidget::load(Database* db) kdfChanged(kdfIndex); } + // properly initialize parallelism spin box (may be overwritten by actual KDF values) + m_uiEncryption->parallelismSpinBox->setValue(QThread::idealThreadCount()); + // Setup kdf parameters auto kdf = m_db->kdf(); m_uiEncryption->transformRoundsSpinBox->setValue(kdf->rounds()); @@ -134,7 +138,7 @@ void DatabaseSettingsWidget::save() { // first perform safety check for KDF rounds auto kdf = KeePass2::uuidToKdf(Uuid(m_uiEncryption->kdfComboBox->currentData().toByteArray())); - if (kdf->uuid() == KeePass2::KDF_ARGON2 && m_uiEncryption->transformRoundsSpinBox->value() > 1000) { + if (kdf->uuid() == KeePass2::KDF_ARGON2 && m_uiEncryption->transformRoundsSpinBox->value() > 10000) { QMessageBox warning; warning.setIcon(QMessageBox::Warning); warning.setWindowTitle(tr("Number of rounds too high")); From b04c8c2b6e45610a4c26874025668d9d2b91cf71 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 10 Jan 2018 21:24:53 +0100 Subject: [PATCH 33/39] Explicitly support AES-KDF in KDBX4 and don't convert KDBX4 files with AES-KDF back to KDBX3 when saving --- src/core/Database.cpp | 5 +++- src/crypto/kdf/AesKdf.cpp | 21 +++++++++++------ src/crypto/kdf/AesKdf.h | 1 + src/format/Kdbx4Reader.cpp | 1 + src/format/KeePass2.cpp | 37 ++++++++++++++++++++++-------- src/format/KeePass2.h | 3 ++- src/format/KeePass2Writer.cpp | 11 +++++---- src/gui/DatabaseSettingsWidget.cpp | 3 ++- src/keys/CompositeKey.cpp | 3 ++- 9 files changed, 59 insertions(+), 26 deletions(-) diff --git a/src/core/Database.cpp b/src/core/Database.cpp index a579ed02d..75b91a5c5 100644 --- a/src/core/Database.cpp +++ b/src/core/Database.cpp @@ -44,7 +44,10 @@ Database::Database() { m_data.cipher = KeePass2::CIPHER_AES; m_data.compressionAlgo = CompressionGZip; - m_data.kdf = QSharedPointer::create(); + + // instantiate default AES-KDF with legacy KDBX3 flag set + // KDBX4+ will re-initialize the KDF using parameters read from the KDBX file + m_data.kdf = QSharedPointer::create(true); m_data.kdf->randomizeSeed(); m_data.hasKey = false; diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp index d668652aa..df4392468 100644 --- a/src/crypto/kdf/AesKdf.cpp +++ b/src/crypto/kdf/AesKdf.cpp @@ -23,7 +23,15 @@ #include "crypto/CryptoHash.h" AesKdf::AesKdf() - : Kdf::Kdf(KeePass2::KDF_AES) + : Kdf::Kdf(KeePass2::KDF_AES_KDBX4) +{ +} + +/** + * @param legacyKdbx3 initialize as legacy KDBX3 KDF + */ +AesKdf::AesKdf(bool legacyKdbx3) + : Kdf::Kdf(legacyKdbx3 ? KeePass2::KDF_AES_KDBX3 : KeePass2::KDF_AES_KDBX4) { } @@ -36,17 +44,16 @@ bool AesKdf::processParameters(const QVariantMap &p) } QByteArray seed = p.value(KeePass2::KDFPARAM_AES_SEED).toByteArray(); - if (!setSeed(seed)) { - return false; - } - - return true; + return setSeed(seed); } QVariantMap AesKdf::writeParameters() { QVariantMap p; - p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_AES.toByteArray()); + + // always write old KDBX3 AES-KDF UUID for compatibility with other applications + p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_AES_KDBX3.toByteArray()); + p.insert(KeePass2::KDFPARAM_AES_ROUNDS, rounds()); p.insert(KeePass2::KDFPARAM_AES_SEED, seed()); return p; diff --git a/src/crypto/kdf/AesKdf.h b/src/crypto/kdf/AesKdf.h index 69c15b8af..31ee1fa70 100644 --- a/src/crypto/kdf/AesKdf.h +++ b/src/crypto/kdf/AesKdf.h @@ -24,6 +24,7 @@ class AesKdf: public Kdf { public: AesKdf(); + explicit AesKdf(bool legacyKdbx3); bool processParameters(const QVariantMap& p) override; QVariantMap writeParameters() override; diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index 1cde6d18c..b0545888f 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -22,6 +22,7 @@ #include "core/Group.h" #include "core/Endian.h" #include "crypto/CryptoHash.h" +#include "crypto/kdf/AesKdf.h" #include "format/KeePass2RandomStream.h" #include "format/KdbxXmlReader.h" #include "streams/HmacBlockStream.h" diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index aa081e7b5..9f0832fb0 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -25,7 +25,8 @@ const Uuid KeePass2::CIPHER_AES = Uuid(QByteArray::fromHex("31c1f2e6bf714350be58 const Uuid KeePass2::CIPHER_TWOFISH = Uuid(QByteArray::fromHex("ad68f29f576f4bb9a36ad47af965346c")); const Uuid KeePass2::CIPHER_CHACHA20 = Uuid(QByteArray::fromHex("D6038A2B8B6F4CB5A524339A31DBB59A")); -const Uuid KeePass2::KDF_AES = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA")); +const Uuid KeePass2::KDF_AES_KDBX3 = Uuid(QByteArray::fromHex("C9D9F39A628A4460BF740D08C18A4FEA")); +const Uuid KeePass2::KDF_AES_KDBX4 = Uuid(QByteArray::fromHex("7C02BB8279A74AC0927D114A00648238")); const Uuid KeePass2::KDF_ARGON2 = Uuid(QByteArray::fromHex("EF636DDF8C29444B91F7A9A403E30A0C")); const QByteArray KeePass2::INNER_STREAM_SALSA20_IV("\xE8\x30\x09\x4B\x97\x20\x5D\x2A"); @@ -43,14 +44,16 @@ const QString KeePass2::KDFPARAM_ARGON2_VERSION("V"); const QString KeePass2::KDFPARAM_ARGON2_SECRET("K"); const QString KeePass2::KDFPARAM_ARGON2_ASSOCDATA("A"); -const QList> KeePass2::CIPHERS { - qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")), - qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")), - qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit")) +const QList> KeePass2::CIPHERS{ + qMakePair(KeePass2::CIPHER_AES, QObject::tr("AES: 256-bit")), + qMakePair(KeePass2::CIPHER_TWOFISH, QObject::tr("Twofish: 256-bit")), + qMakePair(KeePass2::CIPHER_CHACHA20, QObject::tr("ChaCha20: 256-bit")) }; -const QList> KeePass2::KDFS { - qMakePair(KeePass2::KDF_AES, QObject::tr("AES-KDF")), - qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2")), + +const QList> KeePass2::KDFS{ + qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2 (recommended)")), + qMakePair(KeePass2::KDF_AES_KDBX4, QObject::tr("AES-KDF (KDBX 4)")), + qMakePair(KeePass2::KDF_AES_KDBX3, QObject::tr("AES-KDF (KDBX 3.1)")) }; QByteArray KeePass2::hmacKey(QByteArray masterSeed, QByteArray transformedMasterKey) { @@ -61,6 +64,12 @@ QByteArray KeePass2::hmacKey(QByteArray masterSeed, QByteArray transformedMaster return hmacKeyHash.result(); } +/** + * Create KDF object from KDBX4+ KDF parameters. + * + * @param p variant map containing parameters + * @return initialized KDF + */ QSharedPointer KeePass2::kdfFromParameters(const QVariantMap &p) { QByteArray uuidBytes = p.value(KDFPARAM_UUID).toByteArray(); @@ -68,7 +77,12 @@ QSharedPointer KeePass2::kdfFromParameters(const QVariantMap &p) return {}; } - QSharedPointer kdf(uuidToKdf(Uuid(uuidBytes))); + Uuid kdfUuid(uuidBytes); + if (kdfUuid == KDF_AES_KDBX3) { + // upgrade to non-legacy AES-KDF, since KDBX3 doesn't have any KDF parameters + kdfUuid = KDF_AES_KDBX4; + } + QSharedPointer kdf = uuidToKdf(kdfUuid); if (kdf.isNull()) { return {}; } @@ -87,7 +101,10 @@ QVariantMap KeePass2::kdfToParameters(QSharedPointer kdf) QSharedPointer KeePass2::uuidToKdf(const Uuid& uuid) { - if (uuid == KDF_AES) { + if (uuid == KDF_AES_KDBX3) { + return QSharedPointer::create(true); + } + if (uuid == KDF_AES_KDBX4) { return QSharedPointer::create(); } if (uuid == KDF_ARGON2) { diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index f7fa0d397..cd53d7132 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -46,7 +46,8 @@ namespace KeePass2 extern const Uuid CIPHER_TWOFISH; extern const Uuid CIPHER_CHACHA20; - extern const Uuid KDF_AES; + extern const Uuid KDF_AES_KDBX3; + extern const Uuid KDF_AES_KDBX4; extern const Uuid KDF_ARGON2; extern const QByteArray INNER_STREAM_SALSA20_IV; diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index 3e32eb96b..67aeec98f 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -18,8 +18,9 @@ #include #include -#include "format/KeePass2Writer.h" #include "core/Database.h" +#include "crypto/kdf/AesKdf.h" +#include "format/KeePass2Writer.h" #include "format/Kdbx3Writer.h" #include "format/Kdbx4Writer.h" @@ -52,12 +53,12 @@ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) { m_errorStr.clear(); // determine KDBX3 vs KDBX4 - if (db->kdf()->uuid() != KeePass2::KDF_AES || db->publicCustomData().size() > 0) { - m_version = KeePass2::FILE_VERSION_4; - m_writer.reset(new Kdbx4Writer()); - } else { + if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 && db->publicCustomData().isEmpty()) { m_version = KeePass2::FILE_VERSION_3; m_writer.reset(new Kdbx3Writer()); + } else { + m_version = KeePass2::FILE_VERSION_4; + m_writer.reset(new Kdbx4Writer()); } return m_writer->writeDatabase(device, db); diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index 68b0d4ae6..c0cc06296 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -151,7 +151,8 @@ void DatabaseSettingsWidget::save() if (warning.clickedButton() != ok) { return; } - } else if (kdf->uuid() == KeePass2::KDF_AES && m_uiEncryption->transformRoundsSpinBox->value() < 100000) { + } else if ((kdf->uuid() == KeePass2::KDF_AES_KDBX3 || kdf->uuid() == KeePass2::KDF_AES_KDBX4) + && m_uiEncryption->transformRoundsSpinBox->value() < 100000) { QMessageBox warning; warning.setIcon(QMessageBox::Warning); warning.setWindowTitle(tr("Number of rounds too low")); diff --git a/src/keys/CompositeKey.cpp b/src/keys/CompositeKey.cpp index 7a9b2cbe0..e5e507c77 100644 --- a/src/keys/CompositeKey.cpp +++ b/src/keys/CompositeKey.cpp @@ -22,6 +22,7 @@ #include #include "core/Global.h" +#include "crypto/kdf/AesKdf.h" #include "crypto/CryptoHash.h" CompositeKey::CompositeKey() @@ -130,7 +131,7 @@ QByteArray CompositeKey::rawKey(const QByteArray* transformSeed) const */ bool CompositeKey::transform(const Kdf& kdf, QByteArray& result) const { - if (kdf.uuid() == KeePass2::KDF_AES) { + if (kdf.uuid() == KeePass2::KDF_AES_KDBX3) { // legacy KDBX3 AES-KDF, challenge response is added later to the hash return kdf.transform(rawKey(), result); } From 6df54cfe8d15891beddf70b0ca8e246103d6d8f9 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 11 Jan 2018 00:20:37 +0100 Subject: [PATCH 34/39] Do not assert KDF UUID and improve error message --- src/format/Kdbx4Reader.cpp | 2 +- src/format/KeePass2.cpp | 3 +-- src/format/KeePass2.h | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp index b0545888f..38063acf1 100644 --- a/src/format/Kdbx4Reader.cpp +++ b/src/format/Kdbx4Reader.cpp @@ -203,7 +203,7 @@ bool Kdbx4Reader::readHeaderField(StoreDataStream& device) QVariantMap kdfParams = readVariantMap(&bufIoDevice); QSharedPointer kdf = KeePass2::kdfFromParameters(kdfParams); if (!kdf) { - raiseError(tr("Invalid KDF parameters")); + raiseError(tr("Unsupported key derivation function (KDF) or invalid parameters")); return false; } m_db->setKdf(kdf); diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index 9f0832fb0..d58e8bbfd 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -70,7 +70,7 @@ QByteArray KeePass2::hmacKey(QByteArray masterSeed, QByteArray transformedMaster * @param p variant map containing parameters * @return initialized KDF */ -QSharedPointer KeePass2::kdfFromParameters(const QVariantMap &p) +QSharedPointer KeePass2::kdfFromParameters(const QVariantMap& p) { QByteArray uuidBytes = p.value(KDFPARAM_UUID).toByteArray(); if (uuidBytes.size() != Uuid::Length) { @@ -111,7 +111,6 @@ QSharedPointer KeePass2::uuidToKdf(const Uuid& uuid) return QSharedPointer::create(); } - Q_ASSERT_X(false, "uuidToKdf", "Invalid UUID"); return {}; } diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index cd53d7132..c376ecdf2 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -122,7 +122,7 @@ namespace KeePass2 }; QByteArray hmacKey(QByteArray masterSeed, QByteArray transformedMasterKey); - QSharedPointer kdfFromParameters(const QVariantMap &p); + QSharedPointer kdfFromParameters(const QVariantMap& p); QVariantMap kdfToParameters(QSharedPointer kdf); QSharedPointer uuidToKdf(const Uuid& uuid); Uuid kdfToUuid(QSharedPointer kdf); From 337a21f6d6bc022e4a8928a96c941f910ffcc797 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Thu, 11 Jan 2018 00:35:39 +0100 Subject: [PATCH 35/39] Save AES-KDF round parameter as quint64 --- src/crypto/kdf/AesKdf.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/crypto/kdf/AesKdf.cpp b/src/crypto/kdf/AesKdf.cpp index df4392468..593b01c24 100644 --- a/src/crypto/kdf/AesKdf.cpp +++ b/src/crypto/kdf/AesKdf.cpp @@ -54,7 +54,7 @@ QVariantMap AesKdf::writeParameters() // always write old KDBX3 AES-KDF UUID for compatibility with other applications p.insert(KeePass2::KDFPARAM_UUID, KeePass2::KDF_AES_KDBX3.toByteArray()); - p.insert(KeePass2::KDFPARAM_AES_ROUNDS, rounds()); + p.insert(KeePass2::KDFPARAM_AES_ROUNDS, static_cast(rounds())); p.insert(KeePass2::KDFPARAM_AES_SEED, seed()); return p; } From 824607080a5fda83e6d667b076f99a607b09721d Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 13 Jan 2018 01:38:26 +0100 Subject: [PATCH 36/39] Install needed PPA packages for deployment --- AppImage-Recipe.sh | 2 ++ Dockerfile | 22 ++++++++++++++-------- ci/trusty/Dockerfile | 32 +++++++++++++++++++++++--------- cmake/FindLibGPGError.cmake | 2 +- src/format/KdbxXmlReader.cpp | 2 +- 5 files changed, 41 insertions(+), 19 deletions(-) diff --git a/AppImage-Recipe.sh b/AppImage-Recipe.sh index f8d7e105f..2187fde1f 100755 --- a/AppImage-Recipe.sh +++ b/AppImage-Recipe.sh @@ -72,6 +72,8 @@ get_desktop get_icon cat << EOF > ./usr/bin/keepassxc_env #!/usr/bin/env bash +export LD_LIBRARY_PATH="/opt/libgcrypt20-18/lib/x86_64-linux-gnu:\${LD_LIBRARY_PATH}" +export LD_LIBRARY_PATH="/opt/gpg-error-127/lib/x86_64-linux-gnu:\${LD_LIBRARY_PATH}" export LD_LIBRARY_PATH="..$(dirname ${QT_PLUGIN_PATH})/lib:\${LD_LIBRARY_PATH}" export QT_PLUGIN_PATH="..${QT_PLUGIN_PATH}:\${KPXC_QT_PLUGIN_PATH}" diff --git a/Dockerfile b/Dockerfile index 20d7ff352..4ceaf9968 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,6 +16,8 @@ FROM ubuntu:14.04 +ENV REBUILD_COUNTER=1 + ENV QT5_VERSION=59 ENV QT5_PPA_VERSION=${QT5_VERSION}2 @@ -25,8 +27,7 @@ RUN set -x \ RUN set -x \ && add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \ - && add-apt-repository ppa:phoerious/keepassxc \ - && LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php + && add-apt-repository ppa:phoerious/keepassxc RUN set -x \ && apt-get update -y \ @@ -37,7 +38,9 @@ RUN set -x \ && apt-get install -y \ cmake3 \ g++ \ - libgcrypt20-dev \ + libgcrypt20-18-dev \ + libargon2-0-dev \ + libsodium-dev \ qt${QT5_VERSION}base \ qt${QT5_VERSION}tools \ qt${QT5_VERSION}x11extras \ @@ -47,13 +50,16 @@ RUN set -x \ libxtst-dev \ mesa-common-dev \ libyubikey-dev \ - libykpers-1-dev \ - libsodium-dev + libykpers-1-dev -ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake -ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib +ENV CMAKE_PREFIX_PATH="/opt/qt${QT5_VERSION}/lib/cmake" +ENV CMAKE_INCLUDE_PATH="/opt/libgcrypt20-18/include:/opt/gpg-error-127/include" +ENV CMAKE_LIBRARY_PATH="/opt/libgcrypt20-18/lib/x86_64-linux-gnu:/opt/gpg-error-127/lib/x86_64-linux-gnu" +ENV LD_LIBRARY_PATH="/opt/qt${QT5_VERSION}/lib:/opt/libgcrypt20-18/lib/x86_64-linux-gnu:/opt/gpg-error-127/lib/x86_64-linux-gnu" RUN set -x \ - && echo /opt/qt${QT_VERSION}/lib > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf + && echo "/opt/qt${QT_VERSION}/lib" > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf \ + && echo "/opt/libgcrypt20-18/lib/x86_64-linux-gnu" > /etc/ld.so.conf.d/libgcrypt20-18.conf \ + && echo "/opt/gpg-error-127/lib/x86_64-linux-gnu" > /etc/ld.so.conf.d/libgpg-error-127.conf # AppImage dependencies RUN set -x \ diff --git a/ci/trusty/Dockerfile b/ci/trusty/Dockerfile index bd6bec1d2..d8911f2af 100644 --- a/ci/trusty/Dockerfile +++ b/ci/trusty/Dockerfile @@ -18,6 +18,8 @@ FROM ubuntu:14.04 +ENV REBUILD_COUNTER=1 + ENV QT5_VERSION=53 ENV QT5_PPA_VERSION=${QT5_VERSION}2 @@ -27,27 +29,39 @@ RUN set -x \ RUN set -x \ && add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \ - && LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php + && add-apt-repository ppa:phoerious/keepassxc RUN set -x \ && apt-get -y update \ && apt-get -y --no-install-recommends install \ - git build-essential clang-3.6 libclang-common-3.6-dev clang-format-3.6 cmake3 make \ - curl ca-certificates gnupg2 \ - libgcrypt20-dev zlib1g-dev libyubikey-dev libykpers-1-dev \ + build-essential \ + clang-3.6 \ + libclang-common-3.6-dev \ + clang-format-3.6 \ + cmake3 \ + make \ + libgcrypt20-18-dev \ + libargon2-0-dev \ + libsodium-dev \ qt${QT5_VERSION}base \ qt${QT5_VERSION}tools \ qt${QT5_VERSION}x11extras \ qt${QT5_VERSION}translations \ + zlib1g-dev \ + libyubikey-dev \ + libykpers-1-dev \ libxi-dev \ libxtst-dev \ - xvfb \ - libsodium-dev + xvfb -ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake -ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib +ENV CMAKE_PREFIX_PATH="/opt/qt${QT5_VERSION}/lib/cmake" +ENV CMAKE_INCLUDE_PATH="/opt/libgcrypt20-18/include:/opt/gpg-error-127/include" +ENV CMAKE_LIBRARY_PATH="/opt/libgcrypt20-18/lib/x86_64-linux-gnu:/opt/gpg-error-127/lib/x86_64-linux-gnu" +ENV LD_LIBRARY_PATH="/opt/qt${QT5_VERSION}/lib:/opt/libgcrypt20-18/lib/x86_64-linux-gnu:/opt/gpg-error-127/lib/x86_64-linux-gnu" RUN set -x \ - && echo /opt/qt${QT_VERSION}/lib > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf + && echo "/opt/qt${QT_VERSION}/lib" > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf \ + && echo "/opt/libgcrypt20-18/lib/x86_64-linux-gnu" > /etc/ld.so.conf.d/libgcrypt20-18.conf \ + && echo "/opt/gpg-error-127/lib/x86_64-linux-gnu" > /etc/ld.so.conf.d/libgpg-error-127.conf RUN set -x \ && apt-get autoremove --purge \ diff --git a/cmake/FindLibGPGError.cmake b/cmake/FindLibGPGError.cmake index c1e1b8686..9a18371ba 100644 --- a/cmake/FindLibGPGError.cmake +++ b/cmake/FindLibGPGError.cmake @@ -14,10 +14,10 @@ # along with this program. If not, see . find_path(GPGERROR_INCLUDE_DIR gpg-error.h) - find_library(GPGERROR_LIBRARIES gpg-error) mark_as_advanced(GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR) include(FindPackageHandleStandardArgs) +include_directories(${GPGERROR_INCLUDE_DIR}) find_package_handle_standard_args(LibGPGError DEFAULT_MSG GPGERROR_LIBRARIES GPGERROR_INCLUDE_DIR) diff --git a/src/format/KdbxXmlReader.cpp b/src/format/KdbxXmlReader.cpp index be692abe3..774bb00c7 100644 --- a/src/format/KdbxXmlReader.cpp +++ b/src/format/KdbxXmlReader.cpp @@ -629,7 +629,7 @@ void KdbxXmlReader::parseDeletedObject() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject"); - DeletedObject delObj{}; + DeletedObject delObj{{}, {}}; while (!m_xml.hasError() && m_xml.readNextStartElement()) { if (m_xml.name() == "UUID") { From 90380adc901536ddbc0bf6d4b4dea3eff93b414f Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 13 Jan 2018 02:47:02 +0100 Subject: [PATCH 37/39] Fix test compilation errors * Use legacy AES-KDF mode for KeePass1Reader --- src/format/KeePass1Reader.cpp | 13 ++++++------- tests/TestKeePass1Reader.cpp | 2 +- tests/gui/TestGui.cpp | 2 +- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/src/format/KeePass1Reader.cpp b/src/format/KeePass1Reader.cpp index c6e2ed03c..20f9ff232 100644 --- a/src/format/KeePass1Reader.cpp +++ b/src/format/KeePass1Reader.cpp @@ -30,7 +30,6 @@ #include "core/Tools.h" #include "crypto/CryptoHash.h" #include "format/KeePass1.h" -#include "keys/CompositeKey.h" #include "keys/FileKey.h" #include "keys/PasswordKey.h" #include "streams/SymmetricCipherStream.h" @@ -94,13 +93,13 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor bool ok; - quint32 signature1 = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); + auto signature1 = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok || signature1 != KeePass1::SIGNATURE_1) { raiseError(tr("Not a KeePass database.")); return nullptr; } - quint32 signature2 = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); + auto signature2 = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok || signature2 != KeePass1::SIGNATURE_2) { raiseError(tr("Not a KeePass database.")); return nullptr; @@ -112,7 +111,7 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor return nullptr; } - quint32 version = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); + auto version = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok || (version & KeePass1::FILE_VERSION_CRITICAL_MASK) != (KeePass1::FILE_VERSION & KeePass1::FILE_VERSION_CRITICAL_MASK)) { raiseError(tr("Unsupported KeePass database version.")); @@ -131,13 +130,13 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor return nullptr; } - quint32 numGroups = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); + auto numGroups = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok) { raiseError("Invalid number of groups"); return nullptr; } - quint32 numEntries = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); + auto numEntries = Endian::readSizedInt(m_device, KeePass1::BYTEORDER, &ok); if (!ok) { raiseError("Invalid number of entries"); return nullptr; @@ -160,7 +159,7 @@ Database* KeePass1Reader::readDatabase(QIODevice* device, const QString& passwor raiseError("Invalid number of transform rounds"); return nullptr; } - auto kdf = QSharedPointer::create(); + auto kdf = QSharedPointer::create(true); kdf->setRounds(m_transformRounds); kdf->setSeed(m_transformSeed); db->setKdf(kdf); diff --git a/tests/TestKeePass1Reader.cpp b/tests/TestKeePass1Reader.cpp index c372c8715..a4ad56ca6 100644 --- a/tests/TestKeePass1Reader.cpp +++ b/tests/TestKeePass1Reader.cpp @@ -110,7 +110,7 @@ void TestKeePass1Reader::testBasic() void TestKeePass1Reader::testMasterKey() { QVERIFY(m_db->hasKey()); - QCOMPARE(m_db->kdf()->rounds(), static_cast(713)); + QCOMPARE(m_db->kdf()->rounds(), 713); } void TestKeePass1Reader::testCustomIcons() diff --git a/tests/gui/TestGui.cpp b/tests/gui/TestGui.cpp index d591165c4..d1a251069 100644 --- a/tests/gui/TestGui.cpp +++ b/tests/gui/TestGui.cpp @@ -911,7 +911,7 @@ void TestGui::testDatabaseSettings() QTest::keyClick(transformRoundsSpinBox, Qt::Key_Enter); // wait for modified timer QTRY_COMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save*")); - QCOMPARE(m_db->kdf()->rounds(), Q_UINT64_C(123456)); + QCOMPARE(m_db->kdf()->rounds(), 123456); triggerAction("actionDatabaseSave"); QCOMPARE(m_tabWidget->tabText(m_tabWidget->currentIndex()), QString("Save")); From 96e271e8bee1255a84b620fec3328de93189db28 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 13 Jan 2018 15:00:15 +0100 Subject: [PATCH 38/39] Force Docker rebuild to fetch fixed version of libgpg-error-127 --- Dockerfile | 2 +- ci/trusty/Dockerfile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dockerfile b/Dockerfile index 4ceaf9968..69db2ac1f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -16,7 +16,7 @@ FROM ubuntu:14.04 -ENV REBUILD_COUNTER=1 +ENV REBUILD_COUNTER=2 ENV QT5_VERSION=59 ENV QT5_PPA_VERSION=${QT5_VERSION}2 diff --git a/ci/trusty/Dockerfile b/ci/trusty/Dockerfile index d8911f2af..cdaba3a07 100644 --- a/ci/trusty/Dockerfile +++ b/ci/trusty/Dockerfile @@ -18,7 +18,7 @@ FROM ubuntu:14.04 -ENV REBUILD_COUNTER=1 +ENV REBUILD_COUNTER=2 ENV QT5_VERSION=53 ENV QT5_PPA_VERSION=${QT5_VERSION}2 From 157508858526f960a0d9427f82e09ed2d4c429d5 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Sat, 13 Jan 2018 22:45:10 +0100 Subject: [PATCH 39/39] Add 'KDBX 4' to Argon2 KDF name and select first category in the database settings by default --- src/format/KeePass2.cpp | 2 +- src/gui/DatabaseSettingsWidget.cpp | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/format/KeePass2.cpp b/src/format/KeePass2.cpp index d58e8bbfd..30fb304c7 100644 --- a/src/format/KeePass2.cpp +++ b/src/format/KeePass2.cpp @@ -51,7 +51,7 @@ const QList> KeePass2::CIPHERS{ }; const QList> KeePass2::KDFS{ - qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2 (recommended)")), + qMakePair(KeePass2::KDF_ARGON2, QObject::tr("Argon2 (KDBX 4 – recommended)")), qMakePair(KeePass2::KDF_AES_KDBX4, QObject::tr("AES-KDF (KDBX 4)")), qMakePair(KeePass2::KDF_AES_KDBX3, QObject::tr("AES-KDF (KDBX 3.1)")) }; diff --git a/src/gui/DatabaseSettingsWidget.cpp b/src/gui/DatabaseSettingsWidget.cpp index c0cc06296..d145a2381 100644 --- a/src/gui/DatabaseSettingsWidget.cpp +++ b/src/gui/DatabaseSettingsWidget.cpp @@ -132,6 +132,7 @@ void DatabaseSettingsWidget::load(Database* db) } m_uiGeneral->dbNameEdit->setFocus(); + m_ui->categoryList->setCurrentCategory(0); } void DatabaseSettingsWidget::save()