Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'src/format/KeePass2Writer.cpp')
-rw-r--r--src/format/KeePass2Writer.cpp234
1 files changed, 70 insertions, 164 deletions
diff --git a/src/format/KeePass2Writer.cpp b/src/format/KeePass2Writer.cpp
index f8f60f11e..68cf3af99 100644
--- a/src/format/KeePass2Writer.cpp
+++ b/src/format/KeePass2Writer.cpp
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2010 Felix Geyer <debfx@fobos.de>
+ * Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -15,196 +15,102 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "KeePass2Writer.h"
-
-#include <QBuffer>
-#include <QFile>
#include <QIODevice>
+#include <QFile>
#include "core/Database.h"
-#include "core/Endian.h"
-#include "crypto/CryptoHash.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;
-#define CHECK_RETURN_FALSE(x) if (!(x)) return false;
-
-KeePass2Writer::KeePass2Writer()
- : m_device(0)
- , m_error(false)
+#include "core/Group.h"
+#include "core/Metadata.h"
+#include "crypto/kdf/AesKdf.h"
+#include "format/KeePass2Writer.h"
+#include "format/Kdbx3Writer.h"
+#include "format/Kdbx4Writer.h"
+
+/**
+ * Write a database to a KDBX file.
+ *
+ * @param filename output filename
+ * @param db source database
+ * @return true on success
+ */
+bool KeePass2Writer::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);
}
-void KeePass2Writer::writeDatabase(QIODevice* device, Database* db)
-{
+/**
+ * Write a database to a device in KDBX format.
+ *
+ * @param device output device
+ * @param db source database
+ * @return true on success
+ */
+
+bool KeePass2Writer::writeDatabase(QIODevice* device, Database* db) {
m_error = false;
m_errorStr.clear();
- QByteArray transformSeed = randomGen()->randomArray(32);
- 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->transformKeyWithSeed(transformSeed)) {
- 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)));
- CHECK_RETURN(writeHeaderField(KeePass2::MasterSeed, masterSeed));
- CHECK_RETURN(writeHeaderField(KeePass2::TransformSeed, db->transformSeed()));
- CHECK_RETURN(writeHeaderField(KeePass2::TransformRounds,
- Endian::int64ToBytes(db->transformRounds(),
- 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;
+ // determine KDBX3 vs KDBX4
+ bool hasCustomData = !db->publicCustomData().isEmpty() || (db->metadata()->customData() && !db->metadata()->customData()->isEmpty());
+ if (!hasCustomData) {
+ for (const auto& entry: db->rootGroup()->entriesRecursive(true)) {
+ if ((entry->customData() && !entry->customData()->isEmpty()) ||
+ (entry->group() && entry->group()->customData() && !entry->group()->customData()->isEmpty())) {
+ hasCustomData = true;
+ break;
+ }
}
- m_device = ioCompressor.data();
- }
-
- KeePass2RandomStream randomStream;
- 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 (db->kdf()->uuid() == KeePass2::KDF_AES_KDBX3 && !hasCustomData) {
+ m_version = KeePass2::FILE_VERSION_3_1;
+ m_writer.reset(new Kdbx3Writer());
+ } else {
+ m_version = KeePass2::FILE_VERSION_4;
+ m_writer.reset(new Kdbx4Writer());
}
- if (xmlWriter.hasError()) {
- raiseError(xmlWriter.errorString());
- }
+ return m_writer->writeDatabase(device, db);
}
-bool KeePass2Writer::writeData(const QByteArray& data)
+bool KeePass2Writer::hasError() const
{
- if (m_device->write(data) != data.size()) {
- raiseError(m_device->errorString());
- return false;
- }
- else {
- return true;
- }
+ return m_error || (m_writer && m_writer->hasError());
}
-bool KeePass2Writer::writeHeaderField(KeePass2::HeaderFieldID fieldId, const QByteArray& data)
+QString KeePass2Writer::errorString() const
{
- 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;
+ return m_writer ? m_writer->errorString() : m_errorStr;
}
-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()
+/**
+ * Raise an error. Use in case of an unexpected write error.
+ *
+ * @param errorMessage error message
+ */
+void KeePass2Writer::raiseError(const QString& errorMessage)
{
- return m_error;
+ m_error = true;
+ m_errorStr = errorMessage;
}
-QString KeePass2Writer::errorString()
+/**
+ * @return KDBX writer used for writing the output file
+ */
+QSharedPointer<KdbxWriter> KeePass2Writer::writer() const
{
- return m_errorStr;
+ return QSharedPointer<KdbxWriter>();
}
-void KeePass2Writer::raiseError(const QString& errorMessage)
+/**
+ * @return KDBX version used for writing the output file
+ */
+quint32 KeePass2Writer::version() const
{
- m_error = true;
- m_errorStr = errorMessage;
+ return m_version;
}