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)
This commit is contained in:
Christian Kieschnick 2019-01-03 08:46:32 +01:00
parent 52dcc2855e
commit d4c391deb2
18 changed files with 337 additions and 153 deletions

View file

@ -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_BROWSER "Include browser integration with keepassxc-browser." OFF)
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF) option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent 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) if(APPLE)
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF) option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
endif() endif()
@ -59,12 +60,20 @@ if(WITH_XC_ALL)
set(WITH_XC_BROWSER ON) set(WITH_XC_BROWSER ON)
set(WITH_XC_YUBIKEY ON) set(WITH_XC_YUBIKEY ON)
set(WITH_XC_SSHAGENT 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) if(APPLE)
set(WITH_XC_TOUCHID ON) set(WITH_XC_TOUCHID ON)
endif() endif()
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) if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
set(WITH_XC_CRYPTO_SSH ON) set(WITH_XC_CRYPTO_SSH ON)
else() else()
@ -358,7 +367,7 @@ endif()
include_directories(SYSTEM ${ARGON2_INCLUDE_DIR}) include_directories(SYSTEM ${ARGON2_INCLUDE_DIR})
# Optional # Optional
if(WITH_XC_KEESHARE) if(WITH_XC_KEESHARE_SECURE)
find_package(QuaZip REQUIRED) find_package(QuaZip REQUIRED)
include_directories(SYSTEM ${QUAZIP_INCLUDE_DIR}) include_directories(SYSTEM ${QUAZIP_INCLUDE_DIR})

View file

@ -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(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(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(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") add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
if(APPLE) if(APPLE)
add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration") add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration")

View file

@ -18,6 +18,8 @@
#cmakedefine WITH_XC_YUBIKEY #cmakedefine WITH_XC_YUBIKEY
#cmakedefine WITH_XC_SSHAGENT #cmakedefine WITH_XC_SSHAGENT
#cmakedefine WITH_XC_KEESHARE #cmakedefine WITH_XC_KEESHARE
#cmakedefine WITH_XC_KEESHARE_INSECURE
#cmakedefine WITH_XC_KEESHARE_SECURE
#cmakedefine WITH_XC_TOUCHID #cmakedefine WITH_XC_TOUCHID
#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@" #cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"

View file

@ -88,8 +88,12 @@ AboutDialog::AboutDialog(QWidget* parent)
#ifdef WITH_XC_SSHAGENT #ifdef WITH_XC_SSHAGENT
extensions += "\n- " + tr("SSH Agent"); extensions += "\n- " + tr("SSH Agent");
#endif #endif
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE_SECURE) && defined(WITH_XC_KEESHARE_INSECURE)
extensions += "\n- " + tr("KeeShare"); 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 #endif
#ifdef WITH_XC_YUBIKEY #ifdef WITH_XC_YUBIKEY
extensions += "\n- " + tr("YubiKey"); extensions += "\n- " + tr("YubiKey");

View file

@ -373,7 +373,7 @@ void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
connectDatabaseSignals(); connectDatabaseSignals();
m_groupView->changeDatabase(m_db); m_groupView->changeDatabase(m_db);
processAutoOpen(); processAutoOpen();
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
KeeShare::instance()->connectDatabase(m_db, oldDb); KeeShare::instance()->connectDatabase(m_db, oldDb);
#endif #endif
} }

View file

@ -26,7 +26,7 @@
#include "core/FilePath.h" #include "core/FilePath.h"
#include "entry/EntryAttachmentsModel.h" #include "entry/EntryAttachmentsModel.h"
#include "gui/Clipboard.h" #include "gui/Clipboard.h"
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
#include "keeshare/KeeShare.h" #include "keeshare/KeeShare.h"
#endif #endif
@ -107,7 +107,7 @@ void EntryPreviewWidget::setGroup(Group* selectedGroup)
updateGroupGeneralTab(); updateGroupGeneralTab();
updateGroupNotesTab(); updateGroupNotesTab();
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
updateGroupSharingTab(); updateGroupSharingTab();
#endif #endif
@ -297,7 +297,7 @@ void EntryPreviewWidget::updateGroupNotesTab()
m_ui->groupNotesEdit->setText(notes); m_ui->groupNotesEdit->setText(notes);
} }
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
void EntryPreviewWidget::updateGroupSharingTab() void EntryPreviewWidget::updateGroupSharingTab()
{ {
Q_ASSERT(m_currentGroup); Q_ASSERT(m_currentGroup);

View file

@ -57,7 +57,7 @@ private slots:
void updateGroupHeaderLine(); void updateGroupHeaderLine();
void updateGroupGeneralTab(); void updateGroupGeneralTab();
void updateGroupNotesTab(); void updateGroupNotesTab();
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
void updateGroupSharingTab(); void updateGroupSharingTab();
#endif #endif

View file

@ -43,7 +43,7 @@
#include "sshagent/AgentSettingsPage.h" #include "sshagent/AgentSettingsPage.h"
#include "sshagent/SSHAgent.h" #include "sshagent/SSHAgent.h"
#endif #endif
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
#include "keeshare/KeeShare.h" #include "keeshare/KeeShare.h"
#include "keeshare/SettingsPageKeeShare.h" #include "keeshare/SettingsPageKeeShare.h"
#endif #endif
@ -158,7 +158,7 @@ MainWindow::MainWindow()
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget)); m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
#endif #endif
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
KeeShare::init(this); KeeShare::init(this);
m_ui->settingsWidget->addSettingsPage(new SettingsPageKeeShare(m_ui->tabWidget)); m_ui->settingsWidget->addSettingsPage(new SettingsPageKeeShare(m_ui->tabWidget));
connect(KeeShare::instance(), SIGNAL(sharingMessage(QString, MessageWidget::MessageType)), connect(KeeShare::instance(), SIGNAL(sharingMessage(QString, MessageWidget::MessageType)),

View file

@ -25,7 +25,7 @@
#ifdef WITH_XC_BROWSER #ifdef WITH_XC_BROWSER
#include "DatabaseSettingsWidgetBrowser.h" #include "DatabaseSettingsWidgetBrowser.h"
#endif #endif
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
#include "keeshare/DatabaseSettingsPageKeeShare.h" #include "keeshare/DatabaseSettingsPageKeeShare.h"
#endif #endif
@ -80,7 +80,7 @@ DatabaseSettingsDialog::DatabaseSettingsDialog(QWidget* parent)
m_securityTabWidget->addTab(m_masterKeyWidget, tr("Master Key")); m_securityTabWidget->addTab(m_masterKeyWidget, tr("Master Key"));
m_securityTabWidget->addTab(m_encryptionWidget, tr("Encryption Settings")); m_securityTabWidget->addTab(m_encryptionWidget, tr("Encryption Settings"));
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
addSettingsPage(new DatabaseSettingsPageKeeShare()); addSettingsPage(new DatabaseSettingsPageKeeShare());
#endif #endif

View file

@ -23,7 +23,7 @@
#include "gui/EditWidgetIcons.h" #include "gui/EditWidgetIcons.h"
#include "gui/EditWidgetProperties.h" #include "gui/EditWidgetProperties.h"
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
#include "keeshare/group/EditGroupPageKeeShare.h" #include "keeshare/group/EditGroupPageKeeShare.h"
#endif #endif
@ -63,7 +63,7 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
addPage(tr("Group"), FilePath::instance()->icon("actions", "document-edit"), m_editGroupWidgetMain); addPage(tr("Group"), FilePath::instance()->icon("actions", "document-edit"), m_editGroupWidgetMain);
addPage(tr("Icon"), FilePath::instance()->icon("apps", "preferences-desktop-icons"), m_editGroupWidgetIcons); 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)); addEditPage(new EditGroupPageKeeShare(this));
#endif #endif
addPage(tr("Properties"), FilePath::instance()->icon("actions", "document-properties"), m_editWidgetProperties); addPage(tr("Properties"), FilePath::instance()->icon("actions", "document-properties"), m_editWidgetProperties);

View file

@ -125,14 +125,14 @@ QVariant GroupModel::data(const QModelIndex& index, int role) const
if (role == Qt::DisplayRole) { if (role == Qt::DisplayRole) {
QString nameTemplate = tr("%1", "Template for name without annotation"); QString nameTemplate = tr("%1", "Template for name without annotation");
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
nameTemplate = KeeShare::indicatorSuffix(group, nameTemplate); nameTemplate = KeeShare::indicatorSuffix(group, nameTemplate);
#endif #endif
return nameTemplate.arg(group->name()); return nameTemplate.arg(group->name());
} else if (role == Qt::DecorationRole) { } else if (role == Qt::DecorationRole) {
QPixmap pixmap = group->isExpired() ? databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex) QPixmap pixmap = group->isExpired() ? databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex)
: group->iconScaledPixmap(); : group->iconScaledPixmap();
#ifdef WITH_XC_KEESHARE #if defined(WITH_XC_KEESHARE)
pixmap = KeeShare::indicatorBadge(group, pixmap); pixmap = KeeShare::indicatorBadge(group, pixmap);
#endif #endif
return pixmap; return pixmap;

View file

@ -2,18 +2,22 @@ if(WITH_XC_KEESHARE)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
set(keeshare_SOURCES set(keeshare_SOURCES
SettingsPageKeeShare.cpp SettingsPageKeeShare.cpp
SettingsWidgetKeeShare.cpp SettingsWidgetKeeShare.cpp
DatabaseSettingsPageKeeShare.cpp DatabaseSettingsPageKeeShare.cpp
DatabaseSettingsWidgetKeeShare.cpp DatabaseSettingsWidgetKeeShare.cpp
group/EditGroupWidgetKeeShare.cpp group/EditGroupWidgetKeeShare.cpp
group/EditGroupPageKeeShare.cpp group/EditGroupPageKeeShare.cpp
KeeShare.cpp KeeShare.cpp
KeeShareSettings.cpp KeeShareSettings.cpp
ShareObserver.cpp ShareObserver.cpp
Signature.cpp Signature.cpp
) )
add_library(keeshare STATIC ${keeshare_SOURCES}) 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() endif()

View file

@ -184,6 +184,18 @@ void KeeShare::connectDatabase(QSharedPointer<Database> newDb, QSharedPointer<Da
} }
} }
const QString &KeeShare::secureContainerFileType()
{
static const QString filetype("kdbx.share");
return filetype;
}
const QString &KeeShare::insecureContainerFileType()
{
static const QString filetype("kdbx");
return filetype;
}
void KeeShare::handleSettingsChanged(const QString& key) void KeeShare::handleSettingsChanged(const QString& key)
{ {
if (key == KeeShare_Active) { if (key == KeeShare_Active) {

View file

@ -55,6 +55,9 @@ public:
void connectDatabase(QSharedPointer<Database> newDb, QSharedPointer<Database> oldDb); void connectDatabase(QSharedPointer<Database> newDb, QSharedPointer<Database> oldDb);
static const QString& secureContainerFileType();
static const QString& insecureContainerFileType();
signals: signals:
void activeChanged(); void activeChanged();
void sharingMessage(QString, MessageWidget::MessageType); void sharingMessage(QString, MessageWidget::MessageType);

View file

@ -16,6 +16,7 @@
*/ */
#include "ShareObserver.h" #include "ShareObserver.h"
#include "config-keepassx.h"
#include "core/Clock.h" #include "core/Clock.h"
#include "core/Config.h" #include "core/Config.h"
#include "core/CustomData.h" #include "core/CustomData.h"
@ -43,8 +44,10 @@
#include <QPushButton> #include <QPushButton>
#include <QStringBuilder> #include <QStringBuilder>
#if defined(WITH_XC_KEESHARE_SECURE)
#include <quazip5/quazip.h> #include <quazip5/quazip.h>
#include <quazip5/quazipfile.h> #include <quazip5/quazipfile.h>
#endif
namespace namespace
{ {
@ -61,6 +64,11 @@ enum Trust
Own Own
}; };
bool isOfExportType(const QFileInfo &fileInfo, const QString type)
{
return fileInfo.fileName().endsWith(type, Qt::CaseInsensitive);
}
QPair<Trust, KeeShareSettings::Certificate> check(QByteArray& data, QPair<Trust, KeeShareSettings::Certificate> check(QByteArray& data,
const KeeShareSettings::Reference& reference, const KeeShareSettings::Reference& reference,
const KeeShareSettings::Certificate& ownCertificate, const KeeShareSettings::Certificate& ownCertificate,
@ -68,52 +76,61 @@ QPair<Trust, KeeShareSettings::Certificate> check(QByteArray& data,
const KeeShareSettings::Sign& sign) const KeeShareSettings::Sign& sign)
{ {
if (sign.signature.isEmpty()) { if (sign.signature.isEmpty()) {
QMessageBox warning; for (const auto& certificate : knownCertificates) {
warning.setIcon(QMessageBox::Warning); if (certificate.key == certificate.key && certificate.trusted) {
warning.setWindowTitle(ShareObserver::tr("Untrustworthy container without signature")); return {Known, certificate};
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());
} }
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(); if (ownCertificate.key == sign.certificate.key) {
key.openKey(QString()); return {Own, ownCertificate};
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) { for (const auto& certificate : knownCertificates) {
return qMakePair(Own, ownCertificate); if (certificate.key == certificate.key && certificate.trusted) {
} return {Known, certificate};
}
for (const auto& certificate : knownCertificates) {
if (certificate.key == certificate.key && certificate.trusted) {
return qMakePair(Known, certificate);
} }
} }
QMessageBox warning; QMessageBox warning;
warning.setIcon(QMessageBox::Question); KeeShareSettings::Certificate certificate;
warning.setWindowTitle(ShareObserver::tr("Import from untrustworthy certificate for sharing container")); if (sign.signature.isEmpty()){
warning.setText(ShareObserver::tr("Do you want to trust %1 with the fingerprint of %2") warning.setIcon(QMessageBox::Warning);
.arg(sign.certificate.signer) warning.setWindowTitle(ShareObserver::tr("Untrustworthy container without signature"));
.arg(sign.certificate.fingerprint())); 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));
auto yes = warning.addButton(ShareObserver::tr("Import and trust"), QMessageBox::ButtonRole::YesRole); certificate = KeeShareSettings::Certificate();
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);
} }
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 } // End Namespace
@ -266,16 +283,15 @@ void ShareObserver::handleFileUpdated(const QString& path)
notifyAbout(success, warning, error); 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 !defined(WITH_XC_KEESHARE_SECURE)
if (!info.exists()) { Q_UNUSED(targetGroup);
qCritical("File %s does not exist.", qPrintable(info.absoluteFilePath())); return { reference.path, Result::Error, tr("Secured share container are not supported") };
return {reference.path, Result::Warning, tr("File does not exist")}; #else
} QuaZip zip(reference.path);
QuaZip zip(info.absoluteFilePath());
if (!zip.open(QuaZip::mdUnzip)) { 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")}; return {reference.path, Result::Error, tr("File is not readable")};
} }
const auto expected = QSet<QString>() << KeeShare_Signature << KeeShare_Container; const auto expected = QSet<QString>() << KeeShare_Signature << KeeShare_Container;
@ -285,7 +301,7 @@ ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings:
actual << file.name; actual << file.name;
} }
if (expected != actual) { 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")}; 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())); qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
return {reference.path, Result::Error, reader.errorString()}; return {reference.path, Result::Error, reader.errorString()};
} }
auto foreign = KeeShare::foreign(); auto foreign = KeeShare::foreign();
auto own = KeeShare::own(); auto own = KeeShare::own();
auto trusted = check(payload, reference, own.certificate, foreign.certificates, sign); auto trusted = check(payload, reference, own.certificate, foreign.certificates, sign);
@ -352,14 +369,99 @@ ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings:
merger.setForcedMergeMode(Group::Synchronize); merger.setForcedMergeMode(Group::Synchronize);
const bool changed = merger.merge(); const bool changed = merger.merge();
if (changed) { if (changed) {
return {reference.path, Result::Success, tr("Successful import")}; return {reference.path, Result::Success, tr("Successful secured import")};
} }
return {}; return {};
} }
default: default:
Q_ASSERT(false); 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<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
auto sourceDb = QSharedPointer<Database>::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 {}; 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) ShareObserver::Result ShareObserver::importFromReferenceContainer(const QString& path)
@ -465,10 +567,98 @@ QSharedPointer<Database> ShareObserver::database()
return m_db; 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::Result> ShareObserver::exportIntoReferenceContainers() QList<ShareObserver::Result> ShareObserver::exportIntoReferenceContainers()
{ {
QList<Result> results; QList<Result> results;
const auto own = KeeShare::own();
const auto groups = m_db->rootGroup()->groupsRecursive(true); const auto groups = m_db->rootGroup()->groupsRecursive(true);
for (const auto* group : groups) { for (const auto* group : groups) {
const auto reference = KeeShare::referenceOf(group); const auto reference = KeeShare::referenceOf(group);
@ -478,76 +668,19 @@ QList<ShareObserver::Result> ShareObserver::exportIntoReferenceContainers()
m_fileWatcher->ignoreFileChanges(reference.path); m_fileWatcher->ignoreFileChanges(reference.path);
QScopedPointer<Database> targetDb(exportIntoContainer(reference, group)); QScopedPointer<Database> targetDb(exportIntoContainer(reference, group));
QByteArray bytes; QFileInfo info(reference.path);
{ if (isOfExportType(info, KeeShare::secureContainerFileType())) {
QBuffer buffer(&bytes); results << exportIntoReferenceSecureContainer(reference, targetDb.data());
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())};
m_fileWatcher->observeFileChanges(true); m_fileWatcher->observeFileChanges(true);
continue; continue;
} }
{ if (isOfExportType(info, KeeShare::insecureContainerFileType())) {
QuaZipFile file(&zip); results << exportIntoReferenceInsecureContainer(reference, targetDb.data());
const auto signatureOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Signature)); m_fileWatcher->observeFileChanges(true);
if (!signatureOpened) { continue;
::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();
} }
{ Q_ASSERT(false);
QuaZipFile file(&zip); results << Result{reference.path, Result::Error, tr("Unexpected export error occurred")};
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};
} }
return results; return results;
} }

View file

@ -77,10 +77,16 @@ private:
static void resolveReferenceAttributes(Entry* targetEntry, const Database* sourceDb); static void resolveReferenceAttributes(Entry* targetEntry, const Database* sourceDb);
static Database* exportIntoContainer(const KeeShareSettings::Reference& reference, const Group* sourceRoot); 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 importContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup);
static Result importDatabaseInto();
Result importFromReferenceContainer(const QString& path); Result importFromReferenceContainer(const QString& path);
QList<ShareObserver::Result> exportIntoReferenceContainers(); QList<Result> exportIntoReferenceContainers();
void deinitialize(); void deinitialize();
void reinitialize(); void reinitialize();
void notifyAbout(const QStringList& success, const QStringList& warning, const QStringList& error); void notifyAbout(const QStringList& success, const QStringList& warning, const QStringList& error);

View file

@ -170,26 +170,36 @@ void EditGroupWidgetKeeShare::selectPath()
defaultDirPath = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first(); defaultDirPath = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first();
} }
auto reference = KeeShare::referenceOf(m_temporaryGroup); auto reference = KeeShare::referenceOf(m_temporaryGroup);
const auto filetype = tr("kdbx.share", "Filetype for KeeShare container"); QString defaultFiletype = "";
const auto filters = QString("%1 (*." + filetype + ");;%2 (*)").arg(tr("KeeShare Container"), tr("All files")); 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; auto filename = reference.path;
if (filename.isEmpty()) { 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) { switch (reference.type) {
case KeeShareSettings::ImportFrom: case KeeShareSettings::ImportFrom:
filename = fileDialog()->getFileName( filename = fileDialog()->getFileName(
this, tr("Select import source"), defaultDirPath, filters, nullptr, QFileDialog::DontConfirmOverwrite, this, tr("Select import source"), defaultDirPath, filters, nullptr, QFileDialog::DontConfirmOverwrite,
filetype, filename); defaultFiletype, filename);
break; break;
case KeeShareSettings::ExportTo: case KeeShareSettings::ExportTo:
filename = fileDialog()->getFileName( 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; break;
case KeeShareSettings::SynchronizeWith: case KeeShareSettings::SynchronizeWith:
case KeeShareSettings::Inactive: case KeeShareSettings::Inactive:
filename = fileDialog()->getFileName( 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; break;
} }

View file

@ -141,7 +141,7 @@ add_unit_test(NAME testsymmetriccipher SOURCES TestSymmetricCipher.cpp
if(WITH_XC_KEESHARE) if(WITH_XC_KEESHARE)
add_unit_test(NAME testsignature SOURCES TestSignature.cpp add_unit_test(NAME testsignature SOURCES TestSignature.cpp
LIBS ${TEST_LIBRARIES}) LIBS ${TEST_LIBRARIES})
endif() endif()
add_unit_test(NAME testhashedblockstream SOURCES TestHashedBlockStream.cpp add_unit_test(NAME testhashedblockstream SOURCES TestHashedBlockStream.cpp