Refactor database readers/writers and XML handling

* Refactor Kdbx*Reader
* Refactor KdbxWriter
* Refactor KdbxXmlReader
* Refactor KdbxXmlWriter
This commit is contained in:
Janek Bevendorff 2018-01-07 04:08:32 +01:00 committed by Jonathan White
parent 72a1c65d00
commit a6ddc22fb8
No known key found for this signature in database
GPG Key ID: 440FC65F2E0C6E01
29 changed files with 1313 additions and 2917 deletions

View File

@ -85,16 +85,16 @@ set(keepassx_SOURCES
format/KeePass2.cpp
format/KeePass2RandomStream.cpp
format/KeePass2Repair.cpp
format/KdbxReader.cpp
format/KdbxWriter.cpp
format/KdbxXmlReader.cpp
format/KeePass2Reader.cpp
format/KeePass2Writer.cpp
format/Kdbx3Reader.cpp
format/Kdbx3Writer.cpp
format/Kdbx3XmlReader.cpp
format/Kdbx3XmlWriter.cpp
format/Kdbx4Reader.cpp
format/Kdbx4Writer.cpp
format/Kdbx4XmlReader.cpp
format/Kdbx4XmlWriter.cpp
format/KdbxXmlWriter.cpp
gui/AboutDialog.cpp
gui/Application.cpp
gui/CategoryListWidget.cpp

View File

@ -101,7 +101,7 @@ int Extract::execute(QStringList arguments)
Database* db = reader.readDatabase(&dbFile, compositeKey);
delete db;
QByteArray xmlData = reader.xmlData();
QByteArray xmlData = reader.reader()->xmlData();
if (reader.hasError()) {
if (xmlData.isEmpty()) {

View File

@ -18,81 +18,21 @@
#include "Kdbx3Reader.h"
#include <QBuffer>
#include <QFile>
#include <QIODevice>
#include "core/Database.h"
#include "core/Group.h"
#include "core/Endian.h"
#include "crypto/CryptoHash.h"
#include "crypto/kdf/AesKdf.h"
#include "format/KeePass1.h"
#include "format/KeePass2.h"
#include "format/KeePass2RandomStream.h"
#include "format/Kdbx3XmlReader.h"
#include "format/KdbxXmlReader.h"
#include "streams/HashedBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/StoreDataStream.h"
#include "streams/SymmetricCipherStream.h"
Kdbx3Reader::Kdbx3Reader()
: m_device(nullptr)
, m_headerStream(nullptr)
, m_headerEnd(false)
, m_db(nullptr)
#include <QBuffer>
Database* Kdbx3Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
const CompositeKey& key, bool keepDatabase)
{
}
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::readSizedInt<quint32>(m_headerStream, KeePass2::BYTEORDER, &ok);
if (!ok || signature1 != KeePass2::SIGNATURE_1) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 signature2 = Endian::readSizedInt<quint32>(m_headerStream, KeePass2::BYTEORDER, &ok);
if (ok && signature2 == KeePass1::SIGNATURE_2) {
raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
"This is a one-way migration. You won't be able to open the imported "
"database with the old KeePassX 0.4 version."));
return nullptr;
} else if (!ok || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 version = Endian::readSizedInt<quint32>(m_headerStream, KeePass2::BYTEORDER, &ok)
& KeePass2::FILE_VERSION_CRITICAL_MASK;
quint32 maxVersion = KeePass2::FILE_VERSION_3 & KeePass2::FILE_VERSION_CRITICAL_MASK;
if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) {
raiseError(tr("Unsupported KeePass KDBX 2 or 3 database version."));
return nullptr;
}
while (readHeaderField() && !hasError()) {
}
headerStream.close();
Q_ASSERT(m_kdbxVersion <= KeePass2::FILE_VERSION_3);
if (hasError()) {
return nullptr;
@ -111,7 +51,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key,
return nullptr;
}
if (m_db->challengeMasterSeed(m_masterSeed) == false) {
if (!m_db->challengeMasterSeed(m_masterSeed)) {
raiseError(tr("Unable to issue challenge-response."));
return nullptr;
}
@ -123,7 +63,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key,
QByteArray finalKey = hash.result();
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
SymmetricCipherStream cipherStream(m_device, cipher,
SymmetricCipherStream cipherStream(device, cipher,
SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString());
@ -147,7 +87,7 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key,
return nullptr;
}
QIODevice* xmlDevice;
QIODevice* xmlDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (m_db->compressionAlgo() == Database::CompressionNone) {
@ -168,43 +108,43 @@ Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key,
return nullptr;
}
QScopedPointer<QBuffer> buffer;
if (m_saveXml) {
QBuffer buffer;
if (saveXml()) {
m_xmlData = xmlDevice->readAll();
buffer.reset(new QBuffer(&m_xmlData));
buffer->open(QIODevice::ReadOnly);
xmlDevice = buffer.data();
buffer.setBuffer(&m_xmlData);
buffer.open(QIODevice::ReadOnly);
xmlDevice = &buffer;
}
Kdbx3XmlReader xmlReader;
xmlReader.readDatabase(xmlDevice, m_db, &randomStream);
Q_ASSERT(xmlDevice);
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3);
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
if (xmlReader.hasError()) {
raiseError(xmlReader.errorString());
if (keepDatabase) {
return db.take();
} else {
return nullptr;
return m_db.take();
}
return nullptr;
}
Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty());
Q_ASSERT(!xmlReader.headerHash().isEmpty() || m_kdbxVersion < KeePass2::FILE_VERSION_3);
if (!xmlReader.headerHash().isEmpty()) {
QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256);
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);
if (headerHash != xmlReader.headerHash()) {
raiseError("Header doesn't match hash");
return nullptr;
}
}
return db.take();
return m_db.take();
}
bool Kdbx3Reader::readHeaderField()
bool Kdbx3Reader::readHeaderField(StoreDataStream& headerStream)
{
QByteArray fieldIDArray = m_headerStream->read(1);
QByteArray fieldIDArray = headerStream.read(1);
if (fieldIDArray.size() != 1) {
raiseError("Invalid header id size");
return false;
@ -212,7 +152,7 @@ bool Kdbx3Reader::readHeaderField()
char fieldID = fieldIDArray.at(0);
bool ok;
auto fieldLen = Endian::readSizedInt<quint16>(m_headerStream, KeePass2::BYTEORDER, &ok);
auto fieldLen = Endian::readSizedInt<quint16>(&headerStream, KeePass2::BYTEORDER, &ok);
if (!ok) {
raiseError("Invalid header field length");
return false;
@ -220,16 +160,17 @@ bool Kdbx3Reader::readHeaderField()
QByteArray fieldData;
if (fieldLen != 0) {
fieldData = m_headerStream->read(fieldLen);
fieldData = headerStream.read(fieldLen);
if (fieldData.size() != fieldLen) {
raiseError("Invalid header data length");
return false;
}
}
bool headerEnd = false;
switch (static_cast<KeePass2::HeaderFieldID>(fieldID)) {
case KeePass2::HeaderFieldID::EndOfHeader:
m_headerEnd = true;
headerEnd = true;
break;
case KeePass2::HeaderFieldID::CipherID:
@ -273,107 +214,5 @@ bool Kdbx3Reader::readHeaderField()
break;
}
return !m_headerEnd;
}
void Kdbx3Reader::setCipher(const QByteArray& data)
{
if (data.size() != Uuid::Length) {
raiseError("Invalid cipher uuid length");
return;
}
Uuid uuid(data);
if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) {
raiseError("Unsupported cipher");
return;
}
m_db->setCipher(uuid);
}
void Kdbx3Reader::setCompressionFlags(const QByteArray& data)
{
if (data.size() != 4) {
raiseError("Invalid compression flags length");
return;
}
auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
if (id > Database::CompressionAlgorithmMax) {
raiseError("Unsupported compression algorithm");
return;
}
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
}
void Kdbx3Reader::setMasterSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid master seed size");
return;
}
m_masterSeed = data;
}
void Kdbx3Reader::setTransformSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid transform seed size");
return;
}
auto kdf = m_db->kdf();
if (!kdf.isNull()) {
kdf->setSeed(data);
}
}
void Kdbx3Reader::setTransformRounds(const QByteArray& data)
{
if (data.size() != 8) {
raiseError("Invalid transform rounds size");
return;
}
auto rounds = Endian::bytesToSizedInt<quint64>(data, KeePass2::BYTEORDER);
auto kdf = m_db->kdf();
if (!kdf.isNull()) {
kdf->setRounds(rounds);
}
}
void Kdbx3Reader::setEncryptionIV(const QByteArray& data)
{
m_encryptionIV = data;
}
void Kdbx3Reader::setProtectedStreamKey(const QByteArray& data)
{
m_protectedStreamKey = data;
}
void Kdbx3Reader::setStreamStartBytes(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid start bytes size");
return;
}
m_streamStartBytes = data;
}
void Kdbx3Reader::setInnerRandomStreamID(const QByteArray& data)
{
if (data.size() != 4) {
raiseError("Invalid random stream id size");
return;
}
quint32 id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo ||
irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) {
raiseError("Invalid inner random stream cipher");
return;
}
m_irsAlgo = irsAlgo;
return !headerEnd;
}

View File

@ -19,45 +19,19 @@
#ifndef KEEPASSX_KDBX3READER_H
#define KEEPASSX_KDBX3READER_H
#include <QCoreApplication>
#include "format/KdbxReader.h"
#include "format/KeePass2Reader.h"
#include "keys/CompositeKey.h"
class Database;
class QIODevice;
class Kdbx3Reader: public BaseKeePass2Reader
/**
* KDBX 2/3 reader implementation.
*/
class Kdbx3Reader: public KdbxReader
{
Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader)
public:
Kdbx3Reader();
Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
const CompositeKey& key, bool keepDatabase) override;
using BaseKeePass2Reader::readDatabase;
virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override;
private:
bool readHeaderField();
void setCipher(const QByteArray& data);
void setCompressionFlags(const QByteArray& data);
void setMasterSeed(const QByteArray& data);
void setTransformSeed(const QByteArray& data);
void setTransformRounds(const QByteArray& data);
void setEncryptionIV(const QByteArray& data);
void setProtectedStreamKey(const QByteArray& data);
void setStreamStartBytes(const QByteArray& data);
void setInnerRandomStreamID(const QByteArray& data);
QIODevice* m_device;
QIODevice* m_headerStream;
bool m_headerEnd;
Database* m_db;
QByteArray m_masterSeed;
QByteArray m_encryptionIV;
QByteArray m_streamStartBytes;
protected:
bool readHeaderField(StoreDataStream& headerStream) override;
};
#endif // KEEPASSX_KDBX3READER_H

View File

@ -19,25 +19,17 @@
#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/KeePass2.h"
#include "format/KeePass2RandomStream.h"
#include "format/Kdbx3XmlWriter.h"
#include "format/KdbxXmlWriter.h"
#include "streams/HashedBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/SymmetricCipherStream.h"
Kdbx3Writer::Kdbx3Writer()
: m_device(0)
{
}
bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
{
m_error = false;
@ -59,6 +51,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
return false;
}
// generate transformed master key
CryptoHash hash(CryptoHash::Sha256);
hash.addData(masterSeed);
hash.addData(db->challengeResponseKey());
@ -66,37 +59,39 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
// write header
QBuffer header;
header.open(QIODevice::WriteOnly);
m_device = &header;
CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes<qint32>(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes<qint32>(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes<qint32>(KeePass2::FILE_VERSION_3, KeePass2::BYTEORDER)));
writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_3);
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray()));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CompressionFlags,
Endian::sizedIntToBytes<qint32>(db->compressionAlgo(),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray()));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::CompressionFlags,
Endian::sizedIntToBytes<qint32>(db->compressionAlgo(),
KeePass2::BYTEORDER)));
auto kdf = db->kdf();
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::MasterSeed, masterSeed));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::TransformSeed, kdf->seed()));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::TransformRounds,
Endian::sizedIntToBytes<qint64>(kdf->rounds(),
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::TransformSeed, kdf->seed()));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::TransformRounds,
Endian::sizedIntToBytes<qint64>(kdf->rounds(),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::ProtectedStreamKey, protectedStreamKey));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::StreamStartBytes, startBytes));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::InnerRandomStreamID,
Endian::sizedIntToBytes<qint32>(static_cast<qint32>(
KeePass2::ProtectedStreamAlgo::Salsa20),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::ProtectedStreamKey, protectedStreamKey));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::StreamStartBytes, startBytes));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::InnerRandomStreamID,
Endian::sizedIntToBytes<qint32>(static_cast<qint32>(KeePass2::ProtectedStreamAlgo::Salsa20),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EndOfHeader, endOfHeader));
CHECK_RETURN_FALSE(writeHeaderField<quint16>(&header, KeePass2::HeaderFieldID::EndOfHeader, endOfHeader));
header.close();
m_device = device;
QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
CHECK_RETURN_FALSE(writeData(header.data()));
// write header data
CHECK_RETURN_FALSE(writeData(device, header.data()));
// hash header
const QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
// write cipher stream
SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher());
SymmetricCipherStream cipherStream(device, algo,
SymmetricCipher::algorithmMode(algo), SymmetricCipher::Encrypt);
@ -105,8 +100,7 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
raiseError(cipherStream.errorString());
return false;
}
m_device = &cipherStream;
CHECK_RETURN_FALSE(writeData(startBytes));
CHECK_RETURN_FALSE(writeData(&cipherStream, startBytes));
HashedBlockStream hashedStream(&cipherStream);
if (!hashedStream.open(QIODevice::WriteOnly)) {
@ -114,10 +108,11 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
return false;
}
QIODevice* outputDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) {
m_device = &hashedStream;
outputDevice = &hashedStream;
} else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
@ -125,17 +120,19 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
raiseError(ioCompressor->errorString());
return false;
}
m_device = ioCompressor.data();
outputDevice = ioCompressor.data();
}
Q_ASSERT(outputDevice);
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::Salsa20);
if (!randomStream.init(protectedStreamKey)) {
raiseError(randomStream.errorString());
return false;
}
Kdbx3XmlWriter xmlWriter;
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
KdbxXmlWriter xmlWriter(KeePass2::FILE_VERSION_3);
xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash);
// Explicitly close/reset streams so they are flushed and we can detect
// errors. QIODevice::close() resets errorString() etc.
@ -153,31 +150,8 @@ bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
if (xmlWriter.hasError()) {
raiseError(xmlWriter.errorString());
}
return true;
}
bool Kdbx3Writer::writeData(const QByteArray& data)
{
if (m_device->write(data) != data.size()) {
raiseError(m_device->errorString());
return false;
} else {
return true;
}
}
bool Kdbx3Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data)
{
Q_ASSERT(data.size() <= 65535);
QByteArray fieldIdArr;
fieldIdArr[0] = static_cast<char>(fieldId);
CHECK_RETURN_FALSE(writeData(fieldIdArr));
CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes<qint16>(static_cast<quint16>(data.size()),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(data));
return true;
}

View File

@ -1,6 +1,5 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* 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
* it under the terms of the GNU General Public License as published by
@ -19,30 +18,15 @@
#ifndef KEEPASSX_KDBX3WRITER_H
#define KEEPASSX_KDBX3WRITER_H
#include <QCoreApplication>
#include "KdbxWriter.h"
#include "format/KeePass2.h"
#include "format/KeePass2Writer.h"
#include "keys/CompositeKey.h"
class Database;
class QIODevice;
class Kdbx3Writer: public BaseKeePass2Writer
/**
* KDBX2/3 writer implementation.
*/
class Kdbx3Writer: public KdbxWriter
{
Q_DECLARE_TR_FUNCTIONS(Kdbx3Writer)
public:
Kdbx3Writer();
using BaseKeePass2Writer::writeDatabase;
bool writeDatabase(QIODevice* device, Database* db);
private:
bool writeData(const QByteArray& data);
bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data);
QIODevice* m_device;
bool writeDatabase(QIODevice* device, Database* db) override;
};
#endif // KEEPASSX_KDBX3WRITER_H

File diff suppressed because it is too large Load Diff

View File

@ -1,102 +0,0 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* 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_KDBX3XMLREADER_H
#define KEEPASSX_KDBX3XMLREADER_H
#include <QColor>
#include <QCoreApplication>
#include <QDateTime>
#include <QHash>
#include <QPair>
#include <QXmlStreamReader>
#include "core/TimeInfo.h"
#include "core/Uuid.h"
class Database;
class Entry;
class Group;
class KeePass2RandomStream;
class Metadata;
class Kdbx3XmlReader
{
Q_DECLARE_TR_FUNCTIONS(Kdbx3XmlReader)
public:
Kdbx3XmlReader();
Database* readDatabase(QIODevice* device);
void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
Database* readDatabase(const QString& filename);
bool hasError();
QString errorString();
QByteArray headerHash();
void setStrictMode(bool strictMode);
private:
bool parseKeePassFile();
void parseMeta();
void parseMemoryProtection();
void parseCustomIcons();
void parseIcon();
void parseBinaries();
void parseCustomData();
void parseCustomDataItem();
bool parseRoot();
Group* parseGroup();
void parseDeletedObjects();
void parseDeletedObject();
Entry* parseEntry(bool history);
void parseEntryString(Entry* entry);
QPair<QString, QString> parseEntryBinary(Entry* entry);
void parseAutoType(Entry* entry);
void parseAutoTypeAssoc(Entry* entry);
QList<Entry*> parseEntryHistory();
TimeInfo parseTimes();
QString readString();
bool readBool();
QDateTime readDateTime();
QColor readColor();
int readNumber();
Uuid readUuid();
QByteArray readBinary();
QByteArray readCompressedBinary();
Group* getGroup(const Uuid& uuid);
Entry* getEntry(const Uuid& uuid);
void raiseError(const QString& errorMessage);
void skipCurrentElement();
QXmlStreamReader m_xml;
KeePass2RandomStream* m_randomStream;
Database* m_db;
Metadata* m_meta;
Group* m_tmpParent;
QHash<Uuid, Group*> m_groups;
QHash<Uuid, Entry*> m_entries;
QHash<QString, QByteArray> m_binaryPool;
QHash<QString, QPair<Entry*, QString> > m_binaryMap;
QByteArray m_headerHash;
bool m_error;
QString m_errorStr;
bool m_strictMode;
};
#endif // KEEPASSX_KDBX3XMLREADER_H

View File

@ -1,566 +0,0 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* 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 "Kdbx3XmlWriter.h"
#include <QBuffer>
#include <QFile>
#include "core/Metadata.h"
#include "format/KeePass2RandomStream.h"
#include "streams/QtIOCompressor"
Kdbx3XmlWriter::Kdbx3XmlWriter()
: m_db(nullptr)
, m_meta(nullptr)
, m_randomStream(nullptr)
, m_error(false)
{
m_xml.setAutoFormatting(true);
m_xml.setAutoFormattingIndent(-1); // 1 tab
m_xml.setCodec("UTF-8");
}
void Kdbx3XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream,
const QByteArray& headerHash)
{
m_db = db;
m_meta = db->metadata();
m_randomStream = randomStream;
m_headerHash = headerHash;
generateIdMap();
m_xml.setDevice(device);
m_xml.writeStartDocument("1.0", true);
m_xml.writeStartElement("KeePassFile");
writeMetadata();
writeRoot();
m_xml.writeEndElement();
m_xml.writeEndDocument();
if (m_xml.hasError()) {
raiseError(device->errorString());
}
}
void Kdbx3XmlWriter::writeDatabase(const QString& filename, Database* db)
{
QFile file(filename);
file.open(QIODevice::WriteOnly | QIODevice::Truncate);
writeDatabase(&file, db);
}
bool Kdbx3XmlWriter::hasError()
{
return m_error;
}
QString Kdbx3XmlWriter::errorString()
{
return m_errorStr;
}
void Kdbx3XmlWriter::generateIdMap()
{
const QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
int nextId = 0;
for (Entry* entry : allEntries) {
const QList<QString> attachmentKeys = entry->attachments()->keys();
for (const QString& key : attachmentKeys) {
QByteArray data = entry->attachments()->value(key);
if (!m_idMap.contains(data)) {
m_idMap.insert(data, nextId++);
}
}
}
}
void Kdbx3XmlWriter::writeMetadata()
{
m_xml.writeStartElement("Meta");
writeString("Generator", m_meta->generator());
if (!m_headerHash.isEmpty()) {
writeBinary("HeaderHash", m_headerHash);
}
writeString("DatabaseName", m_meta->name());
writeDateTime("DatabaseNameChanged", m_meta->nameChanged());
writeString("DatabaseDescription", m_meta->description());
writeDateTime("DatabaseDescriptionChanged", m_meta->descriptionChanged());
writeString("DefaultUserName", m_meta->defaultUserName());
writeDateTime("DefaultUserNameChanged", m_meta->defaultUserNameChanged());
writeNumber("MaintenanceHistoryDays", m_meta->maintenanceHistoryDays());
writeColor("Color", m_meta->color());
writeDateTime("MasterKeyChanged", m_meta->masterKeyChanged());
writeNumber("MasterKeyChangeRec", m_meta->masterKeyChangeRec());
writeNumber("MasterKeyChangeForce", m_meta->masterKeyChangeForce());
writeMemoryProtection();
writeCustomIcons();
writeBool("RecycleBinEnabled", m_meta->recycleBinEnabled());
writeUuid("RecycleBinUUID", m_meta->recycleBin());
writeDateTime("RecycleBinChanged", m_meta->recycleBinChanged());
writeUuid("EntryTemplatesGroup", m_meta->entryTemplatesGroup());
writeDateTime("EntryTemplatesGroupChanged", m_meta->entryTemplatesGroupChanged());
writeUuid("LastSelectedGroup", m_meta->lastSelectedGroup());
writeUuid("LastTopVisibleGroup", m_meta->lastTopVisibleGroup());
writeNumber("HistoryMaxItems", m_meta->historyMaxItems());
writeNumber("HistoryMaxSize", m_meta->historyMaxSize());
writeBinaries();
writeCustomData();
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeMemoryProtection()
{
m_xml.writeStartElement("MemoryProtection");
writeBool("ProtectTitle", m_meta->protectTitle());
writeBool("ProtectUserName", m_meta->protectUsername());
writeBool("ProtectPassword", m_meta->protectPassword());
writeBool("ProtectURL", m_meta->protectUrl());
writeBool("ProtectNotes", m_meta->protectNotes());
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeCustomIcons()
{
m_xml.writeStartElement("CustomIcons");
const QList<Uuid> customIconsOrder = m_meta->customIconsOrder();
for (const Uuid& uuid : customIconsOrder) {
writeIcon(uuid, m_meta->customIcon(uuid));
}
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
{
m_xml.writeStartElement("Icon");
writeUuid("UUID", uuid);
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
// TODO: check !icon.save()
icon.save(&buffer, "PNG");
buffer.close();
writeBinary("Data", ba);
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeBinaries()
{
m_xml.writeStartElement("Binaries");
QHash<QByteArray, int>::const_iterator i;
for (i = m_idMap.constBegin(); i != m_idMap.constEnd(); ++i) {
m_xml.writeStartElement("Binary");
m_xml.writeAttribute("ID", QString::number(i.value()));
QByteArray data;
if (m_db->compressionAlgo() == Database::CompressionGZip) {
m_xml.writeAttribute("Compressed", "True");
QBuffer buffer;
buffer.open(QIODevice::ReadWrite);
QtIOCompressor compressor(&buffer);
compressor.setStreamFormat(QtIOCompressor::GzipFormat);
compressor.open(QIODevice::WriteOnly);
qint64 bytesWritten = compressor.write(i.key());
Q_ASSERT(bytesWritten == i.key().size());
Q_UNUSED(bytesWritten);
compressor.close();
buffer.seek(0);
data = buffer.readAll();
} else {
data = i.key();
}
if (!data.isEmpty()) {
m_xml.writeCharacters(QString::fromLatin1(data.toBase64()));
}
m_xml.writeEndElement();
}
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeCustomData()
{
m_xml.writeStartElement("CustomData");
QHash<QString, QString> customFields = m_meta->customFields();
const QList<QString> keyList = customFields.keys();
for (const QString& key : keyList) {
writeCustomDataItem(key, customFields.value(key));
}
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeCustomDataItem(const QString& key, const QString& value)
{
m_xml.writeStartElement("Item");
writeString("Key", key);
writeString("Value", value);
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeRoot()
{
Q_ASSERT(m_db->rootGroup());
m_xml.writeStartElement("Root");
writeGroup(m_db->rootGroup());
writeDeletedObjects();
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeGroup(const Group* group)
{
Q_ASSERT(!group->uuid().isNull());
m_xml.writeStartElement("Group");
writeUuid("UUID", group->uuid());
writeString("Name", group->name());
writeString("Notes", group->notes());
writeNumber("IconID", group->iconNumber());
if (!group->iconUuid().isNull()) {
writeUuid("CustomIconUUID", group->iconUuid());
}
writeTimes(group->timeInfo());
writeBool("IsExpanded", group->isExpanded());
writeString("DefaultAutoTypeSequence", group->defaultAutoTypeSequence());
writeTriState("EnableAutoType", group->autoTypeEnabled());
writeTriState("EnableSearching", group->searchingEnabled());
writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry());
const QList<Entry*> entryList = group->entries();
for (const Entry* entry : entryList) {
writeEntry(entry);
}
const QList<Group*> children = group->children();
for (const Group* child : children) {
writeGroup(child);
}
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeTimes(const TimeInfo& ti)
{
m_xml.writeStartElement("Times");
writeDateTime("LastModificationTime", ti.lastModificationTime());
writeDateTime("CreationTime", ti.creationTime());
writeDateTime("LastAccessTime", ti.lastAccessTime());
writeDateTime("ExpiryTime", ti.expiryTime());
writeBool("Expires", ti.expires());
writeNumber("UsageCount", ti.usageCount());
writeDateTime("LocationChanged", ti.locationChanged());
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeDeletedObjects()
{
m_xml.writeStartElement("DeletedObjects");
const QList<DeletedObject> delObjList = m_db->deletedObjects();
for (const DeletedObject& delObj : delObjList) {
writeDeletedObject(delObj);
}
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeDeletedObject(const DeletedObject& delObj)
{
m_xml.writeStartElement("DeletedObject");
writeUuid("UUID", delObj.uuid);
writeDateTime("DeletionTime", delObj.deletionTime);
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeEntry(const Entry* entry)
{
Q_ASSERT(!entry->uuid().isNull());
m_xml.writeStartElement("Entry");
writeUuid("UUID", entry->uuid());
writeNumber("IconID", entry->iconNumber());
if (!entry->iconUuid().isNull()) {
writeUuid("CustomIconUUID", entry->iconUuid());
}
writeColor("ForegroundColor", entry->foregroundColor());
writeColor("BackgroundColor", entry->backgroundColor());
writeString("OverrideURL", entry->overrideUrl());
writeString("Tags", entry->tags());
writeTimes(entry->timeInfo());
const QList<QString> attributesKeyList = entry->attributes()->keys();
for (const QString& key : attributesKeyList) {
m_xml.writeStartElement("String");
bool protect = (((key == "Title") && m_meta->protectTitle()) ||
((key == "UserName") && m_meta->protectUsername()) ||
((key == "Password") && m_meta->protectPassword()) ||
((key == "URL") && m_meta->protectUrl()) ||
((key == "Notes") && m_meta->protectNotes()) ||
entry->attributes()->isProtected(key));
writeString("Key", key);
m_xml.writeStartElement("Value");
QString value;
if (protect) {
if (m_randomStream) {
m_xml.writeAttribute("Protected", "True");
bool ok;
QByteArray rawData = m_randomStream->process(entry->attributes()->value(key).toUtf8(), &ok);
if (!ok) {
raiseError(m_randomStream->errorString());
}
value = QString::fromLatin1(rawData.toBase64());
} else {
m_xml.writeAttribute("ProtectInMemory", "True");
value = entry->attributes()->value(key);
}
} else {
value = entry->attributes()->value(key);
}
if (!value.isEmpty()) {
m_xml.writeCharacters(stripInvalidXml10Chars(value));
}
m_xml.writeEndElement();
m_xml.writeEndElement();
}
const QList<QString> attachmentsKeyList = entry->attachments()->keys();
for (const QString& key : attachmentsKeyList) {
m_xml.writeStartElement("Binary");
writeString("Key", key);
m_xml.writeStartElement("Value");
m_xml.writeAttribute("Ref", QString::number(m_idMap[entry->attachments()->value(key)]));
m_xml.writeEndElement();
m_xml.writeEndElement();
}
writeAutoType(entry);
// write history only for entries that are not history items
if (entry->parent()) {
writeEntryHistory(entry);
}
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeAutoType(const Entry* entry)
{
m_xml.writeStartElement("AutoType");
writeBool("Enabled", entry->autoTypeEnabled());
writeNumber("DataTransferObfuscation", entry->autoTypeObfuscation());
writeString("DefaultSequence", entry->defaultAutoTypeSequence());
const QList<AutoTypeAssociations::Association> autoTypeAssociations = entry->autoTypeAssociations()->getAll();
for (const AutoTypeAssociations::Association& assoc : autoTypeAssociations) {
writeAutoTypeAssoc(assoc);
}
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc)
{
m_xml.writeStartElement("Association");
writeString("Window", assoc.window);
writeString("KeystrokeSequence", assoc.sequence);
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeEntryHistory(const Entry* entry)
{
m_xml.writeStartElement("History");
const QList<Entry*>& historyItems = entry->historyItems();
for (const Entry* item : historyItems) {
writeEntry(item);
}
m_xml.writeEndElement();
}
void Kdbx3XmlWriter::writeString(const QString& qualifiedName, const QString& string)
{
if (string.isEmpty()) {
m_xml.writeEmptyElement(qualifiedName);
} else {
m_xml.writeTextElement(qualifiedName, stripInvalidXml10Chars(string));
}
}
void Kdbx3XmlWriter::writeNumber(const QString& qualifiedName, int number)
{
writeString(qualifiedName, QString::number(number));
}
void Kdbx3XmlWriter::writeBool(const QString& qualifiedName, bool b)
{
writeString(qualifiedName, b ? "True" : "False");
}
void Kdbx3XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime)
{
Q_ASSERT(dateTime.isValid());
Q_ASSERT(dateTime.timeSpec() == Qt::UTC);
QString dateTimeStr = dateTime.toString(Qt::ISODate);
// Qt < 4.8 doesn't append a 'Z' at the end
if (!dateTimeStr.isEmpty() && dateTimeStr[dateTimeStr.size() - 1] != 'Z') {
dateTimeStr.append('Z');
}
writeString(qualifiedName, dateTimeStr);
}
void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid)
{
writeString(qualifiedName, uuid.toBase64());
}
void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Group* group)
{
writeUuid(qualifiedName, group ? group->uuid() : Uuid());
}
void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry)
{
writeUuid(qualifiedName, entry ? entry->uuid() : Uuid());
}
void Kdbx3XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba)
{
writeString(qualifiedName, QString::fromLatin1(ba.toBase64()));
}
void Kdbx3XmlWriter::writeColor(const QString& qualifiedName, const QColor& color)
{
QString colorStr;
if (color.isValid()) {
colorStr = QString("#%1%2%3").arg(colorPartToString(color.red()),
colorPartToString(color.green()),
colorPartToString(color.blue()));
}
writeString(qualifiedName, colorStr);
}
void Kdbx3XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState)
{
QString value;
if (triState == Group::Inherit) {
value = "null";
} else if (triState == Group::Enable) {
value = "true";
} else {
value = "false";
}
writeString(qualifiedName, value);
}
QString Kdbx3XmlWriter::colorPartToString(int value)
{
QString str = QString::number(value, 16).toUpper();
if (str.length() == 1) {
str.prepend("0");
}
return str;
}
QString Kdbx3XmlWriter::stripInvalidXml10Chars(QString str)
{
for (int i = str.size() - 1; i >= 0; i--) {
const QChar ch = str.at(i);
const ushort uc = ch.unicode();
if (ch.isLowSurrogate() && i != 0 && str.at(i - 1).isHighSurrogate()) {
// keep valid surrogate pair
i--;
} else if ((uc < 0x20 && uc != 0x09 && uc != 0x0A && uc != 0x0D) // control characters
|| (uc >= 0x7F && uc <= 0x84) // control characters, valid but discouraged by XML
|| (uc >= 0x86 && uc <= 0x9F) // control characters, valid but discouraged by XML
|| (uc > 0xFFFD) // noncharacter
|| ch.isLowSurrogate() // single low surrogate
|| ch.isHighSurrogate()) // single high surrogate
{
qWarning("Stripping invalid XML 1.0 codepoint %x", uc);
str.remove(i, 1);
}
}
return str;
}
void Kdbx3XmlWriter::raiseError(const QString& errorMessage)
{
m_error = true;
m_errorStr = errorMessage;
}

View File

@ -18,77 +18,24 @@
#include "Kdbx4Reader.h"
#include <QBuffer>
#include <QFile>
#include "crypto/kdf/AesKdf.h"
#include "streams/HmacBlockStream.h"
#include "core/Group.h"
#include "core/Database.h"
#include "core/Endian.h"
#include "crypto/CryptoHash.h"
#include "format/KeePass1.h"
#include "format/KeePass2.h"
#include "format/KeePass2RandomStream.h"
#include "format/Kdbx4XmlReader.h"
#include "streams/HashedBlockStream.h"
#include "format/KdbxXmlReader.h"
#include "streams/HmacBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/StoreDataStream.h"
#include "streams/SymmetricCipherStream.h"
Kdbx4Reader::Kdbx4Reader()
: m_device(nullptr)
, m_db(nullptr)
Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
const CompositeKey& key, bool keepDatabase)
{
}
Q_ASSERT(m_kdbxVersion == KeePass2::FILE_VERSION_4);
Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
{
QScopedPointer<Database> db(new Database());
m_db = db.data();
m_device = device;
m_error = false;
m_errorStr.clear();
m_xmlData.clear();
m_masterSeed.clear();
m_encryptionIV.clear();
m_protectedStreamKey.clear();
m_binaryPool.clear();
StoreDataStream headerStream(m_device);
headerStream.open(QIODevice::ReadOnly);
QIODevice* headerIo = &headerStream;
bool ok;
quint32 signature1 = Endian::readSizedInt<quint32>(headerIo, KeePass2::BYTEORDER, &ok);
if (!ok || signature1 != KeePass2::SIGNATURE_1) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 signature2 = Endian::readSizedInt<quint32>(headerIo, KeePass2::BYTEORDER, &ok);
if (ok && signature2 == KeePass1::SIGNATURE_2) {
raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
"This is a one-way migration. You won't be able to open the imported "
"database with the old KeePassX 0.4 version."));
return nullptr;
} else if (!ok || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 version = Endian::readSizedInt<quint32>(headerIo, KeePass2::BYTEORDER, &ok)
& KeePass2::FILE_VERSION_CRITICAL_MASK;
if (!ok || version != (KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK)) {
raiseError(tr("Unsupported KeePass KDBX 4 database version."));
return nullptr;
}
while (readHeaderField(headerIo) && !hasError()) {
}
headerStream.close();
if (hasError()) {
return nullptr;
}
@ -97,7 +44,7 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key,
if (m_masterSeed.isEmpty()
|| m_encryptionIV.isEmpty()
|| m_db->cipher().isNull()) {
raiseError("missing database headers");
raiseError(tr("missing database headers"));
return nullptr;
}
@ -106,7 +53,7 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key,
return nullptr;
}
if (m_db->challengeMasterSeed(m_masterSeed) == false) {
if (!m_db->challengeMasterSeed(m_masterSeed)) {
raiseError(tr("Unable to issue challenge-response."));
return nullptr;
}
@ -117,24 +64,24 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key,
hash.addData(m_db->transformedMasterKey());
QByteArray finalKey = hash.result();
QByteArray headerSha256 = m_device->read(32);
QByteArray headerHmac = m_device->read(32);
QByteArray headerSha256 = device->read(32);
QByteArray headerHmac = device->read(32);
if (headerSha256.size() != 32 || headerHmac.size() != 32) {
raiseError("Invalid header checksum size");
raiseError(tr("Invalid header checksum size"));
return nullptr;
}
if (headerSha256 != CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256)) {
raiseError("Header SHA256 mismatch");
if (headerSha256 != CryptoHash::hash(headerData, CryptoHash::Sha256)) {
raiseError(tr("Header SHA256 mismatch"));
return nullptr;
}
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, m_db->transformedMasterKey());
if (headerHmac != CryptoHash::hmac(headerStream.storedData(),
if (headerHmac != CryptoHash::hmac(headerData,
HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
raiseError(tr("Wrong key or database file is corrupt. (HMAC mismatch)"));
return nullptr;
}
HmacBlockStream hmacStream(m_device, hmacKey);
HmacBlockStream hmacStream(device, hmacKey);
if (!hmacStream.open(QIODevice::ReadOnly)) {
raiseError(hmacStream.errorString());
return nullptr;
@ -142,7 +89,7 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key,
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
if (cipher == SymmetricCipher::InvalidAlgorithm) {
raiseError("Unknown cipher");
raiseError(tr("Unknown cipher"));
return nullptr;
}
SymmetricCipherStream cipherStream(&hmacStream, cipher,
@ -156,7 +103,7 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key,
return nullptr;
}
QIODevice* xmlDevice;
QIODevice* xmlDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (m_db->compressionAlgo() == Database::CompressionNone) {
@ -171,7 +118,6 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key,
xmlDevice = ioCompressor.data();
}
while (readInnerHeaderField(xmlDevice) && !hasError()) {
}
@ -185,50 +131,51 @@ Database* Kdbx4Reader::readDatabase(QIODevice* device, const CompositeKey& key,
return nullptr;
}
QScopedPointer<QBuffer> buffer;
if (m_saveXml) {
QBuffer buffer;
if (saveXml()) {
m_xmlData = xmlDevice->readAll();
buffer.reset(new QBuffer(&m_xmlData));
buffer->open(QIODevice::ReadOnly);
xmlDevice = buffer.data();
buffer.setBuffer(&m_xmlData);
buffer.open(QIODevice::ReadOnly);
xmlDevice = &buffer;
}
Kdbx4XmlReader xmlReader(m_binaryPool);
xmlReader.readDatabase(xmlDevice, m_db, &randomStream);
Q_ASSERT(xmlDevice);
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, m_binaryPool);
xmlReader.readDatabase(xmlDevice, m_db.data(), &randomStream);
if (xmlReader.hasError()) {
raiseError(xmlReader.errorString());
if (keepDatabase) {
return db.take();
return m_db.take();
}
return nullptr;
}
return db.take();
return m_db.take();
}
bool Kdbx4Reader::readHeaderField(QIODevice* device)
bool Kdbx4Reader::readHeaderField(StoreDataStream& device)
{
QByteArray fieldIDArray = device->read(1);
QByteArray fieldIDArray = device.read(1);
if (fieldIDArray.size() != 1) {
raiseError("Invalid header id size");
raiseError(tr("Invalid header id size"));
return false;
}
char fieldID = fieldIDArray.at(0);
bool ok;
auto fieldLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
auto fieldLen = Endian::readSizedInt<quint32>(&device, KeePass2::BYTEORDER, &ok);
if (!ok) {
raiseError("Invalid header field length");
raiseError(tr("Invalid header field length"));
return false;
}
QByteArray fieldData;
if (fieldLen != 0) {
fieldData = device->read(fieldLen);
fieldData = device.read(fieldLen);
if (static_cast<quint32>(fieldData.size()) != fieldLen) {
raiseError("Invalid header data length");
raiseError(tr("Invalid header data length"));
return false;
}
}
@ -256,13 +203,13 @@ bool Kdbx4Reader::readHeaderField(QIODevice* device)
case KeePass2::HeaderFieldID::KdfParameters: {
QBuffer bufIoDevice(&fieldData);
if (!bufIoDevice.open(QIODevice::ReadOnly)) {
raiseError("Failed to open buffer for KDF parameters in header");
raiseError(tr("Failed to open buffer for KDF parameters in header"));
return false;
}
QVariantMap kdfParams = readVariantMap(&bufIoDevice);
QSharedPointer<Kdf> kdf = KeePass2::kdfFromParameters(kdfParams);
if (!kdf) {
raiseError("Invalid KDF parameters");
raiseError(tr("Invalid KDF parameters"));
return false;
}
m_db->setKdf(kdf);
@ -278,7 +225,7 @@ bool Kdbx4Reader::readHeaderField(QIODevice* device)
case KeePass2::HeaderFieldID::TransformSeed:
case KeePass2::HeaderFieldID::StreamStartBytes:
case KeePass2::HeaderFieldID::InnerRandomStreamID:
raiseError("Legacy header fields found in KDBX4 file.");
raiseError(tr("Legacy header fields found in KDBX4 file."));
return false;
default:
@ -289,19 +236,25 @@ bool Kdbx4Reader::readHeaderField(QIODevice* device)
return true;
}
/**
* Helper method for reading KDBX4 inner header fields.
*
* @param device input device
* @return true if there are more inner header fields
*/
bool Kdbx4Reader::readInnerHeaderField(QIODevice* device)
{
QByteArray fieldIDArray = device->read(1);
if (fieldIDArray.size() != 1) {
raiseError("Invalid inner header id size");
raiseError(tr("Invalid inner header id size"));
return false;
}
KeePass2::InnerHeaderFieldID fieldID = static_cast<KeePass2::InnerHeaderFieldID>(fieldIDArray.at(0));
auto fieldID = static_cast<KeePass2::InnerHeaderFieldID>(fieldIDArray.at(0));
bool ok;
quint32 fieldLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
auto fieldLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
if (!ok) {
raiseError("Invalid inner header field length");
raiseError(tr("Invalid inner header field length"));
return false;
}
@ -309,7 +262,7 @@ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device)
if (fieldLen != 0) {
fieldData = device->read(fieldLen);
if (static_cast<quint32>(fieldData.size()) != fieldLen) {
raiseError("Invalid header data length");
raiseError(tr("Invalid header data length"));
return false;
}
}
@ -328,20 +281,22 @@ bool Kdbx4Reader::readInnerHeaderField(QIODevice* device)
case KeePass2::InnerHeaderFieldID::Binary:
if (fieldLen < 1) {
raiseError("Invalid inner header binary size");
raiseError(tr("Invalid inner header binary size"));
return false;
}
m_binaryPool.insert(QString::number(m_binaryPool.size()), fieldData.mid(1));
break;
default:
qWarning("Unknown inner header field read: id=%hhu", static_cast<quint8>(fieldID));
break;
}
return true;
}
/**
* Helper method for reading KDF parameters into variant map.
*
* @param device input device
* @return filled variant map
*/
QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device)
{
bool ok;
@ -350,41 +305,41 @@ QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device)
quint16 maxVersion = KeePass2::VARIANTMAP_VERSION & KeePass2::VARIANTMAP_CRITICAL_MASK;
if (!ok || (version > maxVersion)) {
raiseError(tr("Unsupported KeePass variant map version."));
return QVariantMap();
return {};
}
QVariantMap vm;
QByteArray fieldTypeArray;
KeePass2::VariantMapFieldType fieldType;
KeePass2::VariantMapFieldType fieldType = KeePass2::VariantMapFieldType::End;
while (((fieldTypeArray = device->read(1)).size() == 1)
&& ((fieldType = static_cast<KeePass2::VariantMapFieldType>(fieldTypeArray.at(0)))
!= KeePass2::VariantMapFieldType::End)) {
quint32 nameLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
auto nameLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
if (!ok) {
raiseError("Invalid variant map entry name length");
return QVariantMap();
raiseError(tr("Invalid variant map entry name length"));
return {};
}
QByteArray nameBytes;
if (nameLen != 0) {
nameBytes = device->read(nameLen);
if (static_cast<quint32>(nameBytes.size()) != nameLen) {
raiseError("Invalid variant map entry name data");
return QVariantMap();
raiseError(tr("Invalid variant map entry name data"));
return {};
}
}
QString name = QString::fromUtf8(nameBytes);
quint32 valueLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
auto valueLen = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
if (!ok) {
raiseError("Invalid variant map entry value length");
return QVariantMap();
raiseError(tr("Invalid variant map entry value length"));
return {};
}
QByteArray valueBytes;
if (valueLen != 0) {
valueBytes = device->read(valueLen);
if (static_cast<quint32>(valueBytes.size()) != valueLen) {
raiseError("Invalid variant map entry value data");
return QVariantMap();
raiseError(tr("Invalid variant map entry value data"));
return {};
}
}
@ -393,127 +348,70 @@ QVariantMap Kdbx4Reader::readVariantMap(QIODevice* device)
if (valueLen == 1) {
vm.insert(name, QVariant(valueBytes.at(0) != 0));
} else {
raiseError("Invalid variant map Bool entry value length");
return QVariantMap();
raiseError(tr("Invalid variant map Bool entry value length"));
return {};
}
break;
case KeePass2::VariantMapFieldType::Int32:
if (valueLen == 4) {
vm.insert(name, QVariant(Endian::bytesToSizedInt<qint32>(valueBytes, KeePass2::BYTEORDER)));
} else {
raiseError("Invalid variant map Int32 entry value length");
return QVariantMap();
raiseError(tr("Invalid variant map Int32 entry value length"));
return {};
}
break;
case KeePass2::VariantMapFieldType::UInt32:
if (valueLen == 4) {
vm.insert(name, QVariant(Endian::bytesToSizedInt<quint32>(valueBytes, KeePass2::BYTEORDER)));
} else {
raiseError("Invalid variant map UInt32 entry value length");
return QVariantMap();
raiseError(tr("Invalid variant map UInt32 entry value length"));
return {};
}
break;
case KeePass2::VariantMapFieldType::Int64:
if (valueLen == 8) {
vm.insert(name, QVariant(Endian::bytesToSizedInt<qint64>(valueBytes, KeePass2::BYTEORDER)));
} else {
raiseError("Invalid variant map Int64 entry value length");
return QVariantMap();
raiseError(tr("Invalid variant map Int64 entry value length"));
return {};
}
break;
case KeePass2::VariantMapFieldType::UInt64:
if (valueLen == 8) {
vm.insert(name, QVariant(Endian::bytesToSizedInt<quint64>(valueBytes, KeePass2::BYTEORDER)));
} else {
raiseError("Invalid variant map UInt64 entry value length");
return QVariantMap();
raiseError(tr("Invalid variant map UInt64 entry value length"));
return {};
}
break;
case KeePass2::VariantMapFieldType::String:
vm.insert(name, QVariant(QString::fromUtf8(valueBytes)));
break;
case KeePass2::VariantMapFieldType::ByteArray:
vm.insert(name, QVariant(valueBytes));
break;
default:
raiseError("Invalid variant map entry type");
return QVariantMap();
raiseError(tr("Invalid variant map entry type"));
return {};
}
}
if (fieldTypeArray.size() != 1) {
raiseError("Invalid variant map field type size");
return QVariantMap();
raiseError(tr("Invalid variant map field type size"));
return {};
}
return vm;
}
void Kdbx4Reader::setCipher(const QByteArray& data)
{
if (data.size() != Uuid::Length) {
raiseError("Invalid cipher uuid length");
return;
}
Uuid uuid(data);
if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) {
raiseError("Unsupported cipher");
return;
}
m_db->setCipher(uuid);
}
void Kdbx4Reader::setCompressionFlags(const QByteArray& data)
{
if (data.size() != 4) {
raiseError("Invalid compression flags length");
return;
}
auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
if (id > Database::CompressionAlgorithmMax) {
raiseError("Unsupported compression algorithm");
return;
}
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
}
void Kdbx4Reader::setMasterSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid master seed size");
return;
}
m_masterSeed = data;
}
void Kdbx4Reader::setEncryptionIV(const QByteArray& data)
{
m_encryptionIV = data;
}
void Kdbx4Reader::setProtectedStreamKey(const QByteArray& data)
{
m_protectedStreamKey = data;
}
void Kdbx4Reader::setInnerRandomStreamID(const QByteArray& data)
{
if (data.size() != 4) {
raiseError("Invalid random stream id size");
return;
}
auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo || irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) {
raiseError("Invalid inner random stream cipher");
return;
}
m_irsAlgo = irsAlgo;
}
QHash<QString, QByteArray> Kdbx4Reader::binaryPool()
QHash<QString, QByteArray> Kdbx4Reader::binaryPool() const
{
return m_binaryPool;
}

View File

@ -18,48 +18,27 @@
#ifndef KEEPASSX_KDBX4READER_H
#define KEEPASSX_KDBX4READER_H
#include <QCoreApplication>
#include <QHash>
#include <QString>
#include <QByteArray>
#include "format/KdbxReader.h"
#include "format/KeePass2.h"
#include "format/KeePass2Reader.h"
#include "crypto/SymmetricCipher.h"
#include "keys/CompositeKey.h"
#include <QVariantMap>
class Database;
class QIODevice;
class Kdbx4Reader : public BaseKeePass2Reader
/**
* KDBX4 reader implementation.
*/
class Kdbx4Reader : public KdbxReader
{
Q_DECLARE_TR_FUNCTIONS(Kdbx4Reader)
public:
Kdbx4Reader();
Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
const CompositeKey& key, bool keepDatabase) override;
QHash<QString, QByteArray> binaryPool() const;
using BaseKeePass2Reader::readDatabase;
virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override;
QHash<QString, QByteArray> binaryPool();
protected:
bool readHeaderField(StoreDataStream& headerStream) override;
private:
bool readHeaderField(QIODevice* device);
bool readInnerHeaderField(QIODevice* device);
QVariantMap readVariantMap(QIODevice* device);
void setCipher(const QByteArray& data);
void setCompressionFlags(const QByteArray& data);
void setMasterSeed(const QByteArray& data);
void setEncryptionIV(const QByteArray& data);
void setProtectedStreamKey(const QByteArray& data);
void setInnerRandomStreamID(const QByteArray& data);
QIODevice* m_device;
Database* m_db;
QByteArray m_masterSeed;
QByteArray m_encryptionIV;
QHash<QString, QByteArray> m_binaryPool;
};

View File

@ -19,25 +19,16 @@
#include <QBuffer>
#include <QFile>
#include <QIODevice>
#include <QList>
#include <QString>
#include "streams/HmacBlockStream.h"
#include "core/Database.h"
#include "core/Endian.h"
#include "crypto/CryptoHash.h"
#include "crypto/Random.h"
#include "format/KeePass2RandomStream.h"
#include "format/Kdbx4XmlWriter.h"
#include "format/KdbxXmlWriter.h"
#include "streams/QtIOCompressor"
#include "streams/SymmetricCipherStream.h"
Kdbx4Writer::Kdbx4Writer()
: m_device(nullptr)
{
}
bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
{
m_error = false;
@ -45,12 +36,12 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher());
if (algo == SymmetricCipher::InvalidAlgorithm) {
raiseError("Invalid symmetric cipher algorithm.");
raiseError(tr("Invalid symmetric cipher algorithm."));
return false;
}
int ivSize = SymmetricCipher::algorithmIvSize(algo);
if (ivSize < 0) {
raiseError("Invalid symmetric cipher IV size.");
raiseError(tr("Invalid symmetric cipher IV size."));
return false;
}
@ -70,6 +61,7 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
return false;
}
// generate transformed master key
CryptoHash hash(CryptoHash::Sha256);
hash.addData(masterSeed);
hash.addData(db->challengeResponseKey());
@ -77,48 +69,49 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
// write header
QByteArray headerData;
{
QBuffer header;
header.open(QIODevice::WriteOnly);
m_device = &header;
CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(KeePass2::FILE_VERSION_4, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray()));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::CompressionFlags,
Endian::sizedIntToBytes(static_cast<int>(db->compressionAlgo()),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::MasterSeed, masterSeed));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
// Convert current Kdf to basic parameters
writeMagicNumbers(&header, KeePass2::SIGNATURE_1, KeePass2::SIGNATURE_2, KeePass2::FILE_VERSION_4);
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CipherID, db->cipher().toByteArray()));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::CompressionFlags,
Endian::sizedIntToBytes(static_cast<int>(db->compressionAlgo()),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::MasterSeed, masterSeed));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EncryptionIV, encryptionIV));
// convert current Kdf to basic parameters
QVariantMap kdfParams = KeePass2::kdfToParameters(db->kdf());
QByteArray kdfParamBytes;
if (!serializeVariantMap(kdfParams, kdfParamBytes)) {
raiseError("Failed to serialise KDF parameters variant map");
raiseError(tr("Failed to serialize KDF parameters variant map"));
return false;
}
QByteArray publicCustomData = db->publicCustomData();
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::KdfParameters, kdfParamBytes));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::KdfParameters, kdfParamBytes));
if (!publicCustomData.isEmpty()) {
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::PublicCustomData, publicCustomData));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::PublicCustomData, publicCustomData));
}
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::HeaderFieldID::EndOfHeader, endOfHeader));
CHECK_RETURN_FALSE(writeHeaderField<quint32>(&header, KeePass2::HeaderFieldID::EndOfHeader, endOfHeader));
header.close();
m_device = device;
headerData = header.data();
}
CHECK_RETURN_FALSE(writeData(headerData));
CHECK_RETURN_FALSE(writeData(device, headerData));
// hash header
QByteArray headerHash = CryptoHash::hash(headerData, CryptoHash::Sha256);
// write HMAC-authenticated cipher stream
QByteArray hmacKey = KeePass2::hmacKey(masterSeed, db->transformedMasterKey());
QByteArray headerHmac = CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey),
CryptoHash::Sha256);
CHECK_RETURN_FALSE(writeData(headerHash));
CHECK_RETURN_FALSE(writeData(headerHmac));
CHECK_RETURN_FALSE(writeData(device, headerHash));
CHECK_RETURN_FALSE(writeData(device, headerHmac));
QScopedPointer<HmacBlockStream> hmacBlockStream;
QScopedPointer<SymmetricCipherStream> cipherStream;
@ -130,8 +123,8 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
}
cipherStream.reset(new SymmetricCipherStream(hmacBlockStream.data(), algo,
SymmetricCipher::algorithmMode(algo),
SymmetricCipher::Encrypt));
SymmetricCipher::algorithmMode(algo),
SymmetricCipher::Encrypt));
if (!cipherStream->init(finalKey, encryptionIV)) {
raiseError(cipherStream->errorString());
@ -142,9 +135,11 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
return false;
}
QIODevice* outputDevice = nullptr;
QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) {
m_device = cipherStream.data();
outputDevice = cipherStream.data();
} else {
ioCompressor.reset(new QtIOCompressor(cipherStream.data()));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
@ -152,30 +147,18 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
raiseError(ioCompressor->errorString());
return false;
}
m_device = ioCompressor.data();
outputDevice = ioCompressor.data();
}
QHash<QByteArray, int> idMap;
Q_ASSERT(outputDevice);
CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::InnerRandomStreamID,
CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::InnerRandomStreamID,
Endian::sizedIntToBytes(static_cast<int>(KeePass2::ProtectedStreamAlgo::ChaCha20),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::InnerRandomStreamKey,
CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::InnerRandomStreamKey,
protectedStreamKey));
const QList<Entry*> allEntries = db->rootGroup()->entriesRecursive(true);
int nextId = 0;
for (Entry* entry : allEntries) {
const QList<QString> attachmentKeys = entry->attachments()->keys();
for (const QString& key : attachmentKeys) {
QByteArray data = entry->attachments()->value(key);
if (!idMap.contains(data)) {
CHECK_RETURN_FALSE(writeBinary(data));
idMap.insert(data, nextId++);
}
}
}
CHECK_RETURN_FALSE(writeInnerHeaderField(KeePass2::InnerHeaderFieldID::End, QByteArray()));
CHECK_RETURN_FALSE(writeInnerHeaderField(outputDevice, KeePass2::InnerHeaderFieldID::End, QByteArray()));
KeePass2RandomStream randomStream(KeePass2::ProtectedStreamAlgo::ChaCha20);
if (!randomStream.init(protectedStreamKey)) {
@ -183,8 +166,8 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
return false;
}
Kdbx4XmlWriter xmlWriter(KeePass2::FILE_VERSION_4, idMap);
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
KdbxXmlWriter xmlWriter(KeePass2::FILE_VERSION_4);
xmlWriter.writeDatabase(outputDevice, db, &randomStream, headerHash);
// Explicitly close/reset streams so they are flushed and we can detect
// errors. QIODevice::close() resets errorString() etc.
@ -208,61 +191,64 @@ bool Kdbx4Writer::writeDatabase(QIODevice* device, Database* db)
return true;
}
bool Kdbx4Writer::writeData(const QByteArray& data)
{
if (m_device->write(data) != data.size()) {
raiseError(m_device->errorString());
return false;
}
return true;
}
bool Kdbx4Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data)
/**
* Write KDBX4 inner header field.
*
* @param device output device
* @param fieldId field identifier
* @param data header payload
* @return true on success
*/
bool Kdbx4Writer::writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data)
{
QByteArray fieldIdArr;
fieldIdArr[0] = static_cast<char>(fieldId);
CHECK_RETURN_FALSE(writeData(fieldIdArr));
CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast<quint32>(data.size()), KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(data));
CHECK_RETURN_FALSE(writeData(device, fieldIdArr));
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes(static_cast<quint32>(data.size()), KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(device, data));
return true;
}
bool Kdbx4Writer::writeInnerHeaderField(KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data)
{
QByteArray fieldIdArr;
fieldIdArr[0] = static_cast<char>(fieldId);
CHECK_RETURN_FALSE(writeData(fieldIdArr));
CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast<quint32>(data.size()), KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(data));
return true;
}
bool Kdbx4Writer::writeBinary(const QByteArray& data)
/**
* Write binary header field..
*
* @param device output device
* @param fieldId field identifier
* @param data header payload
* @return true on success
*/
bool Kdbx4Writer::writeBinary(QIODevice* device, const QByteArray& data)
{
QByteArray fieldIdArr;
fieldIdArr[0] = static_cast<char>(KeePass2::InnerHeaderFieldID::Binary);
CHECK_RETURN_FALSE(writeData(fieldIdArr));
CHECK_RETURN_FALSE(writeData(Endian::sizedIntToBytes(static_cast<quint32>(data.size() + 1), KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(QByteArray(1, '\1')));
CHECK_RETURN_FALSE(writeData(data));
CHECK_RETURN_FALSE(writeData(device, fieldIdArr));
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes(static_cast<quint32>(data.size() + 1), KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(device, QByteArray(1, '\1')));
CHECK_RETURN_FALSE(writeData(device, data));
return true;
}
bool Kdbx4Writer::serializeVariantMap(const QVariantMap& p, QByteArray& o)
/**
* Serialize KDF parameter variant map to byte array.
*
* @param map input variant map
* @param outputBytes output byte array
* @return true on success
*/
bool Kdbx4Writer::serializeVariantMap(const QVariantMap& map, QByteArray& outputBytes)
{
QBuffer buf(&o);
QBuffer buf(&outputBytes);
buf.open(QIODevice::WriteOnly);
CHECK_RETURN_FALSE(buf.write(Endian::sizedIntToBytes(KeePass2::VARIANTMAP_VERSION, KeePass2::BYTEORDER)) == 2);
bool ok;
QList<QString> keys = p.keys();
QList<QString> keys = map.keys();
for (const auto& k : keys) {
KeePass2::VariantMapFieldType fieldType;
QByteArray data;
QVariant v = p.value(k);
QVariant v = map.value(k);
switch (static_cast<QMetaType::Type>(v.type())) {
case QMetaType::Type::Int:
fieldType = KeePass2::VariantMapFieldType::Int32;

View File

@ -18,35 +18,20 @@
#ifndef KEEPASSX_KDBX4WRITER_H
#define KEEPASSX_KDBX4WRITER_H
#include <QCoreApplication>
#include "KdbxWriter.h"
#include "format/KeePass2.h"
#include "format/KeePass2Writer.h"
#include "keys/CompositeKey.h"
class Database;
class QIODevice;
class Kdbx4Writer : public BaseKeePass2Writer
/**
* KDBX4 writer implementation.
*/
class Kdbx4Writer : public KdbxWriter
{
Q_DECLARE_TR_FUNCTIONS(Kdbx4Writer)
public:
Kdbx4Writer();
using BaseKeePass2Writer::writeDatabase;
bool writeDatabase(QIODevice* device, Database* db);
bool writeDatabase(QIODevice* device, Database* db) override;
private:
bool writeData(const QByteArray& data);
bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data);
bool writeInnerHeaderField(KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data);
QIODevice* m_device;
bool writeBinary(const QByteArray& data);
static bool serializeVariantMap(const QVariantMap& p, QByteArray& o);
bool writeInnerHeaderField(QIODevice* device, KeePass2::InnerHeaderFieldID fieldId, const QByteArray& data);
bool writeBinary(QIODevice* device, const QByteArray& data);
static bool serializeVariantMap(const QVariantMap& map, QByteArray& outputBytes);
};
#endif // KEEPASSX_KDBX4WRITER_H

View File

@ -1,102 +0,0 @@
/*
* 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/>.
*/
#ifndef KEEPASSX_KDBX4XMLREADER_H
#define KEEPASSX_KDBX4XMLREADER_H
#include <QColor>
#include <QCoreApplication>
#include <QDateTime>
#include <QHash>
#include <QPair>
#include <QXmlStreamReader>
#include "core/TimeInfo.h"
#include "core/Uuid.h"
#include "core/Group.h"
class Database;
class Entry;
class KeePass2RandomStream;
class Metadata;
class Kdbx4XmlReader
{
Q_DECLARE_TR_FUNCTIONS(Kdbx4XmlReader)
public:
Kdbx4XmlReader();
Kdbx4XmlReader(QHash<QString, QByteArray>& binaryPool);
Database* readDatabase(QIODevice* device);
void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
Database* readDatabase(const QString& filename);
bool hasError();
QString errorString();
QByteArray headerHash();
void setStrictMode(bool strictMode);
private:
bool parseKeePassFile();
void parseMeta();
void parseMemoryProtection();
void parseCustomIcons();
void parseIcon();
void parseBinaries();
void parseCustomData();
void parseCustomDataItem();
bool parseRoot();
Group* parseGroup();
void parseDeletedObjects();
void parseDeletedObject();
Entry* parseEntry(bool history);
void parseEntryString(Entry* entry);
QPair<QString, QString> parseEntryBinary(Entry* entry);
void parseAutoType(Entry* entry);
void parseAutoTypeAssoc(Entry* entry);
QList<Entry*> parseEntryHistory();
TimeInfo parseTimes();
QString readString();
bool readBool();
QDateTime readDateTime();
QColor readColor();
int readNumber();
Uuid readUuid();
QByteArray readBinary();
QByteArray readCompressedBinary();
Group* getGroup(const Uuid& uuid);
Entry* getEntry(const Uuid& uuid);
void raiseError(const QString& errorMessage);
void skipCurrentElement();
QXmlStreamReader m_xml;
KeePass2RandomStream* m_randomStream;
Database* m_db;
Metadata* m_meta;
QScopedPointer<Group> m_tmpParent;
QHash<Uuid, Group*> m_groups;
QHash<Uuid, Entry*> m_entries;
QHash<QString, QByteArray> m_binaryPool;
QHash<QString, QPair<Entry*, QString> > m_binaryMap;
QByteArray m_headerHash;
bool m_error;
QString m_errorStr;
bool m_strictMode;
};
#endif // KEEPASSX_KDBX4XMLREADER_H

267
src/format/KdbxReader.cpp Normal file
View File

@ -0,0 +1,267 @@
/*
* 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 "KdbxReader.h"
#include "core/Database.h"
#include "core/Endian.h"
/**
* Read KDBX magic header numbers from a device.
*
* @param device input device
* @param sig1 KDBX signature 1
* @param sig2 KDBX signature 2
* @param version KDBX version
* @return true if magic numbers were read successfully
*/
bool KdbxReader::readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version)
{
bool ok;
sig1 = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
if (!ok) {
return false;
}
sig2 = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
if (!ok) {
return false;
}
version = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
return ok;
}
/**
* Read KDBX stream from device.
* The device will automatically be reset to 0 before reading.
*
* @param device input device
* @param key database encryption composite key
* @param keepDatabase keep database in case of read failure
* @return pointer to the read database, nullptr on failure
*/
Database* KdbxReader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
{
device->seek(0);
m_db.reset(new Database());
m_xmlData.clear();
m_masterSeed.clear();
m_encryptionIV.clear();
m_streamStartBytes.clear();
m_protectedStreamKey.clear();
StoreDataStream headerStream(device);
headerStream.open(QIODevice::ReadOnly);
// read KDBX magic numbers
quint32 sig1, sig2;
readMagicNumbers(&headerStream, sig1, sig2, m_kdbxVersion);
m_kdbxSignature = qMakePair(sig1, sig2);
// mask out minor version
m_kdbxVersion &= KeePass2::FILE_VERSION_CRITICAL_MASK;
// read header fields
while (readHeaderField(headerStream) && !hasError()) {
}
headerStream.close();
if (hasError()) {
return nullptr;
}
// read payload
return readDatabaseImpl(device, headerStream.storedData(), key, keepDatabase);
}
bool KdbxReader::hasError() const
{
return m_error;
}
QString KdbxReader::errorString() const
{
return m_errorStr;
}
bool KdbxReader::saveXml() const
{
return m_saveXml;
}
void KdbxReader::setSaveXml(bool save)
{
m_saveXml = save;
}
QByteArray KdbxReader::xmlData() const
{
return m_xmlData;
}
QByteArray KdbxReader::streamKey() const
{
return m_protectedStreamKey;
}
KeePass2::ProtectedStreamAlgo KdbxReader::protectedStreamAlgo() const
{
return m_irsAlgo;
}
/**
* @param data stream cipher UUID as bytes
*/
void KdbxReader::setCipher(const QByteArray& data)
{
if (data.size() != Uuid::Length) {
raiseError(tr("Invalid cipher uuid length"));
return;
}
Uuid uuid(data);
if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) {
raiseError(tr("Unsupported cipher"));
return;
}
m_db->setCipher(uuid);
}
/**
* @param data compression flags as bytes
*/
void KdbxReader::setCompressionFlags(const QByteArray& data)
{
if (data.size() != 4) {
raiseError(tr("Invalid compression flags length"));
return;
}
auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
if (id > Database::CompressionAlgorithmMax) {
raiseError(tr("Unsupported compression algorithm"));
return;
}
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
}
/**
* @param data master seed as bytes
*/
void KdbxReader::setMasterSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError(tr("Invalid master seed size"));
return;
}
m_masterSeed = data;
}
/**
* @param data KDF seed as bytes
*/
void KdbxReader::setTransformSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError(tr("Invalid transform seed size"));
return;
}
auto kdf = m_db->kdf();
if (!kdf.isNull()) {
kdf->setSeed(data);
}
}
/**
* @param data KDF transform rounds as bytes
*/
void KdbxReader::setTransformRounds(const QByteArray& data)
{
if (data.size() != 8) {
raiseError(tr("Invalid transform rounds size"));
return;
}
auto rounds = Endian::bytesToSizedInt<quint64>(data, KeePass2::BYTEORDER);
auto kdf = m_db->kdf();
if (!kdf.isNull()) {
kdf->setRounds(static_cast<int>(rounds));
}
}
/**
* @param data cipher stream IV as bytes
*/
void KdbxReader::setEncryptionIV(const QByteArray& data)
{
m_encryptionIV = data;
}
/**
* @param data key for random (inner) stream as bytes
*/
void KdbxReader::setProtectedStreamKey(const QByteArray& data)
{
m_protectedStreamKey = data;
}
/**
* @param data start bytes for cipher stream
*/
void KdbxReader::setStreamStartBytes(const QByteArray& data)
{
if (data.size() != 32) {
raiseError(tr("Invalid start bytes size"));
return;
}
m_streamStartBytes = data;
}
/**
* @param data id of inner cipher stream algorithm
*/
void KdbxReader::setInnerRandomStreamID(const QByteArray& data)
{
if (data.size() != 4) {
raiseError(tr("Invalid random stream id size"));
return;
}
auto id = Endian::bytesToSizedInt<quint32>(data, KeePass2::BYTEORDER);
KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
if (irsAlgo == KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo ||
irsAlgo == KeePass2::ProtectedStreamAlgo::ArcFourVariant) {
raiseError(tr("Invalid inner random stream cipher"));
return;
}
m_irsAlgo = irsAlgo;
}
/**
* Raise an error. Use in case of an unexpected read error.
*
* @param errorMessage error message
*/
void KdbxReader::raiseError(const QString& errorMessage)
{
m_error = true;
m_errorStr = errorMessage;
}

107
src/format/KdbxReader.h Normal file
View File

@ -0,0 +1,107 @@
/*
* 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_KDBXREADER_H
#define KEEPASSXC_KDBXREADER_H
#include "KeePass2.h"
#include "keys/CompositeKey.h"
#include "streams/StoreDataStream.h"
#include <QCoreApplication>
#include <QPointer>
class Database;
class QIODevice;
/**
* Abstract KDBX reader base class.
*/
class KdbxReader
{
Q_DECLARE_TR_FUNCTIONS(KdbxReader)
public:
KdbxReader() = default;
virtual ~KdbxReader() = default;
static bool readMagicNumbers(QIODevice* device, quint32& sig1, quint32& sig2, quint32& version);
Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false);
bool hasError() const;
QString errorString() const;
bool saveXml() const;
void setSaveXml(bool save);
QByteArray xmlData() const;
QByteArray streamKey() const;
KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const;
protected:
/**
* Concrete reader implementation for reading database from device.
*
* @param device input device at the payload starting position
* @param KDBX header data as bytes
* @param key database encryption composite key
* @param keepDatabase keep database in case of read failure
* @return pointer to the read database, nullptr on failure
*/
virtual Database* readDatabaseImpl(QIODevice* device, const QByteArray& headerData,
const CompositeKey& key, bool keepDatabase) = 0;
/**
* Read next header field from stream.
*
* @param headerStream input header stream
* @return true if there are more header fields
*/
virtual bool readHeaderField(StoreDataStream& headerStream) = 0;
virtual void setCipher(const QByteArray& data);
virtual void setCompressionFlags(const QByteArray& data);
virtual void setMasterSeed(const QByteArray& data);
virtual void setTransformSeed(const QByteArray& data);
virtual void setTransformRounds(const QByteArray& data);
virtual void setEncryptionIV(const QByteArray& data);
virtual void setProtectedStreamKey(const QByteArray& data);
virtual void setStreamStartBytes(const QByteArray& data);
virtual void setInnerRandomStreamID(const QByteArray& data);
void raiseError(const QString& errorMessage);
QScopedPointer<Database> m_db;
QPair<quint32, quint32> m_kdbxSignature;
quint32 m_kdbxVersion = 0;
QByteArray m_masterSeed;
QByteArray m_encryptionIV;
QByteArray m_streamStartBytes;
QByteArray m_protectedStreamKey;
KeePass2::ProtectedStreamAlgo m_irsAlgo = KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo;
QByteArray m_xmlData;
private:
bool m_saveXml = false;
bool m_error = false;
QString m_errorStr = "";
};
#endif //KEEPASSXC_KDBXREADER_H

74
src/format/KdbxWriter.cpp Normal file
View File

@ -0,0 +1,74 @@
/*
* 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 "KdbxWriter.h"
bool KdbxWriter::hasError() const
{
return m_error;
}
QString KdbxWriter::errorString() const
{
return m_errorStr;
}
/**
* Write KDBX magic header numbers to a device.
*
* @param device output device
* @param sig1 KDBX signature 1
* @param sig2 KDBX signature 2
* @param version KDBX version
* @return true if magic numbers were written successfully
*/
bool KdbxWriter::writeMagicNumbers(QIODevice* device, quint32 sig1, quint32 sig2, quint32 version)
{
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes<qint32>(sig1, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes<qint32>(sig2, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes<qint32>(version, KeePass2::BYTEORDER)));
return true;
}
/**
* Helper method for writing bytes to the device and raising an error
* in case of write failure.
*
* @param device output device
* @param data byte contents
* @return true on success
*/
bool KdbxWriter::writeData(QIODevice* device, const QByteArray& data)
{
if (device->write(data) != data.size()) {
raiseError(device->errorString());
return false;
}
return true;
}
/**
* Raise an error. Use in case of an unexpected write error.
*
* @param errorMessage error message
*/
void KdbxWriter::raiseError(const QString& errorMessage)
{
m_error = true;
m_errorStr = errorMessage;
}

90
src/format/KdbxWriter.h Normal file
View File

@ -0,0 +1,90 @@
/*
* 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_KDBXWRITER_H
#define KEEPASSXC_KDBXWRITER_H
#include "KeePass2.h"
#include "core/Endian.h"
#include <QCoreApplication>
#define CHECK_RETURN_FALSE(x) if (!(x)) return false;
class QIODevice;
class Database;
/**
* Abstract KDBX writer base class.
*/
class KdbxWriter
{
Q_DECLARE_TR_FUNCTIONS(KdbxWriter)
public:
KdbxWriter() = default;
virtual ~KdbxWriter() = default;
bool writeMagicNumbers(QIODevice* device, quint32 sig1, quint32 sig2, quint32 version);
/**
* Write a database to a device in KDBX format.
*
* @param device output device
* @param db source database
* @return true on success
*/
virtual bool writeDatabase(QIODevice* device, Database* db) = 0;
bool hasError() const;
QString errorString() const;
protected:
/**
* Helper method for writing a KDBX header field to a device.
*
* @tparam SizedQInt field width
* @param device output device
* @param fieldId field identifier
* @param data field contents
* @return true on success
*/
template <typename SizedQInt>
bool writeHeaderField(QIODevice* device, KeePass2::HeaderFieldID fieldId, const QByteArray& data)
{
Q_ASSERT(static_cast<unsigned long>(data.size()) < (1ull << (sizeof(SizedQInt) * 8)));
QByteArray fieldIdArr;
fieldIdArr[0] = static_cast<char>(fieldId);
CHECK_RETURN_FALSE(writeData(device, fieldIdArr));
CHECK_RETURN_FALSE(writeData(device, Endian::sizedIntToBytes<SizedQInt>(static_cast<SizedQInt>(data.size()),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(device, data));
return true;
}
bool writeData(QIODevice* device, const QByteArray& data);
void raiseError(const QString& errorMessage);
bool m_error = false;
QString m_errorStr = "";
};
#endif //KEEPASSXC_KDBXWRITER_H

View File

@ -1,56 +1,86 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
* 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 free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* 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 "Kdbx4XmlReader.h"
#include <QBuffer>
#include <QFile>
#include "core/Endian.h"
#include "core/DatabaseIcons.h"
#include "core/Metadata.h"
#include "KdbxXmlReader.h"
#include "KeePass2RandomStream.h"
#include "core/Global.h"
#include "core/Tools.h"
#include "format/KeePass2RandomStream.h"
#include "core/Entry.h"
#include "core/Group.h"
#include "core/DatabaseIcons.h"
#include "core/Endian.h"
#include "streams/QtIOCompressor"
typedef QPair<QString, QString> StringPair;
#include <QFile>
#include <QBuffer>
Kdbx4XmlReader::Kdbx4XmlReader()
: m_randomStream(nullptr)
, m_db(nullptr)
, m_meta(nullptr)
, m_tmpParent(nullptr)
, m_error(false)
, m_strictMode(false)
/**
* @param version KDBX version
*/
KdbxXmlReader::KdbxXmlReader(quint32 version)
: m_kdbxVersion(version)
{
}
Kdbx4XmlReader::Kdbx4XmlReader(QHash<QString, QByteArray>& binaryPool)
: Kdbx4XmlReader()
/**
* @param version KDBX version
* @param binaryPool binary pool
*/
KdbxXmlReader::KdbxXmlReader(quint32 version, QHash<QString, QByteArray>& binaryPool)
: m_kdbxVersion(version)
, m_binaryPool(binaryPool)
{
m_binaryPool = binaryPool;
}
void Kdbx4XmlReader::setStrictMode(bool strictMode)
/**
* Read XML contents from a file into a new database.
*
* @param device input file
* @return pointer to the new database
*/
Database* KdbxXmlReader::readDatabase(const QString& filename)
{
m_strictMode = strictMode;
QFile file(filename);
file.open(QIODevice::ReadOnly);
return readDatabase(&file);
}
void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream)
/**
* Read XML stream from a device into a new database.
*
* @param device input device
* @return pointer to the new database
*/
Database* KdbxXmlReader::readDatabase(QIODevice* device)
{
auto db = new Database();
readDatabase(device, db);
return db;
}
/**
* Read XML contents from a device into a given database using a \link KeePass2RandomStream.
*
* @param device input device
* @param db database to read into
* @param randomStream random stream to use for decryption
*/
#include "QDebug"
void KdbxXmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream)
{
m_error = false;
m_errorStr.clear();
@ -70,7 +100,7 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando
bool rootGroupParsed = false;
if (m_xml.hasError()) {
raiseError(QString("XML parsing failure: %1").arg(m_xml.error()));
raiseError(tr("XML parsing failure: %1").arg(m_xml.error()));
return;
}
@ -79,17 +109,17 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando
}
if (!rootGroupParsed) {
raiseError("No root group");
raiseError(tr("No root group"));
return;
}
if (!m_tmpParent->children().isEmpty()) {
qWarning("Kdbx4XmlReader::readDatabase: found %d invalid group reference(s)",
qWarning("KdbxXmlReader::readDatabase: found %d invalid group reference(s)",
m_tmpParent->children().size());
}
if (!m_tmpParent->entries().isEmpty()) {
qWarning("Kdbx4XmlReader::readDatabase: found %d invalid entry reference(s)",
qWarning("KdbxXmlReader::readDatabase: found %d invalid entry reference(s)",
m_tmpParent->children().size());
}
@ -103,7 +133,7 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando
}
for (const QString& key : unusedKeys) {
qWarning("Kdbx4XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key));
qWarning("KdbxXmlReader::readDatabase: found unused key \"%s\"", qPrintable(key));
}
QHash<QString, QPair<Entry*, QString> >::const_iterator i;
@ -130,53 +160,46 @@ void Kdbx4XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Rando
}
}
Database* Kdbx4XmlReader::readDatabase(QIODevice* device)
bool KdbxXmlReader::strictMode() const
{
auto db = new Database();
readDatabase(device, db);
return db;
return m_strictMode;
}
Database* Kdbx4XmlReader::readDatabase(const QString& filename)
void KdbxXmlReader::setStrictMode(bool strictMode)
{
QFile file(filename);
file.open(QIODevice::ReadOnly);
return readDatabase(&file);
m_strictMode = strictMode;
}
bool Kdbx4XmlReader::hasError()
bool KdbxXmlReader::hasError() const
{
return m_error || m_xml.hasError();
}
QString Kdbx4XmlReader::errorString()
QString KdbxXmlReader::errorString() const
{
if (m_error) {
return m_errorStr;
}
if (m_xml.hasError()) {
}if (m_xml.hasError()) {
return QString("XML error:\n%1\nLine %2, column %3")
.arg(m_xml.errorString())
.arg(m_xml.lineNumber())
.arg(m_xml.columnNumber());
.arg(m_xml.errorString())
.arg(m_xml.lineNumber())
.arg(m_xml.columnNumber());
}
return QString();
}
void Kdbx4XmlReader::raiseError(const QString& errorMessage)
void KdbxXmlReader::raiseError(const QString& errorMessage)
{
m_error = true;
m_errorStr = errorMessage;
}
QByteArray Kdbx4XmlReader::headerHash()
QByteArray KdbxXmlReader::headerHash() const
{
return m_headerHash;
}
bool Kdbx4XmlReader::parseKeePassFile()
bool KdbxXmlReader::parseKeePassFile()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile");
@ -206,7 +229,7 @@ bool Kdbx4XmlReader::parseKeePassFile()
return rootParsedSuccessfully;
}
void Kdbx4XmlReader::parseMeta()
void KdbxXmlReader::parseMeta()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Meta");
@ -281,7 +304,7 @@ void Kdbx4XmlReader::parseMeta()
}
}
void Kdbx4XmlReader::parseMemoryProtection()
void KdbxXmlReader::parseMemoryProtection()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "MemoryProtection");
@ -302,7 +325,7 @@ void Kdbx4XmlReader::parseMemoryProtection()
}
}
void Kdbx4XmlReader::parseCustomIcons()
void KdbxXmlReader::parseCustomIcons()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomIcons");
@ -315,7 +338,7 @@ void Kdbx4XmlReader::parseCustomIcons()
}
}
void Kdbx4XmlReader::parseIcon()
void KdbxXmlReader::parseIcon()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Icon");
@ -341,10 +364,10 @@ void Kdbx4XmlReader::parseIcon()
return;
}
raiseError("Missing icon uuid or data");
raiseError(tr("Missing icon uuid or data"));
}
void Kdbx4XmlReader::parseBinaries()
void KdbxXmlReader::parseBinaries()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries");
@ -366,7 +389,7 @@ void Kdbx4XmlReader::parseBinaries()
}
if (m_binaryPool.contains(id)) {
qWarning("Kdbx4XmlReader::parseBinaries: overwriting binary item \"%s\"",
qWarning("KdbxXmlReader::parseBinaries: overwriting binary item \"%s\"",
qPrintable(id));
}
@ -374,7 +397,7 @@ void Kdbx4XmlReader::parseBinaries()
}
}
void Kdbx4XmlReader::parseCustomData()
void KdbxXmlReader::parseCustomData()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData");
@ -387,7 +410,7 @@ void Kdbx4XmlReader::parseCustomData()
}
}
void Kdbx4XmlReader::parseCustomDataItem()
void KdbxXmlReader::parseCustomDataItem()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item");
@ -413,10 +436,10 @@ void Kdbx4XmlReader::parseCustomDataItem()
return;
}
raiseError("Missing custom data key or value");
raiseError(tr("Missing custom data key or value"));
}
bool Kdbx4XmlReader::parseRoot()
bool KdbxXmlReader::parseRoot()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Root");
@ -427,7 +450,7 @@ bool Kdbx4XmlReader::parseRoot()
if (m_xml.name() == "Group") {
if (groupElementFound) {
groupParsedSuccessfully = false;
raiseError("Multiple group elements");
raiseError(tr("Multiple group elements"));
continue;
}
@ -450,7 +473,7 @@ bool Kdbx4XmlReader::parseRoot()
return groupParsedSuccessfully;
}
Group* Kdbx4XmlReader::parseGroup()
Group* KdbxXmlReader::parseGroup()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group");
@ -463,7 +486,7 @@ Group* Kdbx4XmlReader::parseGroup()
Uuid uuid = readUuid();
if (uuid.isNull()) {
if (m_strictMode) {
raiseError("Null group uuid");
raiseError(tr("Null group uuid"));
} else {
group->setUuid(Uuid::random());
}
@ -484,11 +507,11 @@ Group* Kdbx4XmlReader::parseGroup()
int iconId = readNumber();
if (iconId < 0) {
if (m_strictMode) {
raiseError("Invalid group icon number");
raiseError(tr("Invalid group icon number"));
}
iconId = 0;
} else if (iconId >= DatabaseIcons::IconCount) {
qWarning("Kdbx4XmlReader::parseGroup: icon id \"%d\" not supported", iconId);
qWarning("KdbxXmlReader::parseGroup: icon id \"%d\" not supported", iconId);
iconId = DatabaseIcons::IconCount - 1;
}
@ -524,7 +547,7 @@ Group* Kdbx4XmlReader::parseGroup()
} else if (str.compare("false", Qt::CaseInsensitive) == 0) {
group->setAutoTypeEnabled(Group::Disable);
} else {
raiseError("Invalid EnableAutoType value");
raiseError(tr("Invalid EnableAutoType value"));
}
continue;
}
@ -538,7 +561,7 @@ Group* Kdbx4XmlReader::parseGroup()
} else if (str.compare("false", Qt::CaseInsensitive) == 0) {
group->setSearchingEnabled(Group::Disable);
} else {
raiseError("Invalid EnableSearching value");
raiseError(tr("Invalid EnableSearching value"));
}
continue;
}
@ -575,7 +598,7 @@ Group* Kdbx4XmlReader::parseGroup()
group->setUpdateTimeinfo(false);
delete tmpGroup;
} else if (!hasError()) {
raiseError("No group uuid found");
raiseError(tr("No group uuid found"));
}
for (Group* child : asConst(children)) {
@ -589,7 +612,7 @@ Group* Kdbx4XmlReader::parseGroup()
return group;
}
void Kdbx4XmlReader::parseDeletedObjects()
void KdbxXmlReader::parseDeletedObjects()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObjects");
@ -602,7 +625,7 @@ void Kdbx4XmlReader::parseDeletedObjects()
}
}
void Kdbx4XmlReader::parseDeletedObject()
void KdbxXmlReader::parseDeletedObject()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject");
@ -613,7 +636,7 @@ void Kdbx4XmlReader::parseDeletedObject()
Uuid uuid = readUuid();
if (uuid.isNull()) {
if (m_strictMode) {
raiseError("Null DeleteObject uuid");
raiseError(tr("Null DeleteObject uuid"));
}
continue;
}
@ -633,11 +656,11 @@ void Kdbx4XmlReader::parseDeletedObject()
}
if (m_strictMode) {
raiseError("Missing DeletedObject uuid or time");
raiseError(tr("Missing DeletedObject uuid or time"));
}
}
Entry* Kdbx4XmlReader::parseEntry(bool history)
Entry* KdbxXmlReader::parseEntry(bool history)
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry");
@ -651,7 +674,7 @@ Entry* Kdbx4XmlReader::parseEntry(bool history)
Uuid uuid = readUuid();
if (uuid.isNull()) {
if (m_strictMode) {
raiseError("Null entry uuid");
raiseError(tr("Null entry uuid"));
} else {
entry->setUuid(Uuid::random());
}
@ -664,7 +687,7 @@ Entry* Kdbx4XmlReader::parseEntry(bool history)
int iconId = readNumber();
if (iconId < 0) {
if (m_strictMode) {
raiseError("Invalid entry icon number");
raiseError(tr("Invalid entry icon number"));
}
iconId = 0;
}
@ -714,7 +737,7 @@ Entry* Kdbx4XmlReader::parseEntry(bool history)
}
if (m_xml.name() == "History") {
if (history) {
raiseError("History element in history entry");
raiseError(tr("History element in history entry"));
} else {
historyItems = parseEntryHistory();
}
@ -741,13 +764,13 @@ Entry* Kdbx4XmlReader::parseEntry(bool history)
delete tmpEntry;
}
} else if (!hasError()) {
raiseError("No entry uuid found");
raiseError(tr("No entry uuid found"));
}
for (Entry* historyItem : asConst(historyItems)) {
if (historyItem->uuid() != entry->uuid()) {
if (m_strictMode) {
raiseError("History element with different uuid");
raiseError(tr("History element with different uuid"));
} else {
historyItem->setUuid(entry->uuid());
}
@ -762,7 +785,7 @@ Entry* Kdbx4XmlReader::parseEntry(bool history)
return entry;
}
void Kdbx4XmlReader::parseEntryString(Entry* entry)
void KdbxXmlReader::parseEntryString(Entry* entry)
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "String");
@ -798,7 +821,7 @@ void Kdbx4XmlReader::parseEntryString(Entry* entry)
value = QString::fromUtf8(plaintext);
}
} else {
raiseError("Unable to decrypt entry string");
raiseError(tr("Unable to decrypt entry string"));
continue;
}
}
@ -814,17 +837,17 @@ void Kdbx4XmlReader::parseEntryString(Entry* entry)
if (keySet && valueSet) {
// the default attributes are always there so additionally check if it's empty
if (entry->attributes()->hasKey(key) && !entry->attributes()->value(key).isEmpty()) {
raiseError("Duplicate custom attribute found");
raiseError(tr("Duplicate custom attribute found"));
return;
}
entry->attributes()->set(key, value, protect);
return;
}
raiseError("Entry string key or value missing");
raiseError(tr("Entry string key or value missing"));
}
QPair<QString, QString> Kdbx4XmlReader::parseEntryBinary(Entry* entry)
QPair<QString, QString> KdbxXmlReader::parseEntryBinary(Entry* entry)
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binary");
@ -851,7 +874,7 @@ QPair<QString, QString> Kdbx4XmlReader::parseEntryBinary(Entry* entry)
// format compatibility
value = readBinary();
bool isProtected = attr.hasAttribute("Protected")
&& (attr.value("Protected") == "True");
&& (attr.value("Protected") == "True");
if (isProtected && !value.isEmpty()) {
if (!m_randomStream->processInPlace(value)) {
@ -868,18 +891,18 @@ QPair<QString, QString> Kdbx4XmlReader::parseEntryBinary(Entry* entry)
if (keySet && valueSet) {
if (entry->attachments()->hasKey(key)) {
raiseError("Duplicate attachment found");
raiseError(tr("Duplicate attachment found"));
} else {
entry->attachments()->set(key, value);
}
} else {
raiseError("Entry binary key or value missing");
raiseError(tr("Entry binary key or value missing"));
}
return poolRef;
}
void Kdbx4XmlReader::parseAutoType(Entry* entry)
void KdbxXmlReader::parseAutoType(Entry* entry)
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "AutoType");
@ -898,7 +921,7 @@ void Kdbx4XmlReader::parseAutoType(Entry* entry)
}
}
void Kdbx4XmlReader::parseAutoTypeAssoc(Entry* entry)
void KdbxXmlReader::parseAutoTypeAssoc(Entry* entry)
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Association");
@ -922,10 +945,10 @@ void Kdbx4XmlReader::parseAutoTypeAssoc(Entry* entry)
entry->autoTypeAssociations()->add(assoc);
return;
}
raiseError("Auto-type association window or sequence missing");
raiseError(tr("Auto-type association window or sequence missing"));
}
QList<Entry*> Kdbx4XmlReader::parseEntryHistory()
QList<Entry*> KdbxXmlReader::parseEntryHistory()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "History");
@ -942,7 +965,7 @@ QList<Entry*> Kdbx4XmlReader::parseEntryHistory()
return historyItems;
}
TimeInfo Kdbx4XmlReader::parseTimes()
TimeInfo KdbxXmlReader::parseTimes()
{
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times");
@ -970,12 +993,12 @@ TimeInfo Kdbx4XmlReader::parseTimes()
return timeInfo;
}
QString Kdbx4XmlReader::readString()
QString KdbxXmlReader::readString()
{
return m_xml.readElementText();
}
bool Kdbx4XmlReader::readBool()
bool KdbxXmlReader::readBool()
{
QString str = readString();
@ -988,11 +1011,11 @@ bool Kdbx4XmlReader::readBool()
if (str.length() == 0) {
return false;
}
raiseError("Invalid bool value");
raiseError(tr("Invalid bool value"));
return false;
}
QDateTime Kdbx4XmlReader::readDateTime()
QDateTime KdbxXmlReader::readDateTime()
{
static QRegularExpression b64regex("^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$");
QString str = readString();
@ -1009,13 +1032,13 @@ QDateTime Kdbx4XmlReader::readDateTime()
}
if (m_strictMode) {
raiseError("Invalid date time value");
raiseError(tr("Invalid date time value"));
}
return QDateTime::currentDateTimeUtc();
}
QColor Kdbx4XmlReader::readColor()
QColor KdbxXmlReader::readColor()
{
QString colorStr = readString();
@ -1025,7 +1048,7 @@ QColor Kdbx4XmlReader::readColor()
if (colorStr.length() != 7 || colorStr[0] != '#') {
if (m_strictMode) {
raiseError("Invalid color value");
raiseError(tr("Invalid color value"));
}
return {};
}
@ -1037,7 +1060,7 @@ QColor Kdbx4XmlReader::readColor()
int rgbPart = rgbPartStr.toInt(&ok, 16);
if (!ok || rgbPart > 255) {
if (m_strictMode) {
raiseError("Invalid color rgb part");
raiseError(tr("Invalid color rgb part"));
}
return {};
}
@ -1054,17 +1077,17 @@ QColor Kdbx4XmlReader::readColor()
return color;
}
int Kdbx4XmlReader::readNumber()
int KdbxXmlReader::readNumber()
{
bool ok;
int result = readString().toInt(&ok);
if (!ok) {
raiseError("Invalid number value");
raiseError(tr("Invalid number value"));
}
return result;
}
Uuid Kdbx4XmlReader::readUuid()
Uuid KdbxXmlReader::readUuid()
{
QByteArray uuidBin = readBinary();
if (uuidBin.isEmpty()) {
@ -1072,19 +1095,19 @@ Uuid Kdbx4XmlReader::readUuid()
}
if (uuidBin.length() != Uuid::Length) {
if (m_strictMode) {
raiseError("Invalid uuid value");
raiseError(tr("Invalid uuid value"));
}
return {};
}
return Uuid(uuidBin);
}
QByteArray Kdbx4XmlReader::readBinary()
QByteArray KdbxXmlReader::readBinary()
{
return QByteArray::fromBase64(readString().toLatin1());
}
QByteArray Kdbx4XmlReader::readCompressedBinary()
QByteArray KdbxXmlReader::readCompressedBinary()
{
QByteArray rawData = readBinary();
@ -1097,12 +1120,12 @@ QByteArray Kdbx4XmlReader::readCompressedBinary()
QByteArray result;
if (!Tools::readAllFromDevice(&compressor, result)) {
raiseError("Unable to decompress binary");
raiseError(tr("Unable to decompress binary"));
}
return result;
}
Group* Kdbx4XmlReader::getGroup(const Uuid& uuid)
Group* KdbxXmlReader::getGroup(const Uuid& uuid)
{
if (uuid.isNull()) {
return nullptr;
@ -1120,7 +1143,7 @@ Group* Kdbx4XmlReader::getGroup(const Uuid& uuid)
return group;
}
Entry* Kdbx4XmlReader::getEntry(const Uuid& uuid)
Entry* KdbxXmlReader::getEntry(const Uuid& uuid)
{
if (uuid.isNull()) {
return nullptr;
@ -1138,8 +1161,9 @@ Entry* Kdbx4XmlReader::getEntry(const Uuid& uuid)
return entry;
}
void Kdbx4XmlReader::skipCurrentElement()
void KdbxXmlReader::skipCurrentElement()
{
qWarning("Kdbx4XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString()));
qWarning("KdbxXmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString()));
m_xml.skipCurrentElement();
}

120
src/format/KdbxXmlReader.h Normal file
View File

@ -0,0 +1,120 @@
/*
* 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_KDBXXMLREADER_H
#define KEEPASSXC_KDBXXMLREADER_H
#include "core/Metadata.h"
#include "core/TimeInfo.h"
#include "core/Uuid.h"
#include "core/Database.h"
#include <QCoreApplication>
#include <QString>
#include <QPair>
#include <QXmlStreamReader>
class QIODevice;
class Group;
class Entry;
class KeePass2RandomStream;
/**
* KDBX XML payload reader.
*/
class KdbxXmlReader
{
Q_DECLARE_TR_FUNCTIONS(KdbxXmlReader)
public:
explicit KdbxXmlReader(quint32 version);
explicit KdbxXmlReader(quint32 version, QHash<QString, QByteArray>& binaryPool);
virtual ~KdbxXmlReader() = default;
virtual Database* readDatabase(const QString& filename);
virtual Database* readDatabase(QIODevice* device);
virtual void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
bool hasError() const;
QString errorString() const;
QByteArray headerHash() const;
bool strictMode() const;
void setStrictMode(bool strictMode);
protected:
typedef QPair<QString, QString> StringPair;
virtual bool parseKeePassFile();
virtual void parseMeta();
virtual void parseMemoryProtection();
virtual void parseCustomIcons();
virtual void parseIcon();
virtual void parseBinaries();
virtual void parseCustomData();
virtual void parseCustomDataItem();
virtual bool parseRoot();
virtual Group* parseGroup();
virtual void parseDeletedObjects();
virtual void parseDeletedObject();
virtual Entry* parseEntry(bool history);
virtual void parseEntryString(Entry* entry);
virtual QPair<QString, QString> parseEntryBinary(Entry* entry);
virtual void parseAutoType(Entry* entry);
virtual void parseAutoTypeAssoc(Entry* entry);
virtual QList<Entry*> parseEntryHistory();
virtual TimeInfo parseTimes();
virtual QString readString();
virtual bool readBool();
virtual QDateTime readDateTime();
virtual QColor readColor();
virtual int readNumber();
virtual Uuid readUuid();
virtual QByteArray readBinary();
virtual QByteArray readCompressedBinary();
virtual void skipCurrentElement();
virtual Group* getGroup(const Uuid& uuid);
virtual Entry* getEntry(const Uuid& uuid);
virtual void raiseError(const QString& errorMessage);
const quint32 m_kdbxVersion;
bool m_strictMode = false;
QPointer<Database> m_db;
QPointer<Metadata> m_meta;
KeePass2RandomStream* m_randomStream = nullptr;
QXmlStreamReader m_xml;
QScopedPointer<Group> m_tmpParent;
QHash<Uuid, Group*> m_groups;
QHash<Uuid, Entry*> m_entries;
QHash<QString, QByteArray> m_binaryPool;
QHash<QString, QPair<Entry*, QString> > m_binaryMap;
QByteArray m_headerHash;
bool m_error = false;
QString m_errorStr = "";
};
#endif //KEEPASSXC_KDBXXMLREADER_H

View File

@ -15,7 +15,7 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Kdbx4XmlWriter.h"
#include "KdbxXmlWriter.h"
#include <QBuffer>
#include <QFile>
@ -25,51 +25,35 @@
#include "format/KeePass2RandomStream.h"
#include "streams/QtIOCompressor"
Kdbx4XmlWriter::Kdbx4XmlWriter()
: Kdbx4XmlWriter(KeePass2::FILE_VERSION_3)
/**
* @param version KDBX version
*/
KdbxXmlWriter::KdbxXmlWriter(quint32 version)
: m_kdbxVersion(version)
{
}
Kdbx4XmlWriter::Kdbx4XmlWriter(quint32 version)
: Kdbx4XmlWriter(version, QHash<QByteArray, int>())
{
}
Kdbx4XmlWriter::Kdbx4XmlWriter(quint32 version, QHash<QByteArray, int> idMap)
: m_db(nullptr)
, m_meta(nullptr)
, m_randomStream(nullptr)
, m_idMap(idMap)
, m_error(false)
, m_version(version)
{
m_xml.setAutoFormatting(true);
m_xml.setAutoFormattingIndent(-1); // 1 tab
m_xml.setCodec("UTF-8");
}
void Kdbx4XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, const QByteArray& headerHash)
void KdbxXmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, const QByteArray& headerHash)
{
m_db = db;
m_meta = db->metadata();
m_randomStream = randomStream;
m_headerHash = headerHash;
if (m_version < KeePass2::FILE_VERSION_4 && m_idMap.isEmpty()) {
generateIdMap();
}
m_xml.setAutoFormatting(true);
m_xml.setAutoFormattingIndent(-1); // 1 tab
m_xml.setCodec("UTF-8");
generateIdMap();
m_xml.setDevice(device);
m_xml.writeStartDocument("1.0", true);
m_xml.writeStartElement("KeePassFile");
writeMetadata();
writeRoot();
m_xml.writeEndElement();
m_xml.writeEndDocument();
if (m_xml.hasError()) {
@ -77,24 +61,24 @@ void Kdbx4XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2Rand
}
}
void Kdbx4XmlWriter::writeDatabase(const QString& filename, Database* db)
void KdbxXmlWriter::writeDatabase(const QString& filename, Database* db)
{
QFile file(filename);
file.open(QIODevice::WriteOnly|QIODevice::Truncate);
writeDatabase(&file, db);
}
bool Kdbx4XmlWriter::hasError()
bool KdbxXmlWriter::hasError()
{
return m_error;
}
QString Kdbx4XmlWriter::errorString()
QString KdbxXmlWriter::errorString()
{
return m_errorStr;
}
void Kdbx4XmlWriter::generateIdMap()
void KdbxXmlWriter::generateIdMap()
{
const QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
int nextId = 0;
@ -110,11 +94,11 @@ void Kdbx4XmlWriter::generateIdMap()
}
}
void Kdbx4XmlWriter::writeMetadata()
void KdbxXmlWriter::writeMetadata()
{
m_xml.writeStartElement("Meta");
writeString("Generator", m_meta->generator());
if (m_version < KeePass2::FILE_VERSION_4 && !m_headerHash.isEmpty()) {
if (m_kdbxVersion < KeePass2::FILE_VERSION_4 && !m_headerHash.isEmpty()) {
writeBinary("HeaderHash", m_headerHash);
}
writeString("DatabaseName", m_meta->name());
@ -139,10 +123,10 @@ void Kdbx4XmlWriter::writeMetadata()
writeUuid("LastTopVisibleGroup", m_meta->lastTopVisibleGroup());
writeNumber("HistoryMaxItems", m_meta->historyMaxItems());
writeNumber("HistoryMaxSize", m_meta->historyMaxSize());
if (m_version >= KeePass2::FILE_VERSION_4) {
if (m_kdbxVersion >= KeePass2::FILE_VERSION_4) {
writeDateTime("SettingsChanged", m_meta->settingsChanged());
}
if (m_version < KeePass2::FILE_VERSION_4) {
if (m_kdbxVersion < KeePass2::FILE_VERSION_4) {
writeBinaries();
}
writeCustomData();
@ -150,7 +134,7 @@ void Kdbx4XmlWriter::writeMetadata()
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeMemoryProtection()
void KdbxXmlWriter::writeMemoryProtection()
{
m_xml.writeStartElement("MemoryProtection");
@ -163,7 +147,7 @@ void Kdbx4XmlWriter::writeMemoryProtection()
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeCustomIcons()
void KdbxXmlWriter::writeCustomIcons()
{
m_xml.writeStartElement("CustomIcons");
@ -175,7 +159,7 @@ void Kdbx4XmlWriter::writeCustomIcons()
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
void KdbxXmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
{
m_xml.writeStartElement("Icon");
@ -192,7 +176,7 @@ void Kdbx4XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeBinaries()
void KdbxXmlWriter::writeBinaries()
{
m_xml.writeStartElement("Binaries");
@ -234,7 +218,7 @@ void Kdbx4XmlWriter::writeBinaries()
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeCustomData()
void KdbxXmlWriter::writeCustomData()
{
m_xml.writeStartElement("CustomData");
@ -247,7 +231,7 @@ void Kdbx4XmlWriter::writeCustomData()
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeCustomDataItem(const QString& key, const QString& value)
void KdbxXmlWriter::writeCustomDataItem(const QString& key, const QString& value)
{
m_xml.writeStartElement("Item");
@ -257,7 +241,7 @@ void Kdbx4XmlWriter::writeCustomDataItem(const QString& key, const QString& valu
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeRoot()
void KdbxXmlWriter::writeRoot()
{
Q_ASSERT(m_db->rootGroup());
@ -269,7 +253,7 @@ void Kdbx4XmlWriter::writeRoot()
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeGroup(const Group* group)
void KdbxXmlWriter::writeGroup(const Group* group)
{
Q_ASSERT(!group->uuid().isNull());
@ -293,12 +277,12 @@ void Kdbx4XmlWriter::writeGroup(const Group* group)
writeUuid("LastTopVisibleEntry", group->lastTopVisibleEntry());
const QList<Entry*> entryList = group->entries();
const QList<Entry*>& entryList = group->entries();
for (const Entry* entry : entryList) {
writeEntry(entry);
}
const QList<Group*> children = group->children();
const QList<Group*>& children = group->children();
for (const Group* child : children) {
writeGroup(child);
}
@ -306,7 +290,7 @@ void Kdbx4XmlWriter::writeGroup(const Group* group)
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeTimes(const TimeInfo& ti)
void KdbxXmlWriter::writeTimes(const TimeInfo& ti)
{
m_xml.writeStartElement("Times");
@ -321,7 +305,7 @@ void Kdbx4XmlWriter::writeTimes(const TimeInfo& ti)
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeDeletedObjects()
void KdbxXmlWriter::writeDeletedObjects()
{
m_xml.writeStartElement("DeletedObjects");
@ -333,7 +317,7 @@ void Kdbx4XmlWriter::writeDeletedObjects()
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeDeletedObject(const DeletedObject& delObj)
void KdbxXmlWriter::writeDeletedObject(const DeletedObject& delObj)
{
m_xml.writeStartElement("DeletedObject");
@ -343,7 +327,7 @@ void Kdbx4XmlWriter::writeDeletedObject(const DeletedObject& delObj)
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeEntry(const Entry* entry)
void KdbxXmlWriter::writeEntry(const Entry* entry)
{
Q_ASSERT(!entry->uuid().isNull());
@ -425,7 +409,7 @@ void Kdbx4XmlWriter::writeEntry(const Entry* entry)
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeAutoType(const Entry* entry)
void KdbxXmlWriter::writeAutoType(const Entry* entry)
{
m_xml.writeStartElement("AutoType");
@ -441,7 +425,7 @@ void Kdbx4XmlWriter::writeAutoType(const Entry* entry)
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc)
void KdbxXmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc)
{
m_xml.writeStartElement("Association");
@ -451,7 +435,7 @@ void Kdbx4XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association&
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeEntryHistory(const Entry* entry)
void KdbxXmlWriter::writeEntryHistory(const Entry* entry)
{
m_xml.writeStartElement("History");
@ -463,7 +447,7 @@ void Kdbx4XmlWriter::writeEntryHistory(const Entry* entry)
m_xml.writeEndElement();
}
void Kdbx4XmlWriter::writeString(const QString& qualifiedName, const QString& string)
void KdbxXmlWriter::writeString(const QString& qualifiedName, const QString& string)
{
if (string.isEmpty()) {
m_xml.writeEmptyElement(qualifiedName);
@ -473,12 +457,12 @@ void Kdbx4XmlWriter::writeString(const QString& qualifiedName, const QString& st
}
}
void Kdbx4XmlWriter::writeNumber(const QString& qualifiedName, int number)
void KdbxXmlWriter::writeNumber(const QString& qualifiedName, int number)
{
writeString(qualifiedName, QString::number(number));
}
void Kdbx4XmlWriter::writeBool(const QString& qualifiedName, bool b)
void KdbxXmlWriter::writeBool(const QString& qualifiedName, bool b)
{
if (b) {
writeString(qualifiedName, "True");
@ -488,13 +472,13 @@ void Kdbx4XmlWriter::writeBool(const QString& qualifiedName, bool b)
}
}
void Kdbx4XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime)
void KdbxXmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime)
{
Q_ASSERT(dateTime.isValid());
Q_ASSERT(dateTime.timeSpec() == Qt::UTC);
QString dateTimeStr;
if (m_version < KeePass2::FILE_VERSION_4) {
if (m_kdbxVersion < KeePass2::FILE_VERSION_4) {
dateTimeStr = dateTime.toString(Qt::ISODate);
// Qt < 4.8 doesn't append a 'Z' at the end
@ -509,12 +493,12 @@ void Kdbx4XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime
writeString(qualifiedName, dateTimeStr);
}
void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid)
void KdbxXmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid)
{
writeString(qualifiedName, uuid.toBase64());
}
void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Group* group)
void KdbxXmlWriter::writeUuid(const QString& qualifiedName, const Group* group)
{
if (group) {
writeUuid(qualifiedName, group->uuid());
@ -524,7 +508,7 @@ void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Group* group)
}
}
void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry)
void KdbxXmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry)
{
if (entry) {
writeUuid(qualifiedName, entry->uuid());
@ -534,12 +518,12 @@ void Kdbx4XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry)
}
}
void Kdbx4XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba)
void KdbxXmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba)
{
writeString(qualifiedName, QString::fromLatin1(ba.toBase64()));
}
void Kdbx4XmlWriter::writeColor(const QString& qualifiedName, const QColor& color)
void KdbxXmlWriter::writeColor(const QString& qualifiedName, const QColor& color)
{
QString colorStr;
@ -552,7 +536,7 @@ void Kdbx4XmlWriter::writeColor(const QString& qualifiedName, const QColor& colo
writeString(qualifiedName, colorStr);
}
void Kdbx4XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState)
void KdbxXmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState)
{
QString value;
@ -569,7 +553,7 @@ void Kdbx4XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState
writeString(qualifiedName, value);
}
QString Kdbx4XmlWriter::colorPartToString(int value)
QString KdbxXmlWriter::colorPartToString(int value)
{
QString str = QString::number(value, 16).toUpper();
if (str.length() == 1) {
@ -579,7 +563,7 @@ QString Kdbx4XmlWriter::colorPartToString(int value)
return str;
}
QString Kdbx4XmlWriter::stripInvalidXml10Chars(QString str)
QString KdbxXmlWriter::stripInvalidXml10Chars(QString str)
{
for (int i = str.size() - 1; i >= 0; i--) {
const QChar ch = str.at(i);
@ -604,7 +588,7 @@ QString Kdbx4XmlWriter::stripInvalidXml10Chars(QString str)
return str;
}
void Kdbx4XmlWriter::raiseError(const QString& errorMessage)
void KdbxXmlWriter::raiseError(const QString& errorMessage)
{
m_error = true;
m_errorStr = errorMessage;

View File

@ -15,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_KDBX4XMLWRITER_H
#define KEEPASSX_KDBX4XMLWRITER_H
#ifndef KEEPASSX_KDBXXMLWRITER_H
#define KEEPASSX_KDBXXMLWRITER_H
#include <QColor>
#include <QDateTime>
@ -32,12 +32,11 @@
class KeePass2RandomStream;
class Metadata;
class Kdbx4XmlWriter
class KdbxXmlWriter
{
public:
Kdbx4XmlWriter();
Kdbx4XmlWriter(quint32 version);
Kdbx4XmlWriter(quint32 version, QHash<QByteArray, int> idMap);
explicit KdbxXmlWriter(quint32 version);
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr,
const QByteArray& headerHash = QByteArray());
void writeDatabase(const QString& filename, Database* db);
@ -79,15 +78,18 @@ private:
void raiseError(const QString& errorMessage);
const quint32 m_kdbxVersion;
QXmlStreamWriter m_xml;
Database* m_db;
Metadata* m_meta;
KeePass2RandomStream* m_randomStream;
QPointer<Database> m_db;
QPointer<Metadata> m_meta;
KeePass2RandomStream* m_randomStream = nullptr;
QHash<QByteArray, int> m_idMap;
bool m_error;
QString m_errorStr;
quint32 m_version;
QByteArray m_headerHash;
bool m_error = false;
QString m_errorStr = "";
};
#endif // KEEPASSX_KDBX4XMLWRITER_H
#endif // KEEPASSX_KDBXXMLWRITER_H

View File

@ -15,29 +15,21 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QtGlobal>
#include <QString>
#include <QFile>
#include "core/Endian.h"
#include "keys/CompositeKey.h"
#include "format/KeePass2Reader.h"
#include "format/KeePass1.h"
#include "format/KeePass2.h"
#include "format/Kdbx3Reader.h"
#include "format/Kdbx4Reader.h"
BaseKeePass2Reader::BaseKeePass2Reader()
: m_error(false)
, m_saveXml(false)
, m_irsAlgo(KeePass2::ProtectedStreamAlgo::InvalidProtectedStreamAlgo)
{
m_errorStr.clear();
m_xmlData.clear();
m_protectedStreamKey.clear();
}
#include <QFile>
Database* BaseKeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key)
/**
* Read database from file and detect correct file format.
*
* @param filename input file
* @param key database encryption composite key
* @return pointer to the read database, nullptr on failure
*/
Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key)
{
QFile file(filename);
if (!file.open(QFile::ReadOnly)) {
@ -55,79 +47,45 @@ Database* BaseKeePass2Reader::readDatabase(const QString& filename, const Compos
return db.take();
}
bool BaseKeePass2Reader::hasError()
{
return m_error;
}
QString BaseKeePass2Reader::errorString()
{
return m_errorStr;
}
void BaseKeePass2Reader::setSaveXml(bool save)
{
m_saveXml = save;
}
QByteArray BaseKeePass2Reader::xmlData()
{
return m_xmlData;
}
QByteArray BaseKeePass2Reader::streamKey()
{
return m_protectedStreamKey;
}
KeePass2::ProtectedStreamAlgo BaseKeePass2Reader::protectedStreamAlgo() const {
return m_irsAlgo;
}
void BaseKeePass2Reader::raiseError(const QString& errorMessage)
{
m_error = true;
m_errorStr = errorMessage;
}
/**
* Read database from device and detect correct file format.
*
* @param device input device
* @param key database encryption composite key
* @param keepDatabase keep database in case of read failure
* @return pointer to the read database, nullptr on failure
*/
Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
{
m_error = false;
m_errorStr.clear();
bool ok;
quint32 signature1, signature2;
bool ok = KdbxReader::readMagicNumbers(device, signature1, signature2, m_version);
quint32 signature1 = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
if (!ok || signature1 != KeePass2::SIGNATURE_1) {
// mask out minor version
m_version &= KeePass2::FILE_VERSION_CRITICAL_MASK;
if (!ok || signature1 != KeePass2::SIGNATURE_1 || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 signature2 = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok);
if (ok && signature2 == KeePass1::SIGNATURE_2) {
if (signature2 == KeePass1::SIGNATURE_2) {
raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
"This is a one-way migration. You won't be able to open the imported "
"database with the old KeePassX 0.4 version."));
return nullptr;
}
else if (!ok || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
m_version = Endian::readSizedInt<quint32>(device, KeePass2::BYTEORDER, &ok)
& KeePass2::FILE_VERSION_CRITICAL_MASK;
quint32 maxVersion = KeePass2::FILE_VERSION_4 & KeePass2::FILE_VERSION_CRITICAL_MASK;
if (!ok || (m_version < KeePass2::FILE_VERSION_MIN) || (m_version > maxVersion)) {
if (m_version < KeePass2::FILE_VERSION_MIN || m_version > maxVersion) {
raiseError(tr("Unsupported KeePass 2 database version."));
return nullptr;
}
device->seek(0);
// Determine KDBX3 vs KDBX4
// determine file format (KDBX 2/3 or 4)
if (m_version < KeePass2::FILE_VERSION_4) {
m_reader.reset(new Kdbx3Reader());
} else {
@ -138,37 +96,49 @@ Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& ke
return m_reader->readDatabase(device, key, keepDatabase);
}
bool KeePass2Reader::hasError()
bool KeePass2Reader::hasError() const
{
return m_error || (!m_reader.isNull() && m_reader->hasError());
}
QString KeePass2Reader::errorString()
QString KeePass2Reader::errorString() const
{
return !m_reader.isNull() ? m_reader->errorString() : m_errorStr;
}
QByteArray KeePass2Reader::xmlData()
bool KeePass2Reader::saveXml() const
{
return !m_reader.isNull() ? m_reader->xmlData() : m_xmlData;
return m_saveXml;
}
QByteArray KeePass2Reader::streamKey()
void KeePass2Reader::setSaveXml(bool save)
{
return !m_reader.isNull() ? m_reader->streamKey() : m_protectedStreamKey;
}
KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const
{
return !m_reader.isNull() ? m_reader->protectedStreamAlgo() : m_irsAlgo;
m_saveXml = save;
}
/**
* @return detected KDBX version
*/
quint32 KeePass2Reader::version() const
{
return m_version;
}
QSharedPointer<BaseKeePass2Reader> KeePass2Reader::reader()
/**
* @return KDBX reader used for reading the input file
*/
QSharedPointer<KdbxReader> KeePass2Reader::reader() const
{
return m_reader;
}
}
/**
* Raise an error. Use in case of an unexpected read error.
*
* @param errorMessage error message
*/
void KeePass2Reader::raiseError(const QString& errorMessage)
{
m_error = true;
m_errorStr = errorMessage;
}

View File

@ -18,6 +18,11 @@
#ifndef KEEPASSX_KEEPASS2READER_H
#define KEEPASSX_KEEPASS2READER_H
#include "format/KeePass2.h"
#include "core/Database.h"
#include "keys/CompositeKey.h"
#include "KdbxReader.h"
#include <QtGlobal>
#include <QByteArray>
#include <QString>
@ -25,58 +30,31 @@
#include <QScopedPointer>
#include <QIODevice>
#include "format/KeePass2.h"
#include "core/Database.h"
#include "keys/CompositeKey.h"
class BaseKeePass2Reader
class KeePass2Reader
{
Q_DECLARE_TR_FUNCTIONS(BaseKeePass2Reader)
Q_DECLARE_TR_FUNCTIONS(KdbxReader)
public:
BaseKeePass2Reader();
Database* readDatabase(const QString& filename, const CompositeKey& key);
Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false);
virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) = 0;
virtual Database* readDatabase(const QString& filename, const CompositeKey& key);
bool hasError() const;
QString errorString() const;
virtual bool hasError();
virtual QString errorString();
virtual void setSaveXml(bool save);
virtual QByteArray xmlData();
virtual QByteArray streamKey();
virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const;
virtual ~BaseKeePass2Reader() = default;
protected:
void raiseError(const QString& errorMessage);
bool m_error;
QString m_errorStr;
bool m_saveXml;
QByteArray m_xmlData;
QByteArray m_protectedStreamKey;
KeePass2::ProtectedStreamAlgo m_irsAlgo;
};
class KeePass2Reader : public BaseKeePass2Reader
{
public:
Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override;
using BaseKeePass2Reader::readDatabase;
bool hasError() override;
QString errorString() override;
QByteArray xmlData() override;
QByteArray streamKey() override;
QSharedPointer<BaseKeePass2Reader> reader();
KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const override;
bool saveXml() const;
void setSaveXml(bool save);
QSharedPointer<KdbxReader> reader() const;
quint32 version() const;
private:
QSharedPointer<BaseKeePass2Reader> m_reader;
quint32 m_version;
void raiseError(const QString& errorMessage);
bool m_saveXml = false;
bool m_error = false;
QString m_errorStr = "";
QSharedPointer<KdbxReader> m_reader;
quint32 m_version = 0;
};
#endif // KEEPASSX_KEEPASS2READER_H

View File

@ -19,15 +19,13 @@
#include "KeePass2Repair.h"
#include <QBuffer>
#include <QScopedPointer>
#include <QRegExp>
#include "core/Group.h"
#include "format/KeePass2.h"
#include "format/KeePass2RandomStream.h"
#include "format/KeePass2Reader.h"
#include "format/Kdbx4Reader.h"
#include "format/Kdbx3XmlReader.h"
#include "format/Kdbx4XmlReader.h"
#include "format/KdbxXmlReader.h"
KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key)
{
@ -41,7 +39,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
return qMakePair(NothingTodo, nullptr);
}
QByteArray xmlData = reader.xmlData();
QByteArray xmlData = reader.reader()->xmlData();
if (!db || xmlData.isEmpty()) {
m_errorStr = reader.errorString();
return qMakePair(UnableToOpen, nullptr);
@ -62,7 +60,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
// try to fix broken databases because of bug #392
for (int i = (xmlData.size() - 1); i >= 0; i--) {
quint8 ch = static_cast<quint8>(xmlData.at(i));
auto ch = static_cast<quint8>(xmlData.at(i));
if (ch < 0x20 && ch != 0x09 && ch != 0x0A && ch != 0x0D) {
xmlData.remove(i, 1);
repairAction = true;
@ -74,20 +72,20 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
return qMakePair(RepairFailed, nullptr);
}
KeePass2RandomStream randomStream(reader.protectedStreamAlgo());
randomStream.init(reader.streamKey());
KeePass2RandomStream randomStream(reader.reader()->protectedStreamAlgo());
randomStream.init(reader.reader()->streamKey());
bool hasError;
QBuffer buffer(&xmlData);
buffer.open(QIODevice::ReadOnly);
if ((reader.version() & KeePass2::FILE_VERSION_CRITICAL_MASK) < KeePass2::FILE_VERSION_4) {
Kdbx3XmlReader xmlReader;
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_3);
xmlReader.readDatabase(&buffer, db.data(), &randomStream);
hasError = xmlReader.hasError();
} else {
auto reader4 = reader.reader().staticCast<Kdbx4Reader>();
QHash<QString, QByteArray> pool = reader4->binaryPool();
Kdbx4XmlReader xmlReader(pool);
KdbxXmlReader xmlReader(KeePass2::FILE_VERSION_4, pool);
xmlReader.readDatabase(&buffer, db.data(), &randomStream);
hasError = xmlReader.hasError();
}

View File

@ -16,7 +16,6 @@
*/
#include <QIODevice>
#include <QString>
#include <QFile>
#include "format/KeePass2Writer.h"
@ -24,66 +23,79 @@
#include "format/Kdbx3Writer.h"
#include "format/Kdbx4Writer.h"
BaseKeePass2Writer::BaseKeePass2Writer() : m_error(false)
{
m_errorStr.clear();
}
BaseKeePass2Writer::~BaseKeePass2Writer() {}
bool BaseKeePass2Writer::hasError()
{
return m_error;
}
QString BaseKeePass2Writer::errorString()
{
return m_errorStr;
}
void BaseKeePass2Writer::raiseError(const QString& errorMessage)
{
m_error = true;
m_errorStr = errorMessage;
}
bool BaseKeePass2Writer::writeDatabase(const QString& filename, Database* db)
/**
* Write a database to a KDBX file.
*
* @param filename output filename
* @param db source database
* @return true on success
*/
bool KeePass2Writer::writeDatabase(const QString& filename, Database* db)
{
QFile file(filename);
if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) {
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
raiseError(file.errorString());
return false;
}
return writeDatabase(&file, db);
}
bool KeePass2Writer::hasError()
{
return m_error || (m_writer && m_writer->hasError());
}
QString KeePass2Writer::errorString()
{
return m_writer ? m_writer->errorString() : m_errorStr;
}
/**
* Write a database to a device in KDBX format.
*
* @param device output device
* @param db source database
* @return true on success
*/
bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) {
bool useKdbx4 = false;
m_error = false;
m_errorStr.clear();
if (db->kdf()->uuid() != KeePass2::KDF_AES) {
useKdbx4 = true;
}
if (db->publicCustomData().size() > 0) {
useKdbx4 = true;
}
// Determine KDBX3 vs KDBX4
if (useKdbx4) {
// determine KDBX3 vs KDBX4
if (db->kdf()->uuid() != KeePass2::KDF_AES || db->publicCustomData().size() > 0) {
m_version = KeePass2::FILE_VERSION_4;
m_writer.reset(new Kdbx4Writer());
} else {
m_version = KeePass2::FILE_VERSION_3;
m_writer.reset(new Kdbx3Writer());
}
return m_writer->writeDatabase(device, db);
}
bool KeePass2Writer::hasError() const
{
return m_error || (m_writer && m_writer->hasError());
}
QString KeePass2Writer::errorString() const
{
return m_writer ? m_writer->errorString() : m_errorStr;
}
/**
* Raise an error. Use in case of an unexpected write error.
*
* @param errorMessage error message
*/
void KeePass2Writer::raiseError(const QString& errorMessage)
{
m_error = true;
m_errorStr = errorMessage;
}
/**
* @return KDBX writer used for writing the output file
*/
QSharedPointer<KdbxWriter> KeePass2Writer::writer() const
{
return QSharedPointer<KdbxWriter>();
}
/**
* @return KDBX version used for writing the output file
*/
quint32 KeePass2Writer::version() const
{
return m_version;
}

View File

@ -18,50 +18,36 @@
#ifndef KEEPASSX_KEEPASS2WRITER_H
#define KEEPASSX_KEEPASS2WRITER_H
#include <QIODevice>
#include <QByteArray>
#include <QString>
#include "KdbxWriter.h"
#include <QCoreApplication>
#include <QScopedPointer>
#include "core/Database.h"
#include "format/KeePass2.h"
class QIODevice;
class Database;
#define CHECK_RETURN_FALSE(x) if (!(x)) return false;
class BaseKeePass2Writer
class KeePass2Writer
{
public:
BaseKeePass2Writer();
virtual bool writeDatabase(QIODevice* device, Database* db) = 0;
virtual bool writeDatabase(const QString& filename, Database* db);
virtual bool hasError();
virtual QString errorString();
virtual ~BaseKeePass2Writer();
protected:
void raiseError(const QString& errorMessage);
bool m_error;
QString m_errorStr;
};
class KeePass2Writer : public BaseKeePass2Writer
{
Q_DECLARE_TR_FUNCTIONS(KeePass2Writer)
Q_DECLARE_TR_FUNCTIONS(KeePass2Writer)
public:
virtual bool writeDatabase(QIODevice* device, Database* db) override;
using BaseKeePass2Writer::writeDatabase;
bool writeDatabase(const QString& filename, Database* db);
bool writeDatabase(QIODevice* device, Database* db);
virtual bool hasError() override;
virtual QString errorString() override;
QSharedPointer<KdbxWriter> writer() const;
quint32 version() const;
bool hasError() const;
QString errorString() const;
private:
QScopedPointer<BaseKeePass2Writer> m_writer;
void raiseError(const QString& errorMessage);
bool m_error = false;
QString m_errorStr = "";
QScopedPointer<KdbxWriter> m_writer;
quint32 m_version = 0;
};
#endif // KEEPASSX_KEEPASS2READER_H

View File

@ -22,7 +22,8 @@
#include "core/Database.h"
#include "core/Group.h"
#include "crypto/Crypto.h"
#include "format/Kdbx3XmlReader.h"
#include "format/KeePass2.h"
#include "format/KdbxXmlReader.h"
#include "config-keepassx-tests.h"
QTEST_GUILESS_MAIN(TestDeletedObjects)
@ -88,7 +89,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize)
void TestDeletedObjects::testDeletedObjectsFromFile()
{
Kdbx3XmlReader reader;
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
reader.setStrictMode(true);
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
Database* db = reader.readDatabase(xmlFile);

View File

@ -20,15 +20,14 @@
#include <QBuffer>
#include <QFile>
#include <QTest>
#include <format/KeePass2.h>
#include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/Crypto.h"
#include "format/Kdbx3XmlReader.h"
#include "format/Kdbx3XmlWriter.h"
#include "format/Kdbx4XmlReader.h"
#include "format/Kdbx4XmlWriter.h"
#include "format/KdbxXmlReader.h"
#include "format/KdbxXmlWriter.h"
#include "config-keepassx-tests.h"
namespace QTest {
@ -83,7 +82,7 @@ void TestKdbx3XmlReader::initTestCase()
{
QVERIFY(Crypto::init());
Kdbx3XmlReader reader;
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
reader.setStrictMode(true);
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
m_db = reader.readDatabase(xmlFile);
@ -95,7 +94,7 @@ void TestKdbx4XmlReader::initTestCase()
{
QVERIFY(Crypto::init());
Kdbx4XmlReader reader;
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
reader.setStrictMode(true);
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
m_db = reader.readDatabase(xmlFile);
@ -105,7 +104,7 @@ void TestKdbx4XmlReader::initTestCase()
void TestKdbx3XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString)
{
Kdbx3XmlReader reader;
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
reader.setStrictMode(strictMode);
db = reader.readDatabase(path);
hasError = reader.hasError();
@ -114,7 +113,7 @@ void TestKdbx3XmlReader::readDatabase(QString path, bool strictMode, Database*&
void TestKdbx3XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString)
{
Kdbx3XmlReader reader;
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
reader.setStrictMode(strictMode);
db = reader.readDatabase(buf);
hasError = reader.hasError();
@ -123,7 +122,7 @@ void TestKdbx3XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*&
void TestKdbx3XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString)
{
Kdbx3XmlWriter writer;
KdbxXmlWriter writer(KeePass2::FILE_VERSION_3);
writer.writeDatabase(buf, db);
hasError = writer.hasError();
errorString = writer.errorString();
@ -131,7 +130,7 @@ void TestKdbx3XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasErro
void TestKdbx4XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString)
{
Kdbx4XmlReader reader;
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
reader.setStrictMode(strictMode);
db = reader.readDatabase(path);
hasError = reader.hasError();
@ -140,7 +139,7 @@ void TestKdbx4XmlReader::readDatabase(QString path, bool strictMode, Database*&
void TestKdbx4XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString)
{
Kdbx4XmlReader reader;
KdbxXmlReader reader(KeePass2::FILE_VERSION_3);
reader.setStrictMode(strictMode);
db = reader.readDatabase(buf);
hasError = reader.hasError();
@ -149,7 +148,7 @@ void TestKdbx4XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*&
void TestKdbx4XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString)
{
Kdbx4XmlWriter writer;
KdbxXmlWriter writer(KeePass2::FILE_VERSION_3);
writer.writeDatabase(buf, db);
hasError = writer.hasError();
errorString = writer.errorString();