Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorChristian Kieschnick <christian.kieschnick@hicknhack-software.com>2018-10-01 17:26:24 +0300
committerJonathan White <support@dmapps.us>2018-10-01 17:39:37 +0300
commiteca9c658f4d0a8e956d49ce2e9eea81704e1de9b (patch)
treef49da9147abee9a96a0acce17548233a988b1f34 /src
parentc1e9f45df9f21b7697241037643770a2862bb7ef (diff)
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
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt23
-rw-r--r--src/config-keepassx.h.cmake1
-rw-r--r--src/core/Clock.cpp4
-rw-r--r--src/core/Config.cpp9
-rw-r--r--src/core/Config.h3
-rw-r--r--src/core/Database.cpp11
-rw-r--r--src/core/Database.h5
-rw-r--r--src/core/DatabaseIcons.cpp2
-rw-r--r--src/core/DatabaseIcons.h2
-rw-r--r--src/core/Entry.cpp14
-rw-r--r--src/core/Entry.h1
-rw-r--r--src/core/FileWatcher.cpp238
-rw-r--r--src/core/FileWatcher.h92
-rw-r--r--src/core/TimeInfo.cpp6
-rw-r--r--src/core/Tools.cpp27
-rw-r--r--src/core/Tools.h27
-rw-r--r--src/crypto/CryptoHash.cpp6
-rw-r--r--src/crypto/CryptoHash.h2
-rw-r--r--src/crypto/Random.cpp20
-rw-r--r--src/crypto/Random.h13
-rw-r--r--src/crypto/SymmetricCipher.cpp2
-rw-r--r--src/crypto/SymmetricCipherBackend.h2
-rw-r--r--src/crypto/SymmetricCipherGcrypt.cpp27
-rw-r--r--src/crypto/SymmetricCipherGcrypt.h6
-rw-r--r--src/crypto/ssh/ASN1Key.cpp (renamed from src/sshagent/ASN1Key.cpp)47
-rw-r--r--src/crypto/ssh/ASN1Key.h (renamed from src/sshagent/ASN1Key.h)9
-rw-r--r--src/crypto/ssh/BinaryStream.cpp (renamed from src/sshagent/BinaryStream.cpp)0
-rw-r--r--src/crypto/ssh/BinaryStream.h (renamed from src/sshagent/BinaryStream.h)6
-rw-r--r--src/crypto/ssh/CMakeLists.txt14
-rw-r--r--src/crypto/ssh/OpenSSHKey.cpp (renamed from src/sshagent/OpenSSHKey.cpp)355
-rw-r--r--src/crypto/ssh/OpenSSHKey.h (renamed from src/sshagent/OpenSSHKey.h)51
-rw-r--r--src/crypto/ssh/bcrypt_pbkdf.cpp (renamed from src/sshagent/bcrypt_pbkdf.cpp)0
-rw-r--r--src/crypto/ssh/blf.h (renamed from src/sshagent/blf.h)0
-rw-r--r--src/crypto/ssh/blowfish.c (renamed from src/sshagent/blowfish.c)0
-rw-r--r--src/crypto/ssh/includes.h (renamed from src/sshagent/includes.h)0
-rw-r--r--src/format/Kdbx4Reader.cpp6
-rw-r--r--src/format/KdbxXmlWriter.cpp2
-rw-r--r--src/format/KdbxXmlWriter.h6
-rw-r--r--src/gui/AboutDialog.cpp3
-rw-r--r--src/gui/ApplicationSettingsWidget.cpp10
-rw-r--r--src/gui/DatabaseTabWidget.cpp50
-rw-r--r--src/gui/DatabaseTabWidget.h3
-rw-r--r--src/gui/DatabaseWidget.cpp62
-rw-r--r--src/gui/DatabaseWidget.h12
-rw-r--r--src/gui/DetailsWidget.cpp18
-rw-r--r--src/gui/DetailsWidget.h4
-rw-r--r--src/gui/DetailsWidget.ui252
-rw-r--r--src/gui/EditWidgetProperties.cpp41
-rw-r--r--src/gui/EditWidgetProperties.h13
-rw-r--r--src/gui/FileDialog.cpp66
-rw-r--r--src/gui/FileDialog.h11
-rw-r--r--src/gui/MainWindow.cpp17
-rw-r--r--src/gui/MainWindow.ui17
-rw-r--r--src/gui/dbsettings/DatabaseSettingsDialog.cpp47
-rw-r--r--src/gui/dbsettings/DatabaseSettingsDialog.h17
-rw-r--r--src/gui/entry/EditEntryWidget.cpp18
-rw-r--r--src/gui/entry/EditEntryWidget.h5
-rw-r--r--src/gui/group/EditGroupWidget.cpp100
-rw-r--r--src/gui/group/EditGroupWidget.h23
-rw-r--r--src/gui/group/GroupModel.cpp18
-rw-r--r--src/keeshare/CMakeLists.txt19
-rw-r--r--src/keeshare/DatabaseSettingsPageKeeShare.cpp53
-rw-r--r--src/keeshare/DatabaseSettingsPageKeeShare.h37
-rw-r--r--src/keeshare/DatabaseSettingsWidgetKeeShare.cpp72
-rw-r--r--src/keeshare/DatabaseSettingsWidgetKeeShare.h51
-rw-r--r--src/keeshare/DatabaseSettingsWidgetKeeShare.ui74
-rw-r--r--src/keeshare/KeeShare.cpp234
-rw-r--r--src/keeshare/KeeShare.h79
-rw-r--r--src/keeshare/KeeShareSettings.cpp463
-rw-r--r--src/keeshare/KeeShareSettings.h161
-rw-r--r--src/keeshare/SettingsPageKeeShare.cpp67
-rw-r--r--src/keeshare/SettingsPageKeeShare.h43
-rw-r--r--src/keeshare/SettingsWidgetKeeShare.cpp214
-rw-r--r--src/keeshare/SettingsWidgetKeeShare.h72
-rw-r--r--src/keeshare/SettingsWidgetKeeShare.ui249
-rw-r--r--src/keeshare/ShareObserver.cpp637
-rw-r--r--src/keeshare/ShareObserver.h112
-rw-r--r--src/keeshare/Signature.cpp260
-rw-r--r--src/keeshare/Signature.h34
-rw-r--r--src/keeshare/group/EditGroupPageKeeShare.cpp55
-rw-r--r--src/keeshare/group/EditGroupPageKeeShare.h37
-rw-r--r--src/keeshare/group/EditGroupWidgetKeeShare.cpp228
-rw-r--r--src/keeshare/group/EditGroupWidgetKeeShare.h59
-rw-r--r--src/keeshare/group/EditGroupWidgetKeeShare.ui139
-rw-r--r--src/sshagent/CMakeLists.txt7
-rw-r--r--src/sshagent/SSHAgent.cpp10
-rw-r--r--src/sshagent/SSHAgent.h8
87 files changed, 4923 insertions, 397 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 3621067e8..b39017718 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -51,6 +51,7 @@ set(keepassx_SOURCES
core/EntryAttributes.cpp
core/EntrySearcher.cpp
core/FilePath.cpp
+ core/FileWatcher.cpp
core/Global.h
core/Group.cpp
core/InactivityTimer.cpp
@@ -211,6 +212,7 @@ add_feature_info(Auto-Type WITH_XC_AUTOTYPE "Automatic password typing")
add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)")
add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with KeePassXC-Browser")
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
+add_feature_info(KeeShare WITH_XC_KEESHARE "Sharing integration with KeeShare")
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
if(APPLE)
add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration")
@@ -226,6 +228,17 @@ endif()
add_subdirectory(autotype)
add_subdirectory(cli)
+add_subdirectory(crypto/ssh)
+if(WITH_XC_CRYPTO_SSH)
+ set(crypto_ssh_LIB crypto_ssh)
+endif()
+
+
+add_subdirectory(keeshare)
+if(WITH_XC_KEESHARE)
+ set(keeshare_LIB keeshare)
+endif()
+
add_subdirectory(sshagent)
if(WITH_XC_SSHAGENT)
set(sshagent_LIB sshagent)
@@ -269,7 +282,6 @@ set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUIL
target_link_libraries(keepassx_core
autotype
${keepassxcbrowser_LIB}
- ${sshagent_LIB}
Qt5::Core
Qt5::Network
Qt5::Concurrent
@@ -280,7 +292,14 @@ target_link_libraries(keepassx_core
${ARGON2_LIBRARIES}
${GCRYPT_LIBRARIES}
${GPGERROR_LIBRARIES}
- ${ZLIB_LIBRARIES})
+ ${ZLIB_LIBRARIES})
+
+if(WITH_XC_SSHAGENT)
+ target_link_libraries(keepassx_core sshagent)
+endif()
+if(WITH_XC_KEESHARE)
+ target_link_libraries(keepassx_core keeshare)
+endif()
if(APPLE)
target_link_libraries(keepassx_core "-framework Foundation")
diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake
index d1f0723b4..b863af91c 100644
--- a/src/config-keepassx.h.cmake
+++ b/src/config-keepassx.h.cmake
@@ -17,6 +17,7 @@
#cmakedefine WITH_XC_BROWSER
#cmakedefine WITH_XC_YUBIKEY
#cmakedefine WITH_XC_SSHAGENT
+#cmakedefine WITH_XC_KEESHARE
#cmakedefine WITH_XC_TOUCHID
#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"
diff --git a/src/core/Clock.cpp b/src/core/Clock.cpp
index 02c2ae1bc..88ac4fb77 100644
--- a/src/core/Clock.cpp
+++ b/src/core/Clock.cpp
@@ -16,7 +16,7 @@
*/
#include "Clock.h"
-QSharedPointer<Clock> Clock::m_instance = QSharedPointer<Clock>();
+QSharedPointer<Clock> Clock::m_instance;
QDateTime Clock::currentDateTimeUtc()
{
@@ -92,7 +92,7 @@ QDateTime Clock::currentDateTimeImpl() const
void Clock::resetInstance()
{
- m_instance.clear();
+ m_instance.reset();
}
void Clock::setInstance(Clock* clock)
diff --git a/src/core/Config.cpp b/src/core/Config.cpp
index 8f1dc55fd..8fd2faad9 100644
--- a/src/core/Config.cpp
+++ b/src/core/Config.cpp
@@ -48,7 +48,16 @@ QString Config::getFileName()
void Config::set(const QString& key, const QVariant& value)
{
+ if (m_settings->contains(key) && m_settings->value(key) == value) {
+ return;
+ }
+ const bool surpressSignal = !m_settings->contains(key) && m_defaults.value(key) == value;
+
m_settings->setValue(key, value);
+
+ if (!surpressSignal) {
+ emit changed(key);
+ }
}
/**
diff --git a/src/core/Config.h b/src/core/Config.h
index fcb27e2ca..347350754 100644
--- a/src/core/Config.h
+++ b/src/core/Config.h
@@ -43,6 +43,9 @@ public:
static void createConfigFromFile(const QString& file);
static void createTempFileInstance();
+signals:
+ void changed(const QString& key);
+
private:
Config(const QString& fileName, QObject* parent);
explicit Config(QObject* parent);
diff --git a/src/core/Database.cpp b/src/core/Database.cpp
index 5b7a3c07d..607ecc93f 100644
--- a/src/core/Database.cpp
+++ b/src/core/Database.cpp
@@ -111,7 +111,7 @@ void Database::setFilePath(const QString& filePath)
m_filePath = filePath;
}
-Entry* Database::resolveEntry(const QUuid& uuid)
+Entry* Database::resolveEntry(const QUuid& uuid) const
{
return findEntryRecursive(uuid, m_rootGroup);
}
@@ -121,7 +121,7 @@ Entry* Database::resolveEntry(const QString& text, EntryReferenceType referenceT
return findEntryRecursive(text, referenceType, m_rootGroup);
}
-Entry* Database::findEntryRecursive(const QUuid& uuid, Group* group)
+Entry* Database::findEntryRecursive(const QUuid& uuid, Group* group) const
{
const QList<Entry*> entryList = group->entries();
for (Entry* entry : entryList) {
@@ -289,8 +289,11 @@ QByteArray Database::challengeResponseKey() const
bool Database::challengeMasterSeed(const QByteArray& masterSeed)
{
- m_data.masterSeed = masterSeed;
- return m_data.key->challenge(masterSeed, m_data.challengeResponseKey);
+ if (m_data.key) {
+ m_data.masterSeed = masterSeed;
+ return m_data.key->challenge(masterSeed, m_data.challengeResponseKey);
+ }
+ return true;
}
void Database::setCipher(const QUuid& cipher)
diff --git a/src/core/Database.h b/src/core/Database.h
index a5ae3effa..9253cb9ea 100644
--- a/src/core/Database.h
+++ b/src/core/Database.h
@@ -23,6 +23,7 @@
#include <QHash>
#include <QObject>
+#include "config-keepassx.h"
#include "crypto/kdf/Kdf.h"
#include "keys/CompositeKey.h"
@@ -88,7 +89,7 @@ public:
const Metadata* metadata() const;
QString filePath() const;
void setFilePath(const QString& filePath);
- Entry* resolveEntry(const QUuid& uuid);
+ Entry* resolveEntry(const QUuid& uuid) const;
Entry* resolveEntry(const QString& text, EntryReferenceType referenceType);
Group* resolveGroup(const QUuid& uuid);
QList<DeletedObject> deletedObjects();
@@ -149,7 +150,7 @@ private slots:
void startModifiedTimer();
private:
- Entry* findEntryRecursive(const QUuid& uuid, Group* group);
+ Entry* findEntryRecursive(const QUuid& uuid, Group* group) const;
Entry* findEntryRecursive(const QString& text, EntryReferenceType referenceType, Group* group);
Group* findGroupRecursive(const QUuid& uuid, Group* group);
diff --git a/src/core/DatabaseIcons.cpp b/src/core/DatabaseIcons.cpp
index ddb4e9106..6219d41f5 100644
--- a/src/core/DatabaseIcons.cpp
+++ b/src/core/DatabaseIcons.cpp
@@ -22,6 +22,8 @@
DatabaseIcons* DatabaseIcons::m_instance(nullptr);
const int DatabaseIcons::IconCount(69);
const int DatabaseIcons::ExpiredIconIndex(45);
+const int DatabaseIcons::SharedIconIndex(1);
+const int DatabaseIcons::UnsharedIconIndex(45);
// clang-format off
const char* const DatabaseIcons::m_indexToName[] = {
diff --git a/src/core/DatabaseIcons.h b/src/core/DatabaseIcons.h
index 43a6df216..ecd38fd8a 100644
--- a/src/core/DatabaseIcons.h
+++ b/src/core/DatabaseIcons.h
@@ -33,6 +33,8 @@ public:
static const int IconCount;
static const int ExpiredIconIndex;
+ static const int SharedIconIndex;
+ static const int UnsharedIconIndex;
private:
DatabaseIcons();
diff --git a/src/core/Entry.cpp b/src/core/Entry.cpp
index 929447f9c..f3391189c 100644
--- a/src/core/Entry.cpp
+++ b/src/core/Entry.cpp
@@ -955,6 +955,20 @@ QString Entry::maskPasswordPlaceholders(const QString& str) const
return result;
}
+Entry* Entry::resolveReference(const QString& str) const
+{
+ QRegularExpressionMatch match = EntryAttributes::matchReference(str);
+ if (!match.hasMatch()) {
+ return nullptr;
+ }
+
+ const QString searchIn = match.captured(EntryAttributes::SearchInGroupName);
+ const QString searchText = match.captured(EntryAttributes::SearchTextGroupName);
+
+ const EntryReferenceType searchInType = Entry::referenceType(searchIn);
+ return m_group->database()->resolveEntry(searchText, searchInType);
+}
+
QString Entry::resolveMultiplePlaceholders(const QString& str) const
{
return resolveMultiplePlaceholdersRecursive(str, ResolveMaximumDepth);
diff --git a/src/core/Entry.h b/src/core/Entry.h
index 05ed30bc0..94649444b 100644
--- a/src/core/Entry.h
+++ b/src/core/Entry.h
@@ -195,6 +195,7 @@ public:
Entry* clone(CloneFlags flags) const;
void copyDataFrom(const Entry* other);
QString maskPasswordPlaceholders(const QString& str) const;
+ Entry* resolveReference(const QString& str) const;
QString resolveMultiplePlaceholders(const QString& str) const;
QString resolvePlaceholder(const QString& str) const;
QString resolveUrlPlaceholder(const QString& str, PlaceholderType placeholderType) const;
diff --git a/src/core/FileWatcher.cpp b/src/core/FileWatcher.cpp
new file mode 100644
index 000000000..ac44174bd
--- /dev/null
+++ b/src/core/FileWatcher.cpp
@@ -0,0 +1,238 @@
+/*
+ * Copyright (C) 2011 Felix Geyer <debfx@fobos.de>
+ * Copyright (C) 2017 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 "FileWatcher.h"
+
+#include "core/Clock.h"
+#include <QFileInfo>
+
+#ifdef Q_OS_LINUX
+#include <sys/vfs.h>
+#endif
+
+namespace
+{
+ const int FileChangeDelay = 500;
+ const int TimerResolution = 100;
+}
+
+DelayingFileWatcher::DelayingFileWatcher(QObject* parent)
+ : QObject(parent)
+ , m_ignoreFileChange(false)
+{
+ connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
+ connect(&m_fileUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges()));
+ connect(&m_fileChangeDelayTimer, SIGNAL(timeout()), SIGNAL(fileChanged()));
+
+ m_fileChangeDelayTimer.setSingleShot(true);
+ m_fileUnblockTimer.setSingleShot(true);
+}
+
+void DelayingFileWatcher::restart()
+{
+ m_fileWatcher.addPath(m_filePath);
+}
+
+void DelayingFileWatcher::stop()
+{
+ m_fileWatcher.removePath(m_filePath);
+}
+
+void DelayingFileWatcher::start(const QString& filePath)
+{
+ if (!m_filePath.isEmpty()) {
+ m_fileWatcher.removePath(m_filePath);
+ }
+
+#if defined(Q_OS_LINUX)
+ struct statfs statfsBuf;
+ bool forcePolling = false;
+ const auto NFS_SUPER_MAGIC = 0x6969;
+
+ if (!statfs(filePath.toLocal8Bit().constData(), &statfsBuf)) {
+ forcePolling = (statfsBuf.f_type == NFS_SUPER_MAGIC);
+ } else {
+ // if we can't get the fs type let's fall back to polling
+ forcePolling = true;
+ }
+ auto objectName = forcePolling ? QLatin1String("_qt_autotest_force_engine_poller") : QLatin1String("");
+ m_fileWatcher.setObjectName(objectName);
+#endif
+
+ m_fileWatcher.addPath(filePath);
+
+ if (!filePath.isEmpty()) {
+ m_filePath = filePath;
+ }
+}
+
+void DelayingFileWatcher::ignoreFileChanges()
+{
+ m_ignoreFileChange = true;
+ m_fileChangeDelayTimer.stop();
+}
+
+void DelayingFileWatcher::observeFileChanges(bool delayed)
+{
+ int timeout = 0;
+ if (delayed) {
+ timeout = FileChangeDelay;
+ } else {
+ m_ignoreFileChange = false;
+ start(m_filePath);
+ }
+ if (timeout > 0 && !m_fileUnblockTimer.isActive()) {
+ m_fileUnblockTimer.start(timeout);
+ }
+}
+
+void DelayingFileWatcher::onWatchedFileChanged()
+{
+ if (m_ignoreFileChange) {
+ // the client forcefully silenced us
+ return;
+ }
+ if (m_fileChangeDelayTimer.isActive()) {
+ // we are waiting to fire the delayed fileChanged event, so nothing
+ // to do here
+ return;
+ }
+
+ m_fileChangeDelayTimer.start(FileChangeDelay);
+}
+
+BulkFileWatcher::BulkFileWatcher(QObject* parent)
+ : QObject(parent)
+{
+ connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(handleFileChanged(QString)));
+ connect(&m_fileWatcher, SIGNAL(directoryChanged(QString)), SLOT(handleDirectoryChanged(QString)));
+ connect(&m_fileWatchUnblockTimer, SIGNAL(timeout()), this, SLOT(observeFileChanges()));
+ m_fileWatchUnblockTimer.setSingleShot(true);
+}
+
+void BulkFileWatcher::clear()
+{
+ for (const QString& path : m_fileWatcher.files() + m_fileWatcher.directories()) {
+ const QFileInfo info(path);
+ m_fileWatcher.removePath(info.absoluteFilePath());
+ m_fileWatcher.removePath(info.absolutePath());
+ }
+ m_filePaths.clear();
+ m_watchedFilesInDirectory.clear();
+ m_ignoreFilesChangess.clear();
+}
+
+void BulkFileWatcher::removePath(const QString& path)
+{
+ const QFileInfo info(path);
+ m_fileWatcher.removePath(info.absoluteFilePath());
+ m_fileWatcher.removePath(info.absolutePath());
+ m_filePaths.remove(info.absoluteFilePath());
+ m_filePaths.remove(info.absolutePath());
+ m_watchedFilesInDirectory[info.absolutePath()].remove(info.absoluteFilePath());
+}
+
+void BulkFileWatcher::addPath(const QString& path)
+{
+ const QFileInfo info(path);
+ m_fileWatcher.addPath(info.absoluteFilePath());
+ m_fileWatcher.addPath(info.absolutePath());
+ m_filePaths.insert(info.absoluteFilePath());
+ m_filePaths.insert(info.absolutePath());
+ m_watchedFilesInDirectory[info.absolutePath()][info.absoluteFilePath()] = info.exists();
+}
+
+void BulkFileWatcher::restart(const QString& path)
+{
+ const QFileInfo info(path);
+ Q_ASSERT(m_filePaths.contains(info.absoluteFilePath()));
+ Q_ASSERT(m_filePaths.contains(info.absolutePath()));
+ m_fileWatcher.addPath(info.absoluteFilePath());
+ m_fileWatcher.addPath(info.absolutePath());
+}
+
+void BulkFileWatcher::handleFileChanged(const QString& path)
+{
+ addPath(path);
+
+ const QFileInfo info(path);
+ if (m_ignoreFilesChangess[info.canonicalFilePath()] > Clock::currentDateTimeUtc()) {
+ // changes are blocked
+ return;
+ }
+
+ emit fileChanged(path);
+}
+
+void BulkFileWatcher::handleDirectoryChanged(const QString& path)
+{
+ qDebug("Directory changed %s", qPrintable(path));
+ const QFileInfo directory(path);
+ const QMap<QString, bool>& watchedFiles = m_watchedFilesInDirectory[directory.absolutePath()];
+ for (const QString& file : watchedFiles.keys()) {
+ const QFileInfo info(file);
+ const bool existed = watchedFiles[info.absoluteFilePath()];
+ if (!info.exists() && existed) {
+ qDebug("Remove watch file %s", qPrintable(info.absoluteFilePath()));
+ m_fileWatcher.removePath(info.absolutePath());
+ emit fileRemoved(info.absoluteFilePath());
+ }
+ if (!existed && info.exists()) {
+ qDebug("Add watch file %s", qPrintable(info.absoluteFilePath()));
+ m_fileWatcher.addPath(info.absolutePath());
+ emit fileCreated(info.absoluteFilePath());
+ }
+ if (existed && info.exists()) {
+ qDebug("Refresh watch file %s", qPrintable(info.absoluteFilePath()));
+ m_fileWatcher.removePath(info.absolutePath());
+ m_fileWatcher.addPath(info.absolutePath());
+ emit fileChanged(info.absoluteFilePath());
+ }
+ m_watchedFilesInDirectory[info.absolutePath()][info.absoluteFilePath()] = info.exists();
+ }
+}
+
+void BulkFileWatcher::ignoreFileChanges(const QString& path)
+{
+ const QFileInfo info(path);
+ m_ignoreFilesChangess[info.canonicalFilePath()] = Clock::currentDateTimeUtc().addMSecs(FileChangeDelay);
+}
+
+void BulkFileWatcher::observeFileChanges(bool delayed)
+{
+ int timeout = 0;
+ if (delayed) {
+ timeout = TimerResolution;
+ } else {
+ const QDateTime current = Clock::currentDateTimeUtc();
+ for (const QString& key : m_ignoreFilesChangess.keys()) {
+ if (m_ignoreFilesChangess[key] < current) {
+ // We assume that there was no concurrent change of the database
+ // during our block - so no need to reimport
+ qDebug("Remove block from %s", qPrintable(key));
+ m_ignoreFilesChangess.remove(key);
+ continue;
+ }
+ qDebug("Keep block from %s", qPrintable(key));
+ timeout = static_cast<int>(current.msecsTo(m_ignoreFilesChangess[key]));
+ }
+ }
+ if (timeout > 0 && !m_fileWatchUnblockTimer.isActive()) {
+ m_fileWatchUnblockTimer.start(timeout);
+ }
+}
diff --git a/src/core/FileWatcher.h b/src/core/FileWatcher.h
new file mode 100644
index 000000000..de7dbb1c2
--- /dev/null
+++ b/src/core/FileWatcher.h
@@ -0,0 +1,92 @@
+/*
+ * 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_FILEWATCHER_H
+#define KEEPASSXC_FILEWATCHER_H
+
+#include <QFileSystemWatcher>
+#include <QSet>
+#include <QTimer>
+#include <QVariant>
+
+class DelayingFileWatcher : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit DelayingFileWatcher(QObject* parent = nullptr);
+
+ void blockAutoReload(bool block);
+ void start(const QString& path);
+
+ void restart();
+ void stop();
+ void ignoreFileChanges();
+
+signals:
+ void fileChanged();
+
+public slots:
+ void observeFileChanges(bool delayed = false);
+
+private slots:
+ void onWatchedFileChanged();
+
+private:
+ QString m_filePath;
+ QFileSystemWatcher m_fileWatcher;
+ QTimer m_fileChangeDelayTimer;
+ QTimer m_fileUnblockTimer;
+ bool m_ignoreFileChange;
+};
+
+class BulkFileWatcher : public QObject
+{
+ Q_OBJECT
+public:
+ explicit BulkFileWatcher(QObject* parent = nullptr);
+
+ void clear();
+
+ void removePath(const QString& path);
+ void addPath(const QString& path);
+
+ void restart(const QString& path);
+
+ void ignoreFileChanges(const QString& path);
+
+signals:
+ void fileCreated(QString);
+ void fileChanged(QString);
+ void fileRemoved(QString);
+
+public slots:
+ void observeFileChanges(bool delayed = false);
+
+private slots:
+ void handleFileChanged(const QString& path);
+ void handleDirectoryChanged(const QString& path);
+
+private:
+ QSet<QString> m_filePaths;
+ QMap<QString, QDateTime> m_ignoreFilesChangess;
+ QFileSystemWatcher m_fileWatcher;
+ QMap<QString, QMap<QString, bool>> m_watchedFilesInDirectory;
+ QTimer m_fileWatchUnblockTimer; // needed for Import/Export-References
+};
+
+#endif // KEEPASSXC_FILEWATCHER_H
diff --git a/src/core/TimeInfo.cpp b/src/core/TimeInfo.cpp
index c774a7c81..b48ad42ea 100644
--- a/src/core/TimeInfo.cpp
+++ b/src/core/TimeInfo.cpp
@@ -124,8 +124,7 @@ bool TimeInfo::equals(const TimeInfo& other, CompareItemOptions options) const
if (::compare(m_creationTime, other.m_creationTime, options) != 0) {
return false;
}
- if (::compare(!options.testFlag(CompareItemIgnoreStatistics), m_lastAccessTime, other.m_lastAccessTime, options)
- != 0) {
+ if (::compare(!options.testFlag(CompareItemIgnoreStatistics), m_lastAccessTime, other.m_lastAccessTime, options) != 0) {
return false;
}
if (::compare(m_expires, m_expiryTime, other.m_expires, other.expiryTime(), options) != 0) {
@@ -134,8 +133,7 @@ bool TimeInfo::equals(const TimeInfo& other, CompareItemOptions options) const
if (::compare(!options.testFlag(CompareItemIgnoreStatistics), m_usageCount, other.m_usageCount, options) != 0) {
return false;
}
- if (::compare(!options.testFlag(CompareItemIgnoreLocation), m_locationChanged, other.m_locationChanged, options)
- != 0) {
+ if (::compare(!options.testFlag(CompareItemIgnoreLocation), m_locationChanged, other.m_locationChanged, options) != 0) {
return false;
}
return true;
diff --git a/src/core/Tools.cpp b/src/core/Tools.cpp
index 458d42988..2dc75873b 100644
--- a/src/core/Tools.cpp
+++ b/src/core/Tools.cpp
@@ -346,4 +346,31 @@ namespace Tools
return bSuccess;
}
+
+ Buffer::Buffer()
+ : raw(nullptr)
+ , size(0)
+ {
+
+ }
+
+ Buffer::~Buffer()
+ {
+ clear();
+ }
+
+ void Buffer::clear()
+ {
+ if(size > 0){
+ free(raw);
+ }
+ raw = nullptr; size = 0;
+ }
+
+ QByteArray Buffer::content() const
+ {
+ return QByteArray(reinterpret_cast<char*>(raw), size );
+ }
+
+
} // namespace Tools
diff --git a/src/core/Tools.h b/src/core/Tools.h
index 4f75b750b..c1814f756 100644
--- a/src/core/Tools.h
+++ b/src/core/Tools.h
@@ -56,6 +56,33 @@ namespace Tools
}
}
+ template <typename Key, typename Value, void deleter(Value)> struct Map
+ {
+ QMap<Key, Value> values;
+ Value& operator[](const Key index)
+ {
+ return values[index];
+ }
+
+ ~Map()
+ {
+ for (Value m : values) {
+ deleter(m);
+ }
+ }
+ };
+
+ struct Buffer
+ {
+ unsigned char* raw;
+ size_t size;
+
+ Buffer();
+ ~Buffer();
+
+ void clear();
+ QByteArray content() const;
+ };
} // namespace Tools
#endif // KEEPASSX_TOOLS_H
diff --git a/src/crypto/CryptoHash.cpp b/src/crypto/CryptoHash.cpp
index 12c6bf791..986326af5 100644
--- a/src/crypto/CryptoHash.cpp
+++ b/src/crypto/CryptoHash.cpp
@@ -58,8 +58,7 @@ CryptoHash::CryptoHash(Algorithm algo, bool hmac)
gcry_error_t error = gcry_md_open(&d->ctx, algoGcrypt, flagsGcrypt);
if (error != GPG_ERR_NO_ERROR) {
- qWarning("Gcrypt error (ctor): %s", gcry_strerror(error));
- qWarning("Gcrypt error (ctor): %s", gcry_strsource(error));
+ qWarning("Gcrypt error (ctor): %s\n %s", gcry_strerror(error), gcry_strsource(error));
}
Q_ASSERT(error == 0); // TODO: error handling
@@ -92,8 +91,7 @@ void CryptoHash::setKey(const QByteArray& data)
gcry_error_t error = gcry_md_setkey(d->ctx, data.constData(), static_cast<size_t>(data.size()));
if (error) {
- qWarning("Gcrypt error (setKey): %s", gcry_strerror(error));
- qWarning("Gcrypt error (setKey): %s", gcry_strsource(error));
+ qWarning("Gcrypt error (setKey): %s\n %s", gcry_strerror(error), gcry_strsource(error));
}
Q_ASSERT(error == 0);
}
diff --git a/src/crypto/CryptoHash.h b/src/crypto/CryptoHash.h
index bd312121a..0d806af42 100644
--- a/src/crypto/CryptoHash.h
+++ b/src/crypto/CryptoHash.h
@@ -35,8 +35,8 @@ public:
~CryptoHash();
void addData(const QByteArray& data);
void reset();
- QByteArray result() const;
void setKey(const QByteArray& data);
+ QByteArray result() const;
static QByteArray hash(const QByteArray& data, Algorithm algo);
static QByteArray hmac(const QByteArray& data, const QByteArray& key, Algorithm algo);
diff --git a/src/crypto/Random.cpp b/src/crypto/Random.cpp
index 69c786306..4203b6c0c 100644
--- a/src/crypto/Random.cpp
+++ b/src/crypto/Random.cpp
@@ -28,7 +28,7 @@ public:
void randomize(void* data, int len) override;
};
-Random* Random::m_instance(nullptr);
+QSharedPointer<Random> Random::m_instance;
void Random::randomize(QByteArray& ba)
{
@@ -70,18 +70,20 @@ quint32 Random::randomUIntRange(quint32 min, quint32 max)
Random* Random::instance()
{
if (!m_instance) {
- m_instance = new Random(new RandomBackendGcrypt());
+ m_instance.reset(new Random(new RandomBackendGcrypt()));
}
- return m_instance;
+ return m_instance.data();
}
-void Random::createWithBackend(RandomBackend* backend)
+void Random::resetInstance()
{
- Q_ASSERT(backend);
- Q_ASSERT(!m_instance);
+ m_instance.reset();
+}
- m_instance = new Random(backend);
+void Random::setInstance(RandomBackend* backend)
+{
+ m_instance.reset(new Random(backend));
}
Random::Random(RandomBackend* backend)
@@ -95,3 +97,7 @@ void RandomBackendGcrypt::randomize(void* data, int len)
gcry_randomize(data, len, GCRY_STRONG_RANDOM);
}
+
+RandomBackend::~RandomBackend()
+{
+}
diff --git a/src/crypto/Random.h b/src/crypto/Random.h
index 1a36c4107..bdf7b9aca 100644
--- a/src/crypto/Random.h
+++ b/src/crypto/Random.h
@@ -20,14 +20,13 @@
#include <QByteArray>
#include <QScopedPointer>
+#include <QSharedPointer>
class RandomBackend
{
public:
virtual void randomize(void* data, int len) = 0;
- virtual ~RandomBackend()
- {
- }
+ virtual ~RandomBackend();
};
class Random
@@ -47,13 +46,17 @@ public:
quint32 randomUIntRange(quint32 min, quint32 max);
static Random* instance();
- static void createWithBackend(RandomBackend* backend);
+
+protected:
+ static void resetInstance();
+ static void setInstance(RandomBackend* backend);
private:
+ static QSharedPointer<Random> m_instance;
+
explicit Random(RandomBackend* backend);
QScopedPointer<RandomBackend> m_backend;
- static Random* m_instance;
Q_DISABLE_COPY(Random)
};
diff --git a/src/crypto/SymmetricCipher.cpp b/src/crypto/SymmetricCipher.cpp
index 0467ad7c2..108eebd84 100644
--- a/src/crypto/SymmetricCipher.cpp
+++ b/src/crypto/SymmetricCipher.cpp
@@ -89,7 +89,7 @@ int SymmetricCipher::blockSize() const
QString SymmetricCipher::errorString() const
{
- return m_backend->errorString();
+ return m_backend->error();
}
SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(const QUuid& cipher)
diff --git a/src/crypto/SymmetricCipherBackend.h b/src/crypto/SymmetricCipherBackend.h
index 27a39177e..649e68313 100644
--- a/src/crypto/SymmetricCipherBackend.h
+++ b/src/crypto/SymmetricCipherBackend.h
@@ -38,7 +38,7 @@ public:
virtual int keySize() const = 0;
virtual int blockSize() const = 0;
- virtual QString errorString() const = 0;
+ virtual QString error() const = 0;
};
#endif // KEEPASSX_SYMMETRICCIPHERBACKEND_H
diff --git a/src/crypto/SymmetricCipherGcrypt.cpp b/src/crypto/SymmetricCipherGcrypt.cpp
index c7a5e6a07..0b533a882 100644
--- a/src/crypto/SymmetricCipherGcrypt.cpp
+++ b/src/crypto/SymmetricCipherGcrypt.cpp
@@ -80,13 +80,12 @@ int SymmetricCipherGcrypt::gcryptMode(SymmetricCipher::Mode mode)
}
}
-void SymmetricCipherGcrypt::setErrorString(gcry_error_t err)
+void SymmetricCipherGcrypt::setError(const gcry_error_t& err)
{
const char* gcryptError = gcry_strerror(err);
const char* gcryptErrorSource = gcry_strsource(err);
- m_errorString =
- QString("%1/%2").arg(QString::fromLocal8Bit(gcryptErrorSource), QString::fromLocal8Bit(gcryptError));
+ m_error = QString("%1/%2").arg(QString::fromLocal8Bit(gcryptErrorSource), QString::fromLocal8Bit(gcryptError));
}
bool SymmetricCipherGcrypt::init()
@@ -99,7 +98,7 @@ bool SymmetricCipherGcrypt::init()
gcry_cipher_close(m_ctx);
error = gcry_cipher_open(&m_ctx, m_algo, m_mode, 0);
if (error != 0) {
- setErrorString(error);
+ setError(error);
return false;
}
@@ -112,7 +111,7 @@ bool SymmetricCipherGcrypt::setKey(const QByteArray& key)
gcry_error_t error = gcry_cipher_setkey(m_ctx, m_key.constData(), m_key.size());
if (error != 0) {
- setErrorString(error);
+ setError(error);
return false;
}
@@ -131,7 +130,7 @@ bool SymmetricCipherGcrypt::setIv(const QByteArray& iv)
}
if (error != 0) {
- setErrorString(error);
+ setError(error);
return false;
}
@@ -154,7 +153,7 @@ QByteArray SymmetricCipherGcrypt::process(const QByteArray& data, bool* ok)
}
if (error != 0) {
- setErrorString(error);
+ setError(error);
*ok = false;
} else {
*ok = true;
@@ -176,7 +175,7 @@ bool SymmetricCipherGcrypt::processInPlace(QByteArray& data)
}
if (error != 0) {
- setErrorString(error);
+ setError(error);
return false;
}
@@ -197,7 +196,7 @@ bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
error = gcry_cipher_decrypt(m_ctx, rawData, size, nullptr, 0);
if (error != 0) {
- setErrorString(error);
+ setError(error);
return false;
}
}
@@ -206,7 +205,7 @@ bool SymmetricCipherGcrypt::processInPlace(QByteArray& data, quint64 rounds)
error = gcry_cipher_encrypt(m_ctx, rawData, size, nullptr, 0);
if (error != 0) {
- setErrorString(error);
+ setError(error);
return false;
}
}
@@ -221,13 +220,13 @@ bool SymmetricCipherGcrypt::reset()
error = gcry_cipher_reset(m_ctx);
if (error != 0) {
- setErrorString(error);
+ setError(error);
return false;
}
error = gcry_cipher_setiv(m_ctx, m_iv.constData(), m_iv.size());
if (error != 0) {
- setErrorString(error);
+ setError(error);
return false;
}
@@ -258,7 +257,7 @@ int SymmetricCipherGcrypt::blockSize() const
return blockSizeT;
}
-QString SymmetricCipherGcrypt::errorString() const
+QString SymmetricCipherGcrypt::error() const
{
- return m_errorString;
+ return m_error;
}
diff --git a/src/crypto/SymmetricCipherGcrypt.h b/src/crypto/SymmetricCipherGcrypt.h
index 6f806b90b..0c5c00099 100644
--- a/src/crypto/SymmetricCipherGcrypt.h
+++ b/src/crypto/SymmetricCipherGcrypt.h
@@ -43,12 +43,12 @@ public:
int keySize() const;
int blockSize() const;
- QString errorString() const;
+ QString error() const;
private:
static int gcryptAlgo(SymmetricCipher::Algorithm algo);
static int gcryptMode(SymmetricCipher::Mode mode);
- void setErrorString(gcry_error_t err);
+ void setError(const gcry_error_t& err);
gcry_cipher_hd_t m_ctx;
const int m_algo;
@@ -56,7 +56,7 @@ private:
const SymmetricCipher::Direction m_direction;
QByteArray m_key;
QByteArray m_iv;
- QString m_errorString;
+ QString m_error;
};
#endif // KEEPASSX_SYMMETRICCIPHERGCRYPT_H
diff --git a/src/sshagent/ASN1Key.cpp b/src/crypto/ssh/ASN1Key.cpp
index dc6da2adc..5a83bcee9 100644
--- a/src/sshagent/ASN1Key.cpp
+++ b/src/crypto/ssh/ASN1Key.cpp
@@ -17,6 +17,8 @@
*/
#include "ASN1Key.h"
+#include "crypto/ssh/BinaryStream.h"
+
#include <gcrypt.h>
namespace
@@ -53,7 +55,17 @@ namespace
return true;
}
- bool parseHeader(BinaryStream& stream, quint8 wantedType)
+ bool parsePublicHeader(BinaryStream& stream)
+ {
+ quint8 tag;
+ quint32 len;
+
+ nextTag(stream, tag, len);
+
+ return (tag == TAG_SEQUENCE);
+ }
+
+ bool parsePrivateHeader(BinaryStream& stream, quint8 wantedType)
{
quint8 tag;
quint32 len;
@@ -118,7 +130,7 @@ bool ASN1Key::parseDSA(QByteArray& ba, OpenSSHKey& key)
{
BinaryStream stream(&ba);
- if (!parseHeader(stream, KEY_ZERO)) {
+ if (!parsePrivateHeader(stream, KEY_ZERO)) {
return false;
}
@@ -149,11 +161,38 @@ bool ASN1Key::parseDSA(QByteArray& ba, OpenSSHKey& key)
return true;
}
-bool ASN1Key::parseRSA(QByteArray& ba, OpenSSHKey& key)
+bool ASN1Key::parsePublicRSA(QByteArray& ba, OpenSSHKey& key)
+{
+ BinaryStream stream(&ba);
+
+ if (!parsePublicHeader(stream)) {
+ return false;
+ }
+
+ QByteArray n, e;
+ readInt(stream, n);
+ readInt(stream, e);
+
+ QList<QByteArray> publicData;
+ publicData.append(e);
+ publicData.append(n);
+
+ QList<QByteArray> privateData;
+ privateData.append(n);
+ privateData.append(e);
+
+ key.setType("ssh-rsa");
+ key.setPublicData(publicData);
+ key.setPrivateData(privateData);
+ key.setComment("");
+ return true;
+}
+
+bool ASN1Key::parsePrivateRSA(QByteArray& ba, OpenSSHKey& key)
{
BinaryStream stream(&ba);
- if (!parseHeader(stream, KEY_ZERO)) {
+ if (!parsePrivateHeader(stream, KEY_ZERO)) {
return false;
}
diff --git a/src/sshagent/ASN1Key.h b/src/crypto/ssh/ASN1Key.h
index 59f8d4e81..54a2bde4e 100644
--- a/src/sshagent/ASN1Key.h
+++ b/src/crypto/ssh/ASN1Key.h
@@ -16,8 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef ASN1KEY_H
-#define ASN1KEY_H
+#ifndef KEEPASSXC_ASN1KEY_H
+#define KEEPASSXC_ASN1KEY_H
#include "OpenSSHKey.h"
#include <QtCore>
@@ -25,7 +25,8 @@
namespace ASN1Key
{
bool parseDSA(QByteArray& ba, OpenSSHKey& key);
- bool parseRSA(QByteArray& ba, OpenSSHKey& key);
+ bool parsePrivateRSA(QByteArray& ba, OpenSSHKey& key);
+ bool parsePublicRSA(QByteArray& ba, OpenSSHKey& key);
}
-#endif // ASN1KEY_H
+#endif // KEEPASSXC_ASN1KEY_H
diff --git a/src/sshagent/BinaryStream.cpp b/src/crypto/ssh/BinaryStream.cpp
index 2aa8ac1c7..2aa8ac1c7 100644
--- a/src/sshagent/BinaryStream.cpp
+++ b/src/crypto/ssh/BinaryStream.cpp
diff --git a/src/sshagent/BinaryStream.h b/src/crypto/ssh/BinaryStream.h
index fa9ded81a..8f4155b65 100644
--- a/src/sshagent/BinaryStream.h
+++ b/src/crypto/ssh/BinaryStream.h
@@ -16,8 +16,8 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef BINARYSTREAM_H
-#define BINARYSTREAM_H
+#ifndef KEEPASSXC_BINARYSTREAM_H
+#define KEEPASSXC_BINARYSTREAM_H
#include <QBuffer>
#include <QIODevice>
@@ -65,4 +65,4 @@ private:
QScopedPointer<QBuffer> m_buffer;
};
-#endif // BINARYSTREAM_H
+#endif // KEEPASSXC_BINARYSTREAM_H
diff --git a/src/crypto/ssh/CMakeLists.txt b/src/crypto/ssh/CMakeLists.txt
new file mode 100644
index 000000000..709dd2f95
--- /dev/null
+++ b/src/crypto/ssh/CMakeLists.txt
@@ -0,0 +1,14 @@
+if(WITH_XC_CRYPTO_SSH)
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
+
+ set(crypto_ssh_SOURCES
+ bcrypt_pbkdf.cpp
+ blowfish.c
+ ASN1Key.cpp
+ BinaryStream.cpp
+ OpenSSHKey.cpp
+ )
+
+ add_library(crypto_ssh STATIC ${crypto_ssh_SOURCES})
+ target_link_libraries(crypto_ssh Qt5::Core ${GCRYPT_LIBRARIES})
+endif()
diff --git a/src/sshagent/OpenSSHKey.cpp b/src/crypto/ssh/OpenSSHKey.cpp
index 44684d620..91f641401 100644
--- a/src/sshagent/OpenSSHKey.cpp
+++ b/src/crypto/ssh/OpenSSHKey.cpp
@@ -17,29 +17,202 @@
*/
#include "OpenSSHKey.h"
-#include "ASN1Key.h"
+
+#include "core/Tools.h"
#include "crypto/SymmetricCipher.h"
+#include "crypto/ssh/ASN1Key.h"
+#include "crypto/ssh/BinaryStream.h"
+
#include <QCryptographicHash>
#include <QRegularExpression>
#include <QStringList>
-const QString OpenSSHKey::TYPE_DSA = "DSA PRIVATE KEY";
-const QString OpenSSHKey::TYPE_RSA = "RSA PRIVATE KEY";
-const QString OpenSSHKey::TYPE_OPENSSH = "OPENSSH PRIVATE KEY";
+#include <gcrypt.h>
+
+const QString OpenSSHKey::TYPE_DSA_PRIVATE = "DSA PRIVATE KEY";
+const QString OpenSSHKey::TYPE_RSA_PRIVATE = "RSA PRIVATE KEY";
+const QString OpenSSHKey::TYPE_RSA_PUBLIC = "RSA PUBLIC KEY";
+const QString OpenSSHKey::TYPE_OPENSSH_PRIVATE = "OPENSSH PRIVATE KEY";
+
+namespace
+{
+ QPair<QString, QList<QByteArray>> binaryDeserialize(const QByteArray& serialized)
+ {
+ if (serialized.isEmpty()) {
+ return {};
+ }
+ QBuffer buffer;
+ buffer.setData(serialized);
+ buffer.open(QBuffer::ReadOnly);
+ BinaryStream stream(&buffer);
+ QString type;
+ stream.readString(type);
+ QByteArray temp;
+ QList<QByteArray> data;
+ while (stream.readString(temp)) {
+ data << temp;
+ }
+ return ::qMakePair(type, data);
+ }
+
+ QByteArray binarySerialize(const QString& type, const QList<QByteArray>& data)
+ {
+ if (type.isEmpty() && data.isEmpty()) {
+ return {};
+ }
+ QByteArray buffer;
+ BinaryStream stream(&buffer);
+ stream.writeString(type);
+ for (const QByteArray& part : data) {
+ stream.writeString(part);
+ }
+ return buffer;
+ }
+}
// bcrypt_pbkdf.cpp
int bcrypt_pbkdf(const QByteArray& pass, const QByteArray& salt, QByteArray& key, quint32 rounds);
+OpenSSHKey OpenSSHKey::generate(bool secure)
+{
+ enum Index
+ {
+ Params,
+ CombinedKey,
+ PrivateKey,
+ PublicKey,
+
+ Private_N,
+ Private_E,
+ Private_D,
+ Private_P,
+ Private_Q,
+ Private_U, // private key
+ Public_N,
+ Public_E,
+ };
+
+ Tools::Map<Index, gcry_mpi_t, &gcry_mpi_release> mpi;
+ Tools::Map<Index, gcry_sexp_t, &gcry_sexp_release> sexp;
+ gcry_error_t rc = GPG_ERR_NO_ERROR;
+ rc = gcry_sexp_build(&sexp[Params], NULL, secure ? "(genkey (rsa (nbits 4:2048)))" : "(genkey (rsa (transient-key) (nbits 4:2048)))");
+ if (rc != GPG_ERR_NO_ERROR) {
+ qWarning() << "Could not create ssh key" << gcry_err_code(rc);
+ return OpenSSHKey();
+ }
+
+ rc = gcry_pk_genkey(&sexp[CombinedKey], sexp[Params]);
+ if (rc != GPG_ERR_NO_ERROR) {
+ qWarning() << "Could not create ssh key" << gcry_err_code(rc);
+ return OpenSSHKey();
+ }
+
+ sexp[PrivateKey] = gcry_sexp_find_token(sexp[CombinedKey], "private-key", 0);
+ sexp[PublicKey] = gcry_sexp_find_token(sexp[CombinedKey], "public-key", 0);
+
+ sexp[Private_N] = gcry_sexp_find_token(sexp[PrivateKey], "n", 1);
+ mpi[Private_N] = gcry_sexp_nth_mpi(sexp[Private_N], 1, GCRYMPI_FMT_USG);
+ sexp[Private_E] = gcry_sexp_find_token(sexp[PrivateKey], "e", 1);
+ mpi[Private_E] = gcry_sexp_nth_mpi(sexp[Private_E], 1, GCRYMPI_FMT_USG);
+ sexp[Private_D] = gcry_sexp_find_token(sexp[PrivateKey], "d", 1);
+ mpi[Private_D] = gcry_sexp_nth_mpi(sexp[Private_D], 1, GCRYMPI_FMT_USG);
+ sexp[Private_Q] = gcry_sexp_find_token(sexp[PrivateKey], "q", 1);
+ mpi[Private_Q] = gcry_sexp_nth_mpi(sexp[Private_Q], 1, GCRYMPI_FMT_USG);
+ sexp[Private_P] = gcry_sexp_find_token(sexp[PrivateKey], "p", 1);
+ mpi[Private_P] = gcry_sexp_nth_mpi(sexp[Private_P], 1, GCRYMPI_FMT_USG);
+ sexp[Private_U] = gcry_sexp_find_token(sexp[PrivateKey], "u", 1);
+ mpi[Private_U] = gcry_sexp_nth_mpi(sexp[Private_U], 1, GCRYMPI_FMT_USG);
+
+ sexp[Public_N] = gcry_sexp_find_token(sexp[PublicKey], "n", 1);
+ mpi[Public_N] = gcry_sexp_nth_mpi(sexp[Public_N], 1, GCRYMPI_FMT_USG);
+ sexp[Public_E] = gcry_sexp_find_token(sexp[PublicKey], "e", 1);
+ mpi[Public_E] = gcry_sexp_nth_mpi(sexp[Public_E], 1, GCRYMPI_FMT_USG);
+
+ QList<QByteArray> publicParts;
+ QList<QByteArray> privateParts;
+ Tools::Buffer buffer;
+ gcry_mpi_format format = GCRYMPI_FMT_USG;
+ rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_N]);
+ if (rc != GPG_ERR_NO_ERROR) {
+ qWarning() << "Could not extract private key part" << gcry_err_code(rc);
+ return OpenSSHKey();
+ }
+ privateParts << buffer.content();
+
+ buffer.clear();
+ rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_E]);
+ if (rc != GPG_ERR_NO_ERROR) {
+ qWarning() << "Could not extract private key part" << gcry_err_code(rc);
+ return OpenSSHKey();
+ }
+ privateParts << buffer.content();
+
+ buffer.clear();
+ rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_D]);
+ if (rc != GPG_ERR_NO_ERROR) {
+ qWarning() << "Could not extract private key part" << gcry_err_code(rc);
+ return OpenSSHKey();
+ }
+ privateParts << buffer.content();
+
+ buffer.clear();
+ rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_U]);
+ if (rc != GPG_ERR_NO_ERROR) {
+ qWarning() << "Could not extract private key part" << gcry_err_code(rc);
+ return OpenSSHKey();
+ }
+ privateParts << buffer.content();
+
+ buffer.clear();
+ rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_P]);
+ if (rc != GPG_ERR_NO_ERROR) {
+ qWarning() << "Could not extract private key part" << gcry_err_code(rc);
+ return OpenSSHKey();
+ }
+ privateParts << buffer.content();
+
+ buffer.clear();
+ rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Private_Q]);
+ if (rc != GPG_ERR_NO_ERROR) {
+ qWarning() << "Could not extract private key part" << gcry_err_code(rc);
+ return OpenSSHKey();
+ }
+ privateParts << buffer.content();
+
+ buffer.clear();
+ rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Public_E]);
+ if (rc != GPG_ERR_NO_ERROR) {
+ qWarning() << "Could not extract public key part" << gcry_err_code(rc);
+ return OpenSSHKey();
+ }
+ publicParts << buffer.content();
+
+ buffer.clear();
+ rc = gcry_mpi_aprint(format, &buffer.raw, &buffer.size, mpi[Public_N]);
+ if (rc != GPG_ERR_NO_ERROR) {
+ qWarning() << "Could not extract public key part" << gcry_err_code(rc);
+ return OpenSSHKey();
+ }
+ publicParts << buffer.content();
+ OpenSSHKey key;
+ key.m_rawType = OpenSSHKey::TYPE_RSA_PRIVATE;
+ key.setType("ssh-rsa");
+ key.setPublicData(publicParts);
+ key.setPrivateData(privateParts);
+ key.setComment("");
+ return key;
+}
+
OpenSSHKey::OpenSSHKey(QObject* parent)
: QObject(parent)
, m_type(QString())
, m_cipherName(QString("none"))
, m_kdfName(QString("none"))
, m_kdfOptions(QByteArray())
- , m_rawPrivateData(QByteArray())
- , m_publicData(QList<QByteArray>())
- , m_privateData(QList<QByteArray>())
- , m_privateType(QString())
+ , m_rawType(QString())
+ , m_rawData(QByteArray())
+ , m_rawPublicData(QList<QByteArray>())
+ , m_rawPrivateData(QList<QByteArray>())
, m_comment(QString())
, m_error(QString())
{
@@ -51,9 +224,10 @@ OpenSSHKey::OpenSSHKey(const OpenSSHKey& other)
, m_cipherName(other.m_cipherName)
, m_kdfName(other.m_kdfName)
, m_kdfOptions(other.m_kdfOptions)
+ , m_rawType(other.m_rawType)
+ , m_rawData(other.m_rawData)
+ , m_rawPublicData(other.m_rawPublicData)
, m_rawPrivateData(other.m_rawPrivateData)
- , m_publicData(other.m_publicData)
- , m_privateData(other.m_privateData)
, m_comment(other.m_comment)
, m_error(other.m_error)
{
@@ -77,22 +251,21 @@ const QString OpenSSHKey::type() const
int OpenSSHKey::keyLength() const
{
- if (m_type == "ssh-dss" && m_publicData.length() == 4) {
- return (m_publicData[0].length() - 1) * 8;
- } else if (m_type == "ssh-rsa" && m_publicData.length() == 2) {
- return (m_publicData[1].length() - 1) * 8;
- } else if (m_type.startsWith("ecdsa-sha2-") && m_publicData.length() == 2) {
- return (m_publicData[1].length() - 1) * 4;
- } else if (m_type == "ssh-ed25519" && m_publicData.length() == 1) {
- return m_publicData[0].length() * 8;
+ if (m_type == "ssh-dss" && m_rawPublicData.length() == 4) {
+ return (m_rawPublicData[0].length() - 1) * 8;
+ } else if (m_type == "ssh-rsa" && m_rawPublicData.length() == 2) {
+ return (m_rawPublicData[1].length() - 1) * 8;
+ } else if (m_type.startsWith("ecdsa-sha2-") && m_rawPublicData.length() == 2) {
+ return (m_rawPublicData[1].length() - 1) * 4;
+ } else if (m_type == "ssh-ed25519" && m_rawPublicData.length() == 1) {
+ return m_rawPublicData[0].length() * 8;
}
-
return 0;
}
const QString OpenSSHKey::fingerprint(QCryptographicHash::Algorithm algo) const
{
- if (m_publicData.isEmpty()) {
+ if (m_rawPublicData.isEmpty()) {
return {};
}
@@ -101,7 +274,7 @@ const QString OpenSSHKey::fingerprint(QCryptographicHash::Algorithm algo) const
stream.writeString(m_type);
- for (QByteArray ba : m_publicData) {
+ for (const QByteArray& ba : m_rawPublicData) {
stream.writeString(ba);
}
@@ -126,9 +299,27 @@ const QString OpenSSHKey::comment() const
return m_comment;
}
+const QString OpenSSHKey::privateKey() const
+{
+ if (m_rawPrivateData.isEmpty()) {
+ return {};
+ }
+
+ QByteArray privateKey;
+ BinaryStream stream(&privateKey);
+
+ stream.writeString(m_type);
+
+ for (QByteArray ba : m_rawPrivateData) {
+ stream.writeString(ba);
+ }
+
+ return m_type + " " + QString::fromLatin1(privateKey.toBase64()) + " " + m_comment;
+}
+
const QString OpenSSHKey::publicKey() const
{
- if (m_publicData.isEmpty()) {
+ if (m_rawPublicData.isEmpty()) {
return {};
}
@@ -137,7 +328,7 @@ const QString OpenSSHKey::publicKey() const
stream.writeString(m_type);
- for (QByteArray ba : m_publicData) {
+ for (QByteArray ba : m_rawPublicData) {
stream.writeString(ba);
}
@@ -156,12 +347,12 @@ void OpenSSHKey::setType(const QString& type)
void OpenSSHKey::setPublicData(const QList<QByteArray>& data)
{
- m_publicData = data;
+ m_rawPublicData = data;
}
void OpenSSHKey::setPrivateData(const QList<QByteArray>& data)
{
- m_privateData = data;
+ m_rawPrivateData = data;
}
void OpenSSHKey::setComment(const QString& comment)
@@ -171,11 +362,11 @@ void OpenSSHKey::setComment(const QString& comment)
void OpenSSHKey::clearPrivate()
{
+ m_rawData.clear();
m_rawPrivateData.clear();
- m_privateData.clear();
}
-bool OpenSSHKey::parsePEM(const QByteArray& in, QByteArray& out)
+bool OpenSSHKey::extractPEM(const QByteArray& in, QByteArray& out)
{
QString pem = QString::fromLatin1(in);
QStringList rows = pem.split(QRegularExpression("(?:\r?\n|\r)"), QString::SkipEmptyParts);
@@ -201,7 +392,7 @@ bool OpenSSHKey::parsePEM(const QByteArray& in, QByteArray& out)
return false;
}
- m_privateType = beginMatch.captured(1);
+ m_rawType = beginMatch.captured(1);
rows.removeFirst();
rows.removeLast();
@@ -237,17 +428,17 @@ bool OpenSSHKey::parsePEM(const QByteArray& in, QByteArray& out)
return true;
}
-bool OpenSSHKey::parse(const QByteArray& in)
+bool OpenSSHKey::parsePKCS1PEM(const QByteArray& in)
{
QByteArray data;
- if (!parsePEM(in, data)) {
+ if (!extractPEM(in, data)) {
return false;
}
- if (m_privateType == TYPE_DSA || m_privateType == TYPE_RSA) {
- m_rawPrivateData = data;
- } else if (m_privateType == TYPE_OPENSSH) {
+ if (m_rawType == TYPE_DSA_PRIVATE || m_rawType == TYPE_RSA_PRIVATE || m_rawType == TYPE_RSA_PUBLIC) {
+ m_rawData = data;
+ } else if (m_rawType == TYPE_OPENSSH_PRIVATE) {
BinaryStream stream(&data);
QByteArray magic;
@@ -291,18 +482,18 @@ bool OpenSSHKey::parse(const QByteArray& in)
}
// padded list of keys
- if (!stream.readString(m_rawPrivateData)) {
+ if (!stream.readString(m_rawData)) {
m_error = tr("Corrupted key file, reading private key failed");
return false;
}
} else {
- m_error = tr("Unsupported key type: %1").arg(m_privateType);
+ m_error = tr("Unsupported key type: %1").arg(m_rawType);
return false;
}
// load private if no encryption
if (!encrypted()) {
- return openPrivateKey();
+ return openKey();
}
return true;
@@ -313,15 +504,15 @@ bool OpenSSHKey::encrypted() const
return (m_cipherName != "none");
}
-bool OpenSSHKey::openPrivateKey(const QString& passphrase)
+bool OpenSSHKey::openKey(const QString& passphrase)
{
QScopedPointer<SymmetricCipher> cipher;
- if (!m_privateData.isEmpty()) {
+ if (!m_rawPrivateData.isEmpty()) {
return true;
}
- if (m_rawPrivateData.isEmpty()) {
+ if (m_rawData.isEmpty()) {
m_error = tr("No private key payload to decrypt");
return false;
}
@@ -390,7 +581,7 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
hash.addData(m_cipherIV.data(), 8);
mdBuf = hash.result();
keyData.append(mdBuf);
- } while(keyData.size() < cipher->keySize());
+ } while (keyData.size() < cipher->keySize());
if (keyData.size() > cipher->keySize()) {
// If our key size isn't a multiple of 16 (e.g. AES-192 or something),
@@ -407,33 +598,38 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
return false;
}
- QByteArray rawPrivateData = m_rawPrivateData;
+ QByteArray rawData = m_rawData;
if (cipher && cipher->isInitalized()) {
bool ok = false;
- rawPrivateData = cipher->process(rawPrivateData, &ok);
+ rawData = cipher->process(rawData, &ok);
if (!ok) {
m_error = tr("Decryption failed, wrong passphrase?");
return false;
}
}
- if (m_privateType == TYPE_DSA) {
- if (!ASN1Key::parseDSA(rawPrivateData, *this)) {
+ if (m_rawType == TYPE_DSA_PRIVATE) {
+ if (!ASN1Key::parseDSA(rawData, *this)) {
m_error = tr("Decryption failed, wrong passphrase?");
return false;
}
return true;
- } else if (m_privateType == TYPE_RSA) {
- if (!ASN1Key::parseRSA(rawPrivateData, *this)) {
+ } else if (m_rawType == TYPE_RSA_PRIVATE) {
+ if (!ASN1Key::parsePrivateRSA(rawData, *this)) {
m_error = tr("Decryption failed, wrong passphrase?");
return false;
}
-
return true;
- } else if (m_privateType == TYPE_OPENSSH) {
- BinaryStream keyStream(&rawPrivateData);
+ } else if (m_rawType == TYPE_RSA_PUBLIC) {
+ if (!ASN1Key::parsePublicRSA(rawData, *this)) {
+ m_error = tr("Decryption failed, wrong passphrase?");
+ return false;
+ }
+ return true;
+ } else if (m_rawType == TYPE_OPENSSH_PRIVATE) {
+ BinaryStream keyStream(&rawData);
quint32 checkInt1;
quint32 checkInt2;
@@ -449,13 +645,13 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
return readPrivate(keyStream);
}
- m_error = tr("Unsupported key type: %1").arg(m_privateType);
+ m_error = tr("Unsupported key type: %1").arg(m_rawType);
return false;
}
bool OpenSSHKey::readPublic(BinaryStream& stream)
{
- m_publicData.clear();
+ m_rawPublicData.clear();
if (!stream.readString(m_type)) {
m_error = tr("Unexpected EOF while reading public key");
@@ -484,7 +680,7 @@ bool OpenSSHKey::readPublic(BinaryStream& stream)
return false;
}
- m_publicData.append(t);
+ m_rawPublicData.append(t);
}
return true;
@@ -492,7 +688,7 @@ bool OpenSSHKey::readPublic(BinaryStream& stream)
bool OpenSSHKey::readPrivate(BinaryStream& stream)
{
- m_privateData.clear();
+ m_rawPrivateData.clear();
if (!stream.readString(m_type)) {
m_error = tr("Unexpected EOF while reading private key");
@@ -521,7 +717,7 @@ bool OpenSSHKey::readPrivate(BinaryStream& stream)
return false;
}
- m_privateData.append(t);
+ m_rawPrivateData.append(t);
}
if (!stream.readString(m_comment)) {
@@ -534,7 +730,7 @@ bool OpenSSHKey::readPrivate(BinaryStream& stream)
bool OpenSSHKey::writePublic(BinaryStream& stream)
{
- if (m_publicData.isEmpty()) {
+ if (m_rawPublicData.isEmpty()) {
m_error = tr("Can't write public key as it is empty");
return false;
}
@@ -544,7 +740,7 @@ bool OpenSSHKey::writePublic(BinaryStream& stream)
return false;
}
- for (QByteArray t : m_publicData) {
+ for (QByteArray t : m_rawPublicData) {
if (!stream.writeString(t)) {
m_error = tr("Unexpected EOF when writing public key");
return false;
@@ -556,7 +752,7 @@ bool OpenSSHKey::writePublic(BinaryStream& stream)
bool OpenSSHKey::writePrivate(BinaryStream& stream)
{
- if (m_privateData.isEmpty()) {
+ if (m_rawPrivateData.isEmpty()) {
m_error = tr("Can't write private key as it is empty");
return false;
}
@@ -566,7 +762,7 @@ bool OpenSSHKey::writePrivate(BinaryStream& stream)
return false;
}
- for (QByteArray t : m_privateData) {
+ for (QByteArray t : m_rawPrivateData) {
if (!stream.writeString(t)) {
m_error = tr("Unexpected EOF when writing private key");
return false;
@@ -581,6 +777,49 @@ bool OpenSSHKey::writePrivate(BinaryStream& stream)
return true;
}
+QList<QByteArray> OpenSSHKey::publicParts() const
+{
+ return m_rawPublicData;
+}
+
+QList<QByteArray> OpenSSHKey::privateParts() const
+{
+ return m_rawPrivateData;
+}
+
+const QString& OpenSSHKey::privateType() const
+{
+ return m_rawType;
+}
+
+OpenSSHKey OpenSSHKey::restoreFromBinary(Type type, const QByteArray& serialized)
+{
+ OpenSSHKey key;
+ auto data = binaryDeserialize(serialized);
+ key.setType(data.first);
+ switch (type) {
+ case Public:
+ key.setPublicData(data.second);
+ break;
+ case Private:
+ key.setPrivateData(data.second);
+ break;
+ }
+ return key;
+}
+
+QByteArray OpenSSHKey::serializeToBinary(Type type, const OpenSSHKey& key)
+{
+ Q_ASSERT(!key.encrypted());
+ switch (type) {
+ case Public:
+ return binarySerialize(key.type(), key.publicParts());
+ case Private:
+ return binarySerialize(key.type(), key.privateParts());
+ }
+ return {};
+}
+
uint qHash(const OpenSSHKey& key)
{
return qHash(key.fingerprint());
diff --git a/src/sshagent/OpenSSHKey.h b/src/crypto/ssh/OpenSSHKey.h
index 406f390ea..85c288b9f 100644
--- a/src/sshagent/OpenSSHKey.h
+++ b/src/crypto/ssh/OpenSSHKey.h
@@ -16,23 +16,26 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef OPENSSHKEY_H
-#define OPENSSHKEY_H
+#ifndef KEEPASSXC_OPENSSHKEY_H
+#define KEEPASSXC_OPENSSHKEY_H
-#include "BinaryStream.h"
#include <QtCore>
-class OpenSSHKey : QObject
+class BinaryStream;
+
+class OpenSSHKey : public QObject
{
Q_OBJECT
public:
+ static OpenSSHKey generate(bool secure = true);
+
explicit OpenSSHKey(QObject* parent = nullptr);
OpenSSHKey(const OpenSSHKey& other);
bool operator==(const OpenSSHKey& other) const;
- bool parse(const QByteArray& in);
+ bool parsePKCS1PEM(const QByteArray& in);
bool encrypted() const;
- bool openPrivateKey(const QString& passphrase = QString());
+ bool openKey(const QString& passphrase = QString());
const QString cipherName() const;
const QString type() const;
@@ -40,6 +43,7 @@ public:
const QString fingerprint(QCryptographicHash::Algorithm algo = QCryptographicHash::Sha256) const;
const QString comment() const;
const QString publicKey() const;
+ const QString privateKey() const;
const QString errorString() const;
void setType(const QString& type);
@@ -54,26 +58,41 @@ public:
bool writePublic(BinaryStream& stream);
bool writePrivate(BinaryStream& stream);
-private:
- static const QString TYPE_DSA;
- static const QString TYPE_RSA;
- static const QString TYPE_OPENSSH;
+ QList<QByteArray> publicParts() const;
+ QList<QByteArray> privateParts() const;
+ const QString& privateType() const;
+
+ static const QString TYPE_DSA_PRIVATE;
+ static const QString TYPE_RSA_PRIVATE;
+ static const QString TYPE_RSA_PUBLIC;
+ static const QString TYPE_OPENSSH_PRIVATE;
- bool parsePEM(const QByteArray& in, QByteArray& out);
+ enum Type
+ {
+ Public,
+ Private
+ };
+
+ static OpenSSHKey restoreFromBinary(Type eType, const QByteArray& serialized);
+ static QByteArray serializeToBinary(Type eType, const OpenSSHKey& key);
+
+private:
+ bool extractPEM(const QByteArray& in, QByteArray& out);
QString m_type;
QString m_cipherName;
QByteArray m_cipherIV;
QString m_kdfName;
QByteArray m_kdfOptions;
- QByteArray m_rawPrivateData;
- QList<QByteArray> m_publicData;
- QList<QByteArray> m_privateData;
- QString m_privateType;
+
+ QString m_rawType;
+ QByteArray m_rawData;
+ QList<QByteArray> m_rawPublicData;
+ QList<QByteArray> m_rawPrivateData;
QString m_comment;
QString m_error;
};
uint qHash(const OpenSSHKey& key);
-#endif // OPENSSHKEY_H
+#endif // KEEPASSXC_OPENSSHKEY_H
diff --git a/src/sshagent/bcrypt_pbkdf.cpp b/src/crypto/ssh/bcrypt_pbkdf.cpp
index fed4cdb29..fed4cdb29 100644
--- a/src/sshagent/bcrypt_pbkdf.cpp
+++ b/src/crypto/ssh/bcrypt_pbkdf.cpp
diff --git a/src/sshagent/blf.h b/src/crypto/ssh/blf.h
index f1ac5a5c2..f1ac5a5c2 100644
--- a/src/sshagent/blf.h
+++ b/src/crypto/ssh/blf.h
diff --git a/src/sshagent/blowfish.c b/src/crypto/ssh/blowfish.c
index e10f7e7d9..e10f7e7d9 100644
--- a/src/sshagent/blowfish.c
+++ b/src/crypto/ssh/blowfish.c
diff --git a/src/sshagent/includes.h b/src/crypto/ssh/includes.h
index 23b4aeeb6..23b4aeeb6 100644
--- a/src/sshagent/includes.h
+++ b/src/crypto/ssh/includes.h
diff --git a/src/format/Kdbx4Reader.cpp b/src/format/Kdbx4Reader.cpp
index 7b94d34f8..30f642464 100644
--- a/src/format/Kdbx4Reader.cpp
+++ b/src/format/Kdbx4Reader.cpp
@@ -69,8 +69,7 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
}
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, m_db->transformedMasterKey());
- if (headerHmac
- != CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
+ if (headerHmac != CryptoHash::hmac(headerData, HmacBlockStream::getHmacKey(UINT64_MAX, hmacKey), CryptoHash::Sha256)) {
raiseError(tr("Wrong key or database file is corrupt. (HMAC mismatch)"));
return nullptr;
}
@@ -85,8 +84,7 @@ Database* Kdbx4Reader::readDatabaseImpl(QIODevice* device,
raiseError(tr("Unknown cipher"));
return nullptr;
}
- SymmetricCipherStream cipherStream(
- &hmacStream, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
+ SymmetricCipherStream cipherStream(&hmacStream, cipher, SymmetricCipher::algorithmMode(cipher), SymmetricCipher::Decrypt);
if (!cipherStream.init(finalKey, m_encryptionIV)) {
raiseError(cipherStream.errorString());
return nullptr;
diff --git a/src/format/KdbxXmlWriter.cpp b/src/format/KdbxXmlWriter.cpp
index 5ad1e34ae..cff2283e1 100644
--- a/src/format/KdbxXmlWriter.cpp
+++ b/src/format/KdbxXmlWriter.cpp
@@ -34,7 +34,7 @@ KdbxXmlWriter::KdbxXmlWriter(quint32 version)
}
void KdbxXmlWriter::writeDatabase(QIODevice* device,
- Database* db,
+ const Database* db,
KeePass2RandomStream* randomStream,
const QByteArray& headerHash)
{
diff --git a/src/format/KdbxXmlWriter.h b/src/format/KdbxXmlWriter.h
index 51a803497..1e00732fe 100644
--- a/src/format/KdbxXmlWriter.h
+++ b/src/format/KdbxXmlWriter.h
@@ -37,7 +37,7 @@ public:
explicit KdbxXmlWriter(quint32 version);
void writeDatabase(QIODevice* device,
- Database* db,
+ const Database *db,
KeePass2RandomStream* randomStream = nullptr,
const QByteArray& headerHash = QByteArray());
void writeDatabase(const QString& filename, Database* db);
@@ -82,8 +82,8 @@ private:
const quint32 m_kdbxVersion;
QXmlStreamWriter m_xml;
- QPointer<Database> m_db;
- QPointer<Metadata> m_meta;
+ QPointer<const Database> m_db;
+ QPointer<const Metadata> m_meta;
KeePass2RandomStream* m_randomStream = nullptr;
QHash<QByteArray, int> m_idMap;
QByteArray m_headerHash;
diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp
index c7c75a11e..b987815d8 100644
--- a/src/gui/AboutDialog.cpp
+++ b/src/gui/AboutDialog.cpp
@@ -89,6 +89,9 @@ AboutDialog::AboutDialog(QWidget* parent)
#ifdef WITH_XC_SSHAGENT
extensions += "\n- " + tr("SSH Agent");
#endif
+#ifdef WITH_XC_KEESHARE
+ extensions += "\n- " + tr("KeeShare");
+#endif
#ifdef WITH_XC_YUBIKEY
extensions += "\n- " + tr("YubiKey");
#endif
diff --git a/src/gui/ApplicationSettingsWidget.cpp b/src/gui/ApplicationSettingsWidget.cpp
index 285462042..ce854f244 100644
--- a/src/gui/ApplicationSettingsWidget.cpp
+++ b/src/gui/ApplicationSettingsWidget.cpp
@@ -20,8 +20,9 @@
#include "ui_ApplicationSettingsWidgetGeneral.h"
#include "ui_ApplicationSettingsWidgetSecurity.h"
-#include "autotype/AutoType.h"
#include "config-keepassx.h"
+
+#include "autotype/AutoType.h"
#include "core/Config.h"
#include "core/FilePath.h"
#include "core/Global.h"
@@ -77,12 +78,10 @@ ApplicationSettingsWidget::ApplicationSettingsWidget(QWidget* parent)
connect(this, SIGNAL(apply()), SLOT(saveSettings()));
connect(this, SIGNAL(rejected()), SLOT(reject()));
- connect(
- m_generalUi->autoSaveAfterEveryChangeCheckBox, SIGNAL(toggled(bool)), this, SLOT(enableAutoSaveOnExit(bool)));
+ connect(m_generalUi->autoSaveAfterEveryChangeCheckBox, SIGNAL(toggled(bool)), this, SLOT(enableAutoSaveOnExit(bool)));
connect(m_generalUi->systrayShowCheckBox, SIGNAL(toggled(bool)), this, SLOT(enableSystray(bool)));
- connect(
- m_secUi->clearClipboardCheckBox, SIGNAL(toggled(bool)), m_secUi->clearClipboardSpinBox, SLOT(setEnabled(bool)));
+ connect(m_secUi->clearClipboardCheckBox, SIGNAL(toggled(bool)), m_secUi->clearClipboardSpinBox, SLOT(setEnabled(bool)));
connect(m_secUi->lockDatabaseIdleCheckBox,
SIGNAL(toggled(bool)),
m_secUi->lockDatabaseIdleSpinBox,
@@ -120,7 +119,6 @@ void ApplicationSettingsWidget::addSettingsPage(ISettingsPage* page)
void ApplicationSettingsWidget::loadSettings()
{
-
if (config()->hasAccessError()) {
showMessage(tr("Access error for config file %1").arg(config()->getFileName()), MessageWidget::Error);
}
diff --git a/src/gui/DatabaseTabWidget.cpp b/src/gui/DatabaseTabWidget.cpp
index 71bd74814..e5c906fbb 100644
--- a/src/gui/DatabaseTabWidget.cpp
+++ b/src/gui/DatabaseTabWidget.cpp
@@ -40,6 +40,9 @@
#include "gui/entry/EntryView.h"
#include "gui/group/GroupView.h"
#include "gui/wizard/NewDatabaseWizard.h"
+#include "keeshare/KeeShare.h"
+
+#include "config-keepassx.h"
DatabaseManagerStruct::DatabaseManagerStruct()
: dbWidget(nullptr)
@@ -375,6 +378,11 @@ bool DatabaseTabWidget::saveDatabase(Database* db, QString filePath)
dbStruct.saveAttempts = 0;
dbStruct.fileInfo = QFileInfo(filePath);
dbStruct.dbWidget->databaseSaved();
+#ifdef WITH_XC_KEESHARE
+ // TODO HNH: This is hacky - we need to remove the logic from the ui at this point to allow a proper
+ // architecture
+ KeeShare::instance()->handleDatabaseSaved(db);
+#endif
updateTabName(db);
emit messageDismissTab();
return true;
@@ -429,7 +437,14 @@ bool DatabaseTabWidget::saveDatabaseAs(Database* db)
// Failed to save, try again
continue;
}
-
+#ifdef WITH_XC_KEESHARE
+ // Since we change to the saved database we should also export
+ // TODO HNH: This is hacky - we need to remove the logic from the ui at this point to allow a proper
+ // architecture
+ KeeShare::instance()->handleDatabaseSaved(db);
+#endif
+ // changes of the current database
+ // SaveAs for non-existing datbase doesn't matter since one has to set the path while creation
dbStruct.dbWidget->updateFilePath(dbStruct.fileInfo.absoluteFilePath());
updateLastDatabases(dbStruct.fileInfo.absoluteFilePath());
return true;
@@ -628,9 +643,9 @@ void DatabaseTabWidget::updateTabNameFromDbWidgetSender()
}
}
-int DatabaseTabWidget::databaseIndex(Database* db)
+int DatabaseTabWidget::databaseIndex(const Database* db)
{
- QWidget* dbWidget = m_dbList.value(db).dbWidget;
+ QWidget* dbWidget = m_dbList.value(const_cast<Database*>(db)).dbWidget;
return indexOf(dbWidget);
}
@@ -865,6 +880,35 @@ void DatabaseTabWidget::connectDatabase(Database* newDb, Database* oldDb)
connect(newDb, SIGNAL(nameTextChanged()), SLOT(updateTabNameFromDbSender()));
connect(newDb, SIGNAL(modified()), SLOT(modified()));
newDb->setEmitModified(true);
+
+#ifdef WITH_XC_KEESHARE
+ KeeShare::instance()->connectDatabase(newDb, oldDb);
+ connect(KeeShare::instance(),
+ SIGNAL(sharingMessage(Database*, QString, MessageWidget::MessageType)),
+ this,
+ SLOT(handleDatabaseMessage(Database*, QString, MessageWidget::MessageType)),
+ Qt::UniqueConnection);
+ KeeShare::instance()->handleDatabaseOpened(newDb);
+#endif
+}
+
+void DatabaseTabWidget::handleDatabaseMessage(Database* db, QString message, MessageWidget::MessageType type)
+{
+ auto* databaseWidget = currentDatabaseWidget();
+ if (!databaseWidget) {
+ return;
+ }
+ auto* currentDb = currentDatabaseWidget()->database();
+ if (!currentDb) {
+ return;
+ }
+ if (currentDb != db) {
+ auto index = databaseIndex(db);
+ emit messageGlobal(tr("Update in background database %1:\n%2").arg(tabText(index)).arg(message), type);
+
+ } else {
+ emit messageTab(message, type);
+ }
}
void DatabaseTabWidget::performGlobalAutoType()
diff --git a/src/gui/DatabaseTabWidget.h b/src/gui/DatabaseTabWidget.h
index dcb4a62da..d761f6efb 100644
--- a/src/gui/DatabaseTabWidget.h
+++ b/src/gui/DatabaseTabWidget.h
@@ -101,6 +101,7 @@ private slots:
void changeDatabase(Database* newDb, bool unsavedChanges);
void emitActivateDatabaseChanged();
void emitDatabaseUnlockedFromDbWidgetSender();
+ void handleDatabaseMessage(Database* db, QString message, MessageWidget::MessageType type);
private:
Database* execNewDatabaseWizard();
@@ -108,7 +109,7 @@ private:
bool saveDatabaseAs(Database* db);
bool closeDatabase(Database* db);
void deleteDatabase(Database* db);
- int databaseIndex(Database* db);
+ int databaseIndex(const Database* db);
Database* indexDatabase(int index);
DatabaseManagerStruct indexDatabaseManagerStruct(int index);
Database* databaseFromDatabaseWidget(DatabaseWidget* dbWidget);
diff --git a/src/gui/DatabaseWidget.cpp b/src/gui/DatabaseWidget.cpp
index aae6527a1..e840f075e 100644
--- a/src/gui/DatabaseWidget.cpp
+++ b/src/gui/DatabaseWidget.cpp
@@ -35,6 +35,7 @@
#include "core/Config.h"
#include "core/EntrySearcher.h"
#include "core/FilePath.h"
+#include "core/FileWatcher.h"
#include "core/Group.h"
#include "core/Merger.h"
#include "core/Metadata.h"
@@ -73,6 +74,8 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
, m_newGroup(nullptr)
, m_newEntry(nullptr)
, m_newParent(nullptr)
+ , m_importingCsv(false)
+ , m_fileWatcher(new DelayingFileWatcher(this))
{
m_mainWidget = new QWidget(this);
@@ -198,9 +201,7 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(bool)));
connect(m_unlockDatabaseWidget, SIGNAL(editFinished(bool)), SLOT(unlockDatabase(bool)));
connect(m_unlockDatabaseDialog, SIGNAL(unlockDone(bool)), SLOT(unlockDatabase(bool)));
- connect(&m_fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(onWatchedFileChanged()));
- connect(&m_fileWatchTimer, SIGNAL(timeout()), this, SLOT(reloadDatabaseFile()));
- connect(&m_fileWatchUnblockTimer, SIGNAL(timeout()), this, SLOT(unblockAutoReload()));
+ connect(m_fileWatcher.data(), SIGNAL(fileChanged()), this, SLOT(reloadDatabaseFile()));
connect(this, SIGNAL(currentChanged(int)), this, SLOT(emitCurrentModeChanged()));
connect(m_groupView, SIGNAL(groupPressed(Group*)), SLOT(emitPressedGroup(Group*)));
@@ -211,10 +212,6 @@ DatabaseWidget::DatabaseWidget(Database* db, QWidget* parent)
m_databaseModified = false;
- m_fileWatchTimer.setSingleShot(true);
- m_fileWatchUnblockTimer.setSingleShot(true);
- m_ignoreAutoReload = false;
-
m_searchCaseSensitive = false;
m_searchLimitGroup = config()->get("SearchLimitGroup", false).toBool();
@@ -817,9 +814,9 @@ void DatabaseWidget::openDatabase(bool accepted)
m_databaseOpenWidget = nullptr;
delete m_keepass1OpenWidget;
m_keepass1OpenWidget = nullptr;
- m_fileWatcher.addPath(m_filePath);
+ m_fileWatcher->restart();
} else {
- m_fileWatcher.removePath(m_filePath);
+ m_fileWatcher->stop();
if (m_databaseOpenWidget->database()) {
delete m_databaseOpenWidget->database();
}
@@ -1175,26 +1172,7 @@ void DatabaseWidget::lock()
void DatabaseWidget::updateFilePath(const QString& filePath)
{
- if (!m_filePath.isEmpty()) {
- m_fileWatcher.removePath(m_filePath);
- }
-
-#if defined(Q_OS_LINUX)
- struct statfs statfsBuf;
- bool forcePolling = false;
- const auto NFS_SUPER_MAGIC = 0x6969;
-
- if (!statfs(filePath.toLocal8Bit().constData(), &statfsBuf)) {
- forcePolling = (statfsBuf.f_type == NFS_SUPER_MAGIC);
- } else {
- // if we can't get the fs type let's fall back to polling
- forcePolling = true;
- }
- auto objectName = forcePolling ? QLatin1String("_qt_autotest_force_engine_poller") : QLatin1String("");
- m_fileWatcher.setObjectName(objectName);
-#endif
-
- m_fileWatcher.addPath(filePath);
+ m_fileWatcher->start(filePath);
m_filePath = filePath;
m_db->setFilePath(filePath);
}
@@ -1202,28 +1180,10 @@ void DatabaseWidget::updateFilePath(const QString& filePath)
void DatabaseWidget::blockAutoReload(bool block)
{
if (block) {
- m_ignoreAutoReload = true;
- m_fileWatchTimer.stop();
+ m_fileWatcher->ignoreFileChanges();
} else {
- m_fileWatchUnblockTimer.start(500);
- }
-}
-
-void DatabaseWidget::unblockAutoReload()
-{
- m_ignoreAutoReload = false;
- updateFilePath(m_filePath);
-}
-
-void DatabaseWidget::onWatchedFileChanged()
-{
- if (m_ignoreAutoReload) {
- return;
+ m_fileWatcher->observeFileChanges(true);
}
- if (m_fileWatchTimer.isActive())
- return;
-
- m_fileWatchTimer.start(500);
}
void DatabaseWidget::reloadDatabaseFile()
@@ -1249,7 +1209,7 @@ void DatabaseWidget::reloadDatabaseFile()
m_db->markAsModified();
m_databaseModified = true;
// Rewatch the database file
- m_fileWatcher.addPath(m_filePath);
+ m_fileWatcher->restart();
return;
}
}
@@ -1307,7 +1267,7 @@ void DatabaseWidget::reloadDatabaseFile()
}
// Rewatch the database file
- m_fileWatcher.addPath(m_filePath);
+ m_fileWatcher->restart();
}
int DatabaseWidget::numberOfSelectedEntries() const
diff --git a/src/gui/DatabaseWidget.h b/src/gui/DatabaseWidget.h
index 896703eb6..b8e20f019 100644
--- a/src/gui/DatabaseWidget.h
+++ b/src/gui/DatabaseWidget.h
@@ -37,6 +37,7 @@ class EditEntryWidget;
class EditGroupWidget;
class Entry;
class EntryView;
+class DelayingFileWatcher;
class Group;
class GroupView;
class KeePass1OpenWidget;
@@ -48,7 +49,6 @@ class UnlockDatabaseWidget;
class MessageWidget;
class DetailsWidget;
class UnlockDatabaseDialog;
-class QFileSystemWatcher;
namespace Ui
{
@@ -200,10 +200,8 @@ private slots:
void unlockDatabase(bool accepted);
void emitCurrentModeChanged();
// Database autoreload slots
- void onWatchedFileChanged();
void reloadDatabaseFile();
void restoreGroupEntryFocus(const QUuid& groupUuid, const QUuid& EntryUuid);
- void unblockAutoReload();
private:
void setClipboardTextAndMinimize(const QString& text);
@@ -227,11 +225,12 @@ private:
QSplitter* m_detailSplitter;
GroupView* m_groupView;
EntryView* m_entryView;
+ QString m_filePath;
QLabel* m_searchingLabel;
Group* m_newGroup;
Entry* m_newEntry;
Group* m_newParent;
- QString m_filePath;
+
QUuid m_groupBeforeLock;
QUuid m_entryBeforeLock;
MessageWidget* m_messageWidget;
@@ -246,10 +245,7 @@ private:
bool m_importingCsv;
// Autoreload
- QFileSystemWatcher m_fileWatcher;
- QTimer m_fileWatchTimer;
- QTimer m_fileWatchUnblockTimer;
- bool m_ignoreAutoReload;
+ QPointer<DelayingFileWatcher> m_fileWatcher;
bool m_databaseModified;
};
diff --git a/src/gui/DetailsWidget.cpp b/src/gui/DetailsWidget.cpp
index ff8861172..4e8da2623 100644
--- a/src/gui/DetailsWidget.cpp
+++ b/src/gui/DetailsWidget.cpp
@@ -27,6 +27,9 @@
#include "core/FilePath.h"
#include "entry/EntryAttachmentsModel.h"
#include "gui/Clipboard.h"
+#ifdef WITH_XC_KEESHARE
+#include "keeshare/KeeShare.h"
+#endif
namespace
{
@@ -102,7 +105,9 @@ void DetailsWidget::setGroup(Group* selectedGroup)
updateGroupHeaderLine();
updateGroupGeneralTab();
updateGroupNotesTab();
-
+#ifdef WITH_XC_KEESHARE
+ updateGroupSharingTab();
+#endif
setVisible(!config()->get("GUI/HideDetailsView").toBool());
m_ui->stackedWidget->setCurrentWidget(m_ui->pageGroup);
@@ -267,6 +272,17 @@ void DetailsWidget::updateGroupNotesTab()
m_ui->groupNotesEdit->setText(notes);
}
+#ifdef WITH_XC_KEESHARE
+void DetailsWidget::updateGroupSharingTab()
+{
+ Q_ASSERT(m_currentGroup);
+ setTabEnabled(m_ui->groupTabWidget, m_ui->groupShareTab, KeeShare::isShared(m_currentGroup));
+ auto reference = KeeShare::referenceOf(m_currentGroup);
+ m_ui->groupShareTypeLabel->setText(KeeShare::referenceTypeLabel(reference));
+ m_ui->groupSharePathLabel->setText(reference.path);
+}
+#endif
+
void DetailsWidget::updateTotpLabel()
{
if (!m_locked && m_currentEntry && m_currentEntry->hasTotp()) {
diff --git a/src/gui/DetailsWidget.h b/src/gui/DetailsWidget.h
index ba42e5278..513bb9556 100644
--- a/src/gui/DetailsWidget.h
+++ b/src/gui/DetailsWidget.h
@@ -18,6 +18,7 @@
#ifndef KEEPASSX_DETAILSWIDGET_H
#define KEEPASSX_DETAILSWIDGET_H
+#include "config-keepassx.h"
#include "gui/DatabaseWidget.h"
#include <QWidget>
@@ -55,6 +56,9 @@ private slots:
void updateGroupHeaderLine();
void updateGroupGeneralTab();
void updateGroupNotesTab();
+#ifdef WITH_XC_KEESHARE
+ void updateGroupSharingTab();
+#endif
void updateTotpLabel();
void updateTabIndexes();
diff --git a/src/gui/DetailsWidget.ui b/src/gui/DetailsWidget.ui
index 38906150e..27c3c1d2c 100644
--- a/src/gui/DetailsWidget.ui
+++ b/src/gui/DetailsWidget.ui
@@ -2,6 +2,14 @@
<ui version="4.0">
<class>DetailsWidget</class>
<widget class="QWidget" name="DetailsWidget">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>652</width>
+ <height>274</height>
+ </rect>
+ </property>
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>0</number>
@@ -38,7 +46,7 @@
<number>0</number>
</property>
<item>
- <layout class="QHBoxLayout" name="entryHorizontalLayout" stretch="0,0,0,0,0">
+ <layout class="QHBoxLayout" name="entryHorizontalLayout" stretch="0,0,0,0,0,0">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
@@ -77,6 +85,19 @@
</widget>
</item>
<item>
+ <spacer name="entryHorizontalSpacer">
+ <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="QWidget" name="entryTotpWidget" native="true">
<layout class="QGridLayout" name="gridLayout_3">
<property name="leftMargin">
@@ -167,47 +188,6 @@
<property name="bottomMargin">
<number>0</number>
</property>
- <item row="1" column="2">
- <widget class="ElidedLabel" name="entryPasswordLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- </widget>
- </item>
- <item row="2" column="2">
- <widget class="ElidedLabel" name="entryUrlLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="minimumSize">
- <size>
- <width>100</width>
- <height>0</height>
- </size>
- </property>
- <property name="cursor">
- <cursorShape>PointingHandCursor</cursorShape>
- </property>
- <property name="text">
- <string/>
- </property>
- <property name="textInteractionFlags">
- <set>Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse</set>
- </property>
- </widget>
- </item>
<item row="0" column="2">
<widget class="QLabel" name="entryUsernameLabel">
<property name="sizePolicy">
@@ -252,35 +232,13 @@
</property>
</widget>
</item>
- <item row="3" column="1">
- <widget class="QLabel" name="entryExpirationTitleLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="font">
- <font>
- <weight>75</weight>
- <bold>true</bold>
- </font>
- </property>
- <property name="text">
- <string>Expiration</string>
- </property>
- <property name="alignment">
- <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
- </property>
- </widget>
- </item>
- <item row="3" column="2">
- <widget class="QLabel" name="entryExpirationLabel">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
+ <item row="1" column="2">
+ <widget class="ElidedLabel" name="entryPasswordLabel">
+ <property name="minimumSize">
+ <size>
+ <width>100</width>
+ <height>0</height>
+ </size>
</property>
</widget>
</item>
@@ -306,24 +264,24 @@
</property>
</widget>
</item>
- <item row="0" column="0">
- <spacer name="entryLeftHorizontalSpacer">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeType">
- <enum>QSizePolicy::Fixed</enum>
- </property>
- <property name="sizeHint" stdset="0">
+ <item row="2" column="2">
+ <widget class="ElidedLabel" name="entryUrlLabel">
+ <property name="minimumSize">
<size>
- <width>20</width>
- <height>20</height>
+ <width>100</width>
+ <height>0</height>
</size>
</property>
- </spacer>
+ <property name="cursor">
+ <cursorShape>PointingHandCursor</cursorShape>
+ </property>
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
</item>
- <item row="1" column="1">
- <widget class="QLabel" name="entryPasswordTitleLabel">
+ <item row="3" column="1">
+ <widget class="QLabel" name="entryExpirationTitleLabel">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
@@ -337,22 +295,32 @@
</font>
</property>
<property name="text">
- <string>Password</string>
+ <string>Expiration</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
+ <item row="3" column="2">
+ <widget class="QLabel" name="entryExpirationLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
<item row="4" column="2">
- <spacer name="verticalSpacer">
+ <spacer name="entryBottomVerticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
- <width>0</width>
- <height>0</height>
+ <width>20</width>
+ <height>10</height>
</size>
</property>
</spacer>
@@ -473,7 +441,7 @@
<number>0</number>
</property>
<item>
- <layout class="QHBoxLayout" name="groupHorizontalLayout" stretch="0,0,0">
+ <layout class="QHBoxLayout" name="groupHorizontalLayout" stretch="0,0,0,0">
<property name="sizeConstraint">
<enum>QLayout::SetDefaultConstraint</enum>
</property>
@@ -512,6 +480,19 @@
</widget>
</item>
<item>
+ <spacer name="groupHorizontalSpacer">
+ <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="QToolButton" name="groupCloseButton">
<property name="toolTip">
<string>Close</string>
@@ -673,10 +654,16 @@
</widget>
</item>
<item row="3" column="2">
- <spacer name="verticalSpacer_2">
+ <spacer name="groupBottomVerticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>10</height>
+ </size>
+ </property>
</spacer>
</item>
</layout>
@@ -701,6 +688,89 @@
</item>
</layout>
</widget>
+ <widget class="QWidget" name="groupShareTab">
+ <attribute name="title">
+ <string>Share</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <item>
+ <widget class="QWidget" name="groupShareWidget" native="true">
+ <layout class="QGridLayout" name="gridLayout_4">
+ <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 row="1" column="2">
+ <widget class="QLabel" name="groupSharePathLabel">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Expanding" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ <property name="text">
+ <string notr="true">&lt;path&gt;</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="groupShareTypeLabel">
+ <property name="font">
+ <font>
+ <weight>75</weight>
+ <bold>true</bold>
+ </font>
+ </property>
+ <property name="text">
+ <string notr="true">&lt;type&gt;</string>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="2">
+ <spacer name="verticalSpacer">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>147</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item row="1" column="0">
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeType">
+ <enum>QSizePolicy::Fixed</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>20</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</widget>
</item>
</layout>
diff --git a/src/gui/EditWidgetProperties.cpp b/src/gui/EditWidgetProperties.cpp
index 93e3b0ae8..422e32bb7 100644
--- a/src/gui/EditWidgetProperties.cpp
+++ b/src/gui/EditWidgetProperties.cpp
@@ -22,10 +22,12 @@
#include "MessageBox.h"
#include "ui_EditWidgetProperties.h"
+#include "core/CustomData.h"
+#include "core/TimeInfo.h"
+
EditWidgetProperties::EditWidgetProperties(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::EditWidgetProperties())
- , m_customData(new CustomData(this))
, m_customDataModel(new QStandardItemModel(this))
{
m_ui->setupUi(this);
@@ -51,17 +53,19 @@ void EditWidgetProperties::setFields(const TimeInfo& timeInfo, const QUuid& uuid
m_ui->uuidEdit->setText(uuid.toRfc4122().toHex());
}
-void EditWidgetProperties::setCustomData(const CustomData* customData)
+void EditWidgetProperties::setCustomData(CustomData* customData)
{
- Q_ASSERT(customData);
- m_customData->copyDataFrom(customData);
+ if (m_customData) {
+ m_customData->disconnect(this);
+ }
- updateModel();
-}
+ m_customData = customData;
-const CustomData* EditWidgetProperties::customData() const
-{
- return m_customData;
+ if (m_customData) {
+ connect(m_customData, SIGNAL(modified()), SLOT(update()));
+ }
+
+ update();
}
void EditWidgetProperties::removeSelectedPluginData()
@@ -81,7 +85,7 @@ void EditWidgetProperties::removeSelectedPluginData()
const QString key = index.data().toString();
m_customData->remove(key);
}
- updateModel();
+ update();
}
}
@@ -90,16 +94,17 @@ void EditWidgetProperties::toggleRemoveButton(const QItemSelection& selected)
m_ui->removeCustomDataButton->setEnabled(!selected.isEmpty());
}
-void EditWidgetProperties::updateModel()
+void EditWidgetProperties::update()
{
m_customDataModel->clear();
-
m_customDataModel->setHorizontalHeaderLabels({tr("Key"), tr("Value")});
-
- for (const QString& key : m_customData->keys()) {
- m_customDataModel->appendRow(QList<QStandardItem*>() << new QStandardItem(key)
- << new QStandardItem(m_customData->value(key)));
+ if (!m_customData) {
+ m_ui->removeCustomDataButton->setEnabled(false);
+ } else {
+ for (const QString& key : m_customData->keys()) {
+ m_customDataModel->appendRow(QList<QStandardItem*>() << new QStandardItem(key)
+ << new QStandardItem(m_customData->value(key)));
+ }
+ m_ui->removeCustomDataButton->setEnabled(!m_customData->isEmpty());
}
-
- m_ui->removeCustomDataButton->setEnabled(false);
}
diff --git a/src/gui/EditWidgetProperties.h b/src/gui/EditWidgetProperties.h
index 6fad1f866..30a983e98 100644
--- a/src/gui/EditWidgetProperties.h
+++ b/src/gui/EditWidgetProperties.h
@@ -23,8 +23,9 @@
#include <QStandardItemModel>
#include <QWidget>
-#include "core/CustomData.h"
-#include "core/TimeInfo.h"
+class CustomData;
+class TimeInfo;
+class QUuid;
namespace Ui
{
@@ -40,21 +41,19 @@ public:
~EditWidgetProperties();
void setFields(const TimeInfo& timeInfo, const QUuid& uuid);
- void setCustomData(const CustomData* customData);
-
- const CustomData* customData() const;
+ void setCustomData(CustomData* customData);
private slots:
+ void update();
void removeSelectedPluginData();
void toggleRemoveButton(const QItemSelection& selected);
private:
- void updateModel();
-
const QScopedPointer<Ui::EditWidgetProperties> m_ui;
QPointer<CustomData> m_customData;
QPointer<QStandardItemModel> m_customDataModel;
+
Q_DISABLE_COPY(EditWidgetProperties)
};
diff --git a/src/gui/FileDialog.cpp b/src/gui/FileDialog.cpp
index d58f52928..f404d8fc8 100644
--- a/src/gui/FileDialog.cpp
+++ b/src/gui/FileDialog.cpp
@@ -79,13 +79,70 @@ QStringList FileDialog::getOpenFileNames(QWidget* parent,
}
}
+QString FileDialog::getFileName(QWidget* parent,
+ const QString& caption,
+ QString dir,
+ const QString& filter,
+ QString* selectedFilter,
+ QFileDialog::Options options,
+ const QString& defaultExtension,
+ const QString& defaultName)
+{
+ if (!m_nextFileName.isEmpty()) {
+ QString result = m_nextFileName;
+ m_nextFileName.clear();
+ return result;
+ } else {
+ if (dir.isEmpty()) {
+ dir = config()->get("LastDir").toString();
+ }
+
+ QString result;
+#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
+ Q_UNUSED(defaultName);
+ Q_UNUSED(defaultExtension);
+ // the native dialogs on these platforms already append the file extension
+ result = QFileDialog::getSaveFileName(parent, caption, dir, filter, selectedFilter, options);
+#else
+ QFileDialog dialog(parent, caption, dir, filter);
+ dialog.setFileMode(QFileDialog::AnyFile);
+ dialog.setAcceptMode(QFileDialog::AcceptSave);
+ if (selectedFilter) {
+ dialog.selectNameFilter(*selectedFilter);
+ }
+ if (!defaultName.isEmpty()) {
+ dialog.selectFile(defaultName);
+ }
+ dialog.setOptions(options);
+ dialog.setDefaultSuffix(defaultExtension);
+ dialog.setLabelText(QFileDialog::Accept, QFileDialog::tr("Select"));
+ QStringList results;
+ if (dialog.exec()) {
+ results = dialog.selectedFiles();
+ if (!results.isEmpty()) {
+ result = results[0];
+ }
+ }
+#endif
+
+ // on Mac OS X the focus is lost after closing the native dialog
+ if (parent) {
+ parent->activateWindow();
+ }
+
+ saveLastDir(result);
+ return result;
+ }
+}
+
QString FileDialog::getSaveFileName(QWidget* parent,
const QString& caption,
QString dir,
const QString& filter,
QString* selectedFilter,
QFileDialog::Options options,
- const QString& defaultExtension)
+ const QString& defaultExtension,
+ const QString& defaultName)
{
if (!m_nextFileName.isEmpty()) {
QString result = m_nextFileName;
@@ -98,6 +155,7 @@ QString FileDialog::getSaveFileName(QWidget* parent,
QString result;
#if defined(Q_OS_MAC) || defined(Q_OS_WIN)
+ Q_UNUSED(defaultName);
Q_UNUSED(defaultExtension);
// the native dialogs on these platforms already append the file extension
result = QFileDialog::getSaveFileName(parent, caption, dir, filter, selectedFilter, options);
@@ -108,6 +166,9 @@ QString FileDialog::getSaveFileName(QWidget* parent,
if (selectedFilter) {
dialog.selectNameFilter(*selectedFilter);
}
+ if (!defaultName.isEmpty()) {
+ dialog.selectFile(defaultName);
+ }
dialog.setOptions(options);
dialog.setDefaultSuffix(defaultExtension);
@@ -130,8 +191,7 @@ QString FileDialog::getSaveFileName(QWidget* parent,
}
}
-QString
-FileDialog::getExistingDirectory(QWidget* parent, const QString& caption, QString dir, QFileDialog::Options options)
+QString FileDialog::getExistingDirectory(QWidget* parent, const QString& caption, QString dir, QFileDialog::Options options)
{
if (!m_nextDirName.isEmpty()) {
QString result = m_nextDirName;
diff --git a/src/gui/FileDialog.h b/src/gui/FileDialog.h
index 4862dcfda..8cc1d138e 100644
--- a/src/gui/FileDialog.h
+++ b/src/gui/FileDialog.h
@@ -35,13 +35,22 @@ public:
const QString& filter = QString(),
QString* selectedFilter = nullptr,
QFileDialog::Options options = 0);
+ QString getFileName(QWidget* parent = nullptr,
+ const QString& caption = QString(),
+ QString dir = QString(),
+ const QString& filter = QString(),
+ QString* selectedFilter = nullptr,
+ QFileDialog::Options options = 0,
+ const QString& defaultExtension = QString(),
+ const QString& defaultName = QString());
QString getSaveFileName(QWidget* parent = nullptr,
const QString& caption = QString(),
QString dir = QString(),
const QString& filter = QString(),
QString* selectedFilter = nullptr,
QFileDialog::Options options = 0,
- const QString& defaultExtension = QString());
+ const QString& defaultExtension = QString(),
+ const QString& defaultName = QString());
QString getExistingDirectory(QWidget* parent = nullptr,
const QString& caption = QString(),
QString dir = QString(),
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 733ac0163..dd9894c37 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -40,7 +40,10 @@
#include "sshagent/AgentSettingsPage.h"
#include "sshagent/SSHAgent.h"
#endif
-
+#ifdef WITH_XC_KEESHARE
+#include "keeshare/KeeShare.h"
+#include "keeshare/SettingsPageKeeShare.h"
+#endif
#ifdef WITH_XC_BROWSER
#include "browser/BrowserOptionDialog.h"
#include "browser/BrowserSettings.h"
@@ -141,22 +144,26 @@ MainWindow::MainWindow()
m_countDefaultAttributes = m_ui->menuEntryCopyAttribute->actions().size();
restoreGeometry(config()->get("GUI/MainWindowGeometry").toByteArray());
+
#ifdef WITH_XC_BROWSER
m_ui->settingsWidget->addSettingsPage(new BrowserPlugin(m_ui->tabWidget));
#endif
+
#ifdef WITH_XC_SSHAGENT
SSHAgent::init(this);
connect(SSHAgent::instance(), SIGNAL(error(QString)), this, SLOT(showErrorMessage(QString)));
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
#endif
+#ifdef WITH_XC_KEESHARE
+ KeeShare::init(this);
+ m_ui->settingsWidget->addSettingsPage(new SettingsPageKeeShare(m_ui->tabWidget));
+#endif
setWindowIcon(filePath()->applicationIcon());
m_ui->globalMessageWidget->setHidden(true);
connect(m_ui->globalMessageWidget, &MessageWidget::linkActivated, &MessageWidget::openHttpUrl);
- connect(
- m_ui->globalMessageWidget, SIGNAL(showAnimationStarted()), m_ui->globalMessageWidgetContainer, SLOT(show()));
- connect(
- m_ui->globalMessageWidget, SIGNAL(hideAnimationFinished()), m_ui->globalMessageWidgetContainer, SLOT(hide()));
+ connect(m_ui->globalMessageWidget, SIGNAL(showAnimationStarted()), m_ui->globalMessageWidgetContainer, SLOT(show()));
+ connect(m_ui->globalMessageWidget, SIGNAL(hideAnimationFinished()), m_ui->globalMessageWidgetContainer, SLOT(hide()));
m_clearHistoryAction = new QAction(tr("Clear history"), m_ui->menuFile);
m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases);
diff --git a/src/gui/MainWindow.ui b/src/gui/MainWindow.ui
index 9f6041312..90908f92c 100644
--- a/src/gui/MainWindow.ui
+++ b/src/gui/MainWindow.ui
@@ -608,9 +608,20 @@
<string>Report a &amp;bug</string>
</property>
</action>
+ <action name="actionShare_entry">
+ <property name="text">
+ <string>Share entry</string>
+ </property>
+ </action>
</widget>
<customwidgets>
<customwidget>
+ <class>PasswordGeneratorWidget</class>
+ <extends>QWidget</extends>
+ <header>gui/PasswordGeneratorWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ <customwidget>
<class>MessageWidget</class>
<extends>QWidget</extends>
<header>gui/MessageWidget.h</header>
@@ -634,12 +645,6 @@
<header>gui/WelcomeWidget.h</header>
<container>1</container>
</customwidget>
- <customwidget>
- <class>PasswordGeneratorWidget</class>
- <extends>QWidget</extends>
- <header>gui/PasswordGeneratorWidget.h</header>
- <container>1</container>
- </customwidget>
</customwidgets>
<resources/>
<connections/>
diff --git a/src/gui/dbsettings/DatabaseSettingsDialog.cpp b/src/gui/dbsettings/DatabaseSettingsDialog.cpp
index b2624a425..c95e14409 100644
--- a/src/gui/dbsettings/DatabaseSettingsDialog.cpp
+++ b/src/gui/dbsettings/DatabaseSettingsDialog.cpp
@@ -21,11 +21,36 @@
#include "DatabaseSettingsWidgetGeneral.h"
#include "DatabaseSettingsWidgetEncryption.h"
#include "DatabaseSettingsWidgetMasterKey.h"
+#ifdef WITH_XC_KEESHARE
+#include "keeshare/DatabaseSettingsPageKeeShare.h"
+#endif
+#include "core/Global.h"
#include "core/Config.h"
#include "core/FilePath.h"
#include "core/Database.h"
+class DatabaseSettingsDialog::ExtraPage
+{
+public:
+ ExtraPage(IDatabaseSettingsPage* page, QWidget* widget)
+ : settingsPage(page)
+ , widget(widget)
+ {
+ }
+ void loadSettings(Database* db) const
+ {
+ settingsPage->loadSettings(widget, db);
+ }
+ void saveSettings() const
+ {
+ settingsPage->saveSettings(widget);
+ }
+private:
+ QSharedPointer<IDatabaseSettingsPage> settingsPage;
+ QWidget* widget;
+};
+
DatabaseSettingsDialog::DatabaseSettingsDialog(QWidget* parent)
: DialogyWidget(parent)
, m_ui(new Ui::DatabaseSettingsDialog())
@@ -47,6 +72,10 @@ DatabaseSettingsDialog::DatabaseSettingsDialog(QWidget* parent)
m_securityTabWidget->addTab(m_masterKeyWidget, tr("Master Key"));
m_securityTabWidget->addTab(m_encryptionWidget, tr("Encryption Settings"));
+#ifdef WITH_XC_KEESHARE
+ addSettingsPage(new DatabaseSettingsPageKeeShare());
+#endif
+
m_ui->stackedWidget->setCurrentIndex(0);
m_securityTabWidget->setCurrentIndex(0);
@@ -67,10 +96,24 @@ void DatabaseSettingsDialog::load(Database* db)
m_generalWidget->load(db);
m_masterKeyWidget->load(db);
m_encryptionWidget->load(db);
+ for (const ExtraPage& page : asConst(m_extraPages)) {
+ page.loadSettings(db);
+ }
m_ui->advancedSettingsToggle->setChecked(config()->get("GUI/AdvancedSettings", false).toBool());
m_db = db;
}
+void DatabaseSettingsDialog::addSettingsPage(IDatabaseSettingsPage* page)
+{
+ const int category = m_ui->categoryList->currentCategory();
+ QWidget* widget = page->createWidget();
+ widget->setParent(this);
+ m_extraPages.append(ExtraPage(page, widget));
+ m_ui->stackedWidget->addWidget(widget);
+ m_ui->categoryList->addCategory(page->name(), page->icon());
+ m_ui->categoryList->setCurrentCategory(category);
+}
+
/**
* Show page and tab with database master key settings.
*/
@@ -94,6 +137,10 @@ void DatabaseSettingsDialog::save()
return;
}
+ for (const ExtraPage& extraPage : asConst(m_extraPages)) {
+ extraPage.saveSettings();
+ }
+
#ifdef WITH_XC_TOUCHID
TouchID::getInstance().reset(m_db ? m_db->filePath() : "");
#endif
diff --git a/src/gui/dbsettings/DatabaseSettingsDialog.h b/src/gui/dbsettings/DatabaseSettingsDialog.h
index 50fec32d6..81c295975 100644
--- a/src/gui/dbsettings/DatabaseSettingsDialog.h
+++ b/src/gui/dbsettings/DatabaseSettingsDialog.h
@@ -34,6 +34,19 @@ namespace Ui
class DatabaseSettingsDialog;
}
+class IDatabaseSettingsPage
+{
+public:
+ virtual ~IDatabaseSettingsPage()
+ {
+ }
+ virtual QString name() = 0;
+ virtual QIcon icon() = 0;
+ virtual QWidget* createWidget() = 0;
+ virtual void loadSettings(QWidget* widget, Database* db) = 0;
+ virtual void saveSettings(QWidget* widget) = 0;
+};
+
class DatabaseSettingsDialog : public DialogyWidget
{
Q_OBJECT
@@ -44,6 +57,7 @@ public:
Q_DISABLE_COPY(DatabaseSettingsDialog);
void load(Database* db);
+ void addSettingsPage(IDatabaseSettingsPage* page);
void showMasterKeySettings();
signals:
@@ -68,6 +82,9 @@ private:
QPointer<QTabWidget> m_securityTabWidget;
QPointer<DatabaseSettingsWidgetMasterKey> m_masterKeyWidget;
QPointer<DatabaseSettingsWidgetEncryption> m_encryptionWidget;
+
+ class ExtraPage;
+ QList<ExtraPage> m_extraPages;
};
#endif // KEEPASSX_DATABASESETTINGSWIDGET_H
diff --git a/src/gui/entry/EditEntryWidget.cpp b/src/gui/entry/EditEntryWidget.cpp
index f15ca5328..23cc9c0bc 100644
--- a/src/gui/entry/EditEntryWidget.cpp
+++ b/src/gui/entry/EditEntryWidget.cpp
@@ -44,8 +44,8 @@
#include "core/TimeDelta.h"
#include "core/Tools.h"
#ifdef WITH_XC_SSHAGENT
+#include "crypto/ssh/OpenSSHKey.h"
#include "sshagent/KeeAgentSettings.h"
-#include "sshagent/OpenSSHKey.h"
#include "sshagent/SSHAgent.h"
#endif
#include "gui/Clipboard.h"
@@ -67,11 +67,14 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
, m_autoTypeUi(new Ui::EditEntryWidgetAutoType())
, m_sshAgentUi(new Ui::EditEntryWidgetSSHAgent())
, m_historyUi(new Ui::EditEntryWidgetHistory())
+ , m_customData(new CustomData())
, m_mainWidget(new QWidget())
, m_advancedWidget(new QWidget())
, m_iconsWidget(new EditWidgetIcons())
, m_autoTypeWidget(new QWidget())
+#ifdef WITH_XC_SSHAGENT
, m_sshAgentWidget(new QWidget())
+#endif
, m_editWidgetProperties(new EditWidgetProperties())
, m_historyWidget(new QWidget())
, m_entryAttributes(new EntryAttributes(this))
@@ -87,6 +90,7 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
setupAdvanced();
setupIcon();
setupAutoType();
+
#ifdef WITH_XC_SSHAGENT
if (config()->get("SSHAgent", false).toBool()) {
setupSSHAgent();
@@ -95,6 +99,7 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
m_sshAgentEnabled = false;
}
#endif
+
setupProperties();
setupHistory();
setupEntryUpdate();
@@ -108,6 +113,8 @@ EditEntryWidget::EditEntryWidget(QWidget* parent)
connect(m_iconsWidget, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
m_mainUi->passwordGenerator->layout()->setContentsMargins(0, 0, 0, 0);
+
+ m_editWidgetProperties->setCustomData(m_customData.data());
}
EditEntryWidget::~EditEntryWidget()
@@ -522,13 +529,13 @@ bool EditEntryWidget::getOpenSSHKey(OpenSSHKey& key, bool decrypt)
return false;
}
- if (!key.parse(privateKeyData)) {
+ if (!key.parsePKCS1PEM(privateKeyData)) {
showMessage(key.errorString(), MessageWidget::Error);
return false;
}
if (key.encrypted() && (decrypt || key.publicKey().isEmpty())) {
- if (!key.openPrivateKey(m_entry->password())) {
+ if (!key.openKey(m_entry->password())) {
showMessage(key.errorString(), MessageWidget::Error);
return false;
}
@@ -669,6 +676,8 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const Q
void EditEntryWidget::setForms(const Entry* entry, bool restore)
{
+ m_customData->copyDataFrom(entry->customData());
+
m_mainUi->titleEdit->setReadOnly(m_history);
m_mainUi->usernameEdit->setReadOnly(m_history);
m_mainUi->urlEdit->setReadOnly(m_history);
@@ -764,7 +773,6 @@ void EditEntryWidget::setForms(const Entry* entry, bool restore)
#endif
m_editWidgetProperties->setFields(entry->timeInfo(), entry->uuid());
- m_editWidgetProperties->setCustomData(entry->customData());
if (!m_history && !restore) {
m_historyModel->setEntries(entry->historyItems());
@@ -873,7 +881,7 @@ void EditEntryWidget::updateEntryData(Entry* entry) const
entry->attributes()->copyCustomKeysFrom(m_entryAttributes);
entry->attachments()->copyDataFrom(m_advancedUi->attachmentsWidget->entryAttachments());
- entry->customData()->copyDataFrom(m_editWidgetProperties->customData());
+ entry->customData()->copyDataFrom(m_customData.data());
entry->setTitle(m_mainUi->titleEdit->text().replace(newLineRegex, " "));
entry->setUsername(m_mainUi->usernameEdit->text().replace(newLineRegex, " "));
entry->setUrl(m_mainUi->urlEdit->text().replace(newLineRegex, " "));
diff --git a/src/gui/entry/EditEntryWidget.h b/src/gui/entry/EditEntryWidget.h
index b3c313b19..c8eabb8a9 100644
--- a/src/gui/entry/EditEntryWidget.h
+++ b/src/gui/entry/EditEntryWidget.h
@@ -28,6 +28,7 @@
class AutoTypeAssociations;
class AutoTypeAssociationsModel;
+class CustomData;
class Database;
class EditWidgetIcons;
class EditWidgetProperties;
@@ -153,11 +154,15 @@ private:
const QScopedPointer<Ui::EditEntryWidgetAutoType> m_autoTypeUi;
const QScopedPointer<Ui::EditEntryWidgetSSHAgent> m_sshAgentUi;
const QScopedPointer<Ui::EditEntryWidgetHistory> m_historyUi;
+ const QScopedPointer<CustomData> m_customData;
+
QWidget* const m_mainWidget;
QWidget* const m_advancedWidget;
EditWidgetIcons* const m_iconsWidget;
QWidget* const m_autoTypeWidget;
+#ifdef WITH_XC_SSHAGENT
QWidget* const m_sshAgentWidget;
+#endif
EditWidgetProperties* const m_editWidgetProperties;
QWidget* const m_historyWidget;
EntryAttributes* const m_entryAttributes;
diff --git a/src/gui/group/EditGroupWidget.cpp b/src/gui/group/EditGroupWidget.cpp
index 2abfa5544..e74cc6cfd 100644
--- a/src/gui/group/EditGroupWidget.cpp
+++ b/src/gui/group/EditGroupWidget.cpp
@@ -23,14 +23,40 @@
#include "gui/EditWidgetIcons.h"
#include "gui/EditWidgetProperties.h"
+#ifdef WITH_XC_KEESHARE
+#include "keeshare/group/EditGroupPageKeeShare.h"
+#endif
+
+class EditGroupWidget::ExtraPage
+{
+public:
+ ExtraPage(IEditGroupPage* page, QWidget* widget)
+ : editPage(page)
+ , widget(widget)
+ {
+ }
+
+ void set(Group* temporaryGroup) const
+ {
+ editPage->set(widget, temporaryGroup);
+ }
+
+ void assign() const
+ {
+ editPage->assign(widget);
+ }
+
+private:
+ QSharedPointer<IEditGroupPage> editPage;
+ QWidget* widget;
+};
+
EditGroupWidget::EditGroupWidget(QWidget* parent)
: EditWidget(parent)
, m_mainUi(new Ui::EditGroupWidgetMain())
, m_editGroupWidgetMain(new QWidget())
, m_editGroupWidgetIcons(new EditWidgetIcons())
, m_editWidgetProperties(new EditWidgetProperties())
- , m_group(nullptr)
- , m_database(nullptr)
{
m_mainUi->setupUi(m_editGroupWidgetMain);
@@ -52,6 +78,10 @@ EditGroupWidget::EditGroupWidget(QWidget* parent)
SIGNAL(messageEditEntry(QString, MessageWidget::MessageType)),
SLOT(showMessage(QString, MessageWidget::MessageType)));
connect(m_editGroupWidgetIcons, SIGNAL(messageEditEntryDismiss()), SLOT(hideMessage()));
+
+#ifdef WITH_XC_KEESHARE
+ addEditPage(new EditGroupPageKeeShare(this));
+#endif
}
EditGroupWidget::~EditGroupWidget()
@@ -63,6 +93,8 @@ void EditGroupWidget::loadGroup(Group* group, bool create, Database* database)
m_group = group;
m_database = database;
+ m_temporaryGroup.reset(group->clone(Entry::CloneNoFlags, Group::CloneNoFlags));
+
if (create) {
setHeadline(tr("Add group"));
} else {
@@ -91,12 +123,15 @@ void EditGroupWidget::loadGroup(Group* group, bool create, Database* database)
m_mainUi->autoTypeSequenceCustomEdit->setText(group->effectiveAutoTypeSequence());
IconStruct iconStruct;
- iconStruct.uuid = group->iconUuid();
- iconStruct.number = group->iconNumber();
- m_editGroupWidgetIcons->load(group->uuid(), database, iconStruct);
-
- m_editWidgetProperties->setFields(group->timeInfo(), group->uuid());
- m_editWidgetProperties->setCustomData(group->customData());
+ iconStruct.uuid = m_temporaryGroup->iconUuid();
+ iconStruct.number = m_temporaryGroup->iconNumber();
+ m_editGroupWidgetIcons->load(m_temporaryGroup->uuid(), m_database, iconStruct);
+ m_editWidgetProperties->setFields(m_temporaryGroup->timeInfo(), m_temporaryGroup->uuid());
+ m_editWidgetProperties->setCustomData(m_temporaryGroup->customData());
+
+ for (const ExtraPage& page : asConst(m_extraPages)) {
+ page.set(m_temporaryGroup.data());
+ }
setCurrentPage(0);
@@ -112,50 +147,61 @@ void EditGroupWidget::save()
void EditGroupWidget::apply()
{
- m_group->setName(m_mainUi->editName->text());
- m_group->setNotes(m_mainUi->editNotes->toPlainText());
- m_group->setExpires(m_mainUi->expireCheck->isChecked());
- m_group->setExpiryTime(m_mainUi->expireDatePicker->dateTime().toUTC());
-
- m_group->setSearchingEnabled(triStateFromIndex(m_mainUi->searchComboBox->currentIndex()));
- m_group->setAutoTypeEnabled(triStateFromIndex(m_mainUi->autotypeComboBox->currentIndex()));
+ m_temporaryGroup->setName(m_mainUi->editName->text());
+ m_temporaryGroup->setNotes(m_mainUi->editNotes->toPlainText());
+ m_temporaryGroup->setExpires(m_mainUi->expireCheck->isChecked());
+ m_temporaryGroup->setExpiryTime(m_mainUi->expireDatePicker->dateTime().toUTC());
- m_group->customData()->copyDataFrom(m_editWidgetProperties->customData());
+ m_temporaryGroup->setSearchingEnabled(triStateFromIndex(m_mainUi->searchComboBox->currentIndex()));
+ m_temporaryGroup->setAutoTypeEnabled(triStateFromIndex(m_mainUi->autotypeComboBox->currentIndex()));
if (m_mainUi->autoTypeSequenceInherit->isChecked()) {
- m_group->setDefaultAutoTypeSequence(QString());
+ m_temporaryGroup->setDefaultAutoTypeSequence(QString());
} else {
- m_group->setDefaultAutoTypeSequence(m_mainUi->autoTypeSequenceCustomEdit->text());
+ m_temporaryGroup->setDefaultAutoTypeSequence(m_mainUi->autoTypeSequenceCustomEdit->text());
}
IconStruct iconStruct = m_editGroupWidgetIcons->state();
if (iconStruct.number < 0) {
- m_group->setIcon(Group::DefaultIconNumber);
+ m_temporaryGroup->setIcon(Group::DefaultIconNumber);
} else if (iconStruct.uuid.isNull()) {
- m_group->setIcon(iconStruct.number);
+ m_temporaryGroup->setIcon(iconStruct.number);
} else {
- m_group->setIcon(iconStruct.uuid);
+ m_temporaryGroup->setIcon(iconStruct.uuid);
+ }
+
+ for (const ExtraPage& page : asConst(m_extraPages)) {
+ page.assign();
}
+
+ // Icons add/remove are applied globally outside the transaction!
+ m_group->copyDataFrom(m_temporaryGroup.data());
}
void EditGroupWidget::cancel()
{
- if (!m_group->iconUuid().isNull() && !m_database->metadata()->containsCustomIcon(m_group->iconUuid())) {
- m_group->setIcon(Entry::DefaultIconNumber);
- }
-
clear();
emit editFinished(false);
}
void EditGroupWidget::clear()
{
- m_group = nullptr;
- m_database = nullptr;
+ m_temporaryGroup.reset(nullptr);
+ m_database.clear();
+ m_group.clear();
m_editGroupWidgetIcons->reset();
}
+void EditGroupWidget::addEditPage(IEditGroupPage* page)
+{
+ QWidget* widget = page->createWidget();
+ widget->setParent(this);
+
+ m_extraPages.append(ExtraPage(page, widget));
+ addPage(page->name(), page->icon(), widget);
+}
+
void EditGroupWidget::addTriStateItems(QComboBox* comboBox, bool inheritDefault)
{
QString inheritDefaultString;
diff --git a/src/gui/group/EditGroupWidget.h b/src/gui/group/EditGroupWidget.h
index 87271871d..fcba1e5fc 100644
--- a/src/gui/group/EditGroupWidget.h
+++ b/src/gui/group/EditGroupWidget.h
@@ -24,6 +24,7 @@
#include "core/Group.h"
#include "gui/EditWidget.h"
+class CustomData;
class EditWidgetIcons;
class EditWidgetProperties;
@@ -33,6 +34,19 @@ namespace Ui
class EditWidget;
}
+class IEditGroupPage
+{
+public:
+ virtual ~IEditGroupPage()
+ {
+ }
+ virtual QString name() = 0;
+ virtual QIcon icon() = 0;
+ virtual QWidget* createWidget() = 0;
+ virtual void set(QWidget* widget, Group* tempoaryGroup) = 0;
+ virtual void assign(QWidget* widget) = 0;
+};
+
class EditGroupWidget : public EditWidget
{
Q_OBJECT
@@ -44,6 +58,8 @@ public:
void loadGroup(Group* group, bool create, Database* database);
void clear();
+ void addEditPage(IEditGroupPage* page);
+
signals:
void editFinished(bool accepted);
void messageEditEntry(QString, MessageWidget::MessageType);
@@ -60,12 +76,17 @@ private:
Group::TriState triStateFromIndex(int index);
const QScopedPointer<Ui::EditGroupWidgetMain> m_mainUi;
+
QPointer<QWidget> m_editGroupWidgetMain;
QPointer<EditWidgetIcons> m_editGroupWidgetIcons;
QPointer<EditWidgetProperties> m_editWidgetProperties;
- QPointer<Group> m_group;
+ QScopedPointer<Group> m_temporaryGroup;
QPointer<Database> m_database;
+ QPointer<Group> m_group;
+
+ class ExtraPage;
+ QList<ExtraPage> m_extraPages;
Q_DISABLE_COPY(EditGroupWidget)
};
diff --git a/src/gui/group/GroupModel.cpp b/src/gui/group/GroupModel.cpp
index e8f51909f..690463724 100644
--- a/src/gui/group/GroupModel.cpp
+++ b/src/gui/group/GroupModel.cpp
@@ -25,6 +25,7 @@
#include "core/Group.h"
#include "core/Metadata.h"
#include "core/Tools.h"
+#include "keeshare/KeeShare.h"
GroupModel::GroupModel(Database* db, QObject* parent)
: QAbstractItemModel(parent)
@@ -125,13 +126,18 @@ QVariant GroupModel::data(const QModelIndex& index, int role) const
Group* group = groupFromIndex(index);
if (role == Qt::DisplayRole) {
- return group->name();
+ QString nameTemplate = tr("%1", "Template for name without annotation");
+#ifdef WITH_XC_KEESHARE
+ nameTemplate = KeeShare::indicatorSuffix(group, nameTemplate);
+#endif
+ return nameTemplate.arg(group->name());
} else if (role == Qt::DecorationRole) {
- if (group->isExpired()) {
- return databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex);
- } else {
- return group->iconScaledPixmap();
- }
+ QPixmap pixmap = group->isExpired() ? databaseIcons()->iconPixmap(DatabaseIcons::ExpiredIconIndex)
+ : group->iconScaledPixmap();
+#ifdef WITH_XC_KEESHARE
+ pixmap = KeeShare::indicatorBadge(group, pixmap);
+#endif
+ return pixmap;
} else if (role == Qt::FontRole) {
QFont font;
if (group->isExpired()) {
diff --git a/src/keeshare/CMakeLists.txt b/src/keeshare/CMakeLists.txt
new file mode 100644
index 000000000..30a6bc4e1
--- /dev/null
+++ b/src/keeshare/CMakeLists.txt
@@ -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()
diff --git a/src/keeshare/DatabaseSettingsPageKeeShare.cpp b/src/keeshare/DatabaseSettingsPageKeeShare.cpp
new file mode 100644
index 000000000..1bd117327
--- /dev/null
+++ b/src/keeshare/DatabaseSettingsPageKeeShare.cpp
@@ -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();
+}
diff --git a/src/keeshare/DatabaseSettingsPageKeeShare.h b/src/keeshare/DatabaseSettingsPageKeeShare.h
new file mode 100644
index 000000000..23a4d5f8a
--- /dev/null
+++ b/src/keeshare/DatabaseSettingsPageKeeShare.h
@@ -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
diff --git a/src/keeshare/DatabaseSettingsWidgetKeeShare.cpp b/src/keeshare/DatabaseSettingsWidgetKeeShare.cpp
new file mode 100644
index 000000000..522aaa603
--- /dev/null
+++ b/src/keeshare/DatabaseSettingsWidgetKeeShare.cpp
@@ -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
+}
diff --git a/src/keeshare/DatabaseSettingsWidgetKeeShare.h b/src/keeshare/DatabaseSettingsWidgetKeeShare.h
new file mode 100644
index 000000000..80ece51a4
--- /dev/null
+++ b/src/keeshare/DatabaseSettingsWidgetKeeShare.h
@@ -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
diff --git a/src/keeshare/DatabaseSettingsWidgetKeeShare.ui b/src/keeshare/DatabaseSettingsWidgetKeeShare.ui
new file mode 100644
index 000000000..85bf2083a
--- /dev/null
+++ b/src/keeshare/DatabaseSettingsWidgetKeeShare.ui
@@ -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>
diff --git a/src/keeshare/KeeShare.cpp b/src/keeshare/KeeShare.cpp
new file mode 100644
index 000000000..b2c7fd143
--- /dev/null
+++ b/src/keeshare/KeeShare.cpp
@@ -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)));
+}
diff --git a/src/keeshare/KeeShare.h b/src/keeshare/KeeShare.h
new file mode 100644
index 000000000..cd4d538f0
--- /dev/null
+++ b/src/keeshare/KeeShare.h
@@ -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
diff --git a/src/keeshare/KeeShareSettings.cpp b/src/keeshare/KeeShareSettings.cpp
new file mode 100644
index 000000000..a1fcfac37
--- /dev/null
+++ b/src/keeshare/KeeShareSettings.cpp
@@ -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;
+ }
+}
diff --git a/src/keeshare/KeeShareSettings.h b/src/keeshare/KeeShareSettings.h
new file mode 100644
index 000000000..94d67d1ca
--- /dev/null
+++ b/src/keeshare/KeeShareSettings.h
@@ -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
diff --git a/src/keeshare/SettingsPageKeeShare.cpp b/src/keeshare/SettingsPageKeeShare.cpp
new file mode 100644
index 000000000..04a0f1058
--- /dev/null
+++ b/src/keeshare/SettingsPageKeeShare.cpp
@@ -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();
+}
diff --git a/src/keeshare/SettingsPageKeeShare.h b/src/keeshare/SettingsPageKeeShare.h
new file mode 100644
index 000000000..975f64393
--- /dev/null
+++ b/src/keeshare/SettingsPageKeeShare.h
@@ -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
diff --git a/src/keeshare/SettingsWidgetKeeShare.cpp b/src/keeshare/SettingsWidgetKeeShare.cpp
new file mode 100644
index 000000000..7da48c4df
--- /dev/null
+++ b/src/keeshare/SettingsWidgetKeeShare.cpp
@@ -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();
+}
diff --git a/src/keeshare/SettingsWidgetKeeShare.h b/src/keeshare/SettingsWidgetKeeShare.h
new file mode 100644
index 000000000..f68b76792
--- /dev/null
+++ b/src/keeshare/SettingsWidgetKeeShare.h
@@ -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
diff --git a/src/keeshare/SettingsWidgetKeeShare.ui b/src/keeshare/SettingsWidgetKeeShare.ui
new file mode 100644
index 000000000..c736bdedd
--- /dev/null
+++ b/src/keeshare/SettingsWidgetKeeShare.ui
@@ -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>
diff --git a/src/keeshare/ShareObserver.cpp b/src/keeshare/ShareObserver.cpp
new file mode 100644
index 000000000..fb0b419fa
--- /dev/null
+++ b/src/keeshare/ShareObserver.cpp
@@ -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;
+}
diff --git a/src/keeshare/ShareObserver.h b/src/keeshare/ShareObserver.h
new file mode 100644
index 000000000..ae7734ea0
--- /dev/null
+++ b/src/keeshare/ShareObserver.h
@@ -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
diff --git a/src/keeshare/Signature.cpp b/src/keeshare/Signature.cpp
new file mode 100644
index 000000000..fdc0481fb
--- /dev/null
+++ b/src/keeshare/Signature.cpp
@@ -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;
+}
diff --git a/src/keeshare/Signature.h b/src/keeshare/Signature.h
new file mode 100644
index 000000000..59c32339f
--- /dev/null
+++ b/src/keeshare/Signature.h
@@ -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
diff --git a/src/keeshare/group/EditGroupPageKeeShare.cpp b/src/keeshare/group/EditGroupPageKeeShare.cpp
new file mode 100644
index 000000000..6d2eabb92
--- /dev/null
+++ b/src/keeshare/group/EditGroupPageKeeShare.cpp
@@ -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
+}
diff --git a/src/keeshare/group/EditGroupPageKeeShare.h b/src/keeshare/group/EditGroupPageKeeShare.h
new file mode 100644
index 000000000..786c43435
--- /dev/null
+++ b/src/keeshare/group/EditGroupPageKeeShare.h
@@ -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
diff --git a/src/keeshare/group/EditGroupWidgetKeeShare.cpp b/src/keeshare/group/EditGroupWidgetKeeShare.cpp
new file mode 100644
index 000000000..a9299bf7f
--- /dev/null
+++ b/src/keeshare/group/EditGroupWidgetKeeShare.cpp
@@ -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);
+}
diff --git a/src/keeshare/group/EditGroupWidgetKeeShare.h b/src/keeshare/group/EditGroupWidgetKeeShare.h
new file mode 100644
index 000000000..b01bada44
--- /dev/null
+++ b/src/keeshare/group/EditGroupWidgetKeeShare.h
@@ -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
diff --git a/src/keeshare/group/EditGroupWidgetKeeShare.ui b/src/keeshare/group/EditGroupWidgetKeeShare.ui
new file mode 100644
index 000000000..02361f92d
--- /dev/null
+++ b/src/keeshare/group/EditGroupWidgetKeeShare.ui
@@ -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>
diff --git a/src/sshagent/CMakeLists.txt b/src/sshagent/CMakeLists.txt
index a612ff076..ff14356ec 100644
--- a/src/sshagent/CMakeLists.txt
+++ b/src/sshagent/CMakeLists.txt
@@ -2,17 +2,12 @@ if(WITH_XC_SSHAGENT)
include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
set(sshagent_SOURCES
- bcrypt_pbkdf.cpp
- blowfish.c
AgentSettingsPage.cpp
AgentSettingsWidget.cpp
- BinaryStream.cpp
KeeAgentSettings.cpp
- OpenSSHKey.cpp
- ASN1Key.cpp
SSHAgent.cpp
)
add_library(sshagent STATIC ${sshagent_SOURCES})
- target_link_libraries(sshagent Qt5::Core Qt5::Widgets Qt5::Network ${GCRYPT_LIBRARIES})
+ target_link_libraries(sshagent Qt5::Core Qt5::Widgets Qt5::Network ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB})
endif()
diff --git a/src/sshagent/SSHAgent.cpp b/src/sshagent/SSHAgent.cpp
index 758c86851..84281bf7d 100644
--- a/src/sshagent/SSHAgent.cpp
+++ b/src/sshagent/SSHAgent.cpp
@@ -17,8 +17,10 @@
*/
#include "SSHAgent.h"
-#include "BinaryStream.h"
-#include "KeeAgentSettings.h"
+
+#include "crypto/ssh/OpenSSHKey.h"
+#include "crypto/ssh/BinaryStream.h"
+#include "sshagent/KeeAgentSettings.h"
#ifndef Q_OS_WIN
#include <QtNetwork>
@@ -306,11 +308,11 @@ void SSHAgent::databaseModeChanged(DatabaseWidget::Mode mode)
OpenSSHKey key;
- if (!key.parse(keyData)) {
+ if (!key.parsePKCS1PEM(keyData)) {
continue;
}
- if (!key.openPrivateKey(e->password())) {
+ if (!key.openKey(e->password())) {
continue;
}
diff --git a/src/sshagent/SSHAgent.h b/src/sshagent/SSHAgent.h
index acef6d62e..e6564d572 100644
--- a/src/sshagent/SSHAgent.h
+++ b/src/sshagent/SSHAgent.h
@@ -16,14 +16,14 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef AGENTCLIENT_H
-#define AGENTCLIENT_H
+#ifndef KEEPASSXC_SSHAGENT_H
+#define KEEPASSXC_SSHAGENT_H
-#include "OpenSSHKey.h"
#include <QList>
#include <QtCore>
#include "gui/DatabaseWidget.h"
+#include "crypto/ssh/OpenSSHKey.h"
class SSHAgent : public QObject
{
@@ -75,4 +75,4 @@ private:
QString m_error;
};
-#endif // AGENTCLIENT_H
+#endif // KEEPASSXC_SSHAGENT_H