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/keeshare/ShareExport.cpp')
-rw-r--r--src/keeshare/ShareExport.cpp232
1 files changed, 232 insertions, 0 deletions
diff --git a/src/keeshare/ShareExport.cpp b/src/keeshare/ShareExport.cpp
new file mode 100644
index 000000000..c17c5052c
--- /dev/null
+++ b/src/keeshare/ShareExport.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2019 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 "ShareExport.h"
+#include "config-keepassx.h"
+#include "core/Group.h"
+#include "core/Metadata.h"
+#include "format/KeePass2Writer.h"
+#include "keeshare/KeeShare.h"
+#include "keeshare/Signature.h"
+#include "keys/PasswordKey.h"
+
+#if defined(WITH_XC_KEESHARE_SECURE)
+#include <quazip.h>
+#include <quazipfile.h>
+#endif
+
+namespace
+{
+ void resolveReferenceAttributes(Entry* targetEntry, const Database* sourceDb)
+ {
+ for (const auto& attribute : EntryAttributes::DefaultAttributes) {
+ const auto standardValue = targetEntry->attributes()->value(attribute);
+ const auto type = targetEntry->placeholderType(standardValue);
+ if (type != Entry::PlaceholderType::Reference) {
+ // No reference to resolve
+ continue;
+ }
+ const auto* referencedTargetEntry = targetEntry->resolveReference(standardValue);
+ if (referencedTargetEntry) {
+ // References is within scope, no resolving needed
+ continue;
+ }
+ // We could do more sophisticated **** trying to point the reference to the next in-scope reference
+ // but those cases with high propability constructed examples and very rare in real usage
+ const auto* sourceReference = sourceDb->rootGroup()->findEntryByUuid(targetEntry->uuid());
+ const auto resolvedValue = sourceReference->resolveMultiplePlaceholders(standardValue);
+ targetEntry->setUpdateTimeinfo(false);
+ targetEntry->attributes()->set(attribute, resolvedValue, targetEntry->attributes()->isProtected(attribute));
+ targetEntry->setUpdateTimeinfo(true);
+ }
+ }
+
+ Database* extractIntoDatabase(const KeeShareSettings::Reference& reference, const Group* sourceRoot)
+ {
+ const auto* sourceDb = sourceRoot->database();
+ auto* targetDb = new Database();
+ auto* targetMetadata = targetDb->metadata();
+ targetMetadata->setRecycleBinEnabled(false);
+ auto key = QSharedPointer<CompositeKey>::create();
+ key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
+
+ // Copy the source root as the root of the export database, memory manage the old root node
+ auto* targetRoot = sourceRoot->clone(Entry::CloneNoFlags, Group::CloneNoFlags);
+ const bool updateTimeinfo = targetRoot->canUpdateTimeinfo();
+ targetRoot->setUpdateTimeinfo(false);
+ KeeShare::setReferenceTo(targetRoot, KeeShareSettings::Reference());
+ targetRoot->setUpdateTimeinfo(updateTimeinfo);
+ const auto sourceEntries = sourceRoot->entriesRecursive(false);
+ for (const Entry* sourceEntry : sourceEntries) {
+ auto* targetEntry = sourceEntry->clone(Entry::CloneIncludeHistory);
+ const bool updateTimeinfo = targetEntry->canUpdateTimeinfo();
+ targetEntry->setUpdateTimeinfo(false);
+ targetEntry->setGroup(targetRoot);
+ targetEntry->setUpdateTimeinfo(updateTimeinfo);
+ const auto iconUuid = targetEntry->iconUuid();
+ if (!iconUuid.isNull() && !targetMetadata->containsCustomIcon(iconUuid)) {
+ targetMetadata->addCustomIcon(iconUuid, sourceEntry->icon());
+ }
+ }
+
+ targetDb->setKey(key);
+ auto* obsoleteRoot = targetDb->rootGroup();
+ targetDb->setRootGroup(targetRoot);
+ delete obsoleteRoot;
+
+ targetDb->metadata()->setName(sourceRoot->name());
+
+ // Push all deletions of the source database to the target
+ // simple moving out of a share group will not trigger a deletion in the
+ // target - a more elaborate mechanism may need the use of another custom
+ // attribute to share unshared entries from the target db
+ for (const auto& object : sourceDb->deletedObjects()) {
+ targetDb->addDeletedObject(object);
+ }
+ for (auto* targetEntry : targetRoot->entriesRecursive(false)) {
+ if (targetEntry->hasReferences()) {
+ resolveReferenceAttributes(targetEntry, sourceDb);
+ }
+ }
+ return targetDb;
+ }
+
+ ShareObserver::Result
+ intoSignedContainer(const QString& resolvedPath, const KeeShareSettings::Reference& reference, Database* targetDb)
+ {
+#if !defined(WITH_XC_KEESHARE_SECURE)
+ Q_UNUSED(targetDb);
+ Q_UNUSED(resolvedPath);
+ return {reference.path,
+ ShareObserver::Result::Warning,
+ ShareExport::tr("Overwriting signed share container is not supported - export prevented")};
+#else
+ QByteArray bytes;
+ {
+ QBuffer buffer(&bytes);
+ buffer.open(QIODevice::WriteOnly);
+ KeePass2Writer writer;
+ writer.writeDatabase(&buffer, targetDb);
+ if (writer.hasError()) {
+ qWarning("Serializing export dabase failed: %s.", writer.errorString().toLatin1().data());
+ return {reference.path, ShareObserver::Result::Error, writer.errorString()};
+ }
+ }
+ const auto own = KeeShare::own();
+ QuaZip zip(resolvedPath);
+ zip.setFileNameCodec("UTF-8");
+ const bool zipOpened = zip.open(QuaZip::mdCreate);
+ if (!zipOpened) {
+ ::qWarning("Opening export file failed: %d", zip.getZipError());
+ return {reference.path,
+ ShareObserver::Result::Error,
+ ShareExport::tr("Could not write export container (%1)").arg(zip.getZipError())};
+ }
+ {
+ QuaZipFile file(&zip);
+ const auto signatureOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare::signatureFileName()));
+ if (!signatureOpened) {
+ ::qWarning("Embedding signature failed: Could not open file to write (%d)", zip.getZipError());
+ return {reference.path,
+ ShareObserver::Result::Error,
+ ShareExport::tr("Could not embed signature: Could not open file to write (%1)")
+ .arg(file.getZipError())};
+ }
+ QTextStream stream(&file);
+ KeeShareSettings::Sign sign;
+ auto sshKey = own.key.sshKey();
+ sshKey.openKey(QString());
+ sign.signature = Signature::create(bytes, sshKey);
+ sign.certificate = own.certificate;
+ stream << KeeShareSettings::Sign::serialize(sign);
+ stream.flush();
+ if (file.getZipError() != ZIP_OK) {
+ ::qWarning("Embedding signature failed: Could not write file (%d)", zip.getZipError());
+ return {
+ reference.path,
+ ShareObserver::Result::Error,
+ ShareExport::tr("Could not embed signature: Could not write file (%1)").arg(file.getZipError())};
+ }
+ file.close();
+ }
+ {
+ QuaZipFile file(&zip);
+ const auto dbOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare::containerFileName()));
+ if (!dbOpened) {
+ ::qWarning("Embedding database failed: Could not open file to write (%d)", zip.getZipError());
+ return {reference.path,
+ ShareObserver::Result::Error,
+ ShareExport::tr("Could not embed database: Could not open file to write (%1)")
+ .arg(file.getZipError())};
+ }
+ file.write(bytes);
+ if (file.getZipError() != ZIP_OK) {
+ ::qWarning("Embedding database failed: Could not write file (%d)", zip.getZipError());
+ return {reference.path,
+ ShareObserver::Result::Error,
+ ShareExport::tr("Could not embed database: Could not write file (%1)").arg(file.getZipError())};
+ }
+ file.close();
+ }
+ zip.close();
+ return {reference.path};
+#endif
+ }
+
+ ShareObserver::Result
+ intoUnsignedContainer(const QString& resolvedPath, const KeeShareSettings::Reference& reference, Database* targetDb)
+ {
+#if !defined(WITH_XC_KEESHARE_INSECURE)
+ Q_UNUSED(targetDb);
+ Q_UNUSED(resolvedPath);
+ return {reference.path,
+ ShareObserver::Result::Warning,
+ ShareExport::tr("Overwriting unsigned share container is not supported - export prevented")};
+#else
+ QFile file(resolvedPath);
+ const bool fileOpened = file.open(QIODevice::WriteOnly);
+ if (!fileOpened) {
+ ::qWarning("Opening export file failed");
+ return {reference.path, ShareObserver::Result::Error, ShareExport::tr("Could not write export container")};
+ }
+ KeePass2Writer writer;
+ writer.writeDatabase(&file, targetDb);
+ if (writer.hasError()) {
+ qWarning("Exporting dabase failed: %s.", writer.errorString().toLatin1().data());
+ return {reference.path, ShareObserver::Result::Error, writer.errorString()};
+ }
+ file.close();
+#endif
+ return {reference.path};
+ }
+
+} // namespace
+
+ShareObserver::Result ShareExport::intoContainer(const QString& resolvedPath,
+ const KeeShareSettings::Reference& reference,
+ const Group* group)
+{
+ QScopedPointer<Database> targetDb(extractIntoDatabase(reference, group));
+ const QFileInfo info(resolvedPath);
+ if (KeeShare::isContainerType(info, KeeShare::signedContainerFileType())) {
+ return intoSignedContainer(resolvedPath, reference, targetDb.data());
+ }
+ if (KeeShare::isContainerType(info, KeeShare::unsignedContainerFileType())) {
+ return intoUnsignedContainer(resolvedPath, reference, targetDb.data());
+ }
+ Q_ASSERT(false);
+ return {reference.path, ShareObserver::Result::Error, tr("Unexpected export error occurred")};
+}