mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-02 01:25:13 -05:00
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:
parent
e5ec585f98
commit
3461cbfb06
@ -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
394
src/format/Kdbx3Reader.cpp
Normal 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
62
src/format/Kdbx3Reader.h
Normal 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
187
src/format/Kdbx3Writer.cpp
Normal 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
47
src/format/Kdbx3Writer.h
Normal 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
|
@ -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();
|
||||
}
|
@ -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
|
@ -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;
|
@ -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
|
@ -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();
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
22
tests/TestKdbx3XmlReader.cpp
Normal file
22
tests/TestKdbx3XmlReader.cpp
Normal 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)
|
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user