From 3461cbfb060012dbdaaea5af418eccbeb0fedc04 Mon Sep 17 00:00:00 2001 From: angelsl Date: Mon, 13 Nov 2017 02:55:03 +0800 Subject: [PATCH] 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. --- src/CMakeLists.txt | 8 +- src/format/Kdbx3Reader.cpp | 394 ++++++++++++++++ src/format/Kdbx3Reader.h | 62 +++ src/format/Kdbx3Writer.cpp | 187 ++++++++ src/format/Kdbx3Writer.h | 47 ++ ...ePass2XmlReader.cpp => Kdbx3XmlReader.cpp} | 92 ++-- .../{KeePass2XmlReader.h => Kdbx3XmlReader.h} | 12 +- ...ePass2XmlWriter.cpp => Kdbx3XmlWriter.cpp} | 72 +-- .../{KeePass2XmlWriter.h => Kdbx3XmlWriter.h} | 10 +- src/format/KeePass2Reader.cpp | 437 +++--------------- src/format/KeePass2Reader.h | 83 ++-- src/format/KeePass2Repair.cpp | 5 +- src/format/KeePass2Writer.cpp | 209 ++------- src/format/KeePass2Writer.h | 51 +- tests/CMakeLists.txt | 2 +- tests/TestDeletedObjects.cpp | 4 +- tests/TestKdbx3XmlReader.cpp | 22 + tests/TestKeePass2Writer.cpp | 1 - tests/TestKeePass2XmlReader.cpp | 112 +++-- tests/TestKeePass2XmlReader.h | 23 +- 20 files changed, 1099 insertions(+), 734 deletions(-) create mode 100644 src/format/Kdbx3Reader.cpp create mode 100644 src/format/Kdbx3Reader.h create mode 100644 src/format/Kdbx3Writer.cpp create mode 100644 src/format/Kdbx3Writer.h rename src/format/{KeePass2XmlReader.cpp => Kdbx3XmlReader.cpp} (92%) rename src/format/{KeePass2XmlReader.h => Kdbx3XmlReader.h} (92%) rename src/format/{KeePass2XmlWriter.cpp => Kdbx3XmlWriter.cpp} (86%) rename src/format/{KeePass2XmlWriter.h => Kdbx3XmlWriter.h} (94%) create mode 100644 tests/TestKdbx3XmlReader.cpp diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 2e10cdcf4..35ea97817 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -84,11 +84,13 @@ set(keepassx_SOURCES format/KeePass1Reader.cpp format/KeePass2.cpp format/KeePass2RandomStream.cpp - format/KeePass2Reader.cpp format/KeePass2Repair.cpp + format/KeePass2Reader.cpp format/KeePass2Writer.cpp - format/KeePass2XmlReader.cpp - format/KeePass2XmlWriter.cpp + format/Kdbx3Reader.cpp + format/Kdbx3Writer.cpp + format/Kdbx3XmlReader.cpp + format/Kdbx3XmlWriter.cpp gui/AboutDialog.cpp gui/Application.cpp gui/CategoryListWidget.cpp diff --git a/src/format/Kdbx3Reader.cpp b/src/format/Kdbx3Reader.cpp new file mode 100644 index 000000000..fc3dcbd64 --- /dev/null +++ b/src/format/Kdbx3Reader.cpp @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * 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 . + */ + +#include "Kdbx3Reader.h" + +#include +#include +#include + +#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 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 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 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(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(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(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; + } + } +} diff --git a/src/format/Kdbx3Reader.h b/src/format/Kdbx3Reader.h new file mode 100644 index 000000000..1083b4bb4 --- /dev/null +++ b/src/format/Kdbx3Reader.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * 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 . + */ + +#ifndef KEEPASSX_KDBX3READER_H +#define KEEPASSX_KDBX3READER_H + +#include + +#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 diff --git a/src/format/Kdbx3Writer.cpp b/src/format/Kdbx3Writer.cpp new file mode 100644 index 000000000..d07ff9663 --- /dev/null +++ b/src/format/Kdbx3Writer.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * 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 . + */ + +#include "Kdbx3Writer.h" + +#include +#include +#include + +#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(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 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(data.size()), + KeePass2::BYTEORDER))); + CHECK_RETURN_FALSE(writeData(data)); + + return true; +} diff --git a/src/format/Kdbx3Writer.h b/src/format/Kdbx3Writer.h new file mode 100644 index 000000000..6b5b8f28f --- /dev/null +++ b/src/format/Kdbx3Writer.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2010 Felix Geyer + * + * 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 . + */ + +#ifndef KEEPASSX_KDBX3WRITER_H +#define KEEPASSX_KDBX3WRITER_H + +#include + +#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 diff --git a/src/format/KeePass2XmlReader.cpp b/src/format/Kdbx3XmlReader.cpp similarity index 92% rename from src/format/KeePass2XmlReader.cpp rename to src/format/Kdbx3XmlReader.cpp index de7ca6d79..6f3745553 100644 --- a/src/format/KeePass2XmlReader.cpp +++ b/src/format/Kdbx3XmlReader.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -#include "KeePass2XmlReader.h" +#include "Kdbx3XmlReader.h" #include #include @@ -30,7 +30,7 @@ typedef QPair StringPair; -KeePass2XmlReader::KeePass2XmlReader() +Kdbx3XmlReader::Kdbx3XmlReader() : m_randomStream(nullptr) , m_db(nullptr) , m_meta(nullptr) @@ -40,12 +40,12 @@ KeePass2XmlReader::KeePass2XmlReader() { } -void KeePass2XmlReader::setStrictMode(bool strictMode) +void Kdbx3XmlReader::setStrictMode(bool strictMode) { m_strictMode = strictMode; } -void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) +void Kdbx3XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream) { m_error = false; m_errorStr.clear(); @@ -76,12 +76,12 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra if (!m_xml.error()) { if (!m_tmpParent->children().isEmpty()) { - qWarning("KeePass2XmlReader::readDatabase: found %d invalid group reference(s)", + qWarning("Kdbx3XmlReader::readDatabase: found %d invalid group reference(s)", m_tmpParent->children().size()); } if (!m_tmpParent->entries().isEmpty()) { - qWarning("KeePass2XmlReader::readDatabase: found %d invalid entry reference(s)", + qWarning("Kdbx3XmlReader::readDatabase: found %d invalid entry reference(s)", m_tmpParent->children().size()); } } @@ -97,7 +97,7 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra if (!m_xml.error()) { for (const QString& key : unusedKeys) { - qWarning("KeePass2XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); + qWarning("Kdbx3XmlReader::readDatabase: found unused key \"%s\"", qPrintable(key)); } } @@ -127,26 +127,26 @@ void KeePass2XmlReader::readDatabase(QIODevice* device, Database* db, KeePass2Ra delete m_tmpParent; } -Database* KeePass2XmlReader::readDatabase(QIODevice* device) +Database* Kdbx3XmlReader::readDatabase(QIODevice* device) { Database* db = new Database(); readDatabase(device, db); return db; } -Database* KeePass2XmlReader::readDatabase(const QString& filename) +Database* Kdbx3XmlReader::readDatabase(const QString& filename) { QFile file(filename); file.open(QIODevice::ReadOnly); return readDatabase(&file); } -bool KeePass2XmlReader::hasError() +bool Kdbx3XmlReader::hasError() { return m_error || m_xml.hasError(); } -QString KeePass2XmlReader::errorString() +QString Kdbx3XmlReader::errorString() { if (m_error) { return m_errorStr; @@ -162,18 +162,18 @@ QString KeePass2XmlReader::errorString() } } -void KeePass2XmlReader::raiseError(const QString& errorMessage) +void Kdbx3XmlReader::raiseError(const QString& errorMessage) { m_error = true; m_errorStr = errorMessage; } -QByteArray KeePass2XmlReader::headerHash() +QByteArray Kdbx3XmlReader::headerHash() { return m_headerHash; } -bool KeePass2XmlReader::parseKeePassFile() +bool Kdbx3XmlReader::parseKeePassFile() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "KeePassFile"); @@ -202,7 +202,7 @@ bool KeePass2XmlReader::parseKeePassFile() return rootParsedSuccessfully; } -void KeePass2XmlReader::parseMeta() +void Kdbx3XmlReader::parseMeta() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Meta"); @@ -303,7 +303,7 @@ void KeePass2XmlReader::parseMeta() } } -void KeePass2XmlReader::parseMemoryProtection() +void Kdbx3XmlReader::parseMemoryProtection() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "MemoryProtection"); @@ -329,7 +329,7 @@ void KeePass2XmlReader::parseMemoryProtection() } } -void KeePass2XmlReader::parseCustomIcons() +void Kdbx3XmlReader::parseCustomIcons() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomIcons"); @@ -343,7 +343,7 @@ void KeePass2XmlReader::parseCustomIcons() } } -void KeePass2XmlReader::parseIcon() +void Kdbx3XmlReader::parseIcon() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Icon"); @@ -374,7 +374,7 @@ void KeePass2XmlReader::parseIcon() } } -void KeePass2XmlReader::parseBinaries() +void Kdbx3XmlReader::parseBinaries() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binaries"); @@ -393,7 +393,7 @@ void KeePass2XmlReader::parseBinaries() } if (m_binaryPool.contains(id)) { - qWarning("KeePass2XmlReader::parseBinaries: overwriting binary item \"%s\"", + qWarning("Kdbx3XmlReader::parseBinaries: overwriting binary item \"%s\"", qPrintable(id)); } @@ -405,7 +405,7 @@ void KeePass2XmlReader::parseBinaries() } } -void KeePass2XmlReader::parseCustomData() +void Kdbx3XmlReader::parseCustomData() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "CustomData"); @@ -419,7 +419,7 @@ void KeePass2XmlReader::parseCustomData() } } -void KeePass2XmlReader::parseCustomDataItem() +void Kdbx3XmlReader::parseCustomDataItem() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Item"); @@ -450,7 +450,7 @@ void KeePass2XmlReader::parseCustomDataItem() } } -bool KeePass2XmlReader::parseRoot() +bool Kdbx3XmlReader::parseRoot() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Root"); @@ -486,7 +486,7 @@ bool KeePass2XmlReader::parseRoot() return groupParsedSuccessfully; } -Group* KeePass2XmlReader::parseGroup() +Group* Kdbx3XmlReader::parseGroup() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Group"); @@ -525,7 +525,7 @@ Group* KeePass2XmlReader::parseGroup() } else { if (iconId >= DatabaseIcons::IconCount) { - qWarning("KeePass2XmlReader::parseGroup: icon id \"%d\" not supported", iconId); + qWarning("Kdbx3XmlReader::parseGroup: icon id \"%d\" not supported", iconId); } group->setIcon(iconId); } @@ -623,7 +623,7 @@ Group* KeePass2XmlReader::parseGroup() return group; } -void KeePass2XmlReader::parseDeletedObjects() +void Kdbx3XmlReader::parseDeletedObjects() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObjects"); @@ -637,7 +637,7 @@ void KeePass2XmlReader::parseDeletedObjects() } } -void KeePass2XmlReader::parseDeletedObject() +void Kdbx3XmlReader::parseDeletedObject() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "DeletedObject"); @@ -671,7 +671,7 @@ void KeePass2XmlReader::parseDeletedObject() } } -Entry* KeePass2XmlReader::parseEntry(bool history) +Entry* Kdbx3XmlReader::parseEntry(bool history) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Entry"); @@ -793,7 +793,7 @@ Entry* KeePass2XmlReader::parseEntry(bool history) return entry; } -void KeePass2XmlReader::parseEntryString(Entry* entry) +void Kdbx3XmlReader::parseEntryString(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "String"); @@ -855,7 +855,7 @@ void KeePass2XmlReader::parseEntryString(Entry* entry) } } -QPair KeePass2XmlReader::parseEntryBinary(Entry* entry) +QPair Kdbx3XmlReader::parseEntryBinary(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Binary"); @@ -913,7 +913,7 @@ QPair KeePass2XmlReader::parseEntryBinary(Entry* entry) return poolRef; } -void KeePass2XmlReader::parseAutoType(Entry* entry) +void Kdbx3XmlReader::parseAutoType(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "AutoType"); @@ -936,7 +936,7 @@ void KeePass2XmlReader::parseAutoType(Entry* entry) } } -void KeePass2XmlReader::parseAutoTypeAssoc(Entry* entry) +void Kdbx3XmlReader::parseAutoTypeAssoc(Entry* entry) { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Association"); @@ -966,7 +966,7 @@ void KeePass2XmlReader::parseAutoTypeAssoc(Entry* entry) } } -QList KeePass2XmlReader::parseEntryHistory() +QList Kdbx3XmlReader::parseEntryHistory() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "History"); @@ -984,7 +984,7 @@ QList KeePass2XmlReader::parseEntryHistory() return historyItems; } -TimeInfo KeePass2XmlReader::parseTimes() +TimeInfo Kdbx3XmlReader::parseTimes() { Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "Times"); @@ -1019,12 +1019,12 @@ TimeInfo KeePass2XmlReader::parseTimes() return timeInfo; } -QString KeePass2XmlReader::readString() +QString Kdbx3XmlReader::readString() { return m_xml.readElementText(); } -bool KeePass2XmlReader::readBool() +bool Kdbx3XmlReader::readBool() { QString str = readString(); @@ -1043,7 +1043,7 @@ bool KeePass2XmlReader::readBool() } } -QDateTime KeePass2XmlReader::readDateTime() +QDateTime Kdbx3XmlReader::readDateTime() { QString str = readString(); QDateTime dt = QDateTime::fromString(str, Qt::ISODate); @@ -1060,7 +1060,7 @@ QDateTime KeePass2XmlReader::readDateTime() return dt; } -QColor KeePass2XmlReader::readColor() +QColor Kdbx3XmlReader::readColor() { QString colorStr = readString(); @@ -1101,7 +1101,7 @@ QColor KeePass2XmlReader::readColor() return color; } -int KeePass2XmlReader::readNumber() +int Kdbx3XmlReader::readNumber() { bool ok; int result = readString().toInt(&ok); @@ -1111,7 +1111,7 @@ int KeePass2XmlReader::readNumber() return result; } -Uuid KeePass2XmlReader::readUuid() +Uuid Kdbx3XmlReader::readUuid() { QByteArray uuidBin = readBinary(); if (uuidBin.isEmpty()) { @@ -1128,12 +1128,12 @@ Uuid KeePass2XmlReader::readUuid() } } -QByteArray KeePass2XmlReader::readBinary() +QByteArray Kdbx3XmlReader::readBinary() { return QByteArray::fromBase64(readString().toLatin1()); } -QByteArray KeePass2XmlReader::readCompressedBinary() +QByteArray Kdbx3XmlReader::readCompressedBinary() { QByteArray rawData = readBinary(); @@ -1151,7 +1151,7 @@ QByteArray KeePass2XmlReader::readCompressedBinary() return result; } -Group* KeePass2XmlReader::getGroup(const Uuid& uuid) +Group* Kdbx3XmlReader::getGroup(const Uuid& uuid) { if (uuid.isNull()) { return nullptr; @@ -1170,7 +1170,7 @@ Group* KeePass2XmlReader::getGroup(const Uuid& uuid) } } -Entry* KeePass2XmlReader::getEntry(const Uuid& uuid) +Entry* Kdbx3XmlReader::getEntry(const Uuid& uuid) { if (uuid.isNull()) { return nullptr; @@ -1189,8 +1189,8 @@ Entry* KeePass2XmlReader::getEntry(const Uuid& uuid) } } -void KeePass2XmlReader::skipCurrentElement() +void Kdbx3XmlReader::skipCurrentElement() { - qWarning("KeePass2XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString())); + qWarning("Kdbx3XmlReader::skipCurrentElement: skip element \"%s\"", qPrintable(m_xml.name().toString())); m_xml.skipCurrentElement(); } diff --git a/src/format/KeePass2XmlReader.h b/src/format/Kdbx3XmlReader.h similarity index 92% rename from src/format/KeePass2XmlReader.h rename to src/format/Kdbx3XmlReader.h index d2e0e0025..1837aaab6 100644 --- a/src/format/KeePass2XmlReader.h +++ b/src/format/Kdbx3XmlReader.h @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#ifndef KEEPASSX_KEEPASS2XMLREADER_H -#define KEEPASSX_KEEPASS2XMLREADER_H +#ifndef KEEPASSX_KDBX3XMLREADER_H +#define KEEPASSX_KDBX3XMLREADER_H #include #include @@ -34,12 +34,12 @@ class Group; class KeePass2RandomStream; class Metadata; -class KeePass2XmlReader +class Kdbx3XmlReader { - Q_DECLARE_TR_FUNCTIONS(KeePass2XmlReader) + Q_DECLARE_TR_FUNCTIONS(Kdbx3XmlReader) public: - KeePass2XmlReader(); + Kdbx3XmlReader(); Database* readDatabase(QIODevice* device); void readDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr); Database* readDatabase(const QString& filename); @@ -98,4 +98,4 @@ private: bool m_strictMode; }; -#endif // KEEPASSX_KEEPASS2XMLREADER_H +#endif // KEEPASSX_KDBX3XMLREADER_H diff --git a/src/format/KeePass2XmlWriter.cpp b/src/format/Kdbx3XmlWriter.cpp similarity index 86% rename from src/format/KeePass2XmlWriter.cpp rename to src/format/Kdbx3XmlWriter.cpp index fa67ece93..ff9262a01 100644 --- a/src/format/KeePass2XmlWriter.cpp +++ b/src/format/Kdbx3XmlWriter.cpp @@ -15,7 +15,7 @@ * along with this program. If not, see . */ -#include "KeePass2XmlWriter.h" +#include "Kdbx3XmlWriter.h" #include #include @@ -24,7 +24,7 @@ #include "format/KeePass2RandomStream.h" #include "streams/QtIOCompressor" -KeePass2XmlWriter::KeePass2XmlWriter() +Kdbx3XmlWriter::Kdbx3XmlWriter() : m_db(nullptr) , m_meta(nullptr) , m_randomStream(nullptr) @@ -35,7 +35,7 @@ KeePass2XmlWriter::KeePass2XmlWriter() m_xml.setCodec("UTF-8"); } -void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, +void Kdbx3XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream, const QByteArray& headerHash) { m_db = db; @@ -63,24 +63,24 @@ void KeePass2XmlWriter::writeDatabase(QIODevice* device, Database* db, KeePass2R } } -void KeePass2XmlWriter::writeDatabase(const QString& filename, Database* db) +void Kdbx3XmlWriter::writeDatabase(const QString& filename, Database* db) { QFile file(filename); file.open(QIODevice::WriteOnly|QIODevice::Truncate); writeDatabase(&file, db); } -bool KeePass2XmlWriter::hasError() +bool Kdbx3XmlWriter::hasError() { return m_error; } -QString KeePass2XmlWriter::errorString() +QString Kdbx3XmlWriter::errorString() { return m_errorStr; } -void KeePass2XmlWriter::generateIdMap() +void Kdbx3XmlWriter::generateIdMap() { const QList allEntries = m_db->rootGroup()->entriesRecursive(true); int nextId = 0; @@ -96,7 +96,7 @@ void KeePass2XmlWriter::generateIdMap() } } -void KeePass2XmlWriter::writeMetadata() +void Kdbx3XmlWriter::writeMetadata() { m_xml.writeStartElement("Meta"); @@ -132,7 +132,7 @@ void KeePass2XmlWriter::writeMetadata() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeMemoryProtection() +void Kdbx3XmlWriter::writeMemoryProtection() { m_xml.writeStartElement("MemoryProtection"); @@ -145,7 +145,7 @@ void KeePass2XmlWriter::writeMemoryProtection() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeCustomIcons() +void Kdbx3XmlWriter::writeCustomIcons() { m_xml.writeStartElement("CustomIcons"); @@ -157,7 +157,7 @@ void KeePass2XmlWriter::writeCustomIcons() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) +void Kdbx3XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) { m_xml.writeStartElement("Icon"); @@ -174,7 +174,7 @@ void KeePass2XmlWriter::writeIcon(const Uuid& uuid, const QImage& icon) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeBinaries() +void Kdbx3XmlWriter::writeBinaries() { m_xml.writeStartElement("Binaries"); @@ -216,7 +216,7 @@ void KeePass2XmlWriter::writeBinaries() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeCustomData() +void Kdbx3XmlWriter::writeCustomData() { m_xml.writeStartElement("CustomData"); @@ -229,7 +229,7 @@ void KeePass2XmlWriter::writeCustomData() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeCustomDataItem(const QString& key, const QString& value) +void Kdbx3XmlWriter::writeCustomDataItem(const QString& key, const QString& value) { m_xml.writeStartElement("Item"); @@ -239,7 +239,7 @@ void KeePass2XmlWriter::writeCustomDataItem(const QString& key, const QString& v m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeRoot() +void Kdbx3XmlWriter::writeRoot() { Q_ASSERT(m_db->rootGroup()); @@ -251,7 +251,7 @@ void KeePass2XmlWriter::writeRoot() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeGroup(const Group* group) +void Kdbx3XmlWriter::writeGroup(const Group* group) { Q_ASSERT(!group->uuid().isNull()); @@ -288,7 +288,7 @@ void KeePass2XmlWriter::writeGroup(const Group* group) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeTimes(const TimeInfo& ti) +void Kdbx3XmlWriter::writeTimes(const TimeInfo& ti) { m_xml.writeStartElement("Times"); @@ -303,7 +303,7 @@ void KeePass2XmlWriter::writeTimes(const TimeInfo& ti) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeDeletedObjects() +void Kdbx3XmlWriter::writeDeletedObjects() { m_xml.writeStartElement("DeletedObjects"); @@ -315,7 +315,7 @@ void KeePass2XmlWriter::writeDeletedObjects() m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeDeletedObject(const DeletedObject& delObj) +void Kdbx3XmlWriter::writeDeletedObject(const DeletedObject& delObj) { m_xml.writeStartElement("DeletedObject"); @@ -325,7 +325,7 @@ void KeePass2XmlWriter::writeDeletedObject(const DeletedObject& delObj) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeEntry(const Entry* entry) +void Kdbx3XmlWriter::writeEntry(const Entry* entry) { Q_ASSERT(!entry->uuid().isNull()); @@ -407,7 +407,7 @@ void KeePass2XmlWriter::writeEntry(const Entry* entry) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeAutoType(const Entry* entry) +void Kdbx3XmlWriter::writeAutoType(const Entry* entry) { m_xml.writeStartElement("AutoType"); @@ -423,7 +423,7 @@ void KeePass2XmlWriter::writeAutoType(const Entry* entry) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc) +void Kdbx3XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Association& assoc) { m_xml.writeStartElement("Association"); @@ -433,7 +433,7 @@ void KeePass2XmlWriter::writeAutoTypeAssoc(const AutoTypeAssociations::Associati m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeEntryHistory(const Entry* entry) +void Kdbx3XmlWriter::writeEntryHistory(const Entry* entry) { m_xml.writeStartElement("History"); @@ -445,7 +445,7 @@ void KeePass2XmlWriter::writeEntryHistory(const Entry* entry) m_xml.writeEndElement(); } -void KeePass2XmlWriter::writeString(const QString& qualifiedName, const QString& string) +void Kdbx3XmlWriter::writeString(const QString& qualifiedName, const QString& string) { if (string.isEmpty()) { m_xml.writeEmptyElement(qualifiedName); @@ -455,12 +455,12 @@ void KeePass2XmlWriter::writeString(const QString& qualifiedName, const QString& } } -void KeePass2XmlWriter::writeNumber(const QString& qualifiedName, int number) +void Kdbx3XmlWriter::writeNumber(const QString& qualifiedName, int number) { writeString(qualifiedName, QString::number(number)); } -void KeePass2XmlWriter::writeBool(const QString& qualifiedName, bool b) +void Kdbx3XmlWriter::writeBool(const QString& qualifiedName, bool b) { if (b) { writeString(qualifiedName, "True"); @@ -470,7 +470,7 @@ void KeePass2XmlWriter::writeBool(const QString& qualifiedName, bool b) } } -void KeePass2XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime) +void Kdbx3XmlWriter::writeDateTime(const QString& qualifiedName, const QDateTime& dateTime) { Q_ASSERT(dateTime.isValid()); Q_ASSERT(dateTime.timeSpec() == Qt::UTC); @@ -485,12 +485,12 @@ void KeePass2XmlWriter::writeDateTime(const QString& qualifiedName, const QDateT writeString(qualifiedName, dateTimeStr); } -void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid) +void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Uuid& uuid) { writeString(qualifiedName, uuid.toBase64()); } -void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) +void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Group* group) { if (group) { writeUuid(qualifiedName, group->uuid()); @@ -500,7 +500,7 @@ void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Group* gro } } -void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) +void Kdbx3XmlWriter::writeUuid(const QString& qualifiedName, const Entry* entry) { if (entry) { writeUuid(qualifiedName, entry->uuid()); @@ -510,12 +510,12 @@ void KeePass2XmlWriter::writeUuid(const QString& qualifiedName, const Entry* ent } } -void KeePass2XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba) +void Kdbx3XmlWriter::writeBinary(const QString& qualifiedName, const QByteArray& ba) { writeString(qualifiedName, QString::fromLatin1(ba.toBase64())); } -void KeePass2XmlWriter::writeColor(const QString& qualifiedName, const QColor& color) +void Kdbx3XmlWriter::writeColor(const QString& qualifiedName, const QColor& color) { QString colorStr; @@ -528,7 +528,7 @@ void KeePass2XmlWriter::writeColor(const QString& qualifiedName, const QColor& c writeString(qualifiedName, colorStr); } -void KeePass2XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState) +void Kdbx3XmlWriter::writeTriState(const QString& qualifiedName, Group::TriState triState) { QString value; @@ -545,7 +545,7 @@ void KeePass2XmlWriter::writeTriState(const QString& qualifiedName, Group::TriSt writeString(qualifiedName, value); } -QString KeePass2XmlWriter::colorPartToString(int value) +QString Kdbx3XmlWriter::colorPartToString(int value) { QString str = QString::number(value, 16).toUpper(); if (str.length() == 1) { @@ -555,7 +555,7 @@ QString KeePass2XmlWriter::colorPartToString(int value) return str; } -QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str) +QString Kdbx3XmlWriter::stripInvalidXml10Chars(QString str) { for (int i = str.size() - 1; i >= 0; i--) { const QChar ch = str.at(i); @@ -580,7 +580,7 @@ QString KeePass2XmlWriter::stripInvalidXml10Chars(QString str) return str; } -void KeePass2XmlWriter::raiseError(const QString& errorMessage) +void Kdbx3XmlWriter::raiseError(const QString& errorMessage) { m_error = true; m_errorStr = errorMessage; diff --git a/src/format/KeePass2XmlWriter.h b/src/format/Kdbx3XmlWriter.h similarity index 94% rename from src/format/KeePass2XmlWriter.h rename to src/format/Kdbx3XmlWriter.h index 23e148dbb..2bb130566 100644 --- a/src/format/KeePass2XmlWriter.h +++ b/src/format/Kdbx3XmlWriter.h @@ -15,8 +15,8 @@ * along with this program. If not, see . */ -#ifndef KEEPASSX_KEEPASS2XMLWRITER_H -#define KEEPASSX_KEEPASS2XMLWRITER_H +#ifndef KEEPASSX_KDBX3XMLWRITER_H +#define KEEPASSX_KDBX3XMLWRITER_H #include #include @@ -32,10 +32,10 @@ class KeePass2RandomStream; class Metadata; -class KeePass2XmlWriter +class Kdbx3XmlWriter { public: - KeePass2XmlWriter(); + Kdbx3XmlWriter(); void writeDatabase(QIODevice* device, Database* db, KeePass2RandomStream* randomStream = nullptr, const QByteArray& headerHash = QByteArray()); void writeDatabase(const QString& filename, Database* db); @@ -87,4 +87,4 @@ private: QString m_errorStr; }; -#endif // KEEPASSX_KEEPASS2XMLWRITER_H +#endif // KEEPASSX_KDBX3XMLWRITER_H diff --git a/src/format/KeePass2Reader.cpp b/src/format/KeePass2Reader.cpp index f42e7b9bb..a09f0adce 100644 --- a/src/format/KeePass2Reader.cpp +++ b/src/format/KeePass2Reader.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,198 +15,30 @@ * along with this program. If not, see . */ -#include "KeePass2Reader.h" - -#include +#include +#include #include -#include -#include "core/Database.h" #include "core/Endian.h" -#include "crypto/CryptoHash.h" -#include "crypto/kdf/AesKdf.h" +#include "keys/CompositeKey.h" +#include "format/KeePass2Reader.h" #include "format/KeePass1.h" #include "format/KeePass2.h" -#include "format/KeePass2RandomStream.h" -#include "format/KeePass2XmlReader.h" -#include "streams/HashedBlockStream.h" -#include "streams/QtIOCompressor" -#include "streams/StoreDataStream.h" -#include "streams/SymmetricCipherStream.h" +#include "format/Kdbx3Reader.h" -KeePass2Reader::KeePass2Reader() - : m_device(nullptr) - , m_headerStream(nullptr) - , m_error(false) - , m_headerEnd(false) - , m_saveXml(false) - , m_db(nullptr) - , m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo) +BaseKeePass2Reader::BaseKeePass2Reader() : + m_error(false), + m_saveXml(false), + m_irsAlgo(KeePass2::InvalidProtectedStreamAlgo) { -} - -Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) -{ - QScopedPointer db(new Database()); - m_db = db.data(); - m_device = device; - m_error = false; m_errorStr.clear(); - m_headerEnd = false; m_xmlData.clear(); - m_masterSeed.clear(); - m_encryptionIV.clear(); - m_streamStartBytes.clear(); m_protectedStreamKey.clear(); - - StoreDataStream headerStream(m_device); - headerStream.open(QIODevice::ReadOnly); - m_headerStream = &headerStream; - - bool ok; - - quint32 signature1 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); - if (!ok || signature1 != KeePass2::SIGNATURE_1) { - raiseError(tr("Not a KeePass database.")); - return nullptr; - } - - quint32 signature2 = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok); - if (ok && signature2 == KeePass1::SIGNATURE_2) { - raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" - "You can import it by clicking on Database > 'Import KeePass 1 database...'.\n" - "This is a one-way migration. You won't be able to open the imported " - "database with the old KeePassX 0.4 version.")); - return nullptr; - } - else if (!ok || signature2 != KeePass2::SIGNATURE_2) { - raiseError(tr("Not a KeePass database.")); - return nullptr; - } - - quint32 version = Endian::readUInt32(m_headerStream, KeePass2::BYTEORDER, &ok) - & KeePass2::FILE_VERSION_CRITICAL_MASK; - quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; - if (!ok || (version < KeePass2::FILE_VERSION_MIN) || (version > maxVersion)) { - raiseError(tr("Unsupported KeePass database version.")); - return nullptr; - } - - while (readHeaderField() && !hasError()) { - } - - headerStream.close(); - - if (hasError()) { - return nullptr; - } - - // check if all required headers were present - if (m_masterSeed.isEmpty() || m_encryptionIV.isEmpty() - || m_streamStartBytes.isEmpty() || m_protectedStreamKey.isEmpty() - || m_db->cipher().isNull()) { - raiseError("missing database headers"); - return nullptr; - } - - if (!m_db->setKey(key, false)) { - raiseError(tr("Unable to calculate master key")); - return nullptr; - } - - if (m_db->challengeMasterSeed(m_masterSeed) == false) { - raiseError(tr("Unable to issue challenge-response.")); - return nullptr; - } - - CryptoHash hash(CryptoHash::Sha256); - hash.addData(m_masterSeed); - hash.addData(m_db->challengeResponseKey()); - hash.addData(m_db->transformedMasterKey()); - QByteArray finalKey = hash.result(); - - SymmetricCipherStream cipherStream(m_device, SymmetricCipher::cipherToAlgorithm(m_db->cipher()), - SymmetricCipher::Cbc, SymmetricCipher::Decrypt); - if (!cipherStream.init(finalKey, m_encryptionIV)) { - raiseError(cipherStream.errorString()); - return nullptr; - } - if (!cipherStream.open(QIODevice::ReadOnly)) { - raiseError(cipherStream.errorString()); - return nullptr; - } - - QByteArray realStart = cipherStream.read(32); - - if (realStart != m_streamStartBytes) { - raiseError(tr("Wrong key or database file is corrupt.")); - return nullptr; - } - - HashedBlockStream hashedStream(&cipherStream); - if (!hashedStream.open(QIODevice::ReadOnly)) { - raiseError(hashedStream.errorString()); - return nullptr; - } - - QIODevice* xmlDevice; - QScopedPointer 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 buffer; - - if (m_saveXml) { - m_xmlData = xmlDevice->readAll(); - buffer.reset(new QBuffer(&m_xmlData)); - buffer->open(QIODevice::ReadOnly); - xmlDevice = buffer.data(); - } - - KeePass2XmlReader xmlReader; - xmlReader.readDatabase(xmlDevice, m_db, &randomStream); - - if (xmlReader.hasError()) { - raiseError(xmlReader.errorString()); - if (keepDatabase) { - return db.take(); - } - else { - return nullptr; - } - } - - Q_ASSERT(version < 0x00030001 || !xmlReader.headerHash().isEmpty()); - - if (!xmlReader.headerHash().isEmpty()) { - QByteArray headerHash = CryptoHash::hash(headerStream.storedData(), CryptoHash::Sha256); - if (headerHash != xmlReader.headerHash()) { - raiseError("Header doesn't match hash"); - return nullptr; - } - } - - return db.take(); } -Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key) +BaseKeePass2Reader::~BaseKeePass2Reader() {} + +Database* BaseKeePass2Reader::readDatabase(const QString& filename, const CompositeKey& key) { QFile file(filename); if (!file.open(QFile::ReadOnly)) { @@ -224,238 +56,113 @@ Database* KeePass2Reader::readDatabase(const QString& filename, const CompositeK return db.take(); } -bool KeePass2Reader::hasError() +bool BaseKeePass2Reader::hasError() { return m_error; } -QString KeePass2Reader::errorString() +QString BaseKeePass2Reader::errorString() { return m_errorStr; } -void KeePass2Reader::setSaveXml(bool save) +void BaseKeePass2Reader::setSaveXml(bool save) { m_saveXml = save; } -QByteArray KeePass2Reader::xmlData() +QByteArray BaseKeePass2Reader::xmlData() { return m_xmlData; } -QByteArray KeePass2Reader::streamKey() +QByteArray BaseKeePass2Reader::streamKey() { return m_protectedStreamKey; } -void KeePass2Reader::raiseError(const QString& errorMessage) +KeePass2::ProtectedStreamAlgo BaseKeePass2Reader::protectedStreamAlgo() const { + return m_irsAlgo; +} + + +void BaseKeePass2Reader::raiseError(const QString& errorMessage) { m_error = true; m_errorStr = errorMessage; } -bool KeePass2Reader::readHeaderField() +Database* KeePass2Reader::readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase) { - QByteArray fieldIDArray = m_headerStream->read(1); - if (fieldIDArray.size() != 1) { - raiseError("Invalid header id size"); - return false; - } - quint8 fieldID = fieldIDArray.at(0); + m_error = false; + m_errorStr.clear(); bool ok; - quint16 fieldLen = Endian::readUInt16(m_headerStream, KeePass2::BYTEORDER, &ok); - if (!ok) { - raiseError("Invalid header field length"); - return false; + + quint32 signature1 = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok); + if (!ok || signature1 != KeePass2::SIGNATURE_1) { + raiseError(tr("Not a KeePass database.")); + return nullptr; } - QByteArray fieldData; - if (fieldLen != 0) { - fieldData = m_headerStream->read(fieldLen); - if (fieldData.size() != fieldLen) { - raiseError("Invalid header data length"); - return false; - } + quint32 signature2 = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok); + if (ok && signature2 == KeePass1::SIGNATURE_2) { + raiseError(tr("The selected file is an old KeePass 1 database (.kdb).\n\n" + "You can import it by clicking on Database > 'Import KeePass 1 database...'.\n" + "This is a one-way migration. You won't be able to open the imported " + "database with the old KeePassX 0.4 version.")); + return nullptr; + } + else if (!ok || signature2 != KeePass2::SIGNATURE_2) { + raiseError(tr("Not a KeePass database.")); + return nullptr; } - switch (fieldID) { - case KeePass2::EndOfHeader: - m_headerEnd = true; - break; - - case KeePass2::CipherID: - setCipher(fieldData); - break; - - case KeePass2::CompressionFlags: - setCompressionFlags(fieldData); - break; - - case KeePass2::MasterSeed: - setMasterSeed(fieldData); - break; - - case KeePass2::TransformSeed: - setTransformSeed(fieldData); - break; - - case KeePass2::TransformRounds: - setTransformRounds(fieldData); - break; - - case KeePass2::EncryptionIV: - setEncryptionIV(fieldData); - break; - - case KeePass2::ProtectedStreamKey: - setProtectedStreamKey(fieldData); - break; - - case KeePass2::StreamStartBytes: - setStreamStartBytes(fieldData); - break; - - case KeePass2::InnerRandomStreamID: - setInnerRandomStreamID(fieldData); - break; - - default: - qWarning("Unknown header field read: id=%d", fieldID); - break; + m_version = Endian::readUInt32(device, KeePass2::BYTEORDER, &ok) + & KeePass2::FILE_VERSION_CRITICAL_MASK; + quint32 maxVersion = KeePass2::FILE_VERSION & KeePass2::FILE_VERSION_CRITICAL_MASK; + if (!ok || (m_version < KeePass2::FILE_VERSION_MIN) || (m_version > maxVersion)) { + raiseError(tr("Unsupported KeePass 2 database version.")); + return nullptr; } - return !m_headerEnd; + device->seek(0); + m_reader.reset(static_cast(new Kdbx3Reader())); + m_reader->setSaveXml(m_saveXml); + return m_reader->readDatabase(device, key, keepDatabase); } -void KeePass2Reader::setCipher(const QByteArray& data) +bool KeePass2Reader::hasError() { - if (data.size() != Uuid::Length) { - raiseError("Invalid cipher uuid length"); - } - else { - Uuid uuid(data); - - if (uuid != KeePass2::CIPHER_AES && uuid != KeePass2::CIPHER_TWOFISH) { - raiseError("Unsupported cipher"); - } - else { - m_db->setCipher(uuid); - } - } + return m_error || (m_reader && m_reader->hasError()); } -void KeePass2Reader::setCompressionFlags(const QByteArray& data) +QString KeePass2Reader::errorString() { - if (data.size() != 4) { - raiseError("Invalid compression flags length"); - } - else { - quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); - - if (id > Database::CompressionAlgorithmMax) { - raiseError("Unsupported compression algorithm"); - } - else { - m_db->setCompressionAlgo(static_cast(id)); - } - } + return m_reader ? m_reader->errorString() : m_errorStr; } -void KeePass2Reader::setMasterSeed(const QByteArray& data) +QByteArray KeePass2Reader::xmlData() { - if (data.size() != 32) { - raiseError("Invalid master seed size"); - } - else { - m_masterSeed = data; - } + return m_reader ? m_reader->xmlData() : m_xmlData; } -void KeePass2Reader::setTransformSeed(const QByteArray& data) +QByteArray KeePass2Reader::streamKey() { - if (data.size() != 32) { - raiseError("Invalid transform seed size"); - } - else { - AesKdf* aesKdf; - if (m_db->kdf()->type() == Kdf::Type::AES) { - aesKdf = static_cast(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(m_db->kdf()); - } else { - aesKdf = new AesKdf(); - m_db->setKdf(aesKdf); - } - - aesKdf->setRounds(rounds); - } -} - -void KeePass2Reader::setEncryptionIV(const QByteArray& data) -{ - if (data.size() != 16) { - raiseError("Invalid encryption iv size"); - } - else { - m_encryptionIV = data; - } -} - -void KeePass2Reader::setProtectedStreamKey(const QByteArray& data) -{ - if (data.size() != 32) { - raiseError("Invalid stream key size"); - } - else { - m_protectedStreamKey = data; - } -} - -void KeePass2Reader::setStreamStartBytes(const QByteArray& data) -{ - if (data.size() != 32) { - raiseError("Invalid start bytes size"); - } - else { - m_streamStartBytes = data; - } -} - -void KeePass2Reader::setInnerRandomStreamID(const QByteArray& data) -{ - if (data.size() != 4) { - raiseError("Invalid random stream id size"); - } - else { - quint32 id = Endian::bytesToUInt32(data, KeePass2::BYTEORDER); - m_irsAlgo = KeePass2::idToProtectedStreamAlgo(id); - if (m_irsAlgo == KeePass2::ArcFourVariant || m_irsAlgo == KeePass2::InvalidProtectedStreamAlgo) { - raiseError("Unsupported random stream algorithm"); - } - } + return m_reader ? m_reader->streamKey() : m_protectedStreamKey; } KeePass2::ProtectedStreamAlgo KeePass2Reader::protectedStreamAlgo() const { - return m_irsAlgo; + return m_reader ? m_reader->protectedStreamAlgo() : m_irsAlgo; +} + +quint32 KeePass2Reader::version() const +{ + return m_version; +} + +BaseKeePass2Reader* KeePass2Reader::reader() const +{ + return m_reader.data(); } diff --git a/src/format/KeePass2Reader.h b/src/format/KeePass2Reader.h index e9a947284..21c317023 100644 --- a/src/format/KeePass2Reader.h +++ b/src/format/KeePass2Reader.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,58 +18,65 @@ #ifndef KEEPASSX_KEEPASS2READER_H #define KEEPASSX_KEEPASS2READER_H +#include +#include +#include #include +#include +#include -#include "keys/CompositeKey.h" #include "format/KeePass2.h" +#include "core/Database.h" +#include "keys/CompositeKey.h" -class Database; -class QIODevice; - -class KeePass2Reader +class BaseKeePass2Reader { - Q_DECLARE_TR_FUNCTIONS(KeePass2Reader) - public: - KeePass2Reader(); - Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false); - Database* readDatabase(const QString& filename, const CompositeKey& key); - bool hasError(); - QString errorString(); - void setSaveXml(bool save); - QByteArray xmlData(); - QByteArray streamKey(); - KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const; + BaseKeePass2Reader(); -private: + virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) = 0; + virtual Database* readDatabase(const QString& filename, const CompositeKey& key); + + virtual bool hasError(); + virtual QString errorString(); + virtual void setSaveXml(bool save); + virtual QByteArray xmlData(); + virtual QByteArray streamKey(); + virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const; + + virtual ~BaseKeePass2Reader(); + +protected: void raiseError(const QString& errorMessage); - bool readHeaderField(); - - void setCipher(const QByteArray& data); - void setCompressionFlags(const QByteArray& data); - void setMasterSeed(const QByteArray& data); - void setTransformSeed(const QByteArray& data); - void setTransformRounds(const QByteArray& data); - void setEncryptionIV(const QByteArray& data); - void setProtectedStreamKey(const QByteArray& data); - void setStreamStartBytes(const QByteArray& data); - void setInnerRandomStreamID(const QByteArray& data); - - QIODevice* m_device; - QIODevice* m_headerStream; bool m_error; QString m_errorStr; - bool m_headerEnd; + bool m_saveXml; QByteArray m_xmlData; - - Database* m_db; - QByteArray m_masterSeed; - QByteArray m_encryptionIV; - QByteArray m_streamStartBytes; QByteArray m_protectedStreamKey; KeePass2::ProtectedStreamAlgo m_irsAlgo; }; +class KeePass2Reader : public BaseKeePass2Reader +{ + Q_DECLARE_TR_FUNCTIONS(KeePass2Reader) + +public: + virtual Database* readDatabase(QIODevice* device, const CompositeKey& key, bool keepDatabase = false) override; + using BaseKeePass2Reader::readDatabase; + + virtual bool hasError() override; + virtual QString errorString() override; + virtual QByteArray xmlData() override; + virtual QByteArray streamKey() override; + virtual KeePass2::ProtectedStreamAlgo protectedStreamAlgo() const override; + + quint32 version() const; + BaseKeePass2Reader* reader() const; +private: + QScopedPointer m_reader; + quint32 m_version; +}; + #endif // KEEPASSX_KEEPASS2READER_H diff --git a/src/format/KeePass2Repair.cpp b/src/format/KeePass2Repair.cpp index 5f2732e2a..0e79fa8ba 100644 --- a/src/format/KeePass2Repair.cpp +++ b/src/format/KeePass2Repair.cpp @@ -22,9 +22,10 @@ #include #include +#include "format/KeePass2.h" #include "format/KeePass2RandomStream.h" #include "format/KeePass2Reader.h" -#include "format/KeePass2XmlReader.h" +#include "format/Kdbx3XmlReader.h" KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, const CompositeKey& key) { @@ -73,7 +74,7 @@ KeePass2Repair::RepairOutcome KeePass2Repair::repairDatabase(QIODevice* device, KeePass2RandomStream randomStream(reader.protectedStreamAlgo()); randomStream.init(reader.streamKey()); - KeePass2XmlReader xmlReader; + Kdbx3XmlReader xmlReader; QBuffer buffer(&xmlData); buffer.open(QIODevice::ReadOnly); xmlReader.readDatabase(&buffer, db.data(), &randomStream); diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp index 6dc89c36d..00392dc05 100644 --- a/src/format/KeePass2Writer.cpp +++ b/src/format/KeePass2Writer.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -15,197 +15,58 @@ * along with this program. If not, see . */ -#include "KeePass2Writer.h" - -#include -#include #include +#include +#include +#include "format/KeePass2Writer.h" #include "core/Database.h" -#include "core/Endian.h" -#include "crypto/CryptoHash.h" -#include "crypto/kdf/AesKdf.h" -#include "crypto/Random.h" -#include "format/KeePass2RandomStream.h" -#include "format/KeePass2XmlWriter.h" -#include "streams/HashedBlockStream.h" -#include "streams/QtIOCompressor" -#include "streams/SymmetricCipherStream.h" +#include "format/Kdbx3Writer.h" -#define CHECK_RETURN(x) if (!(x)) return; -#define CHECK_RETURN_FALSE(x) if (!(x)) return false; - -KeePass2Writer::KeePass2Writer() - : m_device(0) - , m_error(false) +BaseKeePass2Writer::BaseKeePass2Writer() : m_error(false) { -} - -void KeePass2Writer::writeDatabase(QIODevice* device, Database* db) -{ - m_error = false; m_errorStr.clear(); - - QByteArray masterSeed = randomGen()->randomArray(32); - QByteArray encryptionIV = randomGen()->randomArray(16); - QByteArray protectedStreamKey = randomGen()->randomArray(32); - QByteArray startBytes = randomGen()->randomArray(32); - QByteArray endOfHeader = "\r\n\r\n"; - - if (db->challengeMasterSeed(masterSeed) == false) { - raiseError(tr("Unable to issue challenge-response.")); - return; - } - - if (!db->setKey(db->key(), false, true)) { - raiseError(tr("Unable to calculate master key")); - return; - } - - CryptoHash hash(CryptoHash::Sha256); - hash.addData(masterSeed); - hash.addData(db->challengeResponseKey()); - Q_ASSERT(!db->transformedMasterKey().isEmpty()); - hash.addData(db->transformedMasterKey()); - QByteArray finalKey = hash.result(); - - QBuffer header; - header.open(QIODevice::WriteOnly); - m_device = &header; - - CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_1, KeePass2::BYTEORDER))); - CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::SIGNATURE_2, KeePass2::BYTEORDER))); - CHECK_RETURN(writeData(Endian::int32ToBytes(KeePass2::FILE_VERSION, KeePass2::BYTEORDER))); - - CHECK_RETURN(writeHeaderField(KeePass2::CipherID, db->cipher().toByteArray())); - CHECK_RETURN(writeHeaderField(KeePass2::CompressionFlags, - Endian::int32ToBytes(db->compressionAlgo(), - KeePass2::BYTEORDER))); - AesKdf* kdf = static_cast(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 ioCompressor; - - if (db->compressionAlgo() == Database::CompressionNone) { - m_device = &hashedStream; - } - else { - ioCompressor.reset(new QtIOCompressor(&hashedStream)); - ioCompressor->setStreamFormat(QtIOCompressor::GzipFormat); - if (!ioCompressor->open(QIODevice::WriteOnly)) { - raiseError(ioCompressor->errorString()); - return; - } - m_device = ioCompressor.data(); - } - - KeePass2RandomStream randomStream(KeePass2::Salsa20); - if (!randomStream.init(protectedStreamKey)) { - raiseError(randomStream.errorString()); - return; - } - - KeePass2XmlWriter xmlWriter; - xmlWriter.writeDatabase(m_device, db, &randomStream, headerHash); - - // Explicitly close/reset streams so they are flushed and we can detect - // errors. QIODevice::close() resets errorString() etc. - if (ioCompressor) { - ioCompressor->close(); - } - if (!hashedStream.reset()) { - raiseError(hashedStream.errorString()); - return; - } - if (!cipherStream.reset()) { - raiseError(cipherStream.errorString()); - return; - } - - if (xmlWriter.hasError()) { - raiseError(xmlWriter.errorString()); - } } -bool KeePass2Writer::writeData(const QByteArray& data) -{ - if (m_device->write(data) != data.size()) { - raiseError(m_device->errorString()); - return false; - } - else { - return true; - } -} +BaseKeePass2Writer::~BaseKeePass2Writer() {} -bool KeePass2Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data) -{ - Q_ASSERT(data.size() <= 65535); - - QByteArray fieldIdArr; - fieldIdArr[0] = fieldId; - CHECK_RETURN_FALSE(writeData(fieldIdArr)); - CHECK_RETURN_FALSE(writeData(Endian::int16ToBytes(static_cast(data.size()), - KeePass2::BYTEORDER))); - CHECK_RETURN_FALSE(writeData(data)); - - return true; -} - -void KeePass2Writer::writeDatabase(const QString& filename, Database* db) -{ - QFile file(filename); - if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) { - raiseError(file.errorString()); - return; - } - writeDatabase(&file, db); -} - -bool KeePass2Writer::hasError() +bool BaseKeePass2Writer::hasError() { return m_error; } -QString KeePass2Writer::errorString() +QString BaseKeePass2Writer::errorString() { return m_errorStr; } -void KeePass2Writer::raiseError(const QString& errorMessage) +void BaseKeePass2Writer::raiseError(const QString& errorMessage) { m_error = true; m_errorStr = errorMessage; } + +bool BaseKeePass2Writer::writeDatabase(const QString& filename, Database* db) +{ + QFile file(filename); + if (!file.open(QIODevice::WriteOnly|QIODevice::Truncate)) { + raiseError(file.errorString()); + return false; + } + return writeDatabase(&file, db); +} + +bool KeePass2Writer::hasError() +{ + return m_error || (m_writer && m_writer->hasError()); +} + +QString KeePass2Writer::errorString() +{ + return m_writer ? m_writer->errorString() : m_errorStr; +} + +bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) { + m_writer.reset(static_cast(new Kdbx3Writer())); + return m_writer->writeDatabase(device, db); +} diff --git a/src/format/KeePass2Writer.h b/src/format/KeePass2Writer.h index 184aa1a71..85802eb44 100644 --- a/src/format/KeePass2Writer.h +++ b/src/format/KeePass2Writer.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010 Felix Geyer + * Copyright (C) 2017 KeePassXC Team * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -18,33 +18,48 @@ #ifndef KEEPASSX_KEEPASS2WRITER_H #define KEEPASSX_KEEPASS2WRITER_H +#include +#include +#include #include +#include +#include "core/Database.h" #include "format/KeePass2.h" -#include "keys/CompositeKey.h" -class Database; -class QIODevice; - -class KeePass2Writer +class BaseKeePass2Writer { - Q_DECLARE_TR_FUNCTIONS(KeePass2Writer) - public: - KeePass2Writer(); - void writeDatabase(QIODevice* device, Database* db); - void writeDatabase(const QString& filename, Database* db); - bool hasError(); - QString errorString(); + BaseKeePass2Writer(); -private: - bool writeData(const QByteArray& data); - bool writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data); + virtual bool writeDatabase(QIODevice* device, Database* db) = 0; + virtual bool writeDatabase(const QString& filename, Database* db); + + virtual bool hasError(); + virtual QString errorString(); + + virtual ~BaseKeePass2Writer(); + +protected: void raiseError(const QString& errorMessage); - QIODevice* m_device; bool m_error; QString m_errorStr; }; -#endif // KEEPASSX_KEEPASS2WRITER_H +class KeePass2Writer : public BaseKeePass2Writer +{ + Q_DECLARE_TR_FUNCTIONS(KeePass2Writer) + +public: + virtual bool writeDatabase(QIODevice* device, Database* db) override; + using BaseKeePass2Writer::writeDatabase; + + virtual bool hasError() override; + virtual QString errorString() override; + +private: + QScopedPointer m_writer; +}; + +#endif // KEEPASSX_KEEPASS2READER_H diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index c36eefd4a..bdf0e6f86 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -107,7 +107,7 @@ endif() add_unit_test(NAME testgroup SOURCES TestGroup.cpp LIBS ${TEST_LIBRARIES}) -add_unit_test(NAME testkeepass2xmlreader SOURCES TestKeePass2XmlReader.cpp +add_unit_test(NAME testkdbx3xmlreader SOURCES TestKeePass2XmlReader.cpp TestKdbx3XmlReader.cpp LIBS ${TEST_LIBRARIES}) add_unit_test(NAME testkeys SOURCES TestKeys.cpp diff --git a/tests/TestDeletedObjects.cpp b/tests/TestDeletedObjects.cpp index 5af017885..371d318db 100644 --- a/tests/TestDeletedObjects.cpp +++ b/tests/TestDeletedObjects.cpp @@ -22,7 +22,7 @@ #include "core/Database.h" #include "core/Group.h" #include "crypto/Crypto.h" -#include "format/KeePass2XmlReader.h" +#include "format/Kdbx3XmlReader.h" #include "config-keepassx-tests.h" QTEST_GUILESS_MAIN(TestDeletedObjects) @@ -88,7 +88,7 @@ void TestDeletedObjects::createAndDelete(Database* db, int delObjectsSize) void TestDeletedObjects::testDeletedObjectsFromFile() { - KeePass2XmlReader reader; + Kdbx3XmlReader reader; reader.setStrictMode(true); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); Database* db = reader.readDatabase(xmlFile); diff --git a/tests/TestKdbx3XmlReader.cpp b/tests/TestKdbx3XmlReader.cpp new file mode 100644 index 000000000..d4ce58542 --- /dev/null +++ b/tests/TestKdbx3XmlReader.cpp @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2017 KeePassXC Team + * + * 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 . + */ + +#include + +#include "TestKeePass2XmlReader.h" + +QTEST_GUILESS_MAIN(TestKdbx3XmlReader) diff --git a/tests/TestKeePass2Writer.cpp b/tests/TestKeePass2Writer.cpp index f6d3f58ad..049c3e66e 100644 --- a/tests/TestKeePass2Writer.cpp +++ b/tests/TestKeePass2Writer.cpp @@ -30,7 +30,6 @@ #include "format/KeePass2Reader.h" #include "format/KeePass2Repair.h" #include "format/KeePass2Writer.h" -#include "format/KeePass2XmlWriter.h" #include "keys/PasswordKey.h" QTEST_GUILESS_MAIN(TestKeePass2Writer) diff --git a/tests/TestKeePass2XmlReader.cpp b/tests/TestKeePass2XmlReader.cpp index 495b39acf..c1e3b4b63 100644 --- a/tests/TestKeePass2XmlReader.cpp +++ b/tests/TestKeePass2XmlReader.cpp @@ -25,12 +25,10 @@ #include "core/Group.h" #include "core/Metadata.h" #include "crypto/Crypto.h" -#include "format/KeePass2XmlReader.h" -#include "format/KeePass2XmlWriter.h" +#include "format/Kdbx3XmlReader.h" +#include "format/Kdbx3XmlWriter.h" #include "config-keepassx-tests.h" -QTEST_GUILESS_MAIN(TestKeePass2XmlReader) - namespace QTest { template<> char* toString(const Uuid& uuid) @@ -79,11 +77,11 @@ QByteArray TestKeePass2XmlReader::strToBytes(const QString& str) return result; } -void TestKeePass2XmlReader::initTestCase() +void TestKdbx3XmlReader::initTestCase() { QVERIFY(Crypto::init()); - KeePass2XmlReader reader; + Kdbx3XmlReader reader; reader.setStrictMode(true); QString xmlFile = QString(KEEPASSX_TEST_DATA_DIR).append("/NewDatabase.xml"); m_db = reader.readDatabase(xmlFile); @@ -91,6 +89,32 @@ void TestKeePass2XmlReader::initTestCase() QVERIFY(!reader.hasError()); } +void TestKdbx3XmlReader::readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) +{ + Kdbx3XmlReader reader; + reader.setStrictMode(strictMode); + db = reader.readDatabase(path); + hasError = reader.hasError(); + errorString = reader.errorString(); +} + +void TestKdbx3XmlReader::readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) +{ + Kdbx3XmlReader reader; + reader.setStrictMode(strictMode); + db = reader.readDatabase(buf); + hasError = reader.hasError(); + errorString = reader.errorString(); +} + +void TestKdbx3XmlReader::writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) +{ + Kdbx3XmlWriter writer; + writer.writeDatabase(buf, db); + hasError = writer.hasError(); + errorString = writer.errorString(); +} + void TestKeePass2XmlReader::testMetadata() { QCOMPARE(m_db->metadata()->generator(), QString("KeePass")); @@ -374,15 +398,20 @@ void TestKeePass2XmlReader::testBroken() QFETCH(bool, strictMode); QFETCH(bool, expectError); - KeePass2XmlReader reader; - reader.setStrictMode(strictMode); + QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, baseName); QVERIFY(QFile::exists(xmlFile)); - QScopedPointer db(reader.readDatabase(xmlFile)); - if (reader.hasError()) { - qWarning("Reader error: %s", qPrintable(reader.errorString())); + bool hasError; + QString errorString; + Database* db; + readDatabase(xmlFile, strictMode, db, hasError, errorString); + if (hasError) { + qWarning("Reader error: %s", qPrintable(errorString)); + } + QCOMPARE(hasError, expectError); + if (db) { + delete db; } - QCOMPARE(reader.hasError(), expectError); } void TestKeePass2XmlReader::testBroken_data() @@ -412,15 +441,20 @@ void TestKeePass2XmlReader::testBroken_data() void TestKeePass2XmlReader::testEmptyUuids() { - KeePass2XmlReader reader; - reader.setStrictMode(true); + QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "EmptyUuids"); QVERIFY(QFile::exists(xmlFile)); - QScopedPointer db(reader.readDatabase(xmlFile)); - if (reader.hasError()) { - qWarning("Reader error: %s", qPrintable(reader.errorString())); + Database* dbp; + bool hasError; + QString errorString; + readDatabase(xmlFile, true, dbp, hasError, errorString); + if (hasError) { + qWarning("Reader error: %s", qPrintable(errorString)); + } + QVERIFY(!hasError); + if (dbp) { + delete dbp; } - QVERIFY(!reader.hasError()); } void TestKeePass2XmlReader::testInvalidXmlChars() @@ -459,19 +493,19 @@ void TestKeePass2XmlReader::testInvalidXmlChars() QBuffer buffer; buffer.open(QIODevice::ReadWrite); - KeePass2XmlWriter writer; - writer.writeDatabase(&buffer, dbWrite.data()); - QVERIFY(!writer.hasError()); + bool hasError; + QString errorString; + writeDatabase(&buffer, dbWrite.data(), hasError, errorString); + QVERIFY(!hasError); buffer.seek(0); - KeePass2XmlReader reader; - reader.setStrictMode(true); - QScopedPointer dbRead(reader.readDatabase(&buffer)); - if (reader.hasError()) { - qWarning("Database read error: %s", qPrintable(reader.errorString())); + Database* dbRead; + readDatabase(&buffer, true, dbRead, hasError, errorString); + if (hasError) { + qWarning("Database read error: %s", qPrintable(errorString)); } - QVERIFY(!reader.hasError()); - QVERIFY(!dbRead.isNull()); + QVERIFY(!hasError); + QVERIFY(dbRead); QCOMPARE(dbRead->rootGroup()->entries().size(), 1); Entry* entryRead = dbRead->rootGroup()->entries().at(0); EntryAttributes* attrRead = entryRead->attributes(); @@ -486,22 +520,28 @@ void TestKeePass2XmlReader::testInvalidXmlChars() QCOMPARE(strToBytes(attrRead->value("LowLowSurrogate")), QByteArray()); QCOMPARE(strToBytes(attrRead->value("SurrogateValid1")), strToBytes(strSurrogateValid1)); QCOMPARE(strToBytes(attrRead->value("SurrogateValid2")), strToBytes(strSurrogateValid2)); + + if (dbRead) { + delete dbRead; + } } void TestKeePass2XmlReader::testRepairUuidHistoryItem() { - KeePass2XmlReader reader; QString xmlFile = QString("%1/%2.xml").arg(KEEPASSX_TEST_DATA_DIR, "BrokenDifferentEntryHistoryUuid"); QVERIFY(QFile::exists(xmlFile)); - QScopedPointer db(reader.readDatabase(xmlFile)); - if (reader.hasError()) { - qWarning("Database read error: %s", qPrintable(reader.errorString())); + Database* db; + bool hasError; + QString errorString; + readDatabase(xmlFile, true, db, hasError, errorString); + if (hasError) { + qWarning("Database read error: %s", qPrintable(errorString)); } - QVERIFY(!reader.hasError()); + QVERIFY(!hasError); - QList entries = db.data()->rootGroup()->entries(); + QList entries = db->rootGroup()->entries(); QCOMPARE(entries.size(), 1); Entry* entry = entries.at(0); @@ -512,6 +552,10 @@ void TestKeePass2XmlReader::testRepairUuidHistoryItem() QVERIFY(!entry->uuid().isNull()); QVERIFY(!historyItem->uuid().isNull()); QCOMPARE(historyItem->uuid(), entry->uuid()); + + if (db) { + delete db; + } } void TestKeePass2XmlReader::cleanupTestCase() diff --git a/tests/TestKeePass2XmlReader.h b/tests/TestKeePass2XmlReader.h index 628964b46..2ce122235 100644 --- a/tests/TestKeePass2XmlReader.h +++ b/tests/TestKeePass2XmlReader.h @@ -20,6 +20,7 @@ #include #include +#include class Database; @@ -27,8 +28,8 @@ class TestKeePass2XmlReader : public QObject { Q_OBJECT -private slots: - void initTestCase(); +protected slots: + virtual void initTestCase() = 0; void testMetadata(); void testCustomIcons(); void testCustomData(); @@ -46,11 +47,27 @@ private slots: void testRepairUuidHistoryItem(); void cleanupTestCase(); -private: +protected: + virtual void readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) = 0; + virtual void readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) = 0; + virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) = 0; static QDateTime genDT(int year, int month, int day, int hour, int min, int second); static QByteArray strToBytes(const QString& str); Database* m_db; }; +class TestKdbx3XmlReader : public TestKeePass2XmlReader +{ + Q_OBJECT + +private slots: + virtual void initTestCase() override; + +protected: + virtual void readDatabase(QBuffer* buf, bool strictMode, Database*& db, bool& hasError, QString& errorString) override; + virtual void readDatabase(QString path, bool strictMode, Database*& db, bool& hasError, QString& errorString) override; + virtual void writeDatabase(QBuffer* buf, Database* db, bool& hasError, QString& errorString) override; +}; + #endif // KEEPASSX_TESTKEEPASS2XMLREADER_H