mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-02-08 18:58:29 -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/KeePass1Reader.cpp
|
||||||
format/KeePass2.cpp
|
format/KeePass2.cpp
|
||||||
format/KeePass2RandomStream.cpp
|
format/KeePass2RandomStream.cpp
|
||||||
format/KeePass2Reader.cpp
|
|
||||||
format/KeePass2Repair.cpp
|
format/KeePass2Repair.cpp
|
||||||
|
format/KeePass2Reader.cpp
|
||||||
format/KeePass2Writer.cpp
|
format/KeePass2Writer.cpp
|
||||||
format/KeePass2XmlReader.cpp
|
format/Kdbx3Reader.cpp
|
||||||
format/KeePass2XmlWriter.cpp
|
format/Kdbx3Writer.cpp
|
||||||
|
format/Kdbx3XmlReader.cpp
|
||||||
|
format/Kdbx3XmlWriter.cpp
|
||||||
gui/AboutDialog.cpp
|
gui/AboutDialog.cpp
|
||||||
gui/Application.cpp
|
gui/Application.cpp
|
||||||
gui/CategoryListWidget.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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "KeePass2XmlReader.h"
|
#include "Kdbx3XmlReader.h"
|
||||||
|
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
@ -30,7 +30,7 @@
|
|||||||
|
|
||||||
typedef QPair<QString, QString> StringPair;
|
typedef QPair<QString, QString> StringPair;
|
||||||
|
|
||||||
KeePass2XmlReader::KeePass2XmlReader()
|
Kdbx3XmlReader::Kdbx3XmlReader()
|
||||||
: m_randomStream(nullptr)
|
: m_randomStream(nullptr)
|
||||||
, m_db(nullptr)
|
, m_db(nullptr)
|
||||||
, m_meta(nullptr)
|
, m_meta(nullptr)
|
||||||
@ -40,12 +40,12 @@ KeePass2XmlReader::KeePass2XmlReader()
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlReader::setStrictMode(bool strictMode)
|
void Kdbx3XmlReader::setStrictMode(bool strictMode)
|
||||||
{
|
{
|
||||||
m_strictMode = 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_error = false;
|
||||||
m_errorStr.clear();
|
m_errorStr.clear();
|
||||||
@ -76,12 +76,12 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra
|
|||||||
|
|
||||||
if (!m_xml.error()) {
|
if (!m_xml.error()) {
|
||||||
if (!m_tmpParent->children().isEmpty()) {
|
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());
|
m_tmpParent->children().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_tmpParent->entries().isEmpty()) {
|
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());
|
m_tmpParent->children().size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -97,7 +97,7 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra
|
|||||||
|
|
||||||
if (!m_xml.error()) {
|
if (!m_xml.error()) {
|
||||||
for (const QString& key : unusedKeys) {
|
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;
|
delete m_tmpParent;
|
||||||
}
|
}
|
||||||
|
|
||||||
Database* KeePass2XmlReader::readDatabase(QIODevice* device)
|
Database* Kdbx3XmlReader::readDatabase(QIODevice* device)
|
||||||
{
|
{
|
||||||
Database* db = new Database();
|
Database* db = new Database();
|
||||||
readDatabase(device, db);
|
readDatabase(device, db);
|
||||||
return db;
|
return db;
|
||||||
}
|
}
|
||||||
|
|
||||||
Database* KeePass2XmlReader::readDatabase(const QString& filename)
|
Database* Kdbx3XmlReader::readDatabase(const QString& filename)
|
||||||
{
|
{
|
||||||
QFile file(filename);
|
QFile file(filename);
|
||||||
file.open(QIODevice::ReadOnly);
|
file.open(QIODevice::ReadOnly);
|
||||||
return readDatabase(&file);
|
return readDatabase(&file);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeePass2XmlReader::hasError()
|
bool Kdbx3XmlReader::hasError()
|
||||||
{
|
{
|
||||||
return m_error || m_xml.hasError();
|
return m_error || m_xml.hasError();
|
||||||
}
|
}
|
||||||
|
|
||||||
QString KeePass2XmlReader::errorString()
|
QString Kdbx3XmlReader::errorString()
|
||||||
{
|
{
|
||||||
if (m_error) {
|
if (m_error) {
|
||||||
return m_errorStr;
|
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_error = true;
|
||||||
m_errorStr = errorMessage;
|
m_errorStr = errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray KeePass2XmlReader::headerHash()
|
QByteArray Kdbx3XmlReader::headerHash()
|
||||||
{
|
{
|
||||||
return m_headerHash;
|
return m_headerHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeePass2XmlReader::parseKeePassFile()
|
bool Kdbx3XmlReader::parseKeePassFile()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile");
|
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile");
|
||||||
|
|
||||||
@ -202,7 +202,7 @@ bool KeePass2XmlReader::parseKeePassFile()
|
|||||||
return rootParsedSuccessfully;
|
return rootParsedSuccessfully;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlReader::parseMeta()
|
void Kdbx3XmlReader::parseMeta()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Meta");
|
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");
|
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");
|
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");
|
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");
|
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries");
|
||||||
|
|
||||||
@ -393,7 +393,7 @@ void KeePass2XmlReader::parseBinaries()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (m_binaryPool.contains(id)) {
|
if (m_binaryPool.contains(id)) {
|
||||||
qWarning("KeePass2XmlReader::parseBinaries: overwriting binary item \"%s\"",
|
qWarning("Kdbx3XmlReader::parseBinaries: overwriting binary item \"%s\"",
|
||||||
qPrintable(id));
|
qPrintable(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -405,7 +405,7 @@ void KeePass2XmlReader::parseBinaries()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlReader::parseCustomData()
|
void Kdbx3XmlReader::parseCustomData()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData");
|
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");
|
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");
|
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Root");
|
||||||
|
|
||||||
@ -486,7 +486,7 @@ bool KeePass2XmlReader::parseRoot()
|
|||||||
return groupParsedSuccessfully;
|
return groupParsedSuccessfully;
|
||||||
}
|
}
|
||||||
|
|
||||||
Group* KeePass2XmlReader::parseGroup()
|
Group* Kdbx3XmlReader::parseGroup()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group");
|
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group");
|
||||||
|
|
||||||
@ -525,7 +525,7 @@ Group* KeePass2XmlReader::parseGroup()
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
if (iconId >= DatabaseIcons::IconCount) {
|
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);
|
group->setIcon(iconId);
|
||||||
}
|
}
|
||||||
@ -623,7 +623,7 @@ Group* KeePass2XmlReader::parseGroup()
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlReader::parseDeletedObjects()
|
void Kdbx3XmlReader::parseDeletedObjects()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObjects");
|
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");
|
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");
|
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry");
|
||||||
|
|
||||||
@ -793,7 +793,7 @@ Entry* KeePass2XmlReader::parseEntry(bool history)
|
|||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlReader::parseEntryString(Entry* entry)
|
void Kdbx3XmlReader::parseEntryString(Entry* entry)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "String");
|
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");
|
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binary");
|
||||||
|
|
||||||
@ -913,7 +913,7 @@ QPair<QString, QString> KeePass2XmlReader::parseEntryBinary(Entry* entry)
|
|||||||
return poolRef;
|
return poolRef;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlReader::parseAutoType(Entry* entry)
|
void Kdbx3XmlReader::parseAutoType(Entry* entry)
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "AutoType");
|
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");
|
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");
|
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "History");
|
||||||
|
|
||||||
@ -984,7 +984,7 @@ QList<Entry*> KeePass2XmlReader::parseEntryHistory()
|
|||||||
return historyItems;
|
return historyItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
TimeInfo KeePass2XmlReader::parseTimes()
|
TimeInfo Kdbx3XmlReader::parseTimes()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times");
|
Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times");
|
||||||
|
|
||||||
@ -1019,12 +1019,12 @@ TimeInfo KeePass2XmlReader::parseTimes()
|
|||||||
return timeInfo;
|
return timeInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString KeePass2XmlReader::readString()
|
QString Kdbx3XmlReader::readString()
|
||||||
{
|
{
|
||||||
return m_xml.readElementText();
|
return m_xml.readElementText();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeePass2XmlReader::readBool()
|
bool Kdbx3XmlReader::readBool()
|
||||||
{
|
{
|
||||||
QString str = readString();
|
QString str = readString();
|
||||||
|
|
||||||
@ -1043,7 +1043,7 @@ bool KeePass2XmlReader::readBool()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime KeePass2XmlReader::readDateTime()
|
QDateTime Kdbx3XmlReader::readDateTime()
|
||||||
{
|
{
|
||||||
QString str = readString();
|
QString str = readString();
|
||||||
QDateTime dt = QDateTime::fromString(str, Qt::ISODate);
|
QDateTime dt = QDateTime::fromString(str, Qt::ISODate);
|
||||||
@ -1060,7 +1060,7 @@ QDateTime KeePass2XmlReader::readDateTime()
|
|||||||
return dt;
|
return dt;
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor KeePass2XmlReader::readColor()
|
QColor Kdbx3XmlReader::readColor()
|
||||||
{
|
{
|
||||||
QString colorStr = readString();
|
QString colorStr = readString();
|
||||||
|
|
||||||
@ -1101,7 +1101,7 @@ QColor KeePass2XmlReader::readColor()
|
|||||||
return color;
|
return color;
|
||||||
}
|
}
|
||||||
|
|
||||||
int KeePass2XmlReader::readNumber()
|
int Kdbx3XmlReader::readNumber()
|
||||||
{
|
{
|
||||||
bool ok;
|
bool ok;
|
||||||
int result = readString().toInt(&ok);
|
int result = readString().toInt(&ok);
|
||||||
@ -1111,7 +1111,7 @@ int KeePass2XmlReader::readNumber()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Uuid KeePass2XmlReader::readUuid()
|
Uuid Kdbx3XmlReader::readUuid()
|
||||||
{
|
{
|
||||||
QByteArray uuidBin = readBinary();
|
QByteArray uuidBin = readBinary();
|
||||||
if (uuidBin.isEmpty()) {
|
if (uuidBin.isEmpty()) {
|
||||||
@ -1128,12 +1128,12 @@ Uuid KeePass2XmlReader::readUuid()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray KeePass2XmlReader::readBinary()
|
QByteArray Kdbx3XmlReader::readBinary()
|
||||||
{
|
{
|
||||||
return QByteArray::fromBase64(readString().toLatin1());
|
return QByteArray::fromBase64(readString().toLatin1());
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray KeePass2XmlReader::readCompressedBinary()
|
QByteArray Kdbx3XmlReader::readCompressedBinary()
|
||||||
{
|
{
|
||||||
QByteArray rawData = readBinary();
|
QByteArray rawData = readBinary();
|
||||||
|
|
||||||
@ -1151,7 +1151,7 @@ QByteArray KeePass2XmlReader::readCompressedBinary()
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
Group* KeePass2XmlReader::getGroup(const Uuid& uuid)
|
Group* Kdbx3XmlReader::getGroup(const Uuid& uuid)
|
||||||
{
|
{
|
||||||
if (uuid.isNull()) {
|
if (uuid.isNull()) {
|
||||||
return nullptr;
|
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()) {
|
if (uuid.isNull()) {
|
||||||
return nullptr;
|
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();
|
m_xml.skipCurrentElement();
|
||||||
}
|
}
|
@ -15,8 +15,8 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef KEEPASSX_KEEPASS2XMLREADER_H
|
#ifndef KEEPASSX_KDBX3XMLREADER_H
|
||||||
#define KEEPASSX_KEEPASS2XMLREADER_H
|
#define KEEPASSX_KDBX3XMLREADER_H
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
@ -34,12 +34,12 @@ class Group;
|
|||||||
class KeePass2RandomStream;
|
class KeePass2RandomStream;
|
||||||
class Metadata;
|
class Metadata;
|
||||||
|
|
||||||
class KeePass2XmlReader
|
class Kdbx3XmlReader
|
||||||
{
|
{
|
||||||
Q_DECLARE_TR_FUNCTIONS(KeePass2XmlReader)
|
Q_DECLARE_TR_FUNCTIONS(Kdbx3XmlReader)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
KeePass2XmlReader();
|
Kdbx3XmlReader();
|
||||||
Database* readDatabase(QIODevice* device);
|
Database* readDatabase(QIODevice* device);
|
||||||
void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
|
void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr);
|
||||||
Database* readDatabase(const QString& filename);
|
Database* readDatabase(const QString& filename);
|
||||||
@ -98,4 +98,4 @@ private:
|
|||||||
bool m_strictMode;
|
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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "KeePass2XmlWriter.h"
|
#include "Kdbx3XmlWriter.h"
|
||||||
|
|
||||||
#include <QBuffer>
|
#include <QBuffer>
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
@ -24,7 +24,7 @@
|
|||||||
#include "format/KeePass2RandomStream.h"
|
#include "format/KeePass2RandomStream.h"
|
||||||
#include "streams/QtIOCompressor"
|
#include "streams/QtIOCompressor"
|
||||||
|
|
||||||
KeePass2XmlWriter::KeePass2XmlWriter()
|
Kdbx3XmlWriter::Kdbx3XmlWriter()
|
||||||
: m_db(nullptr)
|
: m_db(nullptr)
|
||||||
, m_meta(nullptr)
|
, m_meta(nullptr)
|
||||||
, m_randomStream(nullptr)
|
, m_randomStream(nullptr)
|
||||||
@ -35,7 +35,7 @@ KeePass2XmlWriter::KeePass2XmlWriter()
|
|||||||
m_xml.setCodec("UTF-8");
|
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)
|
const QByteArray& headerHash)
|
||||||
{
|
{
|
||||||
m_db = db;
|
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);
|
QFile file(filename);
|
||||||
file.open(QIODevice::WriteOnly|QIODevice::Truncate);
|
file.open(QIODevice::WriteOnly|QIODevice::Truncate);
|
||||||
writeDatabase(&file, db);
|
writeDatabase(&file, db);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeePass2XmlWriter::hasError()
|
bool Kdbx3XmlWriter::hasError()
|
||||||
{
|
{
|
||||||
return m_error;
|
return m_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString KeePass2XmlWriter::errorString()
|
QString Kdbx3XmlWriter::errorString()
|
||||||
{
|
{
|
||||||
return m_errorStr;
|
return m_errorStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::generateIdMap()
|
void Kdbx3XmlWriter::generateIdMap()
|
||||||
{
|
{
|
||||||
const QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
|
const QList<Entry*> allEntries = m_db->rootGroup()->entriesRecursive(true);
|
||||||
int nextId = 0;
|
int nextId = 0;
|
||||||
@ -96,7 +96,7 @@ void KeePass2XmlWriter::generateIdMap()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeMetadata()
|
void Kdbx3XmlWriter::writeMetadata()
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("Meta");
|
m_xml.writeStartElement("Meta");
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ void KeePass2XmlWriter::writeMetadata()
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeMemoryProtection()
|
void Kdbx3XmlWriter::writeMemoryProtection()
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("MemoryProtection");
|
m_xml.writeStartElement("MemoryProtection");
|
||||||
|
|
||||||
@ -145,7 +145,7 @@ void KeePass2XmlWriter::writeMemoryProtection()
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeCustomIcons()
|
void Kdbx3XmlWriter::writeCustomIcons()
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("CustomIcons");
|
m_xml.writeStartElement("CustomIcons");
|
||||||
|
|
||||||
@ -157,7 +157,7 @@ void KeePass2XmlWriter::writeCustomIcons()
|
|||||||
m_xml.writeEndElement();
|
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");
|
m_xml.writeStartElement("Icon");
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon)
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeBinaries()
|
void Kdbx3XmlWriter::writeBinaries()
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("Binaries");
|
m_xml.writeStartElement("Binaries");
|
||||||
|
|
||||||
@ -216,7 +216,7 @@ void KeePass2XmlWriter::writeBinaries()
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeCustomData()
|
void Kdbx3XmlWriter::writeCustomData()
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("CustomData");
|
m_xml.writeStartElement("CustomData");
|
||||||
|
|
||||||
@ -229,7 +229,7 @@ void KeePass2XmlWriter::writeCustomData()
|
|||||||
m_xml.writeEndElement();
|
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");
|
m_xml.writeStartElement("Item");
|
||||||
|
|
||||||
@ -239,7 +239,7 @@ void KeePass2XmlWriter::writeCustomDataItem(const QString& key, const QString& v
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeRoot()
|
void Kdbx3XmlWriter::writeRoot()
|
||||||
{
|
{
|
||||||
Q_ASSERT(m_db->rootGroup());
|
Q_ASSERT(m_db->rootGroup());
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ void KeePass2XmlWriter::writeRoot()
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeGroup(const Group* group)
|
void Kdbx3XmlWriter::writeGroup(const Group* group)
|
||||||
{
|
{
|
||||||
Q_ASSERT(!group->uuid().isNull());
|
Q_ASSERT(!group->uuid().isNull());
|
||||||
|
|
||||||
@ -288,7 +288,7 @@ void KeePass2XmlWriter::writeGroup(const Group* group)
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeTimes(const TimeInfo& ti)
|
void Kdbx3XmlWriter::writeTimes(const TimeInfo& ti)
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("Times");
|
m_xml.writeStartElement("Times");
|
||||||
|
|
||||||
@ -303,7 +303,7 @@ void KeePass2XmlWriter::writeTimes(const TimeInfo& ti)
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeDeletedObjects()
|
void Kdbx3XmlWriter::writeDeletedObjects()
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("DeletedObjects");
|
m_xml.writeStartElement("DeletedObjects");
|
||||||
|
|
||||||
@ -315,7 +315,7 @@ void KeePass2XmlWriter::writeDeletedObjects()
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeDeletedObject(const DeletedObject& delObj)
|
void Kdbx3XmlWriter::writeDeletedObject(const DeletedObject& delObj)
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("DeletedObject");
|
m_xml.writeStartElement("DeletedObject");
|
||||||
|
|
||||||
@ -325,7 +325,7 @@ void KeePass2XmlWriter::writeDeletedObject(const DeletedObject& delObj)
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeEntry(const Entry* entry)
|
void Kdbx3XmlWriter::writeEntry(const Entry* entry)
|
||||||
{
|
{
|
||||||
Q_ASSERT(!entry->uuid().isNull());
|
Q_ASSERT(!entry->uuid().isNull());
|
||||||
|
|
||||||
@ -407,7 +407,7 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry)
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeAutoType(const Entry* entry)
|
void Kdbx3XmlWriter::writeAutoType(const Entry* entry)
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("AutoType");
|
m_xml.writeStartElement("AutoType");
|
||||||
|
|
||||||
@ -423,7 +423,7 @@ void KeePass2XmlWriter::writeAutoType(const Entry* entry)
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc)
|
void Kdbx3XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc)
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("Association");
|
m_xml.writeStartElement("Association");
|
||||||
|
|
||||||
@ -433,7 +433,7 @@ void KeePass2XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Associati
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeEntryHistory(const Entry* entry)
|
void Kdbx3XmlWriter::writeEntryHistory(const Entry* entry)
|
||||||
{
|
{
|
||||||
m_xml.writeStartElement("History");
|
m_xml.writeStartElement("History");
|
||||||
|
|
||||||
@ -445,7 +445,7 @@ void KeePass2XmlWriter::writeEntryHistory(const Entry* entry)
|
|||||||
m_xml.writeEndElement();
|
m_xml.writeEndElement();
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeString(const QString& qualifiedName, const QString& string)
|
void Kdbx3XmlWriter::writeString(const QString& qualifiedName, const QString& string)
|
||||||
{
|
{
|
||||||
if (string.isEmpty()) {
|
if (string.isEmpty()) {
|
||||||
m_xml.writeEmptyElement(qualifiedName);
|
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));
|
writeString(qualifiedName, QString::number(number));
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeBool(const QString& qualifiedName, bool b)
|
void Kdbx3XmlWriter::writeBool(const QString& qualifiedName, bool b)
|
||||||
{
|
{
|
||||||
if (b) {
|
if (b) {
|
||||||
writeString(qualifiedName, "True");
|
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.isValid());
|
||||||
Q_ASSERT(dateTime.timeSpec() == Qt::UTC);
|
Q_ASSERT(dateTime.timeSpec() == Qt::UTC);
|
||||||
@ -485,12 +485,12 @@ void KeePass2XmlWriter::writeDateTime(const QString& qualifiedName, const QDateT
|
|||||||
writeString(qualifiedName, dateTimeStr);
|
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());
|
writeString(qualifiedName, uuid.toBase64());
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Group* group)
|
void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Group* group)
|
||||||
{
|
{
|
||||||
if (group) {
|
if (group) {
|
||||||
writeUuid(qualifiedName, group->uuid());
|
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) {
|
if (entry) {
|
||||||
writeUuid(qualifiedName, entry->uuid());
|
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()));
|
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;
|
QString colorStr;
|
||||||
|
|
||||||
@ -528,7 +528,7 @@ void KeePass2XmlWriter::writeColor(const QString& qualifiedName, const QColor& c
|
|||||||
writeString(qualifiedName, colorStr);
|
writeString(qualifiedName, colorStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState)
|
void Kdbx3XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState)
|
||||||
{
|
{
|
||||||
QString value;
|
QString value;
|
||||||
|
|
||||||
@ -545,7 +545,7 @@ void KeePass2XmlWriter::writeTriState(const QString& qualifiedName, Group::TriSt
|
|||||||
writeString(qualifiedName, value);
|
writeString(qualifiedName, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString KeePass2XmlWriter::colorPartToString(int value)
|
QString Kdbx3XmlWriter::colorPartToString(int value)
|
||||||
{
|
{
|
||||||
QString str = QString::number(value, 16).toUpper();
|
QString str = QString::number(value, 16).toUpper();
|
||||||
if (str.length() == 1) {
|
if (str.length() == 1) {
|
||||||
@ -555,7 +555,7 @@ QString KeePass2XmlWriter::colorPartToString(int value)
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str)
|
QString Kdbx3XmlWriter::stripInvalidXml10Chars(QString str)
|
||||||
{
|
{
|
||||||
for (int i = str.size() - 1; i >= 0; i--) {
|
for (int i = str.size() - 1; i >= 0; i--) {
|
||||||
const QChar ch = str.at(i);
|
const QChar ch = str.at(i);
|
||||||
@ -580,7 +580,7 @@ QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str)
|
|||||||
return str;
|
return str;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2XmlWriter::raiseError(const QString& errorMessage)
|
void Kdbx3XmlWriter::raiseError(const QString& errorMessage)
|
||||||
{
|
{
|
||||||
m_error = true;
|
m_error = true;
|
||||||
m_errorStr = errorMessage;
|
m_errorStr = errorMessage;
|
@ -15,8 +15,8 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#ifndef KEEPASSX_KEEPASS2XMLWRITER_H
|
#ifndef KEEPASSX_KDBX3XMLWRITER_H
|
||||||
#define KEEPASSX_KEEPASS2XMLWRITER_H
|
#define KEEPASSX_KDBX3XMLWRITER_H
|
||||||
|
|
||||||
#include <QColor>
|
#include <QColor>
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
@ -32,10 +32,10 @@
|
|||||||
class KeePass2RandomStream;
|
class KeePass2RandomStream;
|
||||||
class Metadata;
|
class Metadata;
|
||||||
|
|
||||||
class KeePass2XmlWriter
|
class Kdbx3XmlWriter
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
KeePass2XmlWriter();
|
Kdbx3XmlWriter();
|
||||||
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr,
|
void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr,
|
||||||
const QByteArray& headerHash = QByteArray());
|
const QByteArray& headerHash = QByteArray());
|
||||||
void writeDatabase(const QString& filename, Database* db);
|
void writeDatabase(const QString& filename, Database* db);
|
||||||
@ -87,4 +87,4 @@ private:
|
|||||||
QString m_errorStr;
|
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
|
* 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
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "KeePass2Reader.h"
|
#include <QtGlobal>
|
||||||
|
#include <QString>
|
||||||
#include <QBuffer>
|
|
||||||
#include <QFile>
|
#include <QFile>
|
||||||
#include <QIODevice>
|
|
||||||
|
|
||||||
#include "core/Database.h"
|
|
||||||
#include "core/Endian.h"
|
#include "core/Endian.h"
|
||||||
#include "crypto/CryptoHash.h"
|
#include "keys/CompositeKey.h"
|
||||||
#include "crypto/kdf/AesKdf.h"
|
#include "format/KeePass2Reader.h"
|
||||||
#include "format/KeePass1.h"
|
#include "format/KeePass1.h"
|
||||||
#include "format/KeePass2.h"
|
#include "format/KeePass2.h"
|
||||||
#include "format/KeePass2RandomStream.h"
|
#include "format/Kdbx3Reader.h"
|
||||||
#include "format/KeePass2XmlReader.h"
|
|
||||||
#include "streams/HashedBlockStream.h"
|
|
||||||
#include "streams/QtIOCompressor"
|
|
||||||
#include "streams/StoreDataStream.h"
|
|
||||||
#include "streams/SymmetricCipherStream.h"
|
|
||||||
|
|
||||||
KeePass2Reader::KeePass2Reader()
|
BaseKeePass2Reader::BaseKeePass2Reader() :
|
||||||
: m_device(nullptr)
|
m_error(false),
|
||||||
, m_headerStream(nullptr)
|
m_saveXml(false),
|
||||||
, m_error(false)
|
m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo)
|
||||||
, m_headerEnd(false)
|
|
||||||
, m_saveXml(false)
|
|
||||||
, m_db(nullptr)
|
|
||||||
, 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_errorStr.clear();
|
||||||
m_headerEnd = false;
|
|
||||||
m_xmlData.clear();
|
m_xmlData.clear();
|
||||||
m_masterSeed.clear();
|
|
||||||
m_encryptionIV.clear();
|
|
||||||
m_streamStartBytes.clear();
|
|
||||||
m_protectedStreamKey.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);
|
QFile file(filename);
|
||||||
if (!file.open(QFile::ReadOnly)) {
|
if (!file.open(QFile::ReadOnly)) {
|
||||||
@ -224,238 +56,113 @@ Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeK
|
|||||||
return db.take();
|
return db.take();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeePass2Reader::hasError()
|
bool BaseKeePass2Reader::hasError()
|
||||||
{
|
{
|
||||||
return m_error;
|
return m_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString KeePass2Reader::errorString()
|
QString BaseKeePass2Reader::errorString()
|
||||||
{
|
{
|
||||||
return m_errorStr;
|
return m_errorStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2Reader::setSaveXml(bool save)
|
void BaseKeePass2Reader::setSaveXml(bool save)
|
||||||
{
|
{
|
||||||
m_saveXml = save;
|
m_saveXml = save;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray KeePass2Reader::xmlData()
|
QByteArray BaseKeePass2Reader::xmlData()
|
||||||
{
|
{
|
||||||
return m_xmlData;
|
return m_xmlData;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray KeePass2Reader::streamKey()
|
QByteArray BaseKeePass2Reader::streamKey()
|
||||||
{
|
{
|
||||||
return m_protectedStreamKey;
|
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_error = true;
|
||||||
m_errorStr = errorMessage;
|
m_errorStr = errorMessage;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool KeePass2Reader::readHeaderField()
|
Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase)
|
||||||
{
|
{
|
||||||
QByteArray fieldIDArray = m_headerStream->read(1);
|
m_error = false;
|
||||||
if (fieldIDArray.size() != 1) {
|
m_errorStr.clear();
|
||||||
raiseError("Invalid header id size");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
quint8 fieldID = fieldIDArray.at(0);
|
|
||||||
|
|
||||||
bool ok;
|
bool ok;
|
||||||
quint16 fieldLen = Endian::readUInt16(m_headerStream, KeePass2::BYTEORDER, &ok);
|
|
||||||
if (!ok) {
|
quint32 signature1 = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok);
|
||||||
raiseError("Invalid header field length");
|
if (!ok || signature1 != KeePass2::SIGNATURE_1) {
|
||||||
return false;
|
raiseError(tr("Not a KeePass database."));
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
QByteArray fieldData;
|
quint32 signature2 = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok);
|
||||||
if (fieldLen != 0) {
|
if (ok && signature2 == KeePass1::SIGNATURE_2) {
|
||||||
fieldData = m_headerStream->read(fieldLen);
|
raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n"
|
||||||
if (fieldData.size() != fieldLen) {
|
"You can import it by clicking on Database > 'Import KeePass 1 database...'.\n"
|
||||||
raiseError("Invalid header data length");
|
"This is a one-way migration. You won't be able to open the imported "
|
||||||
return false;
|
"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) {
|
m_version = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok)
|
||||||
case KeePass2::EndOfHeader:
|
& KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||||
m_headerEnd = true;
|
quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK;
|
||||||
break;
|
if (!ok || (m_version < KeePass2::FILE_VERSION_MIN) || (m_version > maxVersion)) {
|
||||||
|
raiseError(tr("Unsupported KeePass 2 database version."));
|
||||||
case KeePass2::CipherID:
|
return nullptr;
|
||||||
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;
|
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) {
|
return m_error || (m_reader && m_reader->hasError());
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2Reader::setCompressionFlags(const QByteArray& data)
|
QString KeePass2Reader::errorString()
|
||||||
{
|
{
|
||||||
if (data.size() != 4) {
|
return m_reader ? m_reader->errorString() : m_errorStr;
|
||||||
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 KeePass2Reader::setMasterSeed(const QByteArray& data)
|
QByteArray KeePass2Reader::xmlData()
|
||||||
{
|
{
|
||||||
if (data.size() != 32) {
|
return m_reader ? m_reader->xmlData() : m_xmlData;
|
||||||
raiseError("Invalid master seed size");
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
m_masterSeed = data;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2Reader::setTransformSeed(const QByteArray& data)
|
QByteArray KeePass2Reader::streamKey()
|
||||||
{
|
{
|
||||||
if (data.size() != 32) {
|
return m_reader ? m_reader->streamKey() : m_protectedStreamKey;
|
||||||
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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const
|
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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -18,58 +18,65 @@
|
|||||||
#ifndef KEEPASSX_KEEPASS2READER_H
|
#ifndef KEEPASSX_KEEPASS2READER_H
|
||||||
#define KEEPASSX_KEEPASS2READER_H
|
#define KEEPASSX_KEEPASS2READER_H
|
||||||
|
|
||||||
|
#include <QtGlobal>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
#include <QIODevice>
|
||||||
|
|
||||||
#include "keys/CompositeKey.h"
|
|
||||||
#include "format/KeePass2.h"
|
#include "format/KeePass2.h"
|
||||||
|
#include "core/Database.h"
|
||||||
|
#include "keys/CompositeKey.h"
|
||||||
|
|
||||||
class Database;
|
class BaseKeePass2Reader
|
||||||
class QIODevice;
|
|
||||||
|
|
||||||
class KeePass2Reader
|
|
||||||
{
|
{
|
||||||
Q_DECLARE_TR_FUNCTIONS(KeePass2Reader)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
KeePass2Reader();
|
BaseKeePass2Reader();
|
||||||
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;
|
|
||||||
|
|
||||||
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);
|
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;
|
bool m_error;
|
||||||
QString m_errorStr;
|
QString m_errorStr;
|
||||||
bool m_headerEnd;
|
|
||||||
bool m_saveXml;
|
bool m_saveXml;
|
||||||
QByteArray m_xmlData;
|
QByteArray m_xmlData;
|
||||||
|
|
||||||
Database* m_db;
|
|
||||||
QByteArray m_masterSeed;
|
|
||||||
QByteArray m_encryptionIV;
|
|
||||||
QByteArray m_streamStartBytes;
|
|
||||||
QByteArray m_protectedStreamKey;
|
QByteArray m_protectedStreamKey;
|
||||||
KeePass2::ProtectedStreamAlgo m_irsAlgo;
|
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
|
#endif // KEEPASSX_KEEPASS2READER_H
|
||||||
|
@ -22,9 +22,10 @@
|
|||||||
#include <QScopedPointer>
|
#include <QScopedPointer>
|
||||||
#include <QRegExp>
|
#include <QRegExp>
|
||||||
|
|
||||||
|
#include "format/KeePass2.h"
|
||||||
#include "format/KeePass2RandomStream.h"
|
#include "format/KeePass2RandomStream.h"
|
||||||
#include "format/KeePass2Reader.h"
|
#include "format/KeePass2Reader.h"
|
||||||
#include "format/KeePass2XmlReader.h"
|
#include "format/Kdbx3XmlReader.h"
|
||||||
|
|
||||||
KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key)
|
KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key)
|
||||||
{
|
{
|
||||||
@ -73,7 +74,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device,
|
|||||||
|
|
||||||
KeePass2RandomStream randomStream(reader.protectedStreamAlgo());
|
KeePass2RandomStream randomStream(reader.protectedStreamAlgo());
|
||||||
randomStream.init(reader.streamKey());
|
randomStream.init(reader.streamKey());
|
||||||
KeePass2XmlReader xmlReader;
|
Kdbx3XmlReader xmlReader;
|
||||||
QBuffer buffer(&xmlData);
|
QBuffer buffer(&xmlData);
|
||||||
buffer.open(QIODevice::ReadOnly);
|
buffer.open(QIODevice::ReadOnly);
|
||||||
xmlReader.readDatabase(&buffer, db.data(), &randomStream);
|
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
|
* 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
|
* 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/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "KeePass2Writer.h"
|
|
||||||
|
|
||||||
#include <QBuffer>
|
|
||||||
#include <QFile>
|
|
||||||
#include <QIODevice>
|
#include <QIODevice>
|
||||||
|
#include <QString>
|
||||||
|
#include <QFile>
|
||||||
|
|
||||||
|
#include "format/KeePass2Writer.h"
|
||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
#include "core/Endian.h"
|
#include "format/Kdbx3Writer.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"
|
|
||||||
|
|
||||||
#define CHECK_RETURN(x) if (!(x)) return;
|
BaseKeePass2Writer::BaseKeePass2Writer() : m_error(false)
|
||||||
#define CHECK_RETURN_FALSE(x) if (!(x)) return false;
|
|
||||||
|
|
||||||
KeePass2Writer::KeePass2Writer()
|
|
||||||
: m_device(0)
|
|
||||||
, m_error(false)
|
|
||||||
{
|
{
|
||||||
}
|
|
||||||
|
|
||||||
void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
|
|
||||||
{
|
|
||||||
m_error = false;
|
|
||||||
m_errorStr.clear();
|
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)
|
BaseKeePass2Writer::~BaseKeePass2Writer() {}
|
||||||
{
|
|
||||||
if (m_device->write(data) != data.size()) {
|
|
||||||
raiseError(m_device->errorString());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool KeePass2Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data)
|
bool BaseKeePass2Writer::hasError()
|
||||||
{
|
|
||||||
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()
|
|
||||||
{
|
{
|
||||||
return m_error;
|
return m_error;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString KeePass2Writer::errorString()
|
QString BaseKeePass2Writer::errorString()
|
||||||
{
|
{
|
||||||
return m_errorStr;
|
return m_errorStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void KeePass2Writer::raiseError(const QString& errorMessage)
|
void BaseKeePass2Writer::raiseError(const QString& errorMessage)
|
||||||
{
|
{
|
||||||
m_error = true;
|
m_error = true;
|
||||||
m_errorStr = errorMessage;
|
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
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -18,33 +18,48 @@
|
|||||||
#ifndef KEEPASSX_KEEPASS2WRITER_H
|
#ifndef KEEPASSX_KEEPASS2WRITER_H
|
||||||
#define KEEPASSX_KEEPASS2WRITER_H
|
#define KEEPASSX_KEEPASS2WRITER_H
|
||||||
|
|
||||||
|
#include <QIODevice>
|
||||||
|
#include <QByteArray>
|
||||||
|
#include <QString>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
|
#include <QScopedPointer>
|
||||||
|
|
||||||
|
#include "core/Database.h"
|
||||||
#include "format/KeePass2.h"
|
#include "format/KeePass2.h"
|
||||||
#include "keys/CompositeKey.h"
|
|
||||||
|
|
||||||
class Database;
|
class BaseKeePass2Writer
|
||||||
class QIODevice;
|
|
||||||
|
|
||||||
class KeePass2Writer
|
|
||||||
{
|
{
|
||||||
Q_DECLARE_TR_FUNCTIONS(KeePass2Writer)
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
KeePass2Writer();
|
BaseKeePass2Writer();
|
||||||
void writeDatabase(QIODevice* device, Database* db);
|
|
||||||
void writeDatabase(const QString& filename, Database* db);
|
|
||||||
bool hasError();
|
|
||||||
QString errorString();
|
|
||||||
|
|
||||||
private:
|
virtual bool writeDatabase(QIODevice* device, Database* db) = 0;
|
||||||
bool writeData(const QByteArray& data);
|
virtual bool writeDatabase(const QString& filename, Database* db);
|
||||||
bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data);
|
|
||||||
|
virtual bool hasError();
|
||||||
|
virtual QString errorString();
|
||||||
|
|
||||||
|
virtual ~BaseKeePass2Writer();
|
||||||
|
|
||||||
|
protected:
|
||||||
void raiseError(const QString& errorMessage);
|
void raiseError(const QString& errorMessage);
|
||||||
|
|
||||||
QIODevice* m_device;
|
|
||||||
bool m_error;
|
bool m_error;
|
||||||
QString m_errorStr;
|
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
|
add_unit_test(NAME testgroup SOURCES TestGroup.cpp
|
||||||
LIBS ${TEST_LIBRARIES})
|
LIBS ${TEST_LIBRARIES})
|
||||||
|
|
||||||
add_unit_test(NAME testkeepass2xmlreader SOURCES TestKeePass2XmlReader.cpp
|
add_unit_test(NAME testkdbx3xmlreader SOURCES TestKeePass2XmlReader.cpp TestKdbx3XmlReader.cpp
|
||||||
LIBS ${TEST_LIBRARIES})
|
LIBS ${TEST_LIBRARIES})
|
||||||
|
|
||||||
add_unit_test(NAME testkeys SOURCES TestKeys.cpp
|
add_unit_test(NAME testkeys SOURCES TestKeys.cpp
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
#include "core/Database.h"
|
#include "core/Database.h"
|
||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
#include "crypto/Crypto.h"
|
#include "crypto/Crypto.h"
|
||||||
#include "format/KeePass2XmlReader.h"
|
#include "format/Kdbx3XmlReader.h"
|
||||||
#include "config-keepassx-tests.h"
|
#include "config-keepassx-tests.h"
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(TestDeletedObjects)
|
QTEST_GUILESS_MAIN(TestDeletedObjects)
|
||||||
@ -88,7 +88,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize)
|
|||||||
|
|
||||||
void TestDeletedObjects::testDeletedObjectsFromFile()
|
void TestDeletedObjects::testDeletedObjectsFromFile()
|
||||||
{
|
{
|
||||||
KeePass2XmlReader reader;
|
Kdbx3XmlReader reader;
|
||||||
reader.setStrictMode(true);
|
reader.setStrictMode(true);
|
||||||
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
|
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
|
||||||
Database* db = reader.readDatabase(xmlFile);
|
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/KeePass2Reader.h"
|
||||||
#include "format/KeePass2Repair.h"
|
#include "format/KeePass2Repair.h"
|
||||||
#include "format/KeePass2Writer.h"
|
#include "format/KeePass2Writer.h"
|
||||||
#include "format/KeePass2XmlWriter.h"
|
|
||||||
#include "keys/PasswordKey.h"
|
#include "keys/PasswordKey.h"
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(TestKeePass2Writer)
|
QTEST_GUILESS_MAIN(TestKeePass2Writer)
|
||||||
|
@ -25,12 +25,10 @@
|
|||||||
#include "core/Group.h"
|
#include "core/Group.h"
|
||||||
#include "core/Metadata.h"
|
#include "core/Metadata.h"
|
||||||
#include "crypto/Crypto.h"
|
#include "crypto/Crypto.h"
|
||||||
#include "format/KeePass2XmlReader.h"
|
#include "format/Kdbx3XmlReader.h"
|
||||||
#include "format/KeePass2XmlWriter.h"
|
#include "format/Kdbx3XmlWriter.h"
|
||||||
#include "config-keepassx-tests.h"
|
#include "config-keepassx-tests.h"
|
||||||
|
|
||||||
QTEST_GUILESS_MAIN(TestKeePass2XmlReader)
|
|
||||||
|
|
||||||
namespace QTest {
|
namespace QTest {
|
||||||
template<>
|
template<>
|
||||||
char* toString(const Uuid& uuid)
|
char* toString(const Uuid& uuid)
|
||||||
@ -79,11 +77,11 @@ QByteArray TestKeePass2XmlReader::strToBytes(const QString& str)
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestKeePass2XmlReader::initTestCase()
|
void TestKdbx3XmlReader::initTestCase()
|
||||||
{
|
{
|
||||||
QVERIFY(Crypto::init());
|
QVERIFY(Crypto::init());
|
||||||
|
|
||||||
KeePass2XmlReader reader;
|
Kdbx3XmlReader reader;
|
||||||
reader.setStrictMode(true);
|
reader.setStrictMode(true);
|
||||||
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
|
QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml");
|
||||||
m_db = reader.readDatabase(xmlFile);
|
m_db = reader.readDatabase(xmlFile);
|
||||||
@ -91,6 +89,32 @@ void TestKeePass2XmlReader::initTestCase()
|
|||||||
QVERIFY(!reader.hasError());
|
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()
|
void TestKeePass2XmlReader::testMetadata()
|
||||||
{
|
{
|
||||||
QCOMPARE(m_db->metadata()->generator(), QString("KeePass"));
|
QCOMPARE(m_db->metadata()->generator(), QString("KeePass"));
|
||||||
@ -374,15 +398,20 @@ void TestKeePass2XmlReader::testBroken()
|
|||||||
QFETCH(bool, strictMode);
|
QFETCH(bool, strictMode);
|
||||||
QFETCH(bool, expectError);
|
QFETCH(bool, expectError);
|
||||||
|
|
||||||
KeePass2XmlReader reader;
|
|
||||||
reader.setStrictMode(strictMode);
|
|
||||||
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName);
|
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName);
|
||||||
QVERIFY(QFile::exists(xmlFile));
|
QVERIFY(QFile::exists(xmlFile));
|
||||||
QScopedPointer<Database> db(reader.readDatabase(xmlFile));
|
bool hasError;
|
||||||
if (reader.hasError()) {
|
QString errorString;
|
||||||
qWarning("Reader error: %s", qPrintable(reader.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()
|
void TestKeePass2XmlReader::testBroken_data()
|
||||||
@ -412,15 +441,20 @@ void TestKeePass2XmlReader::testBroken_data()
|
|||||||
|
|
||||||
void TestKeePass2XmlReader::testEmptyUuids()
|
void TestKeePass2XmlReader::testEmptyUuids()
|
||||||
{
|
{
|
||||||
KeePass2XmlReader reader;
|
|
||||||
reader.setStrictMode(true);
|
|
||||||
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "EmptyUuids");
|
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "EmptyUuids");
|
||||||
QVERIFY(QFile::exists(xmlFile));
|
QVERIFY(QFile::exists(xmlFile));
|
||||||
QScopedPointer<Database> db(reader.readDatabase(xmlFile));
|
Database* dbp;
|
||||||
if (reader.hasError()) {
|
bool hasError;
|
||||||
qWarning("Reader error: %s", qPrintable(reader.errorString()));
|
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()
|
void TestKeePass2XmlReader::testInvalidXmlChars()
|
||||||
@ -459,19 +493,19 @@ void TestKeePass2XmlReader::testInvalidXmlChars()
|
|||||||
|
|
||||||
QBuffer buffer;
|
QBuffer buffer;
|
||||||
buffer.open(QIODevice::ReadWrite);
|
buffer.open(QIODevice::ReadWrite);
|
||||||
KeePass2XmlWriter writer;
|
bool hasError;
|
||||||
writer.writeDatabase(&buffer, dbWrite.data());
|
QString errorString;
|
||||||
QVERIFY(!writer.hasError());
|
writeDatabase(&buffer, dbWrite.data(), hasError, errorString);
|
||||||
|
QVERIFY(!hasError);
|
||||||
buffer.seek(0);
|
buffer.seek(0);
|
||||||
|
|
||||||
KeePass2XmlReader reader;
|
Database* dbRead;
|
||||||
reader.setStrictMode(true);
|
readDatabase(&buffer, true, dbRead, hasError, errorString);
|
||||||
QScopedPointer<Database> dbRead(reader.readDatabase(&buffer));
|
if (hasError) {
|
||||||
if (reader.hasError()) {
|
qWarning("Database read error: %s", qPrintable(errorString));
|
||||||
qWarning("Database read error: %s", qPrintable(reader.errorString()));
|
|
||||||
}
|
}
|
||||||
QVERIFY(!reader.hasError());
|
QVERIFY(!hasError);
|
||||||
QVERIFY(!dbRead.isNull());
|
QVERIFY(dbRead);
|
||||||
QCOMPARE(dbRead->rootGroup()->entries().size(), 1);
|
QCOMPARE(dbRead->rootGroup()->entries().size(), 1);
|
||||||
Entry* entryRead = dbRead->rootGroup()->entries().at(0);
|
Entry* entryRead = dbRead->rootGroup()->entries().at(0);
|
||||||
EntryAttributes* attrRead = entryRead->attributes();
|
EntryAttributes* attrRead = entryRead->attributes();
|
||||||
@ -486,22 +520,28 @@ void TestKeePass2XmlReader::testInvalidXmlChars()
|
|||||||
QCOMPARE(strToBytes(attrRead->value("LowLowSurrogate")), QByteArray());
|
QCOMPARE(strToBytes(attrRead->value("LowLowSurrogate")), QByteArray());
|
||||||
QCOMPARE(strToBytes(attrRead->value("SurrogateValid1")), strToBytes(strSurrogateValid1));
|
QCOMPARE(strToBytes(attrRead->value("SurrogateValid1")), strToBytes(strSurrogateValid1));
|
||||||
QCOMPARE(strToBytes(attrRead->value("SurrogateValid2")), strToBytes(strSurrogateValid2));
|
QCOMPARE(strToBytes(attrRead->value("SurrogateValid2")), strToBytes(strSurrogateValid2));
|
||||||
|
|
||||||
|
if (dbRead) {
|
||||||
|
delete dbRead;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestKeePass2XmlReader::testRepairUuidHistoryItem()
|
void TestKeePass2XmlReader::testRepairUuidHistoryItem()
|
||||||
{
|
{
|
||||||
KeePass2XmlReader reader;
|
|
||||||
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid");
|
QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid");
|
||||||
QVERIFY(QFile::exists(xmlFile));
|
QVERIFY(QFile::exists(xmlFile));
|
||||||
QScopedPointer<Database> db(reader.readDatabase(xmlFile));
|
Database* db;
|
||||||
if (reader.hasError()) {
|
bool hasError;
|
||||||
qWarning("Database read error: %s", qPrintable(reader.errorString()));
|
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);
|
QCOMPARE(entries.size(), 1);
|
||||||
Entry* entry = entries.at(0);
|
Entry* entry = entries.at(0);
|
||||||
|
|
||||||
@ -512,6 +552,10 @@ void TestKeePass2XmlReader::testRepairUuidHistoryItem()
|
|||||||
QVERIFY(!entry->uuid().isNull());
|
QVERIFY(!entry->uuid().isNull());
|
||||||
QVERIFY(!historyItem->uuid().isNull());
|
QVERIFY(!historyItem->uuid().isNull());
|
||||||
QCOMPARE(historyItem->uuid(), entry->uuid());
|
QCOMPARE(historyItem->uuid(), entry->uuid());
|
||||||
|
|
||||||
|
if (db) {
|
||||||
|
delete db;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void TestKeePass2XmlReader::cleanupTestCase()
|
void TestKeePass2XmlReader::cleanupTestCase()
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
|
#include <QBuffer>
|
||||||
|
|
||||||
class Database;
|
class Database;
|
||||||
|
|
||||||
@ -27,8 +28,8 @@ class TestKeePass2XmlReader : public QObject
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
private slots:
|
protected slots:
|
||||||
void initTestCase();
|
virtual void initTestCase() = 0;
|
||||||
void testMetadata();
|
void testMetadata();
|
||||||
void testCustomIcons();
|
void testCustomIcons();
|
||||||
void testCustomData();
|
void testCustomData();
|
||||||
@ -46,11 +47,27 @@ private slots:
|
|||||||
void testRepairUuidHistoryItem();
|
void testRepairUuidHistoryItem();
|
||||||
void cleanupTestCase();
|
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 QDateTime genDT(int year, int month, int day, int hour, int min, int second);
|
||||||
static QByteArray strToBytes(const QString& str);
|
static QByteArray strToBytes(const QString& str);
|
||||||
|
|
||||||
Database* m_db;
|
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
|
#endif // KEEPASSX_TESTKEEPASS2XMLREADER_H
|
||||||
|
Loading…
x
Reference in New Issue
Block a user