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

@ -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
@ -86,6 +87,7 @@ 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)

View File

@ -32,7 +32,8 @@ so please check out your distribution's package list to see if KeePassXC is avai
- 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)
[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.

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

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

@ -81,6 +81,7 @@ PasswordGeneratorWidget::PasswordGeneratorWidget(QWidget* parent)
m_ui->labelWordList->setVisible(false);
}
m_dicewareGenerator->setDefaultWordList();
loadSettings();
reset();
}

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