Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for browser extension(s) with Native Messaging #608

Merged

Conversation

varjolintu
Copy link
Member

@varjolintu varjolintu commented May 29, 2017

Added a new plugin "Browser extension (Native Messaging)" to the project. It listens the stdin asynchronously and transfers messages between a browser extension which supports Native Messaging API. This implementation doesn't restrict the development of another browser extension if anyone wants to do so.

Implements a new compilation option WITH_XC_BROWSER. The only dependency is libsodium which is used for the crypto.

Description

KeePassXC can be used with a safer browser extension (when compared to chromeIPass or passIFox) where no plain text keys or messages concerning login data are transferred. Also, the keepassxc-browser extension is WebExtension compatible (can be used with Firefox 57/Nightly when it's finally released).

There's also support for Unix domain sockets (named pipes on Windows) listening for supporting a proxy application between KeePassXC and the browser extension (disabled by default). This prevents browsers from communicating, launching and closing the KeePassXC instance directly.

Motivation and context

  • chromeIPass and passIFox are outdated with deprecated API calls
  • Fixes #28

How has this been tested?

The code was tested with the keepassxc-browser extension, with and without the compilation option. Environments used for are macOS 10.12.5 and Ubuntu Linux 17.04.
Changes are not affecting to other areas of the code. Browser extension plugin is not enabled by default even when the compilation option is being used (works similar to KeePassHTTP plugin).

Also, the code has been tested with multiple databases open, databases closed, and other various situations.

Types of changes

  • ✅ New feature (non-breaking change which adds functionality)

Checklist:

  • ✅ I have read the CONTRIBUTING document. [REQUIRED]
  • ✅ My code follows the code style of this project. [REQUIRED]
  • ✅ All new and existing tests passed. [REQUIRED]
  • ✅ I have compiled and verified my code with -DWITH_ASAN=ON. [REQUIRED]
  • ✅ My change requires a change to the documentation and I have updated it accordingly.

@varjolintu varjolintu changed the title Feature/chrome kee pass xc Feature/chromeKeePassXC May 29, 2017
@varjolintu varjolintu changed the title Feature/chromeKeePassXC Support for browser extension(s) with Native Messaging May 29, 2017
@phoerious phoerious added this to the v2.3.0 milestone May 29, 2017
@droidmonkey
Copy link
Member

This is great work, some of the commits should be squashed prior to merging in the future.

@varjolintu
Copy link
Member Author

Thank you. The commits will be squashed in the near future when the final changes are ready. The feature now supports both direct Native Messaging with the browser extension and a proxy method through UDP sockets. The direct method requires option "Automatically save after every change" to be enabled. Without it the database loses any changes when browser exits. The latter method solves the issue but requires additional application (keepassxc-proxy, under development) between the KeePassXC and keepassxc-browser.

@droidmonkey
Copy link
Member

You seriously rock! Thanks man

@mauron85
Copy link

mauron85 commented Jun 20, 2017

Awesome work. Hat off.

I've found this https://chrome.google.com/webstore/detail/keepassxc-browser/iopaggbpplllidnfmcghoonnokmjoicf/related

Is it replacement for keepassxc-browser (which I've never found/used) ?
Edit: I'm idiot, it's actually keepassxc-browser. :-)

@mauron85 mauron85 mentioned this pull request Jun 22, 2017
Copy link
Member

@phoerious phoerious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for updating the pull request.

I marked all issues I found during a first read-through. They are mostly formatting issues, but I also found a few gotyas.

#include "ui_BrowserAccessControlDialog.h"
#include "core/Entry.h"

BrowserAccessControlDialog::BrowserAccessControlDialog(QWidget *parent) :
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reference and pointer operators belong to the type, not the parameter name. No space before and one space after the operator.

Many more instances of this follow.

return false;

QVariantMap map = doc.object().toVariantMap();
for (QVariantMap::iterator iter = map.begin(); iter != map.end(); ++iter) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should use const_iterator here.

m_ui->bestMatchOnly->setChecked(settings.bestMatchOnly());
m_ui->unlockDatabase->setChecked(settings.unlockDatabase());
m_ui->matchUrlScheme->setChecked(settings.matchUrlScheme());
if (settings.sortByUsername())
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use braces.

@@ -0,0 +1,346 @@
/*
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You seem to duplicate the whole PasswordGeneratorWidget. Isn't there a way to integrate the existing widget?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. But the UI is different in the plugin than in the normal password generator. For example, there's no need for any generate buttons or the password text field etc. Of course I could give it a try.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use PasswordGeneratorWidget. About the generate buttons and the password field we can use something like the standalone mode of the current widget. Like an http mode that will hide useless UI controls

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Those buttons could be made configurable via properties.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, all the config/preference should stay in one place (PasswordGeneratorWidget) and not being duplicated

switch(dbWidget->currentMode()) {
case DatabaseWidget::None:
case DatabaseWidget::LockedMode:
break;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason not to use a return here, too?

Copy link
Member Author

@varjolintu varjolintu Jul 18, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function returns false anyway after that?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. But a return makes things clearer and we are less likely to introduce bugs in the future.

const QByteArray ca = base64Decode(m_clientPublicKey);
const QByteArray sa = base64Decode(m_secretKey);

std::memcpy(m, ma.toStdString().data(), ma.length());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing bounds check?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

crypto_box_open_easy() takes unsigned char arrays as parameters. Is there really a reliable way to convert QByteArray straight to unsigned char*? On solution to this is to wrap the QByteArrays to a std::vector and then use .data(). At least it would be safer than memcpy. What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What about QByteArray::data()? You can cast it to unsigned if needed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's possible yes, but doing a reinterpret_cast for every variable is not gonna look pretty.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It avoids duplicate allocation, though.
If you do it this way, though, you need to check at least if the char arrays have the capacity to hold the data.

return result;
}

QByteArray ChromeListener::decrypt(const QString encrypted, const QString nonce) const
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See comments for encrypt()

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has the same fix as encrypt().

QVariantMap result;
const QMetaObject *metaobject = object->metaObject();
int count = metaobject->propertyCount();
for (int i=0; i<count; ++i) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use spaces between operators.

{
public:
BrowserPlugin(DatabaseTabWidget * tabWidget) {
m_chromeListener = new ChromeListener(tabWidget);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be a smart pointer…

}

~BrowserPlugin() {
delete m_chromeListener;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

… and then this isn't needed.

char buf[4] = {};
async_read(sd, buffer(buf,sizeof(buf)), transfer_at_least(1), [&](error_code ec, size_t br) {
std::array<char, 4> buf;
async_read(sd, buffer(buf), transfer_at_least(1), [&](error_code ec, size_t br) {
if (!ec && br >= 1) {
uint len = 0;
for (int i = 0; i < 4; i++) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you maybe use buf.size() here instead of a magic 4 or at least buf.at(i) in the next line instead of buf[i], so we have explicit bounds checks? This is just to be sure we won't introduce any errors in the future.

@varjolintu
Copy link
Member Author

The final biggest feature is finally added to the branch. The Password Generator tab is completely removed from the Browser extension settings. It has been replaced with a Supported Browsers tab where user can install all the necessary JSON files for Native Messaging hosts. This way there is no need to use a separate installer script with the browser extension.

This tab can detect already installed host files, and also install/remove them when necessary. Under Windows the JSON file is created to the same directory with the executable, and the path to that JSON file is written to the registry with the correspoding browser.

@duk3luk3
Copy link

duk3luk3 commented Aug 8, 2017

Hi @varjolintu, as I already told you in https://github.com/varjolintu/keepassxc-browser/issues/13, I'm working on making some big changes to the KeepassHTTP interface (everything in src/http basically).

As far as I can see from a quick glance at your PR branch, you did a cp -r src/http src/browser and went from there. Did you make any big changes to the (user) interface other than replacing the http server with your own stdin / libsodium based server? I'm wondering about how much extra work I will have if I first make my changes to the http code and then have to redo them for the browser code.

I'm also wondering about the stdin listener - does that mean KeePassXC has to be forked from the browser so it can work? Otherwise, how could the extension attach to KeePassXC's stdin?

@varjolintu
Copy link
Member Author

There's no big differences in the user interfaces. Just some code cleaning and minor changes. The biggest thing is that the password generator tab is removed from the settings dialog.

And yes, connecting to the stdin listener means the browser must start the KeePassXC process. There's also possibility to use a proxy app which is launced by the browser (handles the Native Messaging) and UDP is used to connect it to KeePassXC on-the-fly.

Keeping it short, any new changes to the UI side should't be hard to do because it's almost identical with the older HTTP side.

@duk3luk3
Copy link

duk3luk3 commented Aug 9, 2017

The proxy app sounds like a much better idea - e.g. if I want to run both chrome and firefox at the same time and have access to my keepass database from both, or other applications - Thunderbird comes to mind...

And thanks - I'll keep working on the KeePassHTTP code then and port it over once your code gets merged.

@cryptomilk
Copy link
Contributor

And yes, connecting to the stdin listener means the browser must start the KeePassXC process. There's also possibility to use a proxy app which is launced by the browser (handles the Native Messaging) and UDP is used to connect it to KeePassXC on-the-fly.

It would be really nice if run on UNIX system it would use unix sockets for the connections. This allows to restrict access to only the user who started the keepassxc process. Not only per file permissions also using:

getsockopt(s, SOL_SOCKET, SO_PEERCRED, ...)

@varjolintu
Copy link
Member Author

@cryptomilk, I will definitely look in to this.

@cryptomilk
Copy link
Contributor

I'm a C programmer and C++ is like a different language for me, but I'm happy to review code in this area ;)

@sedrubal
Copy link

If this get's merged until November 14th users that used PassIFox (uses deprecated NPAPI) before, can change to keepassxc-browser (ready for firefox 57) ;)

@jeroenev
Copy link

@sedrubal i think most firefox users have already moved from passifox to KeePassHttp-Connector
since passifox is alreaddy pretty broken on FF55 and 56, and incompatible with multiprocess

@ConorIA
Copy link

ConorIA commented Oct 12, 2017

FWIW, I didn't even find KeePassHttp-Connector, though I didn't look too far once I found keepassxc-browser on the add on portal.

@TheZ3ro
Copy link
Contributor

TheZ3ro commented Oct 18, 2017

@sedrubal @ConorIA now KeePassHTTP-Connector is suggested on the website instead of PassIFox and ChromeIPass https://keepassxc.org/project

@mbrockman1
Copy link

I would love to see this merged, native messaging is so much more secure and better to use!

@ahmedelz
Copy link

Are these changes going to make it to v2.3.0?

@ishitatsuyuki
Copy link

All inline reviews seem to be resolved. What's the status of this PR? Merge conflicts only?

@varjolintu
Copy link
Member Author

@ishitatsuyuki There's still some work left before merging. Mainly I have been removing boost/asio dependencies and replacing them with Qt only code. Also for proxy feature, UDP is removed and replaced with Unix domain sockets, using named pipes under Windows. When these are solved the merging of the PR could be started.

set(pkg_config_libs_private "${pkg_config_libs_private} -lsodium")
else()
message(FATAL_ERROR "libsodium is not installed. Install it, then run CMake again")
endif()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove everything from if(SODIUM_FOUND) to endif(), it is simply not needed at all. The find_package method handles all of this for you.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

)

add_library(keepassxcbrowser STATIC ${keepassxcbrowser_SOURCES})
target_link_libraries(keepassxcbrowser Qt5::Core Qt5::Concurrent Qt5::Widgets Qt5::Network ${sodium_LIBRARY_RELEASE})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change ${sodium_LIBRARY_RELEASE} to simply sodium. All the details are handled in the find_package method.

Copy link
Member

@phoerious phoerious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Found a couple more issues, partially caused by conflict resolution.

@@ -1,15 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
Copy link
Member

@phoerious phoerious Dec 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You messed up conflict resolution here. There should be no diff for this file.


private:
QString getTargetPath(SupportedBrowsers browser) const;
QString getBrowserName(SupportedBrowsers browser) const;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation messed up.

public:
HostInstaller();
bool checkIfInstalled(SupportedBrowsers browser);
void installBrowser(SupportedBrowsers browser, const bool enabled, const bool proxy = false, const QString location = QString());
Copy link
Member

@phoerious phoerious Dec 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must have missed that before: Use const& instead of const value for non-integral parameters. Same for other methods in this class. Also don't default-initialize with QString(). Do that with "".

quint32 length = 0;
std::cin.read(reinterpret_cast<char*>(&length), 4);
if (!std::cin.eof() && length > 0)
{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

{ on same line as if.


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}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This might break non-Homebrew Qt installations (e.g. MacPorts).

Copy link
Member Author

@varjolintu varjolintu Jan 1, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. It's possible. AFAIK there's no way to get the exact framework location with cmake. So this is the current workaround. Maybe doing symbolic links to that location might work with MacPorts. I will do some testing. Of course this should work without any additional operations.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should fix that in a separate PR.
The usual way to handle this is to use file() with several hints for possible locations.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree.

@yan12125
Copy link
Contributor

yan12125 commented Jan 3, 2018

Hello, I'd like to test this patch against MacPorts's Qt, but I got an error before the "changing linking" command. Here are the last few lines of my log:

Scanning dependencies of target KeePassXC
[ 50%] Building CXX object src/CMakeFiles/KeePassXC.dir/main.cpp.o
[ 50%] Building CXX object src/CMakeFiles/KeePassXC.dir/KeePassXC_autogen/mocs_compilation.cpp.o
[ 50%] Linking CXX executable KeePassXC.app/Contents/MacOS/KeePassXC
Deploying app bundle
Copying keepassxc-proxy inside the application
Error copying file "/Users/yen/Projects/keepassxc/build/src/proxy/keepassxc-proxy" to "KeePassXC.app/Contents/MacOS/keepassxc-proxy".
make[2]: *** [src/CMakeFiles/KeePassXC.dir/build.make:139: src/KeePassXC.app/Contents/MacOS/KeePassXC] Error 1
make[2]: *** Deleting file 'src/KeePassXC.app/Contents/MacOS/KeePassXC'
make[1]: *** [CMakeFiles/Makefile2:155: src/CMakeFiles/KeePassXC.dir/all] Error 2
make: *** [Makefile:163: all] Error 2

My build steps are:

git clone https://github.com/keepassxreboot/keepassxc && cd keepassxc
wget "https://github.com/keepassxreboot/keepassxc/pull/608.patch"
patch -Np1 -i 608.patch
mkdir build && cd build
cmake .. -DWITH_XC_BROWSER=ON
make

If I looked into /Users/yen/Projects/keepassxc/build/src/proxy and there's no keepassxc-proxy. If I run make keepassxc-proxy first, the whole build process, including "changing linking" commands, runs fine. I guess add_custom_command commands need more explicit dependency declarations?

Environment: MacPorts, cmake 3.10.1, Qt 5.10.0

@varjolintu
Copy link
Member Author

@yan12125 Just running make builds the keepassxc-proxy also. It's not possible separately (at this point). If the copying won't succeed the build must have failed.

Doing the symbolic links to the correct locations with QtCore and QtNetwork frameworks should work. But let's not fill this PR thread with issues. Please do an issue to my fork with a full log. Thanks.

@phoerious
Copy link
Member

@varjolintu AUTOUIC problems have been fixed in develop, please rebase.

Copy link
Member

@phoerious phoerious left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎉

@phoerious phoerious force-pushed the feature/chromeKeePassXC branch 5 times, most recently from 8945246 to d427eb1 Compare January 4, 2018 21:49
@phoerious phoerious force-pushed the feature/chromeKeePassXC branch from d427eb1 to d80c6b4 Compare January 4, 2018 21:56
@phoerious phoerious merged commit a5a5c67 into keepassxreboot:develop Jan 4, 2018
@phoerious
Copy link
Member

So, no more angry unicorns. Follow-up fixes in fresh PRs, please. 😉

@jeroenev
Copy link

jeroenev commented Jan 5, 2018

🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉 🎉

@phoerious
Copy link
Member

Don't be too excited yet. We still need to figure out the store listing before this is working. 😉

@TheZ3ro
Copy link
Contributor

TheZ3ro commented Jan 5, 2018

Please stop commenting here. I see unicorns every time I load this page 🦄 🦄 🦄 🦄

@varjolintu varjolintu deleted the feature/chromeKeePassXC branch January 11, 2018 06:49
@apiraino
Copy link

Hello,

I wanted to give a sneak peek at this new feature, so I cloned the repo and built the develop branch.
However the keepassxc is not connecting to its FF browser plugin.

Also tried to compile with -DCMAKE_BUILD_TYPE=Debug but I could't get anything useful out.

My test env:

  • Ubuntu 17.10
  • Firefox 59b2
  • I'm on 9c641ddf6b5b888994df2ef2ced2541fd86042bb
  • no firejail

Any hint about that? Thanks!

@TheZ3ro really sorry to comment on this PR - I see the github timeouts, but I didn't really know where else to ask :-)

@TheZ3ro
Copy link
Contributor

TheZ3ro commented Jan 22, 2018

@apiraino check out this issue: keepassxreboot/keepassxc-browser#3
If it doesn't fit you, please open a new issue as this feature is now effectively part of keepassxc

I'm locking conversation on this. If someone read this message, please open a new issue.
Thanks and sorry for the inconvenience

@keepassxreboot keepassxreboot locked as too heated and limited conversation to collaborators Jan 22, 2018
@phoerious phoerious added pr: new feature Pull request that adds a new feature and removed new feature labels Nov 22, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
feature: Browser pr: new feature Pull request that adds a new feature
Projects
None yet
Development

Successfully merging this pull request may close these issues.

KeePassHTTP: Connecting when database is closed sends a new pair request