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

github.com/keepassxreboot/keepassxc.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorvarjolintu <sami.vanttinen@protonmail.com>2017-12-12 11:15:23 +0300
committerJanek Bevendorff <janek@jbev.net>2018-01-04 23:42:20 +0300
commit06518c57361f366d7d4b00fc9cb441dae57174c5 (patch)
tree44373fe0eba4398a4cad67da4b3746cc3922d721
parenta7e6e4ef45874b93cfdc53510fb7d32bb207e401 (diff)
keepassxc-browser
-rw-r--r--CMakeLists.txt4
-rw-r--r--Dockerfile6
-rw-r--r--INSTALL.md12
-rw-r--r--README.md45
-rw-r--r--cmake/Findsodium.cmake267
-rw-r--r--snapcraft.yaml3
-rw-r--r--src/CMakeLists.txt31
-rwxr-xr-xsrc/browser/BrowserAccessControlDialog.cpp59
-rwxr-xr-xsrc/browser/BrowserAccessControlDialog.h48
-rwxr-xr-xsrc/browser/BrowserAccessControlDialog.ui69
-rwxr-xr-xsrc/browser/BrowserAction.cpp546
-rwxr-xr-xsrc/browser/BrowserAction.h97
-rwxr-xr-xsrc/browser/BrowserClients.cpp77
-rwxr-xr-xsrc/browser/BrowserClients.h56
-rw-r--r--src/browser/BrowserEntryConfig.cpp109
-rw-r--r--src/browser/BrowserEntryConfig.h60
-rwxr-xr-xsrc/browser/BrowserOptionDialog.cpp104
-rwxr-xr-xsrc/browser/BrowserOptionDialog.h50
-rwxr-xr-xsrc/browser/BrowserOptionDialog.ui327
-rw-r--r--src/browser/BrowserService.cpp734
-rw-r--r--src/browser/BrowserService.h82
-rwxr-xr-xsrc/browser/BrowserSettings.cpp377
-rwxr-xr-xsrc/browser/BrowserSettings.h105
-rwxr-xr-xsrc/browser/CMakeLists.txt37
-rw-r--r--src/browser/HostInstaller.cpp230
-rw-r--r--src/browser/HostInstaller.h63
-rw-r--r--src/browser/NativeMessagingBase.cpp141
-rw-r--r--src/browser/NativeMessagingBase.h61
-rwxr-xr-xsrc/browser/NativeMessagingHost.cpp200
-rwxr-xr-xsrc/browser/NativeMessagingHost.h67
-rw-r--r--src/browser/Variant.cpp37
-rw-r--r--src/browser/Variant.h25
-rw-r--r--src/config-keepassx.h.cmake1
-rw-r--r--src/core/PassphraseGenerator.cpp13
-rw-r--r--src/core/PassphraseGenerator.h3
-rw-r--r--src/gui/AboutDialog.cpp3
-rw-r--r--src/gui/MainWindow.cpp57
-rw-r--r--src/gui/PasswordGeneratorWidget.cpp5
-rw-r--r--src/main.cpp15
-rwxr-xr-xsrc/proxy/CMakeLists.txt34
-rwxr-xr-xsrc/proxy/NativeMessagingHost.cpp95
-rwxr-xr-xsrc/proxy/NativeMessagingHost.h43
-rw-r--r--src/proxy/keepassxc-proxy.cpp66
43 files changed, 4422 insertions, 42 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 77f4d8624..1d5c198ee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -44,6 +44,7 @@ option(WITH_XC_AUTOTYPE "Include Auto-Type." ON)
option(WITH_XC_HTTP "Include KeePassHTTP and Custom Icon Downloads." OFF)
option(WITH_XC_YUBIKEY "Include YubiKey support." OFF)
option(WITH_XC_SSHAGENT "Include SSH agent support." OFF)
+option(WITH_XC_BROWSER "Include browser extension support." OFF)
# Process ui files automatically from source files
set(CMAKE_AUTOUIC ON)
@@ -200,11 +201,13 @@ endif()
if(MINGW)
set(CLI_INSTALL_DIR ".")
+ set(PROXY_INSTALL_DIR ".")
set(BIN_INSTALL_DIR ".")
set(PLUGIN_INSTALL_DIR ".")
set(DATA_INSTALL_DIR "share")
elseif(APPLE AND WITH_APP_BUNDLE)
set(CLI_INSTALL_DIR "/usr/local/bin")
+ set(PROXY_INSTALL_DIR "/usr/local/bin")
set(BIN_INSTALL_DIR ".")
set(PLUGIN_INSTALL_DIR "${PROGNAME}.app/Contents/PlugIns")
set(DATA_INSTALL_DIR "${PROGNAME}.app/Contents/Resources")
@@ -212,6 +215,7 @@ else()
include(GNUInstallDirs)
set(CLI_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
+ set(PROXY_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
set(BIN_INSTALL_DIR "${CMAKE_INSTALL_BINDIR}")
set(PLUGIN_INSTALL_DIR "${CMAKE_INSTALL_LIBDIR}/keepassxc")
set(DATA_INSTALL_DIR "${CMAKE_INSTALL_DATADIR}/keepassxc")
diff --git a/Dockerfile b/Dockerfile
index 66ecb4c04..c8c637c07 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -25,7 +25,8 @@ RUN set -x \
RUN set -x \
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \
- && add-apt-repository ppa:phoerious/keepassxc
+ && add-apt-repository ppa:phoerious/keepassxc \
+ && LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
RUN set -x \
&& apt-get update -y \
@@ -46,7 +47,8 @@ RUN set -x \
libxtst-dev \
mesa-common-dev \
libyubikey-dev \
- libykpers-1-dev
+ libykpers-1-dev \
+ libsodium-dev
ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
diff --git a/INSTALL.md b/INSTALL.md
index 2d3f7cb2b..0bfa86b2c 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -6,7 +6,7 @@ You can visit the online version of this document at the following link:
https://github.com/keepassxreboot/keepassx/wiki/Install-Instruction-from-Source
-The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using KeePassXC on your
+The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using KeePassXC on your
Windows, Mac, or Linux computer using the pre-built binaries.
Build Dependencies
@@ -25,6 +25,7 @@ The following libraries are required:
* zlib
* libmicrohttpd
* libxi, libxtst, qtx11extras (optional for auto-type on X11)
+* libsodium (>= 1.0.12, optional for keepassxc-browser support)
Prepare the Building Environment
@@ -40,7 +41,7 @@ Build Steps
To compile from source, open a **Terminal (on Linux/MacOS)** or a **MSYS2-MinGW shell (on Windows)**<br/>
**Note:** on Windows make sure you are using a **MINGW shell** by checking the label before the current path
-First, download the KeePassXC [source tarball](https://keepassxc.org/download#source)
+First, download the KeePassXC [source tarball](https://keepassxc.org/download#source)
or check out the latest version from our [Git repository](https://github.com/keepassxreboot/keepassxc).
To clone the project from Git, `cd` to a suitable location and run
@@ -66,10 +67,10 @@ cd build
cmake -DWITH_TESTS=OFF ...and other options - see below...
make
```
-These steps place the compiled KeePassXC binary inside the `./build/src/` directory.
+These steps place the compiled KeePassXC binary inside the `./build/src/` directory.
(Note the cmake notes/options below.)
-**Cmake Notes:**
+**Cmake Notes:**
* Common cmake parameters
@@ -86,7 +87,8 @@ These steps place the compiled KeePassXC binary inside the `./build/src/` direct
-DWITH_XC_AUTOTYPE=[ON|OFF] Enable/Disable Auto-Type (default: ON)
-DWITH_XC_HTTP=[ON|OFF] Enable/Disable KeePassHTTP and custom icon downloads (default: OFF)
-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_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)
diff --git a/README.md b/README.md
index 685ebee0b..5c2373259 100644
--- a/README.md
+++ b/README.md
@@ -1,19 +1,19 @@
-# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC
+# <img src="https://keepassxc.org/logo.png" width="40" height="40"/> KeePassXC
[![TeamCity Build Status](https://ci.keepassxc.org/app/rest/builds/buildType:\(id:KeepassXC_TeamCityCi\)/statusIcon?guest=1)](https://ci.keepassxc.org/viewType.html?buildTypeId=KeepassXC_TeamCityCi&guest=1) [![Coverage Status](https://coveralls.io/repos/github/keepassxreboot/keepassxc/badge.svg)](https://coveralls.io/github/keepassxreboot/keepassxc)
## About KeePassXC
[KeePassXC](https://keepassxc.org) is a cross-platform community fork of
-[KeePassX](https://www.keepassx.org/).
-Our goal is to extend and improve it with new features and bugfixes
-to provide a feature-rich, fully cross-platform and modern
+[KeePassX](https://www.keepassx.org/).
+Our goal is to extend and improve it with new features and bugfixes
+to provide a feature-rich, fully cross-platform and modern
open-source password manager.
## Installation
-The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using
-KeePassXC on your Windows, Mac, or Linux computer using pre-compiled binaries
-from the [downloads page](https://keepassxc.org/download).
-
-Additionally, individual Linux distributions may ship their own versions,
+The [KeePassXC QuickStart](./docs/QUICKSTART.md) gets you started using
+KeePassXC on your Windows, Mac, or Linux computer using pre-compiled binaries
+from the [downloads page](https://keepassxc.org/download).
+
+Additionally, individual Linux distributions may ship their own versions,
so please check out your distribution's package list to see if KeePassXC is available.
## Additional features compared to KeePassX
@@ -29,24 +29,25 @@ so please check out your distribution's package list to see if KeePassXC is avai
- Using website favicons as entry icons
- Merging of databases
- Automatic reload when the database changed on disk
-- Browser integration with KeePassHTTP-Connector for
-[Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and
-[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 KeePassHTTP-Connector for
+[Mozilla Firefox](https://addons.mozilla.org/en-US/firefox/addon/keepasshttp-connector/) and
+[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/iopaggbpplllidnfmcghoonnokmjoicf)
- Many bug fixes
For a full list of features and changes, read the [CHANGELOG](CHANGELOG) document.
## Building KeePassXC
-Detailed instructions are available in the [Build and Install](./INSTALL.md)
+Detailed instructions are available in the [Build and Install](./INSTALL.md)
page or on the [Wiki page](https://github.com/keepassxreboot/keepassxc/wiki/Building-KeePassXC).
## Contributing
-We are always looking for suggestions how to improve our application.
-If you find any bugs or have an idea for a new feature, please let us know by
-opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues)
+We are always looking for suggestions how to improve our application.
+If you find any bugs or have an idea for a new feature, please let us know by
+opening a report in our [issue tracker](https://github.com/keepassxreboot/keepassxc/issues)
on GitHub or join us on IRC on freenode channels #keepassxc or #keepassxc-dev.
You can of course also directly contribute your own code. We are happy to accept your pull requests.
@@ -57,11 +58,11 @@ Please read the [CONTRIBUTING document](.github/CONTRIBUTING.md) for further inf
The KeePassHTTP protocol is not a highly secure protocol.
It has a certain flaw which could allow an attacker to decrypt your passwords
should they manage to impersonate the web browser extension from a remote address.
-<!--intercept communication between a KeePassHTTP server
+<!--intercept communication between a KeePassHTTP server
and PassIFox/chromeIPass over a network connection -->
-(See [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)).
+(See [here](https://github.com/pfn/keepasshttp/issues/258) and [here](https://github.com/keepassxreboot/keepassxc/issues/147)).
-To minimize the risk, KeePassXC strictly limits communication between itself
-and the browser plugin to your local computer (localhost).
-This makes your passwords quite safe,
+To minimize the risk, KeePassXC strictly limits communication between itself
+and the browser plugin to your local computer (localhost).
+This makes your passwords quite safe,
but as with all open source software, use it at your own risk!
diff --git a/cmake/Findsodium.cmake b/cmake/Findsodium.cmake
new file mode 100644
index 000000000..ad7031e5a
--- /dev/null
+++ b/cmake/Findsodium.cmake
@@ -0,0 +1,267 @@
+# Written in 2016 by Henrik Steffen Gaßmann <henrik@gassmann.onl>
+#
+# To the extent possible under law, the author(s) have dedicated all
+# copyright and related and neighboring rights to this software to the
+# public domain worldwide. This software is distributed without any warranty.
+#
+# You should have received a copy of the CC0 Public Domain Dedication
+# along with this software. If not, see
+#
+# http://creativecommons.org/publicdomain/zero/1.0/
+#
+########################################################################
+# Tries to find the local libsodium installation.
+#
+# On Windows the sodium_DIR environment variable is used as a default
+# hint which can be overridden by setting the corresponding cmake variable.
+#
+# Once done the following variables will be defined:
+#
+# sodium_FOUND
+# sodium_INCLUDE_DIR
+# sodium_LIBRARY_DEBUG
+# sodium_LIBRARY_RELEASE
+#
+#
+# Furthermore an imported "sodium" target is created.
+#
+
+if (CMAKE_C_COMPILER_ID STREQUAL "GNU"
+ OR CMAKE_C_COMPILER_ID STREQUAL "Clang")
+ set(_GCC_COMPATIBLE 1)
+endif()
+
+# static library option
+option(sodium_USE_STATIC_LIBS "enable to statically link against sodium")
+if(NOT (sodium_USE_STATIC_LIBS EQUAL sodium_USE_STATIC_LIBS_LAST))
+ unset(sodium_LIBRARY CACHE)
+ unset(sodium_LIBRARY_DEBUG CACHE)
+ unset(sodium_LIBRARY_RELEASE CACHE)
+ unset(sodium_DLL_DEBUG CACHE)
+ unset(sodium_DLL_RELEASE CACHE)
+ set(sodium_USE_STATIC_LIBS_LAST ${sodium_USE_STATIC_LIBS} CACHE INTERNAL "internal change tracking variable")
+endif()
+
+
+########################################################################
+# UNIX
+if (UNIX)
+ # import pkg-config
+ find_package(PkgConfig QUIET)
+ if (PKG_CONFIG_FOUND)
+ pkg_check_modules(sodium_PKG QUIET libsodium)
+ endif()
+
+ if(sodium_USE_STATIC_LIBS)
+ set(XPREFIX sodium_PKG_STATIC)
+ else()
+ set(XPREFIX sodium_PKG)
+ endif()
+
+ find_path(sodium_INCLUDE_DIR sodium.h
+ HINTS ${${XPREFIX}_INCLUDE_DIRS}
+ )
+ find_library(sodium_LIBRARY_DEBUG NAMES ${${XPREFIX}_LIBRARIES} sodium
+ HINTS ${${XPREFIX}_LIBRARY_DIRS}
+ )
+ find_library(sodium_LIBRARY_RELEASE NAMES ${${XPREFIX}_LIBRARIES} sodium
+ HINTS ${${XPREFIX}_LIBRARY_DIRS}
+ )
+
+
+########################################################################
+# Windows
+elseif (WIN32)
+ set(sodium_DIR "$ENV{sodium_DIR}" CACHE FILEPATH "sodium install directory")
+ mark_as_advanced(sodium_DIR)
+
+ find_path(sodium_INCLUDE_DIR sodium.h
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES include
+ )
+
+ if (MSVC)
+ # detect target architecture
+ file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/arch.c" [=[
+ #if defined _M_IX86
+ #error ARCH_VALUE x86_32
+ #elif defined _M_X64
+ #error ARCH_VALUE x86_64
+ #endif
+ #error ARCH_VALUE unknown
+ ]=])
+ try_compile(_UNUSED_VAR "${CMAKE_CURRENT_BINARY_DIR}" "${CMAKE_CURRENT_BINARY_DIR}/arch.c"
+ OUTPUT_VARIABLE _COMPILATION_LOG
+ )
+ string(REGEX REPLACE ".*ARCH_VALUE ([a-zA-Z0-9_]+).*" "\\1" _TARGET_ARCH "${_COMPILATION_LOG}")
+
+ # construct library path
+ if (_TARGET_ARCH STREQUAL "x86_32")
+ string(APPEND _PLATFORM_PATH "Win32")
+ elseif(_TARGET_ARCH STREQUAL "x86_64")
+ string(APPEND _PLATFORM_PATH "x64")
+ else()
+ message(FATAL_ERROR "the ${_TARGET_ARCH} architecture is not supported by Findsodium.cmake.")
+ endif()
+ string(APPEND _PLATFORM_PATH "/$$CONFIG$$")
+
+ if (MSVC_VERSION LESS 1900)
+ math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 60")
+ else()
+ math(EXPR _VS_VERSION "${MSVC_VERSION} / 10 - 50")
+ endif()
+ string(APPEND _PLATFORM_PATH "/v${_VS_VERSION}")
+
+ if (sodium_USE_STATIC_LIBS)
+ string(APPEND _PLATFORM_PATH "/static")
+ else()
+ string(APPEND _PLATFORM_PATH "/dynamic")
+ endif()
+
+ string(REPLACE "$$CONFIG$$" "Debug" _DEBUG_PATH_SUFFIX "${_PLATFORM_PATH}")
+ string(REPLACE "$$CONFIG$$" "Release" _RELEASE_PATH_SUFFIX "${_PLATFORM_PATH}")
+
+ find_library(sodium_LIBRARY_DEBUG libsodium.lib
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
+ )
+ find_library(sodium_LIBRARY_RELEASE libsodium.lib
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
+ )
+ if (NOT sodium_USE_STATIC_LIBS)
+ set(CMAKE_FIND_LIBRARY_SUFFIXES ".dll")
+ find_library(sodium_DLL_DEBUG libsodium
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES ${_DEBUG_PATH_SUFFIX}
+ )
+ find_library(sodium_DLL_RELEASE libsodium
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES ${_RELEASE_PATH_SUFFIX}
+ )
+ endif()
+
+ elseif(_GCC_COMPATIBLE)
+ if (sodium_USE_STATIC_LIBS)
+ find_library(sodium_LIBRARY_DEBUG libsodium.a
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES lib
+ )
+ find_library(sodium_LIBRARY_RELEASE libsodium.a
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES lib
+ )
+ else()
+ find_library(sodium_LIBRARY_DEBUG libsodium.dll.a
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES lib
+ )
+ find_library(sodium_LIBRARY_RELEASE libsodium.dll.a
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES lib
+ )
+
+ file(GLOB _DLL
+ LIST_DIRECTORIES false
+ RELATIVE "${sodium_DIR}/bin"
+ "${sodium_DIR}/bin/libsodium*.dll"
+ )
+ find_library(sodium_DLL_DEBUG ${_DLL} libsodium
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES bin
+ )
+ find_library(sodium_DLL_RELEASE ${_DLL} libsodium
+ HINTS ${sodium_DIR}
+ PATH_SUFFIXES bin
+ )
+ endif()
+ else()
+ message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
+ endif()
+
+
+########################################################################
+# unsupported
+else()
+ message(FATAL_ERROR "this platform is not supported by FindSodium.cmake")
+endif()
+
+
+########################################################################
+# common stuff
+
+# extract sodium version
+if (sodium_INCLUDE_DIR)
+ set(_VERSION_HEADER "${_INCLUDE_DIR}/sodium/version.h")
+ if (EXISTS _VERSION_HEADER)
+ file(READ "${_VERSION_HEADER}" _VERSION_HEADER_CONTENT)
+ string(REGEX REPLACE ".*#[ \t]*define[ \t]*SODIUM_VERSION_STRING[ \t]*\"([^\n]*)\".*" "\\1"
+ sodium_VERSION "${_VERSION_HEADER_CONTENT}")
+ set(sodium_VERSION "${sodium_VERSION}" PARENT_SCOPE)
+ endif()
+endif()
+
+# communicate results
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(sodium
+ REQUIRED_VARS
+ sodium_LIBRARY_RELEASE
+ sodium_LIBRARY_DEBUG
+ sodium_INCLUDE_DIR
+ VERSION_VAR
+ sodium_VERSION
+)
+
+# mark file paths as advanced
+mark_as_advanced(sodium_INCLUDE_DIR)
+mark_as_advanced(sodium_LIBRARY_DEBUG)
+mark_as_advanced(sodium_LIBRARY_RELEASE)
+if (WIN32)
+ mark_as_advanced(sodium_DLL_DEBUG)
+ mark_as_advanced(sodium_DLL_RELEASE)
+endif()
+
+# create imported target
+if(sodium_USE_STATIC_LIBS)
+ set(_LIB_TYPE STATIC)
+else()
+ set(_LIB_TYPE SHARED)
+endif()
+add_library(sodium ${_LIB_TYPE} IMPORTED)
+
+set_target_properties(sodium PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${sodium_INCLUDE_DIR}"
+ IMPORTED_LINK_INTERFACE_LANGUAGES "C"
+)
+
+if (sodium_USE_STATIC_LIBS)
+ set_target_properties(sodium PROPERTIES
+ INTERFACE_COMPILE_DEFINITIONS "SODIUM_STATIC"
+ IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
+ IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
+ )
+else()
+ if (UNIX)
+ set_target_properties(sodium PROPERTIES
+ IMPORTED_LOCATION "${sodium_LIBRARY_RELEASE}"
+ IMPORTED_LOCATION_DEBUG "${sodium_LIBRARY_DEBUG}"
+ )
+ elseif (WIN32)
+ set_target_properties(sodium PROPERTIES
+ IMPORTED_IMPLIB "${sodium_LIBRARY_RELEASE}"
+ IMPORTED_IMPLIB_DEBUG "${sodium_LIBRARY_DEBUG}"
+ )
+ if (NOT (sodium_DLL_DEBUG MATCHES ".*-NOTFOUND"))
+ set_target_properties(sodium PROPERTIES
+ IMPORTED_LOCATION_DEBUG "${sodium_DLL_DEBUG}"
+ )
+ endif()
+ if (NOT (sodium_DLL_RELEASE MATCHES ".*-NOTFOUND"))
+ set_target_properties(sodium PROPERTIES
+ IMPORTED_LOCATION_RELWITHDEBINFO "${sodium_DLL_RELEASE}"
+ IMPORTED_LOCATION_MINSIZEREL "${sodium_DLL_RELEASE}"
+ IMPORTED_LOCATION_RELEASE "${sodium_DLL_RELEASE}"
+ )
+ endif()
+ endif()
+endif()
diff --git a/snapcraft.yaml b/snapcraft.yaml
index 97a4bc255..d614c354f 100644
--- a/snapcraft.yaml
+++ b/snapcraft.yaml
@@ -29,6 +29,7 @@ parts:
- -DWITH_XC_AUTOTYPE=ON
- -DWITH_XC_HTTP=ON
- -DWITH_XC_YUBIKEY=ON
+ - -DWITH_XC_BROWSER=ON
build-packages:
- g++
- libgcrypt20-dev
@@ -41,6 +42,7 @@ parts:
- libxtst-dev
- libyubikey-dev
- libykpers-1-dev
+ - libsodium-dev
stage-packages:
- dbus
- qttranslations5-l10n # common translations
@@ -65,4 +67,3 @@ parts:
- libqt5svg5 # for loading icon themes which are svg
- locales-all
- xdg-user-dirs
-
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 00d628aca..29a2c452c 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -182,12 +182,20 @@ add_feature_info(AutoType WITH_XC_AUTOTYPE "Automatic password typing")
add_feature_info(KeePassHTTP WITH_XC_HTTP "Browser integration compatible with ChromeIPass and PassIFox")
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
+add_feature_info(keepassxc-browser WITH_XC_BROWSER "KeePassXC browser support with keepassxc-browser")
add_subdirectory(http)
if(WITH_XC_HTTP)
set(keepasshttp_LIB keepasshttp qhttp Qt5::Network)
endif()
+set(BROWSER_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/browser)
+add_subdirectory(browser)
+add_subdirectory(proxy)
+if(WITH_XC_BROWSER)
+ set(keepassxcbrowser_LIB keepassxcbrowser)
+endif()
+
add_subdirectory(autotype)
add_subdirectory(cli)
@@ -231,6 +239,7 @@ add_library(keepassx_core STATIC ${keepassx_SOURCES})
set_target_properties(keepassx_core PROPERTIES COMPILE_DEFINITIONS KEEPASSX_BUILDING_CORE)
target_link_libraries(keepassx_core
${keepasshttp_LIB}
+ ${keepassxcbrowser_LIB}
${autotype_LIB}
${sshagent_LIB}
${YUBIKEY_LIBRARIES}
@@ -297,6 +306,28 @@ if(APPLE AND WITH_APP_BUNDLE)
COMMAND ${MACDEPLOYQT_EXE} ${PROGNAME}.app
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
COMMENT "Deploying app bundle")
+
+ if(WITH_XC_BROWSER)
+ set(PROXY_BINARY_DIR "${CMAKE_BINARY_DIR}/src/proxy/keepassxc-proxy")
+ set(PROXY_APP_DIR "${PROGNAME}.app/Contents/MacOS/keepassxc-proxy")
+ add_custom_command(TARGET ${PROGNAME}
+ POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy ${PROXY_BINARY_DIR} ${PROXY_APP_DIR}
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
+ COMMENT "Copying keepassxc-proxy inside the application")
+
+ add_custom_command(TARGET ${PROGNAME}
+ POST_BUILD
+ COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change /usr/local/opt/qt/lib/QtCore.framework/Versions/5/QtCore "@executable_path/../Frameworks/QtCore.framework/Versions/5/QtCore" ${PROXY_APP_DIR}
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
+ COMMENT "Changing linking of keepassxc-proxy QtCore")
+
+ add_custom_command(TARGET ${PROGNAME}
+ POST_BUILD
+ COMMAND ${CMAKE_INSTALL_NAME_TOOL} -change /usr/local/opt/qt/lib/QtNetwork.framework/Versions/5/QtNetwork "@executable_path/../Frameworks/QtNetwork.framework/Versions/5/QtNetwork" ${PROXY_APP_DIR}
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
+ COMMENT "Changing linking of keepassxc-proxy QtNetwork")
+ endif()
endif()
install(TARGETS ${PROGNAME}
diff --git a/src/browser/BrowserAccessControlDialog.cpp b/src/browser/BrowserAccessControlDialog.cpp
new file mode 100755
index 000000000..7090a4d16
--- /dev/null
+++ b/src/browser/BrowserAccessControlDialog.cpp
@@ -0,0 +1,59 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 "BrowserAccessControlDialog.h"
+#include "ui_BrowserAccessControlDialog.h"
+#include "core/Entry.h"
+
+BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget* parent) :
+ QDialog(parent),
+ ui(new Ui::BrowserAccessControlDialog())
+{
+ this->setWindowFlags(windowFlags() | Qt::WindowStaysOnTopHint);
+
+ ui->setupUi(this);
+ connect(ui->allowButton, SIGNAL(clicked()), this, SLOT(accept()));
+ connect(ui->denyButton, SIGNAL(clicked()), this, SLOT(reject()));
+}
+
+BrowserAccessControlDialog::~BrowserAccessControlDialog()
+{
+}
+
+void BrowserAccessControlDialog::setUrl(const QString& url)
+{
+ ui->label->setText(QString(tr("%1 has requested access to passwords for the following item(s).\n"
+ "Please select whether you want to allow access.")).arg(QUrl(url).host()));
+}
+
+void BrowserAccessControlDialog::setItems(const QList<Entry*>& items)
+{
+ for (Entry* entry : items) {
+ ui->itemsList->addItem(entry->title() + " - " + entry->username());
+ }
+}
+
+bool BrowserAccessControlDialog::remember() const
+{
+ return ui->rememberDecisionCheckBox->isChecked();
+}
+
+void BrowserAccessControlDialog::setRemember(bool r)
+{
+ ui->rememberDecisionCheckBox->setChecked(r);
+}
diff --git a/src/browser/BrowserAccessControlDialog.h b/src/browser/BrowserAccessControlDialog.h
new file mode 100755
index 000000000..d2a66ce0d
--- /dev/null
+++ b/src/browser/BrowserAccessControlDialog.h
@@ -0,0 +1,48 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 BROWSERACCESSCONTROLDIALOG_H
+#define BROWSERACCESSCONTROLDIALOG_H
+
+#include <QDialog>
+#include <QScopedPointer>
+
+class Entry;
+
+namespace Ui {
+class BrowserAccessControlDialog;
+}
+
+class BrowserAccessControlDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit BrowserAccessControlDialog(QWidget* parent = nullptr);
+ ~BrowserAccessControlDialog();
+
+ void setUrl(const QString& url);
+ void setItems(const QList<Entry*>& items);
+ bool remember() const;
+ void setRemember(bool r);
+
+private:
+ QScopedPointer<Ui::BrowserAccessControlDialog> ui;
+};
+
+#endif // BROWSERACCESSCONTROLDIALOG_H
diff --git a/src/browser/BrowserAccessControlDialog.ui b/src/browser/BrowserAccessControlDialog.ui
new file mode 100755
index 000000000..d4f104544
--- /dev/null
+++ b/src/browser/BrowserAccessControlDialog.ui
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BrowserAccessControlDialog</class>
+ <widget class="QDialog" name="BrowserAccessControlDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>400</width>
+ <height>221</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>keepassxc-browser Confirm Access</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListWidget" name="itemsList"/>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="rememberDecisionCheckBox">
+ <property name="text">
+ <string>Remember this decision</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <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="allowButton">
+ <property name="text">
+ <string>Allow</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="denyButton">
+ <property name="text">
+ <string>Deny</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp
new file mode 100755
index 000000000..ebd9e2d9a
--- /dev/null
+++ b/src/browser/BrowserAction.cpp
@@ -0,0 +1,546 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 <QJsonDocument>
+#include <QJsonParseError>
+#include "BrowserAction.h"
+#include "BrowserSettings.h"
+#include "sodium.h"
+#include "sodium/crypto_box.h"
+#include "sodium/randombytes.h"
+#include "config-keepassx.h"
+
+BrowserAction::BrowserAction(BrowserService& browserService) :
+ m_mutex(QMutex::Recursive),
+ m_browserService(browserService),
+ m_associated(false)
+{
+
+}
+
+QJsonObject BrowserAction::readResponse(const QJsonObject& json)
+{
+ if (json.isEmpty()) {
+ return QJsonObject();
+ }
+
+ const QString action = json.value("action").toString();
+ if (action.isEmpty()) {
+ return QJsonObject();
+ }
+
+ QMutexLocker locker(&m_mutex);
+ if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !m_browserService.isDatabaseOpened()) {
+ if (m_clientPublicKey.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
+ } else if (!m_browserService.openDatabase()) {
+ return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
+ }
+ }
+
+ return handleAction(json);
+}
+
+
+// Private functions
+///////////////////////
+
+QJsonObject BrowserAction::handleAction(const QJsonObject& json)
+{
+ QString action = json.value("action").toString();
+ if (action.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
+ }
+
+ if (action.compare("change-public-keys", Qt::CaseSensitive) == 0) {
+ return handleChangePublicKeys(json, action);
+ } else if (action.compare("get-databasehash", Qt::CaseSensitive) == 0) {
+ return handleGetDatabaseHash(json, action);
+ } else if (action.compare("associate", Qt::CaseSensitive) == 0) {
+ return handleAssociate(json, action);
+ } else if (action.compare("test-associate", Qt::CaseSensitive) == 0) {
+ return handleTestAssociate(json, action);
+ } else if (action.compare("get-logins", Qt::CaseSensitive) == 0) {
+ return handleGetLogins(json, action);
+ } else if (action.compare("generate-password", Qt::CaseSensitive) == 0) {
+ return handleGeneratePassword(json, action);
+ } else if (action.compare("set-login", Qt::CaseSensitive) == 0) {
+ return handleSetLogin(json, action);
+ } else if (action.compare("lock-database", Qt::CaseSensitive) == 0) {
+ return handleLockDatabase(json, action);
+ }
+
+ // Action was not recognized
+ return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
+}
+
+QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const QString& action)
+{
+ QMutexLocker locker(&m_mutex);
+ const QString nonce = json.value("nonce").toString();
+ const QString clientPublicKey = json.value("publicKey").toString();
+
+ if (clientPublicKey.isEmpty() || nonce.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
+ }
+
+ m_associated = false;
+ unsigned char pk[crypto_box_PUBLICKEYBYTES];
+ unsigned char sk[crypto_box_SECRETKEYBYTES];
+ crypto_box_keypair(pk, sk);
+
+ const QString publicKey = getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES);
+ const QString secretKey = getBase64FromKey(sk, crypto_box_SECRETKEYBYTES);
+ m_clientPublicKey = clientPublicKey;
+ m_publicKey = publicKey;
+ m_secretKey = secretKey;
+
+ QJsonObject response = buildMessage(incrementNonce(nonce));
+ response["action"] = action;
+ response["publicKey"] = publicKey;
+
+ return response;
+}
+
+QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ if (hash.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
+ }
+
+ QString command = decrypted.value("action").toString();
+ if (!command.isEmpty() && command.compare("get-databasehash", Qt::CaseSensitive) == 0) {
+ QJsonObject message;
+ message["hash"] = hash;
+ message["version"] = KEEPASSX_VERSION;
+ return buildResponse(action, message, incrementNonce(nonce));
+ }
+
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+}
+
+QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ const QString key = decrypted.value("key").toString();
+ if (key.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
+ }
+
+ QMutexLocker locker(&m_mutex);
+ if (key.compare(m_clientPublicKey, Qt::CaseSensitive) == 0) {
+ const QString id = m_browserService.storeKey(key);
+ if (id.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
+ }
+
+ m_associated = true;
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["hash"] = hash;
+ message["id"] = id;
+ return buildResponse(action, message, newNonce);
+ }
+
+ return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
+}
+
+QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ const QString responseKey = decrypted.value("key").toString();
+ const QString id = decrypted.value("id").toString();
+ if (responseKey.isEmpty() || id.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
+ }
+
+ QMutexLocker locker(&m_mutex);
+ const QString key = m_browserService.getKey(id);
+ if (key.isEmpty() || key.compare(responseKey, Qt::CaseSensitive) != 0) {
+ return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
+ }
+
+ m_associated = true;
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["hash"] = hash;
+ message["id"] = id;
+
+ return buildResponse(action, message, newNonce);
+}
+
+QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+
+ QMutexLocker locker(&m_mutex);
+ if (!m_associated) {
+ return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
+ }
+
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ const QString url = decrypted.value("url").toString();
+ if (url.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
+ }
+
+ const QString id = decrypted.value("id").toString();
+ const QString submit = decrypted.value("submitUrl").toString();
+ const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "");
+
+ if (users.isEmpty()) {
+ return QJsonObject(); // No logins found. Not an error, return an empty JSON object.
+ }
+
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["count"] = users.count();
+ message["entries"] = users;
+ message["hash"] = hash;
+ message["id"] = id;
+
+ return buildResponse(action, message, newNonce);
+}
+
+QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const QString& action)
+{
+ const QString nonce = json.value("nonce").toString();
+ const QString password = BrowserSettings::generatePassword();
+ const QString bits = QString::number(BrowserSettings::getbits()); // For some reason this always returns 1140 bits?
+
+ if (nonce.isEmpty() || password.isEmpty()) {
+ return QJsonObject();
+ }
+
+ QJsonArray arr;
+ QJsonObject passwd;
+ passwd["login"] = QString::number(password.length() * 8); //bits;
+ passwd["password"] = password;
+ arr.append(passwd);
+
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["entries"] = arr;
+
+ return buildResponse(action, message, newNonce);
+}
+
+QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+
+ QMutexLocker locker(&m_mutex);
+ if (!m_associated) {
+ return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
+ }
+
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ const QString url = decrypted.value("url").toString();
+ if (url.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
+ }
+
+ const QString id = decrypted.value("id").toString();
+ const QString login = decrypted.value("login").toString();
+ const QString password = decrypted.value("password").toString();
+ const QString submitUrl = decrypted.value("submitUrl").toString();
+ const QString uuid = decrypted.value("uuid").toString();
+ const QString realm;
+
+ if (uuid.isEmpty()) {
+ m_browserService.addEntry(id, login, password, url, submitUrl, realm);
+ } else {
+ m_browserService.updateEntry(id, uuid, login, password, url);
+ }
+
+ const QString newNonce = incrementNonce(nonce);
+
+ QJsonObject message = buildMessage(newNonce);
+ message["count"] = QJsonValue::Null;
+ message["entries"] = QJsonValue::Null;
+ message["error"] = "";
+ message["hash"] = hash;
+
+ return buildResponse(action, message, newNonce);
+}
+
+QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QString& action)
+{
+ const QString hash = getDatabaseHash();
+ const QString nonce = json.value("nonce").toString();
+ const QString encrypted = json.value("message").toString();
+ const QJsonObject decrypted = decryptMessage(encrypted, nonce, action);
+
+ if (decrypted.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+ }
+
+ if (hash.isEmpty()) {
+ return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
+ }
+
+ QString command = decrypted.value("action").toString();
+ if (!command.isEmpty() && command.compare("lock-database", Qt::CaseSensitive) == 0) {
+ QMutexLocker locker(&m_mutex);
+ m_browserService.lockDatabase();
+
+ const QString newNonce = incrementNonce(nonce);
+ QJsonObject message = buildMessage(newNonce);
+
+ return buildResponse(action, message, newNonce);
+ }
+
+ return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
+}
+
+QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const
+{
+ QJsonObject response;
+ response["action"] = action;
+ response["errorCode"] = QString::number(errorCode);
+ response["error"] = getErrorMessage(errorCode);
+ return response;
+}
+
+QJsonObject BrowserAction::buildMessage(const QString& nonce) const
+{
+ QJsonObject message;
+ message["version"] = KEEPASSX_VERSION;
+ message["success"] = "true";
+ message["nonce"] = nonce;
+ return message;
+}
+
+QJsonObject BrowserAction::buildResponse(const QString& action, const QJsonObject& message, const QString& nonce)
+{
+ QJsonObject response;
+ response["action"] = action;
+ response["message"] = encryptMessage(message, nonce);
+ response["nonce"] = nonce;
+ return response;
+}
+
+QString BrowserAction::getErrorMessage(const int errorCode) const
+{
+ switch (errorCode) {
+ case ERROR_KEEPASS_DATABASE_NOT_OPENED: return "Database not opened";
+ case ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED: return "Database hash not available";
+ case ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED: return "Client public key not received";
+ case ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE: return "Cannot decrypt message";
+ case ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED: return "Timeout or cannot connect to KeePassXC";
+ case ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED: return "Action cancelled or denied";
+ case ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE: return "Cannot encrypt message or public key not found. Is Native Messaging enabled in KeePassXC?";
+ case ERROR_KEEPASS_ASSOCIATION_FAILED: return "KeePassXC association failed, try again";
+ case ERROR_KEEPASS_KEY_CHANGE_FAILED: return "Key change was not successful";
+ case ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED: return "Encryption key is not recognized";
+ case ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND: return "No saved databases found";
+ case ERROR_KEEPASS_INCORRECT_ACTION: return "Incorrect action";
+ case ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED: return "Empty message received";
+ case ERROR_KEEPASS_NO_URL_PROVIDED: return "No URL provided";
+ default: return "Unknown error";
+ }
+}
+
+QString BrowserAction::getDatabaseHash()
+{
+ QMutexLocker locker(&m_mutex);
+ QByteArray hash = QCryptographicHash::hash(
+ (m_browserService.getDatabaseRootUuid() + m_browserService.getDatabaseRecycleBinUuid()).toUtf8(),
+ QCryptographicHash::Sha256).toHex();
+ return QString(hash);
+}
+
+QString BrowserAction::encryptMessage(const QJsonObject& message, const QString& nonce)
+{
+ if (message.isEmpty() || nonce.isEmpty()) {
+ return QString();
+ }
+
+ const QString reply(QJsonDocument(message).toJson());
+ if (!reply.isEmpty()) {
+ return encrypt(reply, nonce);
+ }
+
+ return QString();
+}
+
+QJsonObject BrowserAction::decryptMessage(const QString& message, const QString& nonce, const QString& action)
+{
+ if (message.isEmpty() || nonce.isEmpty()) {
+ return QJsonObject();
+ }
+
+ QByteArray ba = decrypt(message, nonce);
+ if (!ba.isEmpty()) {
+ return getJsonObject(ba);
+ }
+
+ return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
+}
+
+QString BrowserAction::encrypt(const QString plaintext, const QString nonce)
+{
+ QMutexLocker locker(&m_mutex);
+ const QByteArray ma = plaintext.toUtf8();
+ const QByteArray na = base64Decode(nonce);
+ const QByteArray ca = base64Decode(m_clientPublicKey);
+ const QByteArray sa = base64Decode(m_secretKey);
+
+ std::vector<unsigned char> m(ma.cbegin(), ma.cend());
+ std::vector<unsigned char> n(na.cbegin(), na.cend());
+ std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
+ std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
+
+ std::vector<unsigned char> e;
+ e.resize(max_length);
+
+ if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
+ return QString();
+ }
+
+ if (crypto_box_easy(e.data(), m.data(), m.size(), n.data(), ck.data(), sk.data()) == 0) {
+ QByteArray res = getQByteArray(e.data(), (crypto_box_MACBYTES + ma.length()));
+ return res.toBase64();
+ }
+
+ return QString();
+}
+
+QByteArray BrowserAction::decrypt(const QString encrypted, const QString nonce)
+{
+ QMutexLocker locker(&m_mutex);
+ const QByteArray ma = base64Decode(encrypted);
+ const QByteArray na = base64Decode(nonce);
+ const QByteArray ca = base64Decode(m_clientPublicKey);
+ const QByteArray sa = base64Decode(m_secretKey);
+
+ std::vector<unsigned char> m(ma.cbegin(), ma.cend());
+ std::vector<unsigned char> n(na.cbegin(), na.cend());
+ std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
+ std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
+
+ std::vector<unsigned char> d;
+ d.resize(max_length);
+
+ if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
+ return QByteArray();
+ }
+
+ if (crypto_box_open_easy(d.data(), m.data(), ma.length(), n.data(), ck.data(), sk.data()) == 0) {
+ return getQByteArray(d.data(), std::char_traits<char>::length(reinterpret_cast<const char *>(d.data())));
+ }
+
+ return QByteArray();
+}
+
+QString BrowserAction::getBase64FromKey(const uchar* array, const uint len)
+{
+ return getQByteArray(array, len).toBase64();
+}
+
+QByteArray BrowserAction::getQByteArray(const uchar* array, const uint len) const
+{
+ QByteArray qba;
+ qba.reserve(len);
+ for (uint i = 0; i < len; ++i) {
+ qba.append(static_cast<char>(array[i]));
+ }
+ return qba;
+}
+
+QJsonObject BrowserAction::getJsonObject(const uchar* pArray, const uint len) const
+{
+ QByteArray arr = getQByteArray(pArray, len);
+ QJsonParseError err;
+ QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
+ return doc.object();
+}
+
+QJsonObject BrowserAction::getJsonObject(const QByteArray ba) const
+{
+ QJsonParseError err;
+ QJsonDocument doc(QJsonDocument::fromJson(ba, &err));
+ return doc.object();
+}
+
+QByteArray BrowserAction::base64Decode(const QString str)
+{
+ return QByteArray::fromBase64(str.toUtf8());
+}
+
+QString BrowserAction::incrementNonce(const QString& nonce)
+{
+ const QByteArray nonceArray = base64Decode(nonce);
+ std::vector<unsigned char> n(nonceArray.cbegin(), nonceArray.cend());
+
+ sodium_increment(n.data(), n.size());
+ return getQByteArray(n.data(), n.size()).toBase64();
+}
+
+void BrowserAction::removeSharedEncryptionKeys()
+{
+ QMutexLocker locker(&m_mutex);
+ m_browserService.removeSharedEncryptionKeys();
+}
+
+void BrowserAction::removeStoredPermissions()
+{
+ QMutexLocker locker(&m_mutex);
+ m_browserService.removeStoredPermissions();
+}
diff --git a/src/browser/BrowserAction.h b/src/browser/BrowserAction.h
new file mode 100755
index 000000000..83d89fb7f
--- /dev/null
+++ b/src/browser/BrowserAction.h
@@ -0,0 +1,97 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 BROWSERACTION_H
+#define BROWSERACTION_H
+
+#include <QtCore>
+#include <QObject>
+#include <QJsonObject>
+#include <QMutex>
+#include "BrowserService.h"
+
+class BrowserAction : public QObject
+{
+ Q_OBJECT
+
+ enum {
+ ERROR_KEEPASS_DATABASE_NOT_OPENED = 1,
+ ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED = 2,
+ ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED = 3,
+ ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE = 4,
+ ERROR_KEEPASS_TIMEOUT_OR_NOT_CONNECTED = 5,
+ ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED = 6,
+ ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE = 7,
+ ERROR_KEEPASS_ASSOCIATION_FAILED = 8,
+ ERROR_KEEPASS_KEY_CHANGE_FAILED = 9,
+ ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED = 10,
+ ERROR_KEEPASS_NO_SAVED_DATABASES_FOUND = 11,
+ ERROR_KEEPASS_INCORRECT_ACTION = 12,
+ ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED = 13,
+ ERROR_KEEPASS_NO_URL_PROVIDED = 14
+ };
+
+public:
+ BrowserAction(BrowserService& browserService);
+ ~BrowserAction() = default;
+
+ QJsonObject readResponse(const QJsonObject& json);
+
+public slots:
+ void removeSharedEncryptionKeys();
+ void removeStoredPermissions();
+
+private:
+ QJsonObject handleAction(const QJsonObject& json);
+ QJsonObject handleChangePublicKeys(const QJsonObject& json, const QString& action);
+ QJsonObject handleGetDatabaseHash(const QJsonObject& json, const QString& action);
+ QJsonObject handleAssociate(const QJsonObject& json, const QString& action);
+ QJsonObject handleTestAssociate(const QJsonObject& json, const QString& action);
+ QJsonObject handleGetLogins(const QJsonObject& json, const QString& action);
+ QJsonObject handleGeneratePassword(const QJsonObject& json, const QString& action);
+ QJsonObject handleSetLogin(const QJsonObject& json, const QString& action);
+ QJsonObject handleLockDatabase(const QJsonObject& json, const QString& action);
+
+ QJsonObject buildMessage(const QString& nonce) const;
+ QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);
+ QJsonObject getErrorReply(const QString& action, const int errorCode) const;
+ QString getErrorMessage(const int errorCode) const;
+ QString getDatabaseHash();
+
+ QString encryptMessage(const QJsonObject& message, const QString& nonce);
+ QJsonObject decryptMessage(const QString& message, const QString& nonce, const QString& action = QString());
+ QString encrypt(const QString plaintext, const QString nonce);
+ QByteArray decrypt(const QString encrypted, const QString nonce);
+
+ QString getBase64FromKey(const uchar* array, const uint len);
+ QByteArray getQByteArray(const uchar* array, const uint len) const;
+ QJsonObject getJsonObject(const uchar* pArray, const uint len) const;
+ QJsonObject getJsonObject(const QByteArray ba) const;
+ QByteArray base64Decode(const QString str);
+ QString incrementNonce(const QString& nonce);
+
+private:
+ QMutex m_mutex;
+ BrowserService& m_browserService;
+ QString m_clientPublicKey;
+ QString m_publicKey;
+ QString m_secretKey;
+ bool m_associated;
+};
+
+#endif // BROWSERACTION_H
diff --git a/src/browser/BrowserClients.cpp b/src/browser/BrowserClients.cpp
new file mode 100755
index 000000000..9c47fdd46
--- /dev/null
+++ b/src/browser/BrowserClients.cpp
@@ -0,0 +1,77 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 <QJsonValue>
+#include <QJsonParseError>
+#include "BrowserClients.h"
+
+BrowserClients::BrowserClients(BrowserService& browserService) :
+ m_mutex(QMutex::Recursive),
+ m_browserService(browserService)
+{
+ m_clients.reserve(1000);
+}
+
+QJsonObject BrowserClients::readResponse(const QByteArray& arr)
+{
+ QJsonObject json;
+ const QJsonObject message = byteArrayToJson(arr);
+ const QString clientID = getClientID(message);
+
+ if (!clientID.isEmpty()) {
+ const ClientPtr client = getClient(clientID);
+ if (client->browserAction) {
+ json = client->browserAction->readResponse(message);
+ }
+ }
+
+ return json;
+}
+
+QJsonObject BrowserClients::byteArrayToJson(const QByteArray& arr) const
+{
+ QJsonObject json;
+ QJsonParseError err;
+ QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
+ if (doc.isObject()) {
+ json = doc.object();
+ }
+
+ return json;
+}
+
+QString BrowserClients::getClientID(const QJsonObject& json) const
+{
+ return json["clientID"].toString();
+}
+
+BrowserClients::ClientPtr BrowserClients::getClient(const QString& clientID)
+{
+ QMutexLocker locker(&m_mutex);
+ for (const auto &i : m_clients) {
+ if (i->clientID.compare(clientID, Qt::CaseSensitive) == 0) {
+ return i;
+ }
+ }
+
+ // clientID not found, create a new client
+ QSharedPointer<BrowserAction> ba = QSharedPointer<BrowserAction>::create(m_browserService);
+ ClientPtr client = ClientPtr::create(clientID, ba);
+ m_clients.push_back(client);
+ return m_clients.back();
+}
diff --git a/src/browser/BrowserClients.h b/src/browser/BrowserClients.h
new file mode 100755
index 000000000..f9052e7de
--- /dev/null
+++ b/src/browser/BrowserClients.h
@@ -0,0 +1,56 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 BROWSERCLIENTS_H
+#define BROWSERCLIENTS_H
+
+#include <QJsonObject>
+#include <QMutex>
+#include <QVector>
+#include <QSharedPointer>
+#include <QLocalSocket>
+#include "BrowserAction.h"
+
+class BrowserClients
+{
+ struct Client {
+ Client(const QString& id, QSharedPointer<BrowserAction> ba) : clientID(id), browserAction(ba) {}
+ QString clientID;
+ QSharedPointer<BrowserAction> browserAction;
+ };
+
+ typedef QSharedPointer<Client> ClientPtr;
+
+public:
+ BrowserClients(BrowserService& browserService);
+ ~BrowserClients() = default;
+
+ QJsonObject readResponse(const QByteArray& arr);
+
+private:
+ QJsonObject byteArrayToJson(const QByteArray& arr) const;
+ QString getClientID(const QJsonObject& json) const;
+ ClientPtr getClient(const QString& clientID);
+
+private:
+ QMutex m_mutex;
+ QVector<ClientPtr> m_clients;
+ BrowserService& m_browserService;
+};
+
+#endif // BROWSERCLIENTS_H
diff --git a/src/browser/BrowserEntryConfig.cpp b/src/browser/BrowserEntryConfig.cpp
new file mode 100644
index 000000000..a390e5a75
--- /dev/null
+++ b/src/browser/BrowserEntryConfig.cpp
@@ -0,0 +1,109 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 "BrowserEntryConfig.h"
+#include <QtCore>
+#include "core/Entry.h"
+#include "core/EntryAttributes.h"
+
+static const char KEEPASSBROWSER_NAME[] = "keepassxc-browser Settings"; //TODO: duplicated string (also in Service.cpp)
+
+
+BrowserEntryConfig::BrowserEntryConfig(QObject* parent) :
+ QObject(parent)
+{
+}
+
+QStringList BrowserEntryConfig::allowedHosts() const
+{
+ return m_allowedHosts.toList();
+}
+
+void BrowserEntryConfig::setAllowedHosts(const QStringList& allowedHosts)
+{
+ m_allowedHosts = allowedHosts.toSet();
+}
+
+QStringList BrowserEntryConfig::deniedHosts() const
+{
+ return m_deniedHosts.toList();
+}
+
+void BrowserEntryConfig::setDeniedHosts(const QStringList& deniedHosts)
+{
+ m_deniedHosts = deniedHosts.toSet();
+}
+
+bool BrowserEntryConfig::isAllowed(const QString& host) const
+{
+ return m_allowedHosts.contains(host);
+}
+
+void BrowserEntryConfig::allow(const QString& host)
+{
+ m_allowedHosts.insert(host);
+ m_deniedHosts.remove(host);
+}
+
+bool BrowserEntryConfig::isDenied(const QString& host) const
+{
+ return m_deniedHosts.contains(host);
+}
+
+void BrowserEntryConfig::deny(const QString& host)
+{
+ m_deniedHosts.insert(host);
+ m_allowedHosts.remove(host);
+}
+
+QString BrowserEntryConfig::realm() const
+{
+ return m_realm;
+}
+
+void BrowserEntryConfig::setRealm(const QString& realm)
+{
+ m_realm = realm;
+}
+
+bool BrowserEntryConfig::load(const Entry* entry)
+{
+ QString s = entry->attributes()->value(KEEPASSBROWSER_NAME);
+ if (s.isEmpty()) {
+ return false;
+ }
+
+ QJsonDocument doc = QJsonDocument::fromJson(s.toUtf8());
+ if (doc.isNull()) {
+ return false;
+ }
+
+ QVariantMap map = doc.object().toVariantMap();
+ for (QVariantMap::const_iterator iter = map.cbegin(); iter != map.cend(); ++iter) {
+ setProperty(iter.key().toLatin1(), iter.value());
+ }
+ return true;
+}
+
+void BrowserEntryConfig::save(Entry* entry)
+{
+ QVariantMap v = qo2qv(this);
+ QJsonObject o = QJsonObject::fromVariantMap(v);
+ QByteArray json = QJsonDocument(o).toJson(QJsonDocument::Compact);
+ entry->attributes()->set(KEEPASSBROWSER_NAME, json);
+}
diff --git a/src/browser/BrowserEntryConfig.h b/src/browser/BrowserEntryConfig.h
new file mode 100644
index 000000000..0ffaf985b
--- /dev/null
+++ b/src/browser/BrowserEntryConfig.h
@@ -0,0 +1,60 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 BROWSERENTRYCONFIG_H
+#define BROWSERENTRYCONFIG_H
+
+#include <QtCore/QObject>
+#include <QtCore/QString>
+#include <QtCore/QStringList>
+#include <QtCore/QSet>
+#include "Variant.h"
+
+class Entry;
+
+class BrowserEntryConfig : public QObject
+{
+ Q_OBJECT
+ Q_PROPERTY(QStringList Allow READ allowedHosts WRITE setAllowedHosts)
+ Q_PROPERTY(QStringList Deny READ deniedHosts WRITE setDeniedHosts )
+ Q_PROPERTY(QString Realm READ realm WRITE setRealm )
+
+public:
+ BrowserEntryConfig(QObject* object = 0);
+
+ bool load(const Entry* entry);
+ void save(Entry* entry);
+ bool isAllowed(const QString& host) const;
+ void allow(const QString& host);
+ bool isDenied(const QString& host) const;
+ void deny(const QString& host);
+ QString realm() const;
+ void setRealm(const QString& realm);
+
+private:
+ QStringList allowedHosts() const;
+ void setAllowedHosts(const QStringList& allowedHosts);
+ QStringList deniedHosts() const;
+ void setDeniedHosts(const QStringList& deniedHosts);
+
+ QSet<QString> m_allowedHosts;
+ QSet<QString> m_deniedHosts;
+ QString m_realm;
+};
+
+#endif // BROWSERENTRYCONFIG_H
diff --git a/src/browser/BrowserOptionDialog.cpp b/src/browser/BrowserOptionDialog.cpp
new file mode 100755
index 000000000..9a876ac45
--- /dev/null
+++ b/src/browser/BrowserOptionDialog.cpp
@@ -0,0 +1,104 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 "BrowserOptionDialog.h"
+#include "ui_BrowserOptionDialog.h"
+#include "BrowserSettings.h"
+#include "core/FilePath.h"
+
+#include <QMessageBox>
+
+BrowserOptionDialog::BrowserOptionDialog(QWidget* parent) :
+ QWidget(parent),
+ m_ui(new Ui::BrowserOptionDialog())
+{
+ m_ui->setupUi(this);
+ connect(m_ui->removeSharedEncryptionKeys, SIGNAL(clicked()), this, SIGNAL(removeSharedEncryptionKeys()));
+ connect(m_ui->removeStoredPermissions, SIGNAL(clicked()), this, SIGNAL(removeStoredPermissions()));
+
+ m_ui->warningWidget->showMessage(tr("The following options can be dangerous!\nChange them only if you know what you are doing."), MessageWidget::Warning);
+ m_ui->warningWidget->setIcon(FilePath::instance()->icon("status", "dialog-warning"));
+ m_ui->warningWidget->setCloseButtonVisible(false);
+
+ m_ui->tabWidget->setEnabled(m_ui->enableBrowserSupport->isChecked());
+ connect(m_ui->enableBrowserSupport, SIGNAL(toggled(bool)), m_ui->tabWidget, SLOT(setEnabled(bool)));
+
+ m_ui->customProxyLocation->setEnabled(m_ui->useCustomProxy->isChecked());
+ connect(m_ui->useCustomProxy, SIGNAL(toggled(bool)), m_ui->customProxyLocation, SLOT(setEnabled(bool)));
+}
+
+BrowserOptionDialog::~BrowserOptionDialog()
+{
+}
+
+void BrowserOptionDialog::loadSettings()
+{
+ BrowserSettings settings;
+ m_ui->enableBrowserSupport->setChecked(settings.isEnabled());
+
+ m_ui->showNotification->setChecked(settings.showNotification());
+ m_ui->bestMatchOnly->setChecked(settings.bestMatchOnly());
+ m_ui->unlockDatabase->setChecked(settings.unlockDatabase());
+ m_ui->matchUrlScheme->setChecked(settings.matchUrlScheme());
+
+ if (settings.sortByUsername()) {
+ m_ui->sortByUsername->setChecked(true);
+ } else {
+ m_ui->sortByTitle->setChecked(true);
+ }
+
+ m_ui->alwaysAllowAccess->setChecked(settings.alwaysAllowAccess());
+ m_ui->alwaysAllowUpdate->setChecked(settings.alwaysAllowUpdate());
+ m_ui->searchInAllDatabases->setChecked(settings.searchInAllDatabases());
+ m_ui->supportKphFields->setChecked(settings.supportKphFields());
+ m_ui->supportBrowserProxy->setChecked(settings.supportBrowserProxy());
+ m_ui->useCustomProxy->setChecked(settings.useCustomProxy());
+ m_ui->customProxyLocation->setText(settings.customProxyLocation());
+ m_ui->updateBinaryPath->setChecked(settings.updateBinaryPath());
+ m_ui->chromeSupport->setChecked(settings.chromeSupport());
+ m_ui->chromiumSupport->setChecked(settings.chromiumSupport());
+ m_ui->firefoxSupport->setChecked(settings.firefoxSupport());
+ m_ui->vivaldiSupport->setChecked(settings.vivaldiSupport());
+}
+
+void BrowserOptionDialog::saveSettings()
+{
+ BrowserSettings settings;
+ settings.setEnabled(m_ui->enableBrowserSupport->isChecked());
+ settings.setShowNotification(m_ui->showNotification->isChecked());
+ settings.setBestMatchOnly(m_ui->bestMatchOnly->isChecked());
+ settings.setUnlockDatabase(m_ui->unlockDatabase->isChecked());
+ settings.setMatchUrlScheme(m_ui->matchUrlScheme->isChecked());
+ settings.setSortByUsername(m_ui->sortByUsername->isChecked());
+
+ settings.setSupportBrowserProxy(m_ui->supportBrowserProxy->isChecked());
+ settings.setUseCustomProxy(m_ui->useCustomProxy->isChecked());
+ settings.setCustomProxyLocation(m_ui->customProxyLocation->text());
+
+ settings.setUpdateBinaryPath(m_ui->updateBinaryPath->isChecked());
+ settings.setAlwaysAllowAccess(m_ui->alwaysAllowAccess->isChecked());
+ settings.setAlwaysAllowUpdate(m_ui->alwaysAllowUpdate->isChecked());
+ settings.setSearchInAllDatabases(m_ui->searchInAllDatabases->isChecked());
+ settings.setSupportKphFields(m_ui->supportKphFields->isChecked());
+
+ settings.setChromeSupport(m_ui->chromeSupport->isChecked());
+ settings.setChromiumSupport(m_ui->chromiumSupport->isChecked());
+ settings.setFirefoxSupport(m_ui->firefoxSupport->isChecked());
+ settings.setVivaldiSupport(m_ui->vivaldiSupport->isChecked());
+}
diff --git a/src/browser/BrowserOptionDialog.h b/src/browser/BrowserOptionDialog.h
new file mode 100755
index 000000000..798d215d6
--- /dev/null
+++ b/src/browser/BrowserOptionDialog.h
@@ -0,0 +1,50 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 BROWSEROPTIONDIALOG_H
+#define BROWSEROPTIONDIALOG_H
+
+#include <QWidget>
+#include <QScopedPointer>
+
+namespace Ui {
+class BrowserOptionDialog;
+}
+
+class BrowserOptionDialog : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit BrowserOptionDialog(QWidget* parent = nullptr);
+ ~BrowserOptionDialog();
+
+public slots:
+ void loadSettings();
+ void saveSettings();
+
+signals:
+ void removeSharedEncryptionKeys();
+ void removeStoredPermissions();
+
+private:
+ QScopedPointer<Ui::BrowserOptionDialog> m_ui;
+};
+
+#endif // BROWSEROPTIONDIALOG_H
diff --git a/src/browser/BrowserOptionDialog.ui b/src/browser/BrowserOptionDialog.ui
new file mode 100755
index 000000000..81eaa229d
--- /dev/null
+++ b/src/browser/BrowserOptionDialog.ui
@@ -0,0 +1,327 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>BrowserOptionDialog</class>
+ <widget class="QWidget" name="BrowserOptionDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>577</width>
+ <height>404</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QCheckBox" name="enableBrowserSupport">
+ <property name="toolTip">
+ <string>This is required for accessing your databases with keepassxc-browser</string>
+ </property>
+ <property name="text">
+ <string>Enable KeepassXC browser extension</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>0</number>
+ </property>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>General</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QCheckBox" name="showNotification">
+ <property name="text">
+ <string>Sh&amp;ow a notification when credentials are requested</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="bestMatchOnly">
+ <property name="toolTip">
+ <string>Only returns the best matches for a specific URL instead of all entries for the whole domain.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Return only best-matching entries</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="unlockDatabase">
+ <property name="text">
+ <string>Re&amp;quest to unlock the database if it is locked</string>
+ </property>
+ <property name="checked">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="matchUrlScheme">
+ <property name="toolTip">
+ <string>Only entries with the same scheme (http://, https://, ...) are returned.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Match URL schemes</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="sortByUsername">
+ <property name="text">
+ <string>Sort matching entries by &amp;username</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QRadioButton" name="sortByTitle">
+ <property name="text">
+ <string>Sort &amp;matching entries by title</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeSharedEncryptionKeys">
+ <property name="text">
+ <string>R&amp;emove all shared encryption keys from active database</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="removeStoredPermissions">
+ <property name="text">
+ <string>Re&amp;move all stored permissions from entries in active database</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_2">
+ <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>
+ <widget class="QWidget" name="tab_3">
+ <attribute name="title">
+ <string>Supported browsers</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_4">
+ <item>
+ <widget class="QLabel" name="browserLabel1">
+ <property name="text">
+ <string>Native messaging requires certain .json files to be installed. Already installed browsers are automatically detected.</string>
+ </property>
+ <property name="wordWrap">
+ <bool>true</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLabel" name="browserLabel2">
+ <property name="text">
+ <string>Enable KeePassXC native messaging extension for these browsers:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chromeSupport">
+ <property name="text">
+ <string>Chrome</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="chromiumSupport">
+ <property name="text">
+ <string>Chromium</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="firefoxSupport">
+ <property name="text">
+ <string>Firefox</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="vivaldiSupport">
+ <property name="text">
+ <string>Vivaldi</string>
+ </property>
+ <property name="checked">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <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>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Advanced</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_6">
+ <item>
+ <widget class="MessageWidget" name="warningWidget" native="true">
+ <property name="sizePolicy">
+ <sizepolicy hsizetype="Preferred" vsizetype="Preferred">
+ <horstretch>0</horstretch>
+ <verstretch>0</verstretch>
+ </sizepolicy>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="alwaysAllowAccess">
+ <property name="text">
+ <string>Always allow &amp;access to entries</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="alwaysAllowUpdate">
+ <property name="text">
+ <string>Always allow &amp;updating entries</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="searchInAllDatabases">
+ <property name="toolTip">
+ <string>Only the selected database has to be connected with a client.</string>
+ </property>
+ <property name="text">
+ <string>Searc&amp;h in all opened databases for matching entries</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="supportKphFields">
+ <property name="toolTip">
+ <string>Automatically creating or updating string fields is not supported.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Return advanced string fields which start with &quot;KPH: &quot;</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="updateBinaryPath">
+ <property name="toolTip">
+ <string>Updates KeePassXC or keepassxc-proxy binary path automatically to native messaging scripts on startup.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Update KeePassXC binary path automatically to native messaging scripts on startup</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="supportBrowserProxy">
+ <property name="toolTip">
+ <string>Support a proxy application between KeePassXC and browser extension.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Enable support for proxy application between KeePassXC and browser extension</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QCheckBox" name="useCustomProxy">
+ <property name="toolTip">
+ <string>Use a custom proxy location if you installed a proxy manually.</string>
+ </property>
+ <property name="text">
+ <string>&amp;Use a custom proxy location</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QLineEdit" name="customProxyLocation">
+ <property name="maxLength">
+ <number>999</number>
+ </property>
+ <property name="alignment">
+ <set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_4">
+ <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>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>MessageWidget</class>
+ <extends>QWidget</extends>
+ <header>gui/MessageWidget.h</header>
+ <container>1</container>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp
new file mode 100644
index 000000000..97b8fea42
--- /dev/null
+++ b/src/browser/BrowserService.cpp
@@ -0,0 +1,734 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 <QJsonArray>
+#include <QInputDialog>
+#include <QProgressDialog>
+#include <QMessageBox>
+#include "BrowserService.h"
+#include "BrowserSettings.h"
+#include "BrowserEntryConfig.h"
+#include "BrowserAccessControlDialog.h"
+#include "core/Database.h"
+#include "core/Group.h"
+#include "core/EntrySearcher.h"
+#include "core/Metadata.h"
+#include "core/Uuid.h"
+#include "core/PasswordGenerator.h"
+
+
+// de887cc3-0363-43b8-974b-5911b8816224
+static const unsigned char KEEPASSXCBROWSER_UUID_DATA[] = {
+ 0xde, 0x88, 0x7c, 0xc3, 0x03, 0x63, 0x43, 0xb8,
+ 0x97, 0x4b, 0x59, 0x11, 0xb8, 0x81, 0x62, 0x24
+};
+static const Uuid KEEPASSXCBROWSER_UUID = Uuid(QByteArray::fromRawData(reinterpret_cast<const char *>(KEEPASSXCBROWSER_UUID_DATA), sizeof(KEEPASSXCBROWSER_UUID_DATA)));
+static const char KEEPASSXCBROWSER_NAME[] = "keepassxc-browser Settings";
+static const char ASSOCIATE_KEY_PREFIX[] = "Public Key: ";
+static const char KEEPASSXCBROWSER_GROUP_NAME[] = "keepassxc-browser Passwords";
+static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
+
+BrowserService::BrowserService(DatabaseTabWidget* parent) :
+ m_dbTabWidget(parent),
+ m_dialogActive(false)
+{
+ connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*)));
+ connect(m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
+ connect(m_dbTabWidget, SIGNAL(activateDatabaseChanged(DatabaseWidget*)), this, SLOT(activateDatabaseChanged(DatabaseWidget*)));
+}
+
+bool BrowserService::isDatabaseOpened() const
+{
+ DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
+ if (!dbWidget) {
+ return false;
+ }
+
+ if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
+ return true;
+ }
+
+ return false;
+}
+
+bool BrowserService::openDatabase()
+{
+ if (!BrowserSettings::unlockDatabase()) {
+ return false;
+ }
+
+ DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
+ if (!dbWidget) {
+ return false;
+ }
+
+ if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
+ return true;
+ }
+
+ m_dbTabWidget->activateWindow();
+ return false;
+}
+
+void BrowserService::lockDatabase()
+{
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "lockDatabase", Qt::BlockingQueuedConnection);
+ }
+
+ DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget();
+ if (!dbWidget) {
+ return;
+ }
+
+ if (dbWidget->currentMode() == DatabaseWidget::ViewMode || dbWidget->currentMode() == DatabaseWidget::EditMode) {
+ dbWidget->lock();
+ }
+}
+
+QString BrowserService::getDatabaseRootUuid()
+{
+ Database* db = getDatabase();
+ if (!db) {
+ return QString();
+ }
+
+ Group* rootGroup = db->rootGroup();
+ if (!rootGroup) {
+ return QString();
+ }
+
+ return rootGroup->uuid().toHex();
+}
+
+QString BrowserService::getDatabaseRecycleBinUuid()
+{
+ Database* db = getDatabase();
+ if (!db) {
+ return QString();
+ }
+
+ Group* recycleBin = db->metadata()->recycleBin();
+ if (!recycleBin) {
+ return QString();
+ }
+ return recycleBin->uuid().toHex();
+}
+
+Entry* BrowserService::getConfigEntry(bool create)
+{
+ Entry* entry = nullptr;
+ Database* db = getDatabase();
+ if (!db) {
+ return nullptr;
+ }
+
+ entry = db->resolveEntry(KEEPASSXCBROWSER_UUID);
+ if (!entry && create) {
+ entry = new Entry();
+ entry->setTitle(QLatin1String(KEEPASSXCBROWSER_NAME));
+ entry->setUuid(KEEPASSXCBROWSER_UUID);
+ entry->setAutoTypeEnabled(false);
+ entry->setGroup(db->rootGroup());
+ return entry;
+ }
+
+ if (entry && entry->group() == db->metadata()->recycleBin()) {
+ if (!create) {
+ return nullptr;
+ } else {
+ entry->setGroup(db->rootGroup());
+ return entry;
+ }
+ }
+
+ return entry;
+}
+
+QString BrowserService::storeKey(const QString& key)
+{
+ QString id;
+
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "storeKey", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(QString, id),
+ Q_ARG(const QString&, key));
+ return id;
+ }
+
+ Entry* config = getConfigEntry(true);
+ if (!config) {
+ return QString();
+ }
+
+ bool contains = false;
+ QMessageBox::StandardButton dialogResult = QMessageBox::No;
+
+ do {
+ bool ok = false;
+ id = QInputDialog::getText(0, tr("KeePassXC: New key association request"),
+ tr("You have received an association "
+ "request for the above key.\n"
+ "If you would like to allow it access "
+ "to your KeePassXC database,\n"
+ "give it a unique name to identify and accept it."),
+ QLineEdit::Normal, QString(), &ok);
+ if (!ok || id.isEmpty()) {
+ return QString();
+ }
+
+ contains = config->attributes()->contains(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
+ dialogResult = QMessageBox::warning(0, tr("KeePassXC: Overwrite existing key?"),
+ tr("A shared encryption key with the name \"%1\" already exists.\nDo you want to overwrite it?").arg(id),
+ QMessageBox::Yes | QMessageBox::No);
+ } while (contains && dialogResult == QMessageBox::No);
+
+ config->attributes()->set(QLatin1String(ASSOCIATE_KEY_PREFIX) + id, key, true);
+ return id;
+}
+
+QString BrowserService::getKey(const QString& id)
+{
+ Entry* config = getConfigEntry();
+ if (!config) {
+ return QString();
+ }
+
+ return config->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + id);
+}
+
+// No need to use KeepassHttpProtocol. Just return a JSON array.
+QJsonArray BrowserService::findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm)
+{
+ QJsonArray result;
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "findMatchingEntries", Qt::BlockingQueuedConnection,
+ Q_RETURN_ARG(QJsonArray, result),
+ Q_ARG(const QString&, id),
+ Q_ARG(const QString&, url),
+ Q_ARG(const QString&, submitUrl),
+ Q_ARG(const QString&, realm));
+ return result;
+ }
+
+ const bool alwaysAllowAccess = BrowserSettings::alwaysAllowAccess();
+ const QString host = QUrl(url).host();
+ const QString submitHost = QUrl(submitUrl).host();
+
+ // Check entries for authorization
+ QList<Entry*> pwEntriesToConfirm;
+ QList<Entry*> pwEntries;
+ for (Entry* entry : searchEntries(url)) {
+ switch (checkAccess(entry, host, submitHost, realm)) {
+ case Denied:
+ continue;
+
+ case Unknown:
+ if (alwaysAllowAccess) {
+ pwEntries.append(entry);
+ } else {
+ pwEntriesToConfirm.append(entry);
+ }
+ break;
+
+ case Allowed:
+ pwEntries.append(entry);
+ break;
+ }
+ }
+
+ // Confirm entries
+ if (confirmEntries(pwEntriesToConfirm, url, host, submitHost, realm)) {
+ pwEntries.append(pwEntriesToConfirm);
+ }
+
+ if (pwEntries.isEmpty()) {
+ return QJsonArray();
+ }
+
+ // Sort results
+ pwEntries = sortEntries(pwEntries, host, submitUrl);
+
+ // Fill the list
+ for (Entry* entry : pwEntries) {
+ result << prepareEntry(entry);
+ }
+
+ return result;
+}
+
+void BrowserService::addEntry(const QString&, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm)
+{
+ Group* group = findCreateAddEntryGroup();
+ if (!group) {
+ return;
+ }
+
+ Entry* entry = new Entry();
+ entry->setUuid(Uuid::random());
+ entry->setTitle(QUrl(url).host());
+ entry->setUrl(url);
+ entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
+ entry->setUsername(login);
+ entry->setPassword(password);
+ entry->setGroup(group);
+
+ const QString host = QUrl(url).host();
+ const QString submitHost = QUrl(submitUrl).host();
+ BrowserEntryConfig config;
+ config.allow(host);
+
+ if (!submitHost.isEmpty()) {
+ config.allow(submitHost);
+ }
+ if (!realm.isEmpty()) {
+ config.setRealm(realm);
+ }
+ config.save(entry);
+}
+
+void BrowserService::updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url)
+{
+ if (thread() != QThread::currentThread()) {
+ QMetaObject::invokeMethod(this, "updateEntry", Qt::BlockingQueuedConnection,
+ Q_ARG(const QString&, id),
+ Q_ARG(const QString&, uuid),
+ Q_ARG(const QString&, login),
+ Q_ARG(const QString&, password),
+ Q_ARG(const QString&, url));
+ }
+
+ Database* db = getDatabase();
+ if (!db) {
+ return;
+ }
+
+ Entry* entry = db->resolveEntry(Uuid::fromHex(uuid));
+ if (!entry) {
+ return;
+ }
+
+ QString username = entry->username();
+ if (username.isEmpty()) {
+ return;
+ }
+
+ if (username.compare(login, Qt::CaseSensitive) != 0 || entry->password().compare(password, Qt::CaseSensitive) != 0) {
+ QMessageBox::StandardButton dialogResult = QMessageBox::No;
+ if (!BrowserSettings::alwaysAllowUpdate()) {
+ dialogResult = QMessageBox::warning(0, tr("KeePassXC: Update Entry"),
+ tr("Do you want to update the information in %1 - %2?")
+ .arg(QUrl(url).host()).arg(username),
+ QMessageBox::Yes|QMessageBox::No);
+ }
+
+ if (BrowserSettings::alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) {
+ entry->beginUpdate();
+ entry->setUsername(login);
+ entry->setPassword(password);
+ entry->endUpdate();
+ }
+ }
+}
+
+QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostname)
+{
+ QList<Entry*> entries;
+ Group* rootGroup = db->rootGroup();
+ if (!rootGroup) {
+ return entries;
+ }
+
+ for (Entry* entry : EntrySearcher().search(hostname, rootGroup, Qt::CaseInsensitive)) {
+ QString title = entry->title();
+ QString url = entry->url();
+
+ // Filter to match hostname in Title and Url fields
+ if ((!title.isEmpty() && hostname.contains(title))
+ || (!url.isEmpty() && hostname.contains(url))
+ || (matchUrlScheme(title) && hostname.endsWith(QUrl(title).host()))
+ || (matchUrlScheme(url) && hostname.endsWith(QUrl(url).host())) ) {
+ entries.append(entry);
+ }
+ }
+
+ return entries;
+}
+
+QList<Entry*> BrowserService::searchEntries(const QString& text)
+{
+ // Get the list of databases to search
+ QList<Database*> databases;
+ if (BrowserSettings::searchInAllDatabases()) {
+ const int count = m_dbTabWidget->count();
+ for (int i = 0; i < count; ++i) {
+ if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
+ if (Database* db = dbWidget->database()) {
+ databases << db;
+ }
+ }
+ }
+ } else if (Database* db = getDatabase()) {
+ databases << db;
+ }
+
+ // Search entries matching the hostname
+ QString hostname = QUrl(text).host();
+ QList<Entry*> entries;
+ do {
+ for (Database* db : databases) {
+ entries << searchEntries(db, hostname);
+ }
+ } while (entries.isEmpty() && removeFirstDomain(hostname));
+
+ return entries;
+}
+
+void BrowserService::removeSharedEncryptionKeys()
+{
+ if (!isDatabaseOpened()) {
+ QMessageBox::critical(0, tr("KeePassXC: Database locked!"),
+ tr("The active database is locked!\n"
+ "Please unlock the selected database or choose another one which is unlocked."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ Entry* entry = getConfigEntry();
+ if (!entry) {
+ QMessageBox::information(0, tr("KeePassXC: Settings not available!"),
+ tr("The active database does not contain a settings entry."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ QStringList keysToRemove;
+ for (const QString& key : entry->attributes()->keys()) {
+ if (key.startsWith(ASSOCIATE_KEY_PREFIX)) {
+ keysToRemove << key;
+ }
+ }
+
+ if (keysToRemove.isEmpty()) {
+ QMessageBox::information(0, tr("KeePassXC: No keys found"),
+ tr("No shared encryption keys found in KeePassXC Settings."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ entry->beginUpdate();
+ for (const QString& key : keysToRemove) {
+ entry->attributes()->remove(key);
+ }
+ entry->endUpdate();
+
+ const int count = keysToRemove.count();
+ QMessageBox::information(0, tr("KeePassXC: Removed keys from database"),
+ tr("Successfully removed %n encryption key(s) from KeePassXC settings.", "", count),
+ QMessageBox::Ok);
+
+}
+
+void BrowserService::removeStoredPermissions()
+{
+ if (!isDatabaseOpened()) {
+ QMessageBox::critical(0, tr("KeePassXC: Database locked!"),
+ tr("The active database is locked!\n"
+ "Please unlock the selected database or choose another one which is unlocked."),
+ QMessageBox::Ok);
+ return;
+ }
+
+ Database* db = m_dbTabWidget->currentDatabaseWidget()->database();
+ if (!db) {
+ return;
+ }
+
+ QList<Entry*> entries = db->rootGroup()->entriesRecursive();
+
+ QProgressDialog progress(tr("Removing stored permissions…"), tr("Abort"), 0, entries.count());
+ progress.setWindowModality(Qt::WindowModal);
+
+ uint counter = 0;
+ for (Entry* entry : entries) {
+ if (progress.wasCanceled()) {
+ return;
+ }
+
+ if (entry->attributes()->contains(KEEPASSXCBROWSER_NAME)) {
+ entry->beginUpdate();
+ entry->attributes()->remove(KEEPASSXCBROWSER_NAME);
+ entry->endUpdate();
+ ++counter;
+ }
+ progress.setValue(progress.value() + 1);
+ }
+ progress.reset();
+
+ if (counter > 0) {
+ QMessageBox::information(0, tr("KeePassXC: Removed permissions"),
+ tr("Successfully removed permissions from %n entry(s).", "", counter),
+ QMessageBox::Ok);
+ } else {
+ QMessageBox::information(0, tr("KeePassXC: No entry with permissions found!"),
+ tr("The active database does not contain an entry with permissions."),
+ QMessageBox::Ok);
+ }
+}
+
+QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& entryUrl)
+{
+ QUrl url(entryUrl);
+ if (url.scheme().isEmpty()) {
+ url.setScheme("http");
+ }
+
+ const QString submitUrl = url.toString(QUrl::StripTrailingSlash);
+ const QString baseSubmitUrl = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
+
+ QMultiMap<int, const Entry*> priorities;
+ for (const Entry* entry : pwEntries) {
+ priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
+ }
+
+ QString field = BrowserSettings::sortByTitle() ? "Title" : "UserName";
+ std::sort(pwEntries.begin(), pwEntries.end(), [&priorities, &field](const Entry* left, const Entry* right) {
+ int res = priorities.key(left) - priorities.key(right);
+ if (res == 0) {
+ return QString::localeAwareCompare(left->attributes()->value(field), right->attributes()->value(field)) < 0;
+ }
+ return res < 0;
+ });
+
+ return pwEntries;
+}
+
+bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm, const QString& url, const QString& host, const QString& submitHost, const QString& realm)
+{
+ if (pwEntriesToConfirm.isEmpty() || m_dialogActive) {
+ return false;
+ }
+
+ m_dialogActive = true;
+ BrowserAccessControlDialog accessControlDialog;
+ accessControlDialog.setUrl(url);
+ accessControlDialog.setItems(pwEntriesToConfirm);
+
+ int res = accessControlDialog.exec();
+ if (accessControlDialog.remember()) {
+ for (Entry* entry : pwEntriesToConfirm) {
+ BrowserEntryConfig config;
+ config.load(entry);
+ if (res == QDialog::Accepted) {
+ config.allow(host);
+ if (!submitHost.isEmpty() && host != submitHost)
+ config.allow(submitHost);
+ } else if (res == QDialog::Rejected) {
+ config.deny(host);
+ if (!submitHost.isEmpty() && host != submitHost) {
+ config.deny(submitHost);
+ }
+ }
+ if (!realm.isEmpty()) {
+ config.setRealm(realm);
+ }
+ config.save(entry);
+ }
+ }
+
+ m_dialogActive = false;
+ if (res == QDialog::Accepted) {
+ return true;
+ }
+
+ return false;
+}
+
+QJsonObject BrowserService::prepareEntry(const Entry* entry)
+{
+ QJsonObject res;
+ res["login"] = entry->resolveMultiplePlaceholders(entry->username());
+ res["password"] = entry->resolveMultiplePlaceholders(entry->password());
+ res["name"] = entry->resolveMultiplePlaceholders(entry->title());
+ res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuid().toHex());
+
+ if (BrowserSettings::supportKphFields()) {
+ const EntryAttributes* attr = entry->attributes();
+ QJsonArray stringFields;
+ for (const QString& key : attr->keys()) {
+ if (key.startsWith(QLatin1String("KPH: "))) {
+ QJsonObject sField;
+ sField[key] = entry->resolveMultiplePlaceholders(attr->value(key));
+ stringFields << sField;
+ }
+ }
+ res["stringFields"] = stringFields;
+ }
+ return res;
+}
+
+BrowserService::Access BrowserService::checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm)
+{
+ BrowserEntryConfig config;
+ if (!config.load(entry)) {
+ return Unknown;
+ }
+ if ((config.isAllowed(host)) && (submitHost.isEmpty() || config.isAllowed(submitHost))) {
+ return Allowed;
+ }
+ if ((config.isDenied(host)) || (!submitHost.isEmpty() && config.isDenied(submitHost))) {
+ return Denied;
+ }
+ if (!realm.isEmpty() && config.realm() != realm) {
+ return Denied;
+ }
+ return Unknown;
+}
+
+Group* BrowserService::findCreateAddEntryGroup()
+{
+ Database* db = getDatabase();
+ if (!db) {
+ return nullptr;
+ }
+
+ Group* rootGroup = db->rootGroup();
+ if (!rootGroup) {
+ return nullptr;
+ }
+
+ const QString groupName = QLatin1String(KEEPASSXCBROWSER_GROUP_NAME); //TODO: setting to decide where new keys are created
+
+ for (const Group* g : rootGroup->groupsRecursive(true)) {
+ if (g->name() == groupName) {
+ return db->resolveGroup(g->uuid());
+ }
+ }
+
+ Group* group = new Group();
+ group->setUuid(Uuid::random());
+ group->setName(groupName);
+ group->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
+ group->setParent(rootGroup);
+ return group;
+}
+
+int BrowserService::sortPriority(const Entry* entry, const QString& host, const QString& submitUrl, const QString& baseSubmitUrl) const
+{
+ QUrl url(entry->url());
+ if (url.scheme().isEmpty()) {
+ url.setScheme("http");
+ }
+ const QString entryURL = url.toString(QUrl::StripTrailingSlash);
+ const QString baseEntryURL = url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
+
+ if (submitUrl == entryURL) {
+ return 100;
+ }
+ if (submitUrl.startsWith(entryURL) && entryURL != host && baseSubmitUrl != entryURL) {
+ return 90;
+ }
+ if (submitUrl.startsWith(baseEntryURL) && entryURL != host && baseSubmitUrl != baseEntryURL) {
+ return 80;
+ }
+ if (entryURL == host) {
+ return 70;
+ }
+ if (entryURL == baseSubmitUrl) {
+ return 60;
+ }
+ if (entryURL.startsWith(submitUrl)) {
+ return 50;
+ }
+ if (entryURL.startsWith(baseSubmitUrl) && baseSubmitUrl != host) {
+ return 40;
+ }
+ if (submitUrl.startsWith(entryURL)) {
+ return 30;
+ }
+ if (submitUrl.startsWith(baseEntryURL)) {
+ return 20;
+ }
+ if (entryURL.startsWith(host)) {
+ return 10;
+ }
+ if (host.startsWith(entryURL)) {
+ return 5;
+ }
+ return 0;
+}
+
+bool BrowserService::matchUrlScheme(const QString& url)
+{
+ QUrl address(url);
+ return !address.scheme().isEmpty();
+}
+
+bool BrowserService::removeFirstDomain(QString& hostname)
+{
+ int pos = hostname.indexOf(".");
+ if (pos < 0) {
+ return false;
+ }
+
+ // Don't remove the second-level domain if it's the only one
+ if (hostname.count(".") > 1) {
+ hostname = hostname.mid(pos + 1);
+ return !hostname.isEmpty();
+ }
+
+ // Nothing removed
+ return false;
+}
+
+Database* BrowserService::getDatabase()
+{
+ if (DatabaseWidget* dbWidget = m_dbTabWidget->currentDatabaseWidget()) {
+ if (Database* db = dbWidget->database()) {
+ return db;
+ }
+ }
+ return nullptr;
+}
+
+void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
+{
+ if (dbWidget) {
+ emit databaseLocked();
+ }
+}
+
+void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
+{
+ if (dbWidget) {
+ emit databaseUnlocked();
+ }
+}
+
+void BrowserService::activateDatabaseChanged(DatabaseWidget* dbWidget)
+{
+ if (dbWidget) {
+ auto currentMode = dbWidget->currentMode();
+ if (currentMode == DatabaseWidget::ViewMode || currentMode == DatabaseWidget::EditMode) {
+ emit databaseUnlocked();
+ } else {
+ emit databaseLocked();
+ }
+ }
+}
diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h
new file mode 100644
index 000000000..859daee2a
--- /dev/null
+++ b/src/browser/BrowserService.h
@@ -0,0 +1,82 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* Copyright (C) 2017 KeePassXC Team <team@keepassxc.org>
+*
+* This program is free software: you can redistribute it and/or modify
+* it under the terms of the GNU General Public License as published by
+* the Free Software Foundation, either version 2 or (at your option)
+* version 3 of the License.
+*
+* This program is distributed in the hope that it will be useful,
+* but WITHOUT ANY WARRANTY; without even the implied warranty of
+* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+* GNU General Public License for more details.
+*
+* You should have received a copy of the GNU General Public License
+* along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BROWSERSERVICE_H
+#define BROWSERSERVICE_H
+
+#include <QtCore>
+#include <QObject>
+#include "gui/DatabaseTabWidget.h"
+#include "core/Entry.h"
+
+enum { max_length = 16*1024 };
+
+class BrowserService : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit BrowserService(DatabaseTabWidget* parent);
+
+ bool isDatabaseOpened() const;
+ bool openDatabase();
+ QString getDatabaseRootUuid();
+ QString getDatabaseRecycleBinUuid();
+ Entry* getConfigEntry(bool create = false);
+ QString getKey(const QString& id);
+ void addEntry(const QString& id, const QString& login, const QString& password, const QString& url, const QString& submitUrl, const QString& realm);
+ QList<Entry*> searchEntries(Database* db, const QString& hostname);
+ QList<Entry*> searchEntries(const QString& text);
+ void removeSharedEncryptionKeys();
+ void removeStoredPermissions();
+
+public slots:
+ QJsonArray findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm);
+ QString storeKey(const QString& key);
+ void updateEntry(const QString& id, const QString& uuid, const QString& login, const QString& password, const QString& url);
+ void databaseLocked(DatabaseWidget* dbWidget);
+ void databaseUnlocked(DatabaseWidget* dbWidget);
+ void activateDatabaseChanged(DatabaseWidget* dbWidget);
+ void lockDatabase();
+
+signals:
+ void databaseLocked();
+ void databaseUnlocked();
+ void databaseChanged();
+
+private:
+ enum Access { Denied, Unknown, Allowed};
+
+private:
+ QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& host, const QString& submitUrl);
+ bool confirmEntries(QList<Entry*>& pwEntriesToConfirm, const QString& url, const QString& host, const QString& submitHost, const QString& realm);
+ QJsonObject prepareEntry(const Entry* entry);
+ Access checkAccess(const Entry* entry, const QString& host, const QString& submitHost, const QString& realm);
+ Group* findCreateAddEntryGroup();
+ int sortPriority(const Entry* entry, const QString &host, const QString& submitUrl, const QString& baseSubmitUrl) const;
+ bool matchUrlScheme(const QString& url);
+ bool removeFirstDomain(QString& hostname);
+ Database* getDatabase();
+
+private:
+ DatabaseTabWidget* const m_dbTabWidget;
+ bool m_dialogActive;
+};
+
+#endif // BROWSERSERVICE_H
diff --git a/src/browser/BrowserSettings.cpp b/src/browser/BrowserSettings.cpp
new file mode 100755
index 000000000..d15f57d46
--- /dev/null
+++ b/src/browser/BrowserSettings.cpp
@@ -0,0 +1,377 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 "BrowserSettings.h"
+#include "core/Config.h"
+
+PasswordGenerator BrowserSettings::m_passwordGenerator;
+PassphraseGenerator BrowserSettings::m_passPhraseGenerator;
+HostInstaller BrowserSettings::m_hostInstaller;
+
+bool BrowserSettings::isEnabled()
+{
+ return config()->get("Browser/Enabled", false).toBool();
+}
+
+void BrowserSettings::setEnabled(bool enabled)
+{
+ config()->set("Browser/Enabled", enabled);
+}
+
+bool BrowserSettings::showNotification()
+{
+ return config()->get("Browser/ShowNotification", true).toBool();
+}
+
+void BrowserSettings::setShowNotification(bool showNotification)
+{
+ config()->set("Browser/ShowNotification", showNotification);
+}
+
+bool BrowserSettings::bestMatchOnly()
+{
+ return config()->get("Browser/BestMatchOnly", false).toBool();
+}
+
+void BrowserSettings::setBestMatchOnly(bool bestMatchOnly)
+{
+ config()->set("Browser/BestMatchOnly", bestMatchOnly);
+}
+
+bool BrowserSettings::unlockDatabase()
+{
+ return config()->get("Browser/UnlockDatabase", true).toBool();
+}
+
+void BrowserSettings::setUnlockDatabase(bool unlockDatabase)
+{
+ config()->set("Browser/UnlockDatabase", unlockDatabase);
+}
+
+bool BrowserSettings::matchUrlScheme()
+{
+ return config()->get("Browser/MatchUrlScheme", true).toBool();
+}
+
+void BrowserSettings::setMatchUrlScheme(bool matchUrlScheme)
+{
+ config()->set("Browser/MatchUrlScheme", matchUrlScheme);
+}
+
+bool BrowserSettings::sortByUsername()
+{
+ return config()->get("Browser/SortByUsername", false).toBool();
+}
+
+void BrowserSettings::setSortByUsername(bool sortByUsername)
+{
+ config()->set("Browser/SortByUsername", sortByUsername);
+}
+
+bool BrowserSettings::sortByTitle()
+{
+ return !sortByUsername();
+}
+
+void BrowserSettings::setSortByTitle(bool sortByUsertitle)
+{
+ setSortByUsername(!sortByUsertitle);
+}
+
+bool BrowserSettings::alwaysAllowAccess()
+{
+ return config()->get("Browser/AlwaysAllowAccess", false).toBool();
+}
+
+void BrowserSettings::setAlwaysAllowAccess(bool alwaysAllowAccess)
+{
+ config()->set("Browser/AlwaysAllowAccess", alwaysAllowAccess);
+}
+
+bool BrowserSettings::alwaysAllowUpdate()
+{
+ return config()->get("Browser/AlwaysAllowUpdate", false).toBool();
+}
+
+void BrowserSettings::setAlwaysAllowUpdate(bool alwaysAllowUpdate)
+{
+ config()->set("Browser/AlwaysAllowUpdate", alwaysAllowUpdate);
+}
+
+bool BrowserSettings::searchInAllDatabases()
+{
+ return config()->get("Browser/SearchInAllDatabases", false).toBool();
+}
+
+void BrowserSettings::setSearchInAllDatabases(bool searchInAllDatabases)
+{
+ config()->set("Browser/SearchInAllDatabases", searchInAllDatabases);
+}
+
+bool BrowserSettings::supportKphFields()
+{
+ return config()->get("Browser/SupportKphFields", true).toBool();
+}
+
+void BrowserSettings::setSupportKphFields(bool supportKphFields)
+{
+ config()->set("Browser/SupportKphFields", supportKphFields);
+}
+
+bool BrowserSettings::supportBrowserProxy()
+{
+ return config()->get("Browser/SupportBrowserProxy", true).toBool();
+}
+
+void BrowserSettings::setSupportBrowserProxy(bool enabled)
+{
+ config()->set("Browser/SupportBrowserProxy", enabled);
+}
+
+bool BrowserSettings::useCustomProxy()
+{
+ return config()->get("Browser/UseCustomProxy", false).toBool();
+}
+
+void BrowserSettings::setUseCustomProxy(bool enabled)
+{
+ config()->set("Browser/UseCustomProxy", enabled);
+}
+
+QString BrowserSettings::customProxyLocation()
+{
+ return config()->get("Browser/CustomProxyLocation", " ").toString();
+}
+
+void BrowserSettings::setCustomProxyLocation(QString location)
+{
+ config()->set("Browser/CustomProxyLocation", location);
+}
+
+bool BrowserSettings::updateBinaryPath()
+{
+ return config()->get("Browser/UpdateBinaryPath", false).toBool();
+}
+
+void BrowserSettings::setUpdateBinaryPath(bool enabled)
+{
+ config()->set("Browser/UpdateBinaryPath", enabled);
+}
+
+bool BrowserSettings::chromeSupport() {
+ return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROME);
+}
+
+void BrowserSettings::setChromeSupport(bool enabled) {
+ m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::CHROME, enabled, supportBrowserProxy(), customProxyLocation());
+}
+
+bool BrowserSettings::chromiumSupport() {
+ return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::CHROMIUM);
+}
+
+void BrowserSettings::setChromiumSupport(bool enabled) {
+ m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::CHROMIUM, enabled, supportBrowserProxy(), customProxyLocation());
+}
+
+bool BrowserSettings::firefoxSupport() {
+ return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::FIREFOX);
+}
+
+void BrowserSettings::setFirefoxSupport(bool enabled) {
+ m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::FIREFOX, enabled, supportBrowserProxy(), customProxyLocation());
+}
+
+bool BrowserSettings::vivaldiSupport() {
+ return m_hostInstaller.checkIfInstalled(HostInstaller::SupportedBrowsers::VIVALDI);
+}
+
+void BrowserSettings::setVivaldiSupport(bool enabled) {
+ m_hostInstaller.installBrowser(HostInstaller::SupportedBrowsers::VIVALDI, enabled, supportBrowserProxy(), customProxyLocation());
+}
+
+bool BrowserSettings::passwordUseNumbers()
+{
+ return config()->get("generator/Numbers", true).toBool();
+}
+
+void BrowserSettings::setPasswordUseNumbers(bool useNumbers)
+{
+ config()->set("generator/Numbers", useNumbers);
+}
+
+bool BrowserSettings::passwordUseLowercase()
+{
+ return config()->get("generator/LowerCase", true).toBool();
+}
+
+void BrowserSettings::setPasswordUseLowercase(bool useLowercase)
+{
+ config()->set("generator/LowerCase", useLowercase);
+}
+
+bool BrowserSettings::passwordUseUppercase()
+{
+ return config()->get("generator/UpperCase", true).toBool();
+}
+
+void BrowserSettings::setPasswordUseUppercase(bool useUppercase)
+{
+ config()->set("generator/UpperCase", useUppercase);
+}
+
+bool BrowserSettings::passwordUseSpecial()
+{
+ return config()->get("generator/SpecialChars", false).toBool();
+}
+
+void BrowserSettings::setPasswordUseSpecial(bool useSpecial)
+{
+ config()->set("generator/SpecialChars", useSpecial);
+}
+
+bool BrowserSettings::passwordUseEASCII()
+{
+ return config()->get("generator/EASCII", false).toBool();
+}
+
+void BrowserSettings::setPasswordUseEASCII(bool useEASCII)
+{
+ config()->set("generator/EASCII", useEASCII);
+}
+
+int BrowserSettings::passPhraseWordCount()
+{
+ return config()->get("generator/WordCount", 6).toInt();
+}
+
+void BrowserSettings::setPassPhraseWordCount(int wordCount)
+{
+ config()->set("generator/WordCount", wordCount);
+}
+
+QString BrowserSettings::passPhraseWordSeparator()
+{
+ return config()->get("generator/WordSeparator", " ").toString();
+}
+
+void BrowserSettings::setPassPhraseWordSeparator(QString separator)
+{
+ config()->set("generator/WordSeparator", separator);
+}
+
+int BrowserSettings::generatorType()
+{
+ return config()->get("generator/Type", 0).toInt();
+}
+
+void BrowserSettings::setGeneratorType(int type)
+{
+ config()->set("generator/Type", type);
+}
+
+bool BrowserSettings::passwordEveryGroup()
+{
+ return config()->get("generator/EnsureEvery", true).toBool();
+}
+
+void BrowserSettings::setPasswordEveryGroup(bool everyGroup)
+{
+ config()->get("generator/EnsureEvery", everyGroup);
+}
+
+bool BrowserSettings::passwordExcludeAlike()
+{
+ return config()->get("generator/ExcludeAlike", true).toBool();
+}
+
+void BrowserSettings::setPasswordExcludeAlike(bool excludeAlike)
+{
+ config()->set("generator/ExcludeAlike", excludeAlike);
+}
+
+int BrowserSettings::passwordLength()
+{
+ return config()->get("generator/Length", 20).toInt();
+}
+
+void BrowserSettings::setPasswordLength(int length)
+{
+ config()->set("generator/Length", length);
+ m_passwordGenerator.setLength(length);
+}
+
+PasswordGenerator::CharClasses BrowserSettings::passwordCharClasses()
+{
+ PasswordGenerator::CharClasses classes;
+ if (passwordUseLowercase()) {
+ classes |= PasswordGenerator::LowerLetters;
+ }
+ if (passwordUseUppercase()) {
+ classes |= PasswordGenerator::UpperLetters;
+ }
+ if (passwordUseNumbers()) {
+ classes |= PasswordGenerator::Numbers;
+ }
+ if (passwordUseSpecial()) {
+ classes |= PasswordGenerator::SpecialCharacters;
+ }
+ if (passwordUseEASCII()) {
+ classes |= PasswordGenerator::EASCII;
+ }
+ return classes;
+}
+
+PasswordGenerator::GeneratorFlags BrowserSettings::passwordGeneratorFlags()
+{
+ PasswordGenerator::GeneratorFlags flags;
+ if (passwordExcludeAlike()) {
+ flags |= PasswordGenerator::ExcludeLookAlike;
+ }
+ if (passwordEveryGroup()) {
+ flags |= PasswordGenerator::CharFromEveryGroup;
+ }
+ return flags;
+}
+
+QString BrowserSettings::generatePassword()
+{
+ if (generatorType() == 0) {
+ m_passwordGenerator.setLength(passwordLength());
+ m_passwordGenerator.setCharClasses(passwordCharClasses());
+ m_passwordGenerator.setFlags(passwordGeneratorFlags());
+ return m_passwordGenerator.generatePassword();
+ } else {
+ m_passPhraseGenerator.setDefaultWordList();
+ m_passPhraseGenerator.setWordCount(passPhraseWordCount());
+ m_passPhraseGenerator.setWordSeparator(passPhraseWordSeparator());
+ return m_passPhraseGenerator.generatePassphrase();
+ }
+}
+
+int BrowserSettings::getbits()
+{
+ return m_passwordGenerator.getbits();
+}
+
+void BrowserSettings::updateBinaryPaths(QString customProxyLocation)
+{
+ bool isProxy = supportBrowserProxy();
+ m_hostInstaller.updateBinaryPaths(isProxy, customProxyLocation);
+}
diff --git a/src/browser/BrowserSettings.h b/src/browser/BrowserSettings.h
new file mode 100755
index 000000000..8d08eef71
--- /dev/null
+++ b/src/browser/BrowserSettings.h
@@ -0,0 +1,105 @@
+/*
+* Copyright (C) 2013 Francois Ferrand
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 BROWSERSETTINGS_H
+#define BROWSERSETTINGS_H
+
+#include "core/PasswordGenerator.h"
+#include "core/PassphraseGenerator.h"
+#include "HostInstaller.h"
+
+class BrowserSettings
+{
+public:
+ static bool isEnabled();
+ static void setEnabled(bool enabled);
+
+ static bool showNotification(); //TODO!!
+ static void setShowNotification(bool showNotification);
+ static bool bestMatchOnly(); //TODO!!
+ static void setBestMatchOnly(bool bestMatchOnly);
+ static bool unlockDatabase(); //TODO!!
+ static void setUnlockDatabase(bool unlockDatabase);
+ static bool matchUrlScheme();
+ static void setMatchUrlScheme(bool matchUrlScheme);
+ static bool sortByUsername();
+ static void setSortByUsername(bool sortByUsername = true);
+ static bool sortByTitle();
+ static void setSortByTitle(bool sortByUsertitle = true);
+ static bool alwaysAllowAccess();
+ static void setAlwaysAllowAccess(bool alwaysAllowAccess);
+ static bool alwaysAllowUpdate();
+ static void setAlwaysAllowUpdate(bool alwaysAllowUpdate);
+ static bool searchInAllDatabases();//TODO!!
+ static void setSearchInAllDatabases(bool searchInAllDatabases);
+ static bool supportKphFields();
+ static void setSupportKphFields(bool supportKphFields);
+
+ static bool supportBrowserProxy();
+ static void setSupportBrowserProxy(bool enabled);
+ static bool useCustomProxy();
+ static void setUseCustomProxy(bool enabled);
+ static QString customProxyLocation();
+ static void setCustomProxyLocation(QString location);
+ static bool updateBinaryPath();
+ static void setUpdateBinaryPath(bool enabled);
+ static bool chromeSupport();
+ static void setChromeSupport(bool enabled);
+ static bool chromiumSupport();
+ static void setChromiumSupport(bool enabled);
+ static bool firefoxSupport();
+ static void setFirefoxSupport(bool enabled);
+ static bool vivaldiSupport();
+ static void setVivaldiSupport(bool enabled);
+
+ static bool passwordUseNumbers();
+ static void setPasswordUseNumbers(bool useNumbers);
+ static bool passwordUseLowercase();
+ static void setPasswordUseLowercase(bool useLowercase);
+ static bool passwordUseUppercase();
+ static void setPasswordUseUppercase(bool useUppercase);
+ static bool passwordUseSpecial();
+ static void setPasswordUseSpecial(bool useSpecial);
+ static bool passwordUseEASCII();
+ static void setPasswordUseEASCII(bool useEASCII);
+ static int passPhraseWordCount();
+ static void setPassPhraseWordCount(int wordCount);
+ static QString passPhraseWordSeparator();
+ static void setPassPhraseWordSeparator(QString separator);
+ static int generatorType();
+ static void setGeneratorType(int type);
+ static bool passwordEveryGroup();
+ static void setPasswordEveryGroup(bool everyGroup);
+ static bool passwordExcludeAlike();
+ static void setPasswordExcludeAlike(bool excludeAlike);
+ static int passwordLength();
+ static void setPasswordLength(int length);
+ static PasswordGenerator::CharClasses passwordCharClasses();
+ static PasswordGenerator::GeneratorFlags passwordGeneratorFlags();
+ static QString generatePassword();
+ static int getbits();
+ static void updateBinaryPaths(QString customProxyLocation = QString());
+
+private:
+ static PasswordGenerator m_passwordGenerator;
+ static PassphraseGenerator m_passPhraseGenerator;
+ static HostInstaller m_hostInstaller;
+};
+
+#endif // BROWSERSETTINGS_H
diff --git a/src/browser/CMakeLists.txt b/src/browser/CMakeLists.txt
new file mode 100755
index 000000000..61215c181
--- /dev/null
+++ b/src/browser/CMakeLists.txt
@@ -0,0 +1,37 @@
+# Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# 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/>.
+
+if(WITH_XC_BROWSER)
+ include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR})
+ find_package(sodium 1.0.12 REQUIRED)
+
+ set(keepassxcbrowser_SOURCES
+ BrowserAccessControlDialog.cpp
+ BrowserAction.cpp
+ BrowserClients.cpp
+ BrowserEntryConfig.cpp
+ BrowserOptionDialog.cpp
+ BrowserService.cpp
+ BrowserSettings.cpp
+ HostInstaller.cpp
+ NativeMessagingBase.cpp
+ NativeMessagingHost.cpp
+ Variant.cpp
+ )
+
+ add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
+ target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network sodium)
+endif()
diff --git a/src/browser/HostInstaller.cpp b/src/browser/HostInstaller.cpp
new file mode 100644
index 000000000..bbb5fb6f5
--- /dev/null
+++ b/src/browser/HostInstaller.cpp
@@ -0,0 +1,230 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 "HostInstaller.h"
+#include <QDir>
+#include <QFile>
+#include <QStandardPaths>
+#include <QJsonArray>
+#include <QJsonDocument>
+#include <QCoreApplication>
+#include <QMessageBox>
+
+const QString HostInstaller::HOST_NAME = "org.keepassxc.keepassxc_browser";
+const QStringList HostInstaller::ALLOWED_ORIGINS = QStringList()
+ << "chrome-extension://iopaggbpplllidnfmcghoonnokmjoicf/"
+ << "chrome-extension://fhakpkpdnjecjfceboihdjpfmgajebii/"
+ << "chrome-extension://jaikbblhommnkeialomogohhdlndpfbi/";
+
+const QStringList HostInstaller::ALLOWED_EXTENSIONS = QStringList()
+ << "keepassxc-browser@keepassxc.org";
+
+#if defined(Q_OS_OSX)
+ const QString HostInstaller::TARGET_DIR_CHROME = "/Library/Application Support/Google/Chrome/NativeMessagingHosts";
+ const QString HostInstaller::TARGET_DIR_CHROMIUM = "/Library/Application Support/Chromium/NativeMessagingHosts";
+ const QString HostInstaller::TARGET_DIR_FIREFOX = "/Library/Application Support/Mozilla/NativeMessagingHosts";
+ const QString HostInstaller::TARGET_DIR_VIVALDI = "/Library/Application Support/Vivaldi/NativeMessagingHosts";
+#elif defined(Q_OS_LINUX)
+ const QString HostInstaller::TARGET_DIR_CHROME = "/.config/google-chrome/NativeMessagingHosts";
+ const QString HostInstaller::TARGET_DIR_CHROMIUM = "/.config/chromium/NativeMessagingHosts";
+ const QString HostInstaller::TARGET_DIR_FIREFOX = "/.mozilla/native-messaging-hosts";
+ const QString HostInstaller::TARGET_DIR_VIVALDI = "/.config/vivaldi/NativeMessagingHosts";
+#elif defined(Q_OS_WIN)
+ const QString HostInstaller::TARGET_DIR_CHROME = "HKEY_CURRENT_USER\\Software\\Google\\Chrome\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
+ const QString HostInstaller::TARGET_DIR_CHROMIUM = "HKEY_CURRENT_USER\\Software\\Chromium\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
+ const QString HostInstaller::TARGET_DIR_FIREFOX = "HKEY_CURRENT_USER\\Software\\Mozilla\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
+ const QString HostInstaller::TARGET_DIR_VIVALDI = "HKEY_CURRENT_USER\\Software\\Vivaldi\\NativeMessagingHosts\\" + HostInstaller::HOST_NAME;
+#endif
+
+HostInstaller::HostInstaller()
+{
+
+}
+
+bool HostInstaller::checkIfInstalled(SupportedBrowsers browser)
+{
+ QString fileName = getPath(browser);
+#ifdef Q_OS_WIN
+ QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
+ return registryEntryFound(settings);
+#else
+ return QFile::exists(fileName);
+#endif
+}
+
+void HostInstaller::installBrowser(SupportedBrowsers browser, const bool& enabled, const bool& proxy, const QString& location)
+{
+ if (enabled) {
+ #ifdef Q_OS_WIN
+ // Create a registry key
+ QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
+ if (!registryEntryFound(settings)) {
+ settings.setValue("Default", getPath(browser));
+ }
+ #endif
+ // Always create the script file
+ QJsonObject script = constructFile(browser, proxy, location);
+ if (!saveFile(browser, script)) {
+ QMessageBox::critical(0, tr("KeePassXC: Cannot save file!"),
+ tr("Cannot save the native messaging script file."), QMessageBox::Ok);
+ }
+ } else {
+ // Remove the script file
+ QString fileName = getPath(browser);
+ QFile::remove(fileName);
+ #ifdef Q_OS_WIN
+ // Remove the registry entry
+ QSettings settings(getTargetPath(browser), QSettings::NativeFormat);
+ if (registryEntryFound(settings)) {
+ settings.remove("Default");
+ }
+ #endif
+ }
+}
+
+void HostInstaller::updateBinaryPaths(const bool& proxy, const QString& location)
+{
+ for (int i = 0; i < 4; ++i) {
+ if (checkIfInstalled(static_cast<SupportedBrowsers>(i))) {
+ installBrowser(static_cast<SupportedBrowsers>(i), true, proxy, location);
+ }
+ }
+}
+
+QString HostInstaller::getTargetPath(SupportedBrowsers browser) const
+{
+ switch (browser) {
+ case SupportedBrowsers::CHROME: return HostInstaller::TARGET_DIR_CHROME;
+ case SupportedBrowsers::CHROMIUM: return HostInstaller::TARGET_DIR_CHROMIUM;
+ case SupportedBrowsers::FIREFOX: return HostInstaller::TARGET_DIR_FIREFOX;
+ case SupportedBrowsers::VIVALDI: return HostInstaller::TARGET_DIR_VIVALDI;
+ default: return QString();
+ }
+}
+
+QString HostInstaller::getBrowserName(SupportedBrowsers browser) const
+{
+ switch (browser) {
+ case SupportedBrowsers::CHROME: return "chrome";
+ case SupportedBrowsers::CHROMIUM: return "chromium";
+ case SupportedBrowsers::FIREFOX: return "firefox";
+ case SupportedBrowsers::VIVALDI: return "vivaldi";
+ default: return QString();
+ }
+}
+
+QString HostInstaller::getPath(SupportedBrowsers browser) const
+{
+#ifdef Q_OS_WIN
+ // If portable settings file exists save the JSON scripts to application folder
+ QString userPath;
+ QString portablePath = QCoreApplication::applicationDirPath() + "/keepassxc.ini";
+ if (QFile::exists(portablePath)) {
+ userPath = QCoreApplication::applicationDirPath();
+ } else {
+ userPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation));
+ }
+
+ QString winPath = QString("%1/%2_%3.json").arg(userPath, HostInstaller::HOST_NAME, getBrowserName(browser));
+ winPath.replace("/","\\");
+ return winPath;
+#else
+ QString path = getTargetPath(browser);
+ return QString("%1%2/%3.json").arg(QDir::homePath(), path, HostInstaller::HOST_NAME);
+#endif
+}
+
+QString HostInstaller::getInstallDir(SupportedBrowsers browser) const
+{
+ QString path = getTargetPath(browser);
+#ifdef Q_OS_WIN
+ return QCoreApplication::applicationDirPath();
+#else
+ return QString("%1%2").arg(QDir::homePath(), path);
+#endif
+}
+
+QJsonObject HostInstaller::constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location)
+{
+ QString path;
+ if (proxy) {
+ if (!location.isEmpty()) {
+ path = location;
+ } else {
+ path = QFileInfo(QCoreApplication::applicationFilePath()).absolutePath();
+ path.append("/keepassxc-proxy");
+#ifdef Q_OS_WIN
+ path.append(".exe");
+#endif
+ }
+ } else {
+ path = QFileInfo(QCoreApplication::applicationFilePath()).absoluteFilePath();
+ }
+#ifdef Q_OS_WIN
+ path.replace("/","\\");
+#endif
+
+ QJsonObject script;
+ script["name"] = HostInstaller::HOST_NAME;
+ script["description"] = "KeePassXC integration with native messaging support";
+ script["path"] = path;
+ script["type"] = "stdio";
+
+ QJsonArray arr;
+ if (browser == SupportedBrowsers::FIREFOX) {
+ for (const QString extension : HostInstaller::ALLOWED_EXTENSIONS) {
+ arr.append(extension);
+ }
+ script["allowed_extensions"] = arr;
+ } else {
+ for (const QString origin : HostInstaller::ALLOWED_ORIGINS) {
+ arr.append(origin);
+ }
+ script["allowed_origins"] = arr;
+ }
+
+ return script;
+}
+
+bool HostInstaller::registryEntryFound(const QSettings& settings)
+{
+ return !settings.value("Default").isNull();
+}
+
+bool HostInstaller::saveFile(SupportedBrowsers browser, const QJsonObject& script)
+{
+ QString path = getPath(browser);
+ QString installDir = getInstallDir(browser);
+ QDir dir(installDir);
+ if (!dir.exists()) {
+ QDir().mkpath(installDir);
+ }
+
+ QFile scriptFile(path);
+ if (!scriptFile.open(QIODevice::WriteOnly)) {
+ return false;
+ }
+
+ QJsonDocument doc(script);
+ qint64 bytesWritten = scriptFile.write(doc.toJson());
+ if (bytesWritten < 0) {
+ return false;
+ }
+
+ return true;
+}
diff --git a/src/browser/HostInstaller.h b/src/browser/HostInstaller.h
new file mode 100644
index 000000000..c3fc85620
--- /dev/null
+++ b/src/browser/HostInstaller.h
@@ -0,0 +1,63 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 HOSTINSTALLER_H
+#define HOSTINSTALLER_H
+
+#include <QObject>
+#include <QJsonObject>
+#include <QSettings>
+
+class HostInstaller : public QObject
+{
+ Q_OBJECT
+
+public:
+ enum SupportedBrowsers : int {
+ CHROME = 0,
+ CHROMIUM = 1,
+ FIREFOX = 2,
+ VIVALDI = 3
+ };
+
+public:
+ HostInstaller();
+ bool checkIfInstalled(SupportedBrowsers browser);
+ void installBrowser(SupportedBrowsers browser, const bool& enabled, const bool& proxy = false, const QString& location = "");
+ void updateBinaryPaths(const bool& proxy, const QString& location = "");
+
+private:
+ QString getTargetPath(SupportedBrowsers browser) const;
+ QString getBrowserName(SupportedBrowsers browser) const;
+ QString getPath(SupportedBrowsers browser) const;
+ QString getInstallDir(SupportedBrowsers browser) const;
+ QJsonObject constructFile(SupportedBrowsers browser, const bool& proxy, const QString& location);
+ bool registryEntryFound(const QSettings& settings);
+ bool saveFile(SupportedBrowsers browser, const QJsonObject& script);
+
+private:
+ static const QString HOST_NAME;
+ static const QStringList ALLOWED_EXTENSIONS;
+ static const QStringList ALLOWED_ORIGINS;
+ static const QString TARGET_DIR_CHROME;
+ static const QString TARGET_DIR_CHROMIUM;
+ static const QString TARGET_DIR_FIREFOX;
+ static const QString TARGET_DIR_VIVALDI;
+};
+
+#endif // HOSTINSTALLER_H
diff --git a/src/browser/NativeMessagingBase.cpp b/src/browser/NativeMessagingBase.cpp
new file mode 100644
index 000000000..fb4959688
--- /dev/null
+++ b/src/browser/NativeMessagingBase.cpp
@@ -0,0 +1,141 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 "NativeMessagingBase.h"
+#include <QStandardPaths>
+
+#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
+#include <sys/types.h>
+#include <sys/event.h>
+#include <sys/time.h>
+#include <unistd.h>
+#endif
+
+#ifdef Q_OS_LINUX
+#include <sys/epoll.h>
+#include <unistd.h>
+#endif
+
+#ifdef Q_OS_WIN
+#include <fcntl.h>
+#include <io.h>
+#endif
+
+NativeMessagingBase::NativeMessagingBase()
+{
+#ifdef Q_OS_WIN
+ _setmode(_fileno(stdin), _O_BINARY);
+ _setmode(_fileno(stdout), _O_BINARY);
+#else
+ m_notifier.reset(new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this));
+ connect(m_notifier.data(), SIGNAL(activated(int)), this, SLOT(newNativeMessage()));
+#endif
+}
+
+void NativeMessagingBase::newNativeMessage()
+{
+#if defined(Q_OS_UNIX) && !defined(Q_OS_LINUX)
+ struct kevent ev[1];
+ struct timespec ts = { 5, 0 };
+
+ int fd = kqueue();
+ if (fd == -1) {
+ m_notifier->setEnabled(false);
+ return;
+ }
+
+ EV_SET(ev, fileno(stdin), EVFILT_READ, EV_ADD, 0, 0, nullptr);
+ if (kevent(fd, ev, 1, nullptr, 0, &ts) == -1) {
+ m_notifier->setEnabled(false);
+ return;
+ }
+
+ int ret = kevent(fd, NULL, 0, ev, 1, &ts);
+ if (ret < 1) {
+ m_notifier->setEnabled(false);
+ ::close(fd);
+ return;
+ }
+#elif defined(Q_OS_LINUX)
+ int fd = epoll_create(5);
+ struct epoll_event event;
+ event.events = EPOLLIN;
+ event.data.fd = 0;
+ if (epoll_ctl(fd, EPOLL_CTL_ADD, 0, &event) != 0) {
+ m_notifier->setEnabled(false);
+ return;
+ }
+
+ if (epoll_wait(fd, &event, 1, 5000) < 1) {
+ m_notifier->setEnabled(false);
+ ::close(fd);
+ return;
+ }
+#endif
+ readLength();
+#ifndef Q_OS_WIN
+ ::close(fd);
+#endif
+}
+
+void NativeMessagingBase::readNativeMessages()
+{
+#ifdef Q_OS_WIN
+ quint32 length = 0;
+ while (m_running.load() && !std::cin.eof()) {
+ length = 0;
+ std::cin.read(reinterpret_cast<char*>(&length), 4);
+ readStdIn(length);
+ QThread::msleep(1);
+ }
+#endif
+}
+
+QString NativeMessagingBase::jsonToString(const QJsonObject& json) const
+{
+ return QString(QJsonDocument(json).toJson(QJsonDocument::Compact));
+}
+
+void NativeMessagingBase::sendReply(const QJsonObject& json)
+{
+ if (!json.isEmpty()) {
+ sendReply(jsonToString(json));
+ }
+}
+
+void NativeMessagingBase::sendReply(const QString& reply)
+{
+ if (!reply.isEmpty()) {
+ uint len = reply.length();
+ std::cout << char(((len>>0) & 0xFF)) << char(((len>>8) & 0xFF)) << char(((len>>16) & 0xFF)) << char(((len>>24) & 0xFF));
+ std::cout << reply.toStdString() << std::flush;
+ }
+}
+
+QString NativeMessagingBase::getLocalServerPath() const
+{
+#if defined(Q_OS_WIN)
+ return QStandardPaths::writableLocation(QStandardPaths::TempLocation) + "/kpxc_server";
+#elif defined(Q_OS_UNIX) && !defined(Q_OS_MAC)
+ // Use XDG_RUNTIME_DIR instead of /tmp/ if it's available
+ QString path = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation) + "/kpxc_server";
+ return path.isEmpty() ? "/tmp/kpxc_server" : path;
+#else // Q_OS_MAC and others
+ return "/tmp/kpxc_server";
+#endif
+}
diff --git a/src/browser/NativeMessagingBase.h b/src/browser/NativeMessagingBase.h
new file mode 100644
index 000000000..4253b7585
--- /dev/null
+++ b/src/browser/NativeMessagingBase.h
@@ -0,0 +1,61 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 NATIVEMESSAGINGBASE_H
+#define NATIVEMESSAGINGBASE_H
+
+#include <QObject>
+#include <QJsonObject>
+#include <QJsonDocument>
+#include <QFuture>
+#include <QtConcurrent/QtConcurrent>
+#include <QMutex>
+#include <QSocketNotifier>
+#include <QLocalServer>
+#include <QLocalSocket>
+#include <QAtomicInteger>
+#include <iostream>
+#include <unistd.h>
+
+class NativeMessagingBase : public QObject
+{
+ Q_OBJECT
+
+public:
+ explicit NativeMessagingBase();
+ ~NativeMessagingBase() = default;
+
+protected slots:
+ void newNativeMessage();
+
+protected:
+ virtual void readLength() = 0;
+ virtual void readStdIn(const quint32 length) = 0;
+ void readNativeMessages();
+ QString jsonToString(const QJsonObject& json) const;
+ void sendReply(const QJsonObject& json);
+ void sendReply(const QString& reply);
+ QString getLocalServerPath() const;
+
+protected:
+ QAtomicInteger<quint8> m_running;
+ QSharedPointer<QSocketNotifier> m_notifier;
+ QFuture<void> m_future;
+};
+
+#endif // NATIVEMESSAGINGBASE_H
diff --git a/src/browser/NativeMessagingHost.cpp b/src/browser/NativeMessagingHost.cpp
new file mode 100755
index 000000000..1dcd24cb1
--- /dev/null
+++ b/src/browser/NativeMessagingHost.cpp
@@ -0,0 +1,200 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 <QMutexLocker>
+#include <QtNetwork>
+#include <iostream>
+#include "sodium.h"
+#include "NativeMessagingHost.h"
+#include "BrowserSettings.h"
+
+NativeMessagingHost::NativeMessagingHost(DatabaseTabWidget* parent) :
+ NativeMessagingBase(),
+ m_mutex(QMutex::Recursive),
+ m_browserClients(m_browserService),
+ m_browserService(parent)
+{
+ m_localServer.reset(new QLocalServer(this));
+ m_localServer->setSocketOptions(QLocalServer::UserAccessOption);
+ m_running.store(false);
+
+ if (BrowserSettings::isEnabled() && !m_running) {
+ run();
+ }
+
+ connect(&m_browserService, SIGNAL(databaseLocked()), this, SLOT(databaseLocked()));
+ connect(&m_browserService, SIGNAL(databaseUnlocked()), this, SLOT(databaseUnlocked()));
+}
+
+NativeMessagingHost::~NativeMessagingHost()
+{
+ stop();
+}
+
+int NativeMessagingHost::init()
+{
+ QMutexLocker locker(&m_mutex);
+ return sodium_init();
+}
+
+void NativeMessagingHost::run()
+{
+ QMutexLocker locker(&m_mutex);
+ if (!m_running.load() && init() == -1) {
+ return;
+ }
+
+ // Update KeePassXC/keepassxc-proxy binary paths to Native Messaging scripts
+ if (BrowserSettings::updateBinaryPath()) {
+ BrowserSettings::updateBinaryPaths(BrowserSettings::useCustomProxy() ? BrowserSettings::customProxyLocation() : "");
+ }
+
+ m_running.store(true);
+#ifdef Q_OS_WIN
+ m_future = QtConcurrent::run(this, static_cast<void(NativeMessagingHost::*)()>(&NativeMessagingHost::readNativeMessages));
+#endif
+
+ if (BrowserSettings::supportBrowserProxy()) {
+ QString serverPath = getLocalServerPath();
+ QFile::remove(serverPath);
+ m_localServer->listen(serverPath);
+ connect(m_localServer.data(), SIGNAL(newConnection()), this, SLOT(newLocalConnection()));
+ } else {
+ m_localServer->close();
+ }
+}
+
+void NativeMessagingHost::stop()
+{
+ databaseLocked();
+ QMutexLocker locker(&m_mutex);
+ m_socketList.clear();
+ m_running.testAndSetOrdered(true, false);
+ m_future.waitForFinished();
+ m_localServer->close();
+}
+
+void NativeMessagingHost::readLength()
+{
+ quint32 length = 0;
+ std::cin.read(reinterpret_cast<char*>(&length), 4);
+ if (!std::cin.eof() && length > 0)
+ {
+ readStdIn(length);
+ }
+}
+
+void NativeMessagingHost::readStdIn(const quint32 length)
+{
+ if (length > 0) {
+ QByteArray arr;
+ arr.reserve(length);
+
+ for (quint32 i = 0; i < length; ++i) {
+ arr.append(getchar());
+ }
+
+ if (arr.length() > 0) {
+ QMutexLocker locker(&m_mutex);
+ sendReply(m_browserClients.readResponse(arr));
+ }
+ }
+}
+
+void NativeMessagingHost::newLocalConnection()
+{
+ QLocalSocket* socket = m_localServer->nextPendingConnection();
+ connect(socket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
+ connect(socket, SIGNAL(disconnected()), this, SLOT(disconnectSocket()));
+}
+
+void NativeMessagingHost::newLocalMessage()
+{
+ QLocalSocket* socket = qobject_cast<QLocalSocket*>(QObject::sender());
+
+ if (!socket || socket->bytesAvailable() <= 0) {
+ return;
+ }
+
+ QByteArray arr = socket->readAll();
+ if (arr.isEmpty()) {
+ return;
+ }
+
+ QMutexLocker locker(&m_mutex);
+ if (!m_socketList.contains(socket)) {
+ m_socketList.push_back(socket);
+ }
+
+ QString reply = jsonToString(m_browserClients.readResponse(arr));
+ if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
+ QByteArray arr = reply.toUtf8();
+ socket->write(arr.constData(), arr.length());
+ socket->flush();
+ }
+}
+
+void NativeMessagingHost::sendReplyToAllClients(const QJsonObject& json)
+{
+ QString reply = jsonToString(json);
+ QMutexLocker locker(&m_mutex);
+ for (const auto socket : m_socketList) {
+ if (socket && socket->isValid() && socket->state() == QLocalSocket::ConnectedState) {
+ QByteArray arr = reply.toUtf8();
+ socket->write(arr.constData(), arr.length());
+ socket->flush();
+ }
+ }
+}
+
+void NativeMessagingHost::disconnectSocket()
+{
+ QLocalSocket* socket(qobject_cast<QLocalSocket*>(QObject::sender()));
+ QMutexLocker locker(&m_mutex);
+ for (auto s : m_socketList) {
+ if (s == socket) {
+ m_socketList.removeOne(s);
+ }
+ }
+}
+
+void NativeMessagingHost::removeSharedEncryptionKeys()
+{
+ QMutexLocker locker(&m_mutex);
+ m_browserService.removeSharedEncryptionKeys();
+}
+
+void NativeMessagingHost::removeStoredPermissions()
+{
+ QMutexLocker locker(&m_mutex);
+ m_browserService.removeStoredPermissions();
+}
+
+void NativeMessagingHost::databaseLocked()
+{
+ QJsonObject response;
+ response["action"] = "database-locked";
+ sendReplyToAllClients(response);
+}
+
+void NativeMessagingHost::databaseUnlocked()
+{
+ QJsonObject response;
+ response["action"] = "database-unlocked";
+ sendReplyToAllClients(response);
+}
diff --git a/src/browser/NativeMessagingHost.h b/src/browser/NativeMessagingHost.h
new file mode 100755
index 000000000..80825237b
--- /dev/null
+++ b/src/browser/NativeMessagingHost.h
@@ -0,0 +1,67 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 NATIVEMESSAGINGHOST_H
+#define NATIVEMESSAGINGHOST_H
+
+#include "NativeMessagingBase.h"
+#include "BrowserClients.h"
+#include "BrowserService.h"
+#include "gui/DatabaseTabWidget.h"
+
+class NativeMessagingHost : public NativeMessagingBase
+{
+ Q_OBJECT
+
+ typedef QList<QLocalSocket*> SocketList;
+
+public:
+ explicit NativeMessagingHost(DatabaseTabWidget* parent = 0);
+ ~NativeMessagingHost();
+ int init();
+ void run();
+ void stop();
+
+public slots:
+ void removeSharedEncryptionKeys();
+ void removeStoredPermissions();
+
+signals:
+ void quit();
+
+private:
+ void readLength();
+ void readStdIn(const quint32 length);
+ void sendReplyToAllClients(const QJsonObject& json);
+
+private slots:
+ void databaseLocked();
+ void databaseUnlocked();
+ void newLocalConnection();
+ void newLocalMessage();
+ void disconnectSocket();
+
+private:
+ QMutex m_mutex;
+ BrowserClients m_browserClients;
+ BrowserService m_browserService;
+ QSharedPointer<QLocalServer> m_localServer;
+ SocketList m_socketList;
+};
+
+#endif // NATIVEMESSAGINGHOST_H
diff --git a/src/browser/Variant.cpp b/src/browser/Variant.cpp
new file mode 100644
index 000000000..2d42ac4ec
--- /dev/null
+++ b/src/browser/Variant.cpp
@@ -0,0 +1,37 @@
+/*
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 "Variant.h"
+
+QVariantMap qo2qv(const QObject* object, const QStringList& ignoredProperties)
+{
+ QVariantMap result;
+ const QMetaObject* metaobject = object->metaObject();
+ int count = metaobject->propertyCount();
+ for (int i = 0; i < count; ++i) {
+ QMetaProperty metaproperty = metaobject->property(i);
+ const char* name = metaproperty.name();
+
+ if (ignoredProperties.contains(QLatin1String(name)) || (!metaproperty.isReadable())) {
+ continue;
+ }
+
+ QVariant value = object->property(name);
+ result[QLatin1String(name)] = value;
+ }
+ return result;
+}
diff --git a/src/browser/Variant.h b/src/browser/Variant.h
new file mode 100644
index 000000000..e467b4211
--- /dev/null
+++ b/src/browser/Variant.h
@@ -0,0 +1,25 @@
+/*
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 VARIANT_H
+#define VARIANT_H
+
+#include <QtCore>
+
+QVariantMap qo2qv(const QObject* object, const QStringList& ignoredProperties = QStringList(QString(QLatin1String("objectName"))));
+
+#endif // VARIANT_H
diff --git a/src/config-keepassx.h.cmake b/src/config-keepassx.h.cmake
index b06e702a9..5a00a5dba 100644
--- a/src/config-keepassx.h.cmake
+++ b/src/config-keepassx.h.cmake
@@ -16,6 +16,7 @@
#cmakedefine WITH_XC_AUTOTYPE
#cmakedefine WITH_XC_YUBIKEY
#cmakedefine WITH_XC_SSHAGENT
+#cmakedefine WITH_XC_BROWSER
#cmakedefine KEEPASSXC_DIST
#cmakedefine KEEPASSXC_DIST_TYPE "@KEEPASSXC_DIST_TYPE@"
diff --git a/src/core/PassphraseGenerator.cpp b/src/core/PassphraseGenerator.cpp
index 1af614795..115c70d6d 100644
--- a/src/core/PassphraseGenerator.cpp
+++ b/src/core/PassphraseGenerator.cpp
@@ -28,8 +28,7 @@ PassphraseGenerator::PassphraseGenerator()
: m_wordCount(0)
, m_separator(' ')
{
- const QString path = filePath()->dataPath("wordlists/eff_large.wordlist");
- setWordList(path);
+
}
double PassphraseGenerator::calculateEntropy(QString passphrase)
@@ -46,12 +45,12 @@ double PassphraseGenerator::calculateEntropy(QString passphrase)
void PassphraseGenerator::setWordCount(int wordCount)
{
if (wordCount > 0) {
- m_wordCount = wordCount;
+ m_wordCount = wordCount;
} else {
// safe default if something goes wrong
m_wordCount = 7;
}
-
+
}
void PassphraseGenerator::setWordList(QString path)
@@ -75,6 +74,12 @@ void PassphraseGenerator::setWordList(QString path)
}
}
+void PassphraseGenerator::setDefaultWordList()
+{
+ const QString path = filePath()->dataPath("wordlists/eff_large.wordlist");
+ setWordList(path);
+}
+
void PassphraseGenerator::setWordSeparator(QString separator) {
m_separator = separator;
}
diff --git a/src/core/PassphraseGenerator.h b/src/core/PassphraseGenerator.h
index 3be2d5836..20845ff21 100644
--- a/src/core/PassphraseGenerator.h
+++ b/src/core/PassphraseGenerator.h
@@ -30,6 +30,7 @@ public:
double calculateEntropy(QString passphrase);
void setWordCount(int wordCount);
void setWordList(QString path);
+ void setDefaultWordList();
void setWordSeparator(QString separator);
bool isValid() const;
@@ -43,4 +44,4 @@ private:
Q_DISABLE_COPY(PassphraseGenerator)
};
-#endif // KEEPASSX_PASSPHRASEGENERATOR_H \ No newline at end of file
+#endif // KEEPASSX_PASSPHRASEGENERATOR_H
diff --git a/src/gui/AboutDialog.cpp b/src/gui/AboutDialog.cpp
index e89a7fdcb..44ab895e8 100644
--- a/src/gui/AboutDialog.cpp
+++ b/src/gui/AboutDialog.cpp
@@ -90,6 +90,9 @@ AboutDialog::AboutDialog(QWidget* parent)
#ifdef WITH_XC_SSHAGENT
extensions += "\n- SSH Agent";
#endif
+#ifdef WITH_XC_BROWSER
+ extensions += "\n- Native messaging browser extension";
+#endif
if (extensions.isEmpty())
extensions = " None";
diff --git a/src/gui/MainWindow.cpp b/src/gui/MainWindow.cpp
index 72be3a4aa..4804b47b2 100644
--- a/src/gui/MainWindow.cpp
+++ b/src/gui/MainWindow.cpp
@@ -54,6 +54,12 @@
#include "sshagent/SSHAgent.h"
#endif
+#ifdef WITH_XC_BROWSER
+#include "browser/NativeMessagingHost.h"
+#include "browser/BrowserSettings.h"
+#include "browser/BrowserOptionDialog.h"
+#endif
+
#include "gui/SettingsWidget.h"
#include "gui/PasswordGeneratorWidget.h"
@@ -104,6 +110,54 @@ private:
};
#endif
+#ifdef WITH_XC_BROWSER
+class BrowserPlugin: public ISettingsPage
+{
+ public:
+ BrowserPlugin(DatabaseTabWidget* tabWidget) {
+ m_nativeMessagingHost = QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget));
+ }
+
+ ~BrowserPlugin() {
+
+ }
+
+ QString name() override
+ {
+ return QObject::tr("Browser extension with native messaging");
+ }
+
+ QIcon icon() override
+ {
+ return FilePath::instance()->icon("apps", "internet-web-browser");
+ }
+
+ QWidget* createWidget() override {
+ BrowserOptionDialog* dlg = new BrowserOptionDialog();
+ QObject::connect(dlg, SIGNAL(removeSharedEncryptionKeys()), m_nativeMessagingHost.data(), SLOT(removeSharedEncryptionKeys()));
+ QObject::connect(dlg, SIGNAL(removeStoredPermissions()), m_nativeMessagingHost.data(), SLOT(removeStoredPermissions()));
+ return dlg;
+ }
+
+ void loadSettings(QWidget* widget) override
+ {
+ qobject_cast<BrowserOptionDialog*>(widget)->loadSettings();
+ }
+
+ void saveSettings(QWidget* widget) override
+ {
+ qobject_cast<BrowserOptionDialog*>(widget)->saveSettings();
+ if (BrowserSettings::isEnabled()) {
+ m_nativeMessagingHost->run();
+ } else {
+ m_nativeMessagingHost->stop();
+ }
+ }
+ private:
+ QSharedPointer<NativeMessagingHost> m_nativeMessagingHost;
+};
+#endif
+
const QString MainWindow::BaseWindowTitle = "KeePassXC";
MainWindow::MainWindow()
@@ -134,6 +188,9 @@ MainWindow::MainWindow()
SSHAgent::init(this);
m_ui->settingsWidget->addSettingsPage(new AgentSettingsPage(m_ui->tabWidget));
#endif
+ #ifdef WITH_XC_BROWSER
+ m_ui->settingsWidget->addSettingsPage(new BrowserPlugin(m_ui->tabWidget));
+ #endif
setWindowIcon(filePath()->applicationIcon());
m_ui->globalMessageWidget->setHidden(true);
diff --git a/src/gui/PasswordGeneratorWidget.cpp b/src/gui/PasswordGeneratorWidget.cpp
index 70375a048..1c72bd353 100644
--- a/src/gui/PasswordGeneratorWidget.cpp
+++ b/src/gui/PasswordGeneratorWidget.cpp
@@ -80,7 +80,8 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
m_ui->comboBoxWordList->setVisible(false);
m_ui->labelWordList->setVisible(false);
}
-
+
+ m_dicewareGenerator->setDefaultWordList();
loadSettings();
reset();
}
@@ -164,7 +165,7 @@ void PasswordGeneratorWidget::keyPressEvent(QKeyEvent* e)
}
void PasswordGeneratorWidget::regeneratePassword()
-{
+{
if (m_ui->tabWidget->currentIndex() == Password) {
if (m_passwordGenerator->isValid()) {
QString password = m_passwordGenerator->generatePassword();
diff --git a/src/main.cpp b/src/main.cpp
index 82331f072..deabeabb7 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -69,12 +69,18 @@ int main(int argc, char** argv)
"keyfile");
QCommandLineOption pwstdinOption("pw-stdin",
QCoreApplication::translate("main", "read password of the database from stdin"));
+ // This is needed under Windows where clients send --parent-window parameter with Native Messaging connect method
+ QCommandLineOption parentWindowOption(QStringList() << "pw"
+ << "parent-window",
+ QCoreApplication::translate("main", "Parent window handle"),
+ "handle");
parser.addHelpOption();
parser.addVersionOption();
parser.addOption(configOption);
parser.addOption(keyfileOption);
parser.addOption(pwstdinOption);
+ parser.addOption(parentWindowOption);
parser.process(app);
const QStringList fileNames = parser.positionalArguments();
@@ -86,7 +92,7 @@ int main(int argc, char** argv)
qWarning() << QCoreApplication::translate("Main", "Another instance of KeePassXC is already running.").toUtf8().constData();
return 0;
}
-
+
QApplication::setQuitOnLastWindowClosed(false);
if (!Crypto::init()) {
@@ -116,7 +122,7 @@ int main(int argc, char** argv)
QObject::connect(&app, SIGNAL(applicationActivated()), &mainWindow, SLOT(bringToFront()));
QObject::connect(&app, SIGNAL(openFile(QString)), &mainWindow, SLOT(openDatabase(QString)));
QObject::connect(&app, SIGNAL(quitSignalReceived()), &mainWindow, SLOT(appExit()), Qt::DirectConnection);
-
+
// start minimized if configured
bool minimizeOnStartup = config()->get("GUI/MinimizeOnStartup").toBool();
bool minimizeToTray = config()->get("GUI/MinimizeToTray").toBool();
@@ -126,7 +132,7 @@ int main(int argc, char** argv)
if (!(minimizeOnStartup && minimizeToTray)) {
mainWindow.show();
}
-
+
if (config()->get("OpenPreviousDatabasesOnStartup").toBool()) {
const QStringList fileNames = config()->get("LastOpenedDatabases").toStringList();
for (const QString& filename: fileNames) {
@@ -138,13 +144,12 @@ int main(int argc, char** argv)
const bool pwstdin = parser.isSet(pwstdinOption);
for (const QString& filename: fileNames) {
- if (!filename.isEmpty() && QFile::exists(filename)) {
+ if (!filename.isEmpty() && QFile::exists(filename) && !filename.endsWith(".json", Qt::CaseInsensitive)) {
QString password;
if (pwstdin) {
static QTextStream in(stdin, QIODevice::ReadOnly);
password = in.readLine();
}
- mainWindow.openDatabase(filename, password, parser.value(keyfileOption));
}
}
diff --git a/src/proxy/CMakeLists.txt b/src/proxy/CMakeLists.txt
new file mode 100755
index 000000000..4fe32428d
--- /dev/null
+++ b/src/proxy/CMakeLists.txt
@@ -0,0 +1,34 @@
+# Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+# 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 3 of the License, or
+# (at your option) any later version.
+#
+# 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/>.
+
+if(WITH_XC_BROWSER)
+ include_directories(${BROWSER_SOURCE_DIR})
+
+ set(proxy_SOURCES
+ keepassxc-proxy.cpp
+ ${BROWSER_SOURCE_DIR}/NativeMessagingBase.cpp
+ NativeMessagingHost.cpp)
+
+ add_library(proxy STATIC ${proxy_SOURCES})
+ target_link_libraries(proxy Qt5::Core Qt5::Network)
+ add_executable(keepassxc-proxy keepassxc-proxy.cpp)
+ target_link_libraries(keepassxc-proxy proxy)
+
+ install(TARGETS keepassxc-proxy
+ BUNDLE DESTINATION . COMPONENT Runtime
+ RUNTIME DESTINATION ${PROXY_INSTALL_DIR} COMPONENT Runtime)
+
+endif()
diff --git a/src/proxy/NativeMessagingHost.cpp b/src/proxy/NativeMessagingHost.cpp
new file mode 100755
index 000000000..2add63814
--- /dev/null
+++ b/src/proxy/NativeMessagingHost.cpp
@@ -0,0 +1,95 @@
+/*
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 <QCoreApplication>
+#include "NativeMessagingHost.h"
+
+NativeMessagingHost::NativeMessagingHost() : NativeMessagingBase()
+{
+ m_localSocket = new QLocalSocket();
+ m_localSocket->connectToServer(getLocalServerPath());
+#ifdef Q_OS_WIN
+ m_running.store(true);
+ m_future = QtConcurrent::run(this, static_cast<void(NativeMessagingHost::*)()>(&NativeMessagingHost::readNativeMessages));
+#endif
+ connect(m_localSocket, SIGNAL(readyRead()), this, SLOT(newLocalMessage()));
+ connect(m_localSocket, SIGNAL(disconnected()), this, SLOT(deleteSocket()));
+ connect(m_localSocket, SIGNAL(stateChanged(QLocalSocket::LocalSocketState)), this, SLOT(socketStateChanged(QLocalSocket::LocalSocketState)));
+}
+
+NativeMessagingHost::~NativeMessagingHost()
+{
+#ifdef Q_OS_WIN
+ m_future.waitForFinished();
+#endif
+}
+
+void NativeMessagingHost::readLength()
+{
+ quint32 length = 0;
+ std::cin.read(reinterpret_cast<char*>(&length), 4);
+ if (!std::cin.eof() && length > 0) {
+ readStdIn(length);
+ } else {
+ QCoreApplication::quit();
+ }
+}
+
+void NativeMessagingHost::readStdIn(const quint32 length)
+{
+ if (length > 0) {
+ QByteArray arr;
+ arr.reserve(length);
+
+ for (quint32 i = 0; i < length; ++i) {
+ arr.append(getchar());
+ }
+
+ if (arr.length() > 0 && m_localSocket && m_localSocket->state() == QLocalSocket::ConnectedState) {
+ m_localSocket->write(arr.constData(), arr.length());
+ m_localSocket->flush();
+ }
+ }
+}
+
+void NativeMessagingHost::newLocalMessage()
+{
+ if (!m_localSocket || m_localSocket->bytesAvailable() <= 0) {
+ return;
+ }
+
+ QByteArray arr = m_localSocket->readAll();
+ if (!arr.isEmpty()) {
+ sendReply(arr);
+ }
+}
+
+void NativeMessagingHost::deleteSocket()
+{
+ if (m_notifier) {
+ m_notifier->setEnabled(false);
+ }
+ m_localSocket->deleteLater();
+ QCoreApplication::quit();
+}
+
+void NativeMessagingHost::socketStateChanged(QLocalSocket::LocalSocketState socketState)
+{
+ if (socketState == QLocalSocket::UnconnectedState || socketState == QLocalSocket::ClosingState) {
+ m_running.testAndSetOrdered(true, false);
+ }
+}
diff --git a/src/proxy/NativeMessagingHost.h b/src/proxy/NativeMessagingHost.h
new file mode 100755
index 000000000..41f3ed753
--- /dev/null
+++ b/src/proxy/NativeMessagingHost.h
@@ -0,0 +1,43 @@
+/*
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 NATIVEMESSAGINGHOST_H
+#define NATIVEMESSAGINGHOST_H
+
+#include "NativeMessagingBase.h"
+
+class NativeMessagingHost : public NativeMessagingBase
+{
+ Q_OBJECT
+public:
+ NativeMessagingHost();
+ ~NativeMessagingHost();
+
+public slots:
+ void newLocalMessage();
+ void deleteSocket();
+ void socketStateChanged(QLocalSocket::LocalSocketState socketState);
+
+private:
+ void readLength();
+ void readStdIn(const quint32 length);
+
+private:
+ QLocalSocket* m_localSocket;
+};
+
+#endif // NATIVEMESSAGINGHOST_H
diff --git a/src/proxy/keepassxc-proxy.cpp b/src/proxy/keepassxc-proxy.cpp
new file mode 100644
index 000000000..9509c1eab
--- /dev/null
+++ b/src/proxy/keepassxc-proxy.cpp
@@ -0,0 +1,66 @@
+/*
+* Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
+* 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 3 of the License, or
+* (at your option) any later version.
+*
+* 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 <QCoreApplication>
+#include <iostream>
+#include "NativeMessagingHost.h"
+
+#ifndef Q_OS_WIN
+#include <initializer_list>
+#include <signal.h>
+#include <unistd.h>
+
+// (C) Gist: https://gist.github.com/azadkuh/a2ac6869661ebd3f8588
+void ignoreUnixSignals(std::initializer_list<int> ignoreSignals) {
+ for (int sig : ignoreSignals) {
+ signal(sig, SIG_IGN);
+ }
+}
+
+void catchUnixSignals(std::initializer_list<int> quitSignals) {
+ auto handler = [](int sig) -> void {
+ std::cerr << sig;
+ QCoreApplication::quit();
+ };
+
+ sigset_t blocking_mask;
+ sigemptyset(&blocking_mask);
+ for (auto sig : quitSignals) {
+ sigaddset(&blocking_mask, sig);
+ }
+
+ struct sigaction sa;
+ sa.sa_handler = handler;
+ sa.sa_mask = blocking_mask;
+ sa.sa_flags = 0;
+
+ for (auto sig : quitSignals) {
+ sigaction(sig, &sa, nullptr);
+ }
+}
+#endif
+
+int main(int argc, char *argv[])
+{
+ QCoreApplication a(argc, argv);
+#if defined(Q_OS_UNIX) || defined(Q_OS_LINUX)
+ catchUnixSignals({SIGQUIT, SIGINT, SIGTERM, SIGHUP});
+#endif
+ NativeMessagingHost host;
+ return a.exec();
+}