mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-03-25 23:48:18 -04:00
Refactor database readers/writers and XML handling
* Refactor Kdbx*Reader * Refactor KdbxWriter * Refactor KdbxXmlReader * Refactor KdbxXmlWriter
This commit is contained in:
parent
72a1c65d00
commit
a6ddc22fb8
@ -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
|
||||
|
@ -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()) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
@ -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
|
@ -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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
|
@ -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
267
src/format/KdbxReader.cpp
Normal 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
107
src/format/KdbxReader.h
Normal 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
74
src/format/KdbxWriter.cpp
Normal 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
90
src/format/KdbxWriter.h
Normal 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
|
@ -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
120
src/format/KdbxXmlReader.h
Normal 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
|
@ -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;
|
@ -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
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
Loading…
x
Reference in New Issue
Block a user