Rename KeePass2{,Xml}{R,W} to Kdbx3{,Xml}{R,W}, and add a redirection class

This class will in future select Kdbx4{R,W} as appropriate.
This commit is contained in:
angelsl 2017-11-13 02:55:03 +08:00 committed by Jonathan White
parent e5ec585f98
commit 3461cbfb06
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
20 changed files with 1099 additions and 734 deletions

View File

@ -84,11 +84,13 @@ set(keepassx_SOURCES
format/KeePass1Reader.cpp format/KeePass1Reader.cpp
format/KeePass2.cpp format/KeePass2.cpp
format/KeePass2RandomStream.cpp format/KeePass2RandomStream.cpp
format/KeePass2Reader.cpp
format/KeePass2Repair.cpp format/KeePass2Repair.cpp
format/KeePass2Reader.cpp
format/KeePass2Writer.cpp format/KeePass2Writer.cpp
format/KeePass2XmlReader.cpp format/Kdbx3Reader.cpp
format/KeePass2XmlWriter.cpp format/Kdbx3Writer.cpp
format/Kdbx3XmlReader.cpp
format/Kdbx3XmlWriter.cpp
gui/AboutDialog.cpp gui/AboutDialog.cpp
gui/Application.cpp gui/Application.cpp
gui/CategoryListWidget.cpp gui/CategoryListWidget.cpp

394
src/format/Kdbx3Reader.cpp Normal file
View File

@ -0,0 +1,394 @@
/*
* 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 "Kdbx3Reader.h"
#include <QBuffer>
#include <QFile>
#include <QIODevice>
#include "core/Database.h"
#include "core/Endian.h"
#include "crypto/CryptoHash.h"
#include "crypto/kdf/AesKdf.h"
#include "format/KeePass1.h"
#include "format/KeePass2.h"
#include "format/KeePass2RandomStream.h"
#include "format/Kdbx3XmlReader.h"
#include "streams/HashedBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/StoreDataStream.h"
#include "streams/SymmetricCipherStream.h"
Kdbx3Reader::Kdbx3Reader()
: m_device(nullptr)
, m_headerStream(nullptr)
, m_headerEnd(false)
, m_db(nullptr)
{
}
Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
{
QScopedPointer<Database> db(new Database());
m_db = db.data();
m_device = device;
m_error = false;
m_errorStr.clear();
m_headerEnd = false;
m_xmlData.clear();
m_masterSeed.clear();
m_encryptionIV.clear();
m_streamStartBytes.clear();
m_protectedStreamKey.clear();
StoreDataStream headerStream(m_device);
headerStream.open(QIODevice::ReadOnly);
m_headerStream = &headerStream;
bool ok;
quint32 signature1 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok);
if (!ok || signature1 != KeePass2::SIGNATURE_1) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 signature2 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok);
if (ok && signature2 == KeePass1::SIGNATURE_2) {
raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
"This is a one-way migration. You won't be able to open the imported "
"database with the old KeePassX 0.4 version."));
return nullptr;
}
else if (!ok || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 version = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok)
& KeePass2::FILE_VERSION_CRITICAL_MASK;
quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK;
if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) {
raiseError(tr("Unsupported KeePass KDBX 2 or 3 database version."));
return nullptr;
}
while (readHeaderField() && !hasError()) {
}
headerStream.close();
if (hasError()) {
return nullptr;
}
// check if all required headers were present
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty()
|| m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty()
|| m_db->cipher().isNull()) {
raiseError("missing database headers");
return nullptr;
}
if (!m_db->setKey(key, false)) {
raiseError(tr("Unable to calculate master key"));
return nullptr;
}
if (m_db->challengeMasterSeed(m_masterSeed) == false) {
raiseError(tr("Unable to issue challenge-response."));
return nullptr;
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
hash.addData(m_db->challengeResponseKey());
hash.addData(m_db->transformedMasterKey());
QByteArray finalKey = hash.result();
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
SymmetricCipherStream cipherStream(m_device, cipher,
SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString());
return nullptr;
}
if (!cipherStream.open(QIODevice::ReadOnly)) {
raiseError(cipherStream.errorString());
return nullptr;
}
QByteArray realStart = cipherStream.read(32);
if (realStart != m_streamStartBytes) {
raiseError(tr("Wrong key or database file is corrupt."));
return nullptr;
}
HashedBlockStream hashedStream(&cipherStream);
if (!hashedStream.open(QIODevice::ReadOnly)) {
raiseError(hashedStream.errorString());
return nullptr;
}
QIODevice* xmlDevice;
QScopedPointer<QtIOCompressor> ioCompressor;
if (m_db->compressionAlgo() == Database::CompressionNone) {
xmlDevice = &hashedStream;
}
else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
if (!ioCompressor->open(QIODevice::ReadOnly)) {
raiseError(ioCompressor->errorString());
return nullptr;
}
xmlDevice = ioCompressor.data();
}
KeePass2RandomStream randomStream(KeePass2::Salsa20);
if (!randomStream.init(m_protectedStreamKey)) {
raiseError(randomStream.errorString());
return nullptr;
}
QScopedPointer<QBuffer> buffer;
if (m_saveXml) {
m_xmlData = xmlDevice->readAll();
buffer.reset(new QBuffer(&m_xmlData));
buffer->open(QIODevice::ReadOnly);
xmlDevice = buffer.data();
}
Kdbx3XmlReader xmlReader;
xmlReader.readDatabase(xmlDevice, m_db, &randomStream);
if (xmlReader.hasError()) {
raiseError(xmlReader.errorString());
if (keepDatabase) {
return db.take();
}
else {
return nullptr;
}
}
Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty());
if (!xmlReader.headerHash().isEmpty()) {
QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256);
if (headerHash != xmlReader.headerHash()) {
raiseError("Header doesn't match hash");
return nullptr;
}
}
return db.take();
}
bool Kdbx3Reader::readHeaderField()
{
QByteArray fieldIDArray = m_headerStream->read(1);
if (fieldIDArray.size() != 1) {
raiseError("Invalid header id size");
return false;
}
quint8 fieldID = fieldIDArray.at(0);
bool ok;
quint16 fieldLen = Endian::readUInt16(m_headerStream, KeePass2::BYTEORDER, &ok);
if (!ok) {
raiseError("Invalid header field length");
return false;
}
QByteArray fieldData;
if (fieldLen != 0) {
fieldData = m_headerStream->read(fieldLen);
if (fieldData.size() != fieldLen) {
raiseError("Invalid header data length");
return false;
}
}
switch (fieldID) {
case KeePass2::EndOfHeader:
m_headerEnd = true;
break;
case KeePass2::CipherID:
setCipher(fieldData);
break;
case KeePass2::CompressionFlags:
setCompressionFlags(fieldData);
break;
case KeePass2::MasterSeed:
setMasterSeed(fieldData);
break;
case KeePass2::TransformSeed:
setTransformSeed(fieldData);
break;
case KeePass2::TransformRounds:
setTransformRounds(fieldData);
break;
case KeePass2::EncryptionIV:
setEncryptionIV(fieldData);
break;
case KeePass2::ProtectedStreamKey:
setProtectedStreamKey(fieldData);
break;
case KeePass2::StreamStartBytes:
setStreamStartBytes(fieldData);
break;
case KeePass2::InnerRandomStreamID:
setInnerRandomStreamID(fieldData);
break;
default:
qWarning("Unknown header field read: id=%d", fieldID);
break;
}
return !m_headerEnd;
}
void Kdbx3Reader::setCipher(const QByteArray& data)
{
if (data.size() != Uuid::Length) {
raiseError("Invalid cipher uuid length");
} else {
Uuid uuid(data);
if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) {
raiseError("Unsupported cipher");
} else {
m_db->setCipher(uuid);
}
}
}
void Kdbx3Reader::setCompressionFlags(const QByteArray& data)
{
if (data.size() != 4) {
raiseError("Invalid compression flags length");
}
else {
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
if (id > Database::CompressionAlgorithmMax) {
raiseError("Unsupported compression algorithm");
}
else {
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
}
}
}
void Kdbx3Reader::setMasterSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid master seed size");
}
else {
m_masterSeed = data;
}
}
void Kdbx3Reader::setTransformSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid transform seed size");
}
else {
AesKdf* aesKdf;
if (m_db->kdf()->type() == Kdf::Type::AES) {
aesKdf = static_cast<AesKdf*>(m_db->kdf());
} else {
aesKdf = new AesKdf();
m_db->setKdf(aesKdf);
}
aesKdf->setSeed(data);
}
}
void Kdbx3Reader::setTransformRounds(const QByteArray& data)
{
if (data.size() != 8) {
raiseError("Invalid transform rounds size");
}
else {
quint64 rounds = Endian::bytesToUInt64(data, KeePass2::BYTEORDER);
AesKdf* aesKdf;
if (m_db->kdf()->type() == Kdf::Type::AES) {
aesKdf = static_cast<AesKdf*>(m_db->kdf());
} else {
aesKdf = new AesKdf();
m_db->setKdf(aesKdf);
}
aesKdf->setRounds(rounds);
}
}
void Kdbx3Reader::setEncryptionIV(const QByteArray& data)
{
m_encryptionIV = data;
}
void Kdbx3Reader::setProtectedStreamKey(const QByteArray& data)
{
m_protectedStreamKey = data;
}
void Kdbx3Reader::setStreamStartBytes(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid start bytes size");
}
else {
m_streamStartBytes = data;
}
}
void Kdbx3Reader::setInnerRandomStreamID(const QByteArray& data)
{
if (data.size() != 4) {
raiseError("Invalid random stream id size");
} else {
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
if (irsAlgo == KeePass2::InvalidProtectedStreamAlgo || irsAlgo == KeePass2::ArcFourVariant) {
raiseError("Invalid inner random stream cipher");
} else {
m_irsAlgo = irsAlgo;
}
}
}

62
src/format/Kdbx3Reader.h Normal file
View File

@ -0,0 +1,62 @@
/*
* 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_KDBX3READER_H
#define KEEPASSX_KDBX3READER_H
#include <QCoreApplication>
#include "format/KeePass2Reader.h"
#include "keys/CompositeKey.h"
class Database;
class QIODevice;
class Kdbx3Reader : public BaseKeePass2Reader
{
Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader)
public:
Kdbx3Reader();
using BaseKeePass2Reader::readDatabase;
virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override;
private:
bool readHeaderField();
void setCipher(const QByteArray& data);
void setCompressionFlags(const QByteArray& data);
void setMasterSeed(const QByteArray& data);
void setTransformSeed(const QByteArray& data);
void setTransformRounds(const QByteArray& data);
void setEncryptionIV(const QByteArray& data);
void setProtectedStreamKey(const QByteArray& data);
void setStreamStartBytes(const QByteArray& data);
void setInnerRandomStreamID(const QByteArray& data);
QIODevice* m_device;
QIODevice* m_headerStream;
bool m_headerEnd;
Database* m_db;
QByteArray m_masterSeed;
QByteArray m_encryptionIV;
QByteArray m_streamStartBytes;
};
#endif // KEEPASSX_KDBX3READER_H

187
src/format/Kdbx3Writer.cpp Normal file
View File

@ -0,0 +1,187 @@
/*
* 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 "Kdbx3Writer.h"
#include <QBuffer>
#include <QFile>
#include <QIODevice>
#include "core/Database.h"
#include "core/Endian.h"
#include "crypto/CryptoHash.h"
#include "crypto/kdf/AesKdf.h"
#include "crypto/Random.h"
#include "format/KeePass2RandomStream.h"
#include "format/Kdbx3XmlWriter.h"
#include "streams/HashedBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/SymmetricCipherStream.h"
#define CHECK_RETURN(x) if (!(x)) return;
#define CHECK_RETURN_FALSE(x) if (!(x)) return false;
Kdbx3Writer::Kdbx3Writer()
: m_device(0)
{
}
bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
{
m_error = false;
m_errorStr.clear();
QByteArray masterSeed = randomGen()->randomArray(32);
QByteArray encryptionIV = randomGen()->randomArray(16);
QByteArray protectedStreamKey = randomGen()->randomArray(32);
QByteArray startBytes = randomGen()->randomArray(32);
QByteArray endOfHeader = "\r\n\r\n";
if (db->challengeMasterSeed(masterSeed) == false) {
raiseError(tr("Unable to issue challenge-response."));
return false;
}
if (!db->setKey(db->key(), false, true)) {
raiseError(tr("Unable to calculate master key"));
return false;
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(masterSeed);
hash.addData(db->challengeResponseKey());
Q_ASSERT(!db->transformedMasterKey().isEmpty());
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
QBuffer header;
header.open(QIODevice::WriteOnly);
m_device = &header;
CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray()));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CompressionFlags,
Endian::int32ToBytes(db->compressionAlgo(),
KeePass2::BYTEORDER)));
AesKdf* kdf = static_cast<AesKdf*>(db->kdf());
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::MasterSeed, masterSeed));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformSeed, kdf->seed()));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformRounds,
Endian::int64ToBytes(kdf->rounds(),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EncryptionIV, encryptionIV));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::StreamStartBytes, startBytes));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::InnerRandomStreamID,
Endian::int32ToBytes(KeePass2::Salsa20,
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EndOfHeader, endOfHeader));
header.close();
m_device = device;
QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
CHECK_RETURN_FALSE(writeData(header.data()));
SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher());
SymmetricCipherStream cipherStream(device, algo,
SymmetricCipher::algorithmMode(algo), SymmetricCipher::Encrypt);
cipherStream.init(finalKey, encryptionIV);
if (!cipherStream.open(QIODevice::WriteOnly)) {
raiseError(cipherStream.errorString());
return false;
}
m_device = &cipherStream;
CHECK_RETURN_FALSE(writeData(startBytes));
HashedBlockStream hashedStream(&cipherStream);
if (!hashedStream.open(QIODevice::WriteOnly)) {
raiseError(hashedStream.errorString());
return false;
}
QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) {
m_device = &hashedStream;
}
else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
if (!ioCompressor->open(QIODevice::WriteOnly)) {
raiseError(ioCompressor->errorString());
return false;
}
m_device = ioCompressor.data();
}
KeePass2RandomStream randomStream(KeePass2::Salsa20);
if (!randomStream.init(protectedStreamKey)) {
raiseError(randomStream.errorString());
return false;
}
Kdbx3XmlWriter xmlWriter;
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
// Explicitly close/reset streams so they are flushed and we can detect
// errors. QIODevice::close() resets errorString() etc.
if (ioCompressor) {
ioCompressor->close();
}
if (!hashedStream.reset()) {
raiseError(hashedStream.errorString());
return false;
}
if (!cipherStream.reset()) {
raiseError(cipherStream.errorString());
return false;
}
if (xmlWriter.hasError()) {
raiseError(xmlWriter.errorString());
}
return true;
}
bool Kdbx3Writer::writeData(const QByteArray& data)
{
if (m_device->write(data) != data.size()) {
raiseError(m_device->errorString());
return false;
}
else {
return true;
}
}
bool Kdbx3Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data)
{
Q_ASSERT(data.size() <= 65535);
QByteArray fieldIdArr;
fieldIdArr[0] = fieldId;
CHECK_RETURN_FALSE(writeData(fieldIdArr));
CHECK_RETURN_FALSE(writeData(Endian::int16ToBytes(static_cast<quint16>(data.size()),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(data));
return true;
}

47
src/format/Kdbx3Writer.h Normal file
View File

@ -0,0 +1,47 @@
/*
* 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_KDBX3WRITER_H
#define KEEPASSX_KDBX3WRITER_H
#include <QCoreApplication>
#include "format/KeePass2.h"
#include "format/KeePass2Writer.h"
#include "keys/CompositeKey.h"
class Database;
class QIODevice;
class Kdbx3Writer : public BaseKeePass2Writer
{
Q_DECLARE_TR_FUNCTIONS(Kdbx3Writer)
public:
Kdbx3Writer();
using BaseKeePass2Writer::writeDatabase;
bool writeDatabase(QIODevice* device, Database* db);
private:
bool writeData(const QByteArray& data);
bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data);
QIODevice* m_device;
};
#endif // KEEPASSX_KDBX3WRITER_H

View File

@ -15,7 +15,7 @@
* 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 "KeePass2XmlReader.h" #include "Kdbx3XmlReader.h"
#include <QBuffer> #include <QBuffer>
#include <QFile> #include <QFile>
@ -30,7 +30,7 @@
typedef QPair<QString, QString> StringPair; typedef QPair<QString, QString> StringPair;
KeePass2XmlReader::KeePass2XmlReader() Kdbx3XmlReader::Kdbx3XmlReader()
: m_randomStream(nullptr) : m_randomStream(nullptr)
, m_db(nullptr) , m_db(nullptr)
, m_meta(nullptr) , m_meta(nullptr)
@ -40,12 +40,12 @@ KeePass2XmlReader::KeePass2XmlReader()
{ {
} }
void KeePass2XmlReader::setStrictMode(bool strictMode) void Kdbx3XmlReader::setStrictMode(bool strictMode)
{ {
m_strictMode = strictMode; m_strictMode = strictMode;
} }
void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) void Kdbx3XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream)
{ {
m_error = false; m_error = false;
m_errorStr.clear(); m_errorStr.clear();
@ -76,12 +76,12 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra
if (!m_xml.error()) { if (!m_xml.error()) {
if (!m_tmpParent->children().isEmpty()) { if (!m_tmpParent->children().isEmpty()) {
qWarning("KeePass2XmlReader::readDatabase: found %d invalid group reference(s)", qWarning("Kdbx3XmlReader::readDatabase: found %d invalid group reference(s)",
m_tmpParent->children().size()); m_tmpParent->children().size());
} }
if (!m_tmpParent->entries().isEmpty()) { if (!m_tmpParent->entries().isEmpty()) {
qWarning("KeePass2XmlReader::readDatabase: found %d invalid entry reference(s)", qWarning("Kdbx3XmlReader::readDatabase: found %d invalid entry reference(s)",
m_tmpParent->children().size()); m_tmpParent->children().size());
} }
} }
@ -97,7 +97,7 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra
if (!m_xml.error()) { if (!m_xml.error()) {
for (const QString& key : unusedKeys) { for (const QString& key : unusedKeys) {
qWarning("KeePass2XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); qWarning("Kdbx3XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key));
} }
} }
@ -127,26 +127,26 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra
delete m_tmpParent; delete m_tmpParent;
} }
Database* KeePass2XmlReader::readDatabase(QIODevice* device) Database* Kdbx3XmlReader::readDatabase(QIODevice* device)
{ {
Database* db = new Database(); Database* db = new Database();
readDatabase(device, db); readDatabase(device, db);
return db; return db;
} }
Database* KeePass2XmlReader::readDatabase(const QString& filename) Database* Kdbx3XmlReader::readDatabase(const QString& filename)
{ {
QFile file(filename); QFile file(filename);
file.open(QIODevice::ReadOnly); file.open(QIODevice::ReadOnly);
return readDatabase(&file); return readDatabase(&file);
} }
bool KeePass2XmlReader::hasError() bool Kdbx3XmlReader::hasError()
{ {
return m_error || m_xml.hasError(); return m_error || m_xml.hasError();
} }
QString KeePass2XmlReader::errorString() QString Kdbx3XmlReader::errorString()
{ {
if (m_error) { if (m_error) {
return m_errorStr; return m_errorStr;
@ -162,18 +162,18 @@ QString KeePass2XmlReader::errorString()
} }
} }
void KeePass2XmlReader::raiseError(const QString& errorMessage) void Kdbx3XmlReader::raiseError(const QString& errorMessage)
{ {
m_error = true; m_error = true;
m_errorStr = errorMessage; m_errorStr = errorMessage;
} }
QByteArray KeePass2XmlReader::headerHash() QByteArray Kdbx3XmlReader::headerHash()
{ {
return m_headerHash; return m_headerHash;
} }
bool KeePass2XmlReader::parseKeePassFile() bool Kdbx3XmlReader::parseKeePassFile()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile");
@ -202,7 +202,7 @@ bool KeePass2XmlReader::parseKeePassFile()
return rootParsedSuccessfully; return rootParsedSuccessfully;
} }
void KeePass2XmlReader::parseMeta() void Kdbx3XmlReader::parseMeta()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Meta"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Meta");
@ -303,7 +303,7 @@ void KeePass2XmlReader::parseMeta()
} }
} }
void KeePass2XmlReader::parseMemoryProtection() void Kdbx3XmlReader::parseMemoryProtection()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "MemoryProtection"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "MemoryProtection");
@ -329,7 +329,7 @@ void KeePass2XmlReader::parseMemoryProtection()
} }
} }
void KeePass2XmlReader::parseCustomIcons() void Kdbx3XmlReader::parseCustomIcons()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomIcons"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomIcons");
@ -343,7 +343,7 @@ void KeePass2XmlReader::parseCustomIcons()
} }
} }
void KeePass2XmlReader::parseIcon() void Kdbx3XmlReader::parseIcon()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Icon"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Icon");
@ -374,7 +374,7 @@ void KeePass2XmlReader::parseIcon()
} }
} }
void KeePass2XmlReader::parseBinaries() void Kdbx3XmlReader::parseBinaries()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries");
@ -393,7 +393,7 @@ void KeePass2XmlReader::parseBinaries()
} }
if (m_binaryPool.contains(id)) { if (m_binaryPool.contains(id)) {
qWarning("KeePass2XmlReader::parseBinaries: overwriting binary item \"%s\"", qWarning("Kdbx3XmlReader::parseBinaries: overwriting binary item \"%s\"",
qPrintable(id)); qPrintable(id));
} }
@ -405,7 +405,7 @@ void KeePass2XmlReader::parseBinaries()
} }
} }
void KeePass2XmlReader::parseCustomData() void Kdbx3XmlReader::parseCustomData()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData");
@ -419,7 +419,7 @@ void KeePass2XmlReader::parseCustomData()
} }
} }
void KeePass2XmlReader::parseCustomDataItem() void Kdbx3XmlReader::parseCustomDataItem()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item");
@ -450,7 +450,7 @@ void KeePass2XmlReader::parseCustomDataItem()
} }
} }
bool KeePass2XmlReader::parseRoot() bool Kdbx3XmlReader::parseRoot()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Root"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Root");
@ -486,7 +486,7 @@ bool KeePass2XmlReader::parseRoot()
return groupParsedSuccessfully; return groupParsedSuccessfully;
} }
Group* KeePass2XmlReader::parseGroup() Group* Kdbx3XmlReader::parseGroup()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group");
@ -525,7 +525,7 @@ Group* KeePass2XmlReader::parseGroup()
} }
else { else {
if (iconId >= DatabaseIcons::IconCount) { if (iconId >= DatabaseIcons::IconCount) {
qWarning("KeePass2XmlReader::parseGroup: icon id \"%d\" not supported", iconId); qWarning("Kdbx3XmlReader::parseGroup: icon id \"%d\" not supported", iconId);
} }
group->setIcon(iconId); group->setIcon(iconId);
} }
@ -623,7 +623,7 @@ Group* KeePass2XmlReader::parseGroup()
return group; return group;
} }
void KeePass2XmlReader::parseDeletedObjects() void Kdbx3XmlReader::parseDeletedObjects()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObjects"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObjects");
@ -637,7 +637,7 @@ void KeePass2XmlReader::parseDeletedObjects()
} }
} }
void KeePass2XmlReader::parseDeletedObject() void Kdbx3XmlReader::parseDeletedObject()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject");
@ -671,7 +671,7 @@ void KeePass2XmlReader::parseDeletedObject()
} }
} }
Entry* KeePass2XmlReader::parseEntry(bool history) Entry* Kdbx3XmlReader::parseEntry(bool history)
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry");
@ -793,7 +793,7 @@ Entry* KeePass2XmlReader::parseEntry(bool history)
return entry; return entry;
} }
void KeePass2XmlReader::parseEntryString(Entry* entry) void Kdbx3XmlReader::parseEntryString(Entry* entry)
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "String"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "String");
@ -855,7 +855,7 @@ void KeePass2XmlReader::parseEntryString(Entry* entry)
} }
} }
QPair<QString, QString> KeePass2XmlReader::parseEntryBinary(Entry* entry) QPair<QString, QString> Kdbx3XmlReader::parseEntryBinary(Entry* entry)
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binary"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binary");
@ -913,7 +913,7 @@ QPair<QString, QString> KeePass2XmlReader::parseEntryBinary(Entry* entry)
return poolRef; return poolRef;
} }
void KeePass2XmlReader::parseAutoType(Entry* entry) void Kdbx3XmlReader::parseAutoType(Entry* entry)
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "AutoType"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "AutoType");
@ -936,7 +936,7 @@ void KeePass2XmlReader::parseAutoType(Entry* entry)
} }
} }
void KeePass2XmlReader::parseAutoTypeAssoc(Entry* entry) void Kdbx3XmlReader::parseAutoTypeAssoc(Entry* entry)
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Association"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Association");
@ -966,7 +966,7 @@ void KeePass2XmlReader::parseAutoTypeAssoc(Entry* entry)
} }
} }
QList<Entry*> KeePass2XmlReader::parseEntryHistory() QList<Entry*> Kdbx3XmlReader::parseEntryHistory()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "History"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "History");
@ -984,7 +984,7 @@ QList<Entry*> KeePass2XmlReader::parseEntryHistory()
return historyItems; return historyItems;
} }
TimeInfo KeePass2XmlReader::parseTimes() TimeInfo Kdbx3XmlReader::parseTimes()
{ {
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times"); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times");
@ -1019,12 +1019,12 @@ TimeInfo KeePass2XmlReader::parseTimes()
return timeInfo; return timeInfo;
} }
QString KeePass2XmlReader::readString() QString Kdbx3XmlReader::readString()
{ {
return m_xml.readElementText(); return m_xml.readElementText();
} }
bool KeePass2XmlReader::readBool() bool Kdbx3XmlReader::readBool()
{ {
QString str = readString(); QString str = readString();
@ -1043,7 +1043,7 @@ bool KeePass2XmlReader::readBool()
} }
} }
QDateTime KeePass2XmlReader::readDateTime() QDateTime Kdbx3XmlReader::readDateTime()
{ {
QString str = readString(); QString str = readString();
QDateTime dt = QDateTime::fromString(str, Qt::ISODate); QDateTime dt = QDateTime::fromString(str, Qt::ISODate);
@ -1060,7 +1060,7 @@ QDateTime KeePass2XmlReader::readDateTime()
return dt; return dt;
} }
QColor KeePass2XmlReader::readColor() QColor Kdbx3XmlReader::readColor()
{ {
QString colorStr = readString(); QString colorStr = readString();
@ -1101,7 +1101,7 @@ QColor KeePass2XmlReader::readColor()
return color; return color;
} }
int KeePass2XmlReader::readNumber() int Kdbx3XmlReader::readNumber()
{ {
bool ok; bool ok;
int result = readString().toInt(&ok); int result = readString().toInt(&ok);
@ -1111,7 +1111,7 @@ int KeePass2XmlReader::readNumber()
return result; return result;
} }
Uuid KeePass2XmlReader::readUuid() Uuid Kdbx3XmlReader::readUuid()
{ {
QByteArray uuidBin = readBinary(); QByteArray uuidBin = readBinary();
if (uuidBin.isEmpty()) { if (uuidBin.isEmpty()) {
@ -1128,12 +1128,12 @@ Uuid KeePass2XmlReader::readUuid()
} }
} }
QByteArray KeePass2XmlReader::readBinary() QByteArray Kdbx3XmlReader::readBinary()
{ {
return QByteArray::fromBase64(readString().toLatin1()); return QByteArray::fromBase64(readString().toLatin1());
} }
QByteArray KeePass2XmlReader::readCompressedBinary() QByteArray Kdbx3XmlReader::readCompressedBinary()
{ {
QByteArray rawData = readBinary(); QByteArray rawData = readBinary();
@ -1151,7 +1151,7 @@ QByteArray KeePass2XmlReader::readCompressedBinary()
return result; return result;
} }
Group* KeePass2XmlReader::getGroup(const Uuid& uuid) Group* Kdbx3XmlReader::getGroup(const Uuid& uuid)
{ {
if (uuid.isNull()) { if (uuid.isNull()) {
return nullptr; return nullptr;
@ -1170,7 +1170,7 @@ Group* KeePass2XmlReader::getGroup(const Uuid& uuid)
} }
} }
Entry* KeePass2XmlReader::getEntry(const Uuid& uuid) Entry* Kdbx3XmlReader::getEntry(const Uuid& uuid)
{ {
if (uuid.isNull()) { if (uuid.isNull()) {
return nullptr; return nullptr;
@ -1189,8 +1189,8 @@ Entry* KeePass2XmlReader::getEntry(const Uuid& uuid)
} }
} }
void KeePass2XmlReader::skipCurrentElement() void Kdbx3XmlReader::skipCurrentElement()
{ {
qWarning("KeePass2XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString())); qWarning("Kdbx3XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString()));
m_xml.skipCurrentElement(); m_xml.skipCurrentElement();
} }

View File

@ -15,8 +15,8 @@
* 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_KEEPASS2XMLREADER_H #ifndef KEEPASSX_KDBX3XMLREADER_H
#define KEEPASSX_KEEPASS2XMLREADER_H #define KEEPASSX_KDBX3XMLREADER_H
#include <QColor> #include <QColor>
#include <QCoreApplication> #include <QCoreApplication>
@ -34,12 +34,12 @@ class Group;
class KeePass2RandomStream; class KeePass2RandomStream;
class Metadata; class Metadata;
class KeePass2XmlReader class Kdbx3XmlReader
{ {
Q_DECLARE_TR_FUNCTIONS(KeePass2XmlReader) Q_DECLARE_TR_FUNCTIONS(Kdbx3XmlReader)
public: public:
KeePass2XmlReader(); Kdbx3XmlReader();
Database* readDatabase(QIODevice* device); Database* readDatabase(QIODevice* device);
void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr); void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
Database* readDatabase(const QString& filename); Database* readDatabase(const QString& filename);
@ -98,4 +98,4 @@ private:
bool m_strictMode; bool m_strictMode;
}; };
#endif // KEEPASSX_KEEPASS2XMLREADER_H #endif // KEEPASSX_KDBX3XMLREADER_H

View File

@ -15,7 +15,7 @@
* 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 "KeePass2XmlWriter.h" #include "Kdbx3XmlWriter.h"
#include <QBuffer> #include <QBuffer>
#include <QFile> #include <QFile>
@ -24,7 +24,7 @@
#include "format/KeePass2RandomStream.h" #include "format/KeePass2RandomStream.h"
#include "streams/QtIOCompressor" #include "streams/QtIOCompressor"
KeePass2XmlWriter::KeePass2XmlWriter() Kdbx3XmlWriter::Kdbx3XmlWriter()
: m_db(nullptr) : m_db(nullptr)
, m_meta(nullptr) , m_meta(nullptr)
, m_randomStream(nullptr) , m_randomStream(nullptr)
@ -35,7 +35,7 @@ KeePass2XmlWriter::KeePass2XmlWriter()
m_xml.setCodec("UTF-8"); m_xml.setCodec("UTF-8");
} }
void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, void Kdbx3XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream,
const QByteArray& headerHash) const QByteArray& headerHash)
{ {
m_db = db; m_db = db;
@ -63,24 +63,24 @@ void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2R
} }
} }
void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db) void Kdbx3XmlWriter::writeDatabase(const QString& filename, Database* db)
{ {
QFile file(filename); QFile file(filename);
file.open(QIODevice::WriteOnly|QIODevice::Truncate); file.open(QIODevice::WriteOnly|QIODevice::Truncate);
writeDatabase(&file, db); writeDatabase(&file, db);
} }
bool KeePass2XmlWriter::hasError() bool Kdbx3XmlWriter::hasError()
{ {
return m_error; return m_error;
} }
QString KeePass2XmlWriter::errorString() QString Kdbx3XmlWriter::errorString()
{ {
return m_errorStr; return m_errorStr;
} }
void KeePass2XmlWriter::generateIdMap() void Kdbx3XmlWriter::generateIdMap()
{ {
const QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true); const QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
int nextId = 0; int nextId = 0;
@ -96,7 +96,7 @@ void KeePass2XmlWriter::generateIdMap()
} }
} }
void KeePass2XmlWriter::writeMetadata() void Kdbx3XmlWriter::writeMetadata()
{ {
m_xml.writeStartElement("Meta"); m_xml.writeStartElement("Meta");
@ -132,7 +132,7 @@ void KeePass2XmlWriter::writeMetadata()
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeMemoryProtection() void Kdbx3XmlWriter::writeMemoryProtection()
{ {
m_xml.writeStartElement("MemoryProtection"); m_xml.writeStartElement("MemoryProtection");
@ -145,7 +145,7 @@ void KeePass2XmlWriter::writeMemoryProtection()
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeCustomIcons() void Kdbx3XmlWriter::writeCustomIcons()
{ {
m_xml.writeStartElement("CustomIcons"); m_xml.writeStartElement("CustomIcons");
@ -157,7 +157,7 @@ void KeePass2XmlWriter::writeCustomIcons()
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) void Kdbx3XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
{ {
m_xml.writeStartElement("Icon"); m_xml.writeStartElement("Icon");
@ -174,7 +174,7 @@ void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeBinaries() void Kdbx3XmlWriter::writeBinaries()
{ {
m_xml.writeStartElement("Binaries"); m_xml.writeStartElement("Binaries");
@ -216,7 +216,7 @@ void KeePass2XmlWriter::writeBinaries()
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeCustomData() void Kdbx3XmlWriter::writeCustomData()
{ {
m_xml.writeStartElement("CustomData"); m_xml.writeStartElement("CustomData");
@ -229,7 +229,7 @@ void KeePass2XmlWriter::writeCustomData()
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeCustomDataItem(const QString& key, const QString& value) void Kdbx3XmlWriter::writeCustomDataItem(const QString& key, const QString& value)
{ {
m_xml.writeStartElement("Item"); m_xml.writeStartElement("Item");
@ -239,7 +239,7 @@ void KeePass2XmlWriter::writeCustomDataItem(const QString& key, const QString& v
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeRoot() void Kdbx3XmlWriter::writeRoot()
{ {
Q_ASSERT(m_db->rootGroup()); Q_ASSERT(m_db->rootGroup());
@ -251,7 +251,7 @@ void KeePass2XmlWriter::writeRoot()
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeGroup(const Group* group) void Kdbx3XmlWriter::writeGroup(const Group* group)
{ {
Q_ASSERT(!group->uuid().isNull()); Q_ASSERT(!group->uuid().isNull());
@ -288,7 +288,7 @@ void KeePass2XmlWriter::writeGroup(const Group* group)
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeTimes(const TimeInfo& ti) void Kdbx3XmlWriter::writeTimes(const TimeInfo& ti)
{ {
m_xml.writeStartElement("Times"); m_xml.writeStartElement("Times");
@ -303,7 +303,7 @@ void KeePass2XmlWriter::writeTimes(const TimeInfo& ti)
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeDeletedObjects() void Kdbx3XmlWriter::writeDeletedObjects()
{ {
m_xml.writeStartElement("DeletedObjects"); m_xml.writeStartElement("DeletedObjects");
@ -315,7 +315,7 @@ void KeePass2XmlWriter::writeDeletedObjects()
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeDeletedObject(const DeletedObject& delObj) void Kdbx3XmlWriter::writeDeletedObject(const DeletedObject& delObj)
{ {
m_xml.writeStartElement("DeletedObject"); m_xml.writeStartElement("DeletedObject");
@ -325,7 +325,7 @@ void KeePass2XmlWriter::writeDeletedObject(const DeletedObject& delObj)
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeEntry(const Entry* entry) void Kdbx3XmlWriter::writeEntry(const Entry* entry)
{ {
Q_ASSERT(!entry->uuid().isNull()); Q_ASSERT(!entry->uuid().isNull());
@ -407,7 +407,7 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeAutoType(const Entry* entry) void Kdbx3XmlWriter::writeAutoType(const Entry* entry)
{ {
m_xml.writeStartElement("AutoType"); m_xml.writeStartElement("AutoType");
@ -423,7 +423,7 @@ void KeePass2XmlWriter::writeAutoType(const Entry* entry)
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc) void Kdbx3XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc)
{ {
m_xml.writeStartElement("Association"); m_xml.writeStartElement("Association");
@ -433,7 +433,7 @@ void KeePass2XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Associati
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeEntryHistory(const Entry* entry) void Kdbx3XmlWriter::writeEntryHistory(const Entry* entry)
{ {
m_xml.writeStartElement("History"); m_xml.writeStartElement("History");
@ -445,7 +445,7 @@ void KeePass2XmlWriter::writeEntryHistory(const Entry* entry)
m_xml.writeEndElement(); m_xml.writeEndElement();
} }
void KeePass2XmlWriter::writeString(const QString& qualifiedName, const QString& string) void Kdbx3XmlWriter::writeString(const QString& qualifiedName, const QString& string)
{ {
if (string.isEmpty()) { if (string.isEmpty()) {
m_xml.writeEmptyElement(qualifiedName); m_xml.writeEmptyElement(qualifiedName);
@ -455,12 +455,12 @@ void KeePass2XmlWriter::writeString(const QString& qualifiedName, const QString&
} }
} }
void KeePass2XmlWriter::writeNumber(const QString& qualifiedName, int number) void Kdbx3XmlWriter::writeNumber(const QString& qualifiedName, int number)
{ {
writeString(qualifiedName, QString::number(number)); writeString(qualifiedName, QString::number(number));
} }
void KeePass2XmlWriter::writeBool(const QString& qualifiedName, bool b) void Kdbx3XmlWriter::writeBool(const QString& qualifiedName, bool b)
{ {
if (b) { if (b) {
writeString(qualifiedName, "True"); writeString(qualifiedName, "True");
@ -470,7 +470,7 @@ void KeePass2XmlWriter::writeBool(const QString& qualifiedName, bool b)
} }
} }
void KeePass2XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime) void Kdbx3XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime)
{ {
Q_ASSERT(dateTime.isValid()); Q_ASSERT(dateTime.isValid());
Q_ASSERT(dateTime.timeSpec() == Qt::UTC); Q_ASSERT(dateTime.timeSpec() == Qt::UTC);
@ -485,12 +485,12 @@ void KeePass2XmlWriter::writeDateTime(const QString& qualifiedName, const QDateT
writeString(qualifiedName, dateTimeStr); writeString(qualifiedName, dateTimeStr);
} }
void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid) void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid)
{ {
writeString(qualifiedName, uuid.toBase64()); writeString(qualifiedName, uuid.toBase64());
} }
void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Group* group)
{ {
if (group) { if (group) {
writeUuid(qualifiedName, group->uuid()); writeUuid(qualifiedName, group->uuid());
@ -500,7 +500,7 @@ void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Group* gro
} }
} }
void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry)
{ {
if (entry) { if (entry) {
writeUuid(qualifiedName, entry->uuid()); writeUuid(qualifiedName, entry->uuid());
@ -510,12 +510,12 @@ void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Entry* ent
} }
} }
void KeePass2XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba) void Kdbx3XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba)
{ {
writeString(qualifiedName, QString::fromLatin1(ba.toBase64())); writeString(qualifiedName, QString::fromLatin1(ba.toBase64()));
} }
void KeePass2XmlWriter::writeColor(const QString& qualifiedName, const QColor& color) void Kdbx3XmlWriter::writeColor(const QString& qualifiedName, const QColor& color)
{ {
QString colorStr; QString colorStr;
@ -528,7 +528,7 @@ void KeePass2XmlWriter::writeColor(const QString& qualifiedName, const QColor& c
writeString(qualifiedName, colorStr); writeString(qualifiedName, colorStr);
} }
void KeePass2XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState) void Kdbx3XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState)
{ {
QString value; QString value;
@ -545,7 +545,7 @@ void KeePass2XmlWriter::writeTriState(const QString& qualifiedName, Group::TriSt
writeString(qualifiedName, value); writeString(qualifiedName, value);
} }
QString KeePass2XmlWriter::colorPartToString(int value) QString Kdbx3XmlWriter::colorPartToString(int value)
{ {
QString str = QString::number(value, 16).toUpper(); QString str = QString::number(value, 16).toUpper();
if (str.length() == 1) { if (str.length() == 1) {
@ -555,7 +555,7 @@ QString KeePass2XmlWriter::colorPartToString(int value)
return str; return str;
} }
QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str) QString Kdbx3XmlWriter::stripInvalidXml10Chars(QString str)
{ {
for (int i = str.size() - 1; i >= 0; i--) { for (int i = str.size() - 1; i >= 0; i--) {
const QChar ch = str.at(i); const QChar ch = str.at(i);
@ -580,7 +580,7 @@ QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str)
return str; return str;
} }
void KeePass2XmlWriter::raiseError(const QString& errorMessage) void Kdbx3XmlWriter::raiseError(const QString& errorMessage)
{ {
m_error = true; m_error = true;
m_errorStr = errorMessage; m_errorStr = errorMessage;

View File

@ -15,8 +15,8 @@
* 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_KEEPASS2XMLWRITER_H #ifndef KEEPASSX_KDBX3XMLWRITER_H
#define KEEPASSX_KEEPASS2XMLWRITER_H #define KEEPASSX_KDBX3XMLWRITER_H
#include <QColor> #include <QColor>
#include <QDateTime> #include <QDateTime>
@ -32,10 +32,10 @@
class KeePass2RandomStream; class KeePass2RandomStream;
class Metadata; class Metadata;
class KeePass2XmlWriter class Kdbx3XmlWriter
{ {
public: public:
KeePass2XmlWriter(); Kdbx3XmlWriter();
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr, void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr,
const QByteArray& headerHash = QByteArray()); const QByteArray& headerHash = QByteArray());
void writeDatabase(const QString& filename, Database* db); void writeDatabase(const QString& filename, Database* db);
@ -87,4 +87,4 @@ private:
QString m_errorStr; QString m_errorStr;
}; };
#endif // KEEPASSX_KEEPASS2XMLWRITER_H #endif // KEEPASSX_KDBX3XMLWRITER_H

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2017 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,198 +15,30 @@
* 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 "KeePass2Reader.h" #include <QtGlobal>
#include <QString>
#include <QBuffer>
#include <QFile> #include <QFile>
#include <QIODevice>
#include "core/Database.h"
#include "core/Endian.h" #include "core/Endian.h"
#include "crypto/CryptoHash.h" #include "keys/CompositeKey.h"
#include "crypto/kdf/AesKdf.h" #include "format/KeePass2Reader.h"
#include "format/KeePass1.h" #include "format/KeePass1.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
#include "format/KeePass2RandomStream.h" #include "format/Kdbx3Reader.h"
#include "format/KeePass2XmlReader.h"
#include "streams/HashedBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/StoreDataStream.h"
#include "streams/SymmetricCipherStream.h"
KeePass2Reader::KeePass2Reader() BaseKeePass2Reader::BaseKeePass2Reader() :
: m_device(nullptr) m_error(false),
, m_headerStream(nullptr) m_saveXml(false),
, m_error(false) m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo)
, m_headerEnd(false)
, m_saveXml(false)
, m_db(nullptr)
, m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo)
{ {
}
Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
{
QScopedPointer<Database> db(new Database());
m_db = db.data();
m_device = device;
m_error = false;
m_errorStr.clear(); m_errorStr.clear();
m_headerEnd = false;
m_xmlData.clear(); m_xmlData.clear();
m_masterSeed.clear();
m_encryptionIV.clear();
m_streamStartBytes.clear();
m_protectedStreamKey.clear(); m_protectedStreamKey.clear();
StoreDataStream headerStream(m_device);
headerStream.open(QIODevice::ReadOnly);
m_headerStream = &headerStream;
bool ok;
quint32 signature1 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok);
if (!ok || signature1 != KeePass2::SIGNATURE_1) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 signature2 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok);
if (ok && signature2 == KeePass1::SIGNATURE_2) {
raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
"This is a one-way migration. You won't be able to open the imported "
"database with the old KeePassX 0.4 version."));
return nullptr;
}
else if (!ok || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 version = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok)
& KeePass2::FILE_VERSION_CRITICAL_MASK;
quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK;
if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) {
raiseError(tr("Unsupported KeePass database version."));
return nullptr;
}
while (readHeaderField() && !hasError()) {
}
headerStream.close();
if (hasError()) {
return nullptr;
}
// check if all required headers were present
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty()
|| m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty()
|| m_db->cipher().isNull()) {
raiseError("missing database headers");
return nullptr;
}
if (!m_db->setKey(key, false)) {
raiseError(tr("Unable to calculate master key"));
return nullptr;
}
if (m_db->challengeMasterSeed(m_masterSeed) == false) {
raiseError(tr("Unable to issue challenge-response."));
return nullptr;
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
hash.addData(m_db->challengeResponseKey());
hash.addData(m_db->transformedMasterKey());
QByteArray finalKey = hash.result();
SymmetricCipherStream cipherStream(m_device, SymmetricCipher::cipherToAlgorithm(m_db->cipher()),
SymmetricCipher::Cbc, SymmetricCipher::Decrypt);
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString());
return nullptr;
}
if (!cipherStream.open(QIODevice::ReadOnly)) {
raiseError(cipherStream.errorString());
return nullptr;
}
QByteArray realStart = cipherStream.read(32);
if (realStart != m_streamStartBytes) {
raiseError(tr("Wrong key or database file is corrupt."));
return nullptr;
}
HashedBlockStream hashedStream(&cipherStream);
if (!hashedStream.open(QIODevice::ReadOnly)) {
raiseError(hashedStream.errorString());
return nullptr;
}
QIODevice* xmlDevice;
QScopedPointer<QtIOCompressor> ioCompressor;
if (m_db->compressionAlgo() == Database::CompressionNone) {
xmlDevice = &hashedStream;
}
else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
if (!ioCompressor->open(QIODevice::ReadOnly)) {
raiseError(ioCompressor->errorString());
return nullptr;
}
xmlDevice = ioCompressor.data();
}
KeePass2RandomStream randomStream(m_irsAlgo);
if (!randomStream.init(m_protectedStreamKey)) {
raiseError(randomStream.errorString());
return nullptr;
}
QScopedPointer<QBuffer> buffer;
if (m_saveXml) {
m_xmlData = xmlDevice->readAll();
buffer.reset(new QBuffer(&m_xmlData));
buffer->open(QIODevice::ReadOnly);
xmlDevice = buffer.data();
}
KeePass2XmlReader xmlReader;
xmlReader.readDatabase(xmlDevice, m_db, &randomStream);
if (xmlReader.hasError()) {
raiseError(xmlReader.errorString());
if (keepDatabase) {
return db.take();
}
else {
return nullptr;
}
}
Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty());
if (!xmlReader.headerHash().isEmpty()) {
QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256);
if (headerHash != xmlReader.headerHash()) {
raiseError("Header doesn't match hash");
return nullptr;
}
}
return db.take();
} }
Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key) BaseKeePass2Reader::~BaseKeePass2Reader() {}
Database* BaseKeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key)
{ {
QFile file(filename); QFile file(filename);
if (!file.open(QFile::ReadOnly)) { if (!file.open(QFile::ReadOnly)) {
@ -224,238 +56,113 @@ Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeK
return db.take(); return db.take();
} }
bool KeePass2Reader::hasError() bool BaseKeePass2Reader::hasError()
{ {
return m_error; return m_error;
} }
QString KeePass2Reader::errorString() QString BaseKeePass2Reader::errorString()
{ {
return m_errorStr; return m_errorStr;
} }
void KeePass2Reader::setSaveXml(bool save) void BaseKeePass2Reader::setSaveXml(bool save)
{ {
m_saveXml = save; m_saveXml = save;
} }
QByteArray KeePass2Reader::xmlData() QByteArray BaseKeePass2Reader::xmlData()
{ {
return m_xmlData; return m_xmlData;
} }
QByteArray KeePass2Reader::streamKey() QByteArray BaseKeePass2Reader::streamKey()
{ {
return m_protectedStreamKey; return m_protectedStreamKey;
} }
void KeePass2Reader::raiseError(const QString& errorMessage) KeePass2::ProtectedStreamAlgo BaseKeePass2Reader::protectedStreamAlgo() const {
return m_irsAlgo;
}
void BaseKeePass2Reader::raiseError(const QString& errorMessage)
{ {
m_error = true; m_error = true;
m_errorStr = errorMessage; m_errorStr = errorMessage;
} }
bool KeePass2Reader::readHeaderField() Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
{ {
QByteArray fieldIDArray = m_headerStream->read(1); m_error = false;
if (fieldIDArray.size() != 1) { m_errorStr.clear();
raiseError("Invalid header id size");
return false;
}
quint8 fieldID = fieldIDArray.at(0);
bool ok; bool ok;
quint16 fieldLen = Endian::readUInt16(m_headerStream, KeePass2::BYTEORDER, &ok);
if (!ok) { quint32 signature1 = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok);
raiseError("Invalid header field length"); if (!ok || signature1 != KeePass2::SIGNATURE_1) {
return false; raiseError(tr("Not a KeePass database."));
return nullptr;
} }
QByteArray fieldData; quint32 signature2 = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok);
if (fieldLen != 0) { if (ok && signature2 == KeePass1::SIGNATURE_2) {
fieldData = m_headerStream->read(fieldLen); raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
if (fieldData.size() != fieldLen) { "You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
raiseError("Invalid header data length"); "This is a one-way migration. You won't be able to open the imported "
return false; "database with the old KeePassX 0.4 version."));
} return nullptr;
}
else if (!ok || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
} }
switch (fieldID) { m_version = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok)
case KeePass2::EndOfHeader: & KeePass2::FILE_VERSION_CRITICAL_MASK;
m_headerEnd = true; quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK;
break; if (!ok || (m_version < KeePass2::FILE_VERSION_MIN) || (m_version > maxVersion)) {
raiseError(tr("Unsupported KeePass 2 database version."));
case KeePass2::CipherID: return nullptr;
setCipher(fieldData);
break;
case KeePass2::CompressionFlags:
setCompressionFlags(fieldData);
break;
case KeePass2::MasterSeed:
setMasterSeed(fieldData);
break;
case KeePass2::TransformSeed:
setTransformSeed(fieldData);
break;
case KeePass2::TransformRounds:
setTransformRounds(fieldData);
break;
case KeePass2::EncryptionIV:
setEncryptionIV(fieldData);
break;
case KeePass2::ProtectedStreamKey:
setProtectedStreamKey(fieldData);
break;
case KeePass2::StreamStartBytes:
setStreamStartBytes(fieldData);
break;
case KeePass2::InnerRandomStreamID:
setInnerRandomStreamID(fieldData);
break;
default:
qWarning("Unknown header field read: id=%d", fieldID);
break;
} }
return !m_headerEnd; device->seek(0);
m_reader.reset(static_cast<BaseKeePass2Reader*>(new Kdbx3Reader()));
m_reader->setSaveXml(m_saveXml);
return m_reader->readDatabase(device, key, keepDatabase);
} }
void KeePass2Reader::setCipher(const QByteArray& data) bool KeePass2Reader::hasError()
{ {
if (data.size() != Uuid::Length) { return m_error || (m_reader && m_reader->hasError());
raiseError("Invalid cipher uuid length");
}
else {
Uuid uuid(data);
if (uuid != KeePass2::CIPHER_AES && uuid != KeePass2::CIPHER_TWOFISH) {
raiseError("Unsupported cipher");
}
else {
m_db->setCipher(uuid);
}
}
} }
void KeePass2Reader::setCompressionFlags(const QByteArray& data) QString KeePass2Reader::errorString()
{ {
if (data.size() != 4) { return m_reader ? m_reader->errorString() : m_errorStr;
raiseError("Invalid compression flags length");
}
else {
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
if (id > Database::CompressionAlgorithmMax) {
raiseError("Unsupported compression algorithm");
}
else {
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
}
}
} }
void KeePass2Reader::setMasterSeed(const QByteArray& data) QByteArray KeePass2Reader::xmlData()
{ {
if (data.size() != 32) { return m_reader ? m_reader->xmlData() : m_xmlData;
raiseError("Invalid master seed size");
}
else {
m_masterSeed = data;
}
} }
void KeePass2Reader::setTransformSeed(const QByteArray& data) QByteArray KeePass2Reader::streamKey()
{ {
if (data.size() != 32) { return m_reader ? m_reader->streamKey() : m_protectedStreamKey;
raiseError("Invalid transform seed size");
}
else {
AesKdf* aesKdf;
if (m_db->kdf()->type() == Kdf::Type::AES) {
aesKdf = static_cast<AesKdf*>(m_db->kdf());
} else {
aesKdf = new AesKdf();
m_db->setKdf(aesKdf);
}
aesKdf->setSeed(data);
}
}
void KeePass2Reader::setTransformRounds(const QByteArray& data)
{
if (data.size() != 8) {
raiseError("Invalid transform rounds size");
}
else {
quint64 rounds = Endian::bytesToUInt64(data, KeePass2::BYTEORDER);
AesKdf* aesKdf;
if (m_db->kdf()->type() == Kdf::Type::AES) {
aesKdf = static_cast<AesKdf*>(m_db->kdf());
} else {
aesKdf = new AesKdf();
m_db->setKdf(aesKdf);
}
aesKdf->setRounds(rounds);
}
}
void KeePass2Reader::setEncryptionIV(const QByteArray& data)
{
if (data.size() != 16) {
raiseError("Invalid encryption iv size");
}
else {
m_encryptionIV = data;
}
}
void KeePass2Reader::setProtectedStreamKey(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid stream key size");
}
else {
m_protectedStreamKey = data;
}
}
void KeePass2Reader::setStreamStartBytes(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid start bytes size");
}
else {
m_streamStartBytes = data;
}
}
void KeePass2Reader::setInnerRandomStreamID(const QByteArray& data)
{
if (data.size() != 4) {
raiseError("Invalid random stream id size");
}
else {
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
m_irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
if (m_irsAlgo == KeePass2::ArcFourVariant || m_irsAlgo == KeePass2::InvalidProtectedStreamAlgo) {
raiseError("Unsupported random stream algorithm");
}
}
} }
KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const
{ {
return m_irsAlgo; return m_reader ? m_reader->protectedStreamAlgo() : m_irsAlgo;
}
quint32 KeePass2Reader::version() const
{
return m_version;
}
BaseKeePass2Reader* KeePass2Reader::reader() const
{
return m_reader.data();
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2017 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
@ -18,58 +18,65 @@
#ifndef KEEPASSX_KEEPASS2READER_H #ifndef KEEPASSX_KEEPASS2READER_H
#define KEEPASSX_KEEPASS2READER_H #define KEEPASSX_KEEPASS2READER_H
#include <QtGlobal>
#include <QByteArray>
#include <QString>
#include <QCoreApplication> #include <QCoreApplication>
#include <QScopedPointer>
#include <QIODevice>
#include "keys/CompositeKey.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
#include "core/Database.h"
#include "keys/CompositeKey.h"
class Database; class BaseKeePass2Reader
class QIODevice;
class KeePass2Reader
{ {
Q_DECLARE_TR_FUNCTIONS(KeePass2Reader)
public: public:
KeePass2Reader(); BaseKeePass2Reader();
Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false);
Database* readDatabase(const QString& filename, const CompositeKey& key);
bool hasError();
QString errorString();
void setSaveXml(bool save);
QByteArray xmlData();
QByteArray streamKey();
KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const;
private: virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) = 0;
virtual Database* readDatabase(const QString& filename, const CompositeKey& key);
virtual bool hasError();
virtual QString errorString();
virtual void setSaveXml(bool save);
virtual QByteArray xmlData();
virtual QByteArray streamKey();
virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const;
virtual ~BaseKeePass2Reader();
protected:
void raiseError(const QString& errorMessage); void raiseError(const QString& errorMessage);
bool readHeaderField();
void setCipher(const QByteArray& data);
void setCompressionFlags(const QByteArray& data);
void setMasterSeed(const QByteArray& data);
void setTransformSeed(const QByteArray& data);
void setTransformRounds(const QByteArray& data);
void setEncryptionIV(const QByteArray& data);
void setProtectedStreamKey(const QByteArray& data);
void setStreamStartBytes(const QByteArray& data);
void setInnerRandomStreamID(const QByteArray& data);
QIODevice* m_device;
QIODevice* m_headerStream;
bool m_error; bool m_error;
QString m_errorStr; QString m_errorStr;
bool m_headerEnd;
bool m_saveXml; bool m_saveXml;
QByteArray m_xmlData; QByteArray m_xmlData;
Database* m_db;
QByteArray m_masterSeed;
QByteArray m_encryptionIV;
QByteArray m_streamStartBytes;
QByteArray m_protectedStreamKey; QByteArray m_protectedStreamKey;
KeePass2::ProtectedStreamAlgo m_irsAlgo; KeePass2::ProtectedStreamAlgo m_irsAlgo;
}; };
class KeePass2Reader : public BaseKeePass2Reader
{
Q_DECLARE_TR_FUNCTIONS(KeePass2Reader)
public:
virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override;
using BaseKeePass2Reader::readDatabase;
virtual bool hasError() override;
virtual QString errorString() override;
virtual QByteArray xmlData() override;
virtual QByteArray streamKey() override;
virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const override;
quint32 version() const;
BaseKeePass2Reader* reader() const;
private:
QScopedPointer<BaseKeePass2Reader> m_reader;
quint32 m_version;
};
#endif // KEEPASSX_KEEPASS2READER_H #endif // KEEPASSX_KEEPASS2READER_H

View File

@ -22,9 +22,10 @@
#include <QScopedPointer> #include <QScopedPointer>
#include <QRegExp> #include <QRegExp>
#include "format/KeePass2.h"
#include "format/KeePass2RandomStream.h" #include "format/KeePass2RandomStream.h"
#include "format/KeePass2Reader.h" #include "format/KeePass2Reader.h"
#include "format/KeePass2XmlReader.h" #include "format/Kdbx3XmlReader.h"
KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key) KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key)
{ {
@ -73,7 +74,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
KeePass2RandomStream randomStream(reader.protectedStreamAlgo()); KeePass2RandomStream randomStream(reader.protectedStreamAlgo());
randomStream.init(reader.streamKey()); randomStream.init(reader.streamKey());
KeePass2XmlReader xmlReader; Kdbx3XmlReader xmlReader;
QBuffer buffer(&xmlData); QBuffer buffer(&xmlData);
buffer.open(QIODevice::ReadOnly); buffer.open(QIODevice::ReadOnly);
xmlReader.readDatabase(&buffer, db.data(), &randomStream); xmlReader.readDatabase(&buffer, db.data(), &randomStream);

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2017 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,197 +15,58 @@
* 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 "KeePass2Writer.h"
#include <QBuffer>
#include <QFile>
#include <QIODevice> #include <QIODevice>
#include <QString>
#include <QFile>
#include "format/KeePass2Writer.h"
#include "core/Database.h" #include "core/Database.h"
#include "core/Endian.h" #include "format/Kdbx3Writer.h"
#include "crypto/CryptoHash.h"
#include "crypto/kdf/AesKdf.h"
#include "crypto/Random.h"
#include "format/KeePass2RandomStream.h"
#include "format/KeePass2XmlWriter.h"
#include "streams/HashedBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/SymmetricCipherStream.h"
#define CHECK_RETURN(x) if (!(x)) return; BaseKeePass2Writer::BaseKeePass2Writer() : m_error(false)
#define CHECK_RETURN_FALSE(x) if (!(x)) return false;
KeePass2Writer::KeePass2Writer()
: m_device(0)
, m_error(false)
{ {
}
void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
{
m_error = false;
m_errorStr.clear(); m_errorStr.clear();
QByteArray masterSeed = randomGen()->randomArray(32);
QByteArray encryptionIV = randomGen()->randomArray(16);
QByteArray protectedStreamKey = randomGen()->randomArray(32);
QByteArray startBytes = randomGen()->randomArray(32);
QByteArray endOfHeader = "\r\n\r\n";
if (db->challengeMasterSeed(masterSeed) == false) {
raiseError(tr("Unable to issue challenge-response."));
return;
}
if (!db->setKey(db->key(), false, true)) {
raiseError(tr("Unable to calculate master key"));
return;
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(masterSeed);
hash.addData(db->challengeResponseKey());
Q_ASSERT(!db->transformedMasterKey().isEmpty());
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
QBuffer header;
header.open(QIODevice::WriteOnly);
m_device = &header;
CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER)));
CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER)));
CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER)));
CHECK_RETURN(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray()));
CHECK_RETURN(writeHeaderField(KeePass2::CompressionFlags,
Endian::int32ToBytes(db->compressionAlgo(),
KeePass2::BYTEORDER)));
AesKdf* kdf = static_cast<AesKdf*>(db->kdf());
CHECK_RETURN(writeHeaderField(KeePass2::MasterSeed, masterSeed));
CHECK_RETURN(writeHeaderField(KeePass2::TransformSeed, kdf->seed()));
CHECK_RETURN(writeHeaderField(KeePass2::TransformRounds,
Endian::int64ToBytes(kdf->rounds(),
KeePass2::BYTEORDER)));
CHECK_RETURN(writeHeaderField(KeePass2::EncryptionIV, encryptionIV));
CHECK_RETURN(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey));
CHECK_RETURN(writeHeaderField(KeePass2::StreamStartBytes, startBytes));
CHECK_RETURN(writeHeaderField(KeePass2::InnerRandomStreamID,
Endian::int32ToBytes(KeePass2::Salsa20,
KeePass2::BYTEORDER)));
CHECK_RETURN(writeHeaderField(KeePass2::EndOfHeader, endOfHeader));
header.close();
m_device = device;
QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
CHECK_RETURN(writeData(header.data()));
SymmetricCipherStream cipherStream(device, SymmetricCipher::cipherToAlgorithm(db->cipher()),
SymmetricCipher::Cbc, SymmetricCipher::Encrypt);
cipherStream.init(finalKey, encryptionIV);
if (!cipherStream.open(QIODevice::WriteOnly)) {
raiseError(cipherStream.errorString());
return;
}
m_device = &cipherStream;
CHECK_RETURN(writeData(startBytes));
HashedBlockStream hashedStream(&cipherStream);
if (!hashedStream.open(QIODevice::WriteOnly)) {
raiseError(hashedStream.errorString());
return;
}
QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) {
m_device = &hashedStream;
}
else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
if (!ioCompressor->open(QIODevice::WriteOnly)) {
raiseError(ioCompressor->errorString());
return;
}
m_device = ioCompressor.data();
}
KeePass2RandomStream randomStream(KeePass2::Salsa20);
if (!randomStream.init(protectedStreamKey)) {
raiseError(randomStream.errorString());
return;
}
KeePass2XmlWriter xmlWriter;
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
// Explicitly close/reset streams so they are flushed and we can detect
// errors. QIODevice::close() resets errorString() etc.
if (ioCompressor) {
ioCompressor->close();
}
if (!hashedStream.reset()) {
raiseError(hashedStream.errorString());
return;
}
if (!cipherStream.reset()) {
raiseError(cipherStream.errorString());
return;
}
if (xmlWriter.hasError()) {
raiseError(xmlWriter.errorString());
}
} }
bool KeePass2Writer::writeData(const QByteArray& data) BaseKeePass2Writer::~BaseKeePass2Writer() {}
{
if (m_device->write(data) != data.size()) {
raiseError(m_device->errorString());
return false;
}
else {
return true;
}
}
bool KeePass2Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data) bool BaseKeePass2Writer::hasError()
{
Q_ASSERT(data.size() <= 65535);
QByteArray fieldIdArr;
fieldIdArr[0] = fieldId;
CHECK_RETURN_FALSE(writeData(fieldIdArr));
CHECK_RETURN_FALSE(writeData(Endian::int16ToBytes(static_cast<quint16>(data.size()),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(data));
return true;
}
void KeePass2Writer::writeDatabase(const QString& filename, Database* db)
{
QFile file(filename);
if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) {
raiseError(file.errorString());
return;
}
writeDatabase(&file, db);
}
bool KeePass2Writer::hasError()
{ {
return m_error; return m_error;
} }
QString KeePass2Writer::errorString() QString BaseKeePass2Writer::errorString()
{ {
return m_errorStr; return m_errorStr;
} }
void KeePass2Writer::raiseError(const QString& errorMessage) void BaseKeePass2Writer::raiseError(const QString& errorMessage)
{ {
m_error = true; m_error = true;
m_errorStr = errorMessage; m_errorStr = errorMessage;
} }
bool BaseKeePass2Writer::writeDatabase(const QString& filename, Database* db)
{
QFile file(filename);
if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) {
raiseError(file.errorString());
return false;
}
return writeDatabase(&file, db);
}
bool KeePass2Writer::hasError()
{
return m_error || (m_writer && m_writer->hasError());
}
QString KeePass2Writer::errorString()
{
return m_writer ? m_writer->errorString() : m_errorStr;
}
bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) {
m_writer.reset(static_cast<BaseKeePass2Writer*>(new Kdbx3Writer()));
return m_writer->writeDatabase(device, db);
}

View File

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de> * Copyright (C) 2017 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
@ -18,33 +18,48 @@
#ifndef KEEPASSX_KEEPASS2WRITER_H #ifndef KEEPASSX_KEEPASS2WRITER_H
#define KEEPASSX_KEEPASS2WRITER_H #define KEEPASSX_KEEPASS2WRITER_H
#include <QIODevice>
#include <QByteArray>
#include <QString>
#include <QCoreApplication> #include <QCoreApplication>
#include <QScopedPointer>
#include "core/Database.h"
#include "format/KeePass2.h" #include "format/KeePass2.h"
#include "keys/CompositeKey.h"
class Database; class BaseKeePass2Writer
class QIODevice;
class KeePass2Writer
{ {
Q_DECLARE_TR_FUNCTIONS(KeePass2Writer)
public: public:
KeePass2Writer(); BaseKeePass2Writer();
void writeDatabase(QIODevice* device, Database* db);
void writeDatabase(const QString& filename, Database* db);
bool hasError();
QString errorString();
private: virtual bool writeDatabase(QIODevice* device, Database* db) = 0;
bool writeData(const QByteArray& data); virtual bool writeDatabase(const QString& filename, Database* db);
bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data);
virtual bool hasError();
virtual QString errorString();
virtual ~BaseKeePass2Writer();
protected:
void raiseError(const QString& errorMessage); void raiseError(const QString& errorMessage);
QIODevice* m_device;
bool m_error; bool m_error;
QString m_errorStr; QString m_errorStr;
}; };
#endif // KEEPASSX_KEEPASS2WRITER_H class KeePass2Writer : public BaseKeePass2Writer
{
Q_DECLARE_TR_FUNCTIONS(KeePass2Writer)
public:
virtual bool writeDatabase(QIODevice* device, Database* db) override;
using BaseKeePass2Writer::writeDatabase;
virtual bool hasError() override;
virtual QString errorString() override;
private:
QScopedPointer<BaseKeePass2Writer> m_writer;
};
#endif // KEEPASSX_KEEPASS2READER_H

View File

@ -107,7 +107,7 @@ 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 testkeepass2xmlreader SOURCES TestKeePass2XmlReader.cpp add_unit_test(NAME testkdbx3xmlreader SOURCES TestKeePass2XmlReader.cpp TestKdbx3XmlReader.cpp
LIBS ${TEST_LIBRARIES}) LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testkeys SOURCES TestKeys.cpp add_unit_test(NAME testkeys SOURCES TestKeys.cpp

View File

@ -22,7 +22,7 @@
#include "core/Database.h" #include "core/Database.h"
#include "core/Group.h" #include "core/Group.h"
#include "crypto/Crypto.h" #include "crypto/Crypto.h"
#include "format/KeePass2XmlReader.h" #include "format/Kdbx3XmlReader.h"
#include "config-keepassx-tests.h" #include "config-keepassx-tests.h"
QTEST_GUILESS_MAIN(TestDeletedObjects) QTEST_GUILESS_MAIN(TestDeletedObjects)
@ -88,7 +88,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize)
void TestDeletedObjects::testDeletedObjectsFromFile() void TestDeletedObjects::testDeletedObjectsFromFile()
{ {
KeePass2XmlReader reader; Kdbx3XmlReader reader;
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);

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2017 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 <QTest>
#include "TestKeePass2XmlReader.h"
QTEST_GUILESS_MAIN(TestKdbx3XmlReader)

View File

@ -30,7 +30,6 @@
#include "format/KeePass2Reader.h" #include "format/KeePass2Reader.h"
#include "format/KeePass2Repair.h" #include "format/KeePass2Repair.h"
#include "format/KeePass2Writer.h" #include "format/KeePass2Writer.h"
#include "format/KeePass2XmlWriter.h"
#include "keys/PasswordKey.h" #include "keys/PasswordKey.h"
QTEST_GUILESS_MAIN(TestKeePass2Writer) QTEST_GUILESS_MAIN(TestKeePass2Writer)

View File

@ -25,12 +25,10 @@
#include "core/Group.h" #include "core/Group.h"
#include "core/Metadata.h" #include "core/Metadata.h"
#include "crypto/Crypto.h" #include "crypto/Crypto.h"
#include "format/KeePass2XmlReader.h" #include "format/Kdbx3XmlReader.h"
#include "format/KeePass2XmlWriter.h" #include "format/Kdbx3XmlWriter.h"
#include "config-keepassx-tests.h" #include "config-keepassx-tests.h"
QTEST_GUILESS_MAIN(TestKeePass2XmlReader)
namespace QTest { namespace QTest {
template<> template<>
char* toString(const Uuid& uuid) char* toString(const Uuid& uuid)
@ -79,11 +77,11 @@ QByteArray TestKeePass2XmlReader::strToBytes(const QString& str)
return result; return result;
} }
void TestKeePass2XmlReader::initTestCase() void TestKdbx3XmlReader::initTestCase()
{ {
QVERIFY(Crypto::init()); QVERIFY(Crypto::init());
KeePass2XmlReader reader; Kdbx3XmlReader reader;
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");
m_db = reader.readDatabase(xmlFile); m_db = reader.readDatabase(xmlFile);
@ -91,6 +89,32 @@ void TestKeePass2XmlReader::initTestCase()
QVERIFY(!reader.hasError()); QVERIFY(!reader.hasError());
} }
void TestKdbx3XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString)
{
Kdbx3XmlReader reader;
reader.setStrictMode(strictMode);
db = reader.readDatabase(path);
hasError = reader.hasError();
errorString = reader.errorString();
}
void TestKdbx3XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString)
{
Kdbx3XmlReader reader;
reader.setStrictMode(strictMode);
db = reader.readDatabase(buf);
hasError = reader.hasError();
errorString = reader.errorString();
}
void TestKdbx3XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString)
{
Kdbx3XmlWriter writer;
writer.writeDatabase(buf, db);
hasError = writer.hasError();
errorString = writer.errorString();
}
void TestKeePass2XmlReader::testMetadata() void TestKeePass2XmlReader::testMetadata()
{ {
QCOMPARE(m_db->metadata()->generator(), QString("KeePass")); QCOMPARE(m_db->metadata()->generator(), QString("KeePass"));
@ -374,15 +398,20 @@ void TestKeePass2XmlReader::testBroken()
QFETCH(bool, strictMode); QFETCH(bool, strictMode);
QFETCH(bool, expectError); QFETCH(bool, expectError);
KeePass2XmlReader reader;
reader.setStrictMode(strictMode);
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName); QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName);
QVERIFY(QFile::exists(xmlFile)); QVERIFY(QFile::exists(xmlFile));
QScopedPointer<Database> db(reader.readDatabase(xmlFile)); bool hasError;
if (reader.hasError()) { QString errorString;
qWarning("Reader error: %s", qPrintable(reader.errorString())); Database* db;
readDatabase(xmlFile, strictMode, db, hasError, errorString);
if (hasError) {
qWarning("Reader error: %s", qPrintable(errorString));
}
QCOMPARE(hasError, expectError);
if (db) {
delete db;
} }
QCOMPARE(reader.hasError(), expectError);
} }
void TestKeePass2XmlReader::testBroken_data() void TestKeePass2XmlReader::testBroken_data()
@ -412,15 +441,20 @@ void TestKeePass2XmlReader::testBroken_data()
void TestKeePass2XmlReader::testEmptyUuids() void TestKeePass2XmlReader::testEmptyUuids()
{ {
KeePass2XmlReader reader;
reader.setStrictMode(true);
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));
QScopedPointer<Database> db(reader.readDatabase(xmlFile)); Database* dbp;
if (reader.hasError()) { bool hasError;
qWarning("Reader error: %s", qPrintable(reader.errorString())); QString errorString;
readDatabase(xmlFile, true, dbp, hasError, errorString);
if (hasError) {
qWarning("Reader error: %s", qPrintable(errorString));
}
QVERIFY(!hasError);
if (dbp) {
delete dbp;
} }
QVERIFY(!reader.hasError());
} }
void TestKeePass2XmlReader::testInvalidXmlChars() void TestKeePass2XmlReader::testInvalidXmlChars()
@ -459,19 +493,19 @@ void TestKeePass2XmlReader::testInvalidXmlChars()
QBuffer buffer; QBuffer buffer;
buffer.open(QIODevice::ReadWrite); buffer.open(QIODevice::ReadWrite);
KeePass2XmlWriter writer; bool hasError;
writer.writeDatabase(&buffer, dbWrite.data()); QString errorString;
QVERIFY(!writer.hasError()); writeDatabase(&buffer, dbWrite.data(), hasError, errorString);
QVERIFY(!hasError);
buffer.seek(0); buffer.seek(0);
KeePass2XmlReader reader; Database* dbRead;
reader.setStrictMode(true); readDatabase(&buffer, true, dbRead, hasError, errorString);
QScopedPointer<Database> dbRead(reader.readDatabase(&buffer)); if (hasError) {
if (reader.hasError()) { qWarning("Database read error: %s", qPrintable(errorString));
qWarning("Database read error: %s", qPrintable(reader.errorString()));
} }
QVERIFY(!reader.hasError()); QVERIFY(!hasError);
QVERIFY(!dbRead.isNull()); QVERIFY(dbRead);
QCOMPARE(dbRead->rootGroup()->entries().size(), 1); QCOMPARE(dbRead->rootGroup()->entries().size(), 1);
Entry* entryRead = dbRead->rootGroup()->entries().at(0); Entry* entryRead = dbRead->rootGroup()->entries().at(0);
EntryAttributes* attrRead = entryRead->attributes(); EntryAttributes* attrRead = entryRead->attributes();
@ -486,22 +520,28 @@ void TestKeePass2XmlReader::testInvalidXmlChars()
QCOMPARE(strToBytes(attrRead->value("LowLowSurrogate")), QByteArray()); QCOMPARE(strToBytes(attrRead->value("LowLowSurrogate")), QByteArray());
QCOMPARE(strToBytes(attrRead->value("SurrogateValid1")), strToBytes(strSurrogateValid1)); QCOMPARE(strToBytes(attrRead->value("SurrogateValid1")), strToBytes(strSurrogateValid1));
QCOMPARE(strToBytes(attrRead->value("SurrogateValid2")), strToBytes(strSurrogateValid2)); QCOMPARE(strToBytes(attrRead->value("SurrogateValid2")), strToBytes(strSurrogateValid2));
if (dbRead) {
delete dbRead;
}
} }
void TestKeePass2XmlReader::testRepairUuidHistoryItem() void TestKeePass2XmlReader::testRepairUuidHistoryItem()
{ {
KeePass2XmlReader reader;
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));
QScopedPointer<Database> db(reader.readDatabase(xmlFile)); Database* db;
if (reader.hasError()) { bool hasError;
qWarning("Database read error: %s", qPrintable(reader.errorString())); QString errorString;
readDatabase(xmlFile, true, db, hasError, errorString);
if (hasError) {
qWarning("Database read error: %s", qPrintable(errorString));
} }
QVERIFY(!reader.hasError()); QVERIFY(!hasError);
QList<Entry*> entries = db.data()->rootGroup()->entries(); QList<Entry*> entries = db->rootGroup()->entries();
QCOMPARE(entries.size(), 1); QCOMPARE(entries.size(), 1);
Entry* entry = entries.at(0); Entry* entry = entries.at(0);
@ -512,6 +552,10 @@ void TestKeePass2XmlReader::testRepairUuidHistoryItem()
QVERIFY(!entry->uuid().isNull()); QVERIFY(!entry->uuid().isNull());
QVERIFY(!historyItem->uuid().isNull()); QVERIFY(!historyItem->uuid().isNull());
QCOMPARE(historyItem->uuid(), entry->uuid()); QCOMPARE(historyItem->uuid(), entry->uuid());
if (db) {
delete db;
}
} }
void TestKeePass2XmlReader::cleanupTestCase() void TestKeePass2XmlReader::cleanupTestCase()

View File

@ -20,6 +20,7 @@
#include <QDateTime> #include <QDateTime>
#include <QObject> #include <QObject>
#include <QBuffer>
class Database; class Database;
@ -27,8 +28,8 @@ class TestKeePass2XmlReader : public QObject
{ {
Q_OBJECT Q_OBJECT
private slots: protected slots:
void initTestCase(); virtual void initTestCase() = 0;
void testMetadata(); void testMetadata();
void testCustomIcons(); void testCustomIcons();
void testCustomData(); void testCustomData();
@ -46,11 +47,27 @@ private slots:
void testRepairUuidHistoryItem(); void testRepairUuidHistoryItem();
void cleanupTestCase(); void cleanupTestCase();
private: protected:
virtual void readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) = 0;
virtual void readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) = 0;
virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) = 0;
static QDateTime genDT(int year, int month, int day, int hour, int min, int second); static QDateTime genDT(int year, int month, int day, int hour, int min, int second);
static QByteArray strToBytes(const QString& str); static QByteArray strToBytes(const QString& str);
Database* m_db; Database* m_db;
}; };
class TestKdbx3XmlReader : public TestKeePass2XmlReader
{
Q_OBJECT
private slots:
virtual void initTestCase() override;
protected:
virtual void readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) override;
virtual void readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) override;
virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override;
};
#endif // KEEPASSX_TESTKEEPASS2XMLREADER_H #endif // KEEPASSX_TESTKEEPASS2XMLREADER_H