Merge pull request #2109 from hicknhack-software/feature/sharing_groups
Implement group synchronization feature
@ -47,6 +47,8 @@ option(WITH_XC_NETWORKING "Include networking code (e.g. for downlading website
|
||||
option(WITH_XC_BROWSER "Include browser integration with keepassxc-browser." OFF)
|
||||
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
|
||||
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
|
||||
option(WITH_XC_KEESHARE "Sharing integration with KeeShare" OFF)
|
||||
option(WITH_XC_KEESHARE_SECURE "Sharing integration with secured KeeShare containers" OFF)
|
||||
if(APPLE)
|
||||
option(WITH_XC_TOUCHID "Include TouchID support for macOS." OFF)
|
||||
endif()
|
||||
@ -58,9 +60,20 @@ if(WITH_XC_ALL)
|
||||
set(WITH_XC_BROWSER ON)
|
||||
set(WITH_XC_YUBIKEY ON)
|
||||
set(WITH_XC_SSHAGENT ON)
|
||||
if(APPLE)
|
||||
set(WITH_XC_TOUCHID ON)
|
||||
endif()
|
||||
set(WITH_XC_KEESHARE ON)
|
||||
if(APPLE)
|
||||
set(WITH_XC_TOUCHID ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(WITH_XC_KEESHARE_SECURE)
|
||||
set(WITH_XC_KEESHARE ON)
|
||||
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")
|
||||
@ -349,6 +362,19 @@ endif()
|
||||
|
||||
include_directories(SYSTEM ${ARGON2_INCLUDE_DIR})
|
||||
|
||||
# Optional
|
||||
if(WITH_XC_KEESHARE)
|
||||
set(WITH_XC_KEESHARE_INSECURE ON)
|
||||
if(WITH_XC_KEESHARE_SECURE)
|
||||
# ZLIB is needed and already required
|
||||
find_package(QuaZip REQUIRED)
|
||||
include_directories(SYSTEM ${QUAZIP_INCLUDE_DIR})
|
||||
endif()
|
||||
else()
|
||||
set(WITH_XC_KEESHARE_INSECURE OFF)
|
||||
set(WITH_XC_KEESHARE_SECURE OFF)
|
||||
endif()
|
||||
|
||||
# Optional
|
||||
if(WITH_XC_YUBIKEY)
|
||||
find_package(YubiKey REQUIRED)
|
||||
|
@ -54,6 +54,8 @@ RUN set -x \
|
||||
zlib1g-dev \
|
||||
libxi-dev \
|
||||
libxtst-dev \
|
||||
# ubuntu:14.04 has no quazip (it's optional)
|
||||
# libquazip5-dev \
|
||||
mesa-common-dev \
|
||||
libyubikey-dev \
|
||||
libykpers-1-dev \
|
||||
|
10
INSTALL.md
@ -98,15 +98,19 @@ 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_KEESHARE=[ON|OFF] Enable/Disable KeeShare group syncronization 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_XC_KEESHARE_SECURE=[ON|OFF] Enable/Disable KeeShare secure containers, requires libquazip5 (default: OFF)
|
||||
|
||||
-DWITH_TESTS=[ON|OFF] Enable/Disable building of unit tests (default: ON)
|
||||
-DWITH_GUI_TESTS=[ON|OFF] Enable/Disable building of GUI tests (default: OFF)
|
||||
-DWITH_DEV_BUILD=[ON|OFF] Enable/Disable deprecated method warnings (default: OFF)
|
||||
-DWITH_ASAN=[ON|OFF] Enable/Disable address sanitizer checks (Linux / macOS only) (default: OFF)
|
||||
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
|
||||
-DWITH_COVERAGE=[ON|OFF] Enable/Disable coverage tests (GCC only) (default: OFF)
|
||||
-DWITH_APP_BUNDLE=[ON|OFF] Enable Application Bundle for macOS (default: ON)
|
||||
```
|
||||
|
||||
* If you are on MacOS you must add this parameter to **Cmake**, with the Qt version you have installed<br/> `-DCMAKE_PREFIX_PATH=/usr/local/Cellar/qt5/5.6.2/lib/cmake/`
|
||||
|
@ -1,7 +1,6 @@
|
||||
# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC
|
||||
[![TeamCity Build Status](https://ci.keepassxc.org/app/rest/builds/buildType:\(project:KeepassXC\)/statusIcon)](https://ci.keepassxc.org/?guest=1) [![codecov](https://codecov.io/gh/keepassxreboot/keepassxc/branch/develop/graph/badge.svg)](https://codecov.io/gh/keepassxreboot/keepassxc)
|
||||
|
||||
|
||||
## About KeePassXC
|
||||
[KeePassXC](https://keepassxc.org) is a cross-platform community fork of
|
||||
[KeePassX](https://www.keepassx.org/).
|
||||
@ -35,6 +34,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)
|
||||
- Synchronize 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.
|
||||
|
@ -54,6 +54,8 @@ RUN set -x \
|
||||
zlib1g-dev \
|
||||
libyubikey-dev \
|
||||
libykpers-1-dev \
|
||||
# ubuntu:14.04 has no quazip (it's optional)
|
||||
# libquazip5-dev \
|
||||
libxi-dev \
|
||||
libxtst-dev \
|
||||
libqrencode-dev \
|
||||
|
@ -28,9 +28,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
|
||||
|
41
cmake/FindQuaZip.cmake
Normal file
@ -0,0 +1,41 @@
|
||||
# QUAZIP_FOUND - QuaZip library was found
|
||||
# QUAZIP_INCLUDE_DIR - Path to QuaZip include dir
|
||||
# QUAZIP_INCLUDE_DIRS - Path to QuaZip and zlib include dir (combined from QUAZIP_INCLUDE_DIR + ZLIB_INCLUDE_DIR)
|
||||
# QUAZIP_LIBRARIES - List of QuaZip libraries
|
||||
# QUAZIP_ZLIB_INCLUDE_DIR - The include dir of zlib headers
|
||||
|
||||
IF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)
|
||||
# in cache already
|
||||
SET(QUAZIP_FOUND TRUE)
|
||||
ELSE(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)
|
||||
IF(Qt5Core_FOUND)
|
||||
set(QUAZIP_LIB_VERSION_SUFFIX 5)
|
||||
ENDIF()
|
||||
IF(WIN32)
|
||||
FIND_PATH(QUAZIP_LIBRARY_DIR
|
||||
WIN32_DEBUG_POSTFIX d
|
||||
NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll
|
||||
HINTS "C:/Programme/" "C:/Program Files"
|
||||
PATH_SUFFIXES QuaZip/lib
|
||||
)
|
||||
FIND_LIBRARY(QUAZIP_LIBRARIES NAMES libquazip${QUAZIP_LIB_VERSION_SUFFIX}.dll HINTS ${QUAZIP_LIBRARY_DIR})
|
||||
FIND_PATH(QUAZIP_INCLUDE_DIR NAMES quazip.h HINTS ${QUAZIP_LIBRARY_DIR}/../ PATH_SUFFIXES include/quazip5)
|
||||
FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR NAMES zlib.h)
|
||||
ELSE(WIN32)
|
||||
FIND_PACKAGE(PkgConfig)
|
||||
pkg_check_modules(PC_QUAZIP quazip)
|
||||
FIND_LIBRARY(QUAZIP_LIBRARIES
|
||||
WIN32_DEBUG_POSTFIX d
|
||||
NAMES quazip${QUAZIP_LIB_VERSION_SUFFIX}
|
||||
HINTS /usr/lib /usr/lib64
|
||||
)
|
||||
FIND_PATH(QUAZIP_INCLUDE_DIR quazip.h
|
||||
HINTS /usr/include /usr/local/include
|
||||
PATH_SUFFIXES quazip${QUAZIP_LIB_VERSION_SUFFIX}
|
||||
)
|
||||
FIND_PATH(QUAZIP_ZLIB_INCLUDE_DIR zlib.h HINTS /usr/include /usr/local/include)
|
||||
ENDIF(WIN32)
|
||||
INCLUDE(FindPackageHandleStandardArgs)
|
||||
SET(QUAZIP_INCLUDE_DIRS ${QUAZIP_INCLUDE_DIR} ${QUAZIP_ZLIB_INCLUDE_DIR})
|
||||
find_package_handle_standard_args(QUAZIP DEFAULT_MSG QUAZIP_LIBRARIES QUAZIP_INCLUDE_DIR QUAZIP_ZLIB_INCLUDE_DIR QUAZIP_INCLUDE_DIRS)
|
||||
ENDIF(QUAZIP_INCLUDE_DIRS AND QUAZIP_LIBRARIES)
|
BIN
docs/KeePassHTTP/KeePassXC-Accept-Button.png
Normal file
After Width: | Height: | Size: 31 KiB |
BIN
docs/KeePassHTTP/KeePassXC-Confirm.png
Normal file
After Width: | Height: | Size: 18 KiB |
BIN
docs/KeePassHTTP/KeePassXC-Connect.png
Normal file
After Width: | Height: | Size: 25 KiB |
Before Width: | Height: | Size: 97 KiB |
Before Width: | Height: | Size: 71 KiB |
Before Width: | Height: | Size: 101 KiB |
BIN
docs/KeeShare/AppSettings.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
docs/KeeShare/DatabaseSettings.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
docs/KeeShare/GroupSettings_Export.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
docs/KeeShare/GroupSettings_Import.png
Normal file
After Width: | Height: | Size: 30 KiB |
BIN
docs/KeeShare/GroupSettings_Sync.png
Normal file
After Width: | Height: | Size: 30 KiB |
@ -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 or Tor Browser](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,87 @@ 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 for the application.
|
||||
|
||||
1. Go to Tools → Settings
|
||||
2. Select the category KeeShare
|
||||
3. Check _Allow import_ if you want to import shared credentials
|
||||
4. Check _Allow export_ if you want to share credentials
|
||||
|
||||
To make sure that your data is valid when im imported by another client, please _generate_ (or _import_) a public/private key pair and enter your _signer_ name. This way your client may verify that the imported data is valid. When Importing, you'll see the known sources with names and fingerprint in the list at the bottom. This is the place to _trust_ or _untrust_ signers. It is only possible to trust someone on application level.
|
||||
|
||||
<img src="./KeeShare/AppSettings.png" height="600" width="800" alt="KeeShare Application 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 container
|
||||
|
||||
The export file will not be generated automatically. Instead, each time the database is saved, the file gets written (so please deactivate the autosafe feature). 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/GroupSettings_Export.png" height="600" width="800" alt="KeeShare 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 immediately 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 share container that is shared with you
|
||||
1. Enter the password for the shared container
|
||||
|
||||
KeeShare observes the container for changes and merges them into your database when necessary. Importing merges in time order, so older data is moved to the history, which should have a sufficient size to prevent loss of needed data.
|
||||
|
||||
Please note, that the import currently is not restricted to the configured group. Every entry which was imported and moved outside the import group will be updated regardless of it's location!
|
||||
|
||||
<img src="./KeeShare/GroupSettings_Import.png" height="600" width="800" alt="KeeShare 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/GroupSettings_Sync.png" height="600" width="800" alt="KeeShare Group Synchronization Settings">
|
||||
|
||||
### Disable Sharing for Credentials
|
||||
|
||||
In case you don't want to share (import or export) some credentials, it is possible to you can
|
||||
* use the application settings and uncheck the options or
|
||||
* instead of selecting _Import from path_, _Export to path_ or _Synchronize with path_ you'll select _Inactive_ while leaving the path and the password untouched
|
||||
|
||||
### Sharing overview
|
||||
|
||||
There is a simple overview of shared groups to keep track of your data.
|
||||
|
||||
1. Open the Database Settings
|
||||
1. Select the KeeShare category
|
||||
|
||||
<img src="./KeeShare/DatabaseSettings.png" height="600" width="800" alt="KeeShare Group Sharing Ovewview">
|
||||
|
||||
## Technical Details and Limitations of Sharing
|
||||
|
||||
Sharing relies on the combination of file exports and imports as well as the synchronization mechanism provided by KeePassXC. Since the merge algorithm uses the history of entries to prevent data loss, this history must be enabled and have a sufficient size. Furthermore, the merge algorithm is location independend, therefore it does not matter if entries are moved outside of an import group. These entries will be updated none the less. Moving entries outside of export groups will prevent a further export of the entry, but it will not ensure that the already shared data will be removed from any client.
|
||||
|
||||
KeeShare uses a custom certification mechanism to ensure that the source of the data is the expected one. This ensures that the data was exported by the signer but it is not possible to detect if someone replaced the data with an older version from a valid signer. To prevent this, the container could be placed at a location which is only writeable for valid signers.
|
||||
|
||||
|
||||
|
After Width: | Height: | Size: 13 KiB |
@ -50,6 +50,7 @@ set(keepassx_SOURCES
|
||||
core/EntryAttributes.cpp
|
||||
core/EntrySearcher.cpp
|
||||
core/FilePath.cpp
|
||||
core/FileWatcher.cpp
|
||||
core/Bootstrap.cpp
|
||||
core/Group.cpp
|
||||
core/InactivityTimer.cpp
|
||||
@ -201,6 +202,8 @@ add_feature_info(Auto-Type WITH_XC_AUTOTYPE "Automatic password typing")
|
||||
add_feature_info(Networking WITH_XC_NETWORKING "Compile KeePassXC with network access code (e.g. for downloading website icons)")
|
||||
add_feature_info(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(KeeShare-Secure WITH_XC_KEESHARE_SECURE "Sharing integration with KeeShare with secure sources")
|
||||
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
|
||||
if(APPLE)
|
||||
add_feature_info(TouchID WITH_XC_TOUCHID "TouchID integration")
|
||||
@ -219,6 +222,16 @@ add_subdirectory(cli)
|
||||
add_subdirectory(qrcode)
|
||||
set(qrcode_LIB qrcode)
|
||||
|
||||
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)
|
||||
@ -257,21 +270,24 @@ set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUIL
|
||||
target_link_libraries(keepassx_core
|
||||
autotype
|
||||
${keepassxcbrowser_LIB}
|
||||
${sshagent_LIB}
|
||||
${qrcode_LIB}
|
||||
Qt5::Core
|
||||
Qt5::Concurrent
|
||||
Qt5::Network
|
||||
Qt5::Widgets
|
||||
${CURL_LIBRARIES}
|
||||
${YUBIKEY_LIBRARIES}
|
||||
${ZXCVBN_LIBRARIES}
|
||||
${ARGON2_LIBRARIES}
|
||||
${GCRYPT_LIBRARIES}
|
||||
${GPGERROR_LIBRARIES}
|
||||
${YUBIKEY_LIBRARIES}
|
||||
${ZLIB_LIBRARIES}
|
||||
${ZXCVBN_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 -framework AppKit")
|
||||
|
@ -17,6 +17,9 @@
|
||||
#cmakedefine WITH_XC_BROWSER
|
||||
#cmakedefine WITH_XC_YUBIKEY
|
||||
#cmakedefine WITH_XC_SSHAGENT
|
||||
#cmakedefine WITH_XC_KEESHARE
|
||||
#cmakedefine WITH_XC_KEESHARE_INSECURE
|
||||
#cmakedefine WITH_XC_KEESHARE_SECURE
|
||||
#cmakedefine WITH_XC_TOUCHID
|
||||
|
||||
#cmakedefine KEEPASSXC_BUILD_TYPE "@KEEPASSXC_BUILD_TYPE@"
|
||||
|
@ -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)
|
||||
|
@ -65,7 +65,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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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);
|
||||
|
@ -472,8 +472,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 false;
|
||||
}
|
||||
|
||||
void Database::setCipher(const QUuid& cipher)
|
||||
|
@ -24,6 +24,7 @@
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "crypto/kdf/Kdf.h"
|
||||
#include "format/KeePass2.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
|
@ -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[] = {
|
||||
|
@ -33,6 +33,8 @@ public:
|
||||
|
||||
static const int IconCount;
|
||||
static const int ExpiredIconIndex;
|
||||
static const int SharedIconIndex;
|
||||
static const int UnsharedIconIndex;
|
||||
|
||||
private:
|
||||
DatabaseIcons();
|
||||
|
@ -1027,6 +1027,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()->rootGroup()->findEntryBySearchTerm(searchText, searchInType);
|
||||
}
|
||||
|
||||
QString Entry::resolveMultiplePlaceholders(const QString& str) const
|
||||
{
|
||||
return resolveMultiplePlaceholdersRecursive(str, ResolveMaximumDepth);
|
||||
|
@ -201,6 +201,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;
|
||||
|
295
src/core/FileWatcher.cpp
Normal file
@ -0,0 +1,295 @@
|
||||
/*
|
||||
* 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()), this, 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()));
|
||||
connect(&m_pendingSignalsTimer, SIGNAL(timeout()), this, SLOT(emitSignals()));
|
||||
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_watchedPaths.clear();
|
||||
m_watchedFilesInDirectory.clear();
|
||||
m_ignoreFilesChangess.clear();
|
||||
}
|
||||
|
||||
void BulkFileWatcher::removePath(const QString& path)
|
||||
{
|
||||
const QFileInfo info(path);
|
||||
const QString filePath = info.absoluteFilePath();
|
||||
const QString directoryPath = info.absolutePath();
|
||||
m_watchedFilesInDirectory[directoryPath].remove(filePath);
|
||||
m_fileWatcher.removePath(filePath);
|
||||
m_watchedPaths.remove(filePath);
|
||||
if (m_watchedFilesInDirectory[directoryPath].isEmpty()) {
|
||||
m_fileWatcher.removePath(directoryPath);
|
||||
m_watchedPaths.remove(directoryPath);
|
||||
m_watchedFilesInDirectory.remove(directoryPath);
|
||||
}
|
||||
}
|
||||
|
||||
void BulkFileWatcher::addPath(const QString& path)
|
||||
{
|
||||
const QFileInfo info(path);
|
||||
const QString filePath = info.absoluteFilePath();
|
||||
const QString directoryPath = info.absolutePath();
|
||||
if (!m_watchedPaths.value(filePath)) {
|
||||
const bool fileSuccess = m_fileWatcher.addPath(filePath);
|
||||
m_watchedPaths[filePath] = fileSuccess;
|
||||
}
|
||||
if (!m_watchedPaths.value(directoryPath)) {
|
||||
const bool directorySuccess = m_fileWatcher.addPath(directoryPath);
|
||||
m_watchedPaths[directoryPath] = directorySuccess;
|
||||
}
|
||||
m_watchedFilesInDirectory[directoryPath][filePath] = info.exists();
|
||||
}
|
||||
|
||||
void BulkFileWatcher::handleFileChanged(const QString& path)
|
||||
{
|
||||
const QFileInfo info(path);
|
||||
const QString filePath = info.absoluteFilePath();
|
||||
const QString directoryPath = info.absolutePath();
|
||||
const QMap<QString, bool>& watchedFiles = m_watchedFilesInDirectory[directoryPath];
|
||||
const bool created = !watchedFiles[filePath] && info.exists();
|
||||
const bool deleted = watchedFiles[filePath] && !info.exists();
|
||||
const bool changed = !created && !deleted;
|
||||
addPath(path);
|
||||
|
||||
if (m_ignoreFilesChangess[info.canonicalFilePath()] > Clock::currentDateTimeUtc()) {
|
||||
// changes are blocked
|
||||
return;
|
||||
}
|
||||
if (created) {
|
||||
qDebug("File created %s", qPrintable(path));
|
||||
scheduleSignal(Created, filePath);
|
||||
}
|
||||
if (changed) {
|
||||
qDebug("File changed %s", qPrintable(path));
|
||||
scheduleSignal(Updated, filePath);
|
||||
}
|
||||
if (deleted) {
|
||||
qDebug("File removed %s", qPrintable(path));
|
||||
scheduleSignal(Removed, filePath);
|
||||
}
|
||||
}
|
||||
|
||||
void BulkFileWatcher::handleDirectoryChanged(const QString& path)
|
||||
{
|
||||
qDebug("Directory changed %s", qPrintable(path));
|
||||
const QFileInfo directoryInfo(path);
|
||||
const QString directoryPath = directoryInfo.absoluteFilePath();
|
||||
QMap<QString, bool>& watchedFiles = m_watchedFilesInDirectory[directoryPath];
|
||||
for (const QString& filename : watchedFiles.keys()) {
|
||||
const QFileInfo fileInfo(filename);
|
||||
const QString filePath = fileInfo.absoluteFilePath();
|
||||
const bool existed = watchedFiles[filePath];
|
||||
if (!fileInfo.exists() && existed) {
|
||||
qDebug("Remove watch file %s", qPrintable(filePath));
|
||||
m_fileWatcher.removePath(filePath);
|
||||
m_watchedPaths.remove(filePath);
|
||||
watchedFiles.remove(filePath);
|
||||
scheduleSignal(Removed, filePath);
|
||||
}
|
||||
if (!existed && fileInfo.exists()) {
|
||||
qDebug("Add watch file %s", qPrintable(filePath));
|
||||
if (!m_watchedPaths.value(filePath)) {
|
||||
const bool success = m_fileWatcher.addPath(filePath);
|
||||
m_watchedPaths[filePath] = success;
|
||||
watchedFiles[filePath] = fileInfo.exists();
|
||||
}
|
||||
scheduleSignal(Created, filePath);
|
||||
}
|
||||
if (existed && fileInfo.exists()) {
|
||||
// this case is handled using
|
||||
qDebug("Refresh watch file %s", qPrintable(fileInfo.absoluteFilePath()));
|
||||
m_fileWatcher.removePath(fileInfo.absolutePath());
|
||||
m_fileWatcher.addPath(fileInfo.absolutePath());
|
||||
scheduleSignal(Updated, filePath);
|
||||
}
|
||||
m_watchedFilesInDirectory[directoryPath][filePath] = fileInfo.exists();
|
||||
}
|
||||
}
|
||||
|
||||
void BulkFileWatcher::emitSignals()
|
||||
{
|
||||
QMap<QString, QList<Signal>> queued;
|
||||
m_pendingSignals.swap(queued);
|
||||
for (const auto& path : queued.keys()){
|
||||
const auto &signal = queued[path];
|
||||
if (signal.last() == Removed) {
|
||||
emit fileRemoved(path);
|
||||
continue;
|
||||
}
|
||||
if (signal.first() == Created){
|
||||
emit fileCreated(path);
|
||||
continue;
|
||||
}
|
||||
emit fileChanged(path);
|
||||
}
|
||||
}
|
||||
|
||||
void BulkFileWatcher::scheduleSignal(Signal signal, const QString &path)
|
||||
{
|
||||
// we need to collect signals since the file watcher API may send multiple signals for a "single" change
|
||||
// therefore we wait until the event loop finished before starting to import any changes
|
||||
const QString filePath = QFileInfo(path).absoluteFilePath();
|
||||
m_pendingSignals[filePath] << signal;
|
||||
|
||||
if (!m_pendingSignalsTimer.isActive()) {
|
||||
m_pendingSignalsTimer.start();
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
105
src/core/FileWatcher.h
Normal file
@ -0,0 +1,105 @@
|
||||
/*
|
||||
* 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
|
||||
|
||||
enum Signal {
|
||||
Created,
|
||||
Updated,
|
||||
Removed
|
||||
};
|
||||
public:
|
||||
explicit BulkFileWatcher(QObject* parent = nullptr);
|
||||
|
||||
void clear();
|
||||
|
||||
void removePath(const QString& path);
|
||||
void addPath(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);
|
||||
void emitSignals();
|
||||
|
||||
private:
|
||||
void scheduleSignal(Signal event, const QString &path);
|
||||
|
||||
private:
|
||||
QMap<QString, bool> m_watchedPaths;
|
||||
QMap<QString, QDateTime> m_ignoreFilesChangess;
|
||||
QFileSystemWatcher m_fileWatcher;
|
||||
QMap<QString, QMap<QString, bool>> m_watchedFilesInDirectory;
|
||||
// needed for Import/Export-References to prevent update after self-write
|
||||
QTimer m_fileWatchUnblockTimer;
|
||||
// needed to tolerate multiple signals for same event
|
||||
QTimer m_pendingSignalsTimer;
|
||||
QMap<QString, QList<Signal>> m_pendingSignals;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_FILEWATCHER_H
|
@ -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;
|
||||
|
@ -211,4 +211,30 @@ namespace Tools
|
||||
QString uuidToHex(const QUuid& uuid) {
|
||||
return QString::fromLatin1(uuid.toRfc4122().toHex());
|
||||
}
|
||||
|
||||
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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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()
|
||||
{
|
||||
}
|
||||
|
@ -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)
|
||||
};
|
||||
|
@ -87,7 +87,7 @@ int SymmetricCipher::blockSize() const
|
||||
|
||||
QString SymmetricCipher::errorString() const
|
||||
{
|
||||
return m_backend->errorString();
|
||||
return m_backend->error();
|
||||
}
|
||||
|
||||
SymmetricCipher::Algorithm SymmetricCipher::cipherToAlgorithm(const QUuid& cipher)
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
@ -195,7 +194,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;
|
||||
}
|
||||
}
|
||||
@ -204,7 +203,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;
|
||||
}
|
||||
}
|
||||
@ -219,13 +218,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;
|
||||
}
|
||||
|
||||
@ -256,7 +255,7 @@ int SymmetricCipherGcrypt::blockSize() const
|
||||
return blockSizeT;
|
||||
}
|
||||
|
||||
QString SymmetricCipherGcrypt::errorString() const
|
||||
QString SymmetricCipherGcrypt::error() const
|
||||
{
|
||||
return m_errorString;
|
||||
return m_error;
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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 (!parseHeader(stream, KEY_ZERO)) {
|
||||
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 (!parsePrivateHeader(stream, KEY_ZERO)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -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);
|
||||
} // namespace ASN1Key
|
||||
bool parsePrivateRSA(QByteArray& ba, OpenSSHKey& key);
|
||||
bool parsePublicRSA(QByteArray& ba, OpenSSHKey& key);
|
||||
}
|
||||
|
||||
#endif // ASN1KEY_H
|
||||
#endif // KEEPASSXC_ASN1KEY_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
|
14
src/crypto/ssh/CMakeLists.txt
Normal file
@ -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()
|
@ -17,45 +17,219 @@
|
||||
*/
|
||||
|
||||
#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_comment(QString())
|
||||
, m_error(QString())
|
||||
: QObject(parent)
|
||||
, m_type(QString())
|
||||
, m_cipherName(QString("none"))
|
||||
, m_kdfName(QString("none"))
|
||||
, m_kdfOptions(QByteArray())
|
||||
, m_rawType(QString())
|
||||
, m_rawData(QByteArray())
|
||||
, m_rawPublicData(QList<QByteArray>())
|
||||
, m_rawPrivateData(QList<QByteArray>())
|
||||
, m_comment(QString())
|
||||
, m_error(QString())
|
||||
{
|
||||
}
|
||||
|
||||
OpenSSHKey::OpenSSHKey(const OpenSSHKey& other)
|
||||
: QObject(nullptr)
|
||||
, m_type(other.m_type)
|
||||
, m_cipherName(other.m_cipherName)
|
||||
, m_kdfName(other.m_kdfName)
|
||||
, m_kdfOptions(other.m_kdfOptions)
|
||||
, 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)
|
||||
: QObject(nullptr)
|
||||
, m_type(other.m_type)
|
||||
, 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_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 (const 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 (const 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;
|
||||
}
|
||||
@ -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 (const 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 (const 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());
|
@ -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;
|
||||
|
||||
bool parsePEM(const QByteArray& in, QByteArray& out);
|
||||
static const QString TYPE_DSA_PRIVATE;
|
||||
static const QString TYPE_RSA_PRIVATE;
|
||||
static const QString TYPE_RSA_PUBLIC;
|
||||
static const QString TYPE_OPENSSH_PRIVATE;
|
||||
|
||||
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
|
@ -69,8 +69,7 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
||||
}
|
||||
|
||||
QByteArray hmacKey = KeePass2::hmacKey(m_masterSeed, 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 false;
|
||||
}
|
||||
@ -85,8 +84,7 @@ bool Kdbx4Reader::readDatabaseImpl(QIODevice* device,
|
||||
raiseError(tr("Unknown cipher"));
|
||||
return false;
|
||||
}
|
||||
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 false;
|
||||
|
@ -34,7 +34,7 @@ KdbxXmlWriter::KdbxXmlWriter(quint32 version)
|
||||
}
|
||||
|
||||
void KdbxXmlWriter::writeDatabase(QIODevice* device,
|
||||
Database* db,
|
||||
const Database* db,
|
||||
KeePass2RandomStream* randomStream,
|
||||
const QByteArray& headerHash)
|
||||
{
|
||||
|
@ -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);
|
||||
@ -86,8 +86,8 @@ private:
|
||||
bool m_innerStreamProtectionDisabled = false;
|
||||
|
||||
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;
|
||||
|
@ -65,15 +65,15 @@ AboutDialog::AboutDialog(QWidget* parent)
|
||||
#endif
|
||||
|
||||
debugInfo.append("\n").append(
|
||||
QString("%1\n- Qt %2\n- %3\n\n")
|
||||
.arg(tr("Libraries:"), QString::fromLocal8Bit(qVersion()), Crypto::backendVersion()));
|
||||
QString("%1\n- Qt %2\n- %3\n\n")
|
||||
.arg(tr("Libraries:"), QString::fromLocal8Bit(qVersion()), Crypto::backendVersion()));
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 4, 0)
|
||||
debugInfo.append(tr("Operating system: %1\nCPU architecture: %2\nKernel: %3 %4")
|
||||
.arg(QSysInfo::prettyProductName(),
|
||||
QSysInfo::currentCpuArchitecture(),
|
||||
QSysInfo::kernelType(),
|
||||
QSysInfo::kernelVersion()));
|
||||
.arg(QSysInfo::prettyProductName(),
|
||||
QSysInfo::currentCpuArchitecture(),
|
||||
QSysInfo::kernelType(),
|
||||
QSysInfo::kernelVersion()));
|
||||
|
||||
debugInfo.append("\n\n");
|
||||
#endif
|
||||
@ -88,6 +88,13 @@ AboutDialog::AboutDialog(QWidget* parent)
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
extensions += "\n- " + tr("SSH Agent");
|
||||
#endif
|
||||
#if defined(WITH_XC_KEESHARE_SECURE) && defined(WITH_XC_KEESHARE_INSECURE)
|
||||
extensions += "\n- " + tr("KeeShare (signed and unsigned sharing)");
|
||||
#elif defined(WITH_XC_KEESHARE_SECURE)
|
||||
extensions += "\n- " + tr("KeeShare (only signed sharing)");
|
||||
#elif defined(WITH_XC_KEESHARE_INSECURE)
|
||||
extensions += "\n- " + tr("KeeShare (only unsigned sharing)");
|
||||
#endif
|
||||
#ifdef WITH_XC_YUBIKEY
|
||||
extensions += "\n- " + tr("YubiKey");
|
||||
#endif
|
||||
|
@ -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,19 +78,18 @@ 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)));
|
||||
// clang-format off
|
||||
connect(m_generalUi->autoSaveAfterEveryChangeCheckBox, SIGNAL(toggled(bool)), SLOT(enableAutoSaveOnExit(bool)));
|
||||
connect(m_generalUi->systrayShowCheckBox, SIGNAL(toggled(bool)), this, SLOT(enableSystray(bool)));
|
||||
connect(m_generalUi->toolbarHideCheckBox, SIGNAL(toggled(bool)), this, SLOT(enableToolbarSettings(bool)));
|
||||
|
||||
connect(
|
||||
m_secUi->clearClipboardCheckBox, SIGNAL(toggled(bool)), m_secUi->clearClipboardSpinBox, SLOT(setEnabled(bool)));
|
||||
connect(m_secUi->lockDatabaseIdleCheckBox,
|
||||
SIGNAL(toggled(bool)),
|
||||
m_secUi->lockDatabaseIdleSpinBox,
|
||||
SLOT(setEnabled(bool)));
|
||||
|
||||
connect(m_secUi->touchIDResetCheckBox, SIGNAL(toggled(bool)), m_secUi->touchIDResetSpinBox, 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, SLOT(setEnabled(bool)));
|
||||
connect(m_secUi->touchIDResetCheckBox, SIGNAL(toggled(bool)),
|
||||
m_secUi->touchIDResetSpinBox, SLOT(setEnabled(bool)));
|
||||
// clang-format on
|
||||
|
||||
#ifndef WITH_XC_NETWORKING
|
||||
m_secUi->privacy->setVisible(false);
|
||||
@ -121,7 +121,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);
|
||||
}
|
||||
|
@ -36,6 +36,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"
|
||||
@ -57,6 +58,7 @@
|
||||
#include "gui/entry/EntryView.h"
|
||||
#include "gui/group/EditGroupWidget.h"
|
||||
#include "gui/group/GroupView.h"
|
||||
#include "keeshare/KeeShare.h"
|
||||
#include "touchid/TouchID.h"
|
||||
|
||||
#include "config-keepassx.h"
|
||||
@ -72,7 +74,6 @@
|
||||
DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
: QStackedWidget(parent)
|
||||
, m_db(std::move(db))
|
||||
|
||||
, m_mainWidget(new QWidget(this))
|
||||
, m_mainSplitter(new QSplitter(m_mainWidget))
|
||||
, m_messageWidget(new MessageWidget(this))
|
||||
@ -87,6 +88,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
, m_databaseOpenWidget(new DatabaseOpenWidget(this))
|
||||
, m_keepass1OpenWidget(new KeePass1OpenWidget(this))
|
||||
, m_groupView(new GroupView(m_db.data(), m_mainSplitter))
|
||||
, m_fileWatcher(new DelayingFileWatcher(this))
|
||||
{
|
||||
m_messageWidget->setHidden(true);
|
||||
|
||||
@ -129,7 +131,7 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
m_searchingLabel->setStyleSheet("color: rgb(0, 0, 0);"
|
||||
"background-color: rgb(255, 253, 160);"
|
||||
"border: 2px solid rgb(190, 190, 190);"
|
||||
"border-radius: 2px;");
|
||||
"border-radius: 4px;");
|
||||
m_searchingLabel->setVisible(false);
|
||||
|
||||
m_previewView->hide();
|
||||
@ -174,17 +176,11 @@ DatabaseWidget::DatabaseWidget(QSharedPointer<Database> db, QWidget* parent)
|
||||
connect(m_databaseOpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
|
||||
connect(m_keepass1OpenWidget, SIGNAL(dialogFinished(bool)), SLOT(loadDatabase(bool)));
|
||||
connect(m_csvImportWizard, SIGNAL(importFinished(bool)), SLOT(csvImportFinished(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()));
|
||||
// clang-format on
|
||||
|
||||
connectDatabaseSignals();
|
||||
|
||||
m_fileWatchTimer.setSingleShot(true);
|
||||
m_fileWatchUnblockTimer.setSingleShot(true);
|
||||
m_ignoreAutoReload = false;
|
||||
connectDatabaseSignals();
|
||||
|
||||
m_EntrySearcher = new EntrySearcher(false);
|
||||
m_searchLimitGroup = config()->get("SearchLimitGroup", false).toBool();
|
||||
@ -377,6 +373,9 @@ void DatabaseWidget::replaceDatabase(QSharedPointer<Database> db)
|
||||
connectDatabaseSignals();
|
||||
m_groupView->changeDatabase(m_db);
|
||||
processAutoOpen();
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
KeeShare::instance()->connectDatabase(m_db, oldDb);
|
||||
#endif
|
||||
}
|
||||
|
||||
void DatabaseWidget::cloneEntry()
|
||||
@ -831,10 +830,10 @@ void DatabaseWidget::loadDatabase(bool accepted)
|
||||
if (accepted) {
|
||||
replaceDatabase(openWidget->database());
|
||||
switchToMainView();
|
||||
m_fileWatcher.addPath(m_db->filePath());
|
||||
m_fileWatcher->restart();
|
||||
emit databaseUnlocked();
|
||||
} else {
|
||||
m_fileWatcher.removePath(m_db->filePath());
|
||||
m_fileWatcher->stop();
|
||||
if (m_databaseOpenWidget->database()) {
|
||||
m_databaseOpenWidget->database().reset();
|
||||
}
|
||||
@ -1246,56 +1245,19 @@ bool DatabaseWidget::lock()
|
||||
|
||||
void DatabaseWidget::updateFilePath(const QString& filePath)
|
||||
{
|
||||
if (!m_db->filePath().isEmpty()) {
|
||||
m_fileWatcher.removePath(m_db->filePath());
|
||||
}
|
||||
|
||||
#ifdef 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_db->setFilePath(filePath);
|
||||
}
|
||||
|
||||
void DatabaseWidget::blockAutoReload(bool block)
|
||||
{
|
||||
if (block) {
|
||||
m_ignoreAutoReload = true;
|
||||
m_fileWatchTimer.stop();
|
||||
m_fileWatcher->ignoreFileChanges();
|
||||
} else {
|
||||
m_fileWatchUnblockTimer.start(500);
|
||||
m_fileWatcher->observeFileChanges(true);
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseWidget::unblockAutoReload()
|
||||
{
|
||||
m_ignoreAutoReload = false;
|
||||
updateFilePath(m_db->filePath());
|
||||
}
|
||||
|
||||
void DatabaseWidget::onWatchedFileChanged()
|
||||
{
|
||||
if (m_ignoreAutoReload) {
|
||||
return;
|
||||
}
|
||||
if (m_fileWatchTimer.isActive())
|
||||
return;
|
||||
|
||||
m_fileWatchTimer.start(500);
|
||||
}
|
||||
|
||||
void DatabaseWidget::reloadDatabaseFile()
|
||||
{
|
||||
if (!m_db || isLocked()) {
|
||||
@ -1313,7 +1275,7 @@ void DatabaseWidget::reloadDatabaseFile()
|
||||
// Notify everyone the database does not match the file
|
||||
m_db->markAsModified();
|
||||
// Rewatch the database file
|
||||
m_fileWatcher.addPath(m_db->filePath());
|
||||
m_fileWatcher->restart();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -1361,7 +1323,7 @@ void DatabaseWidget::reloadDatabaseFile()
|
||||
}
|
||||
|
||||
// Rewatch the database file
|
||||
m_fileWatcher.addPath(m_db->filePath());
|
||||
m_fileWatcher->restart();
|
||||
}
|
||||
|
||||
int DatabaseWidget::numberOfSelectedEntries() const
|
||||
|
@ -34,6 +34,7 @@ class DatabaseOpenWidget;
|
||||
class KeePass1OpenWidget;
|
||||
class DatabaseSettingsDialog;
|
||||
class Database;
|
||||
class DelayingFileWatcher;
|
||||
class EditEntryWidget;
|
||||
class EditGroupWidget;
|
||||
class Entry;
|
||||
@ -47,7 +48,6 @@ class QSplitter;
|
||||
class QLabel;
|
||||
class MessageWidget;
|
||||
class EntryPreviewWidget;
|
||||
class QFileSystemWatcher;
|
||||
|
||||
namespace Ui
|
||||
{
|
||||
@ -215,10 +215,8 @@ private slots:
|
||||
void mergeDatabase(bool accepted);
|
||||
void emitCurrentModeChanged();
|
||||
// Database autoreload slots
|
||||
void onWatchedFileChanged();
|
||||
void reloadDatabaseFile();
|
||||
void restoreGroupEntryFocus(const QUuid& groupUuid, const QUuid& EntryUuid);
|
||||
void unblockAutoReload();
|
||||
|
||||
private:
|
||||
int addChildWidget(QWidget* w);
|
||||
@ -258,10 +256,7 @@ private:
|
||||
bool m_searchLimitGroup;
|
||||
|
||||
// Autoreload
|
||||
QFileSystemWatcher m_fileWatcher;
|
||||
QTimer m_fileWatchTimer;
|
||||
QTimer m_fileWatchUnblockTimer;
|
||||
bool m_ignoreAutoReload;
|
||||
QPointer<DelayingFileWatcher> m_fileWatcher;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASEWIDGET_H
|
||||
|
@ -18,6 +18,8 @@
|
||||
#include "EditWidgetProperties.h"
|
||||
#include "ui_EditWidgetProperties.h"
|
||||
|
||||
#include "core/CustomData.h"
|
||||
#include "core/TimeInfo.h"
|
||||
#include "MessageBox.h"
|
||||
|
||||
#include <QUuid>
|
||||
@ -25,7 +27,6 @@
|
||||
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);
|
||||
@ -53,17 +54,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(customDataModified()), SLOT(update()));
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void EditWidgetProperties::removeSelectedPluginData()
|
||||
@ -85,7 +88,7 @@ void EditWidgetProperties::removeSelectedPluginData()
|
||||
const QString key = index.data().toString();
|
||||
m_customData->remove(key);
|
||||
}
|
||||
updateModel();
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,16 +97,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);
|
||||
}
|
||||
|
@ -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)
|
||||
};
|
||||
|
||||
|
@ -26,10 +26,12 @@
|
||||
#include "core/FilePath.h"
|
||||
#include "entry/EntryAttachmentsModel.h"
|
||||
#include "gui/Clipboard.h"
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
#include "keeshare/KeeShare.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr int GeneralTabIndex = 0;
|
||||
namespace {
|
||||
constexpr int GeneralTabIndex = 0;
|
||||
}
|
||||
|
||||
EntryPreviewWidget::EntryPreviewWidget(QWidget* parent)
|
||||
@ -104,6 +106,10 @@ void EntryPreviewWidget::setGroup(Group* selectedGroup)
|
||||
updateGroupGeneralTab();
|
||||
updateGroupNotesTab();
|
||||
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
updateGroupSharingTab();
|
||||
#endif
|
||||
|
||||
setVisible(!config()->get("GUI/HidePreviewPanel").toBool());
|
||||
|
||||
m_ui->stackedWidget->setCurrentWidget(m_ui->pageGroup);
|
||||
@ -202,7 +208,7 @@ void EntryPreviewWidget::updateEntryGeneralTab()
|
||||
|
||||
const TimeInfo entryTime = m_currentEntry->timeInfo();
|
||||
const QString expires =
|
||||
entryTime.expires() ? entryTime.expiryTime().toLocalTime().toString(Qt::DefaultLocaleShortDate) : tr("Never");
|
||||
entryTime.expires() ? entryTime.expiryTime().toLocalTime().toString(Qt::DefaultLocaleShortDate) : tr("Never");
|
||||
m_ui->entryExpirationLabel->setText(expires);
|
||||
}
|
||||
|
||||
@ -252,7 +258,7 @@ void EntryPreviewWidget::updateEntryAutotypeTab()
|
||||
const auto associations = autotypeAssociations->getAll();
|
||||
for (const auto& assoc : associations) {
|
||||
const QString sequence =
|
||||
assoc.sequence.isEmpty() ? m_currentEntry->effectiveAutoTypeSequence() : assoc.sequence;
|
||||
assoc.sequence.isEmpty() ? m_currentEntry->effectiveAutoTypeSequence() : assoc.sequence;
|
||||
items.append(new QTreeWidgetItem(m_ui->entryAutotypeTree, {assoc.window, sequence}));
|
||||
}
|
||||
|
||||
@ -278,7 +284,7 @@ void EntryPreviewWidget::updateGroupGeneralTab()
|
||||
|
||||
const TimeInfo groupTime = m_currentGroup->timeInfo();
|
||||
const QString expiresText =
|
||||
groupTime.expires() ? groupTime.expiryTime().toString(Qt::DefaultLocaleShortDate) : tr("Never");
|
||||
groupTime.expires() ? groupTime.expiryTime().toString(Qt::DefaultLocaleShortDate) : tr("Never");
|
||||
m_ui->groupExpirationLabel->setText(expiresText);
|
||||
}
|
||||
|
||||
@ -290,6 +296,17 @@ void EntryPreviewWidget::updateGroupNotesTab()
|
||||
m_ui->groupNotesEdit->setText(notes);
|
||||
}
|
||||
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
void EntryPreviewWidget::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 EntryPreviewWidget::updateTotpLabel()
|
||||
{
|
||||
if (!m_locked && m_currentEntry && m_currentEntry->hasTotp()) {
|
||||
|
@ -18,6 +18,7 @@
|
||||
#ifndef KEEPASSX_DETAILSWIDGET_H
|
||||
#define KEEPASSX_DETAILSWIDGET_H
|
||||
|
||||
#include "config-keepassx.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
|
||||
#include <QWidget>
|
||||
@ -56,6 +57,9 @@ private slots:
|
||||
void updateGroupHeaderLine();
|
||||
void updateGroupGeneralTab();
|
||||
void updateGroupNotesTab();
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
void updateGroupSharingTab();
|
||||
#endif
|
||||
|
||||
void updateTotpLabel();
|
||||
void updateTabIndexes();
|
||||
|
@ -79,13 +79,72 @@ 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);
|
||||
if (!defaultExtension.isEmpty()) {
|
||||
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 +157,7 @@ QString FileDialog::getSaveFileName(QWidget* parent,
|
||||
|
||||
QString result;
|
||||
#if defined(Q_OS_MACOS) || 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 +168,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 +193,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;
|
||||
|
@ -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(),
|
||||
|
@ -21,6 +21,7 @@
|
||||
|
||||
#include <QCloseEvent>
|
||||
#include <QDesktopServices>
|
||||
#include <QFileInfo>
|
||||
#include <QMimeData>
|
||||
#include <QShortcut>
|
||||
#include <QTimer>
|
||||
@ -32,18 +33,21 @@
|
||||
#include "core/FilePath.h"
|
||||
#include "core/InactivityTimer.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
#include "keys/FileKey.h"
|
||||
#include "gui/AboutDialog.h"
|
||||
#include "gui/DatabaseWidget.h"
|
||||
#include "gui/SearchWidget.h"
|
||||
#include "keys/CompositeKey.h"
|
||||
#include "keys/FileKey.h"
|
||||
#include "keys/PasswordKey.h"
|
||||
|
||||
#ifdef WITH_XC_SSHAGENT
|
||||
#include "sshagent/AgentSettingsPage.h"
|
||||
#include "sshagent/SSHAgent.h"
|
||||
#endif
|
||||
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
#include "keeshare/KeeShare.h"
|
||||
#include "keeshare/SettingsPageKeeShare.h"
|
||||
#endif
|
||||
#ifdef WITH_XC_BROWSER
|
||||
#include "browser/BrowserOptionDialog.h"
|
||||
#include "browser/BrowserSettings.h"
|
||||
@ -114,7 +118,10 @@ private:
|
||||
const QString MainWindow::BaseWindowTitle = "KeePassXC";
|
||||
|
||||
MainWindow* g_MainWindow = nullptr;
|
||||
MainWindow* getMainWindow() { return g_MainWindow; }
|
||||
MainWindow* getMainWindow()
|
||||
{
|
||||
return g_MainWindow;
|
||||
}
|
||||
|
||||
MainWindow::MainWindow()
|
||||
: m_ui(new Ui::MainWindow())
|
||||
@ -148,19 +155,27 @@ MainWindow::MainWindow()
|
||||
#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
|
||||
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
KeeShare::init(this);
|
||||
m_ui->settingsWidget->addSettingsPage(new SettingsPageKeeShare(m_ui->tabWidget));
|
||||
connect(KeeShare::instance(),
|
||||
SIGNAL(sharingMessage(QString, MessageWidget::MessageType)),
|
||||
SLOT(displayGlobalMessage(QString, MessageWidget::MessageType)));
|
||||
#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()));
|
||||
// clang-format off
|
||||
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()));
|
||||
// clang-format on
|
||||
|
||||
m_clearHistoryAction = new QAction(tr("Clear history"), m_ui->menuFile);
|
||||
m_lastDatabasesActions = new QActionGroup(m_ui->menuRecentDatabases);
|
||||
@ -349,10 +364,10 @@ MainWindow::MainWindow()
|
||||
setUnifiedTitleAndToolBarOnMac(true);
|
||||
#endif
|
||||
// clang-format off
|
||||
connect(m_ui->tabWidget,
|
||||
SIGNAL(messageGlobal(QString,MessageWidget::MessageType)),
|
||||
this,
|
||||
SLOT(displayGlobalMessage(QString,MessageWidget::MessageType)));
|
||||
connect(m_ui->tabWidget,
|
||||
SIGNAL(messageGlobal(QString,MessageWidget::MessageType)),
|
||||
this,
|
||||
SLOT(displayGlobalMessage(QString,MessageWidget::MessageType)));
|
||||
// clang-format on
|
||||
|
||||
connect(m_ui->tabWidget, SIGNAL(messageDismissGlobal()), this, SLOT(hideGlobalMessage()));
|
||||
@ -838,7 +853,8 @@ bool MainWindow::saveLastDatabases()
|
||||
bool openPreviousDatabasesOnStartup = config()->get("OpenPreviousDatabasesOnStartup").toBool();
|
||||
|
||||
if (openPreviousDatabasesOnStartup) {
|
||||
connect(m_ui->tabWidget, SIGNAL(databaseClosed(const QString&)), this, SLOT(rememberOpenDatabases(const QString&)));
|
||||
connect(
|
||||
m_ui->tabWidget, SIGNAL(databaseClosed(const QString&)), this, SLOT(rememberOpenDatabases(const QString&)));
|
||||
}
|
||||
|
||||
accept = m_ui->tabWidget->closeAllDatabaseTabs();
|
||||
|
@ -640,8 +640,19 @@
|
||||
<string>Report a &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>
|
||||
@ -666,12 +677,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/>
|
||||
|
@ -25,12 +25,37 @@
|
||||
#ifdef WITH_XC_BROWSER
|
||||
#include "DatabaseSettingsWidgetBrowser.h"
|
||||
#endif
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
#include "keeshare/DatabaseSettingsPageKeeShare.h"
|
||||
#endif
|
||||
|
||||
#include "core/Global.h"
|
||||
#include "core/Config.h"
|
||||
#include "core/Database.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "touchid/TouchID.h"
|
||||
|
||||
class DatabaseSettingsDialog::ExtraPage
|
||||
{
|
||||
public:
|
||||
ExtraPage(IDatabaseSettingsPage* page, QWidget* widget)
|
||||
: settingsPage(page)
|
||||
, widget(widget)
|
||||
{
|
||||
}
|
||||
void loadSettings(QSharedPointer<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())
|
||||
@ -55,6 +80,10 @@ DatabaseSettingsDialog::DatabaseSettingsDialog(QWidget* parent)
|
||||
m_securityTabWidget->addTab(m_masterKeyWidget, tr("Master Key"));
|
||||
m_securityTabWidget->addTab(m_encryptionWidget, tr("Encryption Settings"));
|
||||
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
addSettingsPage(new DatabaseSettingsPageKeeShare());
|
||||
#endif
|
||||
|
||||
m_ui->stackedWidget->setCurrentIndex(0);
|
||||
m_securityTabWidget->setCurrentIndex(0);
|
||||
|
||||
@ -84,10 +113,24 @@ void DatabaseSettingsDialog::load(QSharedPointer<Database> db)
|
||||
#ifdef WITH_XC_BROWSER
|
||||
m_browserWidget->load(db);
|
||||
#endif
|
||||
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.
|
||||
*/
|
||||
@ -111,6 +154,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
|
||||
|
@ -39,6 +39,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, QSharedPointer<Database> db) = 0;
|
||||
virtual void saveSettings(QWidget* widget) = 0;
|
||||
};
|
||||
|
||||
class DatabaseSettingsDialog : public DialogyWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -49,6 +62,7 @@ public:
|
||||
Q_DISABLE_COPY(DatabaseSettingsDialog);
|
||||
|
||||
void load(QSharedPointer<Database> db);
|
||||
void addSettingsPage(IDatabaseSettingsPage* page);
|
||||
void showMasterKeySettings();
|
||||
|
||||
signals:
|
||||
@ -76,6 +90,9 @@ private:
|
||||
#ifdef WITH_XC_BROWSER
|
||||
QPointer<DatabaseSettingsWidgetBrowser> m_browserWidget;
|
||||
#endif
|
||||
|
||||
class ExtraPage;
|
||||
QList<ExtraPage> m_extraPages;
|
||||
};
|
||||
|
||||
#endif // KEEPASSX_DATABASESETTINGSWIDGET_H
|
||||
|
@ -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();
|
||||
@ -111,6 +116,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()
|
||||
@ -543,13 +550,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;
|
||||
}
|
||||
@ -684,6 +691,8 @@ void EditEntryWidget::loadEntry(Entry* entry, bool create, bool history, const Q
|
||||
|
||||
void EditEntryWidget::setForms(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);
|
||||
@ -779,7 +788,6 @@ void EditEntryWidget::setForms(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());
|
||||
@ -888,7 +896,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, " "));
|
||||
|
@ -30,6 +30,7 @@
|
||||
|
||||
class AutoTypeAssociations;
|
||||
class AutoTypeAssociationsModel;
|
||||
class CustomData;
|
||||
class Database;
|
||||
class EditWidgetIcons;
|
||||
class EditWidgetProperties;
|
||||
@ -157,11 +158,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;
|
||||
|
@ -23,18 +23,49 @@
|
||||
#include "gui/EditWidgetIcons.h"
|
||||
#include "gui/EditWidgetProperties.h"
|
||||
|
||||
#if defined(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)
|
||||
: EditWidget(parent)
|
||||
, m_mainUi(new Ui::EditGroupWidgetMain())
|
||||
, m_editGroupWidgetMain(new QWidget())
|
||||
, m_editGroupWidgetIcons(new EditWidgetIcons())
|
||||
, m_editWidgetProperties(new EditWidgetProperties())
|
||||
, m_group(nullptr)
|
||||
{
|
||||
m_mainUi->setupUi(m_editGroupWidgetMain);
|
||||
|
||||
addPage(tr("Group"), FilePath::instance()->icon("actions", "document-edit"), m_editGroupWidgetMain);
|
||||
addPage(tr("Icon"), FilePath::instance()->icon("apps", "preferences-desktop-icons"), m_editGroupWidgetIcons);
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
addEditPage(new EditGroupPageKeeShare(this));
|
||||
#endif
|
||||
addPage(tr("Properties"), FilePath::instance()->icon("actions", "document-properties"), m_editWidgetProperties);
|
||||
|
||||
connect(m_mainUi->expireCheck, SIGNAL(toggled(bool)), m_mainUi->expireDatePicker, SLOT(setEnabled(bool)));
|
||||
@ -65,6 +96,8 @@ void EditGroupWidget::loadGroup(Group* group, bool create, QSharedPointer<Databa
|
||||
m_group = group;
|
||||
m_db = database;
|
||||
|
||||
m_temporaryGroup.reset(group->clone(Entry::CloneNoFlags, Group::CloneNoFlags));
|
||||
|
||||
if (create) {
|
||||
setHeadline(tr("Add group"));
|
||||
} else {
|
||||
@ -93,12 +126,15 @@ void EditGroupWidget::loadGroup(Group* group, bool create, QSharedPointer<Databa
|
||||
m_mainUi->autoTypeSequenceCustomEdit->setText(group->effectiveAutoTypeSequence());
|
||||
|
||||
IconStruct iconStruct;
|
||||
iconStruct.uuid = group->iconUuid();
|
||||
iconStruct.number = group->iconNumber();
|
||||
m_editGroupWidgetIcons->load(group->uuid(), database, iconStruct);
|
||||
iconStruct.uuid = m_temporaryGroup->iconUuid();
|
||||
iconStruct.number = m_temporaryGroup->iconNumber();
|
||||
m_editGroupWidgetIcons->load(m_temporaryGroup->uuid(), m_db, iconStruct);
|
||||
m_editWidgetProperties->setFields(m_temporaryGroup->timeInfo(), m_temporaryGroup->uuid());
|
||||
m_editWidgetProperties->setCustomData(m_temporaryGroup->customData());
|
||||
|
||||
m_editWidgetProperties->setFields(group->timeInfo(), group->uuid());
|
||||
m_editWidgetProperties->setCustomData(group->customData());
|
||||
for (const ExtraPage& page : asConst(m_extraPages)) {
|
||||
page.set(m_temporaryGroup.data());
|
||||
}
|
||||
|
||||
setCurrentPage(0);
|
||||
|
||||
@ -114,31 +150,36 @@ 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_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->setSearchingEnabled(triStateFromIndex(m_mainUi->searchComboBox->currentIndex()));
|
||||
m_group->setAutoTypeEnabled(triStateFromIndex(m_mainUi->autotypeComboBox->currentIndex()));
|
||||
|
||||
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()
|
||||
@ -155,9 +196,19 @@ void EditGroupWidget::clear()
|
||||
{
|
||||
m_group = nullptr;
|
||||
m_db.reset();
|
||||
m_temporaryGroup.reset(nullptr);
|
||||
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;
|
||||
|
@ -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;
|
||||
} // namespace Ui
|
||||
|
||||
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, QSharedPointer<Database> database);
|
||||
void clear();
|
||||
|
||||
void addEditPage(IEditGroupPage* page);
|
||||
|
||||
signals:
|
||||
void editFinished(bool accepted);
|
||||
void messageEditEntry(QString, MessageWidget::MessageType);
|
||||
@ -60,13 +76,18 @@ private:
|
||||
Group::TriState triStateFromIndex(int index);
|
||||
|
||||
const QScopedPointer<Ui::EditGroupWidgetMain> m_mainUi;
|
||||
|
||||
QPointer<QWidget> m_editGroupWidgetMain;
|
||||
QPointer<EditWidgetIcons> m_editGroupWidgetIcons;
|
||||
QPointer<EditWidgetProperties> m_editWidgetProperties;
|
||||
|
||||
QScopedPointer<Group> m_temporaryGroup;
|
||||
QPointer<Group> m_group;
|
||||
QSharedPointer<Database> m_db;
|
||||
|
||||
class ExtraPage;
|
||||
QList<ExtraPage> m_extraPages;
|
||||
|
||||
Q_DISABLE_COPY(EditGroupWidget)
|
||||
};
|
||||
|
||||
|
@ -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)
|
||||
@ -123,13 +124,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");
|
||||
#if defined(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();
|
||||
#if defined(WITH_XC_KEESHARE)
|
||||
pixmap = KeeShare::indicatorBadge(group, pixmap);
|
||||
#endif
|
||||
return pixmap;
|
||||
} else if (role == Qt::FontRole) {
|
||||
QFont font;
|
||||
if (group->isExpired()) {
|
||||
|
23
src/keeshare/CMakeLists.txt
Normal file
@ -0,0 +1,23 @@
|
||||
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})
|
||||
if(WITH_XC_KEESHARE_SECURE)
|
||||
target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${QUAZIP_LIBRARIES} ${crypto_ssh_LIB})
|
||||
else()
|
||||
target_link_libraries(keeshare Qt5::Core Qt5::Widgets ${GCRYPT_LIBRARIES} ${crypto_ssh_LIB})
|
||||
endif()
|
||||
endif()
|
53
src/keeshare/DatabaseSettingsPageKeeShare.cpp
Normal file
@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DatabaseSettingsPageKeeShare.h"
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "core/Group.h"
|
||||
#include "keeshare/DatabaseSettingsWidgetKeeShare.h"
|
||||
#include "keeshare/KeeShare.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
QString DatabaseSettingsPageKeeShare::name()
|
||||
{
|
||||
return QApplication::tr("KeeShare");
|
||||
}
|
||||
|
||||
QIcon DatabaseSettingsPageKeeShare::icon()
|
||||
{
|
||||
return FilePath::instance()->icon("apps", "preferences-system-network-sharing");
|
||||
}
|
||||
|
||||
QWidget* DatabaseSettingsPageKeeShare::createWidget()
|
||||
{
|
||||
return new DatabaseSettingsWidgetKeeShare();
|
||||
}
|
||||
|
||||
void DatabaseSettingsPageKeeShare::loadSettings(QWidget* widget, QSharedPointer<Database> db)
|
||||
{
|
||||
DatabaseSettingsWidgetKeeShare* settingsWidget = reinterpret_cast<DatabaseSettingsWidgetKeeShare*>(widget);
|
||||
settingsWidget->loadSettings(db);
|
||||
}
|
||||
|
||||
void DatabaseSettingsPageKeeShare::saveSettings(QWidget* widget)
|
||||
{
|
||||
DatabaseSettingsWidgetKeeShare* settingsWidget = reinterpret_cast<DatabaseSettingsWidgetKeeShare*>(widget);
|
||||
settingsWidget->saveSettings();
|
||||
}
|
37
src/keeshare/DatabaseSettingsPageKeeShare.h
Normal file
@ -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, QSharedPointer<Database> db) override;
|
||||
void saveSettings(QWidget* widget) override;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_DATABASESETTINGSPAGEKEESHARE_H
|
72
src/keeshare/DatabaseSettingsWidgetKeeShare.cpp
Normal file
@ -0,0 +1,72 @@
|
||||
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "DatabaseSettingsWidgetKeeShare.h"
|
||||
#include "ui_DatabaseSettingsWidgetKeeShare.h"
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/Group.h"
|
||||
#include "core/Metadata.h"
|
||||
#include "keeshare/KeeShare.h"
|
||||
#include "keeshare/KeeShareSettings.h"
|
||||
|
||||
#include <QMessageBox>
|
||||
#include <QStandardItemModel>
|
||||
|
||||
DatabaseSettingsWidgetKeeShare::DatabaseSettingsWidgetKeeShare(QWidget* parent)
|
||||
: QWidget(parent)
|
||||
, m_ui(new Ui::DatabaseSettingsWidgetKeeShare())
|
||||
{
|
||||
m_ui->setupUi(this);
|
||||
}
|
||||
|
||||
DatabaseSettingsWidgetKeeShare::~DatabaseSettingsWidgetKeeShare()
|
||||
{
|
||||
}
|
||||
|
||||
void DatabaseSettingsWidgetKeeShare::loadSettings(QSharedPointer<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(tr(" > ", "Breadcrumb separator")));
|
||||
row << new QStandardItem(KeeShare::referenceTypeLabel(reference));
|
||||
row << new QStandardItem(reference.path);
|
||||
m_referencesModel->appendRow(row);
|
||||
}
|
||||
|
||||
m_ui->sharedGroupsView->setModel(m_referencesModel.data());
|
||||
}
|
||||
|
||||
void DatabaseSettingsWidgetKeeShare::saveSettings()
|
||||
{
|
||||
// nothing to do - the tab is passive
|
||||
}
|
51
src/keeshare/DatabaseSettingsWidgetKeeShare.h
Normal file
@ -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(QSharedPointer<Database> db);
|
||||
void saveSettings();
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::DatabaseSettingsWidgetKeeShare> m_ui;
|
||||
|
||||
QScopedPointer<QStandardItemModel> m_referencesModel;
|
||||
QSharedPointer<Database> m_db;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_DATABASESETTINGSWIDGETKEESHARE_H
|
74
src/keeshare/DatabaseSettingsWidgetKeeShare.ui
Normal file
@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>DatabaseSettingsWidgetKeeShare</class>
|
||||
<widget class="QWidget" name="DatabaseSettingsWidgetKeeShare">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>327</width>
|
||||
<height>379</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QVBoxLayout" name="verticalLayout" stretch="0,0">
|
||||
<property name="leftMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="topMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="rightMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<property name="bottomMargin">
|
||||
<number>0</number>
|
||||
</property>
|
||||
<item>
|
||||
<widget class="QGroupBox" name="enableGroupBox">
|
||||
<property name="title">
|
||||
<string>Sharing</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0" rowspan="2">
|
||||
<widget class="QTableView" name="sharedGroupsView">
|
||||
<property name="editTriggers">
|
||||
<set>QAbstractItemView::NoEditTriggers</set>
|
||||
</property>
|
||||
<property name="showDropIndicator" stdset="0">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="textElideMode">
|
||||
<enum>Qt::ElideMiddle</enum>
|
||||
</property>
|
||||
<property name="sortingEnabled">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
<attribute name="horizontalHeaderStretchLastSection">
|
||||
<bool>true</bool>
|
||||
</attribute>
|
||||
<attribute name="verticalHeaderVisible">
|
||||
<bool>false</bool>
|
||||
</attribute>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections/>
|
||||
</ui>
|
218
src/keeshare/KeeShare.cpp
Normal file
@ -0,0 +1,218 @@
|
||||
/*
|
||||
* 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");
|
||||
} // namespace
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
KeeShare::KeeShare(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
connect(config(), SIGNAL(changed(QString)), SLOT(handleSettingsChanged(QString)));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
bool KeeShare::isEnabled(const Group* group)
|
||||
{
|
||||
const auto reference = KeeShare::referenceOf(group);
|
||||
#if !defined(WITH_XC_KEESHARE_SECURE)
|
||||
if (reference.path.endsWith(signedContainerFileType(), Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
#if !defined(WITH_XC_KEESHARE_INSECURE)
|
||||
if (reference.path.endsWith(unsignedContainerFileType(), Qt::CaseInsensitive)) {
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
const auto active = KeeShare::active();
|
||||
return (reference.isImporting() && active.in) || (reference.isExporting() && active.out);
|
||||
}
|
||||
|
||||
QPixmap KeeShare::indicatorBadge(const Group* group, QPixmap pixmap)
|
||||
{
|
||||
if (!isShared(group)) {
|
||||
return pixmap;
|
||||
}
|
||||
const QPixmap badge = isEnabled(group) ? 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(QSharedPointer<Database> newDb, QSharedPointer<Database> oldDb)
|
||||
{
|
||||
if (oldDb && m_observersByDatabase.contains(oldDb->uuid())) {
|
||||
QPointer<ShareObserver> observer = m_observersByDatabase.take(oldDb->uuid());
|
||||
if (observer) {
|
||||
delete observer;
|
||||
}
|
||||
}
|
||||
|
||||
if (newDb && !m_observersByDatabase.contains(newDb->uuid())) {
|
||||
QPointer<ShareObserver> observer(new ShareObserver(newDb, this));
|
||||
m_observersByDatabase[newDb->uuid()] = observer;
|
||||
connect(observer.data(),
|
||||
SIGNAL(sharingMessage(QString, MessageWidget::MessageType)),
|
||||
SIGNAL(sharingMessage(QString, MessageWidget::MessageType)));
|
||||
}
|
||||
}
|
||||
|
||||
const QString& KeeShare::signedContainerFileType()
|
||||
{
|
||||
static const QString filetype("kdbx.share");
|
||||
return filetype;
|
||||
}
|
||||
|
||||
const QString& KeeShare::unsignedContainerFileType()
|
||||
{
|
||||
static const QString filetype("kdbx");
|
||||
return filetype;
|
||||
}
|
||||
|
||||
void KeeShare::handleSettingsChanged(const QString& key)
|
||||
{
|
||||
if (key == KeeShare_Active) {
|
||||
emit activeChanged();
|
||||
}
|
||||
}
|
77
src/keeshare/KeeShare.h
Normal file
@ -0,0 +1,77 @@
|
||||
/*
|
||||
* 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 <QUuid>
|
||||
|
||||
#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 bool isEnabled(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(QSharedPointer<Database> newDb, QSharedPointer<Database> oldDb);
|
||||
|
||||
static const QString& signedContainerFileType();
|
||||
static const QString& unsignedContainerFileType();
|
||||
|
||||
signals:
|
||||
void activeChanged();
|
||||
void sharingMessage(QString, MessageWidget::MessageType);
|
||||
|
||||
private slots:
|
||||
void handleSettingsChanged(const QString&);
|
||||
|
||||
private:
|
||||
static KeeShare* m_instance;
|
||||
|
||||
explicit KeeShare(QObject* parent);
|
||||
|
||||
QMap<QUuid, QPointer<ShareObserver>> m_observersByDatabase;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_KEESHARE_H
|
497
src/keeshare/KeeShareSettings.cpp
Normal file
@ -0,0 +1,497 @@
|
||||
/*
|
||||
* 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, const QString& signer)
|
||||
{
|
||||
KeeShareSettings::Certificate extracted;
|
||||
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);
|
||||
}
|
||||
} // namespace
|
||||
|
||||
void Certificate::serialize(QXmlStreamWriter& writer, const Certificate& certificate)
|
||||
{
|
||||
if (certificate.isNull()) {
|
||||
return;
|
||||
}
|
||||
writer.writeStartElement("Signer");
|
||||
writer.writeCharacters(certificate.signer);
|
||||
writer.writeEndElement();
|
||||
writer.writeStartElement("Key");
|
||||
writer.writeCharacters(certificate.key.toBase64());
|
||||
writer.writeEndElement();
|
||||
}
|
||||
|
||||
bool Certificate::operator==(const Certificate& other) const
|
||||
{
|
||||
return key == other.key && signer == other.signer;
|
||||
}
|
||||
|
||||
bool Certificate::operator!=(const Certificate& other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
bool Certificate::isNull() const
|
||||
{
|
||||
return 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() == "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, 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;
|
||||
}
|
||||
|
||||
bool ScopedCertificate::operator==(const ScopedCertificate& other) const
|
||||
{
|
||||
return trust == other.trust && path == other.path && certificate == other.certificate;
|
||||
}
|
||||
|
||||
bool ScopedCertificate::operator!=(const ScopedCertificate& other) const
|
||||
{
|
||||
return !operator==(other);
|
||||
}
|
||||
|
||||
void ScopedCertificate::serialize(QXmlStreamWriter& writer, const ScopedCertificate& scopedCertificate)
|
||||
{
|
||||
writer.writeAttribute("Path", scopedCertificate.path);
|
||||
QString trust = "Ask";
|
||||
if (scopedCertificate.trust == KeeShareSettings::Trust::Trusted) {
|
||||
trust = "Trusted";
|
||||
}
|
||||
if (scopedCertificate.trust == KeeShareSettings::Trust::Untrusted) {
|
||||
trust = "Untrusted";
|
||||
}
|
||||
writer.writeAttribute("Trust", trust);
|
||||
Certificate::serialize(writer, scopedCertificate.certificate);
|
||||
}
|
||||
|
||||
ScopedCertificate ScopedCertificate::deserialize(QXmlStreamReader& reader)
|
||||
{
|
||||
ScopedCertificate scopedCertificate;
|
||||
scopedCertificate.path = reader.attributes().value("Path").toString();
|
||||
scopedCertificate.trust = KeeShareSettings::Trust::Ask;
|
||||
auto trust = reader.attributes().value("Trust").toString();
|
||||
if (trust.compare("Trusted", Qt::CaseInsensitive) == 0) {
|
||||
scopedCertificate.trust = KeeShareSettings::Trust::Trusted;
|
||||
}
|
||||
if (trust.compare("Untrusted", Qt::CaseInsensitive) == 0) {
|
||||
scopedCertificate.trust = KeeShareSettings::Trust::Untrusted;
|
||||
}
|
||||
scopedCertificate.certificate = Certificate::deserialize(reader);
|
||||
return scopedCertificate;
|
||||
}
|
||||
|
||||
QString Foreign::serialize(const Foreign& foreign)
|
||||
{
|
||||
return xmlSerialize([&](QXmlStreamWriter& writer) {
|
||||
writer.writeStartElement("Foreign");
|
||||
for (const ScopedCertificate& scopedCertificate : foreign.certificates) {
|
||||
writer.writeStartElement("Certificate");
|
||||
ScopedCertificate::serialize(writer, scopedCertificate);
|
||||
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 << ScopedCertificate::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;
|
||||
}
|
||||
} // namespace KeeShareSettings
|
181
src/keeshare/KeeShareSettings.h
Normal file
@ -0,0 +1,181 @@
|
||||
/*
|
||||
* 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 operator==(const Certificate& other) const;
|
||||
bool operator!=(const Certificate& other) const;
|
||||
|
||||
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();
|
||||
};
|
||||
|
||||
enum class Trust
|
||||
{
|
||||
Ask,
|
||||
Untrusted,
|
||||
Trusted
|
||||
};
|
||||
struct ScopedCertificate
|
||||
{
|
||||
QString path;
|
||||
Certificate certificate;
|
||||
Trust trust;
|
||||
|
||||
bool operator==(const ScopedCertificate& other) const;
|
||||
bool operator!=(const ScopedCertificate& other) const;
|
||||
|
||||
bool isUnknown() const
|
||||
{
|
||||
return certificate.isNull();
|
||||
}
|
||||
bool isKnown() const
|
||||
{
|
||||
return !certificate.isNull();
|
||||
}
|
||||
|
||||
static void serialize(QXmlStreamWriter& writer, const ScopedCertificate& certificate);
|
||||
static ScopedCertificate deserialize(QXmlStreamReader& reader);
|
||||
};
|
||||
|
||||
struct Foreign
|
||||
{
|
||||
QList<ScopedCertificate> certificates;
|
||||
|
||||
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);
|
||||
};
|
||||
}; // namespace KeeShareSettings
|
||||
|
||||
#endif // KEEPASSXC_KEESHARESETTINGS_H
|
67
src/keeshare/SettingsPageKeeShare.cpp
Normal file
@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "SettingsPageKeeShare.h"
|
||||
|
||||
#include "core/Database.h"
|
||||
#include "core/FilePath.h"
|
||||
#include "core/Group.h"
|
||||
#include "gui/DatabaseTabWidget.h"
|
||||
#include "gui/MessageWidget.h"
|
||||
#include "keeshare/KeeShare.h"
|
||||
#include "keeshare/SettingsWidgetKeeShare.h"
|
||||
#include <QApplication>
|
||||
#include <QObject>
|
||||
|
||||
SettingsPageKeeShare::SettingsPageKeeShare(DatabaseTabWidget* tabWidget)
|
||||
: m_tabWidget(tabWidget)
|
||||
{
|
||||
}
|
||||
|
||||
QString SettingsPageKeeShare::name()
|
||||
{
|
||||
return QApplication::tr("KeeShare");
|
||||
}
|
||||
|
||||
QIcon SettingsPageKeeShare::icon()
|
||||
{
|
||||
return FilePath::instance()->icon("apps", "preferences-system-network-sharing");
|
||||
}
|
||||
|
||||
QWidget* SettingsPageKeeShare::createWidget()
|
||||
{
|
||||
auto* widget = new SettingsWidgetKeeShare();
|
||||
QObject::connect(widget,
|
||||
SIGNAL(settingsMessage(QString, MessageWidget::MessageType)),
|
||||
m_tabWidget,
|
||||
SIGNAL(messageGlobal(QString, MessageWidget::MessageType)));
|
||||
return widget;
|
||||
}
|
||||
|
||||
void SettingsPageKeeShare::loadSettings(QWidget* widget)
|
||||
{
|
||||
Q_UNUSED(widget);
|
||||
SettingsWidgetKeeShare* settingsWidget = reinterpret_cast<SettingsWidgetKeeShare*>(widget);
|
||||
settingsWidget->loadSettings();
|
||||
}
|
||||
|
||||
void SettingsPageKeeShare::saveSettings(QWidget* widget)
|
||||
{
|
||||
Q_UNUSED(widget);
|
||||
SettingsWidgetKeeShare* settingsWidget = reinterpret_cast<SettingsWidgetKeeShare*>(widget);
|
||||
return settingsWidget->saveSettings();
|
||||
}
|
43
src/keeshare/SettingsPageKeeShare.h
Normal file
@ -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
|
245
src/keeshare/SettingsWidgetKeeShare.cpp
Normal file
@ -0,0 +1,245 @@
|
||||
/*
|
||||
* 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);
|
||||
|
||||
#if !defined(WITH_XC_KEESHARE_SECURE)
|
||||
// Setting does not help the user of Version without signed export
|
||||
m_ui->ownCertificateGroupBox->setVisible(false);
|
||||
#endif
|
||||
|
||||
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->askImportedCertificateButton, SIGNAL(clicked(bool)), SLOT(askSelectedCertificates()));
|
||||
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()
|
||||
{
|
||||
auto headers = QStringList() << tr("Path") << tr("Status");
|
||||
#if defined(WITH_XC_KEESHARE_SECURE)
|
||||
headers << tr("Signer") << tr("Fingerprint") << tr("Certificate");
|
||||
#endif
|
||||
|
||||
m_importedCertificateModel.reset(new QStandardItemModel());
|
||||
m_importedCertificateModel->setHorizontalHeaderLabels(headers);
|
||||
|
||||
for (const auto& scopedCertificate : m_foreign.certificates) {
|
||||
const auto items = QList<QStandardItem*>()
|
||||
<< new QStandardItem(scopedCertificate.path)
|
||||
<< new QStandardItem(scopedCertificate.trust == KeeShareSettings::Trust::Ask
|
||||
? tr("Ask")
|
||||
: (scopedCertificate.trust == KeeShareSettings::Trust::Trusted
|
||||
? tr("Trusted")
|
||||
: tr("Untrusted")))
|
||||
#if defined(WITH_XC_KEESHARE_SECURE)
|
||||
<< new QStandardItem(scopedCertificate.isKnown() ? scopedCertificate.certificate.signer
|
||||
: tr("Unknown"))
|
||||
<< new QStandardItem(scopedCertificate.certificate.fingerprint())
|
||||
<< new QStandardItem(scopedCertificate.certificate.publicKey())
|
||||
#endif
|
||||
;
|
||||
m_importedCertificateModel->appendRow(items);
|
||||
}
|
||||
|
||||
m_ui->importedCertificateTableView->setModel(m_importedCertificateModel.data());
|
||||
m_ui->importedCertificateTableView->resizeColumnsToContents();
|
||||
}
|
||||
|
||||
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
|
||||
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, QFileDialog::Options(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, QFileDialog::Options(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()].trust = KeeShareSettings::Trust::Trusted;
|
||||
}
|
||||
|
||||
updateForeignCertificates();
|
||||
}
|
||||
|
||||
void SettingsWidgetKeeShare::askSelectedCertificates()
|
||||
{
|
||||
const auto* selectionModel = m_ui->importedCertificateTableView->selectionModel();
|
||||
Q_ASSERT(selectionModel);
|
||||
for (const auto& index : selectionModel->selectedRows()) {
|
||||
m_foreign.certificates[index.row()].trust = KeeShareSettings::Trust::Ask;
|
||||
}
|
||||
|
||||
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()].trust = KeeShareSettings::Trust::Untrusted;
|
||||
}
|
||||
|
||||
updateForeignCertificates();
|
||||
}
|
||||
|
||||
void SettingsWidgetKeeShare::removeSelectedCertificates()
|
||||
{
|
||||
auto 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();
|
||||
}
|
73
src/keeshare/SettingsWidgetKeeShare.h
Normal file
@ -0,0 +1,73 @@
|
||||
/*
|
||||
* 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 askSelectedCertificates();
|
||||
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
|
256
src/keeshare/SettingsWidgetKeeShare.ui
Normal file
@ -0,0 +1,256 @@
|
||||
<?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>434</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="askImportedCertificateButton">
|
||||
<property name="text">
|
||||
<string>Ask</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>
|
806
src/keeshare/ShareObserver.cpp
Normal file
@ -0,0 +1,806 @@
|
||||
/*
|
||||
* 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 "config-keepassx.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>
|
||||
|
||||
#if defined(WITH_XC_KEESHARE_SECURE)
|
||||
#include <quazip5/quazip.h>
|
||||
#include <quazip5/quazipfile.h>
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
static const QString KeeShare_Signature("container.share.signature");
|
||||
static const QString KeeShare_Container("container.share.kdbx");
|
||||
|
||||
enum Trust
|
||||
{
|
||||
Invalid,
|
||||
Own,
|
||||
UntrustedForever,
|
||||
UntrustedOnce,
|
||||
TrustedOnce,
|
||||
TrustedForever,
|
||||
};
|
||||
|
||||
bool isOfExportType(const QFileInfo& fileInfo, const QString type)
|
||||
{
|
||||
return fileInfo.fileName().endsWith(type, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
QPair<Trust, KeeShareSettings::Certificate>
|
||||
check(QByteArray& data,
|
||||
const KeeShareSettings::Reference& reference,
|
||||
const KeeShareSettings::Certificate& ownCertificate,
|
||||
const QList<KeeShareSettings::ScopedCertificate>& knownCertificates,
|
||||
const KeeShareSettings::Sign& sign)
|
||||
{
|
||||
KeeShareSettings::Certificate certificate;
|
||||
if (!sign.signature.isEmpty()) {
|
||||
certificate = sign.certificate;
|
||||
auto key = sign.certificate.sshKey();
|
||||
key.openKey(QString());
|
||||
const auto signer = Signature();
|
||||
if (!signer.verify(data, sign.signature, key)) {
|
||||
qCritical("Invalid signature for sharing container %s.", qPrintable(reference.path));
|
||||
return {Invalid, KeeShareSettings::Certificate()};
|
||||
}
|
||||
|
||||
if (ownCertificate.key == sign.certificate.key) {
|
||||
return {Own, ownCertificate};
|
||||
}
|
||||
}
|
||||
enum Scope
|
||||
{
|
||||
Invalid,
|
||||
Global,
|
||||
Local
|
||||
};
|
||||
Scope scope = Invalid;
|
||||
KeeShareSettings::Trust trusted = KeeShareSettings::Trust::Ask;
|
||||
for (const auto& scopedCertificate : knownCertificates) {
|
||||
if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) {
|
||||
// Global scope is overwritten by local scope
|
||||
scope = Global;
|
||||
trusted = scopedCertificate.trust;
|
||||
}
|
||||
if (scopedCertificate.certificate.key == certificate.key && scopedCertificate.path == reference.path) {
|
||||
scope = Local;
|
||||
trusted = scopedCertificate.trust;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (scope != Invalid && trusted != KeeShareSettings::Trust::Ask) {
|
||||
// we introduce now scopes if there is a global
|
||||
return {trusted == KeeShareSettings::Trust::Trusted ? TrustedForever : UntrustedForever, certificate};
|
||||
}
|
||||
|
||||
QMessageBox warning;
|
||||
if (sign.signature.isEmpty()) {
|
||||
warning.setIcon(QMessageBox::Warning);
|
||||
warning.setWindowTitle(ShareObserver::tr("Import from container without signature"));
|
||||
warning.setText(ShareObserver::tr("We cannot verify the source of the shared container because it is not "
|
||||
"signed. Do you really want to import from %1?")
|
||||
.arg(reference.path));
|
||||
} else {
|
||||
warning.setIcon(QMessageBox::Question);
|
||||
warning.setWindowTitle(ShareObserver::tr("Import from container with certificate"));
|
||||
warning.setText(ShareObserver::tr("Do you want to trust %1 with the fingerprint of %2 from %3")
|
||||
.arg(certificate.signer, certificate.fingerprint(), reference.path));
|
||||
}
|
||||
auto untrustedOnce = warning.addButton(ShareObserver::tr("Not this time"), QMessageBox::ButtonRole::NoRole);
|
||||
auto untrustedForever = warning.addButton(ShareObserver::tr("Never"), QMessageBox::ButtonRole::NoRole);
|
||||
auto trustedForever = warning.addButton(ShareObserver::tr("Always"), QMessageBox::ButtonRole::YesRole);
|
||||
auto trustedOnce = warning.addButton(ShareObserver::tr("Just this time"), QMessageBox::ButtonRole::YesRole);
|
||||
warning.setDefaultButton(untrustedOnce);
|
||||
warning.exec();
|
||||
if (warning.clickedButton() == trustedForever) {
|
||||
return {TrustedForever, certificate};
|
||||
}
|
||||
if (warning.clickedButton() == trustedOnce) {
|
||||
return {TrustedOnce, certificate};
|
||||
}
|
||||
if (warning.clickedButton() == untrustedOnce) {
|
||||
return {UntrustedOnce, certificate};
|
||||
}
|
||||
if (warning.clickedButton() == untrustedForever) {
|
||||
return {UntrustedForever, certificate};
|
||||
}
|
||||
return {UntrustedOnce, certificate};
|
||||
}
|
||||
|
||||
} // End Namespace
|
||||
|
||||
ShareObserver::ShareObserver(QSharedPointer<Database> db, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_db(std::move(db))
|
||||
, m_fileWatcher(new BulkFileWatcher(this))
|
||||
{
|
||||
connect(KeeShare::instance(), SIGNAL(activeChanged()), SLOT(handleDatabaseChanged()));
|
||||
|
||||
connect(m_db.data(), SIGNAL(databaseModified()), SLOT(handleDatabaseChanged()));
|
||||
connect(m_db.data(), SIGNAL(databaseSaved()), SLOT(handleDatabaseSaved()));
|
||||
|
||||
connect(m_fileWatcher, SIGNAL(fileCreated(QString)), SLOT(handleFileCreated(QString)));
|
||||
connect(m_fileWatcher, SIGNAL(fileChanged(QString)), SLOT(handleFileUpdated(QString)));
|
||||
connect(m_fileWatcher, SIGNAL(fileRemoved(QString)), SLOT(handleFileDeleted(QString)));
|
||||
|
||||
handleDatabaseChanged();
|
||||
}
|
||||
|
||||
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 (const auto& 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 auto 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::handleFileCreated(const QString& path)
|
||||
{
|
||||
// there is currently no difference in handling an added share or updating from one
|
||||
this->handleFileUpdated(path);
|
||||
}
|
||||
|
||||
void ShareObserver::handleFileDeleted(const QString& path)
|
||||
{
|
||||
Q_UNUSED(path);
|
||||
// There is nothing we can or should do for now, ignore deletion
|
||||
}
|
||||
|
||||
void ShareObserver::handleFileUpdated(const QString& path)
|
||||
{
|
||||
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, result.message);
|
||||
} else if (result.isWarning()) {
|
||||
warning << tr("Import from %1 failed (%2)").arg(result.path, result.message);
|
||||
} else if (result.isInfo()) {
|
||||
success << tr("Import from %1 successful (%2)").arg(result.path, result.message);
|
||||
} else {
|
||||
success << tr("Imported from %1").arg(result.path);
|
||||
}
|
||||
notifyAbout(success, warning, error);
|
||||
}
|
||||
|
||||
ShareObserver::Result ShareObserver::importSingedContainerInto(const KeeShareSettings::Reference& reference,
|
||||
Group* targetGroup)
|
||||
{
|
||||
#if !defined(WITH_XC_KEESHARE_SECURE)
|
||||
Q_UNUSED(targetGroup);
|
||||
return {reference.path, Result::Warning, tr("Signed share container are not supported - import prevented")};
|
||||
#else
|
||||
QuaZip zip(reference.path);
|
||||
if (!zip.open(QuaZip::mdUnzip)) {
|
||||
qCritical("Unable to open file %s.", qPrintable(reference.path));
|
||||
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(reference.path));
|
||||
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 = QSharedPointer<Database>::create();
|
||||
if (!reader.readDatabase(&buffer, key, sourceDb.data())) {
|
||||
qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
|
||||
return {reference.path, Result::Error, reader.errorString()};
|
||||
}
|
||||
|
||||
auto foreign = KeeShare::foreign();
|
||||
auto own = KeeShare::own();
|
||||
auto trust = check(payload, reference, own.certificate, foreign.certificates, sign);
|
||||
switch (trust.first) {
|
||||
case Invalid:
|
||||
qWarning("Prevent untrusted import");
|
||||
return {reference.path, Result::Error, tr("Untrusted import prevented")};
|
||||
|
||||
case UntrustedForever:
|
||||
case TrustedForever: {
|
||||
bool found = false;
|
||||
const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted
|
||||
: KeeShareSettings::Trust::Untrusted;
|
||||
for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) {
|
||||
if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) {
|
||||
scopedCertificate.certificate.signer = trust.second.signer;
|
||||
scopedCertificate.path = reference.path;
|
||||
scopedCertificate.trust = trusted;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
foreign.certificates << KeeShareSettings::ScopedCertificate{reference.path, trust.second, trusted};
|
||||
}
|
||||
// update foreign certificates with new settings
|
||||
KeeShare::setForeign(foreign);
|
||||
|
||||
if (trust.first == TrustedForever) {
|
||||
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 signed import")};
|
||||
}
|
||||
}
|
||||
// Silent ignore of untrusted import or unchanging import
|
||||
return {};
|
||||
}
|
||||
case TrustedOnce:
|
||||
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 signed import")};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
default:
|
||||
Q_ASSERT(false);
|
||||
return {reference.path, Result::Error, tr("Unexpected error")};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ShareObserver::Result ShareObserver::importUnsignedContainerInto(const KeeShareSettings::Reference& reference,
|
||||
Group* targetGroup)
|
||||
{
|
||||
#if !defined(WITH_XC_KEESHARE_INSECURE)
|
||||
Q_UNUSED(targetGroup);
|
||||
return {reference.path, Result::Warning, tr("Unsigned share container are not supported - import prevented")};
|
||||
#else
|
||||
QFile file(reference.path);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
qCritical("Unable to open file %s.", qPrintable(reference.path));
|
||||
return {reference.path, Result::Error, tr("File is not readable")};
|
||||
}
|
||||
auto payload = file.readAll();
|
||||
file.close();
|
||||
QBuffer buffer(&payload);
|
||||
buffer.open(QIODevice::ReadOnly);
|
||||
|
||||
KeePass2Reader reader;
|
||||
auto key = QSharedPointer<CompositeKey>::create();
|
||||
key->addKey(QSharedPointer<PasswordKey>::create(reference.password));
|
||||
auto sourceDb = QSharedPointer<Database>::create();
|
||||
if (!reader.readDatabase(&buffer, key, sourceDb.data())) {
|
||||
qCritical("Error while parsing the database: %s", qPrintable(reader.errorString()));
|
||||
return {reference.path, Result::Error, reader.errorString()};
|
||||
}
|
||||
|
||||
auto foreign = KeeShare::foreign();
|
||||
const auto own = KeeShare::own();
|
||||
const auto sign = KeeShareSettings::Sign(); // invalid sign
|
||||
auto trust = check(payload, reference, own.certificate, foreign.certificates, sign);
|
||||
switch (trust.first) {
|
||||
case UntrustedForever:
|
||||
case TrustedForever: {
|
||||
bool found = false;
|
||||
const auto trusted = trust.first == TrustedForever ? KeeShareSettings::Trust::Trusted
|
||||
: KeeShareSettings::Trust::Untrusted;
|
||||
for (KeeShareSettings::ScopedCertificate& scopedCertificate : foreign.certificates) {
|
||||
if (scopedCertificate.certificate.key == trust.second.key && scopedCertificate.path == reference.path) {
|
||||
scopedCertificate.certificate.signer = trust.second.signer;
|
||||
scopedCertificate.path = reference.path;
|
||||
scopedCertificate.trust = trusted;
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
foreign.certificates << KeeShareSettings::ScopedCertificate{reference.path, trust.second, trusted};
|
||||
}
|
||||
// update foreign certificates with new settings
|
||||
KeeShare::setForeign(foreign);
|
||||
|
||||
if (trust.first == TrustedForever) {
|
||||
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 signed import")};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
case TrustedOnce: {
|
||||
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 unsigned import")};
|
||||
}
|
||||
return {};
|
||||
}
|
||||
default:
|
||||
qWarning("Prevent untrusted import");
|
||||
return {reference.path, Result::Warning, tr("Untrusted import prevented")};
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
ShareObserver::Result ShareObserver::importContainerInto(const KeeShareSettings::Reference& reference,
|
||||
Group* targetGroup)
|
||||
{
|
||||
const QFileInfo info(reference.path);
|
||||
if (!info.exists()) {
|
||||
qCritical("File %s does not exist.", qPrintable(info.absoluteFilePath()));
|
||||
return {reference.path, Result::Warning, tr("File does not exist")};
|
||||
}
|
||||
|
||||
if (isOfExportType(info, KeeShare::signedContainerFileType())) {
|
||||
return importSingedContainerInto(reference, targetGroup);
|
||||
}
|
||||
if (isOfExportType(info, KeeShare::unsignedContainerFileType())) {
|
||||
return importUnsignedContainerInto(reference, targetGroup);
|
||||
}
|
||||
return {reference.path, Result::Error, tr("Unknown share container type")};
|
||||
}
|
||||
|
||||
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) {
|
||||
// changes of inactive references are ignored
|
||||
return {};
|
||||
}
|
||||
if (reference.type == KeeShareSettings::ExportTo) {
|
||||
// changes of export only references are ignored
|
||||
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->rootGroup()->findEntryByUuid(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;
|
||||
}
|
||||
|
||||
QSharedPointer<Database> ShareObserver::database()
|
||||
{
|
||||
return m_db;
|
||||
}
|
||||
|
||||
ShareObserver::Result ShareObserver::exportIntoReferenceSignedContainer(const KeeShareSettings::Reference& reference,
|
||||
Database* targetDb)
|
||||
{
|
||||
#if !defined(WITH_XC_KEESHARE_SECURE)
|
||||
Q_UNUSED(targetDb);
|
||||
return {
|
||||
reference.path, Result::Warning, tr("Overwriting signed share container is not supported - export prevented")};
|
||||
#else
|
||||
QByteArray bytes;
|
||||
{
|
||||
QBuffer buffer(&bytes);
|
||||
buffer.open(QIODevice::WriteOnly);
|
||||
KeePass2Writer writer;
|
||||
writer.writeDatabase(&buffer, targetDb);
|
||||
if (writer.hasError()) {
|
||||
qWarning("Serializing export dabase failed: %s.", writer.errorString().toLatin1().data());
|
||||
return {reference.path, Result::Error, writer.errorString()};
|
||||
}
|
||||
}
|
||||
const auto own = KeeShare::own();
|
||||
QuaZip zip(reference.path);
|
||||
zip.setFileNameCodec("UTF-8");
|
||||
const bool zipOpened = zip.open(QuaZip::mdCreate);
|
||||
if (!zipOpened) {
|
||||
::qWarning("Opening export file failed: %d", zip.getZipError());
|
||||
return {reference.path, Result::Error, tr("Could not write export container (%1)").arg(zip.getZipError())};
|
||||
}
|
||||
{
|
||||
QuaZipFile file(&zip);
|
||||
const auto signatureOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Signature));
|
||||
if (!signatureOpened) {
|
||||
::qWarning("Embedding signature failed: %d", zip.getZipError());
|
||||
return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())};
|
||||
}
|
||||
QTextStream stream(&file);
|
||||
KeeShareSettings::Sign sign;
|
||||
auto sshKey = own.key.sshKey();
|
||||
sshKey.openKey(QString());
|
||||
const Signature signer;
|
||||
sign.signature = signer.create(bytes, sshKey);
|
||||
sign.certificate = own.certificate;
|
||||
stream << KeeShareSettings::Sign::serialize(sign);
|
||||
stream.flush();
|
||||
if (file.getZipError() != ZIP_OK) {
|
||||
::qWarning("Embedding signature failed: %d", zip.getZipError());
|
||||
return {reference.path, Result::Error, tr("Could not embed signature (%1)").arg(file.getZipError())};
|
||||
}
|
||||
file.close();
|
||||
}
|
||||
{
|
||||
QuaZipFile file(&zip);
|
||||
const auto dbOpened = file.open(QIODevice::WriteOnly, QuaZipNewInfo(KeeShare_Container));
|
||||
if (!dbOpened) {
|
||||
::qWarning("Embedding database failed: %d", zip.getZipError());
|
||||
return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())};
|
||||
}
|
||||
if (file.getZipError() != ZIP_OK) {
|
||||
::qWarning("Embedding database failed: %d", zip.getZipError());
|
||||
return {reference.path, Result::Error, tr("Could not embed database (%1)").arg(file.getZipError())};
|
||||
}
|
||||
file.write(bytes);
|
||||
file.close();
|
||||
}
|
||||
zip.close();
|
||||
return {reference.path};
|
||||
#endif
|
||||
}
|
||||
|
||||
ShareObserver::Result ShareObserver::exportIntoReferenceUnsignedContainer(const KeeShareSettings::Reference& reference,
|
||||
Database* targetDb)
|
||||
{
|
||||
#if !defined(WITH_XC_KEESHARE_INSECURE)
|
||||
Q_UNUSED(targetDb);
|
||||
return {reference.path,
|
||||
Result::Warning,
|
||||
tr("Overwriting unsigned share container is not supported - export prevented")};
|
||||
#else
|
||||
QFile file(reference.path);
|
||||
const bool fileOpened = file.open(QIODevice::WriteOnly);
|
||||
if (!fileOpened) {
|
||||
::qWarning("Opening export file failed");
|
||||
return {reference.path, Result::Error, tr("Could not write export container")};
|
||||
}
|
||||
KeePass2Writer writer;
|
||||
writer.writeDatabase(&file, targetDb);
|
||||
if (writer.hasError()) {
|
||||
qWarning("Exporting dabase failed: %s.", writer.errorString().toLatin1().data());
|
||||
return {reference.path, Result::Error, writer.errorString()};
|
||||
}
|
||||
file.close();
|
||||
#endif
|
||||
return {reference.path};
|
||||
}
|
||||
|
||||
QList<ShareObserver::Result> ShareObserver::exportIntoReferenceContainers()
|
||||
{
|
||||
QList<Result> results;
|
||||
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));
|
||||
QFileInfo info(reference.path);
|
||||
if (isOfExportType(info, KeeShare::signedContainerFileType())) {
|
||||
results << exportIntoReferenceSignedContainer(reference, targetDb.data());
|
||||
m_fileWatcher->observeFileChanges(true);
|
||||
continue;
|
||||
}
|
||||
if (isOfExportType(info, KeeShare::unsignedContainerFileType())) {
|
||||
results << exportIntoReferenceUnsignedContainer(reference, targetDb.data());
|
||||
m_fileWatcher->observeFileChanges(true);
|
||||
continue;
|
||||
}
|
||||
Q_ASSERT(false);
|
||||
results << Result{reference.path, Result::Error, tr("Unexpected export error occurred")};
|
||||
}
|
||||
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;
|
||||
}
|
106
src/keeshare/ShareObserver.h
Normal file
@ -0,0 +1,106 @@
|
||||
/*
|
||||
* 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(QSharedPointer<Database> db, QObject* parent = nullptr);
|
||||
~ShareObserver();
|
||||
|
||||
QSharedPointer<Database> database();
|
||||
|
||||
signals:
|
||||
void sharingMessage(QString, MessageWidget::MessageType);
|
||||
|
||||
private slots:
|
||||
void handleDatabaseChanged();
|
||||
void handleDatabaseSaved();
|
||||
void handleFileCreated(const QString& path);
|
||||
void handleFileUpdated(const QString& path);
|
||||
void handleFileDeleted(const QString& path);
|
||||
|
||||
private:
|
||||
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 exportIntoReferenceUnsignedContainer(const KeeShareSettings::Reference& reference,
|
||||
Database* targetDb);
|
||||
static Result exportIntoReferenceSignedContainer(const KeeShareSettings::Reference& reference, Database* targetDb);
|
||||
static Result importSingedContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup);
|
||||
static Result importUnsignedContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup);
|
||||
static Result importContainerInto(const KeeShareSettings::Reference& reference, Group* targetGroup);
|
||||
static Result importDatabaseInto();
|
||||
|
||||
Result importFromReferenceContainer(const QString& path);
|
||||
QList<Result> exportIntoReferenceContainers();
|
||||
|
||||
void deinitialize();
|
||||
void reinitialize();
|
||||
void notifyAbout(const QStringList& success, const QStringList& warning, const QStringList& error);
|
||||
|
||||
private:
|
||||
QSharedPointer<Database> m_db;
|
||||
QMap<KeeShareSettings::Reference, QPointer<Group>> m_referenceToGroup;
|
||||
QMap<QPointer<Group>, KeeShareSettings::Reference> m_groupToReference;
|
||||
QMap<QString, QPointer<Group>> m_shareToGroup;
|
||||
|
||||
BulkFileWatcher* m_fileWatcher;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_SHAREOBSERVER_H
|
260
src/keeshare/Signature.cpp
Normal file
@ -0,0 +1,260 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "Signature.h"
|
||||
#include "core/Tools.h"
|
||||
#include "crypto/Crypto.h"
|
||||
#include "crypto/CryptoHash.h"
|
||||
#include "crypto/ssh/OpenSSHKey.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <gcrypt.h>
|
||||
|
||||
struct RSASigner
|
||||
{
|
||||
gcry_error_t rc;
|
||||
QString error;
|
||||
|
||||
void raiseError(const QString& message = QString())
|
||||
{
|
||||
if (message.isEmpty()) {
|
||||
error = QString("%1/%2").arg(QString::fromLocal8Bit(gcry_strsource(rc)),
|
||||
QString::fromLocal8Bit(gcry_strerror(rc)));
|
||||
} else {
|
||||
error = message;
|
||||
}
|
||||
}
|
||||
|
||||
RSASigner()
|
||||
: rc(GPG_ERR_NO_ERROR)
|
||||
{
|
||||
}
|
||||
|
||||
QString sign(const QByteArray& data, const OpenSSHKey& key)
|
||||
{
|
||||
enum Index
|
||||
{
|
||||
N,
|
||||
E,
|
||||
D,
|
||||
P,
|
||||
Q,
|
||||
U, // private key
|
||||
R,
|
||||
S, // signature
|
||||
|
||||
Data,
|
||||
Key,
|
||||
Sig
|
||||
};
|
||||
|
||||
const QList<QByteArray> parts = key.privateParts();
|
||||
if (parts.count() != 6) {
|
||||
raiseError("Unsupported signing key");
|
||||
return QString();
|
||||
}
|
||||
|
||||
const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256);
|
||||
|
||||
Tools::Map<Index, gcry_mpi_t, &gcry_mpi_release> mpi;
|
||||
Tools::Map<Index, gcry_sexp_t, &gcry_sexp_release> sexp;
|
||||
const gcry_mpi_format format = GCRYMPI_FMT_USG;
|
||||
rc = gcry_mpi_scan(&mpi[N], format, parts[0].data(), parts[0].size(), nullptr);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
rc = gcry_mpi_scan(&mpi[E], format, parts[1].data(), parts[1].size(), nullptr);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
rc = gcry_mpi_scan(&mpi[D], format, parts[2].data(), parts[2].size(), nullptr);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
rc = gcry_mpi_scan(&mpi[U], format, parts[3].data(), parts[3].size(), nullptr);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
rc = gcry_mpi_scan(&mpi[P], format, parts[4].data(), parts[4].size(), nullptr);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
rc = gcry_mpi_scan(&mpi[Q], format, parts[5].data(), parts[5].size(), nullptr);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
if (gcry_mpi_cmp(mpi[P], mpi[Q]) > 0) {
|
||||
// see https://www.gnupg.org/documentation/manuals/gcrypt/RSA-key-parameters.html#RSA-key-parameters
|
||||
gcry_mpi_swap(mpi[P], mpi[Q]);
|
||||
gcry_mpi_invm(mpi[U], mpi[P], mpi[Q]);
|
||||
}
|
||||
rc = gcry_sexp_build(&sexp[Key],
|
||||
NULL,
|
||||
"(private-key (rsa (n %m) (e %m) (d %m) (p %m) (q %m) (u %m)))",
|
||||
mpi[N],
|
||||
mpi[E],
|
||||
mpi[D],
|
||||
mpi[P],
|
||||
mpi[Q],
|
||||
mpi[U]);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
|
||||
rc = gcry_pk_testkey(sexp[Key]);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
|
||||
rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags pkcs1) (hash sha256 %b))", block.size(), block.data());
|
||||
// rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags raw) (value %b))", data.size(), data.data());
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
rc = gcry_pk_sign(&sexp[Sig], sexp[Data], sexp[Key]);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
sexp[S] = gcry_sexp_find_token(sexp[Sig], "s", 1);
|
||||
mpi[S] = gcry_sexp_nth_mpi(sexp[S], 1, GCRYMPI_FMT_USG);
|
||||
Tools::Buffer buffer;
|
||||
rc = gcry_mpi_aprint(GCRYMPI_FMT_STD, &buffer.raw, &buffer.size, mpi[S]);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return QString();
|
||||
}
|
||||
return QString("rsa|%1").arg(QString::fromLatin1(buffer.content().toHex()));
|
||||
}
|
||||
|
||||
bool verify(const QByteArray& data, const OpenSSHKey& key, const QString& signature)
|
||||
{
|
||||
const gcry_mpi_format format = GCRYMPI_FMT_USG;
|
||||
enum MPI
|
||||
{
|
||||
N,
|
||||
E, // public key
|
||||
R,
|
||||
S // signature
|
||||
};
|
||||
enum SEXP
|
||||
{
|
||||
Data,
|
||||
Key,
|
||||
Sig
|
||||
};
|
||||
|
||||
const QList<QByteArray> parts = key.publicParts();
|
||||
if (parts.count() != 2) {
|
||||
raiseError("Unsupported verification key");
|
||||
return false;
|
||||
}
|
||||
|
||||
const QByteArray block = CryptoHash::hash(data, CryptoHash::Sha256);
|
||||
|
||||
Tools::Map<MPI, gcry_mpi_t, &gcry_mpi_release> mpi;
|
||||
Tools::Map<SEXP, gcry_sexp_t, &gcry_sexp_release> sexp;
|
||||
|
||||
rc = gcry_mpi_scan(&mpi[E], format, parts[0].data(), parts[0].size(), nullptr);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return false;
|
||||
}
|
||||
rc = gcry_mpi_scan(&mpi[N], format, parts[1].data(), parts[1].size(), nullptr);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return false;
|
||||
}
|
||||
rc = gcry_sexp_build(&sexp[Key], NULL, "(public-key (rsa (n %m) (e %m)))", mpi[N], mpi[E]);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return false;
|
||||
}
|
||||
|
||||
QRegExp extractor("rsa\\|([a-f0-9]+)", Qt::CaseInsensitive);
|
||||
if (!extractor.exactMatch(signature) || extractor.captureCount() != 1) {
|
||||
raiseError("Could not unpack signature parts");
|
||||
return false;
|
||||
}
|
||||
const QByteArray sig_s = QByteArray::fromHex(extractor.cap(1).toLatin1());
|
||||
|
||||
rc = gcry_mpi_scan(&mpi[S], GCRYMPI_FMT_STD, sig_s.data(), sig_s.size(), nullptr);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return false;
|
||||
}
|
||||
rc = gcry_sexp_build(&sexp[Sig], NULL, "(sig-val (rsa (s %m)))", mpi[S]);
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return false;
|
||||
}
|
||||
rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags pkcs1) (hash sha256 %b))", block.size(), block.data());
|
||||
// rc = gcry_sexp_build(&sexp[Data], NULL, "(data (flags raw) (value %b))", data.size(), data.data());
|
||||
if (rc != GPG_ERR_NO_ERROR) {
|
||||
raiseError();
|
||||
return false;
|
||||
}
|
||||
rc = gcry_pk_verify(sexp[Sig], sexp[Data], sexp[Key]);
|
||||
if (rc != GPG_ERR_NO_ERROR && rc != GPG_ERR_BAD_SIGNATURE) {
|
||||
raiseError();
|
||||
return false;
|
||||
}
|
||||
return rc != GPG_ERR_BAD_SIGNATURE;
|
||||
}
|
||||
};
|
||||
|
||||
QString Signature::create(const QByteArray& data, const OpenSSHKey& key)
|
||||
{
|
||||
// TODO HNH: currently we publish the signature in our own non-standard format - it would
|
||||
// be better to use a standard format (like ASN1 - but this would be more easy
|
||||
// when we integrate a proper library)
|
||||
// Even more, we could publish standard self signed certificates with the container
|
||||
// instead of the custom certificates
|
||||
if (key.type() == "ssh-rsa") {
|
||||
RSASigner signer;
|
||||
QString result = signer.sign(data, key);
|
||||
if (signer.rc != GPG_ERR_NO_ERROR) {
|
||||
::qWarning() << signer.error;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
::qWarning() << "Unsupported Public/Private key format";
|
||||
return QString();
|
||||
}
|
||||
|
||||
bool Signature::verify(const QByteArray& data, const QString& signature, const OpenSSHKey& key)
|
||||
{
|
||||
if (key.type() == "ssh-rsa") {
|
||||
RSASigner signer;
|
||||
bool result = signer.verify(data, key, signature);
|
||||
if (signer.rc != GPG_ERR_NO_ERROR) {
|
||||
::qWarning() << signer.error;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
::qWarning() << "Unsupported Public/Private key format";
|
||||
return false;
|
||||
}
|
34
src/keeshare/Signature.h
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef KEEPASSXC_SIGNATURE_H
|
||||
#define KEEPASSXC_SIGNATURE_H
|
||||
|
||||
#include <QString>
|
||||
#include <gcrypt.h>
|
||||
|
||||
class QByteArray;
|
||||
class OpenSSHKey;
|
||||
|
||||
class Signature
|
||||
{
|
||||
public:
|
||||
static QString create(const QByteArray& data, const OpenSSHKey& key);
|
||||
static bool verify(const QByteArray& data, const QString& signature, const OpenSSHKey& key);
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_SIGNATURE_H
|
55
src/keeshare/group/EditGroupPageKeeShare.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright (C) 2018 KeePassXC Team <team@keepassxc.org>
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 or (at your option)
|
||||
* version 3 of the License.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "EditGroupPageKeeShare.h"
|
||||
|
||||
#include "core/FilePath.h"
|
||||
#include "keeshare/group/EditGroupWidgetKeeShare.h"
|
||||
|
||||
#include <QApplication>
|
||||
|
||||
EditGroupPageKeeShare::EditGroupPageKeeShare(EditGroupWidget* widget)
|
||||
{
|
||||
Q_UNUSED(widget);
|
||||
}
|
||||
|
||||
QString EditGroupPageKeeShare::name()
|
||||
{
|
||||
return QApplication::tr("KeeShare");
|
||||
}
|
||||
|
||||
QIcon EditGroupPageKeeShare::icon()
|
||||
{
|
||||
return FilePath::instance()->icon("apps", "preferences-system-network-sharing");
|
||||
}
|
||||
|
||||
QWidget* EditGroupPageKeeShare::createWidget()
|
||||
{
|
||||
return new EditGroupWidgetKeeShare();
|
||||
}
|
||||
|
||||
void EditGroupPageKeeShare::set(QWidget* widget, Group* temporaryGroup)
|
||||
{
|
||||
EditGroupWidgetKeeShare* settingsWidget = reinterpret_cast<EditGroupWidgetKeeShare*>(widget);
|
||||
settingsWidget->setGroup(temporaryGroup);
|
||||
}
|
||||
|
||||
void EditGroupPageKeeShare::assign(QWidget* widget)
|
||||
{
|
||||
Q_UNUSED(widget);
|
||||
// everything is saved directly
|
||||
}
|
37
src/keeshare/group/EditGroupPageKeeShare.h
Normal file
@ -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
|
304
src/keeshare/group/EditGroupWidgetKeeShare.cpp
Normal file
@ -0,0 +1,304 @@
|
||||
/*
|
||||
* 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();
|
||||
m_ui->messageWidget->setCloseButtonVisible(false);
|
||||
m_ui->messageWidget->setAutoHideTimeout(-1);
|
||||
|
||||
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(editingFinished()), SLOT(selectPath()));
|
||||
connect(m_ui->pathSelectionButton, SIGNAL(pressed()), SLOT(launchPathSelectionDialog()));
|
||||
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(groupModified()), SLOT(update()));
|
||||
}
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
void EditGroupWidgetKeeShare::showSharingState()
|
||||
{
|
||||
if (!m_temporaryGroup) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto supportedExtensions = QStringList();
|
||||
#if defined(WITH_XC_KEESHARE_INSECURE)
|
||||
supportedExtensions << KeeShare::unsignedContainerFileType();
|
||||
#endif
|
||||
#if defined(WITH_XC_KEESHARE_SECURE)
|
||||
supportedExtensions << KeeShare::signedContainerFileType();
|
||||
#endif
|
||||
const auto reference = KeeShare::referenceOf(m_temporaryGroup);
|
||||
if (!reference.path.isEmpty()) {
|
||||
bool supported = false;
|
||||
for (const auto& extension : supportedExtensions) {
|
||||
if (reference.path.endsWith(extension, Qt::CaseInsensitive)) {
|
||||
supported = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!supported) {
|
||||
m_ui->messageWidget->showMessage(
|
||||
tr("Your KeePassXC version does not support sharing your container type. Please use %1.")
|
||||
.arg(supportedExtensions.join(", ")),
|
||||
MessageWidget::Warning);
|
||||
return;
|
||||
} else {
|
||||
m_ui->messageWidget->hide();
|
||||
}
|
||||
}
|
||||
const auto active = KeeShare::active();
|
||||
if (!active.in && !active.out) {
|
||||
m_ui->messageWidget->showMessage(tr("Database sharing is disabled"), MessageWidget::Information);
|
||||
return;
|
||||
}
|
||||
if (active.in && !active.out) {
|
||||
m_ui->messageWidget->showMessage(tr("Database export is disabled"), MessageWidget::Information);
|
||||
return;
|
||||
}
|
||||
if (!active.in && active.out) {
|
||||
m_ui->messageWidget->showMessage(tr("Database import is disabled"), MessageWidget::Information);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void EditGroupWidgetKeeShare::update()
|
||||
{
|
||||
if (!m_temporaryGroup) {
|
||||
m_ui->passwordEdit->clear();
|
||||
m_ui->pathEdit->clear();
|
||||
} 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();
|
||||
}
|
||||
|
||||
m_ui->passwordGenerator->hide();
|
||||
m_ui->togglePasswordGeneratorButton->setChecked(false);
|
||||
m_ui->togglePasswordButton->setChecked(false);
|
||||
}
|
||||
|
||||
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::selectPath()
|
||||
{
|
||||
if (!m_temporaryGroup) {
|
||||
return;
|
||||
}
|
||||
auto reference = KeeShare::referenceOf(m_temporaryGroup);
|
||||
reference.path = m_ui->pathEdit->text();
|
||||
KeeShare::setReferenceTo(m_temporaryGroup, reference);
|
||||
}
|
||||
|
||||
void EditGroupWidgetKeeShare::launchPathSelectionDialog()
|
||||
{
|
||||
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);
|
||||
QString defaultFiletype = "";
|
||||
auto supportedExtensions = QStringList();
|
||||
auto unsupportedExtensions = QStringList();
|
||||
auto knownFilters = QStringList() << QString("%1 (*)").arg("All files");
|
||||
#if defined(WITH_XC_KEESHARE_INSECURE)
|
||||
defaultFiletype = KeeShare::unsignedContainerFileType();
|
||||
supportedExtensions << KeeShare::unsignedContainerFileType();
|
||||
knownFilters.prepend(
|
||||
QString("%1 (*.%2)").arg(tr("KeeShare unsigned container"), KeeShare::unsignedContainerFileType()));
|
||||
#else
|
||||
unsupportedExtensions << KeeShare::unsignedContainerFileType();
|
||||
#endif
|
||||
#if defined(WITH_XC_KEESHARE_SECURE)
|
||||
defaultFiletype = KeeShare::signedContainerFileType();
|
||||
supportedExtensions << KeeShare::signedContainerFileType();
|
||||
knownFilters.prepend(
|
||||
QString("%1 (*.%2)").arg(tr("KeeShare signed container"), KeeShare::signedContainerFileType()));
|
||||
#else
|
||||
unsupportedExtensions << KeeShare::signedContainerFileType();
|
||||
#endif
|
||||
|
||||
const auto filters = knownFilters.join(";;");
|
||||
auto filename = reference.path;
|
||||
if (filename.isEmpty()) {
|
||||
filename = m_temporaryGroup->name();
|
||||
}
|
||||
switch (reference.type) {
|
||||
case KeeShareSettings::ImportFrom:
|
||||
filename = fileDialog()->getFileName(this,
|
||||
tr("Select import source"),
|
||||
defaultDirPath,
|
||||
filters,
|
||||
nullptr,
|
||||
QFileDialog::DontConfirmOverwrite,
|
||||
defaultFiletype,
|
||||
filename);
|
||||
break;
|
||||
case KeeShareSettings::ExportTo:
|
||||
filename = fileDialog()->getFileName(this,
|
||||
tr("Select export target"),
|
||||
defaultDirPath,
|
||||
filters,
|
||||
nullptr,
|
||||
QFileDialog::Option(0),
|
||||
defaultFiletype,
|
||||
filename);
|
||||
break;
|
||||
case KeeShareSettings::SynchronizeWith:
|
||||
case KeeShareSettings::Inactive:
|
||||
filename = fileDialog()->getFileName(this,
|
||||
tr("Select import/export file"),
|
||||
defaultDirPath,
|
||||
filters,
|
||||
nullptr,
|
||||
QFileDialog::Option(0),
|
||||
defaultFiletype,
|
||||
filename);
|
||||
break;
|
||||
}
|
||||
|
||||
if (filename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
bool validFilename = false;
|
||||
for (const auto& extension : supportedExtensions + unsupportedExtensions) {
|
||||
if (filename.endsWith(extension, Qt::CaseInsensitive)) {
|
||||
validFilename = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!validFilename) {
|
||||
filename += (!filename.endsWith(".") ? "." : "") + defaultFiletype;
|
||||
}
|
||||
|
||||
m_ui->pathEdit->setText(filename);
|
||||
selectPath();
|
||||
config()->set("KeeShare/LastShareDir", QFileInfo(filename).absolutePath());
|
||||
}
|
||||
|
||||
void EditGroupWidgetKeeShare::selectPassword()
|
||||
{
|
||||
if (!m_temporaryGroup) {
|
||||
return;
|
||||
}
|
||||
auto reference = KeeShare::referenceOf(m_temporaryGroup);
|
||||
reference.password = m_ui->passwordEdit->text();
|
||||
KeeShare::setReferenceTo(m_temporaryGroup, reference);
|
||||
}
|
||||
|
||||
void EditGroupWidgetKeeShare::selectType()
|
||||
{
|
||||
if (!m_temporaryGroup) {
|
||||
return;
|
||||
}
|
||||
auto reference = KeeShare::referenceOf(m_temporaryGroup);
|
||||
reference.type = static_cast<KeeShareSettings::Type>(m_ui->typeComboBox->currentData().toInt());
|
||||
KeeShare::setReferenceTo(m_temporaryGroup, reference);
|
||||
}
|
59
src/keeshare/group/EditGroupWidgetKeeShare.h
Normal file
@ -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 launchPathSelectionDialog();
|
||||
void selectPath();
|
||||
void setGeneratedPassword(const QString& password);
|
||||
void togglePasswordGeneratorButton(bool checked);
|
||||
|
||||
private:
|
||||
QScopedPointer<Ui::EditGroupWidgetKeeShare> m_ui;
|
||||
QPointer<Group> m_temporaryGroup;
|
||||
};
|
||||
|
||||
#endif // KEEPASSXC_EDITGROUPWIDGETKEESHARE_H
|