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

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

View File

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

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

@ -0,0 +1,394 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Kdbx3Reader.h"
#include <QBuffer>
#include <QFile>
#include <QIODevice>
#include "core/Database.h"
#include "core/Endian.h"
#include "crypto/CryptoHash.h"
#include "crypto/kdf/AesKdf.h"
#include "format/KeePass1.h"
#include "format/KeePass2.h"
#include "format/KeePass2RandomStream.h"
#include "format/Kdbx3XmlReader.h"
#include "streams/HashedBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/StoreDataStream.h"
#include "streams/SymmetricCipherStream.h"
Kdbx3Reader::Kdbx3Reader()
: m_device(nullptr)
, m_headerStream(nullptr)
, m_headerEnd(false)
, m_db(nullptr)
{
}
Database* Kdbx3Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
{
QScopedPointer<Database> db(new Database());
m_db = db.data();
m_device = device;
m_error = false;
m_errorStr.clear();
m_headerEnd = false;
m_xmlData.clear();
m_masterSeed.clear();
m_encryptionIV.clear();
m_streamStartBytes.clear();
m_protectedStreamKey.clear();
StoreDataStream headerStream(m_device);
headerStream.open(QIODevice::ReadOnly);
m_headerStream = &headerStream;
bool ok;
quint32 signature1 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok);
if (!ok || signature1 != KeePass2::SIGNATURE_1) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 signature2 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok);
if (ok && signature2 == KeePass1::SIGNATURE_2) {
raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
"This is a one-way migration. You won't be able to open the imported "
"database with the old KeePassX 0.4 version."));
return nullptr;
}
else if (!ok || signature2 != KeePass2::SIGNATURE_2) {
raiseError(tr("Not a KeePass database."));
return nullptr;
}
quint32 version = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok)
& KeePass2::FILE_VERSION_CRITICAL_MASK;
quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK;
if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) {
raiseError(tr("Unsupported KeePass KDBX 2 or 3 database version."));
return nullptr;
}
while (readHeaderField() && !hasError()) {
}
headerStream.close();
if (hasError()) {
return nullptr;
}
// check if all required headers were present
if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty()
|| m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty()
|| m_db->cipher().isNull()) {
raiseError("missing database headers");
return nullptr;
}
if (!m_db->setKey(key, false)) {
raiseError(tr("Unable to calculate master key"));
return nullptr;
}
if (m_db->challengeMasterSeed(m_masterSeed) == false) {
raiseError(tr("Unable to issue challenge-response."));
return nullptr;
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(m_masterSeed);
hash.addData(m_db->challengeResponseKey());
hash.addData(m_db->transformedMasterKey());
QByteArray finalKey = hash.result();
SymmetricCipher::Algorithm cipher = SymmetricCipher::cipherToAlgorithm(m_db->cipher());
SymmetricCipherStream cipherStream(m_device, cipher,
SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString());
return nullptr;
}
if (!cipherStream.open(QIODevice::ReadOnly)) {
raiseError(cipherStream.errorString());
return nullptr;
}
QByteArray realStart = cipherStream.read(32);
if (realStart != m_streamStartBytes) {
raiseError(tr("Wrong key or database file is corrupt."));
return nullptr;
}
HashedBlockStream hashedStream(&cipherStream);
if (!hashedStream.open(QIODevice::ReadOnly)) {
raiseError(hashedStream.errorString());
return nullptr;
}
QIODevice* xmlDevice;
QScopedPointer<QtIOCompressor> ioCompressor;
if (m_db->compressionAlgo() == Database::CompressionNone) {
xmlDevice = &hashedStream;
}
else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
if (!ioCompressor->open(QIODevice::ReadOnly)) {
raiseError(ioCompressor->errorString());
return nullptr;
}
xmlDevice = ioCompressor.data();
}
KeePass2RandomStream randomStream(KeePass2::Salsa20);
if (!randomStream.init(m_protectedStreamKey)) {
raiseError(randomStream.errorString());
return nullptr;
}
QScopedPointer<QBuffer> buffer;
if (m_saveXml) {
m_xmlData = xmlDevice->readAll();
buffer.reset(new QBuffer(&m_xmlData));
buffer->open(QIODevice::ReadOnly);
xmlDevice = buffer.data();
}
Kdbx3XmlReader xmlReader;
xmlReader.readDatabase(xmlDevice, m_db, &randomStream);
if (xmlReader.hasError()) {
raiseError(xmlReader.errorString());
if (keepDatabase) {
return db.take();
}
else {
return nullptr;
}
}
Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty());
if (!xmlReader.headerHash().isEmpty()) {
QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256);
if (headerHash != xmlReader.headerHash()) {
raiseError("Header doesn't match hash");
return nullptr;
}
}
return db.take();
}
bool Kdbx3Reader::readHeaderField()
{
QByteArray fieldIDArray = m_headerStream->read(1);
if (fieldIDArray.size() != 1) {
raiseError("Invalid header id size");
return false;
}
quint8 fieldID = fieldIDArray.at(0);
bool ok;
quint16 fieldLen = Endian::readUInt16(m_headerStream, KeePass2::BYTEORDER, &ok);
if (!ok) {
raiseError("Invalid header field length");
return false;
}
QByteArray fieldData;
if (fieldLen != 0) {
fieldData = m_headerStream->read(fieldLen);
if (fieldData.size() != fieldLen) {
raiseError("Invalid header data length");
return false;
}
}
switch (fieldID) {
case KeePass2::EndOfHeader:
m_headerEnd = true;
break;
case KeePass2::CipherID:
setCipher(fieldData);
break;
case KeePass2::CompressionFlags:
setCompressionFlags(fieldData);
break;
case KeePass2::MasterSeed:
setMasterSeed(fieldData);
break;
case KeePass2::TransformSeed:
setTransformSeed(fieldData);
break;
case KeePass2::TransformRounds:
setTransformRounds(fieldData);
break;
case KeePass2::EncryptionIV:
setEncryptionIV(fieldData);
break;
case KeePass2::ProtectedStreamKey:
setProtectedStreamKey(fieldData);
break;
case KeePass2::StreamStartBytes:
setStreamStartBytes(fieldData);
break;
case KeePass2::InnerRandomStreamID:
setInnerRandomStreamID(fieldData);
break;
default:
qWarning("Unknown header field read: id=%d", fieldID);
break;
}
return !m_headerEnd;
}
void Kdbx3Reader::setCipher(const QByteArray& data)
{
if (data.size() != Uuid::Length) {
raiseError("Invalid cipher uuid length");
} else {
Uuid uuid(data);
if (SymmetricCipher::cipherToAlgorithm(uuid) == SymmetricCipher::InvalidAlgorithm) {
raiseError("Unsupported cipher");
} else {
m_db->setCipher(uuid);
}
}
}
void Kdbx3Reader::setCompressionFlags(const QByteArray& data)
{
if (data.size() != 4) {
raiseError("Invalid compression flags length");
}
else {
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
if (id > Database::CompressionAlgorithmMax) {
raiseError("Unsupported compression algorithm");
}
else {
m_db->setCompressionAlgo(static_cast<Database::CompressionAlgorithm>(id));
}
}
}
void Kdbx3Reader::setMasterSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid master seed size");
}
else {
m_masterSeed = data;
}
}
void Kdbx3Reader::setTransformSeed(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid transform seed size");
}
else {
AesKdf* aesKdf;
if (m_db->kdf()->type() == Kdf::Type::AES) {
aesKdf = static_cast<AesKdf*>(m_db->kdf());
} else {
aesKdf = new AesKdf();
m_db->setKdf(aesKdf);
}
aesKdf->setSeed(data);
}
}
void Kdbx3Reader::setTransformRounds(const QByteArray& data)
{
if (data.size() != 8) {
raiseError("Invalid transform rounds size");
}
else {
quint64 rounds = Endian::bytesToUInt64(data, KeePass2::BYTEORDER);
AesKdf* aesKdf;
if (m_db->kdf()->type() == Kdf::Type::AES) {
aesKdf = static_cast<AesKdf*>(m_db->kdf());
} else {
aesKdf = new AesKdf();
m_db->setKdf(aesKdf);
}
aesKdf->setRounds(rounds);
}
}
void Kdbx3Reader::setEncryptionIV(const QByteArray& data)
{
m_encryptionIV = data;
}
void Kdbx3Reader::setProtectedStreamKey(const QByteArray& data)
{
m_protectedStreamKey = data;
}
void Kdbx3Reader::setStreamStartBytes(const QByteArray& data)
{
if (data.size() != 32) {
raiseError("Invalid start bytes size");
}
else {
m_streamStartBytes = data;
}
}
void Kdbx3Reader::setInnerRandomStreamID(const QByteArray& data)
{
if (data.size() != 4) {
raiseError("Invalid random stream id size");
} else {
quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER);
KeePass2::ProtectedStreamAlgo irsAlgo = KeePass2::idToProtectedStreamAlgo(id);
if (irsAlgo == KeePass2::InvalidProtectedStreamAlgo || irsAlgo == KeePass2::ArcFourVariant) {
raiseError("Invalid inner random stream cipher");
} else {
m_irsAlgo = irsAlgo;
}
}
}

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

@ -0,0 +1,62 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_KDBX3READER_H
#define KEEPASSX_KDBX3READER_H
#include <QCoreApplication>
#include "format/KeePass2Reader.h"
#include "keys/CompositeKey.h"
class Database;
class QIODevice;
class Kdbx3Reader : public BaseKeePass2Reader
{
Q_DECLARE_TR_FUNCTIONS(Kdbx3Reader)
public:
Kdbx3Reader();
using BaseKeePass2Reader::readDatabase;
virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override;
private:
bool readHeaderField();
void setCipher(const QByteArray& data);
void setCompressionFlags(const QByteArray& data);
void setMasterSeed(const QByteArray& data);
void setTransformSeed(const QByteArray& data);
void setTransformRounds(const QByteArray& data);
void setEncryptionIV(const QByteArray& data);
void setProtectedStreamKey(const QByteArray& data);
void setStreamStartBytes(const QByteArray& data);
void setInnerRandomStreamID(const QByteArray& data);
QIODevice* m_device;
QIODevice* m_headerStream;
bool m_headerEnd;
Database* m_db;
QByteArray m_masterSeed;
QByteArray m_encryptionIV;
QByteArray m_streamStartBytes;
};
#endif // KEEPASSX_KDBX3READER_H

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

@ -0,0 +1,187 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "Kdbx3Writer.h"
#include <QBuffer>
#include <QFile>
#include <QIODevice>
#include "core/Database.h"
#include "core/Endian.h"
#include "crypto/CryptoHash.h"
#include "crypto/kdf/AesKdf.h"
#include "crypto/Random.h"
#include "format/KeePass2RandomStream.h"
#include "format/Kdbx3XmlWriter.h"
#include "streams/HashedBlockStream.h"
#include "streams/QtIOCompressor"
#include "streams/SymmetricCipherStream.h"
#define CHECK_RETURN(x) if (!(x)) return;
#define CHECK_RETURN_FALSE(x) if (!(x)) return false;
Kdbx3Writer::Kdbx3Writer()
: m_device(0)
{
}
bool Kdbx3Writer::writeDatabase(QIODevice* device, Database* db)
{
m_error = false;
m_errorStr.clear();
QByteArray masterSeed = randomGen()->randomArray(32);
QByteArray encryptionIV = randomGen()->randomArray(16);
QByteArray protectedStreamKey = randomGen()->randomArray(32);
QByteArray startBytes = randomGen()->randomArray(32);
QByteArray endOfHeader = "\r\n\r\n";
if (db->challengeMasterSeed(masterSeed) == false) {
raiseError(tr("Unable to issue challenge-response."));
return false;
}
if (!db->setKey(db->key(), false, true)) {
raiseError(tr("Unable to calculate master key"));
return false;
}
CryptoHash hash(CryptoHash::Sha256);
hash.addData(masterSeed);
hash.addData(db->challengeResponseKey());
Q_ASSERT(!db->transformedMasterKey().isEmpty());
hash.addData(db->transformedMasterKey());
QByteArray finalKey = hash.result();
QBuffer header;
header.open(QIODevice::WriteOnly);
m_device = &header;
CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(Endian::int32ToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray()));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::CompressionFlags,
Endian::int32ToBytes(db->compressionAlgo(),
KeePass2::BYTEORDER)));
AesKdf* kdf = static_cast<AesKdf*>(db->kdf());
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::MasterSeed, masterSeed));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformSeed, kdf->seed()));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::TransformRounds,
Endian::int64ToBytes(kdf->rounds(),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EncryptionIV, encryptionIV));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::ProtectedStreamKey, protectedStreamKey));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::StreamStartBytes, startBytes));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::InnerRandomStreamID,
Endian::int32ToBytes(KeePass2::Salsa20,
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeHeaderField(KeePass2::EndOfHeader, endOfHeader));
header.close();
m_device = device;
QByteArray headerHash = CryptoHash::hash(header.data(), CryptoHash::Sha256);
CHECK_RETURN_FALSE(writeData(header.data()));
SymmetricCipher::Algorithm algo = SymmetricCipher::cipherToAlgorithm(db->cipher());
SymmetricCipherStream cipherStream(device, algo,
SymmetricCipher::algorithmMode(algo), SymmetricCipher::Encrypt);
cipherStream.init(finalKey, encryptionIV);
if (!cipherStream.open(QIODevice::WriteOnly)) {
raiseError(cipherStream.errorString());
return false;
}
m_device = &cipherStream;
CHECK_RETURN_FALSE(writeData(startBytes));
HashedBlockStream hashedStream(&cipherStream);
if (!hashedStream.open(QIODevice::WriteOnly)) {
raiseError(hashedStream.errorString());
return false;
}
QScopedPointer<QtIOCompressor> ioCompressor;
if (db->compressionAlgo() == Database::CompressionNone) {
m_device = &hashedStream;
}
else {
ioCompressor.reset(new QtIOCompressor(&hashedStream));
ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat);
if (!ioCompressor->open(QIODevice::WriteOnly)) {
raiseError(ioCompressor->errorString());
return false;
}
m_device = ioCompressor.data();
}
KeePass2RandomStream randomStream(KeePass2::Salsa20);
if (!randomStream.init(protectedStreamKey)) {
raiseError(randomStream.errorString());
return false;
}
Kdbx3XmlWriter xmlWriter;
xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash);
// Explicitly close/reset streams so they are flushed and we can detect
// errors. QIODevice::close() resets errorString() etc.
if (ioCompressor) {
ioCompressor->close();
}
if (!hashedStream.reset()) {
raiseError(hashedStream.errorString());
return false;
}
if (!cipherStream.reset()) {
raiseError(cipherStream.errorString());
return false;
}
if (xmlWriter.hasError()) {
raiseError(xmlWriter.errorString());
}
return true;
}
bool Kdbx3Writer::writeData(const QByteArray& data)
{
if (m_device->write(data) != data.size()) {
raiseError(m_device->errorString());
return false;
}
else {
return true;
}
}
bool Kdbx3Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data)
{
Q_ASSERT(data.size() <= 65535);
QByteArray fieldIdArr;
fieldIdArr[0] = fieldId;
CHECK_RETURN_FALSE(writeData(fieldIdArr));
CHECK_RETURN_FALSE(writeData(Endian::int16ToBytes(static_cast<quint16>(data.size()),
KeePass2::BYTEORDER)));
CHECK_RETURN_FALSE(writeData(data));
return true;
}

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

@ -0,0 +1,47 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_KDBX3WRITER_H
#define KEEPASSX_KDBX3WRITER_H
#include <QCoreApplication>
#include "format/KeePass2.h"
#include "format/KeePass2Writer.h"
#include "keys/CompositeKey.h"
class Database;
class QIODevice;
class Kdbx3Writer : public BaseKeePass2Writer
{
Q_DECLARE_TR_FUNCTIONS(Kdbx3Writer)
public:
Kdbx3Writer();
using BaseKeePass2Writer::writeDatabase;
bool writeDatabase(QIODevice* device, Database* db);
private:
bool writeData(const QByteArray& data);
bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data);
QIODevice* m_device;
};
#endif // KEEPASSX_KDBX3WRITER_H

View File

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

View File

@ -15,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_KEEPASS2XMLREADER_H
#define KEEPASSX_KEEPASS2XMLREADER_H
#ifndef KEEPASSX_KDBX3XMLREADER_H
#define KEEPASSX_KDBX3XMLREADER_H
#include <QColor>
#include <QCoreApplication>
@ -34,12 +34,12 @@ class Group;
class KeePass2RandomStream;
class Metadata;
class KeePass2XmlReader
class Kdbx3XmlReader
{
Q_DECLARE_TR_FUNCTIONS(KeePass2XmlReader)
Q_DECLARE_TR_FUNCTIONS(Kdbx3XmlReader)
public:
KeePass2XmlReader();
Kdbx3XmlReader();
Database* readDatabase(QIODevice* device);
void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
Database* readDatabase(const QString& filename);
@ -98,4 +98,4 @@ private:
bool m_strictMode;
};
#endif // KEEPASSX_KEEPASS2XMLREADER_H
#endif // KEEPASSX_KDBX3XMLREADER_H

View File

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

View File

@ -15,8 +15,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef KEEPASSX_KEEPASS2XMLWRITER_H
#define KEEPASSX_KEEPASS2XMLWRITER_H
#ifndef KEEPASSX_KDBX3XMLWRITER_H
#define KEEPASSX_KDBX3XMLWRITER_H
#include <QColor>
#include <QDateTime>
@ -32,10 +32,10 @@
class KeePass2RandomStream;
class Metadata;
class KeePass2XmlWriter
class Kdbx3XmlWriter
{
public:
KeePass2XmlWriter();
Kdbx3XmlWriter();
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr,
const QByteArray& headerHash = QByteArray());
void writeDatabase(const QString& filename, Database* db);
@ -87,4 +87,4 @@ private:
QString m_errorStr;
};
#endif // KEEPASSX_KEEPASS2XMLWRITER_H
#endif // KEEPASSX_KDBX3XMLWRITER_H

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,58 +18,65 @@
#ifndef KEEPASSX_KEEPASS2READER_H
#define KEEPASSX_KEEPASS2READER_H
#include <QtGlobal>
#include <QByteArray>
#include <QString>
#include <QCoreApplication>
#include <QScopedPointer>
#include <QIODevice>
#include "keys/CompositeKey.h"
#include "format/KeePass2.h"
#include "core/Database.h"
#include "keys/CompositeKey.h"
class Database;
class QIODevice;
class KeePass2Reader
class BaseKeePass2Reader
{
Q_DECLARE_TR_FUNCTIONS(KeePass2Reader)
public:
KeePass2Reader();
Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false);
Database* readDatabase(const QString& filename, const CompositeKey& key);
bool hasError();
QString errorString();
void setSaveXml(bool save);
QByteArray xmlData();
QByteArray streamKey();
KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const;
BaseKeePass2Reader();
private:
virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) = 0;
virtual Database* readDatabase(const QString& filename, const CompositeKey& key);
virtual bool hasError();
virtual QString errorString();
virtual void setSaveXml(bool save);
virtual QByteArray xmlData();
virtual QByteArray streamKey();
virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const;
virtual ~BaseKeePass2Reader();
protected:
void raiseError(const QString& errorMessage);
bool readHeaderField();
void setCipher(const QByteArray& data);
void setCompressionFlags(const QByteArray& data);
void setMasterSeed(const QByteArray& data);
void setTransformSeed(const QByteArray& data);
void setTransformRounds(const QByteArray& data);
void setEncryptionIV(const QByteArray& data);
void setProtectedStreamKey(const QByteArray& data);
void setStreamStartBytes(const QByteArray& data);
void setInnerRandomStreamID(const QByteArray& data);
QIODevice* m_device;
QIODevice* m_headerStream;
bool m_error;
QString m_errorStr;
bool m_headerEnd;
bool m_saveXml;
QByteArray m_xmlData;
Database* m_db;
QByteArray m_masterSeed;
QByteArray m_encryptionIV;
QByteArray m_streamStartBytes;
QByteArray m_protectedStreamKey;
KeePass2::ProtectedStreamAlgo m_irsAlgo;
};
class KeePass2Reader : public BaseKeePass2Reader
{
Q_DECLARE_TR_FUNCTIONS(KeePass2Reader)
public:
virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override;
using BaseKeePass2Reader::readDatabase;
virtual bool hasError() override;
virtual QString errorString() override;
virtual QByteArray xmlData() override;
virtual QByteArray streamKey() override;
virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const override;
quint32 version() const;
BaseKeePass2Reader* reader() const;
private:
QScopedPointer<BaseKeePass2Reader> m_reader;
quint32 m_version;
};
#endif // KEEPASSX_KEEPASS2READER_H

View File

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

View File

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

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@ -18,33 +18,48 @@
#ifndef KEEPASSX_KEEPASS2WRITER_H
#define KEEPASSX_KEEPASS2WRITER_H
#include <QIODevice>
#include <QByteArray>
#include <QString>
#include <QCoreApplication>
#include <QScopedPointer>
#include "core/Database.h"
#include "format/KeePass2.h"
#include "keys/CompositeKey.h"
class Database;
class QIODevice;
class KeePass2Writer
class BaseKeePass2Writer
{
Q_DECLARE_TR_FUNCTIONS(KeePass2Writer)
public:
KeePass2Writer();
void writeDatabase(QIODevice* device, Database* db);
void writeDatabase(const QString& filename, Database* db);
bool hasError();
QString errorString();
BaseKeePass2Writer();
private:
bool writeData(const QByteArray& data);
bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data);
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);
QIODevice* m_device;
bool m_error;
QString m_errorStr;
};
#endif // KEEPASSX_KEEPASS2WRITER_H
class KeePass2Writer : public BaseKeePass2Writer
{
Q_DECLARE_TR_FUNCTIONS(KeePass2Writer)
public:
virtual bool writeDatabase(QIODevice* device, Database* db) override;
using BaseKeePass2Writer::writeDatabase;
virtual bool hasError() override;
virtual QString errorString() override;
private:
QScopedPointer<BaseKeePass2Writer> m_writer;
};
#endif // KEEPASSX_KEEPASS2READER_H

View File

@ -107,7 +107,7 @@ endif()
add_unit_test(NAME testgroup SOURCES TestGroup.cpp
LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testkeepass2xmlreader SOURCES TestKeePass2XmlReader.cpp
add_unit_test(NAME testkdbx3xmlreader SOURCES TestKeePass2XmlReader.cpp TestKdbx3XmlReader.cpp
LIBS ${TEST_LIBRARIES})
add_unit_test(NAME testkeys SOURCES TestKeys.cpp

View File

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

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 2 or (at your option)
* version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <QTest>
#include "TestKeePass2XmlReader.h"
QTEST_GUILESS_MAIN(TestKdbx3XmlReader)

View File

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

View File

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

View File

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