mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Refactor and extend file format tests
This commit is contained in:
parent
cdefc7ea9b
commit
a595239624
@ -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);
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
@ -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})
|
||||||
|
|
||||||
|
@ -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
68
tests/TestKdbx2.cpp
Normal 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"));
|
||||||
|
}
|
@ -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
|
@ -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"));
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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;
|
||||||
|
}
|
91
tests/TestKeePass2Format.h
Normal file
91
tests/TestKeePass2Format.h
Normal 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
|
@ -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"));
|
|
||||||
}
|
|
@ -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;
|
|
||||||
}
|
|
@ -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
|
|
@ -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
|
|
Loading…
Reference in New Issue
Block a user