Add sharing of groups between databases

* Add source folder keeshare for sharing with corresponding define WITH_XC_KEESHARE
* Move common crypto parts to src/crypto/ssh
* Extended OpenSSHKey
* Move filewatching to own file (currently in two related classes DelayedFileWatcher and BulkFileWatcher)
* Small improvements for style and code in several classes
* Sharing is secured using RSA-Keys which are generated on demand
* Publisher signs the container using their private key
* Client can verify the signed container and choose to decline an import,
import only once or trust the publisher and automatically import all
data of this source henceforth
* Integration of settings into Group-Settings, Database-Settings and Application-Settings
* Introduced dependency QuaZip as dependency to allow combined export of
key container and the (custom format) certificate
This commit is contained in:
Christian Kieschnick
2018-10-01 10:26:24 -04:00
committed by Jonathan White
parent c1e9f45df9
commit eca9c658f4
106 changed files with 5828 additions and 503 deletions

View File

@@ -0,0 +1,19 @@
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
)
add_library(keeshare STATIC ${keeshare_SOURCES})
target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${QUAZIP_LIBRARIES} ${crypto_ssh_LIB})
endif()

View File

@@ -0,0 +1,53 @@
/*
* Copyright (C) 2018 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 "DatabaseSettingsPageKeeShare.h"
#include "core/Database.h"
#include "core/FilePath.h"
#include "core/Group.h"
#include "keeshare/DatabaseSettingsWidgetKeeShare.h"
#include "keeshare/KeeShare.h"
#include <QApplication>
QString DatabaseSettingsPageKeeShare::name()
{
return QApplication::tr("KeeShare");
}
QIcon DatabaseSettingsPageKeeShare::icon()
{
return FilePath::instance()->icon("apps", "preferences-system-network-sharing");
}
QWidget* DatabaseSettingsPageKeeShare::createWidget()
{
return new DatabaseSettingsWidgetKeeShare();
}
void DatabaseSettingsPageKeeShare::loadSettings(QWidget* widget, Database* db)
{
DatabaseSettingsWidgetKeeShare* settingsWidget = reinterpret_cast<DatabaseSettingsWidgetKeeShare*>(widget);
settingsWidget->loadSettings(db);
}
void DatabaseSettingsPageKeeShare::saveSettings(QWidget* widget)
{
DatabaseSettingsWidgetKeeShare* settingsWidget = reinterpret_cast<DatabaseSettingsWidgetKeeShare*>(widget);
settingsWidget->saveSettings();
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_DATABASESETTINGSPAGEKEESHARE_H
#define KEEPASSXC_DATABASESETTINGSPAGEKEESHARE_H
#include <QObject>
#include <QPointer>
#include <QWidget>
#include "gui/dbsettings/DatabaseSettingsDialog.h"
class DatabaseSettingsPageKeeShare : public IDatabaseSettingsPage
{
public:
QString name() override;
QIcon icon() override;
QWidget* createWidget() override;
void loadSettings(QWidget* widget, Database* db) override;
void saveSettings(QWidget* widget) override;
};
#endif // KEEPASSXC_DATABASESETTINGSPAGEKEESHARE_H

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2018 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 "DatabaseSettingsWidgetKeeShare.h"
#include "ui_DatabaseSettingsWidgetKeeShare.h"
#include "core/Database.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "keeshare/KeeShare.h"
#include "keeshare/KeeShareSettings.h"
#include <QMessageBox>
#include <QStandardItemModel>
DatabaseSettingsWidgetKeeShare::DatabaseSettingsWidgetKeeShare(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::DatabaseSettingsWidgetKeeShare())
{
m_ui->setupUi(this);
}
DatabaseSettingsWidgetKeeShare::~DatabaseSettingsWidgetKeeShare()
{
}
void DatabaseSettingsWidgetKeeShare::loadSettings(Database* db)
{
m_db = db;
m_referencesModel.reset(new QStandardItemModel());
m_referencesModel->setHorizontalHeaderLabels(
QStringList() << tr("Breadcrumb") << tr("Type") << tr("Path") << tr("Last Signer") << tr("Certificates"));
const QList<Group*> groups = db->rootGroup()->groupsRecursive(true);
for (const Group* group : groups) {
if (!KeeShare::isShared(group)) {
continue;
}
const KeeShareSettings::Reference reference = KeeShare::referenceOf(group);
QStringList hierarchy = group->hierarchy();
hierarchy.removeFirst();
QList<QStandardItem*> row = QList<QStandardItem*>();
row << new QStandardItem(hierarchy.join(" > "));
row << new QStandardItem(KeeShare::referenceTypeLabel(reference));
row << new QStandardItem(reference.path);
m_referencesModel->appendRow(row);
}
m_ui->sharedGroupsView->setModel(m_referencesModel.data());
}
void DatabaseSettingsWidgetKeeShare::saveSettings()
{
// nothing to do - the tab is passive
}

View File

@@ -0,0 +1,51 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_DATABASESETTINGSWIDGETKEESHARE_H
#define KEEPASSXC_DATABASESETTINGSWIDGETKEESHARE_H
#include <QPointer>
#include <QScopedPointer>
#include <QWidget>
class Database;
class QStandardItemModel;
namespace Ui
{
class DatabaseSettingsWidgetKeeShare;
}
class DatabaseSettingsWidgetKeeShare : public QWidget
{
Q_OBJECT
public:
explicit DatabaseSettingsWidgetKeeShare(QWidget* parent = nullptr);
~DatabaseSettingsWidgetKeeShare();
void loadSettings(Database* db);
void saveSettings();
private:
QScopedPointer<Ui::DatabaseSettingsWidgetKeeShare> m_ui;
QScopedPointer<QStandardItemModel> m_referencesModel;
QPointer<Database> m_db;
};
#endif // KEEPASSXC_DATABASESETTINGSWIDGETKEESHARE_H

View File

@@ -0,0 +1,74 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>DatabaseSettingsWidgetKeeShare</class>
<widget class="QWidget" name="DatabaseSettingsWidgetKeeShare">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>327</width>
<height>379</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="enableGroupBox">
<property name="title">
<string>Sharing</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" rowspan="2">
<widget class="QTableView" name="sharedGroupsView">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="textElideMode">
<enum>Qt::ElideMiddle</enum>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

234
src/keeshare/KeeShare.cpp Normal file
View File

@@ -0,0 +1,234 @@
/*
* Copyright (C) 2018 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 "KeeShare.h"
#include "core/Config.h"
#include "core/CustomData.h"
#include "core/Database.h"
#include "core/DatabaseIcons.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/ssh/OpenSSHKey.h"
#include "keeshare/ShareObserver.h"
#include "keeshare/Signature.h"
#include <QMessageBox>
#include <QPainter>
#include <QPushButton>
namespace
{
static const QString KeeShare_Reference("KeeShare/Reference");
static const QString KeeShare_Own("KeeShare/Settings.own");
static const QString KeeShare_Foreign("KeeShare/Settings.foreign");
static const QString KeeShare_Active("KeeShare/Settings.active");
}
KeeShare* KeeShare::m_instance = nullptr;
KeeShare* KeeShare::instance()
{
if (!m_instance) {
qFatal("Race condition: instance wanted before it was initialized, this is a bug.");
}
return m_instance;
}
void KeeShare::init(QObject* parent)
{
Q_ASSERT(!m_instance);
m_instance = new KeeShare(parent);
}
KeeShareSettings::Own KeeShare::own()
{
return KeeShareSettings::Own::deserialize(config()->get(KeeShare_Own).toString());
}
KeeShareSettings::Active KeeShare::active()
{
return KeeShareSettings::Active::deserialize(config()->get(KeeShare_Active).toString());
}
KeeShareSettings::Foreign KeeShare::foreign()
{
return KeeShareSettings::Foreign::deserialize(config()->get(KeeShare_Foreign).toString());
}
void KeeShare::setForeign(const KeeShareSettings::Foreign& foreign)
{
config()->set(KeeShare_Foreign, KeeShareSettings::Foreign::serialize(foreign));
}
void KeeShare::setActive(const KeeShareSettings::Active& active)
{
config()->set(KeeShare_Active, KeeShareSettings::Active::serialize(active));
}
void KeeShare::setOwn(const KeeShareSettings::Own& own)
{
config()->set(KeeShare_Own, KeeShareSettings::Own::serialize(own));
}
bool KeeShare::isShared(const Group* group)
{
return group->customData()->contains(KeeShare_Reference);
}
KeeShareSettings::Reference KeeShare::referenceOf(const Group* group)
{
static const KeeShareSettings::Reference s_emptyReference;
const CustomData* customData = group->customData();
if (!customData->contains(KeeShare_Reference)) {
return s_emptyReference;
}
const auto encoded = customData->value(KeeShare_Reference);
const auto serialized = QString::fromUtf8(QByteArray::fromBase64(encoded.toLatin1()));
KeeShareSettings::Reference reference = KeeShareSettings::Reference::deserialize(serialized);
if (reference.isNull()) {
qWarning("Invalid sharing reference detected - sharing disabled");
return s_emptyReference;
}
return reference;
}
void KeeShare::setReferenceTo(Group* group, const KeeShareSettings::Reference& reference)
{
CustomData* customData = group->customData();
if (reference.isNull()) {
customData->remove(KeeShare_Reference);
return;
}
const auto serialized = KeeShareSettings::Reference::serialize(reference);
const auto encoded = serialized.toUtf8().toBase64();
customData->set(KeeShare_Reference, encoded);
}
QPixmap KeeShare::indicatorBadge(const Group* group, QPixmap pixmap)
{
if (!isShared(group)) {
return pixmap;
}
const auto reference = KeeShare::referenceOf(group);
const auto active = KeeShare::active();
const bool enabled = (reference.isImporting() && active.in) || (reference.isExporting() && active.out);
const QPixmap badge = enabled ? databaseIcons()->iconPixmap(DatabaseIcons::SharedIconIndex)
: databaseIcons()->iconPixmap(DatabaseIcons::UnsharedIconIndex);
QImage canvas = pixmap.toImage();
const QRectF target(canvas.width() * 0.4, canvas.height() * 0.4, canvas.width() * 0.6, canvas.height() * 0.6);
QPainter painter(&canvas);
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.drawPixmap(target, badge, badge.rect());
pixmap.convertFromImage(canvas);
return pixmap;
}
QString KeeShare::referenceTypeLabel(const KeeShareSettings::Reference& reference)
{
switch (reference.type) {
case KeeShareSettings::Inactive:
return tr("Disabled share");
case KeeShareSettings::ImportFrom:
return tr("Import from");
case KeeShareSettings::ExportTo:
return tr("Export to");
case KeeShareSettings::SynchronizeWith:
return tr("Synchronize with");
}
return "";
}
QString KeeShare::indicatorSuffix(const Group* group, const QString& text)
{
// we not adjust the display name for now - it's just an alternative to the icon
Q_UNUSED(group);
return text;
}
void KeeShare::connectDatabase(Database* newDb, Database* oldDb)
{
if (oldDb && m_observersByDatabase.contains(oldDb)) {
QPointer<ShareObserver> observer = m_observersByDatabase.take(oldDb);
if (observer) {
delete observer;
}
}
if (newDb && !m_observersByDatabase.contains(newDb)) {
QPointer<ShareObserver> observer(new ShareObserver(newDb, newDb));
m_observersByDatabase[newDb] = observer;
connect(observer.data(),
SIGNAL(sharingMessage(QString, MessageWidget::MessageType)),
this,
SLOT(emitSharingMessage(QString, MessageWidget::MessageType)));
}
}
void KeeShare::handleDatabaseOpened(Database* db)
{
QPointer<ShareObserver> observer = m_observersByDatabase.value(db);
if (observer) {
observer->handleDatabaseOpened();
}
}
void KeeShare::handleDatabaseSaved(Database* db)
{
QPointer<ShareObserver> observer = m_observersByDatabase.value(db);
if (observer) {
observer->handleDatabaseSaved();
}
}
void KeeShare::emitSharingMessage(const QString& message, KMessageWidget::MessageType type)
{
QObject* observer = sender();
Database* db = m_databasesByObserver.value(observer);
if (db) {
emit sharingMessage(db, message, type);
}
}
void KeeShare::handleDatabaseDeleted(QObject* db)
{
auto observer = m_observersByDatabase.take(db);
if (observer) {
m_databasesByObserver.remove(observer);
}
}
void KeeShare::handleObserverDeleted(QObject* observer)
{
auto database = m_databasesByObserver.take(observer);
if (database) {
m_observersByDatabase.remove(database);
}
}
void KeeShare::handleSettingsChanged(const QString& key)
{
if (key == KeeShare_Active) {
emit activeChanged();
}
}
KeeShare::KeeShare(QObject* parent)
: QObject(parent)
{
connect(config(), SIGNAL(changed(QString)), this, SLOT(handleSettingsChanged(QString)));
}

79
src/keeshare/KeeShare.h Normal file
View File

@@ -0,0 +1,79 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_KEESHARE_H
#define KEEPASSXC_KEESHARE_H
#include <QMap>
#include <QObject>
#include "gui/MessageWidget.h"
#include "keeshare/KeeShareSettings.h"
class Group;
class Database;
class ShareObserver;
class QXmlStreamWriter;
class QXmlStreamReader;
class KeeShare : public QObject
{
Q_OBJECT
public:
static KeeShare* instance();
static void init(QObject* parent);
static QString indicatorSuffix(const Group* group, const QString& text);
static QPixmap indicatorBadge(const Group* group, QPixmap pixmap);
static bool isShared(const Group* group);
static KeeShareSettings::Own own();
static KeeShareSettings::Active active();
static KeeShareSettings::Foreign foreign();
static void setForeign(const KeeShareSettings::Foreign& foreign);
static void setActive(const KeeShareSettings::Active& active);
static void setOwn(const KeeShareSettings::Own& own);
static KeeShareSettings::Reference referenceOf(const Group* group);
static void setReferenceTo(Group* group, const KeeShareSettings::Reference& reference);
static QString referenceTypeLabel(const KeeShareSettings::Reference& reference);
void connectDatabase(Database* newDb, Database* oldDb);
void handleDatabaseOpened(Database* db);
void handleDatabaseSaved(Database* db);
signals:
void activeChanged();
void sharingMessage(Database*, QString, MessageWidget::MessageType);
private slots:
void emitSharingMessage(const QString&, MessageWidget::MessageType);
void handleDatabaseDeleted(QObject*);
void handleObserverDeleted(QObject*);
void handleSettingsChanged(const QString&);
private:
static KeeShare* m_instance;
explicit KeeShare(QObject* parent);
QMap<QObject*, QPointer<ShareObserver>> m_observersByDatabase;
QMap<QObject*, QPointer<Database>> m_databasesByObserver;
};
#endif // KEEPASSXC_KEESHARE_H

View File

@@ -0,0 +1,463 @@
/*
* Copyright (C) 2018 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 "KeeShareSettings.h"
#include "core/CustomData.h"
#include "core/Database.h"
#include "core/DatabaseIcons.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/ssh/OpenSSHKey.h"
#include "keeshare/Signature.h"
#include <QMessageBox>
#include <QPainter>
#include <QPushButton>
namespace KeeShareSettings
{
namespace
{
Certificate packCertificate(const OpenSSHKey& key, bool verified, const QString& signer)
{
KeeShareSettings::Certificate extracted;
extracted.trusted = verified;
extracted.signer = signer;
Q_ASSERT(key.type() == "ssh-rsa");
extracted.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Public, key);
return extracted;
}
Key packKey(const OpenSSHKey& key)
{
KeeShareSettings::Key extracted;
Q_ASSERT(key.type() == "ssh-rsa");
extracted.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Private, key);
return extracted;
}
OpenSSHKey unpackKey(const Key& sign)
{
if (sign.key.isEmpty()) {
return OpenSSHKey();
}
OpenSSHKey key = OpenSSHKey::restoreFromBinary(OpenSSHKey::Private, sign.key);
Q_ASSERT(key.type() == "ssh-rsa");
return key;
}
OpenSSHKey unpackCertificate(const Certificate& certificate)
{
if (certificate.key.isEmpty()) {
return OpenSSHKey();
}
OpenSSHKey key = OpenSSHKey::restoreFromBinary(OpenSSHKey::Public, certificate.key);
Q_ASSERT(key.type() == "ssh-rsa");
return key;
}
QString xmlSerialize(std::function<void(QXmlStreamWriter& writer)> specific)
{
QString buffer;
QXmlStreamWriter writer(&buffer);
writer.setCodec(QTextCodec::codecForName("UTF-8"));
writer.setAutoFormatting(true);
writer.setAutoFormattingIndent(2);
writer.writeStartDocument();
writer.writeStartElement("KeeShare");
writer.writeAttribute("xmlns:xsd", "http://www.w3.org/2001/XMLSchema");
writer.writeAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
specific(writer);
writer.writeEndElement();
writer.writeEndElement();
writer.writeEndDocument();
return buffer;
}
void xmlDeserialize(const QString& raw, std::function<void(QXmlStreamReader& reader)> specific)
{
QXmlStreamReader reader(raw);
if (!reader.readNextStartElement() || reader.qualifiedName() != "KeeShare") {
return;
}
specific(reader);
}
}
void Certificate::serialize(QXmlStreamWriter& writer, const Certificate& certificate)
{
if (certificate.isNull()) {
return;
}
writer.writeStartElement("Signer");
writer.writeCharacters(certificate.signer);
writer.writeEndElement();
writer.writeStartElement("Trusted");
writer.writeCharacters(certificate.trusted ? "True" : "False");
writer.writeEndElement();
writer.writeStartElement("Key");
writer.writeCharacters(certificate.key.toBase64());
writer.writeEndElement();
}
bool Certificate::operator==(const Certificate& other) const
{
return trusted == other.trusted && key == other.key && signer == other.signer;
}
bool Certificate::operator!=(const Certificate& other) const
{
return !operator==(other);
}
bool Certificate::isNull() const
{
return !trusted && key.isEmpty() && signer.isEmpty();
}
QString Certificate::fingerprint() const
{
if (isNull()) {
return {};
}
return sshKey().fingerprint();
}
OpenSSHKey Certificate::sshKey() const
{
return unpackCertificate(*this);
}
QString Certificate::publicKey() const
{
if (isNull()) {
return {};
}
return sshKey().publicKey();
}
Certificate Certificate::deserialize(QXmlStreamReader& reader)
{
Certificate certificate;
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Signer") {
certificate.signer = reader.readElementText();
} else if (reader.name() == "Trusted") {
certificate.trusted = reader.readElementText() == "True";
} else if (reader.name() == "Key") {
certificate.key = QByteArray::fromBase64(reader.readElementText().toLatin1());
}
}
return certificate;
}
bool Key::operator==(const Key& other) const
{
return key == other.key;
}
bool Key::operator!=(const Key& other) const
{
return !operator==(other);
}
bool Key::isNull() const
{
return key.isEmpty();
}
QString Key::privateKey() const
{
if (isNull()) {
return {};
}
return sshKey().privateKey();
}
OpenSSHKey Key::sshKey() const
{
return unpackKey(*this);
}
void Key::serialize(QXmlStreamWriter& writer, const Key& key)
{
if (key.isNull()) {
return;
}
writer.writeCharacters(key.key.toBase64());
}
Key Key::deserialize(QXmlStreamReader& reader)
{
Key key;
key.key = QByteArray::fromBase64(reader.readElementText().toLatin1());
return key;
}
Own Own::generate()
{
OpenSSHKey key = OpenSSHKey::generate(false);
key.openKey(QString());
Own own;
own.key = packKey(key);
const QString name = qgetenv("USER"); // + "@" + QHostInfo::localHostName();
own.certificate = packCertificate(key, true, name);
return own;
}
QString Active::serialize(const Active& active)
{
return xmlSerialize([&](QXmlStreamWriter& writer) {
writer.writeStartElement("Active");
if (active.in) {
writer.writeEmptyElement("Import");
}
if (active.out) {
writer.writeEmptyElement("Export");
}
writer.writeEndElement();
});
}
Active Active::deserialize(const QString& raw)
{
Active active;
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Active") {
while (reader.readNextStartElement()) {
if (reader.name() == "Import") {
active.in = true;
reader.skipCurrentElement();
} else if (reader.name() == "Export") {
active.out = true;
reader.skipCurrentElement();
} else {
break;
}
}
} else {
::qWarning() << "Unknown KeeShareSettings element" << reader.name();
reader.skipCurrentElement();
}
}
});
return active;
}
bool Own::operator==(const Own& other) const
{
return key == other.key && certificate == other.certificate;
}
bool Own::operator!=(const Own& other) const
{
return !operator==(other);
}
QString Own::serialize(const Own& own)
{
return xmlSerialize([&](QXmlStreamWriter& writer) {
writer.writeStartElement("PrivateKey");
Key::serialize(writer, own.key);
writer.writeEndElement();
writer.writeStartElement("PublicKey");
Certificate::serialize(writer, own.certificate);
writer.writeEndElement();
});
}
Own Own::deserialize(const QString& raw)
{
Own own;
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "PrivateKey") {
own.key = Key::deserialize(reader);
} else if (reader.name() == "PublicKey") {
own.certificate = Certificate::deserialize(reader);
} else {
::qWarning() << "Unknown KeeShareSettings element" << reader.name();
reader.skipCurrentElement();
}
}
});
return own;
}
QString Foreign::serialize(const Foreign& foreign)
{
return xmlSerialize([&](QXmlStreamWriter& writer) {
writer.writeStartElement("Foreign");
for (const Certificate& certificate : foreign.certificates) {
writer.writeStartElement("Certificate");
Certificate::serialize(writer, certificate);
writer.writeEndElement();
}
writer.writeEndElement();
});
}
Foreign Foreign::deserialize(const QString& raw)
{
Foreign foreign;
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Foreign") {
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Certificate") {
foreign.certificates << Certificate::deserialize(reader);
} else {
::qWarning() << "Unknown Cerificates element" << reader.name();
reader.skipCurrentElement();
}
}
} else {
::qWarning() << "Unknown KeeShareSettings element" << reader.name();
reader.skipCurrentElement();
}
}
});
return foreign;
}
Reference::Reference()
: type(Inactive)
, uuid(QUuid::createUuid())
{
}
bool Reference::isNull() const
{
return type == Inactive && path.isEmpty() && password.isEmpty();
}
bool Reference::isValid() const
{
return type != Inactive && !path.isEmpty();
}
bool Reference::isExporting() const
{
return (type & ExportTo) != 0 && !path.isEmpty();
}
bool Reference::isImporting() const
{
return (type & ImportFrom) != 0 && !path.isEmpty();
}
bool Reference::operator<(const Reference& other) const
{
if (type != other.type) {
return type < other.type;
}
return path < other.path;
}
bool Reference::operator==(const Reference& other) const
{
return path == other.path && uuid == other.uuid && password == other.password && type == other.type;
}
QString Reference::serialize(const Reference& reference)
{
return xmlSerialize([&](QXmlStreamWriter& writer) {
writer.writeStartElement("Type");
if ((reference.type & ImportFrom) == ImportFrom) {
writer.writeEmptyElement("Import");
}
if ((reference.type & ExportTo) == ExportTo) {
writer.writeEmptyElement("Export");
}
writer.writeEndElement();
writer.writeStartElement("Group");
writer.writeCharacters(reference.uuid.toRfc4122().toBase64());
writer.writeEndElement();
writer.writeStartElement("Path");
writer.writeCharacters(reference.path.toUtf8().toBase64());
writer.writeEndElement();
writer.writeStartElement("Password");
writer.writeCharacters(reference.password.toUtf8().toBase64());
writer.writeEndElement();
});
}
Reference Reference::deserialize(const QString& raw)
{
Reference reference;
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Type") {
while (reader.readNextStartElement()) {
if (reader.name() == "Import") {
reference.type |= ImportFrom;
reader.skipCurrentElement();
} else if (reader.name() == "Export") {
reference.type |= ExportTo;
reader.skipCurrentElement();
} else {
break;
}
}
} else if (reader.name() == "Group") {
reference.uuid = QUuid::fromRfc4122(QByteArray::fromBase64(reader.readElementText().toLatin1()));
} else if (reader.name() == "Path") {
reference.path = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1()));
} else if (reader.name() == "Password") {
reference.password = QString::fromUtf8(QByteArray::fromBase64(reader.readElementText().toLatin1()));
} else {
::qWarning() << "Unknown Reference element" << reader.name();
reader.skipCurrentElement();
}
}
});
return reference;
}
QString Sign::serialize(const Sign& sign)
{
return xmlSerialize([&](QXmlStreamWriter& writer) {
writer.writeStartElement("Signature");
writer.writeCharacters(sign.signature);
writer.writeEndElement();
writer.writeStartElement("Certificate");
Certificate::serialize(writer, sign.certificate);
writer.writeEndElement();
});
}
Sign Sign::deserialize(const QString& raw)
{
Sign sign;
xmlDeserialize(raw, [&](QXmlStreamReader& reader) {
while (!reader.error() && reader.readNextStartElement()) {
if (reader.name() == "Signature") {
sign.signature = reader.readElementText();
} else if (reader.name() == "Certificate") {
sign.certificate = KeeShareSettings::Certificate::deserialize(reader);
} else {
::qWarning() << "Unknown Sign element" << reader.name();
reader.skipCurrentElement();
}
}
});
return sign;
}
}

View File

@@ -0,0 +1,161 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_KEESHARESETTINGS_H
#define KEEPASSXC_KEESHARESETTINGS_H
#include <QMap>
#include <QObject>
#include "crypto/ssh/OpenSSHKey.h"
class CustomData;
class QXmlStreamWriter;
class QXmlStreamReader;
namespace KeeShareSettings
{
struct Certificate
{
QByteArray key;
QString signer;
bool trusted;
bool operator==(const Certificate& other) const;
bool operator!=(const Certificate& other) const;
Certificate()
: trusted(false)
{
}
bool isNull() const;
QString fingerprint() const;
QString publicKey() const;
OpenSSHKey sshKey() const;
static void serialize(QXmlStreamWriter& writer, const Certificate& certificate);
static Certificate deserialize(QXmlStreamReader& reader);
};
struct Key
{
QByteArray key;
bool operator==(const Key& other) const;
bool operator!=(const Key& other) const;
bool isNull() const;
QString privateKey() const;
OpenSSHKey sshKey() const;
static void serialize(QXmlStreamWriter& writer, const Key& key);
static Key deserialize(QXmlStreamReader& reader);
};
struct Active
{
bool in;
bool out;
Active()
: in(false)
, out(false)
{
}
bool isNull() const
{
return !in && !out;
}
static QString serialize(const Active& active);
static Active deserialize(const QString& raw);
};
struct Own
{
Key key;
Certificate certificate;
bool operator==(const Own& other) const;
bool operator!=(const Own& other) const;
bool isNull() const
{
return key.isNull() && certificate.isNull();
}
static QString serialize(const Own& own);
static Own deserialize(const QString& raw);
static Own generate();
};
struct Foreign
{
QList<Certificate> certificates;
bool isNull() const
{
return certificates.isEmpty();
}
static QString serialize(const Foreign& foreign);
static Foreign deserialize(const QString& raw);
};
struct Sign
{
QString signature;
Certificate certificate;
bool isNull() const
{
return signature.isEmpty() && certificate.isNull();
}
static QString serialize(const Sign& sign);
static Sign deserialize(const QString& raw);
};
enum TypeFlag
{
Inactive = 0,
ImportFrom = 1 << 0,
ExportTo = 1 << 1,
SynchronizeWith = ImportFrom | ExportTo
};
Q_DECLARE_FLAGS(Type, TypeFlag)
struct Reference
{
Type type;
QUuid uuid;
QString path;
QString password;
Reference();
bool isNull() const;
bool isValid() const;
bool isExporting() const;
bool isImporting() const;
bool operator<(const Reference& other) const;
bool operator==(const Reference& other) const;
static QString serialize(const Reference& reference);
static Reference deserialize(const QString& raw);
};
};
#endif // KEEPASSXC_KEESHARESETTINGS_H

View File

@@ -0,0 +1,67 @@
/*
* Copyright (C) 2018 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 "SettingsPageKeeShare.h"
#include "core/Database.h"
#include "core/FilePath.h"
#include "core/Group.h"
#include "gui/DatabaseTabWidget.h"
#include "gui/MessageWidget.h"
#include "keeshare/KeeShare.h"
#include "keeshare/SettingsWidgetKeeShare.h"
#include <QApplication>
#include <QObject>
SettingsPageKeeShare::SettingsPageKeeShare(DatabaseTabWidget* tabWidget)
: m_tabWidget(tabWidget)
{
}
QString SettingsPageKeeShare::name()
{
return QApplication::tr("KeeShare");
}
QIcon SettingsPageKeeShare::icon()
{
return FilePath::instance()->icon("apps", "preferences-system-network-sharing");
}
QWidget* SettingsPageKeeShare::createWidget()
{
auto* widget = new SettingsWidgetKeeShare();
QObject::connect(widget,
SIGNAL(settingsMessage(QString, MessageWidget::MessageType)),
m_tabWidget,
SIGNAL(messageGlobal(QString, MessageWidget::MessageType)));
return widget;
}
void SettingsPageKeeShare::loadSettings(QWidget* widget)
{
Q_UNUSED(widget);
SettingsWidgetKeeShare* settingsWidget = reinterpret_cast<SettingsWidgetKeeShare*>(widget);
settingsWidget->loadSettings();
}
void SettingsPageKeeShare::saveSettings(QWidget* widget)
{
Q_UNUSED(widget);
SettingsWidgetKeeShare* settingsWidget = reinterpret_cast<SettingsWidgetKeeShare*>(widget);
return settingsWidget->saveSettings();
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_SETTINGSPAGEKEESHARE_H
#define KEEPASSXC_SETTINGSPAGEKEESHARE_H
#include <QObject>
#include <QPointer>
#include <QWidget>
#include "gui/ApplicationSettingsWidget.h"
class DatabaseTabWidget;
class SettingsPageKeeShare : public ISettingsPage
{
public:
SettingsPageKeeShare(DatabaseTabWidget* tabWidget);
QString name() override;
QIcon icon() override;
QWidget* createWidget() override;
void loadSettings(QWidget* widget) override;
void saveSettings(QWidget* widget) override;
private:
QPointer<DatabaseTabWidget> m_tabWidget;
};
#endif // KEEPASSXC_SETTINGSPAGEKEESHARE_H

View File

@@ -0,0 +1,214 @@
/*
* Copyright (C) 2018 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 "SettingsWidgetKeeShare.h"
#include "ui_SettingsWidgetKeeShare.h"
#include "core/Config.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "gui/FileDialog.h"
#include "keeshare/KeeShare.h"
#include "keeshare/KeeShareSettings.h"
#include <QMessageBox>
#include <QStandardItemModel>
SettingsWidgetKeeShare::SettingsWidgetKeeShare(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::SettingsWidgetKeeShare())
{
m_ui->setupUi(this);
connect(m_ui->ownCertificateSignerEdit, SIGNAL(textChanged(QString)), SLOT(setVerificationExporter(QString)));
connect(m_ui->generateOwnCerticateButton, SIGNAL(clicked(bool)), SLOT(generateCertificate()));
connect(m_ui->importOwnCertificateButton, SIGNAL(clicked(bool)), SLOT(importCertificate()));
connect(m_ui->exportOwnCertificateButton, SIGNAL(clicked(bool)), SLOT(exportCertificate()));
connect(m_ui->trustImportedCertificateButton, SIGNAL(clicked(bool)), SLOT(trustSelectedCertificates()));
connect(m_ui->untrustImportedCertificateButton, SIGNAL(clicked(bool)), SLOT(untrustSelectedCertificates()));
connect(m_ui->removeImportedCertificateButton, SIGNAL(clicked(bool)), SLOT(removeSelectedCertificates()));
}
SettingsWidgetKeeShare::~SettingsWidgetKeeShare()
{
}
void SettingsWidgetKeeShare::loadSettings()
{
const auto active = KeeShare::active();
m_ui->enableExportCheckBox->setChecked(active.out);
m_ui->enableImportCheckBox->setChecked(active.in);
m_own = KeeShare::own();
updateOwnCertificate();
m_foreign = KeeShare::foreign();
updateForeignCertificates();
}
void SettingsWidgetKeeShare::updateForeignCertificates()
{
m_importedCertificateModel.reset(new QStandardItemModel());
m_importedCertificateModel->setHorizontalHeaderLabels(
QStringList() << tr("Signer") << tr("Status") << tr("Fingerprint") << tr("Certificate"));
for (const KeeShareSettings::Certificate& certificate : m_foreign.certificates) {
QStandardItem* signer = new QStandardItem(certificate.signer);
QStandardItem* verified = new QStandardItem(certificate.trusted ? tr("trusted") : tr("untrusted"));
QStandardItem* fingerprint = new QStandardItem(certificate.fingerprint());
QStandardItem* key = new QStandardItem(certificate.publicKey());
m_importedCertificateModel->appendRow(QList<QStandardItem*>() << signer << verified << fingerprint << key);
}
m_ui->importedCertificateTableView->setModel(m_importedCertificateModel.data());
}
void SettingsWidgetKeeShare::updateOwnCertificate()
{
m_ui->ownCertificateSignerEdit->setText(m_own.certificate.signer);
m_ui->ownCertificatePublicKeyEdit->setText(m_own.certificate.publicKey());
m_ui->ownCertificatePrivateKeyEdit->setText(m_own.key.privateKey());
m_ui->ownCertificateFingerprintEdit->setText(m_own.certificate.fingerprint());
}
void SettingsWidgetKeeShare::saveSettings()
{
KeeShareSettings::Active active;
active.out = m_ui->enableExportCheckBox->isChecked();
active.in = m_ui->enableImportCheckBox->isChecked();
// TODO HNH: This depends on the order of saving new data - a better model would be to
// store changes to the settings in a temporary object and check on the final values
// of this object (similar scheme to Entry) - this way we could validate the settings before save
if (active.in) {
emit settingsMessage(tr("Make sure to have a history size greater than 2 to prevent data loss when importing!"), MessageWidget::Warning);
}
KeeShare::setOwn(m_own);
KeeShare::setForeign(m_foreign);
KeeShare::setActive(active);
}
void SettingsWidgetKeeShare::setVerificationExporter(const QString& signer)
{
m_own.certificate.signer = signer;
m_ui->ownCertificateSignerEdit->setText(m_own.certificate.signer);
}
void SettingsWidgetKeeShare::generateCertificate()
{
m_own = KeeShareSettings::Own::generate();
m_ui->ownCertificateSignerEdit->setText(m_own.certificate.signer);
m_ui->ownCertificatePublicKeyEdit->setText(m_own.certificate.publicKey());
m_ui->ownCertificatePrivateKeyEdit->setText(m_own.key.privateKey());
m_ui->ownCertificateFingerprintEdit->setText(m_own.certificate.fingerprint());
}
void SettingsWidgetKeeShare::importCertificate()
{
QString defaultDirPath = config()->get("KeeShare/LastKeyDir").toString();
const bool dirExists = !defaultDirPath.isEmpty() && QDir(defaultDirPath).exists();
if (!dirExists) {
defaultDirPath = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first();
}
const auto filetype = tr("key.share", "Filetype for KeeShare key");
const auto filters = QString("%1 (*." + filetype + ");;%2 (*)").arg(tr("KeeShare key file"), tr("All files"));
QString filename = fileDialog()->getOpenFileName(this, tr("Select path"), defaultDirPath, filters, nullptr, 0);
if (filename.isEmpty()) {
return;
}
QFile file(filename);
file.open(QIODevice::ReadOnly);
QTextStream stream(&file);
m_own = KeeShareSettings::Own::deserialize(stream.readAll());
file.close();
config()->set("KeeShare/LastKeyDir", QFileInfo(filename).absolutePath());
updateOwnCertificate();
}
void SettingsWidgetKeeShare::exportCertificate()
{
if (KeeShare::own() != m_own) {
QMessageBox warning;
warning.setIcon(QMessageBox::Warning);
warning.setWindowTitle(tr("Exporting changed certificate"));
warning.setText(tr("The exported certificate is not the same as the one in use. Do you want to export the current certificate?"));
auto yes = warning.addButton(QMessageBox::StandardButton::Yes);
auto no = warning.addButton(QMessageBox::StandardButton::No);
warning.setDefaultButton(no);
warning.exec();
if (warning.clickedButton() != yes) {
return;
}
}
QString defaultDirPath = config()->get("KeeShare/LastKeyDir").toString();
const bool dirExists = !defaultDirPath.isEmpty() && QDir(defaultDirPath).exists();
if (!dirExists) {
defaultDirPath = QStandardPaths::standardLocations(QStandardPaths::DocumentsLocation).first();
}
const auto filetype = tr("key.share", "Filetype for KeeShare key");
const auto filters = QString("%1 (*." + filetype + ");;%2 (*)").arg(tr("KeeShare key file"), tr("All files"));
QString filename = tr("%1.%2", "Template for KeeShare key file").arg(m_own.certificate.signer).arg(filetype);
filename = fileDialog()->getSaveFileName(this, tr("Select path"), defaultDirPath, filters, nullptr, 0, filetype, filename);
if (filename.isEmpty()) {
return;
}
QFile file(filename);
file.open(QIODevice::Truncate | QIODevice::WriteOnly);
QTextStream stream(&file);
stream << KeeShareSettings::Own::serialize(m_own);
stream.flush();
file.close();
config()->set("KeeShare/LastKeyDir", QFileInfo(filename).absolutePath());
}
void SettingsWidgetKeeShare::trustSelectedCertificates()
{
const auto* selectionModel = m_ui->importedCertificateTableView->selectionModel();
Q_ASSERT(selectionModel);
for (const auto& index : selectionModel->selectedRows()) {
m_foreign.certificates[index.row()].trusted = true;
}
updateForeignCertificates();
}
void SettingsWidgetKeeShare::untrustSelectedCertificates()
{
const auto* selectionModel = m_ui->importedCertificateTableView->selectionModel();
Q_ASSERT(selectionModel);
for (const auto& index : selectionModel->selectedRows()) {
m_foreign.certificates[index.row()].trusted = false;
}
updateForeignCertificates();
}
void SettingsWidgetKeeShare::removeSelectedCertificates()
{
QList<KeeShareSettings::Certificate> certificates = m_foreign.certificates;
const auto* selectionModel = m_ui->importedCertificateTableView->selectionModel();
Q_ASSERT(selectionModel);
for (const auto& index : selectionModel->selectedRows()) {
certificates.removeOne(m_foreign.certificates[index.row()]);
}
m_foreign.certificates = certificates;
updateForeignCertificates();
}

View File

@@ -0,0 +1,72 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_SETTINGSWIDGETKEESHARE_H
#define KEEPASSXC_SETTINGSWIDGETKEESHARE_H
#include <QPointer>
#include <QScopedPointer>
#include <QWidget>
#include "gui/MessageWidget.h"
#include "keeshare/KeeShareSettings.h"
class Database;
class QStandardItemModel;
namespace Ui
{
class SettingsWidgetKeeShare;
}
class SettingsWidgetKeeShare : public QWidget
{
Q_OBJECT
public:
explicit SettingsWidgetKeeShare(QWidget* parent = nullptr);
~SettingsWidgetKeeShare();
void loadSettings();
void saveSettings();
signals:
void settingsMessage(const QString&, MessageWidget::MessageType type);
private slots:
void setVerificationExporter(const QString& signer);
void generateCertificate();
void importCertificate();
void exportCertificate();
void trustSelectedCertificates();
void untrustSelectedCertificates();
void removeSelectedCertificates();
private:
void updateOwnCertificate();
void updateForeignCertificates();
QScopedPointer<Ui::SettingsWidgetKeeShare> m_ui;
KeeShareSettings::Own m_own;
KeeShareSettings::Foreign m_foreign;
QScopedPointer<QStandardItemModel> m_importedCertificateModel;
};
#endif // KEEPASSXC_SETTINGSWIDGETKEESHARE_H

View File

@@ -0,0 +1,249 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingsWidgetKeeShare</class>
<widget class="QWidget" name="SettingsWidgetKeeShare">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>327</width>
<height>423</height>
</rect>
</property>
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0,0,0">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QGroupBox" name="activeGroupBox">
<property name="title">
<string>Active</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QCheckBox" name="enableExportCheckBox">
<property name="text">
<string>Allow export</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="enableImportCheckBox">
<property name="text">
<string>Allow import</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="ownCertificateGroupBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="title">
<string>Own certificate</string>
</property>
<layout class="QGridLayout" name="gridLayout_2" columnstretch="0,1,1">
<item row="5" column="0">
<widget class="QLabel" name="ownCertificateFingerprintLabel">
<property name="text">
<string>Fingerprint:</string>
</property>
</widget>
</item>
<item row="3" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificatePrivateKeyEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="ownCertificatePublicKeyLabel">
<property name="text">
<string>Certificate:</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="ownCertificateSignerLabel">
<property name="text">
<string>Signer</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="ownCertificatePrivateKeyLabel">
<property name="text">
<string>Key:</string>
</property>
</widget>
</item>
<item row="4" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificatePublicKeyEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificateSignerEdit"/>
</item>
<item row="5" column="1" colspan="2">
<widget class="QLineEdit" name="ownCertificateFingerprintEdit">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="0" colspan="3">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="generateOwnCerticateButton">
<property name="text">
<string>Generate</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="importOwnCertificateButton">
<property name="text">
<string>Import</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="exportOwnCertificateButton">
<property name="text">
<string>Export</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="importedCertificatesGroupBox">
<property name="title">
<string>Imported certificates</string>
</property>
<layout class="QGridLayout" name="gridLayout_3">
<item row="1" column="0" rowspan="2">
<widget class="QTableView" name="importedCertificateTableView">
<property name="editTriggers">
<set>QAbstractItemView::NoEditTriggers</set>
</property>
<property name="showDropIndicator" stdset="0">
<bool>false</bool>
</property>
<property name="dragDropOverwriteMode">
<bool>false</bool>
</property>
<property name="alternatingRowColors">
<bool>true</bool>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::MultiSelection</enum>
</property>
<property name="selectionBehavior">
<enum>QAbstractItemView::SelectRows</enum>
</property>
<attribute name="horizontalHeaderVisible">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderVisible">
<bool>false</bool>
</attribute>
</widget>
</item>
<item row="0" column="0">
<layout class="QHBoxLayout" name="certificatesActionLayout">
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="trustImportedCertificateButton">
<property name="text">
<string>Trust</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="untrustImportedCertificateButton">
<property name="text">
<string>Untrust</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="removeImportedCertificateButton">
<property name="text">
<string>Remove</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

View File

@@ -0,0 +1,637 @@
/*
* Copyright (C) 2018 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 "ShareObserver.h"
#include "core/Clock.h"
#include "core/Config.h"
#include "core/CustomData.h"
#include "core/Database.h"
#include "core/DatabaseIcons.h"
#include "core/Entry.h"
#include "core/FilePath.h"
#include "core/FileWatcher.h"
#include "core/Group.h"
#include "core/Merger.h"
#include "core/Metadata.h"
#include "format/KeePass2Reader.h"
#include "format/KeePass2Writer.h"
#include "keeshare/KeeShare.h"
#include "keeshare/KeeShareSettings.h"
#include "keeshare/Signature.h"
#include "keys/PasswordKey.h"
#include <QBuffer>
#include <QDebug>
#include <QFileInfo>
#include <QIcon>
#include <QMessageBox>
#include <QPainter>
#include <QPushButton>
#include <QStringBuilder>
#include <quazip5/quazip.h>
#include <quazip5/quazipfile.h>
namespace
{
static const QString KeeShare_Signature("container.share.signature");
static const QString KeeShare_Container("container.share.kdbx");
enum Trust
{
None,
Invalid,
Single,
Lasting,
Known,
Own
};
QPair<Trust, KeeShareSettings::Certificate> check(QByteArray& data,
const KeeShareSettings::Reference& reference,
const KeeShareSettings::Certificate& ownCertificate,
const QList<KeeShareSettings::Certificate>& knownCertificates,
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());
}
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 qMakePair(Own, ownCertificate);
}
for (const auto& certificate : knownCertificates) {
if (certificate.key == certificate.key && certificate.trusted) {
return qMakePair(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);
}
return qMakePair(Lasting, sign.certificate);
}
}
ShareObserver::ShareObserver(Database* db, QObject* parent)
: QObject(parent)
, m_db(db)
, m_fileWatcher(new BulkFileWatcher(this))
{
connect(KeeShare::instance(), SIGNAL(activeChanged()), this, SLOT(handleDatabaseChanged()));
connect(m_db, SIGNAL(modified()), this, SLOT(handleDatabaseChanged()));
connect(m_fileWatcher, SIGNAL(fileCreated(QString)), this, SLOT(handleFileCreated(QString)));
connect(m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(handleFileChanged(QString)));
connect(m_fileWatcher, SIGNAL(fileRemoved(QString)), this, SLOT(handleFileRemoved(QString)));
}
ShareObserver::~ShareObserver()
{
}
void ShareObserver::deinitialize()
{
m_fileWatcher->clear();
m_groupToReference.clear();
m_referenceToGroup.clear();
}
void ShareObserver::reinitialize()
{
struct Update
{
Group* group;
KeeShareSettings::Reference oldReference;
KeeShareSettings::Reference newReference;
};
const auto active = KeeShare::active();
QList<Update> updated;
QList<Group*> groups = m_db->rootGroup()->groupsRecursive(true);
for (Group* group : groups) {
Update couple{group, m_groupToReference.value(group), KeeShare::referenceOf(group)};
if (couple.oldReference == couple.newReference) {
continue;
}
m_groupToReference.remove(couple.group);
m_referenceToGroup.remove(couple.oldReference);
m_shareToGroup.remove(couple.oldReference.path);
if (couple.newReference.isValid() && ((active.in && couple.newReference.isImporting())
|| (active.out && couple.newReference.isExporting()))) {
m_groupToReference[couple.group] = couple.newReference;
m_referenceToGroup[couple.newReference] = couple.group;
m_shareToGroup[couple.newReference.path] = couple.group;
}
updated << couple;
}
QStringList success;
QStringList warning;
QStringList error;
for (Update update : updated) {
if (!update.oldReference.path.isEmpty()) {
m_fileWatcher->removePath(update.oldReference.path);
}
if (!update.newReference.path.isEmpty() && update.newReference.type != KeeShareSettings::Inactive) {
m_fileWatcher->addPath(update.newReference.path);
}
if (update.newReference.isImporting()) {
const Result result = this->importFromReferenceContainer(update.newReference.path);
if (!result.isValid()) {
// tolerable result - blocked import or missing source
continue;
}
if (result.isError()) {
error << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
} else if (result.isWarning()) {
warning << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
} else if (result.isInfo()) {
success << tr("Import from %1 successful (%2)").arg(result.path).arg(result.message);
} else {
success << tr("Imported from %1").arg(result.path);
}
}
}
notifyAbout(success, warning, error);
}
void ShareObserver::notifyAbout(const QStringList& success, const QStringList& warning, const QStringList& error)
{
if (error.isEmpty() && warning.isEmpty() && success.isEmpty()) {
return;
}
MessageWidget::MessageType type = MessageWidget::Positive;
if (!warning.isEmpty()) {
type = MessageWidget::Warning;
}
if (!error.isEmpty()) {
type = MessageWidget::Error;
}
emit sharingMessage((success + warning + error).join("\n"), type);
}
void ShareObserver::handleDatabaseChanged()
{
if (!m_db) {
Q_ASSERT(m_db);
return;
}
const auto active = KeeShare::active();
if (!active.out && !active.in) {
deinitialize();
} else {
reinitialize();
}
}
void ShareObserver::handleFileUpdated(const QString& path, Change change)
{
switch (change) {
case Creation:
qDebug("File created %s", qPrintable(path));
break;
case Update:
qDebug("File changed %s", qPrintable(path));
break;
case Deletion:
qDebug("File deleted %s", qPrintable(path));
break;
}
const Result result = this->importFromReferenceContainer(path);
if (!result.isValid()) {
return;
}
QStringList success;
QStringList warning;
QStringList error;
if (result.isError()) {
error << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
} else if (result.isWarning()) {
warning << tr("Import from %1 failed (%2)").arg(result.path).arg(result.message);
} else if (result.isInfo()) {
success << tr("Import from %1 successful (%2)").arg(result.path).arg(result.message);
} else {
success << tr("Imported from %1").arg(result.path);
}
notifyAbout(success, warning, error);
}
void ShareObserver::handleFileCreated(const QString& path)
{
handleFileUpdated(path, Creation);
}
void ShareObserver::handleFileChanged(const QString& path)
{
handleFileUpdated(path, Update);
}
void ShareObserver::handleFileRemoved(const QString& path)
{
handleFileUpdated(path, Deletion);
}
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")};
}
QuaZip zip(info.absoluteFilePath());
if (!zip.open(QuaZip::mdUnzip)) {
qCritical("Unable to open file %s.", qPrintable(info.absoluteFilePath()));
return {reference.path, Result::Error, tr("File is not readable")};
}
const auto expected = QSet<QString>() << KeeShare_Signature << KeeShare_Container;
const auto files = zip.getFileInfoList();
QSet<QString> actual;
for (const auto& file : files) {
actual << file.name;
}
if (expected != actual) {
qCritical("Invalid sharing container %s.", qPrintable(info.absoluteFilePath()));
return {reference.path, Result::Error, tr("Invalid sharing container")};
}
zip.setCurrentFile(KeeShare_Signature);
QuaZipFile signatureFile(&zip);
signatureFile.open(QuaZipFile::ReadOnly);
QTextStream stream(&signatureFile);
const auto sign = KeeShareSettings::Sign::deserialize(stream.readAll());
signatureFile.close();
zip.setCurrentFile(KeeShare_Container);
QuaZipFile databaseFile(&zip);
databaseFile.open(QuaZipFile::ReadOnly);
auto payload = databaseFile.readAll();
databaseFile.close();
QBuffer buffer(&payload);
buffer.open(QIODevice::ReadOnly);
KeePass2Reader reader;
auto key = QSharedPointer<CompositeKey>::create();
key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
auto* sourceDb = reader.readDatabase(&buffer, key);
if (reader.hasError()) {
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);
switch (trusted.first) {
case None:
qWarning("Prevent untrusted import");
return {reference.path, Result::Warning, tr("Untrusted import prevented")};
case Invalid:
qCritical("Prevent untrusted import");
return {reference.path, Result::Error, tr("Untrusted import prevented")};
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);
}
}
[[gnu::fallthrough]];
case Single:
case Own: {
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 import")};
}
return {};
}
default:
Q_ASSERT(false);
return {};
}
}
ShareObserver::Result ShareObserver::importFromReferenceContainer(const QString& path)
{
if (!KeeShare::active().in) {
return {};
}
auto shareGroup = m_shareToGroup.value(path);
if (!shareGroup) {
qWarning("Source for %s does not exist", qPrintable(path));
Q_ASSERT(shareGroup);
return {};
}
const auto reference = KeeShare::referenceOf(shareGroup);
if (reference.type == KeeShareSettings::Inactive) {
qDebug("Ignore change of inactive reference %s", qPrintable(reference.path));
return {};
}
if (reference.type == KeeShareSettings::ExportTo) {
qDebug("Ignore change of export reference %s", qPrintable(reference.path));
return {};
}
Q_ASSERT(shareGroup->database() == m_db);
Q_ASSERT(shareGroup == m_db->rootGroup()->findGroupByUuid(shareGroup->uuid()));
return importContainerInto(reference, shareGroup);
}
void ShareObserver::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->resolveEntry(targetEntry->uuid());
const auto resolvedValue = sourceReference->resolveMultiplePlaceholders(standardValue);
targetEntry->setUpdateTimeinfo(false);
targetEntry->attributes()->set(attribute, resolvedValue, targetEntry->attributes()->isProtected(attribute));
targetEntry->setUpdateTimeinfo(true);
}
}
Database* ShareObserver::exportIntoContainer(const KeeShareSettings::Reference& reference, const Group* sourceRoot)
{
const auto* sourceDb = sourceRoot->database();
auto* targetDb = new Database();
targetDb->metadata()->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()) {
targetDb->metadata()->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;
}
const Database* ShareObserver::database() const
{
return m_db;
}
Database* ShareObserver::database()
{
return m_db;
}
void ShareObserver::handleDatabaseOpened()
{
if (!m_db) {
Q_ASSERT(m_db);
return;
}
const auto active = KeeShare::active();
if (!active.in && !active.out) {
deinitialize();
} else {
reinitialize();
}
}
QList<ShareObserver::Result> ShareObserver::exportIntoReferenceContainers()
{
QList<Result> 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);
if (!reference.isExporting()) {
continue;
}
m_fileWatcher->ignoreFileChanges(reference.path);
QScopedPointer<Database> 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())};
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();
}
{
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};
}
return results;
}
void ShareObserver::handleDatabaseSaved()
{
if (!KeeShare::active().out) {
return;
}
QStringList error;
QStringList warning;
QStringList success;
const auto results = exportIntoReferenceContainers();
for (const Result& result : results) {
if (!result.isValid()) {
Q_ASSERT(result.isValid());
continue;
}
if (result.isError()) {
error << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message);
} else if (result.isWarning()) {
warning << tr("Export to %1 failed (%2)").arg(result.path).arg(result.message);
} else if (result.isInfo()) {
success << tr("Export to %1 successful (%2)").arg(result.path).arg(result.message);
} else {
success << tr("Export to %1").arg(result.path);
}
}
notifyAbout(success, warning, error);
}
ShareObserver::Result::Result(const QString& path, ShareObserver::Result::Type type, const QString& message)
: path(path)
, type(type)
, message(message)
{
}
bool ShareObserver::Result::isValid() const
{
return !path.isEmpty() || !message.isEmpty() || !message.isEmpty() || !message.isEmpty();
}
bool ShareObserver::Result::isError() const
{
return !message.isEmpty() && type == Error;
}
bool ShareObserver::Result::isInfo() const
{
return !message.isEmpty() && type == Info;
}
bool ShareObserver::Result::isWarning() const
{
return !message.isEmpty() && type == Warning;
}

View File

@@ -0,0 +1,112 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_SHAREOBSERVER_H
#define KEEPASSXC_SHAREOBSERVER_H
#include <QMap>
#include <QObject>
#include <QSet>
#include <QStringList>
#include <QTimer>
#include "gui/MessageWidget.h"
#include "keeshare/KeeShareSettings.h"
class BulkFileWatcher;
class Entry;
class Group;
class CustomData;
class Database;
class ShareObserver : public QObject
{
Q_OBJECT
public:
explicit ShareObserver(Database* db, QObject* parent = nullptr);
~ShareObserver();
void handleDatabaseSaved();
void handleDatabaseOpened();
const Database* database() const;
Database* database();
signals:
void sharingMessage(QString, MessageWidget::MessageType);
public slots:
void handleDatabaseChanged();
private slots:
void handleFileCreated(const QString& path);
void handleFileChanged(const QString& path);
void handleFileRemoved(const QString& path);
private:
enum Change
{
Creation,
Update,
Deletion
};
struct Result
{
enum Type
{
Success,
Info,
Warning,
Error
};
QString path;
Type type;
QString message;
Result(const QString& path = QString(), Type type = Success, const QString& message = QString());
bool isValid() const;
bool isError() const;
bool isWarning() const;
bool isInfo() const;
};
static void resolveReferenceAttributes(Entry* targetEntry, const Database* sourceDb);
static Database* exportIntoContainer(const KeeShareSettings::Reference& reference, const Group* sourceRoot);
static Result importContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup);
Result importFromReferenceContainer(const QString& path);
QList<ShareObserver::Result> exportIntoReferenceContainers();
void deinitialize();
void reinitialize();
void handleFileUpdated(const QString& path, Change change);
void notifyAbout(const QStringList& success, const QStringList& warning, const QStringList& error);
private:
Database* const m_db;
QMap<KeeShareSettings::Reference, QPointer<Group>> m_referenceToGroup;
QMap<QPointer<Group>, KeeShareSettings::Reference> m_groupToReference;
QMap<QString, QPointer<Group>> m_shareToGroup;
BulkFileWatcher* m_fileWatcher;
};
#endif // KEEPASSXC_SHAREOBSERVER_H

260
src/keeshare/Signature.cpp Normal file
View File

@@ -0,0 +1,260 @@
/*
* Copyright (C) 2018 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 "Signature.h"
#include "core/Tools.h"
#include "crypto/Crypto.h"
#include "crypto/CryptoHash.h"
#include "crypto/ssh/OpenSSHKey.h"
#include <QByteArray>
#include <gcrypt.h>
struct RSASigner
{
gcry_error_t rc;
QString error;
void raiseError(const QString& message = QString())
{
if (message.isEmpty()) {
error = QString("%1/%2").arg(QString::fromLocal8Bit(gcry_strsource(rc)),
QString::fromLocal8Bit(gcry_strerror(rc)));
} else {
error = message;
}
}
RSASigner()
: rc(GPG_ERR_NO_ERROR)
{
}
QString sign(const QByteArray& data, const OpenSSHKey& key)
{
enum Index
{
N,
E,
D,
P,
Q,
U, // private key
R,
S, // signature
Data,
Key,
Sig
};
const QList<QByteArray> parts = key.privateParts();
if (parts.count() != 6) {
raiseError("Unsupported signing key");
return QString();
}
const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256);
Tools::Map<Index, gcry_mpi_t, &gcry_mpi_release> mpi;
Tools::Map<Index, gcry_sexp_t, &gcry_sexp_release> sexp;
const gcry_mpi_format format = GCRYMPI_FMT_USG;
rc = gcry_mpi_scan(&mpi[N], format, parts[0].data(), parts[0].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[E], format, parts[1].data(), parts[1].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[D], format, parts[2].data(), parts[2].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[U], format, parts[3].data(), parts[3].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[P], format, parts[4].data(), parts[4].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_mpi_scan(&mpi[Q], format, parts[5].data(), parts[5].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
if (gcry_mpi_cmp(mpi[P], mpi[Q]) > 0) {
// see https://www.gnupg.org/documentation/manuals/gcrypt/RSA-key-parameters.html#RSA-key-parameters
gcry_mpi_swap(mpi[P], mpi[Q]);
gcry_mpi_invm(mpi[U], mpi[P], mpi[Q]);
}
rc = gcry_sexp_build(&sexp[Key],
NULL,
"(private-key (rsa (n %m) (e %m) (d %m) (p %m) (q %m) (u %m)))",
mpi[N],
mpi[E],
mpi[D],
mpi[P],
mpi[Q],
mpi[U]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_pk_testkey(sexp[Key]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags pkcs1) (hash sha256 %b))", block.size(), block.data());
// rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags raw) (value %b))", data.size(), data.data());
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
rc = gcry_pk_sign(&sexp[Sig], sexp[Data], sexp[Key]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
sexp[S] = gcry_sexp_find_token(sexp[Sig], "s", 1);
mpi[S] = gcry_sexp_nth_mpi(sexp[S], 1, GCRYMPI_FMT_USG);
Tools::Buffer buffer;
rc = gcry_mpi_aprint(GCRYMPI_FMT_STD, &buffer.raw, &buffer.size, mpi[S]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return QString();
}
return QString("rsa|%1").arg(QString::fromLatin1(buffer.content().toHex()));
}
bool verify(const QByteArray& data, const OpenSSHKey& key, const QString& signature)
{
const gcry_mpi_format format = GCRYMPI_FMT_USG;
enum MPI
{
N,
E, // public key
R,
S // signature
};
enum SEXP
{
Data,
Key,
Sig
};
const QList<QByteArray> parts = key.publicParts();
if (parts.count() != 2) {
raiseError("Unsupported verification key");
return false;
}
const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256);
Tools::Map<MPI, gcry_mpi_t, &gcry_mpi_release> mpi;
Tools::Map<SEXP, gcry_sexp_t, &gcry_sexp_release> sexp;
rc = gcry_mpi_scan(&mpi[E], format, parts[0].data(), parts[0].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_mpi_scan(&mpi[N], format, parts[1].data(), parts[1].size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_sexp_build(&sexp[Key], NULL, "(public-key (rsa (n %m) (e %m)))", mpi[N], mpi[E]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
QRegExp extractor("rsa\\|([a-f0-9]+)", Qt::CaseInsensitive);
if (!extractor.exactMatch(signature) || extractor.captureCount() != 1) {
raiseError("Could not unpack signature parts");
return false;
}
const QByteArray sig_s = QByteArray::fromHex(extractor.cap(1).toLatin1());
rc = gcry_mpi_scan(&mpi[S], GCRYMPI_FMT_STD, sig_s.data(), sig_s.size(), nullptr);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_sexp_build(&sexp[Sig], NULL, "(sig-val (rsa (s %m)))", mpi[S]);
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags pkcs1) (hash sha256 %b))", block.size(), block.data());
// rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags raw) (value %b))", data.size(), data.data());
if (rc != GPG_ERR_NO_ERROR) {
raiseError();
return false;
}
rc = gcry_pk_verify(sexp[Sig], sexp[Data], sexp[Key]);
if (rc != GPG_ERR_NO_ERROR && rc != GPG_ERR_BAD_SIGNATURE) {
raiseError();
return false;
}
return rc != GPG_ERR_BAD_SIGNATURE;
}
};
QString Signature::create(const QByteArray& data, const OpenSSHKey& key)
{
// TODO HNH: currently we publish the signature in our own non-standard format - it would
// be better to use a standard format (like ASN1 - but this would be more easy
// when we integrate a proper library)
// Even more, we could publish standard self signed certificates with the container
// instead of the custom certificates
if (key.type() == "ssh-rsa") {
RSASigner signer;
QString result = signer.sign(data, key);
if (signer.rc != GPG_ERR_NO_ERROR) {
::qWarning() << signer.error;
}
return result;
}
::qWarning() << "Unsupported Public/Private key format";
return QString();
}
bool Signature::verify(const QByteArray& data, const QString& signature, const OpenSSHKey& key)
{
if (key.type() == "ssh-rsa") {
RSASigner signer;
bool result = signer.verify(data, key, signature);
if (signer.rc != GPG_ERR_NO_ERROR) {
::qWarning() << signer.error;
}
return result;
}
::qWarning() << "Unsupported Public/Private key format";
return false;
}

34
src/keeshare/Signature.h Normal file
View File

@@ -0,0 +1,34 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_SIGNATURE_H
#define KEEPASSXC_SIGNATURE_H
#include <QString>
#include <gcrypt.h>
class QByteArray;
class OpenSSHKey;
class Signature
{
public:
static QString create(const QByteArray& data, const OpenSSHKey& key);
static bool verify(const QByteArray& data, const QString& signature, const OpenSSHKey& key);
};
#endif // KEEPASSXC_SIGNATURE_H

View File

@@ -0,0 +1,55 @@
/*
* Copyright (C) 2018 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 "EditGroupPageKeeShare.h"
#include "core/FilePath.h"
#include "keeshare/group/EditGroupWidgetKeeShare.h"
#include <QApplication>
EditGroupPageKeeShare::EditGroupPageKeeShare(EditGroupWidget* widget)
{
Q_UNUSED(widget);
}
QString EditGroupPageKeeShare::name()
{
return QApplication::tr("KeeShare");
}
QIcon EditGroupPageKeeShare::icon()
{
return FilePath::instance()->icon("apps", "preferences-system-network-sharing");
}
QWidget* EditGroupPageKeeShare::createWidget()
{
return new EditGroupWidgetKeeShare();
}
void EditGroupPageKeeShare::set(QWidget* widget, Group* temporaryGroup)
{
EditGroupWidgetKeeShare* settingsWidget = reinterpret_cast<EditGroupWidgetKeeShare*>(widget);
settingsWidget->setGroup(temporaryGroup);
}
void EditGroupPageKeeShare::assign(QWidget* widget)
{
Q_UNUSED(widget);
// everything is saved directly
}

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_EDITGROUPPAGEKEESHARE_H
#define KEEPASSXC_EDITGROUPPAGEKEESHARE_H
#include "gui/group/EditGroupWidget.h"
class Group;
class Database;
class EditGroupPageKeeShare : public IEditGroupPage
{
public:
EditGroupPageKeeShare(EditGroupWidget* widget);
QString name() override;
QIcon icon() override;
QWidget* createWidget() override;
void set(QWidget* widget, Group* temporaryGroup) override;
void assign(QWidget* widget) override;
};
#endif // KEEPASSXC_EDITGROUPPAGEKEESHARE_H

View File

@@ -0,0 +1,228 @@
/*
* Copyright (C) 2018 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 "EditGroupWidgetKeeShare.h"
#include "ui_EditGroupWidgetKeeShare.h"
#include "core/Config.h"
#include "core/CustomData.h"
#include "core/FilePath.h"
#include "core/Group.h"
#include "core/Metadata.h"
#include "crypto/ssh/OpenSSHKey.h"
#include "gui/FileDialog.h"
#include "keeshare/KeeShare.h"
#include <QDir>
#include <QStandardPaths>
EditGroupWidgetKeeShare::EditGroupWidgetKeeShare(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::EditGroupWidgetKeeShare())
{
m_ui->setupUi(this);
m_ui->togglePasswordButton->setIcon(filePath()->onOffIcon("actions", "password-show"));
m_ui->togglePasswordGeneratorButton->setIcon(filePath()->icon("actions", "password-generator", false));
m_ui->passwordGenerator->layout()->setContentsMargins(0, 0, 0, 0);
m_ui->passwordGenerator->hide();
m_ui->passwordGenerator->reset();
m_ui->messageWidget->hide();
connect(m_ui->togglePasswordButton, SIGNAL(toggled(bool)), m_ui->passwordEdit, SLOT(setShowPassword(bool)));
connect(m_ui->togglePasswordGeneratorButton, SIGNAL(toggled(bool)), SLOT(togglePasswordGeneratorButton(bool)));
connect(m_ui->passwordEdit, SIGNAL(textChanged(QString)), SLOT(selectPassword()));
connect(m_ui->passwordGenerator, SIGNAL(appliedPassword(QString)), SLOT(setGeneratedPassword(QString)));
connect(m_ui->pathEdit, SIGNAL(textChanged(QString)), SLOT(setPath(QString)));
connect(m_ui->pathSelectionButton, SIGNAL(pressed()), SLOT(selectPath()));
connect(m_ui->typeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(selectType()));
connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(showSharingState()));
const auto types = QList<KeeShareSettings::Type>() << KeeShareSettings::Inactive
<< KeeShareSettings::ImportFrom
<< KeeShareSettings::ExportTo
<< KeeShareSettings::SynchronizeWith;
for (const auto& type : types) {
QString name;
switch (type) {
case KeeShareSettings::Inactive:
name = tr("Inactive");
break;
case KeeShareSettings::ImportFrom:
name = tr("Import from path");
break;
case KeeShareSettings::ExportTo:
name = tr("Export to path");
break;
case KeeShareSettings::SynchronizeWith:
name = tr("Synchronize with path");
break;
}
m_ui->typeComboBox->insertItem(type, name, static_cast<int>(type));
}
}
EditGroupWidgetKeeShare::~EditGroupWidgetKeeShare()
{
}
void EditGroupWidgetKeeShare::setGroup(Group* temporaryGroup)
{
if (m_temporaryGroup) {
m_temporaryGroup->disconnect(this);
}
m_temporaryGroup = temporaryGroup;
if (m_temporaryGroup) {
connect(m_temporaryGroup, SIGNAL(modified()), SLOT(update()));
}
update();
}
void EditGroupWidgetKeeShare::showSharingState()
{
if (!m_temporaryGroup) {
return;
}
const auto active = KeeShare::active();
if (!active.in && !active.out) {
m_ui->messageWidget->showMessage(tr("Database sharing is disabled"), MessageWidget::Information);
}
if (active.in && !active.out) {
m_ui->messageWidget->showMessage(tr("Database export is disabled"), MessageWidget::Information);
}
if (!active.in && active.out) {
m_ui->messageWidget->showMessage(tr("Database import is disabled"), MessageWidget::Information);
}
}
void EditGroupWidgetKeeShare::update()
{
if (!m_temporaryGroup) {
m_ui->passwordEdit->clear();
m_ui->pathEdit->clear();
m_ui->passwordGenerator->hide();
m_ui->togglePasswordGeneratorButton->setChecked(false);
} else {
const auto reference = KeeShare::referenceOf(m_temporaryGroup);
m_ui->typeComboBox->setCurrentIndex(reference.type);
m_ui->passwordEdit->setText(reference.password);
m_ui->pathEdit->setText(reference.path);
showSharingState();
}
}
void EditGroupWidgetKeeShare::togglePasswordGeneratorButton(bool checked)
{
m_ui->passwordGenerator->regeneratePassword();
m_ui->passwordGenerator->setVisible(checked);
}
void EditGroupWidgetKeeShare::setGeneratedPassword(const QString& password)
{
if (!m_temporaryGroup) {
return;
}
auto reference = KeeShare::referenceOf(m_temporaryGroup);
reference.password = password;
KeeShare::setReferenceTo(m_temporaryGroup, reference);
m_ui->togglePasswordGeneratorButton->setChecked(false);
}
void EditGroupWidgetKeeShare::setPath(const QString& path)
{
if (!m_temporaryGroup) {
return;
}
auto reference = KeeShare::referenceOf(m_temporaryGroup);
reference.path = path;
KeeShare::setReferenceTo(m_temporaryGroup, reference);
}
void EditGroupWidgetKeeShare::selectPath()
{
if (!m_temporaryGroup) {
return;
}
QString defaultDirPath = config()->get("KeeShare/LastShareDir").toString();
const bool dirExists = !defaultDirPath.isEmpty() && QDir(defaultDirPath).exists();
if (!dirExists) {
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"));
auto filename = reference.path;
if (filename.isEmpty()) {
filename = tr("%1.%2", "Template for KeeShare container").arg(m_temporaryGroup->name()).arg(filetype);
}
switch (reference.type) {
case KeeShareSettings::ImportFrom:
filename = fileDialog()->getFileName(this,
tr("Select import source"),
defaultDirPath,
filters,
nullptr,
QFileDialog::DontConfirmOverwrite,
filetype,
filename);
break;
case KeeShareSettings::ExportTo:
filename = fileDialog()->getFileName(
this, tr("Select export target"), defaultDirPath, filters, nullptr, 0, filetype, filename);
break;
case KeeShareSettings::SynchronizeWith:
case KeeShareSettings::Inactive:
filename = fileDialog()->getFileName(
this, tr("Select import/export file"), defaultDirPath, filters, nullptr, 0, filetype, filename);
break;
}
if (filename.isEmpty()) {
return;
}
setPath(filename);
config()->set("KeeShare/LastShareDir", QFileInfo(filename).absolutePath());
}
void EditGroupWidgetKeeShare::selectPassword()
{
if (!m_temporaryGroup) {
return;
}
auto reference = KeeShare::referenceOf(m_temporaryGroup);
reference.password = m_ui->passwordEdit->text();
KeeShare::setReferenceTo(m_temporaryGroup, reference);
}
void EditGroupWidgetKeeShare::selectType()
{
if (!m_temporaryGroup) {
return;
}
auto reference = KeeShare::referenceOf(m_temporaryGroup);
reference.type = static_cast<KeeShareSettings::Type>(m_ui->typeComboBox->currentData().toInt());
KeeShare::setReferenceTo(m_temporaryGroup, reference);
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright (C) 2018 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/>.
*/
#ifndef KEEPASSXC_EDITGROUPWIDGETKEESHARE_H
#define KEEPASSXC_EDITGROUPWIDGETKEESHARE_H
#include <QPointer>
#include <QStandardItemModel>
#include <QWidget>
class Group;
class Database;
namespace Ui
{
class EditGroupWidgetKeeShare;
}
class EditGroupWidgetKeeShare : public QWidget
{
Q_OBJECT
public:
explicit EditGroupWidgetKeeShare(QWidget* parent = nullptr);
~EditGroupWidgetKeeShare();
void setGroup(Group* temporaryGroup);
private slots:
void showSharingState();
private slots:
void update();
void selectType();
void selectPassword();
void selectPath();
void setPath(const QString& path);
void setGeneratedPassword(const QString& password);
void togglePasswordGeneratorButton(bool checked);
private:
QScopedPointer<Ui::EditGroupWidgetKeeShare> m_ui;
QPointer<Group> m_temporaryGroup;
};
#endif // KEEPASSXC_EDITGROUPWIDGETKEESHARE_H

View File

@@ -0,0 +1,139 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>EditGroupWidgetKeeShare</class>
<widget class="QWidget" name="EditGroupWidgetKeeShare">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>342</width>
<height>378</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="MessageWidget" name="messageWidget" native="true"/>
</item>
<item>
<layout class="QFormLayout" name="formLayout">
<item row="2" column="0">
<widget class="QLabel" name="typeLabel">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="typeComboBox"/>
</item>
<item row="3" column="0">
<widget class="QLabel" name="pathLabel">
<property name="text">
<string>Path:</string>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="pathLayout">
<item>
<widget class="QLineEdit" name="pathEdit"/>
</item>
<item>
<widget class="QToolButton" name="pathSelectionButton">
<property name="text">
<string>...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="4" column="0">
<widget class="QLabel" name="passwordLabel">
<property name="text">
<string>Password:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<layout class="QHBoxLayout" name="passwordLayout">
<item>
<widget class="PasswordEdit" name="passwordEdit">
<property name="echoMode">
<enum>QLineEdit::Password</enum>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="togglePasswordButton">
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="togglePasswordGeneratorButton">
<property name="checkable">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</item>
<item row="5" column="1">
<widget class="PasswordGeneratorWidget" name="passwordGenerator" native="true"/>
</item>
</layout>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>PasswordGeneratorWidget</class>
<extends>QWidget</extends>
<header>gui/PasswordGeneratorWidget.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>PasswordEdit</class>
<extends>QLineEdit</extends>
<header>gui/PasswordEdit.h</header>
<container>1</container>
</customwidget>
<customwidget>
<class>MessageWidget</class>
<extends>QWidget</extends>
<header>gui/MessageWidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>