mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-06-29 00:47:32 -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
29 changed files with 1313 additions and 2917 deletions
|
@ -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;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue