From d4c391deb2a714fd9cc733b9c91c8fb698679702 Mon Sep 17 00:00:00 2001 From: Christian Kieschnick Date: Thu, 3 Jan 2019 08:46:32 +0100 Subject: [PATCH] Splitted KeeShare into secure and insecure parts KeeShare is now supported in a secure and insecure flavor (set CMake-Flags accordingly to allow or disallow the corresponding import and exports) --- CMakeLists.txt | 15 +- src/CMakeLists.txt | 3 +- src/config-keepassx.h.cmake | 2 + src/gui/AboutDialog.cpp | 8 +- src/gui/DatabaseWidget.cpp | 2 +- src/gui/EntryPreviewWidget.cpp | 6 +- src/gui/EntryPreviewWidget.h | 2 +- src/gui/MainWindow.cpp | 4 +- src/gui/dbsettings/DatabaseSettingsDialog.cpp | 4 +- src/gui/group/EditGroupWidget.cpp | 4 +- src/gui/group/GroupModel.cpp | 4 +- src/keeshare/CMakeLists.txt | 26 +- src/keeshare/KeeShare.cpp | 12 + src/keeshare/KeeShare.h | 3 + src/keeshare/ShareObserver.cpp | 363 ++++++++++++------ src/keeshare/ShareObserver.h | 8 +- .../group/EditGroupWidgetKeeShare.cpp | 22 +- tests/CMakeLists.txt | 2 +- 18 files changed, 337 insertions(+), 153 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b61d7f78c..3370f1873 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -47,7 +47,8 @@ option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF) option(WITH_XC_YUBIKEY "Include YubiKey support." OFF) option(WITH_XC_SSHAGENT "Include SSH agent support." OFF) -option(WITH_XC_KEESHARE "Include sharing support with KeeShare." OFF) +option(WITH_XC_KEESHARE_INSECURE "Sharing integration with KeeShare with insecure sources" OFF) +option(WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources" OFF) if(APPLE) option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF) endif() @@ -59,12 +60,20 @@ if(WITH_XC_ALL) set(WITH_XC_BROWSER ON) set(WITH_XC_YUBIKEY ON) set(WITH_XC_SSHAGENT ON) - set(WITH_XC_KEESHARE ON) + set(WITH_XC_KEESHARE_INSECURE ON) + set(WITH_XC_KEESHARE_SECURE ON) if(APPLE) set(WITH_XC_TOUCHID ON) endif() endif() +if(WITH_XC_KEESHARE_INSECURE OR WITH_XC_KEESHARE_SECURE) + set(WITH_XC_KEESHARE ON) +else() + set(WITH_XC_KEESHARE OFF) +endif() + + if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE) set(WITH_XC_CRYPTO_SSH ON) else() @@ -358,7 +367,7 @@ endif() include_directories(SYSTEM ${ARGON2_INCLUDE_DIR}) # Optional -if(WITH_XC_KEESHARE) +if(WITH_XC_KEESHARE_SECURE) find_package(QuaZip REQUIRED) include_directories(SYSTEM ${QUAZIP_INCLUDE_DIR}) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 14fbd5886..446b74447 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -202,7 +202,8 @@ add_feature_info(Auto-Type WITH_XC_AUTOTYPE "Automatic password typing") add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)") add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser") add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent") -add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare") +add_feature_info(KeeShare-Insecure WITH_XC_KEESHARE_INSECURE "Sharing integration with KeeShare with insecure sources") +add_feature_info(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources") add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response") if(APPLE) add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration") diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake index 9997ec88a..7d7018861 100644 --- a/src/config-keepassx.h.cmake +++ b/src/config-keepassx.h.cmake @@ -18,6 +18,8 @@ #cmakedefine WITH_XC_YUBIKEY #cmakedefine WITH_XC_SSHAGENT #cmakedefine WITH_XC_KEESHARE +#cmakedefine WITH_XC_KEESHARE_INSECURE +#cmakedefine WITH_XC_KEESHARE_SECURE #cmakedefine WITH_XC_TOUCHID #cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@" diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp index 9e260efcb..9c9bf88e2 100644 --- a/src/gui/AboutDialog.cpp +++ b/src/gui/AboutDialog.cpp @@ -88,8 +88,12 @@ AboutDialog::AboutDialog(QWidget* parent) #ifdef WITH_XC_SSHAGENT extensions += "\n- " + tr("SSH Agent"); #endif -#ifdef WITH_XC_KEESHARE - extensions += "\n- " + tr("KeeShare"); +#if defined(WITH_XC_KEESHARE_SECURE) && defined(WITH_XC_KEESHARE_INSECURE) + extensions += "\n- " + tr("KeeShare (secure and insecure sharing)"); +#elif defined(WITH_XC_KEESHARE_SECURE) + extensions += "\n- " + tr("KeeShare (secure sharing)"); +#elif defined(WITH_XC_KEESHARE_INSECURE) + extensions += "\n- " + tr("KeeShare (insecure sharing)"); #endif #ifdef WITH_XC_YUBIKEY extensions += "\n- " + tr("YubiKey"); diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp index dd56b923a..d1afbfa2f 100644 --- a/src/gui/DatabaseWidget.cpp +++ b/src/gui/DatabaseWidget.cpp @@ -373,7 +373,7 @@ void DatabaseWidget::replaceDatabase(QSharedPointer db) connectDatabaseSignals(); m_groupView->changeDatabase(m_db); processAutoOpen(); -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) KeeShare::instance()->connectDatabase(m_db, oldDb); #endif } diff --git a/src/gui/EntryPreviewWidget.cpp b/src/gui/EntryPreviewWidget.cpp index bc6227b6b..19ec53ae9 100644 --- a/src/gui/EntryPreviewWidget.cpp +++ b/src/gui/EntryPreviewWidget.cpp @@ -26,7 +26,7 @@ #include "core/FilePath.h" #include "entry/EntryAttachmentsModel.h" #include "gui/Clipboard.h" -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) #include "keeshare/KeeShare.h" #endif @@ -107,7 +107,7 @@ void EntryPreviewWidget::setGroup(Group* selectedGroup) updateGroupGeneralTab(); updateGroupNotesTab(); -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) updateGroupSharingTab(); #endif @@ -297,7 +297,7 @@ void EntryPreviewWidget::updateGroupNotesTab() m_ui->groupNotesEdit->setText(notes); } -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) void EntryPreviewWidget::updateGroupSharingTab() { Q_ASSERT(m_currentGroup); diff --git a/src/gui/EntryPreviewWidget.h b/src/gui/EntryPreviewWidget.h index f663925ca..5bfd9dbd6 100644 --- a/src/gui/EntryPreviewWidget.h +++ b/src/gui/EntryPreviewWidget.h @@ -57,7 +57,7 @@ private slots: void updateGroupHeaderLine(); void updateGroupGeneralTab(); void updateGroupNotesTab(); -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) void updateGroupSharingTab(); #endif diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp index c7d31796f..fd9610e55 100644 --- a/src/gui/MainWindow.cpp +++ b/src/gui/MainWindow.cpp @@ -43,7 +43,7 @@ #include "sshagent/AgentSettingsPage.h" #include "sshagent/SSHAgent.h" #endif -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) #include "keeshare/KeeShare.h" #include "keeshare/SettingsPageKeeShare.h" #endif @@ -158,7 +158,7 @@ MainWindow::MainWindow() m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget)); #endif -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) KeeShare::init(this); m_ui->settingsWidget->addSettingsPage(new SettingsPageKeeShare(m_ui->tabWidget)); connect(KeeShare::instance(), SIGNAL(sharingMessage(QString, MessageWidget::MessageType)), diff --git a/src/gui/dbsettings/DatabaseSettingsDialog.cpp b/src/gui/dbsettings/DatabaseSettingsDialog.cpp index 992351050..5a75127c2 100644 --- a/src/gui/dbsettings/DatabaseSettingsDialog.cpp +++ b/src/gui/dbsettings/DatabaseSettingsDialog.cpp @@ -25,7 +25,7 @@ #ifdef WITH_XC_BROWSER #include "DatabaseSettingsWidgetBrowser.h" #endif -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) #include "keeshare/DatabaseSettingsPageKeeShare.h" #endif @@ -80,7 +80,7 @@ DatabaseSettingsDialog::DatabaseSettingsDialog(QWidget* parent) m_securityTabWidget->addTab(m_masterKeyWidget, tr("Master Key")); m_securityTabWidget->addTab(m_encryptionWidget, tr("Encryption Settings")); -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) addSettingsPage(new DatabaseSettingsPageKeeShare()); #endif diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp index 4d024eabd..abfc26412 100644 --- a/src/gui/group/EditGroupWidget.cpp +++ b/src/gui/group/EditGroupWidget.cpp @@ -23,7 +23,7 @@ #include "gui/EditWidgetIcons.h" #include "gui/EditWidgetProperties.h" -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) #include "keeshare/group/EditGroupPageKeeShare.h" #endif @@ -63,7 +63,7 @@ EditGroupWidget::EditGroupWidget(QWidget* parent) addPage(tr("Group"), FilePath::instance()->icon("actions", "document-edit"), m_editGroupWidgetMain); addPage(tr("Icon"), FilePath::instance()->icon("apps", "preferences-desktop-icons"), m_editGroupWidgetIcons); -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) addEditPage(new EditGroupPageKeeShare(this)); #endif addPage(tr("Properties"), FilePath::instance()->icon("actions", "document-properties"), m_editWidgetProperties); diff --git a/src/gui/group/GroupModel.cpp b/src/gui/group/GroupModel.cpp index b9345495f..165eaa4a0 100644 --- a/src/gui/group/GroupModel.cpp +++ b/src/gui/group/GroupModel.cpp @@ -125,14 +125,14 @@ QVariant GroupModel::data(const QModelIndex& index, int role) const if (role == Qt::DisplayRole) { QString nameTemplate = tr("%1", "Template for name without annotation"); -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) nameTemplate = KeeShare::indicatorSuffix(group, nameTemplate); #endif return nameTemplate.arg(group->name()); } else if (role == Qt::DecorationRole) { QPixmap pixmap = group->isExpired() ? databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex) : group->iconScaledPixmap(); -#ifdef WITH_XC_KEESHARE +#if defined(WITH_XC_KEESHARE) pixmap = KeeShare::indicatorBadge(group, pixmap); #endif return pixmap; diff --git a/src/keeshare/CMakeLists.txt b/src/keeshare/CMakeLists.txt index 30a6bc4e1..14aa17b99 100644 --- a/src/keeshare/CMakeLists.txt +++ b/src/keeshare/CMakeLists.txt @@ -2,18 +2,22 @@ if(WITH_XC_KEESHARE) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) set(keeshare_SOURCES - SettingsPageKeeShare.cpp - SettingsWidgetKeeShare.cpp - DatabaseSettingsPageKeeShare.cpp - DatabaseSettingsWidgetKeeShare.cpp - group/EditGroupWidgetKeeShare.cpp - group/EditGroupPageKeeShare.cpp - KeeShare.cpp - KeeShareSettings.cpp - ShareObserver.cpp - Signature.cpp + SettingsPageKeeShare.cpp + SettingsWidgetKeeShare.cpp + DatabaseSettingsPageKeeShare.cpp + DatabaseSettingsWidgetKeeShare.cpp + group/EditGroupWidgetKeeShare.cpp + group/EditGroupPageKeeShare.cpp + KeeShare.cpp + KeeShareSettings.cpp + ShareObserver.cpp + Signature.cpp ) add_library(keeshare STATIC ${keeshare_SOURCES}) - target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${QUAZIP_LIBRARIES} ${crypto_ssh_LIB}) + if(WITH_XC_KEESHARE_SECURE) + target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${QUAZIP_LIBRARIES} ${crypto_ssh_LIB}) + else() + target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB}) + endif() endif() diff --git a/src/keeshare/KeeShare.cpp b/src/keeshare/KeeShare.cpp index 576751998..e552fa9c4 100644 --- a/src/keeshare/KeeShare.cpp +++ b/src/keeshare/KeeShare.cpp @@ -184,6 +184,18 @@ void KeeShare::connectDatabase(QSharedPointer newDb, QSharedPointer newDb, QSharedPointer oldDb); + static const QString& secureContainerFileType(); + static const QString& insecureContainerFileType(); + signals: void activeChanged(); void sharingMessage(QString, MessageWidget::MessageType); diff --git a/src/keeshare/ShareObserver.cpp b/src/keeshare/ShareObserver.cpp index 109aebf09..d26c68dcf 100644 --- a/src/keeshare/ShareObserver.cpp +++ b/src/keeshare/ShareObserver.cpp @@ -16,6 +16,7 @@ */ #include "ShareObserver.h" +#include "config-keepassx.h" #include "core/Clock.h" #include "core/Config.h" #include "core/CustomData.h" @@ -43,8 +44,10 @@ #include #include +#if defined(WITH_XC_KEESHARE_SECURE) #include #include +#endif namespace { @@ -61,6 +64,11 @@ enum Trust Own }; +bool isOfExportType(const QFileInfo &fileInfo, const QString type) +{ + return fileInfo.fileName().endsWith(type, Qt::CaseInsensitive); +} + QPair check(QByteArray& data, const KeeShareSettings::Reference& reference, const KeeShareSettings::Certificate& ownCertificate, @@ -68,52 +76,61 @@ QPair check(QByteArray& data, const KeeShareSettings::Sign& sign) { if (sign.signature.isEmpty()) { - QMessageBox warning; - warning.setIcon(QMessageBox::Warning); - warning.setWindowTitle(ShareObserver::tr("Untrustworthy container without signature")); - warning.setText(ShareObserver::tr("Do you want to import from unsigned container %1").arg(reference.path)); - auto yes = warning.addButton(ShareObserver::tr("Import once"), QMessageBox::ButtonRole::YesRole); - auto no = warning.addButton(ShareObserver::tr("No"), QMessageBox::ButtonRole::NoRole); - warning.setDefaultButton(no); - warning.exec(); - const auto trust = warning.clickedButton() == yes ? Single : None; - return qMakePair(trust, KeeShareSettings::Certificate()); + for (const auto& certificate : knownCertificates) { + if (certificate.key == certificate.key && certificate.trusted) { + return {Known, certificate}; + } + } } + else { + auto key = sign.certificate.sshKey(); + key.openKey(QString()); + const Signature signer; + if (!signer.verify(data, sign.signature, key)) { + qCritical("Invalid signature for sharing container %s.", qPrintable(reference.path)); + return {Invalid, KeeShareSettings::Certificate()}; + } - auto key = sign.certificate.sshKey(); - key.openKey(QString()); - const Signature signer; - if (!signer.verify(data, sign.signature, key)) { - const QFileInfo info(reference.path); - qCritical("Invalid signature for sharing container %s.", qPrintable(info.absoluteFilePath())); - return qMakePair(Invalid, KeeShareSettings::Certificate()); - } + if (ownCertificate.key == sign.certificate.key) { + return {Own, ownCertificate}; + } - if (ownCertificate.key == sign.certificate.key) { - return qMakePair(Own, ownCertificate); - } - - for (const auto& certificate : knownCertificates) { - if (certificate.key == certificate.key && certificate.trusted) { - return qMakePair(Known, certificate); + for (const auto& certificate : knownCertificates) { + if (certificate.key == certificate.key && certificate.trusted) { + return {Known, certificate}; + } } } QMessageBox warning; - warning.setIcon(QMessageBox::Question); - warning.setWindowTitle(ShareObserver::tr("Import from untrustworthy certificate for sharing container")); - warning.setText(ShareObserver::tr("Do you want to trust %1 with the fingerprint of %2") - .arg(sign.certificate.signer) - .arg(sign.certificate.fingerprint())); - auto yes = warning.addButton(ShareObserver::tr("Import and trust"), QMessageBox::ButtonRole::YesRole); - auto no = warning.addButton(ShareObserver::tr("No"), QMessageBox::ButtonRole::NoRole); - warning.setDefaultButton(no); - warning.exec(); - if (warning.clickedButton() != yes) { - qWarning("Prevented import due to untrusted certificate of %s", qPrintable(sign.certificate.signer)); - return qMakePair(None, sign.certificate); + KeeShareSettings::Certificate certificate; + if (sign.signature.isEmpty()){ + warning.setIcon(QMessageBox::Warning); + warning.setWindowTitle(ShareObserver::tr("Untrustworthy container without signature")); + warning.setText(ShareObserver::tr("We cannot verify the source of the shared container because it is not signed. Do you really want to import %1?").arg(reference.path)); + certificate = KeeShareSettings::Certificate(); } - return qMakePair(Lasting, sign.certificate); + else { + warning.setIcon(QMessageBox::Question); + warning.setWindowTitle(ShareObserver::tr("Import from untrustworthy certificate for sharing container")); + warning.setText(ShareObserver::tr("Do you want to trust %1 with the fingerprint of %2") + .arg(sign.certificate.signer) + .arg(sign.certificate.fingerprint())); + certificate = sign.certificate; + } + auto once = warning.addButton(ShareObserver::tr("Only this time"), QMessageBox::ButtonRole::YesRole); + auto always = warning.addButton(ShareObserver::tr("Always"), QMessageBox::ButtonRole::YesRole); + auto abort = warning.addButton(ShareObserver::tr("No"), QMessageBox::ButtonRole::NoRole); + warning.setDefaultButton(abort); + warning.exec(); + if (warning.clickedButton() == once){ + return {Single, certificate}; + } + if (warning.clickedButton() == always){ + return {Lasting, certificate}; + } + qWarning("Prevented import due to untrusted certificate of %s", qPrintable(sign.certificate.signer)); + return {None, certificate}; } } // End Namespace @@ -266,16 +283,15 @@ void ShareObserver::handleFileUpdated(const QString& path) notifyAbout(success, warning, error); } -ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup) +ShareObserver::Result ShareObserver::importSecureContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup) { - const QFileInfo info(reference.path); - if (!info.exists()) { - qCritical("File %s does not exist.", qPrintable(info.absoluteFilePath())); - return {reference.path, Result::Warning, tr("File does not exist")}; - } - QuaZip zip(info.absoluteFilePath()); +#if !defined(WITH_XC_KEESHARE_SECURE) + Q_UNUSED(targetGroup); + return { reference.path, Result::Error, tr("Secured share container are not supported") }; +#else + QuaZip zip(reference.path); if (!zip.open(QuaZip::mdUnzip)) { - qCritical("Unable to open file %s.", qPrintable(info.absoluteFilePath())); + qCritical("Unable to open file %s.", qPrintable(reference.path)); return {reference.path, Result::Error, tr("File is not readable")}; } const auto expected = QSet() << KeeShare_Signature << KeeShare_Container; @@ -285,7 +301,7 @@ ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings: actual << file.name; } if (expected != actual) { - qCritical("Invalid sharing container %s.", qPrintable(info.absoluteFilePath())); + qCritical("Invalid sharing container %s.", qPrintable(reference.path)); return {reference.path, Result::Error, tr("Invalid sharing container")}; } @@ -313,6 +329,7 @@ ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings: qCritical("Error while parsing the database: %s", qPrintable(reader.errorString())); return {reference.path, Result::Error, reader.errorString()}; } + auto foreign = KeeShare::foreign(); auto own = KeeShare::own(); auto trusted = check(payload, reference, own.certificate, foreign.certificates, sign); @@ -352,14 +369,99 @@ ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings: merger.setForcedMergeMode(Group::Synchronize); const bool changed = merger.merge(); if (changed) { - return {reference.path, Result::Success, tr("Successful import")}; + return {reference.path, Result::Success, tr("Successful secured import")}; } return {}; } default: Q_ASSERT(false); + return {reference.path, Result::Error, tr("Unexpected error")}; + } +#endif +} + +ShareObserver::Result ShareObserver::importInsecureContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup) +{ +#if !defined(WITH_XC_KEESHARE_INSECURE) + Q_UNUSED(targetGroup); + return {reference.path, Result::Error, tr("Insecured share container are not supported")}; +#else + QFile file(reference.path); + if (!file.open(QIODevice::ReadOnly)){ + qCritical("Unable to open file %s.", qPrintable(reference.path)); + return {reference.path, Result::Error, tr("File is not readable")}; + } + auto payload = file.readAll(); + file.close(); + QBuffer buffer(&payload); + buffer.open(QIODevice::ReadOnly); + + KeePass2Reader reader; + auto key = QSharedPointer::create(); + key->addKey(QSharedPointer::create(reference.password)); + auto sourceDb = QSharedPointer::create(); + if (!reader.readDatabase(&buffer, key, sourceDb.data())) { + qCritical("Error while parsing the database: %s", qPrintable(reader.errorString())); + return {reference.path, Result::Error, reader.errorString()}; + } + + auto foreign = KeeShare::foreign(); + auto own = KeeShare::own(); + static KeeShareSettings::Sign sign; // invalid sign + auto trusted = check(payload, reference, own.certificate, foreign.certificates, sign); + switch(trusted.first) { + case Known: + case Lasting: { + bool found = false; + for (KeeShareSettings::Certificate& knownCertificate : foreign.certificates) { + if (knownCertificate.key == trusted.second.key) { + knownCertificate.signer = trusted.second.signer; + knownCertificate.trusted = true; + found = true; + } + } + if (!found) { + foreign.certificates << trusted.second; + // we need to update with the new signer + KeeShare::setForeign(foreign); + } + } + [[fallthrough]]; + case Single: { + qDebug("Synchronize %s %s with %s", + qPrintable(reference.path), + qPrintable(targetGroup->name()), + qPrintable(sourceDb->rootGroup()->name())); + Merger merger(sourceDb->rootGroup(), targetGroup); + merger.setForcedMergeMode(Group::Synchronize); + const bool changed = merger.merge(); + if (changed) { + return {reference.path, Result::Success, tr("Successful unsecured import")}; + } return {}; } + default: + qWarning("Prevent untrusted import"); + return {reference.path, Result::Warning, tr("Untrusted import prevented")}; + } +#endif +} + +ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup) +{ + const QFileInfo info(reference.path); + if (!info.exists()) { + qCritical("File %s does not exist.", qPrintable(info.absoluteFilePath())); + return {reference.path, Result::Warning, tr("File does not exist")}; + } + + if (isOfExportType(info, KeeShare::secureContainerFileType())) { + return importSecureContainerInto(reference, targetGroup); + } + if (isOfExportType(info, KeeShare::insecureContainerFileType())) { + return importInsecureContainerInto(reference, targetGroup); + } + return {reference.path, Result::Error, tr("Unknown share container type")}; } ShareObserver::Result ShareObserver::importFromReferenceContainer(const QString& path) @@ -465,10 +567,98 @@ QSharedPointer ShareObserver::database() return m_db; } +ShareObserver::Result ShareObserver::exportIntoReferenceSecureContainer(const KeeShareSettings::Reference &reference, Database *targetDb) +{ +#if !defined(WITH_XC_KEESHARE_SECURE) + Q_UNUSED(targetDb); + return {reference.path, Result::Error, tr("Overwriting secured share container is not supported")}; +#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, Result::Error, writer.errorString()}; + } + } + const auto own = KeeShare::own(); + QuaZip zip(reference.path); + zip.setFileNameCodec("UTF-8"); + const bool zipOpened = zip.open(QuaZip::mdCreate); + if (!zipOpened) { + ::qWarning("Opening export file failed: %d", zip.getZipError()); + return {reference.path, Result::Error, tr("Could not write export container (%1)").arg(zip.getZipError())}; + } + { + QuaZipFile file(&zip); + const auto signatureOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Signature)); + if (!signatureOpened) { + ::qWarning("Embedding signature failed: %d", zip.getZipError()); + return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())}; + } + QTextStream stream(&file); + KeeShareSettings::Sign sign; + auto sshKey = own.key.sshKey(); + sshKey.openKey(QString()); + const Signature signer; + sign.signature = signer.create(bytes, sshKey); + sign.certificate = own.certificate; + stream << KeeShareSettings::Sign::serialize(sign); + stream.flush(); + if (file.getZipError() != ZIP_OK) { + ::qWarning("Embedding signature failed: %d", zip.getZipError()); + return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())}; + } + file.close(); + } + { + QuaZipFile file(&zip); + const auto dbOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Container)); + if (!dbOpened) { + ::qWarning("Embedding database failed: %d", zip.getZipError()); + return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())}; + } + if (file.getZipError() != ZIP_OK) { + ::qWarning("Embedding database failed: %d", zip.getZipError()); + return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())}; + } + file.write(bytes); + file.close(); + } + zip.close(); + return {reference.path}; +#endif +} + +ShareObserver::Result ShareObserver::exportIntoReferenceInsecureContainer(const KeeShareSettings::Reference &reference, Database *targetDb) +{ +#if !defined(WITH_XC_KEESHARE_INSECURE) + Q_UNUSED(targetDb); + return {reference.path, Result::Error, tr("Overwriting secured share container is not supported")}; +#else + QFile file(reference.path); + const bool fileOpened = file.open(QIODevice::WriteOnly); + if (!fileOpened) { + ::qWarning("Opening export file failed"); + return {reference.path, Result::Error, 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, Result::Error, writer.errorString()}; + } + file.close(); +#endif + return {reference.path}; +} + QList ShareObserver::exportIntoReferenceContainers() { QList results; - const auto own = KeeShare::own(); const auto groups = m_db->rootGroup()->groupsRecursive(true); for (const auto* group : groups) { const auto reference = KeeShare::referenceOf(group); @@ -478,76 +668,19 @@ QList ShareObserver::exportIntoReferenceContainers() m_fileWatcher->ignoreFileChanges(reference.path); QScopedPointer targetDb(exportIntoContainer(reference, group)); - QByteArray bytes; - { - QBuffer buffer(&bytes); - buffer.open(QIODevice::WriteOnly); - KeePass2Writer writer; - writer.writeDatabase(&buffer, targetDb.data()); - if (writer.hasError()) { - qWarning("Serializing export dabase failed: %s.", writer.errorString().toLatin1().data()); - results << Result{reference.path, Result::Error, writer.errorString()}; - m_fileWatcher->observeFileChanges(true); - continue; - } - } - QuaZip zip(reference.path); - zip.setFileNameCodec("UTF-8"); - const bool zipOpened = zip.open(QuaZip::mdCreate); - if (!zipOpened) { - ::qWarning("Opening export file failed: %d", zip.getZipError()); - results << Result{reference.path, Result::Error, tr("Could not write export container (%1)").arg(zip.getZipError())}; + QFileInfo info(reference.path); + if (isOfExportType(info, KeeShare::secureContainerFileType())) { + results << exportIntoReferenceSecureContainer(reference, targetDb.data()); m_fileWatcher->observeFileChanges(true); continue; } - { - QuaZipFile file(&zip); - const auto signatureOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Signature)); - if (!signatureOpened) { - ::qWarning("Embedding signature failed: %d", zip.getZipError()); - results << Result{reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())}; - m_fileWatcher->observeFileChanges(true); - continue; - } - QTextStream stream(&file); - KeeShareSettings::Sign sign; - auto sshKey = own.key.sshKey(); - sshKey.openKey(QString()); - const Signature signer; - sign.signature = signer.create(bytes, sshKey); - sign.certificate = own.certificate; - stream << KeeShareSettings::Sign::serialize(sign); - stream.flush(); - if (file.getZipError() != ZIP_OK) { - ::qWarning("Embedding signature failed: %d", zip.getZipError()); - results << Result{reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())}; - m_fileWatcher->observeFileChanges(true); - continue; - } - file.close(); + if (isOfExportType(info, KeeShare::insecureContainerFileType())) { + results << exportIntoReferenceInsecureContainer(reference, targetDb.data()); + m_fileWatcher->observeFileChanges(true); + continue; } - { - QuaZipFile file(&zip); - const auto dbOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Container)); - if (!dbOpened) { - ::qWarning("Embedding database failed: %d", zip.getZipError()); - results << Result{reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())}; - m_fileWatcher->observeFileChanges(true); - continue; - } - if (file.getZipError() != ZIP_OK) { - ::qWarning("Embedding database failed: %d", zip.getZipError()); - results << Result{reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())}; - m_fileWatcher->observeFileChanges(true); - continue; - } - file.write(bytes); - file.close(); - } - zip.close(); - - m_fileWatcher->observeFileChanges(true); - results << Result{reference.path}; + Q_ASSERT(false); + results << Result{reference.path, Result::Error, tr("Unexpected export error occurred")}; } return results; } diff --git a/src/keeshare/ShareObserver.h b/src/keeshare/ShareObserver.h index 28b80e093..b21934389 100644 --- a/src/keeshare/ShareObserver.h +++ b/src/keeshare/ShareObserver.h @@ -77,10 +77,16 @@ private: static void resolveReferenceAttributes(Entry* targetEntry, const Database* sourceDb); static Database* exportIntoContainer(const KeeShareSettings::Reference& reference, const Group* sourceRoot); + static Result exportIntoReferenceInsecureContainer(const KeeShareSettings::Reference &reference, Database *targetDb); + static Result exportIntoReferenceSecureContainer(const KeeShareSettings::Reference &reference, Database *targetDb); + static Result importSecureContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup); + static Result importInsecureContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup); static Result importContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup); + static Result importDatabaseInto(); Result importFromReferenceContainer(const QString& path); - QList exportIntoReferenceContainers(); + QList exportIntoReferenceContainers(); + void deinitialize(); void reinitialize(); void notifyAbout(const QStringList& success, const QStringList& warning, const QStringList& error); diff --git a/src/keeshare/group/EditGroupWidgetKeeShare.cpp b/src/keeshare/group/EditGroupWidgetKeeShare.cpp index 2d2205c69..2881a6630 100644 --- a/src/keeshare/group/EditGroupWidgetKeeShare.cpp +++ b/src/keeshare/group/EditGroupWidgetKeeShare.cpp @@ -170,26 +170,36 @@ void EditGroupWidgetKeeShare::selectPath() defaultDirPath = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first(); } auto reference = KeeShare::referenceOf(m_temporaryGroup); - const auto filetype = tr("kdbx.share", "Filetype for KeeShare container"); - const auto filters = QString("%1 (*." + filetype + ");;%2 (*)").arg(tr("KeeShare Container"), tr("All files")); + QString defaultFiletype = ""; + auto knownFilters = QStringList() << QString("%1 (*)").arg("All files"); +#if defined(WITH_XC_KEESHARE_INSECURE) + defaultFiletype = KeeShare::secureContainerFileType(); + knownFilters.prepend(QString("%1 (*.%2)").arg(tr("KeeShare insecure container"), KeeShare::insecureContainerFileType())); +#endif +#if defined(WITH_XC_KEESHARE_SECURE) + defaultFiletype = KeeShare::secureContainerFileType(); + knownFilters.prepend(QString("%1 (*.%2)").arg(tr("KeeShare secure container"), KeeShare::secureContainerFileType())); +#endif + + const auto filters = knownFilters.join(";;"); auto filename = reference.path; if (filename.isEmpty()) { - filename = tr("%1.%2", "Template for KeeShare container").arg(m_temporaryGroup->name()).arg(filetype); + filename = tr("%1.%2", "Template for KeeShare container").arg(m_temporaryGroup->name()).arg(defaultFiletype); } switch (reference.type) { case KeeShareSettings::ImportFrom: filename = fileDialog()->getFileName( this, tr("Select import source"), defaultDirPath, filters, nullptr, QFileDialog::DontConfirmOverwrite, - filetype, filename); + defaultFiletype, filename); break; case KeeShareSettings::ExportTo: filename = fileDialog()->getFileName( - this, tr("Select export target"), defaultDirPath, filters, nullptr, 0, filetype, filename); + this, tr("Select export target"), defaultDirPath, filters, nullptr, QFileDialog::Option(0), defaultFiletype, filename); break; case KeeShareSettings::SynchronizeWith: case KeeShareSettings::Inactive: filename = fileDialog()->getFileName( - this, tr("Select import/export file"), defaultDirPath, filters, nullptr, 0, filetype, filename); + this, tr("Select import/export file"), defaultDirPath, filters, nullptr, QFileDialog::Option(0), defaultFiletype, filename); break; } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index f984874d8..390dda57b 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -141,7 +141,7 @@ add_unit_test(NAME testsymmetriccipher SOURCES TestSymmetricCipher.cpp if(WITH_XC_KEESHARE) add_unit_test(NAME testsignature SOURCES TestSignature.cpp - LIBS ${TEST_LIBRARIES}) + LIBS ${TEST_LIBRARIES}) endif() add_unit_test(NAME testhashedblockstream SOURCES TestHashedBlockStream.cpp