From a595239624dd91f40074747c0ff5995d6bafb4e3 Mon Sep 17 00:00:00 2001 From: Janek Bevendorff Date: Wed, 17 Jan 2018 20:52:29 +0100 Subject: [PATCH] Refactor and extend file format tests --- src/format/Kdbx3Reader.cpp | 6 +- src/format/Kdbx3Writer.cpp | 4 +- src/format/KeePass2.h | 177 +++++----- src/format/KeePass2Repair.cpp | 2 +- src/format/KeePass2Writer.cpp | 2 +- tests/CMakeLists.txt | 13 +- tests/TestDeletedObjects.cpp | 2 +- tests/TestKdbx2.cpp | 68 ++++ tests/{TestKeePass2Reader.h => TestKdbx2.h} | 18 +- tests/TestKdbx3.cpp | 171 ++++++++- tests/TestKdbx3.h | 26 +- tests/TestKdbx4.cpp | 153 +++++++- tests/TestKdbx4.h | 22 +- ...s2XmlReader.cpp => TestKeePass2Format.cpp} | 330 ++++++++++++------ tests/TestKeePass2Format.h | 91 +++++ tests/TestKeePass2Reader.cpp | 180 ---------- tests/TestKeePass2Writer.cpp | 168 --------- tests/TestKeePass2Writer.h | 44 --- tests/TestKeePass2XmlReader.h | 64 ---- 19 files changed, 826 insertions(+), 715 deletions(-) create mode 100644 tests/TestKdbx2.cpp rename tests/{TestKeePass2Reader.h => TestKdbx2.h} (65%) rename tests/{TestKeePass2XmlReader.cpp => TestKeePass2Format.cpp} (67%) create mode 100644 tests/TestKeePass2Format.h delete mode 100644 tests/TestKeePass2Reader.cpp delete mode 100644 tests/TestKeePass2Writer.cpp delete mode 100644 tests/TestKeePass2Writer.h delete mode 100644 tests/TestKeePass2XmlReader.h diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp index 638f84063..84f7db67e 100644 --- a/src/format/Kdbx3Reader.cpp +++ b/src/format/Kdbx3Reader.cpp @@ -32,7 +32,7 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData, const CompositeKey& key, bool keepDatabase) { - Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3); + Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3_1); if (hasError()) { return nullptr; @@ -118,7 +118,7 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& hea Q_ASSERT(xmlDevice); - KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3); + KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3_1); xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream); if (xmlReader.hasError()) { @@ -129,7 +129,7 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& hea return nullptr; } - Q_ASSERT(!xmlReader.headerHash().isEmpty() || m_kdbxVersion < KeePass2::FILE_VERSION_3); + Q_ASSERT(!xmlReader.headerHash().isEmpty() || m_kdbxVersion < KeePass2::FILE_VERSION_3_1); if (!xmlReader.headerHash().isEmpty()) { QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256); diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp index b0b44c6b2..c2fefff1b 100644 --- a/src/format/Kdbx3Writer.cpp +++ b/src/format/Kdbx3Writer.cpp @@ -63,7 +63,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) QBuffer header; header.open(QIODevice::WriteOnly); - writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_3); + writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_3_1); CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray())); CHECK_RETURN_FALSE(writeHeaderField(&header, KeePass2::HeaderFieldID::CompressionFlags, @@ -131,7 +131,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db) return false; } - KdbxXmlWriter xmlWriter(KeePass2::FILE_VERSION_3); + KdbxXmlWriter xmlWriter(KeePass2::FILE_VERSION_3_1); xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash); // Explicitly close/reset streams so they are flushed and we can detect diff --git a/src/format/KeePass2.h b/src/format/KeePass2.h index c376ecdf2..67779121f 100644 --- a/src/format/KeePass2.h +++ b/src/format/KeePass2.h @@ -22,6 +22,7 @@ #include #include #include +#include #include "crypto/SymmetricCipher.h" #include "crypto/kdf/Kdf.h" @@ -29,104 +30,108 @@ namespace KeePass2 { - const quint32 SIGNATURE_1 = 0x9AA2D903; - const quint32 SIGNATURE_2 = 0xB54BFB67; - 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; +constexpr quint32 SIGNATURE_1 = 0x9AA2D903; +constexpr quint32 SIGNATURE_2 = 0xB54BFB67; - const quint16 VARIANTMAP_VERSION = 0x0100; - const quint16 VARIANTMAP_CRITICAL_MASK = 0xFF00; +constexpr quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000; +constexpr quint32 FILE_VERSION_4 = 0x00040000; +constexpr quint32 FILE_VERSION_3_1 = 0x00030001; +constexpr quint32 FILE_VERSION_3 = 0x00030000; +constexpr quint32 FILE_VERSION_2 = 0x00020000; +constexpr quint32 FILE_VERSION_MIN = FILE_VERSION_2; - const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian; +constexpr quint16 VARIANTMAP_VERSION = 0x0100; +constexpr quint16 VARIANTMAP_CRITICAL_MASK = 0xFF00; - extern const Uuid CIPHER_AES; - extern const Uuid CIPHER_TWOFISH; - extern const Uuid CIPHER_CHACHA20; +const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian; - extern const Uuid KDF_AES_KDBX3; - extern const Uuid KDF_AES_KDBX4; - extern const Uuid KDF_ARGON2; +extern const Uuid CIPHER_AES; +extern const Uuid CIPHER_TWOFISH; +extern const Uuid CIPHER_CHACHA20; - extern const QByteArray INNER_STREAM_SALSA20_IV; +extern const Uuid KDF_AES_KDBX3; +extern const Uuid KDF_AES_KDBX4; +extern const Uuid KDF_ARGON2; - 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 QByteArray INNER_STREAM_SALSA20_IV; - extern const QList> CIPHERS; - extern const QList> KDFS; +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; - enum class HeaderFieldID - { - EndOfHeader = 0, - Comment = 1, - CipherID = 2, - CompressionFlags = 3, - MasterSeed = 4, - TransformSeed = 5, - TransformRounds = 6, - EncryptionIV = 7, - ProtectedStreamKey = 8, - StreamStartBytes = 9, - InnerRandomStreamID = 10, - KdfParameters = 11, - PublicCustomData = 12 - }; +extern const QList> CIPHERS; +extern const QList> KDFS; - enum class InnerHeaderFieldID : quint8 - { - End = 0, - InnerRandomStreamID = 1, - InnerRandomStreamKey = 2, - Binary = 3 - }; +enum class HeaderFieldID +{ + EndOfHeader = 0, + Comment = 1, + CipherID = 2, + CompressionFlags = 3, + MasterSeed = 4, + TransformSeed = 5, + TransformRounds = 6, + EncryptionIV = 7, + ProtectedStreamKey = 8, + StreamStartBytes = 9, + InnerRandomStreamID = 10, + KdfParameters = 11, + PublicCustomData = 12 +}; - enum class ProtectedStreamAlgo - { - ArcFourVariant = 1, - Salsa20 = 2, - ChaCha20 = 3, - InvalidProtectedStreamAlgo = -1 - }; +enum class InnerHeaderFieldID : quint8 +{ + End = 0, + InnerRandomStreamID = 1, + InnerRandomStreamKey = 2, + Binary = 3 +}; - 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 - }; +enum class ProtectedStreamAlgo +{ + ArcFourVariant = 1, + Salsa20 = 2, + ChaCha20 = 3, + InvalidProtectedStreamAlgo = -1 +}; - 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); -} +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); + +} // namespace KeePass2 #endif // KEEPASSX_KEEPASS2_H diff --git a/src/format/KeePass2Repair.cpp b/src/format/KeePass2Repair.cpp index e2af16cea..47ed59dc9 100644 --- a/src/format/KeePass2Repair.cpp +++ b/src/format/KeePass2Repair.cpp @@ -79,7 +79,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, QBuffer buffer(&xmlData); buffer.open(QIODevice::ReadOnly); if ((reader.version() & KeePass2::FILE_VERSION_CRITICAL_MASK) < KeePass2::FILE_VERSION_4) { - KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3); + KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3_1); xmlReader.readDatabase(&buffer, db.data(), &randomStream); hasError = xmlReader.hasError(); } else { diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index 67aeec98f..7be3178ba 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -54,7 +54,7 @@ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) { // determine KDBX3 vs KDBX4 if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 && db->publicCustomData().isEmpty()) { - m_version = KeePass2::FILE_VERSION_3; + m_version = KeePass2::FILE_VERSION_3_1; m_writer.reset(new Kdbx3Writer()); } else { m_version = KeePass2::FILE_VERSION_4; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 1e7512268..5e6043609 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -107,21 +107,18 @@ endif() add_unit_test(NAME testgroup SOURCES TestGroup.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testkdbx3 SOURCES TestKeePass2XmlReader.cpp TestKdbx3.cpp +add_unit_test(NAME testkdbx2 SOURCES TestKdbx2.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testkdbx4 SOURCES TestKeePass2XmlReader.cpp TestKdbx4.cpp +add_unit_test(NAME testkdbx3 SOURCES TestKeePass2Format.cpp FailDevice.cpp TestKdbx3.cpp + LIBS ${TEST_LIBRARIES}) + +add_unit_test(NAME testkdbx4 SOURCES TestKeePass2Format.cpp FailDevice.cpp TestKdbx4.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testkeys SOURCES TestKeys.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testkeepass2reader SOURCES TestKeePass2Reader.cpp - LIBS ${TEST_LIBRARIES}) - -add_unit_test(NAME testkeepass2writer SOURCES TestKeePass2Writer.cpp - LIBS testsupport ${TEST_LIBRARIES}) - add_unit_test(NAME testgroupmodel SOURCES TestGroupModel.cpp LIBS testsupport ${TEST_LIBRARIES}) diff --git a/tests/TestDeletedObjects.cpp b/tests/TestDeletedObjects.cpp index c8236f05b..63dab0edf 100644 --- a/tests/TestDeletedObjects.cpp +++ b/tests/TestDeletedObjects.cpp @@ -89,7 +89,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize) void TestDeletedObjects::testDeletedObjectsFromFile() { - KdbxXmlReader reader(KeePass2::FILE_VERSION_3); + KdbxXmlReader reader(KeePass2::FILE_VERSION_3_1); reader.setStrictMode(true); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); Database* db = reader.readDatabase(xmlFile); diff --git a/tests/TestKdbx2.cpp b/tests/TestKdbx2.cpp new file mode 100644 index 000000000..8424b56e4 --- /dev/null +++ b/tests/TestKdbx2.cpp @@ -0,0 +1,68 @@ +/* + * 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 "TestKdbx2.h" +#include "crypto/Crypto.h" +#include "keys/CompositeKey.h" +#include "keys/PasswordKey.h" +#include "format/KeePass2Reader.h" +#include "core/Entry.h" +#include "core/Group.h" +#include "core/Metadata.h" +#include "config-keepassx-tests.h" + +#include + +QTEST_GUILESS_MAIN(TestKdbx2) + +void TestKdbx2::initTestCase() +{ + QVERIFY(Crypto::init()); +} + +void TestKdbx2::testFormat200() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format200.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("a")); + KeePass2Reader reader; + QScopedPointer db(reader.readDatabase(filename, key)); + QCOMPARE(reader.version(), KeePass2::FILE_VERSION_2); + QVERIFY(db.data()); + QVERIFY(!reader.hasError()); + + QCOMPARE(db->rootGroup()->name(), QString("Format200")); + QVERIFY(!db->metadata()->protectTitle()); + QVERIFY(db->metadata()->protectUsername()); + QVERIFY(!db->metadata()->protectPassword()); + QVERIFY(db->metadata()->protectUrl()); + QVERIFY(!db->metadata()->protectNotes()); + + QCOMPARE(db->rootGroup()->entries().size(), 1); + auto entry = db->rootGroup()->entries().at(0); + + QCOMPARE(entry->title(), QString("Sample Entry")); + QCOMPARE(entry->username(), QString("User Name")); + QCOMPARE(entry->attachments()->keys().size(), 2); + QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("abcdefghijk")); + QCOMPARE(entry->attachments()->value("test.txt"), QByteArray("this is a test")); + + QCOMPARE(entry->historyItems().size(), 2); + QCOMPARE(entry->historyItems().at(0)->attachments()->keys().size(), 0); + QCOMPARE(entry->historyItems().at(1)->attachments()->keys().size(), 1); + QCOMPARE(entry->historyItems().at(1)->attachments()->value("myattach.txt"), QByteArray("abcdefghijk")); +} diff --git a/tests/TestKeePass2Reader.h b/tests/TestKdbx2.h similarity index 65% rename from tests/TestKeePass2Reader.h rename to tests/TestKdbx2.h index 6ba9b0dc1..f72672021 100644 --- a/tests/TestKeePass2Reader.h +++ b/tests/TestKdbx2.h @@ -1,5 +1,5 @@ /* - * 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 @@ -15,24 +15,18 @@ * along with this program. If not, see . */ -#ifndef KEEPASSX_TESTKEEPASS2READER_H -#define KEEPASSX_TESTKEEPASS2READER_H +#ifndef KEEPASSXC_TEST_KDBX2_H +#define KEEPASSXC_TEST_KDBX2_H #include -class TestKeePass2Reader : public QObject +class TestKdbx2 : public QObject { - Q_OBJECT +Q_OBJECT private slots: void initTestCase(); - void testNonAscii(); - void testCompressed(); - void testProtectedStrings(); - void testBrokenHeaderHash(); void testFormat200(); - void testFormat300(); - void testFormat400(); }; -#endif // KEEPASSX_TESTKEEPASS2READER_H +#endif // KEEPASSXC_TEST_KDBX2_H diff --git a/tests/TestKdbx3.cpp b/tests/TestKdbx3.cpp index 9bb5d8b54..210ddc752 100644 --- a/tests/TestKdbx3.cpp +++ b/tests/TestKdbx3.cpp @@ -17,31 +17,27 @@ #include "TestKdbx3.h" #include "core/Metadata.h" -#include "crypto/Crypto.h" +#include "keys/PasswordKey.h" #include "format/KeePass2.h" +#include "format/KeePass2Reader.h" +#include "format/KeePass2Writer.h" #include "format/KdbxXmlReader.h" #include "format/KdbxXmlWriter.h" +#include "format/KeePass2Repair.h" #include "config-keepassx-tests.h" #include QTEST_GUILESS_MAIN(TestKdbx3) -void TestKdbx3::initTestCase() +void TestKdbx3::initTestCaseImpl() { - QVERIFY(Crypto::init()); - KdbxXmlReader reader(KeePass2::FILE_VERSION_3); - reader.setStrictMode(true); - QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); - m_db.reset(reader.readDatabase(xmlFile)); - QVERIFY(m_db.data()); - QVERIFY(!reader.hasError()); } -Database* TestKdbx3::readDatabase(QString path, bool strictMode, bool& hasError, QString& errorString) +Database* TestKdbx3::readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) { - KdbxXmlReader reader(KeePass2::FILE_VERSION_3); + KdbxXmlReader reader(KeePass2::FILE_VERSION_3_1); reader.setStrictMode(strictMode); auto db = reader.readDatabase(path); hasError = reader.hasError(); @@ -49,9 +45,9 @@ Database* TestKdbx3::readDatabase(QString path, bool strictMode, bool& hasError, return db; } -Database* TestKdbx3::readDatabase(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) +Database* TestKdbx3::readXml(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) { - KdbxXmlReader reader(KeePass2::FILE_VERSION_3); + KdbxXmlReader reader(KeePass2::FILE_VERSION_3_1); reader.setStrictMode(strictMode); auto db = reader.readDatabase(buf); hasError = reader.hasError(); @@ -59,10 +55,155 @@ Database* TestKdbx3::readDatabase(QBuffer* buf, bool strictMode, bool& hasError, return db; } -void TestKdbx3::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) +void TestKdbx3::writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) { - KdbxXmlWriter writer(KeePass2::FILE_VERSION_3); + KdbxXmlWriter writer(KeePass2::FILE_VERSION_3_1); writer.writeDatabase(buf, db); hasError = writer.hasError(); errorString = writer.errorString(); } + +void TestKdbx3::readKdbx(QIODevice* device, CompositeKey const& key, QScopedPointer& db, + bool& hasError, QString& errorString) +{ + KeePass2Reader reader; + db.reset(reader.readDatabase(device, key)); + hasError = reader.hasError(); + if (hasError) { + errorString = reader.errorString(); + } + QCOMPARE(reader.version(), KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK); +} + +void TestKdbx3::readKdbx(const QString& path, CompositeKey const& key, QScopedPointer& db, + bool& hasError, QString& errorString) +{ + KeePass2Reader reader; + db.reset(reader.readDatabase(path, key)); + hasError = reader.hasError(); + if (hasError) { + errorString = reader.errorString(); + } + QCOMPARE(reader.version(), KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK); +} + +void TestKdbx3::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) +{ + KeePass2Writer writer; + hasError = writer.writeDatabase(device, db); + hasError = writer.hasError(); + if (hasError) { + errorString = writer.errorString(); + } + QCOMPARE(writer.version(), KeePass2::FILE_VERSION_3_1); +} + +void TestKdbx3::testFormat300() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format300.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("a")); + KeePass2Reader reader; + QScopedPointer db(reader.readDatabase(filename, key)); + QCOMPARE(reader.version(), KeePass2::FILE_VERSION_3); + QVERIFY(db.data()); + QVERIFY(!reader.hasError()); + + QCOMPARE(db->rootGroup()->name(), QString("Format300")); + QCOMPARE(db->metadata()->name(), QString("Test Database Format 0x00030000")); +} + +void TestKdbx3::testNonAscii() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/NonAscii.kdbx"); + CompositeKey key; + key.addKey(PasswordKey(QString::fromUtf8("\xce\x94\xc3\xb6\xd8\xb6"))); + KeePass2Reader reader; + QScopedPointer db(reader.readDatabase(filename, key)); + QVERIFY(db.data()); + QVERIFY(!reader.hasError()); + QCOMPARE(db->metadata()->name(), QString("NonAsciiTest")); + QCOMPARE(db->compressionAlgo(), Database::CompressionNone); +} + +void TestKdbx3::testCompressed() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Compressed.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("")); + KeePass2Reader reader; + QScopedPointer db(reader.readDatabase(filename, key)); + QVERIFY(db.data()); + QVERIFY(!reader.hasError()); + QCOMPARE(db->metadata()->name(), QString("Compressed")); + QCOMPARE(db->compressionAlgo(), Database::CompressionGZip); +} + +void TestKdbx3::testProtectedStrings() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/ProtectedStrings.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("masterpw")); + KeePass2Reader reader; + QScopedPointer db(reader.readDatabase(filename, key)); + QVERIFY(db.data()); + QVERIFY(!reader.hasError()); + QCOMPARE(db->metadata()->name(), QString("Protected Strings Test")); + + Entry* entry = db->rootGroup()->entries().at(0); + + QCOMPARE(entry->title(), QString("Sample Entry")); + QCOMPARE(entry->username(), QString("Protected User Name")); + QCOMPARE(entry->password(), QString("ProtectedPassword")); + QCOMPARE(entry->attributes()->value("TestProtected"), QString("ABC")); + QCOMPARE(entry->attributes()->value("TestUnprotected"), QString("DEF")); + + QVERIFY(db->metadata()->protectPassword()); + QVERIFY(entry->attributes()->isProtected("TestProtected")); + QVERIFY(!entry->attributes()->isProtected("TestUnprotected")); +} + +void TestKdbx3::testBrokenHeaderHash() +{ + // The protected stream key has been modified in the header. + // Make sure the database won't open. + + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/BrokenHeaderHash.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("")); + KeePass2Reader reader; + QScopedPointer db(reader.readDatabase(filename, key)); + QVERIFY(!db.data()); + QVERIFY(reader.hasError()); +} + +void TestKdbx3::testKdbxRepair() +{ + QString brokenDbFilename = QString(KEEPASSX_TEST_DATA_DIR).append("/bug392.kdbx"); + // master password = test + // entry username: testuser\x10\x20AC + // entry password: testpw + CompositeKey key; + key.addKey(PasswordKey("test")); + + // test that we can't open the broken database + bool hasError; + QString errorString; + QScopedPointer dbBroken; + readKdbx(brokenDbFilename, key, dbBroken, hasError, errorString); + QVERIFY(!dbBroken.data()); + QVERIFY(hasError); + + // test if we can repair the database + KeePass2Repair repair; + QFile file(brokenDbFilename); + file.open(QIODevice::ReadOnly); + auto result = repair.repairDatabase(&file, key); + QCOMPARE(result.first, KeePass2Repair::RepairSuccess); + QScopedPointer dbRepaired(result.second); + QVERIFY(dbRepaired); + + QCOMPARE(dbRepaired->rootGroup()->entries().size(), 1); + QCOMPARE(dbRepaired->rootGroup()->entries().at(0)->username(), QString("testuser").append(QChar(0x20AC))); + QCOMPARE(dbRepaired->rootGroup()->entries().at(0)->password(), QString("testpw")); +} diff --git a/tests/TestKdbx3.h b/tests/TestKdbx3.h index fac8f7eb1..07c1c5503 100644 --- a/tests/TestKdbx3.h +++ b/tests/TestKdbx3.h @@ -18,19 +18,33 @@ #ifndef KEEPASSXC_TEST_KDBX3_H #define KEEPASSXC_TEST_KDBX3_H -#include "TestKeePass2XmlReader.h" +#include "TestKeePass2Format.h" -class TestKdbx3 : public TestKeePass2XmlReader +class TestKdbx3 : public TestKeePass2Format { Q_OBJECT private slots: - virtual void initTestCase() override; + void testNonAscii(); + void testCompressed(); + void testProtectedStrings(); + void testBrokenHeaderHash(); + void testFormat300(); + void testKdbxRepair(); protected: - virtual Database* readDatabase(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) override; - virtual Database* readDatabase(QString path, bool strictMode, bool& hasError, QString& errorString) override; - virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; + void initTestCaseImpl() override; + + Database* readXml(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) override; + Database* readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) override; + void writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; + + void readKdbx(QIODevice* device, CompositeKey const& key, QScopedPointer& db, + bool& hasError, QString& errorString) override; + void readKdbx(const QString& path, CompositeKey const& key, QScopedPointer& db, + bool& hasError, QString& errorString) override; + void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) override; + }; #endif // KEEPASSXC_TEST_KDBX3_H diff --git a/tests/TestKdbx4.cpp b/tests/TestKdbx4.cpp index e67274cbd..0a21215ac 100644 --- a/tests/TestKdbx4.cpp +++ b/tests/TestKdbx4.cpp @@ -17,8 +17,10 @@ #include "TestKdbx4.h" #include "core/Metadata.h" -#include "crypto/Crypto.h" +#include "keys/PasswordKey.h" #include "format/KeePass2.h" +#include "format/KeePass2Reader.h" +#include "format/KeePass2Writer.h" #include "format/KdbxXmlReader.h" #include "format/KdbxXmlWriter.h" #include "config-keepassx-tests.h" @@ -27,21 +29,15 @@ QTEST_GUILESS_MAIN(TestKdbx4) -void TestKdbx4::initTestCase() +void TestKdbx4::initTestCaseImpl() { - QVERIFY(Crypto::init()); - - KdbxXmlReader reader(KeePass2::FILE_VERSION_3); - reader.setStrictMode(true); - QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); - m_db.reset(reader.readDatabase(xmlFile)); - QVERIFY(m_db.data()); - QVERIFY(!reader.hasError()); + m_xmlDb->changeKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2)); + m_kdbxSourceDb->changeKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2)); } -Database* TestKdbx4::readDatabase(QString path, bool strictMode, bool& hasError, QString& errorString) +Database* TestKdbx4::readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) { - KdbxXmlReader reader(KeePass2::FILE_VERSION_3); + KdbxXmlReader reader(KeePass2::FILE_VERSION_4); reader.setStrictMode(strictMode); auto db = reader.readDatabase(path); hasError = reader.hasError(); @@ -49,9 +45,9 @@ Database* TestKdbx4::readDatabase(QString path, bool strictMode, bool& hasError, return db; } -Database* TestKdbx4::readDatabase(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) +Database* TestKdbx4::readXml(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) { - KdbxXmlReader reader(KeePass2::FILE_VERSION_3); + KdbxXmlReader reader(KeePass2::FILE_VERSION_4); reader.setStrictMode(strictMode); auto db = reader.readDatabase(buf); hasError = reader.hasError(); @@ -59,10 +55,135 @@ Database* TestKdbx4::readDatabase(QBuffer* buf, bool strictMode, bool& hasError, return db; } -void TestKdbx4::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) +void TestKdbx4::writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) { - KdbxXmlWriter writer(KeePass2::FILE_VERSION_3); + KdbxXmlWriter writer(KeePass2::FILE_VERSION_4); writer.writeDatabase(buf, db); hasError = writer.hasError(); errorString = writer.errorString(); } + +void TestKdbx4::readKdbx(QIODevice* device, CompositeKey const& key, QScopedPointer& db, + bool& hasError, QString& errorString) +{ + KeePass2Reader reader; + db.reset(reader.readDatabase(device, key)); + hasError = reader.hasError(); + if (hasError) { + errorString = reader.errorString(); + } + QCOMPARE(reader.version(), KeePass2::FILE_VERSION_4); +} + +void TestKdbx4::readKdbx(const QString& path, CompositeKey const& key, QScopedPointer& db, + bool& hasError, QString& errorString) +{ + KeePass2Reader reader; + db.reset(reader.readDatabase(path, key)); + hasError = reader.hasError(); + if (hasError) { + errorString = reader.errorString(); + } + QCOMPARE(reader.version(), KeePass2::FILE_VERSION_4); +} + +void TestKdbx4::writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) +{ + if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3) { + db->changeKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2)); + } + KeePass2Writer writer; + hasError = writer.writeDatabase(device, db); + hasError = writer.hasError(); + if (hasError) { + errorString = writer.errorString(); + } + QCOMPARE(writer.version(), KeePass2::FILE_VERSION_4); +} + +Q_DECLARE_METATYPE(Uuid); +void TestKdbx4::testFormat400() +{ + QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format400.kdbx"); + CompositeKey key; + key.addKey(PasswordKey("t")); + KeePass2Reader reader; + QScopedPointer db(reader.readDatabase(filename, key)); + QCOMPARE(reader.version(), KeePass2::FILE_VERSION_4); + QVERIFY(db.data()); + QVERIFY(!reader.hasError()); + + QCOMPARE(db->rootGroup()->name(), QString("Format400")); + QCOMPARE(db->metadata()->name(), QString("Format400")); + QCOMPARE(db->rootGroup()->entries().size(), 1); + auto 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")); +} + +void TestKdbx4::testFormat400Upgrade() +{ + QFETCH(Uuid, kdfUuid); + QFETCH(Uuid, cipherUuid); + QFETCH(quint32, expectedVersion); + + QScopedPointer sourceDb(new Database()); + sourceDb->metadata()->setName("Wubba lubba dub dub"); + QCOMPARE(sourceDb->kdf()->uuid(), KeePass2::KDF_AES_KDBX3); // default is legacy AES-KDF + + CompositeKey key; + key.addKey(PasswordKey("I am in great pain, please help me!")); + sourceDb->setKey(key, true, true); + + QBuffer buffer; + buffer.open(QBuffer::ReadWrite); + + // upgrade to KDBX 4 by changing KDF and Cipher + sourceDb->changeKdf(KeePass2::uuidToKdf(kdfUuid)); + sourceDb->setCipher(cipherUuid); + KeePass2Writer writer; + writer.writeDatabase(&buffer, sourceDb.data()); + if (writer.hasError()) { + QFAIL(qPrintable(QString("Error while writing database: %1").arg(writer.errorString()))); + } + + // read buffer back + buffer.seek(0); + KeePass2Reader reader; + QScopedPointer targetDb(reader.readDatabase(&buffer, key)); + if (reader.hasError()) { + QFAIL(qPrintable(QString("Error while reading database: %1").arg(reader.errorString()))); + } + + QVERIFY(targetDb->rootGroup()); + QCOMPARE(targetDb->metadata()->name(), sourceDb->metadata()->name()); + + QCOMPARE(reader.version(), expectedVersion); + QCOMPARE(targetDb->kdf()->uuid(), sourceDb->kdf()->uuid()); + QCOMPARE(targetDb->cipher(), cipherUuid); +} + +void TestKdbx4::testFormat400Upgrade_data() +{ + QTest::addColumn("kdfUuid"); + QTest::addColumn("cipherUuid"); + QTest::addColumn("expectedVersion"); + + auto constexpr kdbx3 = KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK; + auto constexpr kdbx4 = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK; + + QTest::newRow("Argon2 + AES") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_AES << kdbx4; + QTest::newRow("AES-KDF + AES") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_AES << kdbx4; + QTest::newRow("AES-KDF (legacy) + AES") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_AES << kdbx3; + QTest::newRow("Argon2 + ChaCha20") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_CHACHA20 << kdbx4; + QTest::newRow("AES-KDF + ChaCha20") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_CHACHA20 << kdbx4; + QTest::newRow("AES-KDF (legacy) + ChaCha20") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_CHACHA20 << kdbx3; + QTest::newRow("Argon2 + Twofish") << KeePass2::KDF_ARGON2 << KeePass2::CIPHER_TWOFISH << kdbx4; + QTest::newRow("AES-KDF + Twofish") << KeePass2::KDF_AES_KDBX4 << KeePass2::CIPHER_TWOFISH << kdbx4; + QTest::newRow("AES-KDF (legacy) + Twofish") << KeePass2::KDF_AES_KDBX3 << KeePass2::CIPHER_TWOFISH << kdbx3; +} diff --git a/tests/TestKdbx4.h b/tests/TestKdbx4.h index 0615ff3b7..59864b70e 100644 --- a/tests/TestKdbx4.h +++ b/tests/TestKdbx4.h @@ -18,19 +18,29 @@ #ifndef KEEPASSXC_TEST_KDBX4_H #define KEEPASSXC_TEST_KDBX4_H -#include "TestKeePass2XmlReader.h" +#include "TestKeePass2Format.h" -class TestKdbx4 : public TestKeePass2XmlReader +class TestKdbx4 : public TestKeePass2Format { Q_OBJECT private slots: - virtual void initTestCase() override; + void testFormat400(); + void testFormat400Upgrade(); + void testFormat400Upgrade_data(); protected: - virtual Database* readDatabase(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) override; - virtual Database* readDatabase(QString path, bool strictMode, bool& hasError, QString& errorString) override; - virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; + void initTestCaseImpl() override; + + Database* readXml(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) override; + Database* readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) override; + void writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; + + void readKdbx(const QString& path, CompositeKey const& key, QScopedPointer& db, + bool& hasError, QString& errorString) override; + void readKdbx(QIODevice* device, CompositeKey const& key, QScopedPointer& db, + bool& hasError, QString& errorString) override; + void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) override; }; #endif // KEEPASSXC_TEST_KDBX4_H diff --git a/tests/TestKeePass2XmlReader.cpp b/tests/TestKeePass2Format.cpp similarity index 67% rename from tests/TestKeePass2XmlReader.cpp rename to tests/TestKeePass2Format.cpp index a89b9547f..6c3777387 100644 --- a/tests/TestKeePass2XmlReader.cpp +++ b/tests/TestKeePass2Format.cpp @@ -1,5 +1,5 @@ /* - * 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 @@ -15,101 +15,105 @@ * along with this program. If not, see . */ -#include "TestKeePass2XmlReader.h" +#include "TestKeePass2Format.h" #include "core/Group.h" #include "core/Metadata.h" +#include "crypto/Crypto.h" +#include "keys/PasswordKey.h" #include "format/KdbxXmlReader.h" -#include "format/KdbxXmlWriter.h" + +#include "FailDevice.h" #include "config-keepassx-tests.h" #include #include -namespace QTest { -template<> -char* toString(const Uuid& uuid) +void TestKeePass2Format::initTestCase() { - QByteArray ba = "Uuid("; - ba += uuid.toBase64().toLatin1().constData(); - ba += ")"; - return qstrdup(ba.constData()); -} + QVERIFY(Crypto::init()); -template<> -char* toString(const Group::TriState& triState) -{ - QString value; + // read raw XML database + bool hasError; + QString errorString; + m_xmlDb.reset(readXml(QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"), true, hasError, errorString)); + if (hasError) { + QFAIL(qPrintable(QString("Error while reading XML: ").append(errorString))); + } + QVERIFY(m_xmlDb.data()); - if (triState == Group::Inherit) { - value = "null"; - } else if (triState == Group::Enable) { - value = "true"; - } else { - value = "false"; + // construct and write KDBX to buffer + CompositeKey key; + key.addKey(PasswordKey("test")); + + m_kdbxSourceDb.reset(new Database()); + m_kdbxSourceDb->setKey(key); + m_kdbxSourceDb->metadata()->setName("TESTDB"); + Group* group = m_kdbxSourceDb->rootGroup(); + group->setUuid(Uuid::random()); + group->setNotes("I'm a note!"); + auto entry = new Entry(); + entry->setPassword(QString::fromUtf8("\xc3\xa4\xa3\xb6\xc3\xbc\xe9\x9b\xbb\xe7\xb4\x85")); + entry->setUuid(Uuid::random()); + entry->attributes()->set("test", "protectedTest", true); + QVERIFY(entry->attributes()->isProtected("test")); + entry->attachments()->set("myattach.txt", QByteArray("this is an attachment")); + entry->attachments()->set("aaa.txt", QByteArray("also an attachment")); + entry->setGroup(group); + auto groupNew = new Group(); + groupNew->setUuid(Uuid::random()); + groupNew->setName("TESTGROUP"); + groupNew->setNotes("I'm a sub group note!"); + groupNew->setParent(group); + + m_kdbxTargetBuffer.open(QBuffer::ReadWrite); + writeKdbx(&m_kdbxTargetBuffer, m_kdbxSourceDb.data(), hasError, errorString); + if (hasError) { + QFAIL(qPrintable(QString("Error while writing database: ").append(errorString))); } - return qstrdup(value.toLocal8Bit().constData()); -} + // call sub class init method + initTestCaseImpl(); } -QDateTime TestKeePass2XmlReader::genDT(int year, int month, int day, int hour, int min, int second) +void TestKeePass2Format::testXmlMetadata() { - QDate date(year, month, day); - QTime time(hour, min, second); - return QDateTime(date, time, Qt::UTC); + QCOMPARE(m_xmlDb->metadata()->generator(), QString("KeePass")); + QCOMPARE(m_xmlDb->metadata()->name(), QString("ANAME")); + QCOMPARE(m_xmlDb->metadata()->nameChanged(), genDT(2010, 8, 8, 17, 24, 53)); + QCOMPARE(m_xmlDb->metadata()->description(), QString("ADESC")); + QCOMPARE(m_xmlDb->metadata()->descriptionChanged(), genDT(2010, 8, 8, 17, 27, 12)); + QCOMPARE(m_xmlDb->metadata()->defaultUserName(), QString("DEFUSERNAME")); + QCOMPARE(m_xmlDb->metadata()->defaultUserNameChanged(), genDT(2010, 8, 8, 17, 27, 45)); + QCOMPARE(m_xmlDb->metadata()->maintenanceHistoryDays(), 127); + QCOMPARE(m_xmlDb->metadata()->color(), QColor(0xff, 0xef, 0x00)); + QCOMPARE(m_xmlDb->metadata()->masterKeyChanged(), genDT(2012, 4, 5, 17, 9, 34)); + QCOMPARE(m_xmlDb->metadata()->masterKeyChangeRec(), 101); + QCOMPARE(m_xmlDb->metadata()->masterKeyChangeForce(), -1); + QCOMPARE(m_xmlDb->metadata()->protectTitle(), false); + QCOMPARE(m_xmlDb->metadata()->protectUsername(), true); + QCOMPARE(m_xmlDb->metadata()->protectPassword(), false); + QCOMPARE(m_xmlDb->metadata()->protectUrl(), true); + QCOMPARE(m_xmlDb->metadata()->protectNotes(), false); + QCOMPARE(m_xmlDb->metadata()->recycleBinEnabled(), true); + QVERIFY(m_xmlDb->metadata()->recycleBin() != nullptr); + QCOMPARE(m_xmlDb->metadata()->recycleBin()->name(), QString("Recycle Bin")); + QCOMPARE(m_xmlDb->metadata()->recycleBinChanged(), genDT(2010, 8, 25, 16, 12, 57)); + QVERIFY(m_xmlDb->metadata()->entryTemplatesGroup() == nullptr); + QCOMPARE(m_xmlDb->metadata()->entryTemplatesGroupChanged(), genDT(2010, 8, 8, 17, 24, 19)); + QVERIFY(m_xmlDb->metadata()->lastSelectedGroup() != nullptr); + QCOMPARE(m_xmlDb->metadata()->lastSelectedGroup()->name(), QString("NewDatabase")); + QVERIFY(m_xmlDb->metadata()->lastTopVisibleGroup() == m_xmlDb->metadata()->lastSelectedGroup()); + QCOMPARE(m_xmlDb->metadata()->historyMaxItems(), -1); + QCOMPARE(m_xmlDb->metadata()->historyMaxSize(), 5242880); } -QByteArray TestKeePass2XmlReader::strToBytes(const QString& str) +void TestKeePass2Format::testXmlCustomIcons() { - QByteArray result; - - for (auto i : str) { - result.append(static_cast(i.unicode() >> 8)); - result.append(static_cast(i.unicode() & 0xFF)); - } - - return result; -} - -void TestKeePass2XmlReader::testMetadata() -{ - QCOMPARE(m_db->metadata()->generator(), QString("KeePass")); - QCOMPARE(m_db->metadata()->name(), QString("ANAME")); - QCOMPARE(m_db->metadata()->nameChanged(), genDT(2010, 8, 8, 17, 24, 53)); - QCOMPARE(m_db->metadata()->description(), QString("ADESC")); - QCOMPARE(m_db->metadata()->descriptionChanged(), genDT(2010, 8, 8, 17, 27, 12)); - QCOMPARE(m_db->metadata()->defaultUserName(), QString("DEFUSERNAME")); - QCOMPARE(m_db->metadata()->defaultUserNameChanged(), genDT(2010, 8, 8, 17, 27, 45)); - QCOMPARE(m_db->metadata()->maintenanceHistoryDays(), 127); - QCOMPARE(m_db->metadata()->color(), QColor(0xff, 0xef, 0x00)); - QCOMPARE(m_db->metadata()->masterKeyChanged(), genDT(2012, 4, 5, 17, 9, 34)); - QCOMPARE(m_db->metadata()->masterKeyChangeRec(), 101); - QCOMPARE(m_db->metadata()->masterKeyChangeForce(), -1); - QCOMPARE(m_db->metadata()->protectTitle(), false); - QCOMPARE(m_db->metadata()->protectUsername(), true); - QCOMPARE(m_db->metadata()->protectPassword(), false); - QCOMPARE(m_db->metadata()->protectUrl(), true); - QCOMPARE(m_db->metadata()->protectNotes(), false); - QCOMPARE(m_db->metadata()->recycleBinEnabled(), true); - QVERIFY(m_db->metadata()->recycleBin() != nullptr); - QCOMPARE(m_db->metadata()->recycleBin()->name(), QString("Recycle Bin")); - QCOMPARE(m_db->metadata()->recycleBinChanged(), genDT(2010, 8, 25, 16, 12, 57)); - QVERIFY(m_db->metadata()->entryTemplatesGroup() == nullptr); - QCOMPARE(m_db->metadata()->entryTemplatesGroupChanged(), genDT(2010, 8, 8, 17, 24, 19)); - QVERIFY(m_db->metadata()->lastSelectedGroup() != nullptr); - QCOMPARE(m_db->metadata()->lastSelectedGroup()->name(), QString("NewDatabase")); - QVERIFY(m_db->metadata()->lastTopVisibleGroup() == m_db->metadata()->lastSelectedGroup()); - QCOMPARE(m_db->metadata()->historyMaxItems(), -1); - QCOMPARE(m_db->metadata()->historyMaxSize(), 5242880); -} - -void TestKeePass2XmlReader::testCustomIcons() -{ - QCOMPARE(m_db->metadata()->customIcons().size(), 1); + QCOMPARE(m_xmlDb->metadata()->customIcons().size(), 1); Uuid uuid = Uuid::fromBase64("++vyI+daLk6omox4a6kQGA=="); - QVERIFY(m_db->metadata()->customIcons().contains(uuid)); - QImage icon = m_db->metadata()->customIcon(uuid); + QVERIFY(m_xmlDb->metadata()->customIcons().contains(uuid)); + QImage icon = m_xmlDb->metadata()->customIcon(uuid); QCOMPARE(icon.width(), 16); QCOMPARE(icon.height(), 16); @@ -123,18 +127,18 @@ void TestKeePass2XmlReader::testCustomIcons() } } -void TestKeePass2XmlReader::testCustomData() +void TestKeePass2Format::testXmlCustomData() { - QHash customFields = m_db->metadata()->customFields(); + QHash customFields = m_xmlDb->metadata()->customFields(); QCOMPARE(customFields.size(), 2); QCOMPARE(customFields.value("A Sample Test Key"), QString("valu")); QCOMPARE(customFields.value("custom key"), QString("blub")); } -void TestKeePass2XmlReader::testGroupRoot() +void TestKeePass2Format::testXmlGroupRoot() { - const Group* group = m_db->rootGroup(); + const Group* group = m_xmlDb->rootGroup(); QVERIFY(group); QCOMPARE(group->uuid().toBase64(), QString("lmU+9n0aeESKZvcEze+bRg==")); QCOMPARE(group->name(), QString("NewDatabase")); @@ -156,14 +160,14 @@ void TestKeePass2XmlReader::testGroupRoot() QCOMPARE(group->lastTopVisibleEntry()->uuid().toBase64(), QString("+wSUOv6qf0OzW8/ZHAs2sA==")); QCOMPARE(group->children().size(), 3); - QVERIFY(m_db->metadata()->recycleBin() == m_db->rootGroup()->children().at(2)); + QVERIFY(m_xmlDb->metadata()->recycleBin() == m_xmlDb->rootGroup()->children().at(2)); QCOMPARE(group->entries().size(), 2); } -void TestKeePass2XmlReader::testGroup1() +void TestKeePass2Format::testXmlGroup1() { - const Group* group = m_db->rootGroup()->children().at(0); + const Group* group = m_xmlDb->rootGroup()->children().at(0); QCOMPARE(group->uuid().toBase64(), QString("AaUYVdXsI02h4T1RiAlgtg==")); QCOMPARE(group->name(), QString("General")); @@ -177,9 +181,9 @@ void TestKeePass2XmlReader::testGroup1() QVERIFY(!group->lastTopVisibleEntry()); } -void TestKeePass2XmlReader::testGroup2() +void TestKeePass2Format::testXmlGroup2() { - const Group* group = m_db->rootGroup()->children().at(1); + const Group* group = m_xmlDb->rootGroup()->children().at(1); QCOMPARE(group->uuid().toBase64(), QString("1h4NtL5DK0yVyvaEnN//4A==")); QCOMPARE(group->name(), QString("Windows")); @@ -197,9 +201,9 @@ void TestKeePass2XmlReader::testGroup2() QCOMPARE(entry->title(), QString("Subsub Entry")); } -void TestKeePass2XmlReader::testEntry1() +void TestKeePass2Format::testXmlEntry1() { - const Entry* entry = m_db->rootGroup()->entries().at(0); + const Entry* entry = m_xmlDb->rootGroup()->entries().at(0); QCOMPARE(entry->uuid().toBase64(), QString("+wSUOv6qf0OzW8/ZHAs2sA==")); QCOMPARE(entry->historyItems().size(), 2); @@ -259,9 +263,9 @@ void TestKeePass2XmlReader::testEntry1() QCOMPARE(assoc1.sequence, QString("")); } -void TestKeePass2XmlReader::testEntry2() +void TestKeePass2Format::testXmlEntry2() { - const Entry* entry = m_db->rootGroup()->entries().at(1); + const Entry* entry = m_xmlDb->rootGroup()->entries().at(1); QCOMPARE(entry->uuid().toBase64(), QString("4jbADG37hkiLh2O0qUdaOQ==")); QCOMPARE(entry->iconNumber(), 0); @@ -307,9 +311,9 @@ void TestKeePass2XmlReader::testEntry2() QCOMPARE(assoc2.sequence, QString("{Title}{UserName} test")); } -void TestKeePass2XmlReader::testEntryHistory() +void TestKeePass2Format::testXmlEntryHistory() { - const Entry* entryMain = m_db->rootGroup()->entries().at(0); + const Entry* entryMain = m_xmlDb->rootGroup()->entries().at(0); QCOMPARE(entryMain->historyItems().size(), 2); { @@ -333,9 +337,9 @@ void TestKeePass2XmlReader::testEntryHistory() } } -void TestKeePass2XmlReader::testDeletedObjects() +void TestKeePass2Format::testXmlDeletedObjects() { - QList objList = m_db->deletedObjects(); + QList objList = m_xmlDb->deletedObjects(); DeletedObject delObj; delObj = objList.takeFirst(); @@ -349,7 +353,7 @@ void TestKeePass2XmlReader::testDeletedObjects() QVERIFY(objList.isEmpty()); } -void TestKeePass2XmlReader::testBroken() +void TestKeePass2Format::testXmlBroken() { QFETCH(QString, baseName); QFETCH(bool, strictMode); @@ -359,14 +363,14 @@ void TestKeePass2XmlReader::testBroken() QVERIFY(QFile::exists(xmlFile)); bool hasError; QString errorString; - QScopedPointer db(readDatabase(xmlFile, strictMode, hasError, errorString)); + QScopedPointer db(readXml(xmlFile, strictMode, hasError, errorString)); if (hasError) { qWarning("Reader error: %s", qPrintable(errorString)); } QCOMPARE(hasError, expectError); } -void TestKeePass2XmlReader::testBroken_data() +void TestKeePass2Format::testXmlBroken_data() { QTest::addColumn("baseName"); QTest::addColumn("strictMode"); @@ -391,21 +395,21 @@ void TestKeePass2XmlReader::testBroken_data() QTest::newRow("BrokenDifferentEntryHistoryUuid (not strict)") << "BrokenDifferentEntryHistoryUuid" << false << false; } -void TestKeePass2XmlReader::testEmptyUuids() +void TestKeePass2Format::testXmlEmptyUuids() { QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "EmptyUuids"); QVERIFY(QFile::exists(xmlFile)); bool hasError; QString errorString; - QScopedPointer dbp(readDatabase(xmlFile, true, hasError, errorString)); + QScopedPointer dbp(readXml(xmlFile, true, hasError, errorString)); if (hasError) { qWarning("Reader error: %s", qPrintable(errorString)); } QVERIFY(!hasError); } -void TestKeePass2XmlReader::testInvalidXmlChars() +void TestKeePass2Format::testXmlInvalidXmlChars() { QScopedPointer dbWrite(new Database()); @@ -444,11 +448,11 @@ void TestKeePass2XmlReader::testInvalidXmlChars() buffer.open(QIODevice::ReadWrite); bool hasError; QString errorString; - writeDatabase(&buffer, dbWrite.data(), hasError, errorString); + writeXml(&buffer, dbWrite.data(), hasError, errorString); QVERIFY(!hasError); buffer.seek(0); - QScopedPointer dbRead(readDatabase(&buffer, true, hasError, errorString)); + QScopedPointer dbRead(readXml(&buffer, true, hasError, errorString)); if (hasError) { qWarning("Database read error: %s", qPrintable(errorString)); } @@ -470,13 +474,13 @@ void TestKeePass2XmlReader::testInvalidXmlChars() QCOMPARE(strToBytes(attrRead->value("SurrogateValid2")), strToBytes(strSurrogateValid2)); } -void TestKeePass2XmlReader::testRepairUuidHistoryItem() +void TestKeePass2Format::testXmlRepairUuidHistoryItem() { QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid"); QVERIFY(QFile::exists(xmlFile)); bool hasError; QString errorString; - QScopedPointer db(readDatabase(xmlFile, false, hasError, errorString)); + QScopedPointer db(readXml(xmlFile, false, hasError, errorString)); if (hasError) { qWarning("Database read error: %s", qPrintable(errorString)); } @@ -494,3 +498,125 @@ void TestKeePass2XmlReader::testRepairUuidHistoryItem() QVERIFY(!historyItem->uuid().isNull()); QCOMPARE(historyItem->uuid(), entry->uuid()); } + +void TestKeePass2Format::testReadBackTargetDb() +{ + // read back previously constructed KDBX + CompositeKey key; + key.addKey(PasswordKey("test")); + + bool hasError; + QString errorString; + + m_kdbxTargetBuffer.seek(0); + readKdbx(&m_kdbxTargetBuffer, key, m_kdbxTargetDb, hasError, errorString); + if (hasError) { + QFAIL(qPrintable(QString("Error while reading database: ").append(errorString))); + } + QVERIFY(m_kdbxTargetDb.data()); +} + +void TestKeePass2Format::testKdbxBasic() +{ + QCOMPARE(m_kdbxTargetDb->metadata()->name(), m_kdbxSourceDb->metadata()->name()); + QVERIFY(m_kdbxTargetDb->rootGroup()); + QCOMPARE(m_kdbxTargetDb->rootGroup()->children()[0]->name(), m_kdbxSourceDb->rootGroup()->children()[0]->name()); + QCOMPARE(m_kdbxTargetDb->rootGroup()->notes(), m_kdbxSourceDb->rootGroup()->notes()); + QCOMPARE(m_kdbxTargetDb->rootGroup()->children()[0]->notes(), m_kdbxSourceDb->rootGroup()->children()[0]->notes()); +} + +void TestKeePass2Format::testKdbxProtectedAttributes() +{ + QCOMPARE(m_kdbxTargetDb->rootGroup()->entries().size(), 1); + Entry* entry = m_kdbxTargetDb->rootGroup()->entries().at(0); + QCOMPARE(entry->attributes()->value("test"), QString("protectedTest")); + QCOMPARE(entry->attributes()->isProtected("test"), true); +} + +void TestKeePass2Format::testKdbxAttachments() +{ + Entry* entry = m_kdbxTargetDb->rootGroup()->entries().at(0); + QCOMPARE(entry->attachments()->keys().size(), 2); + QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("this is an attachment")); + QCOMPARE(entry->attachments()->value("aaa.txt"), QByteArray("also an attachment")); +} + +void TestKeePass2Format::testKdbxNonAsciiPasswords() +{ + QCOMPARE(m_kdbxTargetDb->rootGroup()->entries()[0]->password(), m_kdbxSourceDb->rootGroup()->entries()[0]->password()); +} + +void TestKeePass2Format::testKdbxDeviceFailure() +{ + CompositeKey key; + key.addKey(PasswordKey("test")); + QScopedPointer db(new Database()); + db->setKey(key); + // Disable compression so we write a predictable number of bytes. + db->setCompressionAlgo(Database::CompressionNone); + + auto entry = new Entry(); + entry->setParent(db->rootGroup()); + QByteArray attachment(4096, 'Z'); + entry->attachments()->set("test", attachment); + + FailDevice failDevice(512); + QVERIFY(failDevice.open(QIODevice::WriteOnly)); + bool hasError; + QString errorString; + writeKdbx(&failDevice, db.data(), hasError, errorString); + QVERIFY(hasError); + QCOMPARE(errorString, QString("FAILDEVICE")); +} + + + +// ==================================================================================================== +// Helper functions +// ==================================================================================================== + +namespace QTest { +template<> +char* toString(const Uuid& uuid) +{ + QByteArray ba = "Uuid("; + ba += uuid.toBase64().toLatin1().constData(); + ba += ")"; + return qstrdup(ba.constData()); +} + +template<> +char* toString(const Group::TriState& triState) +{ + QString value; + + if (triState == Group::Inherit) { + value = "null"; + } else if (triState == Group::Enable) { + value = "true"; + } else { + value = "false"; + } + + return qstrdup(value.toLocal8Bit().constData()); +} +} + +QDateTime TestKeePass2Format::genDT(int year, int month, int day, int hour, int min, int second) +{ + QDate date(year, month, day); + QTime time(hour, min, second); + return QDateTime(date, time, Qt::UTC); +} + +QByteArray TestKeePass2Format::strToBytes(const QString& str) +{ + QByteArray result; + + for (auto i : str) { + result.append(static_cast(i.unicode() >> 8)); + result.append(static_cast(i.unicode() & 0xFF)); + } + + return result; +} diff --git a/tests/TestKeePass2Format.h b/tests/TestKeePass2Format.h new file mode 100644 index 000000000..11420bab0 --- /dev/null +++ b/tests/TestKeePass2Format.h @@ -0,0 +1,91 @@ +/* + * 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_TESTKEEPASS2FORMAT_H +#define KEEPASSXC_TESTKEEPASS2FORMAT_H + +#include +#include +#include +#include + +#include "core/Database.h" + +/** + * Abstract base class for KeePass2 file format tests. + */ +class TestKeePass2Format : public QObject +{ +Q_OBJECT + +private slots: + void initTestCase(); + + /** + * XML Reader / writer tests. + */ + void testXmlMetadata(); + void testXmlCustomIcons(); + void testXmlCustomData(); + void testXmlGroupRoot(); + void testXmlGroup1(); + void testXmlGroup2(); + void testXmlEntry1(); + void testXmlEntry2(); + void testXmlEntryHistory(); + void testXmlDeletedObjects(); + void testXmlBroken(); + void testXmlBroken_data(); + void testXmlEmptyUuids(); + void testXmlInvalidXmlChars(); + void testXmlRepairUuidHistoryItem(); + + /** + * KDBX binary format tests. + */ + void testReadBackTargetDb(); + void testKdbxBasic(); + void testKdbxProtectedAttributes(); + void testKdbxAttachments(); + void testKdbxNonAsciiPasswords(); + void testKdbxDeviceFailure(); + +protected: + virtual void initTestCaseImpl() = 0; + + virtual Database* readXml(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) = 0; + virtual Database* readXml(const QString& path, bool strictMode, bool& hasError, QString& errorString) = 0; + virtual void writeXml(QBuffer* buf, Database* db, bool& hasError, QString& errorString) = 0; + + virtual void readKdbx(QIODevice* device, CompositeKey const& key, QScopedPointer& db, + bool& hasError, QString& errorString) = 0; + virtual void readKdbx(const QString& path, CompositeKey const& key, QScopedPointer& db, + bool& hasError, QString& errorString) = 0; + virtual void writeKdbx(QIODevice* device, 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); + + QScopedPointer m_xmlDb; + QScopedPointer m_kdbxSourceDb; + QScopedPointer m_kdbxTargetDb; + +private: + QBuffer m_kdbxTargetBuffer; +}; + +#endif // KEEPASSXC_TESTKEEPASS2FORMAT_H diff --git a/tests/TestKeePass2Reader.cpp b/tests/TestKeePass2Reader.cpp deleted file mode 100644 index 86dc6db2c..000000000 --- a/tests/TestKeePass2Reader.cpp +++ /dev/null @@ -1,180 +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 "TestKeePass2Reader.h" - -#include - -#include "config-keepassx-tests.h" -#include "core/Database.h" -#include "core/Group.h" -#include "core/Metadata.h" -#include "crypto/Crypto.h" -#include "format/KeePass2Reader.h" -#include "keys/PasswordKey.h" - -QTEST_GUILESS_MAIN(TestKeePass2Reader) - -void TestKeePass2Reader::initTestCase() -{ - QVERIFY(Crypto::init()); -} - -void TestKeePass2Reader::testNonAscii() -{ - QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/NonAscii.kdbx"); - CompositeKey key; - key.addKey(PasswordKey(QString::fromUtf8("\xce\x94\xc3\xb6\xd8\xb6"))); - KeePass2Reader reader; - Database* db = reader.readDatabase(filename, key); - QVERIFY(db); - QVERIFY(!reader.hasError()); - QCOMPARE(db->metadata()->name(), QString("NonAsciiTest")); - QCOMPARE(db->compressionAlgo(), Database::CompressionNone); - - delete db; -} - -void TestKeePass2Reader::testCompressed() -{ - QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Compressed.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("")); - KeePass2Reader reader; - Database* db = reader.readDatabase(filename, key); - QVERIFY(db); - QVERIFY(!reader.hasError()); - QCOMPARE(db->metadata()->name(), QString("Compressed")); - QCOMPARE(db->compressionAlgo(), Database::CompressionGZip); - - delete db; -} - -void TestKeePass2Reader::testProtectedStrings() -{ - QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/ProtectedStrings.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("masterpw")); - KeePass2Reader reader; - Database* db = reader.readDatabase(filename, key); - QVERIFY(db); - QVERIFY(!reader.hasError()); - QCOMPARE(db->metadata()->name(), QString("Protected Strings Test")); - - Entry* entry = db->rootGroup()->entries().at(0); - - QCOMPARE(entry->title(), QString("Sample Entry")); - QCOMPARE(entry->username(), QString("Protected User Name")); - QCOMPARE(entry->password(), QString("ProtectedPassword")); - QCOMPARE(entry->attributes()->value("TestProtected"), QString("ABC")); - QCOMPARE(entry->attributes()->value("TestUnprotected"), QString("DEF")); - - QVERIFY(db->metadata()->protectPassword()); - QVERIFY(entry->attributes()->isProtected("TestProtected")); - QVERIFY(!entry->attributes()->isProtected("TestUnprotected")); - - delete db; -} - -void TestKeePass2Reader::testBrokenHeaderHash() -{ - // The protected stream key has been modified in the header. - // Make sure the database won't open. - - QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/BrokenHeaderHash.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("")); - KeePass2Reader reader; - Database* db = reader.readDatabase(filename, key); - QVERIFY(!db); - QVERIFY(reader.hasError()); - - delete db; -} - -void TestKeePass2Reader::testFormat200() -{ - QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format200.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("a")); - KeePass2Reader reader; - Database* db = reader.readDatabase(filename, key); - QVERIFY(db); - QVERIFY(!reader.hasError()); - - QCOMPARE(db->rootGroup()->name(), QString("Format200")); - QVERIFY(!db->metadata()->protectTitle()); - QVERIFY(db->metadata()->protectUsername()); - QVERIFY(!db->metadata()->protectPassword()); - QVERIFY(db->metadata()->protectUrl()); - QVERIFY(!db->metadata()->protectNotes()); - - QCOMPARE(db->rootGroup()->entries().size(), 1); - Entry* entry = db->rootGroup()->entries().at(0); - - QCOMPARE(entry->title(), QString("Sample Entry")); - QCOMPARE(entry->username(), QString("User Name")); - QCOMPARE(entry->attachments()->keys().size(), 2); - QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("abcdefghijk")); - QCOMPARE(entry->attachments()->value("test.txt"), QByteArray("this is a test")); - - QCOMPARE(entry->historyItems().size(), 2); - QCOMPARE(entry->historyItems().at(0)->attachments()->keys().size(), 0); - QCOMPARE(entry->historyItems().at(1)->attachments()->keys().size(), 1); - QCOMPARE(entry->historyItems().at(1)->attachments()->value("myattach.txt"), QByteArray("abcdefghijk")); - - delete db; -} - -void TestKeePass2Reader::testFormat300() -{ - QString filename = QString(KEEPASSX_TEST_DATA_DIR).append("/Format300.kdbx"); - CompositeKey key; - key.addKey(PasswordKey("a")); - KeePass2Reader reader; - Database* db = reader.readDatabase(filename, key); - QVERIFY(db); - QVERIFY(!reader.hasError()); - - QCOMPARE(db->rootGroup()->name(), QString("Format300")); - QCOMPARE(db->metadata()->name(), QString("Test Database Format 0x00030000")); - - 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/TestKeePass2Writer.cpp b/tests/TestKeePass2Writer.cpp deleted file mode 100644 index 30d0cbf5a..000000000 --- a/tests/TestKeePass2Writer.cpp +++ /dev/null @@ -1,168 +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 "TestKeePass2Writer.h" - -#include -#include -#include - -#include "config-keepassx-tests.h" -#include "FailDevice.h" -#include "core/Database.h" -#include "core/Group.h" -#include "core/Metadata.h" -#include "crypto/Crypto.h" -#include "format/KeePass2Reader.h" -#include "format/KeePass2Repair.h" -#include "format/KeePass2Writer.h" -#include "keys/PasswordKey.h" - -QTEST_GUILESS_MAIN(TestKeePass2Writer) - -void TestKeePass2Writer::initTestCase() -{ - QVERIFY(Crypto::init()); - - CompositeKey key; - key.addKey(PasswordKey("test")); - - m_dbOrg = new Database(); - m_dbOrg->setKey(key); - m_dbOrg->metadata()->setName("TESTDB"); - Group* group = m_dbOrg->rootGroup(); - group->setUuid(Uuid::random()); - group->setNotes("I'm a note!"); - Entry* entry = new Entry(); - entry->setPassword(QString::fromUtf8("\xc3\xa4\xa3\xb6\xc3\xbc\xe9\x9b\xbb\xe7\xb4\x85")); - entry->setUuid(Uuid::random()); - entry->attributes()->set("test", "protectedTest", true); - QVERIFY(entry->attributes()->isProtected("test")); - entry->attachments()->set("myattach.txt", QByteArray("this is an attachment")); - entry->attachments()->set("aaa.txt", QByteArray("also an attachment")); - entry->setGroup(group); - Group* groupNew = new Group(); - groupNew->setUuid(Uuid::random()); - groupNew->setName("TESTGROUP"); - groupNew->setNotes("I'm a sub group note!"); - groupNew->setParent(group); - - QBuffer buffer; - buffer.open(QBuffer::ReadWrite); - - KeePass2Writer writer; - bool writeSuccess = writer.writeDatabase(&buffer, m_dbOrg); - QVERIFY(writeSuccess); - QVERIFY(!writer.hasError()); - buffer.seek(0); - KeePass2Reader reader; - m_dbTest = reader.readDatabase(&buffer, key); - if (reader.hasError()) { - QFAIL(reader.errorString().toUtf8().constData()); - } - QVERIFY(m_dbTest); -} - -void TestKeePass2Writer::testBasic() -{ - QCOMPARE(m_dbTest->metadata()->name(), m_dbOrg->metadata()->name()); - QVERIFY(m_dbTest->rootGroup()); - QCOMPARE(m_dbTest->rootGroup()->children()[0]->name(), m_dbOrg->rootGroup()->children()[0]->name()); - QCOMPARE(m_dbTest->rootGroup()->notes(), m_dbOrg->rootGroup()->notes()); - QCOMPARE(m_dbTest->rootGroup()->children()[0]->notes(), m_dbOrg->rootGroup()->children()[0]->notes()); -} - -void TestKeePass2Writer::testProtectedAttributes() -{ - QCOMPARE(m_dbTest->rootGroup()->entries().size(), 1); - Entry* entry = m_dbTest->rootGroup()->entries().at(0); - QCOMPARE(entry->attributes()->value("test"), QString("protectedTest")); - QCOMPARE(entry->attributes()->isProtected("test"), true); -} - -void TestKeePass2Writer::testAttachments() -{ - Entry* entry = m_dbTest->rootGroup()->entries().at(0); - QCOMPARE(entry->attachments()->keys().size(), 2); - QCOMPARE(entry->attachments()->value("myattach.txt"), QByteArray("this is an attachment")); - QCOMPARE(entry->attachments()->value("aaa.txt"), QByteArray("also an attachment")); -} - -void TestKeePass2Writer::testNonAsciiPasswords() -{ - QCOMPARE(m_dbTest->rootGroup()->entries()[0]->password(), m_dbOrg->rootGroup()->entries()[0]->password()); -} - -void TestKeePass2Writer::testDeviceFailure() -{ - CompositeKey key; - key.addKey(PasswordKey("test")); - Database* db = new Database(); - db->setKey(key); - // Disable compression so we write a predictable number of bytes. - db->setCompressionAlgo(Database::CompressionNone); - - Entry* entry = new Entry(); - entry->setParent(db->rootGroup()); - QByteArray attachment(4096, 'Z'); - entry->attachments()->set("test", attachment); - - FailDevice failDevice(512); - QVERIFY(failDevice.open(QIODevice::WriteOnly)); - KeePass2Writer writer; - writer.writeDatabase(&failDevice, db); - QVERIFY(writer.hasError()); - QCOMPARE(writer.errorString(), QString("FAILDEVICE")); - - delete db; -} - -void TestKeePass2Writer::testRepair() -{ - QString brokenDbFilename = QString(KEEPASSX_TEST_DATA_DIR).append("/bug392.kdbx"); - // master password = test - // entry username: testuser\x10\x20AC - // entry password: testpw - CompositeKey key; - key.addKey(PasswordKey("test")); - - // test that we can't open the broken database - KeePass2Reader reader; - Database* dbBroken = reader.readDatabase(brokenDbFilename, key); - QVERIFY(!dbBroken); - QVERIFY(reader.hasError()); - - // test if we can repair the database - KeePass2Repair repair; - QFile file(brokenDbFilename); - file.open(QIODevice::ReadOnly); - auto result = repair.repairDatabase(&file, key); - QCOMPARE(result.first, KeePass2Repair::RepairSuccess); - Database* dbRepaired = result.second; - QVERIFY(dbRepaired); - - QCOMPARE(dbRepaired->rootGroup()->entries().size(), 1); - QCOMPARE(dbRepaired->rootGroup()->entries().at(0)->username(), QString("testuser").append(QChar(0x20AC))); - QCOMPARE(dbRepaired->rootGroup()->entries().at(0)->password(), QString("testpw")); - delete dbRepaired; -} - -void TestKeePass2Writer::cleanupTestCase() -{ - delete m_dbOrg; - delete m_dbTest; -} diff --git a/tests/TestKeePass2Writer.h b/tests/TestKeePass2Writer.h deleted file mode 100644 index 36a51dce6..000000000 --- a/tests/TestKeePass2Writer.h +++ /dev/null @@ -1,44 +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_TESTKEEPASS2WRITER_H -#define KEEPASSX_TESTKEEPASS2WRITER_H - -#include - -class Database; - -class TestKeePass2Writer : public QObject -{ - Q_OBJECT - -private slots: - void initTestCase(); - void testBasic(); - void testProtectedAttributes(); - void testAttachments(); - void testNonAsciiPasswords(); - void testDeviceFailure(); - void testRepair(); - void cleanupTestCase(); - -private: - Database* m_dbOrg; - Database* m_dbTest; -}; - -#endif // KEEPASSX_TESTKEEPASS2WRITER_H diff --git a/tests/TestKeePass2XmlReader.h b/tests/TestKeePass2XmlReader.h deleted file mode 100644 index ba69eb80f..000000000 --- a/tests/TestKeePass2XmlReader.h +++ /dev/null @@ -1,64 +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_TESTKEEPASS2XMLREADER_H -#define KEEPASSX_TESTKEEPASS2XMLREADER_H - -#include -#include -#include -#include - -#include "core/Database.h" - - -class TestKeePass2XmlReader : public QObject -{ - Q_OBJECT - -protected slots: - virtual void initTestCase() = 0; - -private slots: - void testMetadata(); - void testCustomIcons(); - void testCustomData(); - void testGroupRoot(); - void testGroup1(); - void testGroup2(); - void testEntry1(); - void testEntry2(); - void testEntryHistory(); - void testDeletedObjects(); - void testBroken(); - void testBroken_data(); - void testEmptyUuids(); - void testInvalidXmlChars(); - void testRepairUuidHistoryItem(); - -protected: - virtual Database* readDatabase(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) = 0; - virtual Database* readDatabase(QString path, bool strictMode, 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); - - QScopedPointer m_db; -}; - - -#endif // KEEPASSX_TESTKEEPASS2XMLREADER_H