Merge remote-tracking branch 'upstream/develop' into develop

This commit is contained in:
Vladimir Svyatski
2018-05-10 22:34:38 +03:00
53 changed files with 9817 additions and 1144 deletions

View File

@@ -200,10 +200,6 @@ add_feature_info(KeePassXC-Browser WITH_XC_BROWSER "Browser integration with Kee
add_feature_info(SSHAgent WITH_XC_SSHAGENT "SSH agent integration compatible with KeeAgent")
add_feature_info(YubiKey WITH_XC_YUBIKEY "YubiKey HMAC-SHA1 challenge-response")
if(WITH_XC_NETWORKING)
find_package(CURL REQUIRED)
endif()
set(BROWSER_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/browser)
add_subdirectory(browser)
add_subdirectory(proxy)
@@ -276,7 +272,7 @@ if (UNIX AND NOT APPLE)
target_link_libraries(keepassx_core Qt5::DBus)
endif()
if(MINGW)
target_link_libraries(keepassx_core Wtsapi32.lib)
target_link_libraries(keepassx_core Wtsapi32.lib Ws2_32.lib)
endif()
if(MINGW)

View File

@@ -220,7 +220,7 @@ void AutoType::executeAutoTypeActions(const Entry* entry, QWidget* hideWindow, c
#endif
}
Tools::wait(m_plugin->initialTimeout());
Tools::wait(qMax(100, config()->get("AutoTypeStartDelay", 500).toInt()));
if (!window) {
window = m_plugin->activeWindow();
@@ -364,7 +364,7 @@ bool AutoType::parseActions(const QString& actionSequence, const Entry* entry, Q
{
QString tmpl;
bool inTmpl = false;
m_autoTypeDelay = config()->get("AutoTypeDelay").toInt();
m_autoTypeDelay = qMax(config()->get("AutoTypeDelay").toInt(), 0);
QString sequence = actionSequence;
sequence.replace("{{}", "{LEFTBRACE}");

View File

@@ -35,7 +35,6 @@ public:
virtual bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) = 0;
virtual void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) = 0;
virtual int platformEventFilter(void* event) = 0;
virtual int initialTimeout() = 0;
virtual bool raiseWindow(WId window) = 0;
virtual void unload()
{

View File

@@ -154,11 +154,6 @@ AutoTypeExecutor* AutoTypePlatformMac::createExecutor()
return new AutoTypeExecutorMac(this);
}
int AutoTypePlatformMac::initialTimeout()
{
return 500;
}
//
// Activate window by process id
//

View File

@@ -42,7 +42,6 @@ public:
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
int platformEventFilter(void* event) override;
int initialTimeout() override;
bool raiseWindow(WId pid) override;
AutoTypeExecutor* createExecutor() override;

View File

@@ -103,11 +103,6 @@ void AutoTypePlatformTest::addActionKey(AutoTypeKey* action)
m_actionChars.append(keyToString(action->key));
}
int AutoTypePlatformTest::initialTimeout()
{
return 0;
}
bool AutoTypePlatformTest::raiseWindow(WId window)
{
Q_UNUSED(window);

View File

@@ -40,7 +40,6 @@ public:
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
int platformEventFilter(void* event) override;
int initialTimeout() override;
bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override;

View File

@@ -109,11 +109,6 @@ AutoTypeExecutor* AutoTypePlatformWin::createExecutor()
return new AutoTypeExecutorWin(this);
}
int AutoTypePlatformWin::initialTimeout()
{
return 500;
}
//
// Set foreground window
//

View File

@@ -39,7 +39,6 @@ public:
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
int platformEventFilter(void* event) override;
int initialTimeout() override;
bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override;

View File

@@ -830,11 +830,6 @@ void AutoTypeExecutorX11::execClearField(AutoTypeClearField* action = nullptr)
nanosleep(&ts, nullptr);
}
int AutoTypePlatformX11::initialTimeout()
{
return 500;
}
bool AutoTypePlatformX11::raiseWindow(WId window)
{
if (m_atomNetActiveWindow == None) {

View File

@@ -51,7 +51,6 @@ public:
bool registerGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
void unregisterGlobalShortcut(Qt::Key key, Qt::KeyboardModifiers modifiers) override;
int platformEventFilter(void* event) override;
int initialTimeout() override;
bool raiseWindow(WId window) override;
AutoTypeExecutor* createExecutor() override;

View File

@@ -18,6 +18,7 @@
#include "BrowserAction.h"
#include "BrowserSettings.h"
#include "NativeMessagingBase.h"
#include "config-keepassx.h"
#include "sodium.h"
#include "sodium/crypto_box.h"
@@ -237,9 +238,17 @@ QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QStrin
return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
}
const QJsonArray keys = decrypted.value("keys").toArray();
StringPairList keyList;
for (const QJsonValue val : keys) {
const QJsonObject keyObject = val.toObject();
keyList.push_back(qMakePair(keyObject.value("id").toString(), keyObject.value("key").toString()));
}
const QString id = decrypted.value("id").toString();
const QString submit = decrypted.value("submitUrl").toString();
const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "");
const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "", keyList);
if (users.isEmpty()) {
return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND);
@@ -472,7 +481,7 @@ QString BrowserAction::encrypt(const QString plaintext, const QString nonce)
std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
std::vector<unsigned char> e;
e.resize(max_length);
e.resize(NATIVE_MSG_MAX_LENGTH);
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
return QString();
@@ -500,7 +509,7 @@ QByteArray BrowserAction::decrypt(const QString encrypted, const QString nonce)
std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
std::vector<unsigned char> d;
d.resize(max_length);
d.resize(NATIVE_MSG_MAX_LENGTH);
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
return QByteArray();

View File

@@ -68,7 +68,6 @@ void BrowserOptionDialog::loadSettings()
// hide unimplemented options
// TODO: fix this
m_ui->showNotification->hide();
m_ui->bestMatchOnly->hide();
if (settings.sortByUsername()) {
m_ui->sortByUsername->setChecked(true);

View File

@@ -47,6 +47,7 @@ static int KEEPASSXCBROWSER_DEFAULT_ICON = 1;
BrowserService::BrowserService(DatabaseTabWidget* parent)
: m_dbTabWidget(parent)
, m_dialogActive(false)
, m_bringToFrontRequested(false)
{
connect(m_dbTabWidget, SIGNAL(databaseLocked(DatabaseWidget*)), this, SLOT(databaseLocked(DatabaseWidget*)));
connect(m_dbTabWidget, SIGNAL(databaseUnlocked(DatabaseWidget*)), this, SLOT(databaseUnlocked(DatabaseWidget*)));
@@ -83,6 +84,7 @@ bool BrowserService::openDatabase(bool triggerUnlock)
if (triggerUnlock) {
KEEPASSXC_MAIN_WINDOW->bringToFront();
m_bringToFrontRequested = true;
}
return false;
@@ -228,7 +230,8 @@ QString BrowserService::getKey(const QString& id)
QJsonArray BrowserService::findMatchingEntries(const QString& id,
const QString& url,
const QString& submitUrl,
const QString& realm)
const QString& realm,
const StringPairList& keyList)
{
QJsonArray result;
if (thread() != QThread::currentThread()) {
@@ -239,7 +242,8 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
Q_ARG(const QString&, id),
Q_ARG(const QString&, url),
Q_ARG(const QString&, submitUrl),
Q_ARG(const QString&, realm));
Q_ARG(const QString&, realm),
Q_ARG(const StringPairList&, keyList));
return result;
}
@@ -250,7 +254,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& id,
// Check entries for authorization
QList<Entry*> pwEntriesToConfirm;
QList<Entry*> pwEntries;
for (Entry* entry : searchEntries(url)) {
for (Entry* entry : searchEntries(url, keyList)) {
switch (checkAccess(entry, host, submitHost, realm)) {
case Denied:
continue;
@@ -356,15 +360,19 @@ void BrowserService::updateEntry(const QString& id,
return;
}
if (username.compare(login, Qt::CaseSensitive) != 0
|| entry->password().compare(password, Qt::CaseSensitive) != 0) {
QMessageBox::StandardButton dialogResult = QMessageBox::No;
if (username.compare(login, Qt::CaseSensitive) != 0 || entry->password().compare(password, Qt::CaseSensitive) != 0) {
int 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);
QMessageBox msgBox;
msgBox.setWindowTitle(tr("KeePassXC: Update Entry"));
msgBox.setText(tr("Do you want to update the information in %1 - %2?").arg(QUrl(url).host()).arg(username));
msgBox.setStandardButtons(QMessageBox::Yes);
msgBox.addButton(QMessageBox::No);
msgBox.setDefaultButton(QMessageBox::No);
msgBox.setWindowFlags(Qt::WindowStaysOnTopHint);
msgBox.activateWindow();
msgBox.raise();
dialogResult = msgBox.exec();
}
if (BrowserSettings::alwaysAllowUpdate() || dialogResult == QMessageBox::Yes) {
@@ -399,7 +407,7 @@ QList<Entry*> BrowserService::searchEntries(Database* db, const QString& hostnam
return entries;
}
QList<Entry*> BrowserService::searchEntries(const QString& text)
QList<Entry*> BrowserService::searchEntries(const QString& text, const StringPairList& keyList)
{
// Get the list of databases to search
QList<Database*> databases;
@@ -408,7 +416,16 @@ QList<Entry*> BrowserService::searchEntries(const QString& text)
for (int i = 0; i < count; ++i) {
if (DatabaseWidget* dbWidget = qobject_cast<DatabaseWidget*>(m_dbTabWidget->widget(i))) {
if (Database* db = dbWidget->database()) {
databases << db;
// Check if database is connected with KeePassXC-Browser
for (const StringPair keyPair : keyList) {
Entry* entry = db->resolveEntry(KEEPASSXCBROWSER_UUID);
if (entry) {
QString key = entry->attributes()->value(QLatin1String(ASSOCIATE_KEY_PREFIX) + keyPair.first);
if (!key.isEmpty() && keyPair.second == key) {
databases << db;
}
}
}
}
}
}
@@ -537,21 +554,30 @@ QList<Entry*> BrowserService::sortEntries(QList<Entry*>& pwEntries, const QStrin
const QString baseSubmitUrl =
url.toString(QUrl::StripTrailingSlash | QUrl::RemovePath | QUrl::RemoveQuery | QUrl::RemoveFragment);
QMultiMap<int, const Entry*> priorities;
for (const Entry* entry : pwEntries) {
// Build map of prioritized entries
QMultiMap<int, Entry*> priorities;
for (Entry* entry : pwEntries) {
priorities.insert(sortPriority(entry, host, submitUrl, baseSubmitUrl), entry);
}
QList<Entry*> results;
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;
for (int i = 100; i >= 0; i -= 5) {
if (priorities.count(i) > 0) {
// Sort same priority entries by Title or UserName
auto entries = priorities.values(i);
std::sort(entries.begin(), entries.end(), [&priorities, &field](Entry* left, Entry* right) {
return QString::localeAwareCompare(left->attributes()->value(field), right->attributes()->value(field)) < 0;
});
results << entries;
if (BrowserSettings::bestMatchOnly() && !pwEntries.isEmpty()) {
// Early out once we find the highest batch of matches
break;
}
}
return res < 0;
});
}
return pwEntries;
return results;
}
bool BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
@@ -607,6 +633,10 @@ QJsonObject BrowserService::prepareEntry(const Entry* entry)
res["name"] = entry->resolveMultiplePlaceholders(entry->title());
res["uuid"] = entry->resolveMultiplePlaceholders(entry->uuid().toHex());
if (entry->hasTotp()) {
res["totp"] = entry->totp();
}
if (BrowserSettings::supportKphFields()) {
const EntryAttributes* attr = entry->attributes();
QJsonArray stringFields;
@@ -762,6 +792,10 @@ void BrowserService::databaseLocked(DatabaseWidget* dbWidget)
void BrowserService::databaseUnlocked(DatabaseWidget* dbWidget)
{
if (dbWidget) {
if (m_bringToFrontRequested) {
KEEPASSXC_MAIN_WINDOW->lower();
m_bringToFrontRequested = false;
}
emit databaseUnlocked();
}
}

View File

@@ -25,6 +25,9 @@
#include <QObject>
#include <QtCore>
typedef QPair<QString, QString> StringPair;
typedef QList<StringPair> StringPairList;
enum
{
max_length = 16 * 1024
@@ -50,13 +53,16 @@ public:
const QString& submitUrl,
const QString& realm);
QList<Entry*> searchEntries(Database* db, const QString& hostname);
QList<Entry*> searchEntries(const QString& text);
QList<Entry*> searchEntries(const QString& text, const StringPairList& keyList);
void removeSharedEncryptionKeys();
void removeStoredPermissions();
public slots:
QJsonArray
findMatchingEntries(const QString& id, const QString& url, const QString& submitUrl, const QString& realm);
QJsonArray findMatchingEntries(const QString& id,
const QString& url,
const QString& submitUrl,
const QString& realm,
const StringPairList& keyList);
QString storeKey(const QString& key);
void updateEntry(const QString& id,
const QString& uuid,
@@ -100,6 +106,7 @@ private:
private:
DatabaseTabWidget* const m_dbTabWidget;
bool m_dialogActive;
bool m_bringToFrontRequested;
};
#endif // BROWSERSERVICE_H

View File

@@ -32,7 +32,7 @@ public:
static bool showNotification(); // TODO!!
static void setShowNotification(bool showNotification);
static bool bestMatchOnly(); // TODO!!
static bool bestMatchOnly();
static void setBestMatchOnly(bool bestMatchOnly);
static bool unlockDatabase();
static void setUnlockDatabase(bool unlockDatabase);
@@ -46,7 +46,7 @@ public:
static void setAlwaysAllowAccess(bool alwaysAllowAccess);
static bool alwaysAllowUpdate();
static void setAlwaysAllowUpdate(bool alwaysAllowUpdate);
static bool searchInAllDatabases(); // TODO!!
static bool searchInAllDatabases();
static void setSearchInAllDatabases(bool searchInAllDatabases);
static bool supportKphFields();
static void setSupportKphFields(bool supportKphFields);

View File

@@ -36,14 +36,17 @@
#include <io.h>
#endif
NativeMessagingBase::NativeMessagingBase()
NativeMessagingBase::NativeMessagingBase(const bool enabled)
{
#ifdef Q_OS_WIN
Q_UNUSED(enabled);
_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()));
if (enabled) {
m_notifier.reset(new QSocketNotifier(fileno(stdin), QSocketNotifier::Read, this));
connect(m_notifier.data(), SIGNAL(activated(int)), this, SLOT(newNativeMessage()));
}
#endif
}

View File

@@ -32,12 +32,19 @@
#include <iostream>
#include <unistd.h>
#ifndef Q_OS_WIN
#include <sys/types.h>
#include <sys/socket.h>
#endif
static const int NATIVE_MSG_MAX_LENGTH = 1024*1024;
class NativeMessagingBase : public QObject
{
Q_OBJECT
public:
explicit NativeMessagingBase();
explicit NativeMessagingBase(const bool enabled);
~NativeMessagingBase() = default;
protected slots:

View File

@@ -23,8 +23,12 @@
#include <QtNetwork>
#include <iostream>
NativeMessagingHost::NativeMessagingHost(DatabaseTabWidget* parent)
: NativeMessagingBase()
#ifdef Q_OS_WIN
#include <Winsock2.h>
#endif
NativeMessagingHost::NativeMessagingHost(DatabaseTabWidget* parent, const bool enabled)
: NativeMessagingBase(enabled)
, m_mutex(QMutex::Recursive)
, m_browserClients(m_browserService)
, m_browserService(parent)
@@ -75,6 +79,11 @@ void NativeMessagingHost::run()
QString serverPath = getLocalServerPath();
QFile::remove(serverPath);
// Ensure that STDIN is not being listened when proxy is used
if (m_notifier && m_notifier->isEnabled()) {
m_notifier->setEnabled(false);
}
if (m_localServer->isListening()) {
m_localServer->close();
}
@@ -144,11 +153,17 @@ void NativeMessagingHost::newLocalConnection()
void NativeMessagingHost::newLocalMessage()
{
QLocalSocket* socket = qobject_cast<QLocalSocket*>(QObject::sender());
if (!socket || socket->bytesAvailable() <= 0) {
return;
}
socket->setReadBufferSize(NATIVE_MSG_MAX_LENGTH);
int socketDesc = socket->socketDescriptor();
if (socketDesc) {
int max = NATIVE_MSG_MAX_LENGTH;
setsockopt(socketDesc, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&max), sizeof(max));
}
QByteArray arr = socket->readAll();
if (arr.isEmpty()) {
return;

View File

@@ -31,7 +31,7 @@ class NativeMessagingHost : public NativeMessagingBase
typedef QList<QLocalSocket*> SocketList;
public:
explicit NativeMessagingHost(DatabaseTabWidget* parent = 0);
explicit NativeMessagingHost(DatabaseTabWidget* parent = 0, const bool enabled = false);
~NativeMessagingHost();
int init();
void run();

View File

@@ -135,6 +135,7 @@ void Config::init(const QString& fileName)
m_defaults.insert("AutoTypeEntryTitleMatch", true);
m_defaults.insert("AutoTypeEntryURLMatch", true);
m_defaults.insert("AutoTypeDelay", 25);
m_defaults.insert("AutoTypeStartDelay", 500);
m_defaults.insert("UseGroupIconOnEntryCreation", true);
m_defaults.insert("IgnoreGroupExpansion", true);
m_defaults.insert("security/clearclipboard", true);

View File

@@ -197,7 +197,7 @@ void DetailsWidget::updateEntryNotesTab()
Q_ASSERT(m_currentEntry);
const QString notes = m_currentEntry->notes();
setTabEnabled(m_ui->entryTabWidget, m_ui->entryNotesTab, !notes.isEmpty());
m_ui->entryNotesEdit->setText(m_currentEntry->resolveMultiplePlaceholders(notes));
m_ui->entryNotesEdit->setText(notes);
}
void DetailsWidget::updateEntryAttributesTab()

View File

@@ -31,9 +31,7 @@
#include "gui/MessageBox.h"
#ifdef WITH_XC_NETWORKING
#include "core/AsyncTask.h"
#include <curl/curl.h>
#undef MessageBox
#include <QtNetwork>
#endif
IconStruct::IconStruct()
@@ -42,10 +40,31 @@ IconStruct::IconStruct()
{
}
UrlFetchProgressDialog::UrlFetchProgressDialog(const QUrl &url, QWidget *parent)
: QProgressDialog(parent)
{
setWindowTitle(tr("Download Progress"));
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
setLabelText(tr("Downloading %1.").arg(url.toDisplayString()));
setMinimum(0);
setValue(0);
setMinimumDuration(0);
setMinimumSize(QSize(400, 75));
}
void UrlFetchProgressDialog::networkReplyProgress(qint64 bytesRead, qint64 totalBytes)
{
setMaximum(totalBytes);
setValue(bytesRead);
}
EditWidgetIcons::EditWidgetIcons(QWidget* parent)
: QWidget(parent)
, m_ui(new Ui::EditWidgetIcons())
, m_database(nullptr)
#ifdef WITH_XC_NETWORKING
, m_reply(nullptr)
#endif
, m_defaultIconModel(new DefaultIconModel(this))
, m_customIconModel(new CustomIconModel(this))
{
@@ -146,7 +165,7 @@ void EditWidgetIcons::load(const Uuid& currentUuid,
void EditWidgetIcons::setUrl(const QString& url)
{
#ifdef WITH_XC_NETWORKING
m_url = url;
m_url = QUrl(url);
m_ui->faviconButton->setVisible(!url.isEmpty());
#else
Q_UNUSED(url);
@@ -154,87 +173,152 @@ void EditWidgetIcons::setUrl(const QString& url)
#endif
}
#ifdef WITH_XC_NETWORKING
namespace {
// Try to get the 2nd level domain of the host part of a QUrl. For example,
// "foo.bar.example.com" would become "example.com", and "foo.bar.example.co.uk"
// would become "example.co.uk".
QString getSecondLevelDomain(QUrl url)
{
QString fqdn = url.host();
fqdn.truncate(fqdn.length() - url.topLevelDomain().length());
QStringList parts = fqdn.split('.');
QString newdom = parts.takeLast() + url.topLevelDomain();
return newdom;
}
QUrl convertVariantToUrl(QVariant var)
{
QUrl url;
if (var.canConvert<QUrl>())
url = var.value<QUrl>();
return url;
}
QUrl getRedirectTarget(QNetworkReply *reply)
{
QVariant var = reply->attribute(QNetworkRequest::RedirectionTargetAttribute);
QUrl url = convertVariantToUrl(var);
return url;
}
}
#endif
void EditWidgetIcons::downloadFavicon()
{
#ifdef WITH_XC_NETWORKING
m_ui->faviconButton->setDisabled(true);
QUrl url = QUrl(m_url);
url.setPath("/favicon.ico");
m_redirects = 0;
m_urlsToTry.clear();
QString fullyQualifiedDomain = m_url.host();
QString secondLevelDomain = getSecondLevelDomain(m_url);
// Attempt to simply load the favicon.ico file
QImage image = fetchFavicon(url);
if (fullyQualifiedDomain != secondLevelDomain) {
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + fullyQualifiedDomain + "/favicon.ico"));
}
m_urlsToTry.append(QUrl(m_url.scheme() + "://" + secondLevelDomain + "/favicon.ico"));
// Try to use Google fallback, if enabled
if (config()->get("security/IconDownloadFallbackToGoogle", false).toBool()) {
QUrl urlGoogle = QUrl("https://www.google.com/s2/favicons");
urlGoogle.setQuery("domain=" + QUrl::toPercentEncoding(secondLevelDomain));
m_urlsToTry.append(urlGoogle);
}
startFetchFavicon(m_urlsToTry.takeFirst());
#endif
}
void EditWidgetIcons::fetchReadyRead()
{
#ifdef WITH_XC_NETWORKING
m_bytesReceived += m_reply->readAll();
#endif
}
void EditWidgetIcons::fetchFinished()
{
#ifdef WITH_XC_NETWORKING
QImage image;
bool googleFallbackEnabled = config()->get("security/IconDownloadFallbackToGoogle", false).toBool();
bool error = (m_reply->error() != QNetworkReply::NoError);
QUrl redirectTarget = getRedirectTarget(m_reply);
m_reply->deleteLater();
m_reply = nullptr;
if (!error) {
if (redirectTarget.isValid()) {
// Redirected, we need to follow it, or fall through if we have
// done too many redirects already.
if (m_redirects < 5) {
m_redirects++;
if (redirectTarget.isRelative())
redirectTarget = m_fetchUrl.resolved(redirectTarget);
startFetchFavicon(redirectTarget);
return;
}
} else {
// No redirect, and we theoretically have some icon data now.
image.loadFromData(m_bytesReceived);
}
}
if (!image.isNull()) {
addCustomIcon(image);
} else if (config()->get("security/IconDownloadFallbackToGoogle", false).toBool()) {
QUrl faviconUrl = QUrl("https://www.google.com/s2/favicons");
faviconUrl.setQuery("domain=" + QUrl::toPercentEncoding(url.host()));
// Attempt to load favicon from Google
image = fetchFavicon(faviconUrl);
if (!image.isNull()) {
addCustomIcon(image);
} else if (!m_urlsToTry.empty()) {
m_redirects = 0;
startFetchFavicon(m_urlsToTry.takeFirst());
return;
} else {
if (!googleFallbackEnabled) {
emit messageEditEntry(tr("Unable to fetch favicon.") + "\n" +
tr("Hint: You can enable Google as a fallback under Tools>Settings>Security"),
MessageWidget::Error);
} else {
emit messageEditEntry(tr("Unable to fetch favicon."), MessageWidget::Error);
}
} else {
emit messageEditEntry(tr("Unable to fetch favicon.") + "\n"
+ tr("Hint: You can enable Google as a fallback under Tools>Settings>Security"),
MessageWidget::Error);
}
m_ui->faviconButton->setDisabled(false);
#endif
}
void EditWidgetIcons::fetchCanceled()
{
#ifdef WITH_XC_NETWORKING
namespace
{
std::size_t writeCurlResponse(char* ptr, std::size_t size, std::size_t nmemb, void* data)
{
QByteArray* response = static_cast<QByteArray*>(data);
std::size_t realsize = size * nmemb;
response->append(ptr, realsize);
return realsize;
}
m_reply->abort();
#endif
}
QImage EditWidgetIcons::fetchFavicon(const QUrl& url)
void EditWidgetIcons::startFetchFavicon(const QUrl& url)
{
QImage image;
CURL* curl = curl_easy_init();
if (curl) {
QByteArray imagedata;
QByteArray baUrl = url.url().toLatin1();
#ifdef WITH_XC_NETWORKING
m_bytesReceived.clear();
curl_easy_setopt(curl, CURLOPT_URL, baUrl.data());
curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 5L);
curl_easy_setopt(curl, CURLOPT_REDIR_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl");
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &imagedata);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, &writeCurlResponse);
#ifdef Q_OS_WIN
const QDir appDir = QFileInfo(QCoreApplication::applicationFilePath()).absoluteDir();
if (appDir.exists("ssl\\certs")) {
curl_easy_setopt(
curl, CURLOPT_CAINFO, (appDir.absolutePath() + "\\ssl\\certs\\ca-bundle.crt").toLatin1().data());
}
m_fetchUrl = url;
QNetworkRequest request(url);
m_reply = m_netMgr.get(request);
connect(m_reply, &QNetworkReply::finished, this, &EditWidgetIcons::fetchFinished);
connect(m_reply, &QIODevice::readyRead, this, &EditWidgetIcons::fetchReadyRead);
UrlFetchProgressDialog *progress = new UrlFetchProgressDialog(url, this);
progress->setAttribute(Qt::WA_DeleteOnClose);
connect(m_reply, &QNetworkReply::finished, progress, &QProgressDialog::hide);
connect(m_reply, &QNetworkReply::downloadProgress, progress, &UrlFetchProgressDialog::networkReplyProgress);
connect(progress, &QProgressDialog::canceled, this, &EditWidgetIcons::fetchCanceled);
progress->show();
#else
Q_UNUSED(url);
#endif
// Perform the request in another thread
CURLcode result = AsyncTask::runAndWaitForFuture([curl]() { return curl_easy_perform(curl); });
if (result == CURLE_OK) {
image.loadFromData(imagedata);
}
curl_easy_cleanup(curl);
}
return image;
}
#endif
void EditWidgetIcons::addCustomIconFromFile()
{

View File

@@ -20,8 +20,10 @@
#define KEEPASSX_EDITWIDGETICONS_H
#include <QSet>
#include <QProgressDialog>
#include <QUrl>
#include <QWidget>
#include <QNetworkAccessManager>
#include "config-keepassx.h"
#include "core/Global.h"
@@ -31,6 +33,9 @@
class Database;
class DefaultIconModel;
class CustomIconModel;
#ifdef WITH_XC_NETWORKING
class QNetworkReply;
#endif
namespace Ui
{
@@ -45,6 +50,17 @@ struct IconStruct
int number;
};
class UrlFetchProgressDialog : public QProgressDialog
{
Q_OBJECT
public:
explicit UrlFetchProgressDialog(const QUrl &url, QWidget *parent = nullptr);
public slots:
void networkReplyProgress(qint64 bytesRead, qint64 totalBytes);
};
class EditWidgetIcons : public QWidget
{
Q_OBJECT
@@ -67,9 +83,10 @@ signals:
private slots:
void downloadFavicon();
#ifdef WITH_XC_NETWORKING
QImage fetchFavicon(const QUrl& url);
#endif
void startFetchFavicon(const QUrl& url);
void fetchFinished();
void fetchReadyRead();
void fetchCanceled();
void addCustomIconFromFile();
void addCustomIcon(const QImage& icon);
void removeCustomIcon();
@@ -82,7 +99,15 @@ private:
const QScopedPointer<Ui::EditWidgetIcons> m_ui;
Database* m_database;
Uuid m_currentUuid;
QString m_url;
#ifdef WITH_XC_NETWORKING
QUrl m_url;
QUrl m_fetchUrl;
QList<QUrl> m_urlsToTry;
QByteArray m_bytesReceived;
QNetworkAccessManager m_netMgr;
QNetworkReply *m_reply;
int m_redirects;
#endif
DefaultIconModel* const m_defaultIconModel;
CustomIconModel* const m_customIconModel;

View File

@@ -65,7 +65,7 @@ class BrowserPlugin : public ISettingsPage
public:
BrowserPlugin(DatabaseTabWidget* tabWidget)
{
m_nativeMessagingHost = QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget));
m_nativeMessagingHost = QSharedPointer<NativeMessagingHost>(new NativeMessagingHost(tabWidget, BrowserSettings::isEnabled()));
}
~BrowserPlugin()
@@ -686,7 +686,7 @@ void MainWindow::closeEvent(QCloseEvent* event)
bool minimizeOnClose = isTrayIconEnabled() && config()->get("GUI/MinimizeOnClose").toBool();
if (minimizeOnClose && !m_appExitCalled) {
event->ignore();
event->accept();
hideWindow();
if (config()->get("security/lockdatabaseminimize").toBool()) {

View File

@@ -157,6 +157,7 @@ void SettingsWidget::loadSettings()
}
m_generalUi->autoTypeShortcutWidget->setAttribute(Qt::WA_MacShowFocusRect, true);
m_generalUi->autoTypeDelaySpinBox->setValue(config()->get("AutoTypeDelay").toInt());
m_generalUi->autoTypeStartDelaySpinBox->setValue(config()->get("AutoTypeStartDelay").toInt());
}
m_secUi->clearClipboardCheckBox->setChecked(config()->get("security/clearclipboard").toBool());
@@ -223,6 +224,7 @@ void SettingsWidget::saveSettings()
config()->set("GlobalAutoTypeKey", m_generalUi->autoTypeShortcutWidget->key());
config()->set("GlobalAutoTypeModifiers", static_cast<int>(m_generalUi->autoTypeShortcutWidget->modifiers()));
config()->set("AutoTypeDelay", m_generalUi->autoTypeDelaySpinBox->value());
config()->set("AutoTypeStartDelay", m_generalUi->autoTypeStartDelaySpinBox->value());
}
config()->set("security/clearclipboard", m_secUi->clearClipboardCheckBox->isChecked());
config()->set("security/clearclipboardtimeout", m_secUi->clearClipboardSpinBox->value());

View File

@@ -26,7 +26,7 @@
<item>
<widget class="QTabWidget" name="generalSettingsTabWidget">
<property name="currentIndex">
<number>0</number>
<number>1</number>
</property>
<widget class="QWidget" name="tabGeneral">
<attribute name="title">
@@ -411,7 +411,7 @@
<number>10</number>
</property>
<item row="1" column="0">
<widget class="QLabel" name="autoTypeShortcutLabel_2">
<widget class="QLabel" name="autoTypeShortcutLabel">
<property name="text">
<string>Global Auto-Type shortcut</string>
</property>
@@ -427,14 +427,14 @@
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="autoTypeDelayLabel_2">
<item row="3" column="0">
<widget class="QLabel" name="autoTypeDelayLabel">
<property name="text">
<string>Auto-Type delay</string>
<string>Auto-Type typing delay</string>
</property>
</widget>
</item>
<item row="2" column="1">
<item row="3" column="1">
<widget class="QSpinBox" name="autoTypeDelaySpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
@@ -449,13 +449,48 @@
<string/>
</property>
<property name="maximum">
<number>999</number>
<number>1000</number>
</property>
<property name="value">
<number>25</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="autoTypeStartDelayLabel">
<property name="text">
<string>Auto-Type start delay</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QSpinBox" name="autoTypeStartDelaySpinBox">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="suffix">
<string comment="Milliseconds"> ms</string>
</property>
<property name="prefix">
<string/>
</property>
<property name="minimum">
<number>100</number>
</property>
<property name="maximum">
<number>10000</number>
</property>
<property name="singleStep">
<number>100</number>
</property>
<property name="value">
<number>500</number>
</property>
</widget>
</item>
</layout>
</item>
<item>

View File

@@ -339,7 +339,7 @@ void EditEntryWidget::setupSSHAgent()
connect(m_sshAgentUi->decryptButton, SIGNAL(clicked()), SLOT(decryptPrivateKey()));
connect(m_sshAgentUi->copyToClipboardButton, SIGNAL(clicked()), SLOT(copyPublicKey()));
connect(m_advancedUi->attachmentsWidget->entryAttachments(), SIGNAL(modified()), SLOT(updateAttachments()));
connect(m_advancedUi->attachmentsWidget->entryAttachments(), SIGNAL(modified()), SLOT(updateSSHAgentAttachments()));
addPage(tr("SSH Agent"), FilePath::instance()->icon("apps", "utilities-terminal"), m_sshAgentWidget);
}

View File

@@ -185,7 +185,7 @@ QVariant EntryModel::data(const QModelIndex& index, int role) const
return result;
case Notes:
// Display only first line of notes in simplified format
result = entry->resolveMultiplePlaceholders(entry->notes().section("\n", 0, 0).simplified());
result = entry->notes().section("\n", 0, 0).simplified();
if (attr->isReference(EntryAttributes::NotesKey)) {
result.prepend(tr("Ref: ", "Reference abbreviation"));
}

View File

@@ -45,6 +45,21 @@ Q_IMPORT_PLUGIN(QXcbIntegrationPlugin)
#endif
#endif
static inline void earlyQNetworkAccessManagerWorkaround()
{
// When QNetworkAccessManager is instantiated it regularly starts polling
// all network interfaces to see if anything changes and if so, what. This
// creates a latency spike every 10 seconds on Mac OS 10.12+ and Windows 7 >=
// when on a wifi connection.
// So here we disable it for lack of better measure.
// This will also cause this message: QObject::startTimer: Timers cannot
// have negative intervals
// For more info see:
// - https://bugreports.qt.io/browse/QTBUG-40332
// - https://bugreports.qt.io/browse/QTBUG-46015
qputenv("QT_BEARER_POLL_TIMEOUT", QByteArray::number(-1));
}
int main(int argc, char** argv)
{
#ifdef QT_NO_DEBUG
@@ -52,6 +67,8 @@ int main(int argc, char** argv)
#endif
Tools::setupSearchPaths();
earlyQNetworkAccessManagerWorkaround();
Application app(argc, argv);
Application::setApplicationName("keepassxc");
Application::setApplicationVersion(KEEPASSX_VERSION);

View File

@@ -55,4 +55,7 @@ if(WITH_XC_BROWSER)
WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/src
COMMENT "Changing linking of keepassxc-proxy")
endif()
if(MINGW)
target_link_libraries(keepassxc-proxy Wtsapi32.lib Ws2_32.lib)
endif()
endif()

View File

@@ -18,11 +18,21 @@
#include "NativeMessagingHost.h"
#include <QCoreApplication>
NativeMessagingHost::NativeMessagingHost()
: NativeMessagingBase()
#ifdef Q_OS_WIN
#include <Winsock2.h>
#endif
NativeMessagingHost::NativeMessagingHost() : NativeMessagingBase(true)
{
m_localSocket = new QLocalSocket();
m_localSocket->connectToServer(getLocalServerPath());
m_localSocket->setReadBufferSize(NATIVE_MSG_MAX_LENGTH);
int socketDesc = m_localSocket->socketDescriptor();
if (socketDesc) {
int max = NATIVE_MSG_MAX_LENGTH;
setsockopt(socketDesc, SOL_SOCKET, SO_SNDBUF, reinterpret_cast<char*>(&max), sizeof(max));
}
#ifdef Q_OS_WIN
m_running.store(true);
m_future =

View File

@@ -317,9 +317,9 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
if (m_cipherName.compare("aes-128-cbc", Qt::CaseInsensitive) == 0) {
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes128, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
} else if (m_cipherName == "aes256-cbc") {
} else if (m_cipherName == "aes256-cbc" || m_cipherName.compare("aes-256-cbc", Qt::CaseInsensitive) == 0) {
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Cbc, SymmetricCipher::Decrypt));
} else if (m_cipherName == "aes256-ctr") {
} else if (m_cipherName == "aes256-ctr" || m_cipherName.compare("aes-256-ctr", Qt::CaseInsensitive) == 0) {
cipher.reset(new SymmetricCipher(SymmetricCipher::Aes256, SymmetricCipher::Ctr, SymmetricCipher::Decrypt));
} else if (m_cipherName != "none") {
m_error = tr("Unknown cipher: %1").arg(m_cipherName);
@@ -370,10 +370,22 @@ bool OpenSSHKey::openPrivateKey(const QString& passphrase)
return false;
}
QCryptographicHash hash(QCryptographicHash::Md5);
hash.addData(passphrase.toUtf8());
hash.addData(m_cipherIV.data(), 8);
QByteArray keyData = hash.result();
QByteArray keyData;
QByteArray mdBuf;
do {
QCryptographicHash hash(QCryptographicHash::Md5);
hash.addData(mdBuf);
hash.addData(passphrase.toUtf8());
hash.addData(m_cipherIV.data(), 8);
mdBuf = hash.result();
keyData.append(mdBuf);
} while(keyData.size() < cipher->keySize());
if (keyData.size() > cipher->keySize()) {
// If our key size isn't a multiple of 16 (e.g. AES-192 or something),
// then we will need to truncate it.
keyData.resize(cipher->keySize());
}
if (!cipher->init(keyData, m_cipherIV)) {
m_error = cipher->errorString();