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
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
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
-rw-r--r--CMakeLists.txt15
-rw-r--r--Dockerfile2
-rw-r--r--INSTALL.md5
-rw-r--r--README.md1
-rw-r--r--ci/trusty/Dockerfile2
-rw-r--r--cmake/CLangFormat.cmake6
-rw-r--r--cmake/FindQuaZip.cmake23
-rw-r--r--docs/QUICKSTART.md62
-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
-rw-r--r--tests/CMakeLists.txt22
-rw-r--r--tests/TestOpenSSHKey.cpp58
-rw-r--r--tests/TestOpenSSHKey.h1
-rw-r--r--tests/TestRandomGenerator.cpp (renamed from tests/TestRandom.cpp)48
-rw-r--r--tests/TestRandomGenerator.h (renamed from tests/TestRandom.h)23
-rw-r--r--tests/TestSharing.cpp306
-rw-r--r--tests/TestSharing.h47
-rw-r--r--tests/TestSignature.cpp198
-rw-r--r--tests/TestSignature.h40
-rw-r--r--tests/stub/TestRandom.cpp62
-rw-r--r--tests/stub/TestRandom.h48
106 files changed, 5807 insertions, 482 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0edd766ff..23fb82663 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -47,6 +47,7 @@ option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website
option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF)
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
+option(WITH_XC_KEESHARE "Include sharing support with KeeShare." OFF)
if(APPLE)
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
endif()
@@ -58,11 +59,18 @@ if(WITH_XC_ALL)
set(WITH_XC_BROWSER ON)
set(WITH_XC_YUBIKEY ON)
set(WITH_XC_SSHAGENT ON)
+ set(WITH_XC_KEESHARE ON)
if(APPLE)
set(WITH_XC_TOUCHID ON)
endif()
endif()
+if(WITH_XC_SSHAGENT OR WITH_XC_KEESHARE)
+ set(WITH_XC_CRYPTO_SSH ON)
+else()
+ set(WITH_XC_CRYPTO_SSH OFF)
+endif()
+
set(KEEPASSXC_VERSION_MAJOR "2")
set(KEEPASSXC_VERSION_MINOR "4")
set(KEEPASSXC_VERSION_PATCH "0")
@@ -348,6 +356,13 @@ endif()
include_directories(SYSTEM ${ARGON2_INCLUDE_DIR})
# Optional
+if(WITH_XC_KEESHARE)
+ find_package(QuaZip REQUIRED)
+
+ include_directories(SYSTEM ${QUAZIP_INCLUDE_DIR})
+endif()
+
+# Optional
if(WITH_XC_YUBIKEY)
find_package(YubiKey REQUIRED)
diff --git a/Dockerfile b/Dockerfile
index 89ee04464..95c3ed99f 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -53,6 +53,8 @@ RUN set -x \
zlib1g-dev \
libxi-dev \
libxtst-dev \
+ libquazip5-headers \
+ libquazip5-dev \
mesa-common-dev \
libyubikey-dev \
libykpers-1-dev
diff --git a/INSTALL.md b/INSTALL.md
index a7e1f3a5c..c13263913 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -98,8 +98,9 @@ These steps place the compiled KeePassXC binary inside the `./build/src/` direct
-DWITH_XC_YUBIKEY=[ON|OFF] Enable/Disable YubiKey HMAC-SHA1 authentication support (default: OFF)
-DWITH_XC_BROWSER=[ON|OFF] Enable/Disable KeePassXC-Browser extension support (default: OFF)
-DWITH_XC_NETWORKING=[ON|OFF] Enable/Disable Networking support (favicon download) (default: OFF)
- -DWITH_XC_SSHAGENT=[ON|OFF] Include SSH agent support. (default: OFF)
-
+ -DWITH_XC_SSHAGENT=[ON|OFF] Enable/Disable SSHAgent support (default: OFF)
+ -DWITH_XC_SHARING=[ON|OFF] Enable/Disable Sharing extension (default: OFF)
+ -DWITH_XC_TOUCHID=[ON|OFF] (macOS Only) Enable/Disable Touch ID unlock (default:OFF)
-DWITH_XC_ALL=[ON|OFF] Enable/Disable compiling all plugins above (default: OFF)
-DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON)
diff --git a/README.md b/README.md
index fc63beda2..3de0f38f6 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,7 @@ so please check out your distribution's package list to see if KeePassXC is avai
[Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepasshttp-connector/dafgdjggglmmknipkhngniifhplpcldb), and
[passafari](https://github.com/mmichaa/passafari.safariextension/) in Safari. [[See note about KeePassHTTP]](#note-about-keepasshttp)
- Browser integration with KeePassXC-Browser using [native messaging](https://developer.chrome.com/extensions/nativeMessaging) for [Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) and [Google Chrome or Chromium](https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk)
+- Sharing of passwords using KeeShare. See [Using Sharing](./docs/QUICKSTART.md#using-sharing) for more details.
- Many bug fixes
For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document.
diff --git a/ci/trusty/Dockerfile b/ci/trusty/Dockerfile
index 04aee25a5..55c604e75 100644
--- a/ci/trusty/Dockerfile
+++ b/ci/trusty/Dockerfile
@@ -52,6 +52,8 @@ RUN set -x \
zlib1g-dev \
libyubikey-dev \
libykpers-1-dev \
+ libquazip5-headers \
+ libquazip5-dev \
libxi-dev \
libxtst-dev \
xvfb
diff --git a/cmake/CLangFormat.cmake b/cmake/CLangFormat.cmake
index 8c26db93b..68d28311b 100644
--- a/cmake/CLangFormat.cmake
+++ b/cmake/CLangFormat.cmake
@@ -29,9 +29,9 @@ set(EXCLUDED_FILES
gui/KMessageWidget.cpp
gui/MainWindowAdaptor.h
gui/MainWindowAdaptor.cpp
- sshagent/bcrypt_pbkdf.cpp
- sshagent/blf.h
- sshagent/blowfish.c
+ crypto/ssh/bcrypt_pbkdf.cpp
+ crypto/ssh/blf.h
+ crypto/ssh/blowfish.c
tests/modeltest.cpp
tests/modeltest.h
# objective-c files
diff --git a/cmake/FindQuaZip.cmake b/cmake/FindQuaZip.cmake
new file mode 100644
index 000000000..58244f4df
--- /dev/null
+++ b/cmake/FindQuaZip.cmake
@@ -0,0 +1,23 @@
+# 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/>.
+
+find_path(QUAZIP_INCLUDE_DIR quazip5/quazip.h)
+find_library(QUAZIP_LIBRARIES quazip5)
+
+mark_as_advanced(QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR)
+
+include(FindPackageHandleStandardArgs)
+include_directories(${QUAZIP_INCLUDE_DIR})
+find_package_handle_standard_args(QuaZip DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR)
diff --git a/docs/QUICKSTART.md b/docs/QUICKSTART.md
index 8b694888e..0f40958bb 100644
--- a/docs/QUICKSTART.md
+++ b/docs/QUICKSTART.md
@@ -30,12 +30,12 @@ Leave the other options at their defaults.
* *In your default web browser,* install the KeePassXC Browser extension/add-on. Instructions for [Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepassxc-browser/) or [Chrome](https://chrome.google.com/webstore/detail/keepassxc-browser/oboonakemofpalcgghocfoadofidjkkk)
* Click the KeePassXC icon in the upper-right corner. You'll see the dialog below.
* Click the blue Connect button to make the browser extension connect to the KeePassXC application.
-<img src="./KeePassXC-Connect.png" height="200" alt="KeePassXC Connect dialog">
+<img src="./KeePassHTTP/KeePassXC-Connect.png" height="200" alt="KeePassXC Connect dialog">
* *Switch back to KeePassXC.* You'll see a dialog (below) indicating that a request to connect has arrived.
* Give the connection a name (perhaps *Keepass-Browsername*, any unique name will suffice) and click OK to accept it.
* This one-time operation connects KeePassXC and your browser.
-<img src="./KeePassXC-Accept-Button.png" height="200" alt="KeePassXC accept connection dialog">
+<img src="./KeePassHTTP/KeePassXC-Accept-Button.png" height="200" alt="KeePassXC accept connection dialog">
## Using Browser Integration
@@ -45,4 +45,60 @@ or select it and type Ctrl+U (Cmd+U on macOS).
* If there are username/password fields on that page, you will see the dialog below.
Click *Allow* to confirm that KeePassXC may access the credentials to auto-fill the fields.
* Check *Remember this decision* to allow this each time you visit the page.
-<img src="./KeePassXC-Confirm.png" height="200" alt="KeePassCX Confirm Access dialog">
+<img src="./KeePassHTTP/KeePassXC-Confirm.png" height="200" alt="KeePassCX Confirm Access dialog">
+
+## Using Sharing
+
+Sharing allows you to share a subset of your credentials with others and vice versa.
+
+### Enable Sharing
+
+To use sharing, you need to enable it on a database.
+
+1. Go to Database &rarr; Database Settings
+1. Check _Allow import_ if you want to import shared credentials
+1. Check _Allow export_ if you want to share credentials
+
+<img src="./KeeShare/Database-Settings.png" height="600" width="800" alt="KeePassXC Databse Sharing Settings">
+
+### Sharing Credentials
+
+If you checked _Allow export_ in the Sharing settings you now are good to go to share some passwords with others. Sharing always is defined on a group. If you enable sharing on a group, every entry under this group or it's children is shared. If you enable sharing on the root node, **every password** inside your database gets shared!
+
+1. Open the edit sheet on a group you want to share
+1. Select the sharing section
+1. Choose _Export to path_ as the sharing method
+1. Choose a path to store the shared credentials to
+1. Generate a password for this share database
+
+The export file will not be generated automatically. Instead, each time the database is saved, the file gets written. If an old file is present, the old file will be overwritten! The file should be written to a location that is accessible by others. An easy setup is a network share or storing the file inside the cloud.
+
+<img src="./KeeShare/Share-Group.png" height="600" width="800" alt="KeePassXC Group Sharing Settings">
+
+### Using Shared Credentials
+
+Checking _Allow import_ in the Sharing settings of the database enables you to receive credentials from others. KeePass will watch sharing sources and import any changes immoderately into your database using the synchronization feature.
+
+1. Create a group for import
+1. Open the edit sheet on that group
+1. Select the sharing section
+1. Choose _Import from path_ as the sharing method
+1. Choose a database that is shared with you
+1. Enter the password for the shared database
+
+<img src="./KeeShare/Import-Group.png" height="600" width="800" alt="KeePassXC Group Import Settings">
+
+### Using Synchronized Credentials
+
+Instead of using different groups for sharing and importing you can use a single group that acts as both. This way you can synchronize a number of credentials easily across many users without a lot of hassle.
+
+1. Open the edit sheet on a group you want to synchronize
+1. Select the sharing section
+1. Choose _Synchronize with path_ as the sharing method
+1. Choose a database that you want to use a synchronization file
+1. Enter the password for the database
+
+<img src="./KeeShare/Synchronize-Group.png" height="600" width="800" alt="KeePassXC Group Synchronization Settings">
+
+## Technical Details of Sharing
+Sharing relies on the combination of file exports and imports as well as the synchronization mechanism provided by KeePassXC
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
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 73262bae0..22e6fa450 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -95,7 +95,7 @@ set(TEST_LIBRARIES
${ZLIB_LIBRARIES}
)
-set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp stub/TestClock.cpp)
+set(testsupport_SOURCES TestGlobal.h modeltest.cpp FailDevice.cpp stub/TestClock.cpp stub/TestRandom.cpp)
add_library(testsupport STATIC ${testsupport_SOURCES})
target_link_libraries(testsupport Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Test)
@@ -130,6 +130,11 @@ add_unit_test(NAME testcryptohash SOURCES TestCryptoHash.cpp
add_unit_test(NAME testsymmetriccipher SOURCES TestSymmetricCipher.cpp
LIBS ${TEST_LIBRARIES})
+if(WITH_XC_KEESHARE)
+ add_unit_test(NAME testsignature SOURCES TestSignature.cpp
+ LIBS ${TEST_LIBRARIES})
+endif()
+
add_unit_test(NAME testhashedblockstream SOURCES TestHashedBlockStream.cpp
LIBS testsupport ${TEST_LIBRARIES})
@@ -154,9 +159,9 @@ if(WITH_XC_AUTOTYPE)
set_target_properties(testautotype PROPERTIES ENABLE_EXPORTS ON)
endif()
-if(WITH_XC_SSHAGENT)
- add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp
- LIBS sshagent ${TEST_LIBRARIES})
+if(WITH_XC_CRYPTO_SSH)
+add_unit_test(NAME testopensshkey SOURCES TestOpenSSHKey.cpp
+ LIBS ${TEST_LIBRARIES})
endif()
add_unit_test(NAME testentry SOURCES TestEntry.cpp
@@ -174,8 +179,8 @@ add_unit_test(NAME testbase32 SOURCES TestBase32.cpp
add_unit_test(NAME testcsvparser SOURCES TestCsvParser.cpp
LIBS ${TEST_LIBRARIES})
-add_unit_test(NAME testrandom SOURCES TestRandom.cpp
- LIBS ${TEST_LIBRARIES})
+add_unit_test(NAME testrandomgenerator SOURCES TestRandomGenerator.cpp
+ LIBS testsupport ${TEST_LIBRARIES})
add_unit_test(NAME testentrysearcher SOURCES TestEntrySearcher.cpp
LIBS ${TEST_LIBRARIES})
@@ -187,6 +192,11 @@ add_unit_test(NAME testykchallengeresponsekey
SOURCES TestYkChallengeResponseKey.cpp
LIBS ${TEST_LIBRARIES})
+if(WITH_XC_KEESHARE)
+ add_unit_test(NAME testsharing SOURCES TestSharing.cpp
+ LIBS testsupport ${TEST_LIBRARIES})
+endif()
+
add_unit_test(NAME testdatabase SOURCES TestDatabase.cpp
LIBS ${TEST_LIBRARIES})
diff --git a/tests/TestOpenSSHKey.cpp b/tests/TestOpenSSHKey.cpp
index 40fb9c302..222a50f1e 100644
--- a/tests/TestOpenSSHKey.cpp
+++ b/tests/TestOpenSSHKey.cpp
@@ -18,7 +18,8 @@
#include "TestOpenSSHKey.h"
#include "TestGlobal.h"
#include "crypto/Crypto.h"
-#include "sshagent/OpenSSHKey.h"
+#include "crypto/ssh/BinaryStream.h"
+#include "crypto/ssh/OpenSSHKey.h"
QTEST_GUILESS_MAIN(TestOpenSSHKey)
@@ -43,7 +44,7 @@ void TestOpenSSHKey::testParse()
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
- QVERIFY(key.parse(keyData));
+ QVERIFY(key.parsePKCS1PEM(keyData));
QVERIFY(!key.encrypted());
QCOMPARE(key.cipherName(), QString("none"));
QCOMPARE(key.type(), QString("ssh-ed25519"));
@@ -79,7 +80,7 @@ void TestOpenSSHKey::testParseDSA()
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
- QVERIFY(key.parse(keyData));
+ QVERIFY(key.parsePKCS1PEM(keyData));
QVERIFY(!key.encrypted());
QCOMPARE(key.cipherName(), QString("none"));
QCOMPARE(key.type(), QString("ssh-dss"));
@@ -125,11 +126,11 @@ void TestOpenSSHKey::testDecryptRSAAES128CBC()
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
- QVERIFY(key.parse(keyData));
+ QVERIFY(key.parsePKCS1PEM(keyData));
QVERIFY(key.encrypted());
QCOMPARE(key.cipherName(), QString("AES-128-CBC"));
- QVERIFY(!key.openPrivateKey("incorrectpassphrase"));
- QVERIFY(key.openPrivateKey("correctpassphrase"));
+ QVERIFY(!key.openKey("incorrectpassphrase"));
+ QVERIFY(key.openKey("correctpassphrase"));
QCOMPARE(key.type(), QString("ssh-rsa"));
QCOMPARE(key.comment(), QString(""));
QCOMPARE(key.fingerprint(), QString("SHA256:1Hsebt2WWnmc72FERsUOgvaajIGHkrMONxXylcmk87U"));
@@ -168,7 +169,7 @@ void TestOpenSSHKey::testParseRSA()
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
- QVERIFY(key.parse(keyData));
+ QVERIFY(key.parsePKCS1PEM(keyData));
QVERIFY(!key.encrypted());
QCOMPARE(key.cipherName(), QString("none"));
QCOMPARE(key.type(), QString("ssh-rsa"));
@@ -246,8 +247,8 @@ void TestOpenSSHKey::testParseRSACompare()
QByteArray oldPrivateKey, newPrivateKey;
BinaryStream oldPrivateStream(&oldPrivateKey), newPrivateStream(&newPrivateKey);
- QVERIFY(oldKey.parse(oldKeyData));
- QVERIFY(newKey.parse(newKeyData));
+ QVERIFY(oldKey.parsePKCS1PEM(oldKeyData));
+ QVERIFY(newKey.parsePKCS1PEM(newKeyData));
// comment is not part of the old format and writePrivate() includes it
oldKey.setComment("id_rsa");
@@ -274,11 +275,11 @@ void TestOpenSSHKey::testDecryptOpenSSHAES256CBC()
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
- QVERIFY(key.parse(keyData));
+ QVERIFY(key.parsePKCS1PEM(keyData));
QVERIFY(key.encrypted());
QCOMPARE(key.cipherName(), QString("aes256-cbc"));
- QVERIFY(!key.openPrivateKey("incorrectpassphrase"));
- QVERIFY(key.openPrivateKey("correctpassphrase"));
+ QVERIFY(!key.openKey("incorrectpassphrase"));
+ QVERIFY(key.openKey("correctpassphrase"));
QCOMPARE(key.type(), QString("ssh-ed25519"));
QCOMPARE(key.comment(), QString("opensshkey-test-aes256cbc@keepassxc"));
@@ -330,11 +331,11 @@ void TestOpenSSHKey::testDecryptRSAAES256CBC()
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
- QVERIFY(key.parse(keyData));
+ QVERIFY(key.parsePKCS1PEM(keyData));
QVERIFY(key.encrypted());
QCOMPARE(key.cipherName(), QString("AES-256-CBC"));
- QVERIFY(!key.openPrivateKey("incorrectpassphrase"));
- QVERIFY(key.openPrivateKey("correctpassphrase"));
+ QVERIFY(!key.openKey("incorrectpassphrase"));
+ QVERIFY(key.openKey("correctpassphrase"));
QCOMPARE(key.type(), QString("ssh-rsa"));
QCOMPARE(key.comment(), QString(""));
QCOMPARE(key.fingerprint(), QString("SHA256:1Hsebt2WWnmc72FERsUOgvaajIGHkrMONxXylcmk87U"));
@@ -354,11 +355,11 @@ void TestOpenSSHKey::testDecryptOpenSSHAES256CTR()
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
- QVERIFY(key.parse(keyData));
+ QVERIFY(key.parsePKCS1PEM(keyData));
QVERIFY(key.encrypted());
QCOMPARE(key.cipherName(), QString("aes256-ctr"));
- QVERIFY(!key.openPrivateKey("incorrectpassphrase"));
- QVERIFY(key.openPrivateKey("correctpassphrase"));
+ QVERIFY(!key.openKey("incorrectpassphrase"));
+ QVERIFY(key.openKey("correctpassphrase"));
QCOMPARE(key.type(), QString("ssh-ed25519"));
QCOMPARE(key.comment(), QString("opensshkey-test-aes256ctr@keepassxc"));
@@ -410,11 +411,11 @@ void TestOpenSSHKey::testDecryptRSAAES256CTR()
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
- QVERIFY(key.parse(keyData));
+ QVERIFY(key.parsePKCS1PEM(keyData));
QVERIFY(key.encrypted());
QCOMPARE(key.cipherName(), QString("AES-256-CTR"));
- QVERIFY(!key.openPrivateKey("incorrectpassphrase"));
- QVERIFY(key.openPrivateKey("correctpassphrase"));
+ QVERIFY(!key.openKey("incorrectpassphrase"));
+ QVERIFY(key.openKey("correctpassphrase"));
QCOMPARE(key.type(), QString("ssh-rsa"));
QCOMPARE(key.comment(), QString(""));
QCOMPARE(key.fingerprint(), QString("SHA256:1Hsebt2WWnmc72FERsUOgvaajIGHkrMONxXylcmk87U"));
@@ -436,12 +437,21 @@ void TestOpenSSHKey::testDecryptUTF8()
const QByteArray keyData = keyString.toLatin1();
OpenSSHKey key;
- QVERIFY(key.parse(keyData));
+ QVERIFY(key.parsePKCS1PEM(keyData));
QVERIFY(key.encrypted());
QCOMPARE(key.cipherName(), QString("aes256-ctr"));
- QVERIFY(!key.openPrivateKey("incorrectpassphrase"));
- QVERIFY(key.openPrivateKey("äåéëþüúíóö"));
+ QVERIFY(!key.openKey("incorrectpassphrase"));
+ QVERIFY(key.openKey("äåéëþüúíóö"));
QCOMPARE(key.fingerprint(), QString("SHA256:EfUXwvH4rOoys+AlbznCqjMwzIVW8KuhoWu9uT03FYA"));
QCOMPARE(key.type(), QString("ssh-ed25519"));
QCOMPARE(key.comment(), QString("opensshkey-test-utf8@keepassxc"));
}
+
+void TestOpenSSHKey::testGenerateRSA()
+{
+ OpenSSHKey key = OpenSSHKey::generate(false);
+ QVERIFY(!key.encrypted());
+ QCOMPARE(key.cipherName(), QString("none"));
+ QCOMPARE(key.type(), QString("ssh-rsa"));
+ QCOMPARE(key.comment(), QString(""));
+}
diff --git a/tests/TestOpenSSHKey.h b/tests/TestOpenSSHKey.h
index 214de8942..64516b204 100644
--- a/tests/TestOpenSSHKey.h
+++ b/tests/TestOpenSSHKey.h
@@ -38,6 +38,7 @@ private slots:
void testDecryptOpenSSHAES256CTR();
void testDecryptRSAAES256CTR();
void testDecryptUTF8();
+ void testGenerateRSA();
};
#endif // TESTOPENSSHKEY_H
diff --git a/tests/TestRandom.cpp b/tests/TestRandomGenerator.cpp
index 07d1c683a..02f183d07 100644
--- a/tests/TestRandom.cpp
+++ b/tests/TestRandomGenerator.cpp
@@ -15,21 +15,31 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#include "TestRandom.h"
+#include "TestRandomGenerator.h"
#include "TestGlobal.h"
#include "core/Endian.h"
#include "core/Global.h"
+#include "stub/TestRandom.h"
-QTEST_GUILESS_MAIN(TestRandom)
+#include <QTest>
-void TestRandom::initTestCase()
+QTEST_GUILESS_MAIN(TestRandomGenerator)
+
+void TestRandomGenerator::initTestCase()
+{
+ m_backend = new RandomBackendPreset();
+
+ TestRandom::setup(m_backend);
+}
+
+void TestRandomGenerator::cleanupTestCase()
{
- m_backend = new RandomBackendTest();
+ TestRandom::teardown();
- Random::createWithBackend(m_backend);
+ m_backend = nullptr;
}
-void TestRandom::testUInt()
+void TestRandomGenerator::testUInt()
{
QByteArray nextBytes;
@@ -60,7 +70,7 @@ void TestRandom::testUInt()
QCOMPARE(randomGen()->randomUInt((QUINT32_MAX / 2U) + 1U), QUINT32_MAX / 2U);
}
-void TestRandom::testUIntRange()
+void TestRandomGenerator::testUIntRange()
{
QByteArray nextBytes;
@@ -68,27 +78,3 @@ void TestRandom::testUIntRange()
m_backend->setNextBytes(nextBytes);
QCOMPARE(randomGen()->randomUIntRange(100, 200), 142U);
}
-
-RandomBackendTest::RandomBackendTest()
- : m_bytesIndex(0)
-{
-}
-
-void RandomBackendTest::randomize(void* data, int len)
-{
- QVERIFY(len <= (m_nextBytes.size() - m_bytesIndex));
-
- char* charData = reinterpret_cast<char*>(data);
-
- for (int i = 0; i < len; i++) {
- charData[i] = m_nextBytes[m_bytesIndex + i];
- }
-
- m_bytesIndex += len;
-}
-
-void RandomBackendTest::setNextBytes(const QByteArray& nextBytes)
-{
- m_nextBytes = nextBytes;
- m_bytesIndex = 0;
-}
diff --git a/tests/TestRandom.h b/tests/TestRandomGenerator.h
index 323d6b613..addcb0250 100644
--- a/tests/TestRandom.h
+++ b/tests/TestRandomGenerator.h
@@ -15,36 +15,27 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
-#ifndef KEEPASSX_TESTRANDOM_H
-#define KEEPASSX_TESTRANDOM_H
+#ifndef KEEPASSX_TESTRANDOMGENERATOR_H
+#define KEEPASSX_TESTRANDOMGENERATOR_H
#include "crypto/Random.h"
#include <QObject>
-class RandomBackendTest : public RandomBackend
-{
-public:
- RandomBackendTest();
- void randomize(void* data, int len) override;
- void setNextBytes(const QByteArray& nextBytes);
-
-private:
- QByteArray m_nextBytes;
- int m_bytesIndex;
-};
+class RandomBackendPreset;
-class TestRandom : public QObject
+class TestRandomGenerator : public QObject
{
Q_OBJECT
private slots:
void initTestCase();
+ void cleanupTestCase();
void testUInt();
void testUIntRange();
private:
- RandomBackendTest* m_backend;
+ RandomBackendPreset* m_backend;
};
-#endif // KEEPASSX_TESTRANDOM_H
+#endif // KEEPASSX_TESTRANDOMGENERATOR_H
diff --git a/tests/TestSharing.cpp b/tests/TestSharing.cpp
new file mode 100644
index 000000000..f6500ca8b
--- /dev/null
+++ b/tests/TestSharing.cpp
@@ -0,0 +1,306 @@
+/*
+ * 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 "TestSharing.h"
+#include "TestGlobal.h"
+#include "stub/TestRandom.h"
+
+#include <QBuffer>
+#include <QSignalSpy>
+#include <QTemporaryFile>
+#include <QXmlStreamReader>
+#include <QXmlStreamWriter>
+
+#include "config-keepassx-tests.h"
+#include "core/Metadata.h"
+#include "crypto/Crypto.h"
+#include "crypto/Random.h"
+#include "crypto/ssh/OpenSSHKey.h"
+#include "format/KeePass2Writer.h"
+#include "keeshare/KeeShareSettings.h"
+#include "keys/PasswordKey.h"
+
+#include <format/KeePass2Reader.h>
+
+QTEST_GUILESS_MAIN(TestSharing)
+
+Q_DECLARE_METATYPE(KeeShareSettings::Type)
+Q_DECLARE_METATYPE(KeeShareSettings::Key)
+Q_DECLARE_METATYPE(KeeShareSettings::Certificate)
+Q_DECLARE_METATYPE(QList<KeeShareSettings::Certificate>)
+
+void TestSharing::initTestCase()
+{
+ QVERIFY(Crypto::init());
+}
+
+void TestSharing::cleanupTestCase()
+{
+ TestRandom::teardown();
+}
+
+void TestSharing::testIdempotentDatabaseWriting()
+{
+ QScopedPointer<Database> db(new Database());
+ auto key = QSharedPointer<CompositeKey>::create();
+ key->addKey(QSharedPointer<PasswordKey>::create("password"));
+ db->setKey(key);
+
+ Group* sharingGroup = new Group();
+ sharingGroup->setName("SharingGroup");
+ sharingGroup->setUuid(QUuid::createUuid());
+ sharingGroup->setParent(db->rootGroup());
+
+ Entry* entry1 = new Entry();
+ entry1->setUuid(QUuid::createUuid());
+ entry1->beginUpdate();
+ entry1->setTitle("Entry1");
+ entry1->endUpdate();
+ entry1->setGroup(sharingGroup);
+
+ Entry* entry2 = new Entry();
+ entry2->setUuid(QUuid::createUuid());
+ entry2->beginUpdate();
+ entry2->setTitle("Entry2");
+ entry2->endUpdate();
+ entry2->setGroup(sharingGroup);
+
+ // prevent from changes introduced by randomization
+ TestRandom::setup(new RandomBackendNull());
+
+ QByteArray bufferOriginal;
+ {
+ QBuffer device(&bufferOriginal);
+ device.open(QIODevice::ReadWrite);
+ KeePass2Writer writer;
+ writer.writeDatabase(&device, db.data());
+ }
+
+ QByteArray bufferCopy;
+ {
+ QBuffer device(&bufferCopy);
+ device.open(QIODevice::ReadWrite);
+ KeePass2Writer writer;
+ writer.writeDatabase(&device, db.data());
+ }
+
+ QCOMPARE(bufferCopy, bufferOriginal);
+}
+
+void TestSharing::testNullObjects()
+{
+ const QString empty;
+ QXmlStreamReader reader(empty);
+
+ const KeeShareSettings::Key nullKey;
+ QVERIFY(nullKey.isNull());
+ const KeeShareSettings::Key xmlKey = KeeShareSettings::Key::deserialize(reader);
+ QVERIFY(xmlKey.isNull());
+
+ const KeeShareSettings::Certificate certificate;
+ QVERIFY(certificate.isNull());
+ const KeeShareSettings::Certificate xmlCertificate = KeeShareSettings::Certificate::deserialize(reader);
+ QVERIFY(xmlCertificate.isNull());
+
+ const KeeShareSettings::Own own;
+ QVERIFY(own.isNull());
+ const KeeShareSettings::Own xmlOwn = KeeShareSettings::Own::deserialize(empty);
+ QVERIFY(xmlOwn.isNull());
+
+ const KeeShareSettings::Active active;
+ QVERIFY(active.isNull());
+ const KeeShareSettings::Active xmlActive = KeeShareSettings::Active::deserialize(empty);
+ QVERIFY(xmlActive.isNull());
+
+ const KeeShareSettings::Foreign foreign;
+ QVERIFY(foreign.isNull());
+ const KeeShareSettings::Foreign xmlForeign = KeeShareSettings::Foreign::deserialize(empty);
+ QVERIFY(xmlForeign.isNull());
+
+ const KeeShareSettings::Reference reference;
+ QVERIFY(reference.isNull());
+ const KeeShareSettings::Reference xmlReference = KeeShareSettings::Reference::deserialize(empty);
+ QVERIFY(xmlReference.isNull());
+}
+
+void TestSharing::testCertificateSerialization()
+{
+ QFETCH(bool, trusted);
+ const OpenSSHKey& key = stubkey();
+ KeeShareSettings::Certificate original;
+ original.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Public, key);
+ original.signer = "Some <!> &#_\"\" weird string";
+ original.trusted = trusted;
+
+ QString buffer;
+ QXmlStreamWriter writer(&buffer);
+ writer.writeStartDocument();
+ writer.writeStartElement("Certificate");
+ KeeShareSettings::Certificate::serialize(writer, original);
+ writer.writeEndElement();
+ writer.writeEndDocument();
+ QXmlStreamReader reader(buffer);
+ reader.readNextStartElement();
+ QVERIFY(reader.name() == "Certificate");
+ KeeShareSettings::Certificate restored = KeeShareSettings::Certificate::deserialize(reader);
+
+ QCOMPARE(restored.key, original.key);
+ QCOMPARE(restored.signer, original.signer);
+ QCOMPARE(restored.trusted, original.trusted);
+
+ QCOMPARE(restored.sshKey().publicParts(), key.publicParts());
+}
+
+void TestSharing::testCertificateSerialization_data()
+{
+ QTest::addColumn<bool>("trusted");
+ QTest::newRow("Trusted") << true;
+ QTest::newRow("Untrusted") << false;
+}
+
+void TestSharing::testKeySerialization()
+{
+ const OpenSSHKey& key = stubkey();
+ KeeShareSettings::Key original;
+ original.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Private, key);
+
+ QString buffer;
+ QXmlStreamWriter writer(&buffer);
+ writer.writeStartDocument();
+ writer.writeStartElement("Key");
+ KeeShareSettings::Key::serialize(writer, original);
+ writer.writeEndElement();
+ writer.writeEndDocument();
+ QXmlStreamReader reader(buffer);
+ reader.readNextStartElement();
+ QVERIFY(reader.name() == "Key");
+ KeeShareSettings::Key restored = KeeShareSettings::Key::deserialize(reader);
+
+ QCOMPARE(restored.key, original.key);
+ QCOMPARE(restored.sshKey().privateParts(), key.privateParts());
+ QCOMPARE(restored.sshKey().type(), key.type());
+}
+
+void TestSharing::testReferenceSerialization()
+{
+ QFETCH(QString, password);
+ QFETCH(QString, path);
+ QFETCH(QUuid, uuid);
+ QFETCH(int, type);
+ KeeShareSettings::Reference original;
+ original.password = password;
+ original.path = path;
+ original.uuid = uuid;
+ original.type = static_cast<KeeShareSettings::Type>(type);
+
+ const QString serialized = KeeShareSettings::Reference::serialize(original);
+ const KeeShareSettings::Reference restored = KeeShareSettings::Reference::deserialize(serialized);
+
+ QCOMPARE(restored.password, original.password);
+ QCOMPARE(restored.path, original.path);
+ QCOMPARE(restored.uuid, original.uuid);
+ QCOMPARE(int(restored.type), int(original.type));
+}
+
+void TestSharing::testReferenceSerialization_data()
+{
+ QTest::addColumn<QString>("password");
+ QTest::addColumn<QString>("path");
+ QTest::addColumn<QUuid>("uuid");
+ QTest::addColumn<int>("type");
+ QTest::newRow("1") << "Password" << "/some/path" << QUuid::createUuid() << int(KeeShareSettings::Inactive);
+ QTest::newRow("2") << "" << "" << QUuid() << int(KeeShareSettings::SynchronizeWith);
+ QTest::newRow("3") << "" << "/some/path" << QUuid() << int(KeeShareSettings::ExportTo);
+
+}
+
+void TestSharing::testSettingsSerialization()
+{
+ QFETCH(bool, importing);
+ QFETCH(bool, exporting);
+ QFETCH(KeeShareSettings::Certificate, ownCertificate);
+ QFETCH(KeeShareSettings::Key, ownKey);
+ QFETCH(QList<KeeShareSettings::Certificate>, foreignCertificates);
+
+ KeeShareSettings::Own originalOwn;
+ KeeShareSettings::Foreign originalForeign;
+ KeeShareSettings::Active originalActive;
+ originalActive.in = importing;
+ originalActive.out = exporting;
+ originalOwn.certificate = ownCertificate;
+ originalOwn.key = ownKey;
+ originalForeign.certificates = foreignCertificates;
+
+ const QString serializedActive = KeeShareSettings::Active::serialize(originalActive);
+ KeeShareSettings::Active restoredActive = KeeShareSettings::Active::deserialize(serializedActive);
+
+ const QString serializedOwn = KeeShareSettings::Own::serialize(originalOwn);
+ KeeShareSettings::Own restoredOwn = KeeShareSettings::Own::deserialize(serializedOwn);
+
+ const QString serializedForeign = KeeShareSettings::Foreign::serialize(originalForeign);
+ KeeShareSettings::Foreign restoredForeign = KeeShareSettings::Foreign::deserialize(serializedForeign);
+
+ QCOMPARE(restoredActive.in, importing);
+ QCOMPARE(restoredActive.out, exporting);
+ QCOMPARE(restoredOwn.certificate.key, ownCertificate.key);
+ QCOMPARE(restoredOwn.certificate.trusted, ownCertificate.trusted);
+ QCOMPARE(restoredOwn.key.key, ownKey.key);
+ QCOMPARE(restoredForeign.certificates.count(), foreignCertificates.count());
+ for (int i = 0; i < foreignCertificates.count(); ++i) {
+ QCOMPARE(restoredForeign.certificates[i].key, foreignCertificates[i].key);
+ }
+}
+
+void TestSharing::testSettingsSerialization_data()
+{
+ const OpenSSHKey& sshKey0 = stubkey(0);
+ KeeShareSettings::Certificate certificate0;
+ certificate0.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Public, sshKey0);
+ certificate0.signer = "Some <!> &#_\"\" weird string";
+ certificate0.trusted = true;
+
+ KeeShareSettings::Key key0;
+ key0.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Private, sshKey0);
+
+ const OpenSSHKey& sshKey1 = stubkey(1);
+ KeeShareSettings::Certificate certificate1;
+ certificate1.key = OpenSSHKey::serializeToBinary(OpenSSHKey::Public, sshKey1);
+ certificate1.signer = "Another ";
+ certificate1.trusted = true;
+
+ QTest::addColumn<bool>("importing");
+ QTest::addColumn<bool>("exporting");
+ QTest::addColumn<KeeShareSettings::Certificate>("ownCertificate");
+ QTest::addColumn<KeeShareSettings::Key>("ownKey");
+ QTest::addColumn<QList<KeeShareSettings::Certificate>>("foreignCertificates");
+ QTest::newRow("1") << false << false << KeeShareSettings::Certificate() << KeeShareSettings::Key() << QList<KeeShareSettings::Certificate>();
+ QTest::newRow("2") << true << false << KeeShareSettings::Certificate() << KeeShareSettings::Key() << QList<KeeShareSettings::Certificate>();
+ QTest::newRow("3") << true << true << KeeShareSettings::Certificate() << KeeShareSettings::Key() << QList<KeeShareSettings::Certificate>({ certificate0, certificate1 });
+ QTest::newRow("4") << false << true << certificate0 << key0 << QList<KeeShareSettings::Certificate>();
+ QTest::newRow("5") << false << false << certificate0 << key0 << QList<KeeShareSettings::Certificate>({ certificate1 });
+}
+
+const OpenSSHKey& TestSharing::stubkey(int index)
+{
+ static QMap<int, OpenSSHKey*> keys;
+ if (!keys.contains(index)) {
+ OpenSSHKey* key = new OpenSSHKey(OpenSSHKey::generate(false));
+ key->setParent(this);
+ keys[index] = key;
+ }
+ return *keys[index];
+}
diff --git a/tests/TestSharing.h b/tests/TestSharing.h
new file mode 100644
index 000000000..ebf85fd4e
--- /dev/null
+++ b/tests/TestSharing.h
@@ -0,0 +1,47 @@
+/*
+ * 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_TESTSHARING_H
+#define KEEPASSXC_TESTSHARING_H
+
+#include <QObject>
+#include <QSharedPointer>
+
+class OpenSSHKey;
+
+class TestSharing : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+ void cleanupTestCase();
+ void testIdempotentDatabaseWriting();
+ void testNullObjects();
+ void testCertificateSerialization();
+ void testCertificateSerialization_data();
+ void testKeySerialization();
+ void testReferenceSerialization();
+ void testReferenceSerialization_data();
+ void testSettingsSerialization();
+ void testSettingsSerialization_data();
+
+private:
+ const OpenSSHKey& stubkey(int iIndex = 0);
+};
+
+#endif // KEEPASSXC_TESTSHARING_H
diff --git a/tests/TestSignature.cpp b/tests/TestSignature.cpp
new file mode 100644
index 000000000..887143a85
--- /dev/null
+++ b/tests/TestSignature.cpp
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2010 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 "TestSignature.h"
+#include "TestGlobal.h"
+
+#include <QBuffer>
+
+#include "crypto/Crypto.h"
+#include "crypto/ssh/OpenSSHKey.h"
+#include "keeshare/Signature.h"
+
+QTEST_GUILESS_MAIN(TestSignature)
+static const char* rsa_2_private = "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEowIBAAKCAQEAwGdladnqFfcDy02Gubx4sdBT8NYEg2YKXfcKLSwca5gV4X7I\n"
+ "z/+QR51LAfPCkj+QjWpj3DD1/6P7s6jOJ4BNd6CSDukv18DOsIsFn2D+zLmVoir2\n"
+ "lki3sTsmiEz65KvHE8EnQ8IzZCqZDC40tZOcz2bnkZrmcsEibKoxYsmQJk95NwdR\n"
+ "teFymp1qH3zq85xdNWw2u6c5CKzLgI5BjInttO98iSxL0KuY/JmzMx0gTbRiqc0x\n"
+ "22EODtdVsBoNL/pt8v6Q+WLpRg4/Yq7YurAngxk4h38NWvufj2vJvbcRqX4cupcu\n"
+ "92T9SWwSwZmd4Xy3bt+AUlq4XRMa1MlKfPvXmwIDAQABAoIBAGbWnRD/xaup1OBU\n"
+ "dr9N6qD3/fXLHqxw3PeudEUCv8oOhxt43bK3IZH1k8LeXFA5I3VCuU9W6BWUu5Mi\n"
+ "ldXtMPrQ22CW6NiEGLWqCP5QJMCeLUl5d0WKZoyXVhgiNTQGUKjRY8BGy5stXZJy\n"
+ "HAA1fuooUXu09Jm/ezvjl/P6Uk722nZns4g6cc8aUSQDSVoeuCvwDaix5o4Z4RGY\n"
+ "4biIKGj5qYxoe+rbgYH/2zlEcAiSJIuqjYY+Xk4IdB89DYZBYnO/xPkRaDeiY2xl\n"
+ "QM7Inr7PQC8PWJc9zYYvlGnnmIRCkO15mWau70N1Y1rUAsyW61g2GyFhdsIIODH/\n"
+ "878Kc9ECgYEA+2JaUqRWr6dqE+uVPpGkbGiAaRQ79olTcRmxXCnM+Y3c88z9G7kC\n"
+ "2S5UKPRDl7EzwmMJqqb8BZbdSWoAxO4++F6ylSz7TqowPw+13Wxwm3wApvr2Q1Mo\n"
+ "rkq4ltgyHMR+iXvKqOYa2GqZNmRwh7JGLIJ7Y0Z77nwBkkgDc/3ey8MCgYEAw+/N\n"
+ "fxv2t+r6VKxEtjdy3sfn8LLjiWqghPngJzcYH9NdB8fmMN1WHqX075hbKjI9TyJw\n"
+ "77p8onjZI0opLexHHUmepEa6Ijo1zynJJ7XPXnyruiTXXqz49io6lFOLcXi/i+DZ\n"
+ "B2vQcMGWA4qwJxz7KA6EZ/HimjuysV1guvlKf0kCgYA6+JGTvXWQc0eRMLysFuJp\n"
+ "hAJLpDGE3iYy7AINSskI6dyhXL8rl7UxWYroqJSKq0knGrCT1eRdM0zqAfH4QKOJ\n"
+ "BD4EfK7ff1EeGgNh1CR+dRJ6GXlXxdRPPrwattDaqsW8Xsvl30UA69DRT7KOQqXv\n"
+ "nxRu74P3KCP+OuKEfVOcnQKBgQC+/2r1Zj/5huBhW9BbQ/ABBSOuueMeGEfDeIUu\n"
+ "FQG6PGKqbA2TQp9pnuMGECGGH5UuJ+epeMN36Y/ZW7iKoJGuFg7EGoHlTZMYj6Yb\n"
+ "xJoRhDwuZy1eiATkicOyxUHf6hHme9dz6YA1+i+O4knWxuR5ZrVhUiRPrrQBO4JI\n"
+ "oSwiqQKBgHblgOVfOJrG3HDg6bo+qmxQsGFRCD0mehsg9YpokuZVX0UJtGx/RJHU\n"
+ "vIBL00An6YcNfPTSlNJeG83APtk/tdgsXvQd3pmIkeY78x6xWKSieZXv4lyDv7lX\n"
+ "r28lCTj2Ez2gEzEohZgf4V1uzBvTdJefarpQ00ep34UZ9FsNfUwD\n"
+ "-----END RSA PRIVATE KEY-----\n";
+static const char* rsa_2_public = "-----BEGIN RSA PUBLIC KEY-----\n"
+ "MIIBCgKCAQEAwGdladnqFfcDy02Gubx4sdBT8NYEg2YKXfcKLSwca5gV4X7Iz/+Q\n"
+ "R51LAfPCkj+QjWpj3DD1/6P7s6jOJ4BNd6CSDukv18DOsIsFn2D+zLmVoir2lki3\n"
+ "sTsmiEz65KvHE8EnQ8IzZCqZDC40tZOcz2bnkZrmcsEibKoxYsmQJk95NwdRteFy\n"
+ "mp1qH3zq85xdNWw2u6c5CKzLgI5BjInttO98iSxL0KuY/JmzMx0gTbRiqc0x22EO\n"
+ "DtdVsBoNL/pt8v6Q+WLpRg4/Yq7YurAngxk4h38NWvufj2vJvbcRqX4cupcu92T9\n"
+ "SWwSwZmd4Xy3bt+AUlq4XRMa1MlKfPvXmwIDAQAB\n"
+ "-----END RSA PUBLIC KEY-----\n";
+static const char* rsa_2_sign = "4fda1b39f93f174cdc79ac2bd6118155359830c90e2c39b60a1a548852f2c87a"
+ "cd61b2a378259a38befad35dbf208a2c1332ab74faf2cee2ff2e8be49c4c5f41"
+ "dc10e5a5fafb53d3c54e2b7640d7bfee6bb0f24c5a1fb934150a144c2b465fe4"
+ "8a1579e666a097fb1609ae9abc5760f6e6d6e73acb610fb11dd1c409ca284a72"
+ "0be64dd56a28ab257e8721f5bade58816382581ac08d932098dd200d836fe897"
+ "f78a5f02095ac3b21cca2a47b2afd282ce075c6450cba8c85b08b58c5bacb75d"
+ "e1a73bdec4321193d4a3ce653d8e3aa8a4f2beac6a44497328f8855f7e28e15d"
+ "f63b21f8bc7204bf6e202c9cb08be050379be5ad88d8e695a38440a50e75dfdf";
+static const char* rsa_1_private = "-----BEGIN RSA PRIVATE KEY-----\n"
+ "MIIEpAIBAAKCAQEAsCHtJicDPWnvHSIKbnTZaJkIB9vgE0pmLdK580JUqBuonVbB\n"
+ "y1QTy0ZQ7/TtqvLPgwPK88TR46OLO/QGCzo2+XxgJ85uy0xfuyUYRmSuw0drsErN\n"
+ "mH8vU91lSBxsGDp9LtBbgHKoR23vMWZ34IxFRc55XphrIH48ijsMaL6bXBwF/3tD\n"
+ "9T3lm2MpP1huyVNnIY9+GRRWCy4f9LMj/UGu/n4RtwwfpOZBBRwYkq5QkzA9lPm/\n"
+ "VzF3MP1rKTMkvAw+Nfb383mkmc6MRnsa6uh6iDa9aVB7naegM13UJQX/PY1Ks6pO\n"
+ "XDpy/MQ7iCh+HmYNq5dRmARyaNl9xIXJNhz1cQIDAQABAoIBAQCnEUc1LUQxeM5K\n"
+ "wANNCqE+SgoIClPdeHC7fmrLh1ttqe6ib6ybBUFRS31yXs0hnfefunVEDKlaV8K2\n"
+ "N52UAMAsngFHQNRvGh6kEWeZPd9Xc+N98TZbNCjcT+DGKc+Om8wqH5DrodZlCq4c\n"
+ "GaoT4HnE4TjWtZTH2XXrWF9I66PKFWf070R44nvyVcvaZi4pC2YmURRPuGF6K1iK\n"
+ "dH8zM6HHG1UGu2W6hLNn+K01IulG0Lb8eWNaNYMmtQWaxyp7I2IWkkecUs3nCuiR\n"
+ "byFOoomCjdh8r9yZFvwxjGUhgtkALN9GCU0Mwve+s11IB2gevruN+q9/Qejbyfdm\n"
+ "IlgLAeTRAoGBANRcVzW9CYeobCf+U9hKJFEOur8XO+J2mTMaELA0EjWpTJFAeIT7\n"
+ "KeRpCRG4/vOSklxxRF6vP1EACA4Z+5BlN+FTipHHs+bSEgqkPZiiANDH7Zot5Iqv\n"
+ "1q0fRyldNRZNZK7DWp08BPNVWGA/EnEuKJiURxnxBaxNXbUyMCdjxvMvAoGBANRT\n"
+ "utbrqS/bAa/DcHKn3V6DRqBl3TDOfvCNjiKC84a67F2uXgzLIdMktr4d1NyCZVJd\n"
+ "7/zVgWORLIdg1eAi6rYGoOvNV39wwga7CF+m9sBY0wAaKYCELe6L26r4aQHVCX6n\n"
+ "rnIgUv+4o4itmU2iP0r3wlmDC9pDRQP82vfvQPlfAoGASwhleANW/quvq2HdViq8\n"
+ "Mje2HBalfhrRfpDTHK8JUBSFjTzuWG42GxJRtgVbb8x2ElujAKGDCaetMO5VSGu7\n"
+ "Fs5hw6iAFCpdXY0yhl+XUi2R8kwM2EPQ4lKO3jqkq0ClNmqn9a5jQWcCVt9yMLNS\n"
+ "fLbHeI8EpiCf34ngIcrLXNkCgYEAzlcEZuKkC46xB+dNew8pMTUwSKZVm53BfPKD\n"
+ "44QRN6imFbBjU9mAaJnwQbfp6dWKs834cGPolyM4++MeVfB42iZ88ksesgmZdUMD\n"
+ "szkl6O0pOJs0I+HQZVdjRbadDZvD22MHQ3+oST1dJ3FVXz3Cdo9qPuT8esMO6f4r\n"
+ "qfDH2s8CgYAXC/lWWHQ//PGP0pH4oiEXisx1K0X1u0xMGgrChxBRGRiKZUwNMIvJ\n"
+ "TqUu7IKizK19cLHF/NBvxHYHFw+m7puNjn6T1RtRCUjRZT7Dx1VHfVosL9ih5DA8\n"
+ "tpbZA5KGKcvHtB5DDgT0MHwzBZnb4Q//Rhovzn+HXZPsJTTgHHy3NQ==\n"
+ "-----END RSA PRIVATE KEY-----\n";
+static const char* rsa_1_public = "-----BEGIN RSA PUBLIC KEY-----\n"
+ "MIIBCgKCAQEAsCHtJicDPWnvHSIKbnTZaJkIB9vgE0pmLdK580JUqBuonVbBy1QT\n"
+ "y0ZQ7/TtqvLPgwPK88TR46OLO/QGCzo2+XxgJ85uy0xfuyUYRmSuw0drsErNmH8v\n"
+ "U91lSBxsGDp9LtBbgHKoR23vMWZ34IxFRc55XphrIH48ijsMaL6bXBwF/3tD9T3l\n"
+ "m2MpP1huyVNnIY9+GRRWCy4f9LMj/UGu/n4RtwwfpOZBBRwYkq5QkzA9lPm/VzF3\n"
+ "MP1rKTMkvAw+Nfb383mkmc6MRnsa6uh6iDa9aVB7naegM13UJQX/PY1Ks6pOXDpy\n"
+ "/MQ7iCh+HmYNq5dRmARyaNl9xIXJNhz1cQIDAQAB\n"
+ "-----END RSA PUBLIC KEY-----\n";
+
+static QByteArray data("Some trivial test with a longer .... ................................. longer text");
+
+void TestSignature::initTestCase()
+{
+ QVERIFY(Crypto::init());
+}
+
+void TestSignature::testSigningOpenSSH_RSA_PrivateOnly()
+{
+ OpenSSHKey privateKey;
+ privateKey.parsePKCS1PEM(rsa_2_private);
+ privateKey.openKey(QString());
+ QCOMPARE(privateKey.fingerprint(), QString("SHA256:ZAQ/W1QdW59OaIh/0hs3ePl2og5TjXnGX5L0iN7WtNA"));
+ Signature signer;
+ const QString sign = signer.create(data, privateKey);
+ QVERIFY(!sign.isEmpty());
+
+ QCOMPARE(sign, QString("rsa|%1").arg(QString::fromLatin1(rsa_2_sign)));
+
+ Signature verifier;
+ const bool verified = verifier.verify(data, sign, privateKey);
+ QCOMPARE(verified, true);
+}
+
+void TestSignature::testSigningOpenSSH_RSA()
+{
+ OpenSSHKey privateKey;
+ privateKey.parsePKCS1PEM(rsa_2_private);
+ privateKey.openKey(QString());
+ QCOMPARE(privateKey.fingerprint(), QString("SHA256:ZAQ/W1QdW59OaIh/0hs3ePl2og5TjXnGX5L0iN7WtNA"));
+ Signature signer;
+ const QString sign = signer.create(data, privateKey);
+ QVERIFY(!sign.isEmpty());
+
+ OpenSSHKey publicKey;
+ publicKey.parsePKCS1PEM(rsa_2_public);
+ publicKey.openKey(QString());
+ QCOMPARE(publicKey.fingerprint(), QString("SHA256:ZAQ/W1QdW59OaIh/0hs3ePl2og5TjXnGX5L0iN7WtNA"));
+
+ Signature verifier;
+ const bool verified = verifier.verify(data, sign, publicKey);
+ QCOMPARE(verified, true);
+}
+
+void TestSignature::testSigningGenerated_RSA_PrivateOnly()
+{
+ OpenSSHKey privateKey = OpenSSHKey::generate(false);
+ privateKey.openKey(QString());
+
+ Signature signer;
+ const QString sign = signer.create(data, privateKey);
+ QVERIFY(!sign.isEmpty());
+
+ Signature verifier;
+ const bool verified = verifier.verify(data, sign, privateKey);
+ QCOMPARE(verified, true);
+}
+
+void TestSignature::testSigningTest_RSA_PrivateOnly()
+{
+ OpenSSHKey privateKey;
+ privateKey.parsePKCS1PEM(rsa_1_private);
+ privateKey.openKey(QString());
+ QCOMPARE(privateKey.fingerprint(), QString("SHA256:DYdaZciYNxCejr+/8x+OKYxeTU1D5UsuIFUG4PWRFkk"));
+ Signature signer;
+ const QString sign = signer.create(data, privateKey);
+ QVERIFY(!sign.isEmpty());
+
+ Signature verifier;
+ const bool verified = verifier.verify(data, sign, privateKey);
+ QCOMPARE(verified, true);
+}
+
+void TestSignature::testSigningTest_RSA()
+{
+ OpenSSHKey privateKey;
+ privateKey.parsePKCS1PEM(rsa_1_private);
+ privateKey.openKey(QString());
+ QCOMPARE(privateKey.fingerprint(), QString("SHA256:DYdaZciYNxCejr+/8x+OKYxeTU1D5UsuIFUG4PWRFkk"));
+ Signature signer;
+ const QString sign = signer.create(data, privateKey);
+ QVERIFY(!sign.isEmpty());
+
+ OpenSSHKey publicKey;
+ publicKey.parsePKCS1PEM(rsa_1_public);
+ publicKey.openKey(QString());
+ QCOMPARE(publicKey.fingerprint(), QString("SHA256:DYdaZciYNxCejr+/8x+OKYxeTU1D5UsuIFUG4PWRFkk"));
+ Signature verifier;
+ const bool verified = verifier.verify(data, sign, publicKey);
+ QCOMPARE(verified, true);
+}
diff --git a/tests/TestSignature.h b/tests/TestSignature.h
new file mode 100644
index 000000000..38e576462
--- /dev/null
+++ b/tests/TestSignature.h
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2010 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/>.
+ */
+
+#ifndef KEEPASSXC_TESTSIGNATURE_H
+#define KEEPASSXC_TESTSIGNATURE_H
+
+#include <QObject>
+
+class TestSignature : public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void initTestCase();
+
+ void testSigningOpenSSH_RSA_PrivateOnly();
+ void testSigningOpenSSH_RSA();
+
+ void testSigningGenerated_RSA_PrivateOnly();
+
+ void testSigningTest_RSA_PrivateOnly();
+ void testSigningTest_RSA();
+};
+
+#endif // KEEPASSX_TESTSIGNATURE_H
diff --git a/tests/stub/TestRandom.cpp b/tests/stub/TestRandom.cpp
new file mode 100644
index 000000000..d33b1c9b8
--- /dev/null
+++ b/tests/stub/TestRandom.cpp
@@ -0,0 +1,62 @@
+/*
+ * 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 "TestRandom.h"
+#include "TestGlobal.h"
+
+RandomBackendPreset::RandomBackendPreset()
+ : m_bytesIndex(0)
+{
+}
+
+void RandomBackendPreset::randomize(void* data, int len)
+{
+ QVERIFY(len <= (m_nextBytes.size() - m_bytesIndex));
+
+ char* charData = reinterpret_cast<char*>(data);
+
+ for (int i = 0; i < len; i++) {
+ charData[i] = m_nextBytes[m_bytesIndex + i];
+ }
+
+ m_bytesIndex += len;
+}
+
+void RandomBackendPreset::setNextBytes(const QByteArray& nextBytes)
+{
+ m_nextBytes = nextBytes;
+ m_bytesIndex = 0;
+}
+
+void TestRandom::setup(RandomBackend* backend)
+{
+ Random::setInstance(backend);
+}
+
+void TestRandom::teardown()
+{
+ Random::resetInstance();
+}
+
+void RandomBackendNull::randomize(void* data, int len)
+{
+ char* charData = reinterpret_cast<char*>(data);
+
+ for (int i = 0; i < len; i++) {
+ charData[i] = '\0';
+ }
+}
diff --git a/tests/stub/TestRandom.h b/tests/stub/TestRandom.h
new file mode 100644
index 000000000..ec5afb59d
--- /dev/null
+++ b/tests/stub/TestRandom.h
@@ -0,0 +1,48 @@
+/*
+ * 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_TESTRANDOM_H
+#define KEEPASSXC_TESTRANDOM_H
+
+#include "crypto/Random.h"
+
+class RandomBackendPreset : public RandomBackend
+{
+public:
+ RandomBackendPreset();
+ void randomize(void* data, int len) override;
+ void setNextBytes(const QByteArray& nextBytes);
+
+private:
+ QByteArray m_nextBytes;
+ int m_bytesIndex;
+};
+
+class RandomBackendNull : public RandomBackend
+{
+public:
+ void randomize(void* data, int len) override;
+};
+
+class TestRandom : public Random
+{
+public:
+ static void setup(RandomBackend* backend);
+ static void teardown();
+};
+
+#endif // KEEPASSXC_TESTRANDOM_H