Merge pull request #608 from varjolintu/feature/chromeKeePassXC

Support for browser extension(s) with Native Messaging
This commit is contained in:
Janek Bevendorff 2018-01-04 23:08:37 +01:00 committed by GitHub
commit a5a5c6723e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
44 changed files with 4450 additions and 47 deletions

View File

@ -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")

View File

@ -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
@ -59,6 +61,10 @@ RUN set -x \
libfuse2 \
wget
RUN set -x \
&& apt-get autoremove --purge \
&& rm -rf /var/lib/apt/lists/*
VOLUME /keepassxc/src
VOLUME /keepassxc/out
WORKDIR /keepassxc

View File

@ -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)

View File

@ -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!

View File

@ -16,7 +16,18 @@
# TIP: check this Dockerfile using this online tool: https://www.fromlatest.io
FROM ubuntu:trusty
FROM ubuntu:14.04
ENV QT5_VERSION=53
ENV QT5_PPA_VERSION=${QT5_VERSION}2
RUN set -x \
&& apt-get update -y \
&& apt-get -y install software-properties-common
RUN set -x \
&& add-apt-repository ppa:beineri/opt-qt${QT5_PPA_VERSION}-trusty \
&& LC_ALL=C.UTF-8 add-apt-repository ppa:ondrej/php
RUN set -x \
&& apt-get -y update \
@ -24,13 +35,21 @@ RUN set -x \
git build-essential clang-3.6 libclang-common-3.6-dev clang-format-3.6 cmake3 make \
curl ca-certificates gnupg2 \
libgcrypt20-dev zlib1g-dev libyubikey-dev libykpers-1-dev \
qttools5-dev \
qttools5-dev-tools \
qtbase5-dev \
libqt5x11extras5-dev \
qt${QT5_VERSION}base \
qt${QT5_VERSION}tools \
qt${QT5_VERSION}x11extras \
qt${QT5_VERSION}translations \
libxi-dev \
libxtst-dev \
xvfb \
libsodium-dev
ENV CMAKE_PREFIX_PATH=/opt/qt${QT5_VERSION}/lib/cmake
ENV LD_LIBRARY_PATH=/opt/qt${QT5_VERSION}/lib
RUN set -x \
&& echo /opt/qt${QT_VERSION}/lib > /etc/ld.so.conf.d/qt${QT5_VERSION}.conf
RUN set -x \
&& apt-get autoremove --purge \
&& rm -rf /var/lib/apt/lists/*

267
cmake/Findsodium.cmake Normal file
View File

@ -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()

View File

@ -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

View File

@ -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}

View File

@ -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);
}

View File

@ -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

View File

@ -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>

546
src/browser/BrowserAction.cpp Executable file
View File

@ -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();
}

97
src/browser/BrowserAction.h Executable file
View File

@ -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

77
src/browser/BrowserClients.cpp Executable file
View File

@ -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();
}

56
src/browser/BrowserClients.h Executable file
View File

@ -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

View File

@ -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);
}

View File

@ -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

View File

@ -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());
}

View File

@ -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

View File

@ -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>

View File

@ -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();
}
}
}

View File

@ -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

377
src/browser/BrowserSettings.cpp Executable file
View File

@ -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);
}

105
src/browser/BrowserSettings.h Executable file
View File

@ -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

37
src/browser/CMakeLists.txt Executable file
View File

@ -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()

View File

@ -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;
}

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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);
}

View File

@ -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

37
src/browser/Variant.cpp Normal file
View File

@ -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;
}

25
src/browser/Variant.h Normal file
View File

@ -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

View File

@ -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@"

View File

@ -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;
}

View File

@ -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
#endif // KEEPASSX_PASSPHRASEGENERATOR_H

View File

@ -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";

View File

@ -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);

View File

@ -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();

View File

@ -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));
}
}

34
src/proxy/CMakeLists.txt Executable file
View File

@ -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()

View File

@ -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);
}
}

43
src/proxy/NativeMessagingHost.h Executable file
View File

@ -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

View File

@ -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();
}