Refactor and extend file format tests

This commit is contained in:
Janek Bevendorff 2018-01-17 20:52:29 +01:00
parent cdefc7ea9b
commit a595239624
No known key found for this signature in database
GPG Key ID: 2FDEB0D40BCA5E11
19 changed files with 826 additions and 715 deletions

View File

@ -32,7 +32,7 @@
Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData, Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
const CompositeKey& key, bool keepDatabase) const CompositeKey& key, bool keepDatabase)
{ {
Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3); Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3_1);
if (hasError()) { if (hasError()) {
return nullptr; return nullptr;
@ -118,7 +118,7 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& hea
Q_ASSERT(xmlDevice); Q_ASSERT(xmlDevice);
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3); KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3_1);
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream); xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
if (xmlReader.hasError()) { if (xmlReader.hasError()) {
@ -129,7 +129,7 @@ Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& hea
return nullptr; 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()) { if (!xmlReader.headerHash().isEmpty()) {
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256); QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);

View File

@ -63,7 +63,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
QBuffer header; QBuffer header;
header.open(QIODevice::WriteOnly); header.open(QIODevice::WriteOnly);
writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_3); writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_3_1);
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray())); CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray()));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CompressionFlags, CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CompressionFlags,
@ -131,7 +131,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
return false; return false;
} }
KdbxXmlWriter xmlWriter(KeePass2::FILE_VERSION_3); KdbxXmlWriter xmlWriter(KeePass2::FILE_VERSION_3_1);
xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash); xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash);
// Explicitly close/reset streams so they are flushed and we can detect // Explicitly close/reset streams so they are flushed and we can detect

View File

@ -22,6 +22,7 @@
#include <QMap> #include <QMap>
#include <QVariantMap> #include <QVariantMap>
#include <QList> #include <QList>
#include <QSharedPointer>
#include "crypto/SymmetricCipher.h" #include "crypto/SymmetricCipher.h"
#include "crypto/kdf/Kdf.h" #include "crypto/kdf/Kdf.h"
@ -29,104 +30,108 @@
namespace KeePass2 namespace KeePass2
{ {
const quint32 SIGNATURE_1 = 0x9AA2D903;
const quint32 SIGNATURE_2 = 0xB54BFB67;
const quint32 FILE_VERSION_MIN = 0x00020000; constexpr quint32 SIGNATURE_1 = 0x9AA2D903;
const quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000; constexpr quint32 SIGNATURE_2 = 0xB54BFB67;
const quint32 FILE_VERSION_4 = 0x00040000;
const quint32 FILE_VERSION_3 = 0x00030001;
const quint16 VARIANTMAP_VERSION = 0x0100; constexpr quint32 FILE_VERSION_CRITICAL_MASK = 0xFFFF0000;
const quint16 VARIANTMAP_CRITICAL_MASK = 0xFF00; 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; const QSysInfo::Endian BYTEORDER = QSysInfo::LittleEndian;
extern const Uuid CIPHER_TWOFISH;
extern const Uuid CIPHER_CHACHA20;
extern const Uuid KDF_AES_KDBX3; extern const Uuid CIPHER_AES;
extern const Uuid KDF_AES_KDBX4; extern const Uuid CIPHER_TWOFISH;
extern const Uuid KDF_ARGON2; 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 QByteArray INNER_STREAM_SALSA20_IV;
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<QPair<Uuid, QString>> CIPHERS; extern const QString KDFPARAM_UUID;
extern const QList<QPair<Uuid, QString>> KDFS; 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 extern const QList<QPair<Uuid, QString>> CIPHERS;
{ extern const QList<QPair<Uuid, QString>> KDFS;
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 InnerHeaderFieldID : quint8 enum class HeaderFieldID
{ {
End = 0, EndOfHeader = 0,
InnerRandomStreamID = 1, Comment = 1,
InnerRandomStreamKey = 2, CipherID = 2,
Binary = 3 CompressionFlags = 3,
}; MasterSeed = 4,
TransformSeed = 5,
TransformRounds = 6,
EncryptionIV = 7,
ProtectedStreamKey = 8,
StreamStartBytes = 9,
InnerRandomStreamID = 10,
KdfParameters = 11,
PublicCustomData = 12
};
enum class ProtectedStreamAlgo enum class InnerHeaderFieldID : quint8
{ {
ArcFourVariant = 1, End = 0,
Salsa20 = 2, InnerRandomStreamID = 1,
ChaCha20 = 3, InnerRandomStreamKey = 2,
InvalidProtectedStreamAlgo = -1 Binary = 3
}; };
enum class VariantMapFieldType : quint8 enum class ProtectedStreamAlgo
{ {
End = 0, ArcFourVariant = 1,
// Byte = 0x02, Salsa20 = 2,
// UInt16 = 0x03, ChaCha20 = 3,
UInt32 = 0x04, InvalidProtectedStreamAlgo = -1
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); enum class VariantMapFieldType : quint8
QSharedPointer<Kdf> kdfFromParameters(const QVariantMap& p); {
QVariantMap kdfToParameters(QSharedPointer<Kdf> kdf); End = 0,
QSharedPointer<Kdf> uuidToKdf(const Uuid& uuid); // Byte = 0x02,
Uuid kdfToUuid(QSharedPointer<Kdf> kdf); // UInt16 = 0x03,
ProtectedStreamAlgo idToProtectedStreamAlgo(quint32 id); 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<Kdf> kdfFromParameters(const QVariantMap& p);
QVariantMap kdfToParameters(QSharedPointer<Kdf> kdf);
QSharedPointer<Kdf> uuidToKdf(const Uuid& uuid);
Uuid kdfToUuid(QSharedPointer<Kdf> kdf);
ProtectedStreamAlgo idToProtectedStreamAlgo(quint32 id);
} // namespace KeePass2
#endif // KEEPASSX_KEEPASS2_H #endif // KEEPASSX_KEEPASS2_H

View File

@ -79,7 +79,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
QBuffer buffer(&xmlData); QBuffer buffer(&xmlData);
buffer.open(QIODevice::ReadOnly); buffer.open(QIODevice::ReadOnly);
if ((reader.version() & KeePass2::FILE_VERSION_CRITICAL_MASK) < KeePass2::FILE_VERSION_4) { 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); xmlReader.readDatabase(&buffer, db.data(), &randomStream);
hasError = xmlReader.hasError(); hasError = xmlReader.hasError();
} else { } else {

View File

@ -54,7 +54,7 @@ bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) {
// determine KDBX3 vs KDBX4 // determine KDBX3 vs KDBX4
if (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 && db->publicCustomData().isEmpty()) { 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()); m_writer.reset(new Kdbx3Writer());
} else { } else {
m_version = KeePass2::FILE_VERSION_4; m_version = KeePass2::FILE_VERSION_4;

View File

@ -107,21 +107,18 @@ endif()
add_unit_test(NAME testgroup SOURCES TestGroup.cpp add_unit_test(NAME testgroup SOURCES TestGroup.cpp
LIBS ${TEST_LIBRARIES}) LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testkdbx3 SOURCES TestKeePass2XmlReader.cpp TestKdbx3.cpp add_unit_test(NAME testkdbx2 SOURCES TestKdbx2.cpp
LIBS ${TEST_LIBRARIES}) 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}) LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testkeys SOURCES TestKeys.cpp add_unit_test(NAME testkeys SOURCES TestKeys.cpp
LIBS ${TEST_LIBRARIES}) 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 add_unit_test(NAME testgroupmodel SOURCES TestGroupModel.cpp
LIBS testsupport ${TEST_LIBRARIES}) LIBS testsupport ${TEST_LIBRARIES})

View File

@ -89,7 +89,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize)
void TestDeletedObjects::testDeletedObjectsFromFile() void TestDeletedObjects::testDeletedObjectsFromFile()
{ {
KdbxXmlReader reader(KeePass2::FILE_VERSION_3); KdbxXmlReader reader(KeePass2::FILE_VERSION_3_1);
reader.setStrictMode(true); reader.setStrictMode(true);
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
Database* db = reader.readDatabase(xmlFile); Database* db = reader.readDatabase(xmlFile);

68
tests/TestKdbx2.cpp Normal file
View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#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>
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<Database> 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"));
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -15,24 +15,18 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#ifndef KEEPASSX_TESTKEEPASS2READER_H #ifndef KEEPASSXC_TEST_KDBX2_H
#define KEEPASSX_TESTKEEPASS2READER_H #define KEEPASSXC_TEST_KDBX2_H
#include <QObject> #include <QObject>
class TestKeePass2Reader : public QObject class TestKdbx2 : public QObject
{ {
Q_OBJECT Q_OBJECT
private slots: private slots:
void initTestCase(); void initTestCase();
void testNonAscii();
void testCompressed();
void testProtectedStrings();
void testBrokenHeaderHash();
void testFormat200(); void testFormat200();
void testFormat300();
void testFormat400();
}; };
#endif // KEEPASSX_TESTKEEPASS2READER_H #endif // KEEPASSXC_TEST_KDBX2_H

View File

@ -17,31 +17,27 @@
#include "TestKdbx3.h" #include "TestKdbx3.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/Crypto.h" #include "keys/PasswordKey.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
#include "format/KeePass2Reader.h"
#include "format/KeePass2Writer.h"
#include "format/KdbxXmlReader.h" #include "format/KdbxXmlReader.h"
#include "format/KdbxXmlWriter.h" #include "format/KdbxXmlWriter.h"
#include "format/KeePass2Repair.h"
#include "config-keepassx-tests.h" #include "config-keepassx-tests.h"
#include <QTest> #include <QTest>
QTEST_GUILESS_MAIN(TestKdbx3) 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); reader.setStrictMode(strictMode);
auto db = reader.readDatabase(path); auto db = reader.readDatabase(path);
hasError = reader.hasError(); hasError = reader.hasError();
@ -49,9 +45,9 @@ Database* TestKdbx3::readDatabase(QString path, bool strictMode, bool& hasError,
return db; 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); reader.setStrictMode(strictMode);
auto db = reader.readDatabase(buf); auto db = reader.readDatabase(buf);
hasError = reader.hasError(); hasError = reader.hasError();
@ -59,10 +55,155 @@ Database* TestKdbx3::readDatabase(QBuffer* buf, bool strictMode, bool& hasError,
return db; 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); writer.writeDatabase(buf, db);
hasError = writer.hasError(); hasError = writer.hasError();
errorString = writer.errorString(); errorString = writer.errorString();
} }
void TestKdbx3::readKdbx(QIODevice* device, CompositeKey const& key, QScopedPointer<Database>& 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<Database>& 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<Database> 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<Database> 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<Database> 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<Database> 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<Database> 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<Database> 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<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"));
}

View File

@ -18,19 +18,33 @@
#ifndef KEEPASSXC_TEST_KDBX3_H #ifndef KEEPASSXC_TEST_KDBX3_H
#define 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 Q_OBJECT
private slots: private slots:
virtual void initTestCase() override; void testNonAscii();
void testCompressed();
void testProtectedStrings();
void testBrokenHeaderHash();
void testFormat300();
void testKdbxRepair();
protected: protected:
virtual Database* readDatabase(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) override; void initTestCaseImpl() 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; 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<Database>& db,
bool& hasError, QString& errorString) override;
void readKdbx(const QString& path, CompositeKey const& key, QScopedPointer<Database>& db,
bool& hasError, QString& errorString) override;
void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) override;
}; };
#endif // KEEPASSXC_TEST_KDBX3_H #endif // KEEPASSXC_TEST_KDBX3_H

View File

@ -17,8 +17,10 @@
#include "TestKdbx4.h" #include "TestKdbx4.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/Crypto.h" #include "keys/PasswordKey.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
#include "format/KeePass2Reader.h"
#include "format/KeePass2Writer.h"
#include "format/KdbxXmlReader.h" #include "format/KdbxXmlReader.h"
#include "format/KdbxXmlWriter.h" #include "format/KdbxXmlWriter.h"
#include "config-keepassx-tests.h" #include "config-keepassx-tests.h"
@ -27,21 +29,15 @@
QTEST_GUILESS_MAIN(TestKdbx4) QTEST_GUILESS_MAIN(TestKdbx4)
void TestKdbx4::initTestCase() void TestKdbx4::initTestCaseImpl()
{ {
QVERIFY(Crypto::init()); m_xmlDb->changeKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
m_kdbxSourceDb->changeKdf(KeePass2::uuidToKdf(KeePass2::KDF_ARGON2));
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* 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); reader.setStrictMode(strictMode);
auto db = reader.readDatabase(path); auto db = reader.readDatabase(path);
hasError = reader.hasError(); hasError = reader.hasError();
@ -49,9 +45,9 @@ Database* TestKdbx4::readDatabase(QString path, bool strictMode, bool& hasError,
return db; 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); reader.setStrictMode(strictMode);
auto db = reader.readDatabase(buf); auto db = reader.readDatabase(buf);
hasError = reader.hasError(); hasError = reader.hasError();
@ -59,10 +55,135 @@ Database* TestKdbx4::readDatabase(QBuffer* buf, bool strictMode, bool& hasError,
return db; 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); writer.writeDatabase(buf, db);
hasError = writer.hasError(); hasError = writer.hasError();
errorString = writer.errorString(); errorString = writer.errorString();
} }
void TestKdbx4::readKdbx(QIODevice* device, CompositeKey const& key, QScopedPointer<Database>& 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<Database>& 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<Database> 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<Database> 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<Database> 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<Uuid>("kdfUuid");
QTest::addColumn<Uuid>("cipherUuid");
QTest::addColumn<quint32>("expectedVersion");
auto constexpr kdbx3 = KeePass2::FILE_VERSION_3_1 & KeePass2::FILE_VERSION_CRITICAL_MASK;
auto constexpr kdbx4 = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
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;
}

View File

@ -18,19 +18,29 @@
#ifndef KEEPASSXC_TEST_KDBX4_H #ifndef KEEPASSXC_TEST_KDBX4_H
#define 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 Q_OBJECT
private slots: private slots:
virtual void initTestCase() override; void testFormat400();
void testFormat400Upgrade();
void testFormat400Upgrade_data();
protected: protected:
virtual Database* readDatabase(QBuffer* buf, bool strictMode, bool& hasError, QString& errorString) override; void initTestCaseImpl() 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; 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<Database>& db,
bool& hasError, QString& errorString) override;
void readKdbx(QIODevice* device, CompositeKey const& key, QScopedPointer<Database>& db,
bool& hasError, QString& errorString) override;
void writeKdbx(QIODevice* device, Database* db, bool& hasError, QString& errorString) override;
}; };
#endif // KEEPASSXC_TEST_KDBX4_H #endif // KEEPASSXC_TEST_KDBX4_H

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -15,101 +15,105 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>. * along with this program. If not, see <http://www.gnu.org/licenses/>.
*/ */
#include "TestKeePass2XmlReader.h" #include "TestKeePass2Format.h"
#include "core/Group.h" #include "core/Group.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/Crypto.h"
#include "keys/PasswordKey.h"
#include "format/KdbxXmlReader.h" #include "format/KdbxXmlReader.h"
#include "format/KdbxXmlWriter.h"
#include "FailDevice.h"
#include "config-keepassx-tests.h" #include "config-keepassx-tests.h"
#include <QFile> #include <QFile>
#include <QTest> #include <QTest>
namespace QTest { void TestKeePass2Format::initTestCase()
template<>
char* toString(const Uuid& uuid)
{ {
QByteArray ba = "Uuid("; QVERIFY(Crypto::init());
ba += uuid.toBase64().toLatin1().constData();
ba += ")";
return qstrdup(ba.constData());
}
template<> // read raw XML database
char* toString(const Group::TriState& triState) bool hasError;
{ QString errorString;
QString value; 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) { // construct and write KDBX to buffer
value = "null"; CompositeKey key;
} else if (triState == Group::Enable) { key.addKey(PasswordKey("test"));
value = "true";
} else { m_kdbxSourceDb.reset(new Database());
value = "false"; 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); QCOMPARE(m_xmlDb->metadata()->generator(), QString("KeePass"));
QTime time(hour, min, second); QCOMPARE(m_xmlDb->metadata()->name(), QString("ANAME"));
return QDateTime(date, time, Qt::UTC); 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; QCOMPARE(m_xmlDb->metadata()->customIcons().size(), 1);
for (auto i : str) {
result.append(static_cast<char>(i.unicode() >> 8));
result.append(static_cast<char>(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);
Uuid uuid = Uuid::fromBase64("++vyI+daLk6omox4a6kQGA=="); Uuid uuid = Uuid::fromBase64("++vyI+daLk6omox4a6kQGA==");
QVERIFY(m_db->metadata()->customIcons().contains(uuid)); QVERIFY(m_xmlDb->metadata()->customIcons().contains(uuid));
QImage icon = m_db->metadata()->customIcon(uuid); QImage icon = m_xmlDb->metadata()->customIcon(uuid);
QCOMPARE(icon.width(), 16); QCOMPARE(icon.width(), 16);
QCOMPARE(icon.height(), 16); QCOMPARE(icon.height(), 16);
@ -123,18 +127,18 @@ void TestKeePass2XmlReader::testCustomIcons()
} }
} }
void TestKeePass2XmlReader::testCustomData() void TestKeePass2Format::testXmlCustomData()
{ {
QHash<QString, QString> customFields = m_db->metadata()->customFields(); QHash<QString, QString> customFields = m_xmlDb->metadata()->customFields();
QCOMPARE(customFields.size(), 2); QCOMPARE(customFields.size(), 2);
QCOMPARE(customFields.value("A Sample Test Key"), QString("valu")); QCOMPARE(customFields.value("A Sample Test Key"), QString("valu"));
QCOMPARE(customFields.value("custom key"), QString("blub")); 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); QVERIFY(group);
QCOMPARE(group->uuid().toBase64(), QString("lmU+9n0aeESKZvcEze+bRg==")); QCOMPARE(group->uuid().toBase64(), QString("lmU+9n0aeESKZvcEze+bRg=="));
QCOMPARE(group->name(), QString("NewDatabase")); QCOMPARE(group->name(), QString("NewDatabase"));
@ -156,14 +160,14 @@ void TestKeePass2XmlReader::testGroupRoot()
QCOMPARE(group->lastTopVisibleEntry()->uuid().toBase64(), QString("+wSUOv6qf0OzW8/ZHAs2sA==")); QCOMPARE(group->lastTopVisibleEntry()->uuid().toBase64(), QString("+wSUOv6qf0OzW8/ZHAs2sA=="));
QCOMPARE(group->children().size(), 3); 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); 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->uuid().toBase64(), QString("AaUYVdXsI02h4T1RiAlgtg=="));
QCOMPARE(group->name(), QString("General")); QCOMPARE(group->name(), QString("General"));
@ -177,9 +181,9 @@ void TestKeePass2XmlReader::testGroup1()
QVERIFY(!group->lastTopVisibleEntry()); 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->uuid().toBase64(), QString("1h4NtL5DK0yVyvaEnN//4A=="));
QCOMPARE(group->name(), QString("Windows")); QCOMPARE(group->name(), QString("Windows"));
@ -197,9 +201,9 @@ void TestKeePass2XmlReader::testGroup2()
QCOMPARE(entry->title(), QString("Subsub Entry")); 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->uuid().toBase64(), QString("+wSUOv6qf0OzW8/ZHAs2sA=="));
QCOMPARE(entry->historyItems().size(), 2); QCOMPARE(entry->historyItems().size(), 2);
@ -259,9 +263,9 @@ void TestKeePass2XmlReader::testEntry1()
QCOMPARE(assoc1.sequence, QString("")); 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->uuid().toBase64(), QString("4jbADG37hkiLh2O0qUdaOQ=="));
QCOMPARE(entry->iconNumber(), 0); QCOMPARE(entry->iconNumber(), 0);
@ -307,9 +311,9 @@ void TestKeePass2XmlReader::testEntry2()
QCOMPARE(assoc2.sequence, QString("{Title}{UserName} test")); 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); QCOMPARE(entryMain->historyItems().size(), 2);
{ {
@ -333,9 +337,9 @@ void TestKeePass2XmlReader::testEntryHistory()
} }
} }
void TestKeePass2XmlReader::testDeletedObjects() void TestKeePass2Format::testXmlDeletedObjects()
{ {
QList<DeletedObject> objList = m_db->deletedObjects(); QList<DeletedObject> objList = m_xmlDb->deletedObjects();
DeletedObject delObj; DeletedObject delObj;
delObj = objList.takeFirst(); delObj = objList.takeFirst();
@ -349,7 +353,7 @@ void TestKeePass2XmlReader::testDeletedObjects()
QVERIFY(objList.isEmpty()); QVERIFY(objList.isEmpty());
} }
void TestKeePass2XmlReader::testBroken() void TestKeePass2Format::testXmlBroken()
{ {
QFETCH(QString, baseName); QFETCH(QString, baseName);
QFETCH(bool, strictMode); QFETCH(bool, strictMode);
@ -359,14 +363,14 @@ void TestKeePass2XmlReader::testBroken()
QVERIFY(QFile::exists(xmlFile)); QVERIFY(QFile::exists(xmlFile));
bool hasError; bool hasError;
QString errorString; QString errorString;
QScopedPointer<Database> db(readDatabase(xmlFile, strictMode, hasError, errorString)); QScopedPointer<Database> db(readXml(xmlFile, strictMode, hasError, errorString));
if (hasError) { if (hasError) {
qWarning("Reader error: %s", qPrintable(errorString)); qWarning("Reader error: %s", qPrintable(errorString));
} }
QCOMPARE(hasError, expectError); QCOMPARE(hasError, expectError);
} }
void TestKeePass2XmlReader::testBroken_data() void TestKeePass2Format::testXmlBroken_data()
{ {
QTest::addColumn<QString>("baseName"); QTest::addColumn<QString>("baseName");
QTest::addColumn<bool>("strictMode"); QTest::addColumn<bool>("strictMode");
@ -391,21 +395,21 @@ void TestKeePass2XmlReader::testBroken_data()
QTest::newRow("BrokenDifferentEntryHistoryUuid (not strict)") << "BrokenDifferentEntryHistoryUuid" << false << false; 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"); QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "EmptyUuids");
QVERIFY(QFile::exists(xmlFile)); QVERIFY(QFile::exists(xmlFile));
bool hasError; bool hasError;
QString errorString; QString errorString;
QScopedPointer<Database> dbp(readDatabase(xmlFile, true, hasError, errorString)); QScopedPointer<Database> dbp(readXml(xmlFile, true, hasError, errorString));
if (hasError) { if (hasError) {
qWarning("Reader error: %s", qPrintable(errorString)); qWarning("Reader error: %s", qPrintable(errorString));
} }
QVERIFY(!hasError); QVERIFY(!hasError);
} }
void TestKeePass2XmlReader::testInvalidXmlChars() void TestKeePass2Format::testXmlInvalidXmlChars()
{ {
QScopedPointer<Database> dbWrite(new Database()); QScopedPointer<Database> dbWrite(new Database());
@ -444,11 +448,11 @@ void TestKeePass2XmlReader::testInvalidXmlChars()
buffer.open(QIODevice::ReadWrite); buffer.open(QIODevice::ReadWrite);
bool hasError; bool hasError;
QString errorString; QString errorString;
writeDatabase(&buffer, dbWrite.data(), hasError, errorString); writeXml(&buffer, dbWrite.data(), hasError, errorString);
QVERIFY(!hasError); QVERIFY(!hasError);
buffer.seek(0); buffer.seek(0);
QScopedPointer<Database> dbRead(readDatabase(&buffer, true, hasError, errorString)); QScopedPointer<Database> dbRead(readXml(&buffer, true, hasError, errorString));
if (hasError) { if (hasError) {
qWarning("Database read error: %s", qPrintable(errorString)); qWarning("Database read error: %s", qPrintable(errorString));
} }
@ -470,13 +474,13 @@ void TestKeePass2XmlReader::testInvalidXmlChars()
QCOMPARE(strToBytes(attrRead->value("SurrogateValid2")), strToBytes(strSurrogateValid2)); 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"); QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid");
QVERIFY(QFile::exists(xmlFile)); QVERIFY(QFile::exists(xmlFile));
bool hasError; bool hasError;
QString errorString; QString errorString;
QScopedPointer<Database> db(readDatabase(xmlFile, false, hasError, errorString)); QScopedPointer<Database> db(readXml(xmlFile, false, hasError, errorString));
if (hasError) { if (hasError) {
qWarning("Database read error: %s", qPrintable(errorString)); qWarning("Database read error: %s", qPrintable(errorString));
} }
@ -494,3 +498,125 @@ void TestKeePass2XmlReader::testRepairUuidHistoryItem()
QVERIFY(!historyItem->uuid().isNull()); QVERIFY(!historyItem->uuid().isNull());
QCOMPARE(historyItem->uuid(), entry->uuid()); 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<Database> 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<char>(i.unicode() >> 8));
result.append(static_cast<char>(i.unicode() & 0xFF));
}
return result;
}

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSXC_TESTKEEPASS2FORMAT_H
#define KEEPASSXC_TESTKEEPASS2FORMAT_H
#include <QDateTime>
#include <QObject>
#include <QBuffer>
#include <QScopedPointer>
#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<Database>& db,
bool& hasError, QString& errorString) = 0;
virtual void readKdbx(const QString& path, CompositeKey const& key, QScopedPointer<Database>& 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<Database> m_xmlDb;
QScopedPointer<Database> m_kdbxSourceDb;
QScopedPointer<Database> m_kdbxTargetDb;
private:
QBuffer m_kdbxTargetBuffer;
};
#endif // KEEPASSXC_TESTKEEPASS2FORMAT_H

View File

@ -1,180 +0,0 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "TestKeePass2Reader.h"
#include <QTest>
#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"));
}

View File

@ -1,168 +0,0 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#include "TestKeePass2Writer.h"
#include <QBuffer>
#include <QFile>
#include <QTest>
#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;
}

View File

@ -1,44 +0,0 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_TESTKEEPASS2WRITER_H
#define KEEPASSX_TESTKEEPASS2WRITER_H
#include <QObject>
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

View File

@ -1,64 +0,0 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* 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 <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_TESTKEEPASS2XMLREADER_H
#define KEEPASSX_TESTKEEPASS2XMLREADER_H
#include <QDateTime>
#include <QObject>
#include <QBuffer>
#include <QScopedPointer>
#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<Database> m_db;
};
#endif // KEEPASSX_TESTKEEPASS2XMLREADER_H