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:
committed by
Jonathan White
parent
c1e9f45df9
commit
eca9c658f4
19
src/keeshare/CMakeLists.txt
Normal file
19
src/keeshare/CMakeLists.txt
Normal 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()
|
||||
53
src/keeshare/DatabaseSettingsPageKeeShare.cpp
Normal file
53
src/keeshare/DatabaseSettingsPageKeeShare.cpp
Normal 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();
|
||||
}
|
||||
37
src/keeshare/DatabaseSettingsPageKeeShare.h
Normal file
37
src/keeshare/DatabaseSettingsPageKeeShare.h
Normal 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
|
||||
72
src/keeshare/DatabaseSettingsWidgetKeeShare.cpp
Normal file
72
src/keeshare/DatabaseSettingsWidgetKeeShare.cpp
Normal 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
|
||||
}
|
||||
51
src/keeshare/DatabaseSettingsWidgetKeeShare.h
Normal file
51
src/keeshare/DatabaseSettingsWidgetKeeShare.h
Normal 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
|
||||
74
src/keeshare/DatabaseSettingsWidgetKeeShare.ui
Normal file
74
src/keeshare/DatabaseSettingsWidgetKeeShare.ui
Normal 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
234
src/keeshare/KeeShare.cpp
Normal 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
79
src/keeshare/KeeShare.h
Normal 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
|
||||
463
src/keeshare/KeeShareSettings.cpp
Normal file
463
src/keeshare/KeeShareSettings.cpp
Normal 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;
|
||||
}
|
||||
}
|
||||
161
src/keeshare/KeeShareSettings.h
Normal file
161
src/keeshare/KeeShareSettings.h
Normal 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
|
||||
67
src/keeshare/SettingsPageKeeShare.cpp
Normal file
67
src/keeshare/SettingsPageKeeShare.cpp
Normal 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();
|
||||
}
|
||||
43
src/keeshare/SettingsPageKeeShare.h
Normal file
43
src/keeshare/SettingsPageKeeShare.h
Normal 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
|
||||
214
src/keeshare/SettingsWidgetKeeShare.cpp
Normal file
214
src/keeshare/SettingsWidgetKeeShare.cpp
Normal 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();
|
||||
}
|
||||
72
src/keeshare/SettingsWidgetKeeShare.h
Normal file
72
src/keeshare/SettingsWidgetKeeShare.h
Normal 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
|
||||
249
src/keeshare/SettingsWidgetKeeShare.ui
Normal file
249
src/keeshare/SettingsWidgetKeeShare.ui
Normal 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>
|
||||
637
src/keeshare/ShareObserver.cpp
Normal file
637
src/keeshare/ShareObserver.cpp
Normal 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;
|
||||
}
|
||||
112
src/keeshare/ShareObserver.h
Normal file
112
src/keeshare/ShareObserver.h
Normal 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
260
src/keeshare/Signature.cpp
Normal 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
34
src/keeshare/Signature.h
Normal 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
|
||||
55
src/keeshare/group/EditGroupPageKeeShare.cpp
Normal file
55
src/keeshare/group/EditGroupPageKeeShare.cpp
Normal 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
|
||||
}
|
||||
37
src/keeshare/group/EditGroupPageKeeShare.h
Normal file
37
src/keeshare/group/EditGroupPageKeeShare.h
Normal 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
|
||||
228
src/keeshare/group/EditGroupWidgetKeeShare.cpp
Normal file
228
src/keeshare/group/EditGroupWidgetKeeShare.cpp
Normal 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);
|
||||
}
|
||||
59
src/keeshare/group/EditGroupWidgetKeeShare.h
Normal file
59
src/keeshare/group/EditGroupWidgetKeeShare.h
Normal 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
|
||||
139
src/keeshare/group/EditGroupWidgetKeeShare.ui
Normal file
139
src/keeshare/group/EditGroupWidgetKeeShare.ui
Normal 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>
|
||||
Reference in New Issue
Block a user