mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2024-10-01 01:26:01 -04:00
Browser Integration code cleanup (#8489)
Co-authored-by: varjolintu <sami.vanttinen@protonmail.com>
This commit is contained in:
parent
ba15981700
commit
4a30417f76
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2023 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
|
||||
@ -16,21 +16,29 @@
|
||||
*/
|
||||
|
||||
#include "BrowserAction.h"
|
||||
#include "BrowserMessageBuilder.h"
|
||||
#include "BrowserService.h"
|
||||
#include "BrowserSettings.h"
|
||||
#include "BrowserShared.h"
|
||||
#include "config-keepassx.h"
|
||||
#include "core/Global.h"
|
||||
#include "core/Tools.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLocalSocket>
|
||||
|
||||
const int BrowserAction::MaxUrlLength = 256;
|
||||
|
||||
static const QString BROWSER_REQUEST_ASSOCIATE = QStringLiteral("associate");
|
||||
static const QString BROWSER_REQUEST_CHANGE_PUBLIC_KEYS = QStringLiteral("change-public-keys");
|
||||
static const QString BROWSER_REQUEST_CREATE_NEW_GROUP = QStringLiteral("create-new-group");
|
||||
static const QString BROWSER_REQUEST_DELETE_ENTRY = QStringLiteral("delete-entry");
|
||||
static const QString BROWSER_REQUEST_GENERATE_PASSWORD = QStringLiteral("generate-password");
|
||||
static const QString BROWSER_REQUEST_GET_DATABASEHASH = QStringLiteral("get-databasehash");
|
||||
static const QString BROWSER_REQUEST_GET_DATABASE_GROUPS = QStringLiteral("get-database-groups");
|
||||
static const QString BROWSER_REQUEST_GET_LOGINS = QStringLiteral("get-logins");
|
||||
static const QString BROWSER_REQUEST_GET_TOTP = QStringLiteral("get-totp");
|
||||
static const QString BROWSER_REQUEST_LOCK_DATABASE = QStringLiteral("lock-database");
|
||||
static const QString BROWSER_REQUEST_REQUEST_AUTOTYPE = QStringLiteral("request-autotype");
|
||||
static const QString BROWSER_REQUEST_SET_LOGIN = QStringLiteral("set-login");
|
||||
static const QString BROWSER_REQUEST_TEST_ASSOCIATE = QStringLiteral("test-associate");
|
||||
|
||||
QJsonObject BrowserAction::processClientMessage(QLocalSocket* socket, const QJsonObject& json)
|
||||
{
|
||||
if (json.isEmpty()) {
|
||||
@ -38,17 +46,17 @@ QJsonObject BrowserAction::processClientMessage(QLocalSocket* socket, const QJso
|
||||
}
|
||||
|
||||
bool triggerUnlock = false;
|
||||
const QString trigger = json.value("triggerUnlock").toString();
|
||||
const auto trigger = json.value("triggerUnlock").toString();
|
||||
if (!trigger.isEmpty() && trigger.compare(TRUE_STR) == 0) {
|
||||
triggerUnlock = true;
|
||||
}
|
||||
|
||||
const QString action = json.value("action").toString();
|
||||
const auto action = json.value("action").toString();
|
||||
if (action.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
if (action.compare("change-public-keys") != 0 && action.compare("request-autotype") != 0
|
||||
if (action.compare(BROWSER_REQUEST_CHANGE_PUBLIC_KEYS) != 0 && action.compare(BROWSER_REQUEST_REQUEST_AUTOTYPE) != 0
|
||||
&& !browserService()->isDatabaseOpened()) {
|
||||
if (m_clientPublicKey.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
|
||||
@ -67,31 +75,31 @@ QJsonObject BrowserAction::handleAction(QLocalSocket* socket, const QJsonObject&
|
||||
{
|
||||
QString action = json.value("action").toString();
|
||||
|
||||
if (action.compare("change-public-keys") == 0) {
|
||||
if (action.compare(BROWSER_REQUEST_CHANGE_PUBLIC_KEYS) == 0) {
|
||||
return handleChangePublicKeys(json, action);
|
||||
} else if (action.compare("get-databasehash") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_GET_DATABASEHASH) == 0) {
|
||||
return handleGetDatabaseHash(json, action);
|
||||
} else if (action.compare("associate") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_ASSOCIATE) == 0) {
|
||||
return handleAssociate(json, action);
|
||||
} else if (action.compare("test-associate") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_TEST_ASSOCIATE) == 0) {
|
||||
return handleTestAssociate(json, action);
|
||||
} else if (action.compare("get-logins") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_GET_LOGINS) == 0) {
|
||||
return handleGetLogins(json, action);
|
||||
} else if (action.compare("generate-password") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_GENERATE_PASSWORD) == 0) {
|
||||
return handleGeneratePassword(socket, json, action);
|
||||
} else if (action.compare("set-login") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_SET_LOGIN) == 0) {
|
||||
return handleSetLogin(json, action);
|
||||
} else if (action.compare("lock-database") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_LOCK_DATABASE) == 0) {
|
||||
return handleLockDatabase(json, action);
|
||||
} else if (action.compare("get-database-groups") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_GET_DATABASE_GROUPS) == 0) {
|
||||
return handleGetDatabaseGroups(json, action);
|
||||
} else if (action.compare("create-new-group") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_CREATE_NEW_GROUP) == 0) {
|
||||
return handleCreateNewGroup(json, action);
|
||||
} else if (action.compare("get-totp") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_GET_TOTP) == 0) {
|
||||
return handleGetTotp(json, action);
|
||||
} else if (action.compare("delete-entry") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_DELETE_ENTRY) == 0) {
|
||||
return handleDeleteEntry(json, action);
|
||||
} else if (action.compare("request-autotype") == 0) {
|
||||
} else if (action.compare(BROWSER_REQUEST_REQUEST_AUTOTYPE) == 0) {
|
||||
return handleGlobalAutoType(json, action);
|
||||
}
|
||||
|
||||
@ -101,8 +109,8 @@ QJsonObject BrowserAction::handleAction(QLocalSocket* socket, const QJsonObject&
|
||||
|
||||
QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString clientPublicKey = json.value("publicKey").toString();
|
||||
const auto nonce = json.value("nonce").toString();
|
||||
const auto clientPublicKey = json.value("publicKey").toString();
|
||||
|
||||
if (clientPublicKey.isEmpty() || nonce.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
|
||||
@ -118,7 +126,7 @@ QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const
|
||||
m_publicKey = keyPair.first;
|
||||
m_secretKey = keyPair.second;
|
||||
|
||||
QJsonObject response = browserMessageBuilder()->buildMessage(browserMessageBuilder()->incrementNonce(nonce));
|
||||
auto response = browserMessageBuilder()->buildMessage(browserMessageBuilder()->incrementNonce(nonce));
|
||||
response["action"] = action;
|
||||
response["publicKey"] = keyPair.first;
|
||||
|
||||
@ -127,36 +135,19 @@ QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const
|
||||
|
||||
QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
if (hash.isEmpty()) {
|
||||
if (browserRequest.hash.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (!command.isEmpty() && command.compare("get-databasehash") == 0) {
|
||||
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce);
|
||||
message["hash"] = hash;
|
||||
|
||||
// Update a legacy database hash if found
|
||||
const QJsonArray hashes = decrypted.value("connectedKeys").toArray();
|
||||
if (!hashes.isEmpty()) {
|
||||
const QString legacyHash = browserService()->getDatabaseHash(true);
|
||||
if (hashes.contains(legacyHash)) {
|
||||
message["oldHash"] = legacyHash;
|
||||
}
|
||||
}
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
const auto command = browserRequest.getString("action");
|
||||
if (!command.isEmpty() && command.compare(BROWSER_REQUEST_GET_DATABASEHASH) == 0) {
|
||||
const Parameters params{{"hash", browserRequest.hash}};
|
||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
@ -164,16 +155,12 @@ QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const
|
||||
|
||||
QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString key = decrypted.value("key").toString();
|
||||
const auto key = browserRequest.getString("key");
|
||||
if (key.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
@ -181,19 +168,16 @@ QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QStrin
|
||||
if (key.compare(m_clientPublicKey) == 0) {
|
||||
// Check for identification key. If it's not found, ensure backwards compatibility and use the current public
|
||||
// key
|
||||
const QString idKey = decrypted.value("idKey").toString();
|
||||
const QString id = browserService()->storeKey((idKey.isEmpty() ? key : idKey));
|
||||
const auto idKey = browserRequest.getString("idKey");
|
||||
const auto id = browserService()->storeKey((idKey.isEmpty() ? key : idKey));
|
||||
if (id.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
|
||||
}
|
||||
|
||||
m_associated = true;
|
||||
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce);
|
||||
message["hash"] = hash;
|
||||
message["id"] = id;
|
||||
return buildResponse(action, message, newNonce);
|
||||
const Parameters params{{"hash", browserRequest.hash}, {"id", id}};
|
||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
@ -201,97 +185,80 @@ QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QStrin
|
||||
|
||||
QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString responseKey = decrypted.value("key").toString();
|
||||
const QString id = decrypted.value("id").toString();
|
||||
const auto responseKey = browserRequest.getString("key");
|
||||
const auto id = browserRequest.getString("id");
|
||||
if (responseKey.isEmpty() || id.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
|
||||
}
|
||||
|
||||
const QString key = browserService()->getKey(id);
|
||||
const auto key = browserService()->getKey(id);
|
||||
if (key.isEmpty() || key.compare(responseKey) != 0) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
m_associated = true;
|
||||
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce);
|
||||
message["hash"] = hash;
|
||||
message["id"] = id;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
const Parameters params{{"hash", browserRequest.hash}, {"id", id}};
|
||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const auto incrementedNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString siteUrl = decrypted.value("url").toString();
|
||||
const auto siteUrl = browserRequest.getString("url");
|
||||
if (siteUrl.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
|
||||
}
|
||||
|
||||
const QJsonArray keys = decrypted.value("keys").toArray();
|
||||
const auto keys = browserRequest.getArray("keys");
|
||||
|
||||
StringPairList keyList;
|
||||
for (const QJsonValue val : keys) {
|
||||
const QJsonObject keyObject = val.toObject();
|
||||
for (const auto val : keys) {
|
||||
const auto keyObject = val.toObject();
|
||||
keyList.push_back(qMakePair(keyObject.value("id").toString(), keyObject.value("key").toString()));
|
||||
}
|
||||
|
||||
const QString id = decrypted.value("id").toString();
|
||||
const QString formUrl = decrypted.value("submitUrl").toString();
|
||||
const QString auth = decrypted.value("httpAuth").toString();
|
||||
const auto id = browserRequest.getString("id");
|
||||
const auto formUrl = browserRequest.getString("submitUrl");
|
||||
const auto auth = browserRequest.getString("httpAuth");
|
||||
const bool httpAuth = auth.compare(TRUE_STR) == 0;
|
||||
|
||||
const QJsonArray users = browserService()->findMatchingEntries(id, siteUrl, formUrl, "", keyList, httpAuth);
|
||||
EntryParameters entryParameters;
|
||||
entryParameters.dbid = id;
|
||||
entryParameters.hash = browserRequest.hash;
|
||||
entryParameters.siteUrl = siteUrl;
|
||||
entryParameters.formUrl = formUrl;
|
||||
|
||||
const auto users = browserService()->findEntries(entryParameters, keyList, httpAuth);
|
||||
if (users.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND);
|
||||
}
|
||||
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(incrementedNonce);
|
||||
message["count"] = users.count();
|
||||
message["entries"] = users;
|
||||
message["hash"] = hash;
|
||||
message["id"] = id;
|
||||
|
||||
return buildResponse(action, message, incrementedNonce);
|
||||
const Parameters params{{"count", users.count()}, {"entries", users}, {"hash", browserRequest.hash}, {"id", id}};
|
||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGeneratePassword(QLocalSocket* socket, const QJsonObject& json, const QString& action)
|
||||
{
|
||||
auto errorMessage = getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
|
||||
auto nonce = json.value("nonce").toString();
|
||||
auto incrementedNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
auto requestId = decrypted.value("requestID").toString();
|
||||
const auto requestId = browserRequest.getString("requestID");
|
||||
|
||||
// Do not allow multiple requests from the same client
|
||||
if (browserService()->isPasswordGeneratorRequested()) {
|
||||
@ -305,86 +272,80 @@ QJsonObject BrowserAction::handleGeneratePassword(QLocalSocket* socket, const QJ
|
||||
return errorReply;
|
||||
}
|
||||
|
||||
browserService()->showPasswordGenerator(socket, incrementedNonce, m_clientPublicKey, m_secretKey);
|
||||
return QJsonObject();
|
||||
KeyPairMessage keyPairMessage{socket, browserRequest.incrementedNonce, m_clientPublicKey, m_secretKey};
|
||||
|
||||
browserService()->showPasswordGenerator(keyPairMessage);
|
||||
return {};
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
const QString url = decrypted.value("url").toString();
|
||||
const auto url = browserRequest.getString("url");
|
||||
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 group = decrypted.value("group").toString();
|
||||
const QString groupUuid = decrypted.value("groupUuid").toString();
|
||||
const QString downloadFavicon = decrypted.value("downloadFavicon").toString();
|
||||
const auto id = browserRequest.getString("id");
|
||||
const auto login = browserRequest.getString("login");
|
||||
const auto password = browserRequest.getString("password");
|
||||
const auto submitUrl = browserRequest.getString("submitUrl");
|
||||
const auto uuid = browserRequest.getString("uuid");
|
||||
const auto group = browserRequest.getString("group");
|
||||
const auto groupUuid = browserRequest.getString("groupUuid");
|
||||
const auto downloadFavicon = browserRequest.getString("downloadFavicon");
|
||||
const QString realm;
|
||||
|
||||
EntryParameters entryParameters;
|
||||
entryParameters.dbid = id;
|
||||
entryParameters.login = login;
|
||||
entryParameters.password = password;
|
||||
entryParameters.siteUrl = url;
|
||||
entryParameters.formUrl = submitUrl;
|
||||
entryParameters.realm = realm;
|
||||
|
||||
bool result = true;
|
||||
if (uuid.isEmpty()) {
|
||||
auto dlFavicon = !downloadFavicon.isEmpty() && downloadFavicon.compare(TRUE_STR) == 0;
|
||||
browserService()->addEntry(id, login, password, url, submitUrl, realm, group, groupUuid, dlFavicon);
|
||||
browserService()->addEntry(entryParameters, group, groupUuid, dlFavicon);
|
||||
} else {
|
||||
if (!Tools::isValidUuid(uuid)) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_VALID_UUID_PROVIDED);
|
||||
}
|
||||
|
||||
result = browserService()->updateEntry(id, uuid, login, password, url, submitUrl);
|
||||
result = browserService()->updateEntry(entryParameters, uuid);
|
||||
}
|
||||
|
||||
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce);
|
||||
message["count"] = QJsonValue::Null;
|
||||
message["entries"] = QJsonValue::Null;
|
||||
message["error"] = result ? QStringLiteral("success") : QStringLiteral("error");
|
||||
message["hash"] = hash;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
const Parameters params{{"count", QJsonValue::Null},
|
||||
{"entries", QJsonValue::Null},
|
||||
{"error", result ? QStringLiteral("success") : QStringLiteral("error")},
|
||||
{"hash", browserRequest.hash}};
|
||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
if (hash.isEmpty()) {
|
||||
if (browserRequest.hash.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (!command.isEmpty() && command.compare("lock-database") == 0) {
|
||||
const auto command = browserRequest.getString("action");
|
||||
if (!command.isEmpty() && command.compare(BROWSER_REQUEST_LOCK_DATABASE) == 0) {
|
||||
browserService()->lockDatabase();
|
||||
|
||||
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce);
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
return buildResponse(action, browserRequest.incrementedNonce);
|
||||
}
|
||||
|
||||
return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
|
||||
@ -392,164 +353,127 @@ QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QSt
|
||||
|
||||
QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (command.isEmpty() || command.compare("get-database-groups") != 0) {
|
||||
const auto command = browserRequest.getString("action");
|
||||
if (command.isEmpty() || command.compare(BROWSER_REQUEST_GET_DATABASE_GROUPS) != 0) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
const QJsonObject groups = browserService()->getDatabaseGroups();
|
||||
const auto groups = browserService()->getDatabaseGroups();
|
||||
if (groups.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_GROUPS_FOUND);
|
||||
}
|
||||
|
||||
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce);
|
||||
message["groups"] = groups;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
const Parameters params{{"groups", groups}};
|
||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString hash = browserService()->getDatabaseHash();
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (command.isEmpty() || command.compare("create-new-group") != 0) {
|
||||
const auto command = browserRequest.getString("action");
|
||||
if (command.isEmpty() || command.compare(BROWSER_REQUEST_CREATE_NEW_GROUP) != 0) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
QString group = decrypted.value("groupName").toString();
|
||||
const QJsonObject newGroup = browserService()->createNewGroup(group);
|
||||
const auto group = browserRequest.getString("groupName");
|
||||
const auto newGroup = browserService()->createNewGroup(group);
|
||||
if (newGroup.isEmpty() || newGroup["name"].toString().isEmpty() || newGroup["uuid"].toString().isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP);
|
||||
}
|
||||
|
||||
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce);
|
||||
message["name"] = newGroup["name"];
|
||||
message["uuid"] = newGroup["uuid"];
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
const Parameters params{{"name", newGroup["name"]}, {"uuid", newGroup["uuid"]}};
|
||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGetTotp(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (command.isEmpty() || command.compare("get-totp") != 0) {
|
||||
const auto command = browserRequest.getString("action");
|
||||
if (command.isEmpty() || command.compare(BROWSER_REQUEST_GET_TOTP) != 0) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
const QString uuid = decrypted.value("uuid").toString();
|
||||
const auto uuid = browserRequest.getString("uuid");
|
||||
if (!Tools::isValidUuid(uuid)) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_VALID_UUID_PROVIDED);
|
||||
}
|
||||
|
||||
// Get the current TOTP
|
||||
const auto totp = browserService()->getCurrentTotp(uuid);
|
||||
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce);
|
||||
message["totp"] = totp;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
const Parameters params{{"totp", browserService()->getCurrentTotp(uuid)}};
|
||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleDeleteEntry(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
|
||||
if (!m_associated) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
|
||||
}
|
||||
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (command.isEmpty() || command.compare("delete-entry") != 0) {
|
||||
const auto command = browserRequest.getString("action");
|
||||
if (command.isEmpty() || command.compare(BROWSER_REQUEST_DELETE_ENTRY) != 0) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
const auto uuid = decrypted.value("uuid").toString();
|
||||
const auto uuid = browserRequest.getString("uuid");
|
||||
if (!Tools::isValidUuid(uuid)) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_VALID_UUID_PROVIDED);
|
||||
}
|
||||
|
||||
const auto result = browserService()->deleteEntry(uuid);
|
||||
|
||||
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce);
|
||||
message["success"] = result ? TRUE_STR : FALSE_STR;
|
||||
|
||||
return buildResponse(action, message, newNonce);
|
||||
const Parameters params{{"success", result ? TRUE_STR : FALSE_STR}};
|
||||
return buildResponse(action, browserRequest.incrementedNonce, params);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::handleGlobalAutoType(const QJsonObject& json, const QString& action)
|
||||
{
|
||||
const QString nonce = json.value("nonce").toString();
|
||||
const QString encrypted = json.value("message").toString();
|
||||
const QJsonObject decrypted = decryptMessage(encrypted, nonce);
|
||||
|
||||
if (decrypted.isEmpty()) {
|
||||
const auto browserRequest = decodeRequest(json);
|
||||
if (browserRequest.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QString command = decrypted.value("action").toString();
|
||||
if (command.isEmpty() || command.compare("request-autotype") != 0) {
|
||||
const auto command = browserRequest.getString("action");
|
||||
if (command.isEmpty() || command.compare(BROWSER_REQUEST_REQUEST_AUTOTYPE) != 0) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
|
||||
}
|
||||
|
||||
const auto topLevelDomain = decrypted.value("search").toString();
|
||||
const auto topLevelDomain = browserRequest.getString("search");
|
||||
if (topLevelDomain.length() > BrowserAction::MaxUrlLength) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
|
||||
}
|
||||
|
||||
browserService()->requestGlobalAutoType(topLevelDomain);
|
||||
|
||||
const QString newNonce = browserMessageBuilder()->incrementNonce(nonce);
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(newNonce);
|
||||
return buildResponse(action, message, newNonce);
|
||||
return buildResponse(action, browserRequest.incrementedNonce);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::decryptMessage(const QString& message, const QString& nonce)
|
||||
@ -562,7 +486,18 @@ QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorC
|
||||
return browserMessageBuilder()->getErrorReply(action, errorCode);
|
||||
}
|
||||
|
||||
QJsonObject BrowserAction::buildResponse(const QString& action, const QJsonObject& message, const QString& nonce)
|
||||
QJsonObject BrowserAction::buildResponse(const QString& action, const QString& nonce, const Parameters& params)
|
||||
{
|
||||
return browserMessageBuilder()->buildResponse(action, message, nonce, m_clientPublicKey, m_secretKey);
|
||||
return browserMessageBuilder()->buildResponse(action, nonce, params, m_clientPublicKey, m_secretKey);
|
||||
}
|
||||
|
||||
BrowserRequest BrowserAction::decodeRequest(const QJsonObject& json)
|
||||
{
|
||||
const auto nonce = json.value("nonce").toString();
|
||||
const auto encrypted = json.value("message").toString();
|
||||
|
||||
return {browserService()->getDatabaseHash(),
|
||||
nonce,
|
||||
browserMessageBuilder()->incrementNonce(nonce),
|
||||
decryptMessage(encrypted, nonce)};
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2023 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
|
||||
@ -18,11 +18,37 @@
|
||||
#ifndef BROWSERACTION_H
|
||||
#define BROWSERACTION_H
|
||||
|
||||
#include "BrowserMessageBuilder.h"
|
||||
|
||||
#include <QJsonArray>
|
||||
#include <QJsonObject>
|
||||
#include <QString>
|
||||
|
||||
class QJsonObject;
|
||||
class QLocalSocket;
|
||||
|
||||
struct BrowserRequest
|
||||
{
|
||||
QString hash;
|
||||
QString nonce;
|
||||
QString incrementedNonce;
|
||||
QJsonObject decrypted;
|
||||
|
||||
inline bool isEmpty() const
|
||||
{
|
||||
return decrypted.isEmpty();
|
||||
}
|
||||
|
||||
inline QJsonArray getArray(const QString& param) const
|
||||
{
|
||||
return decrypted.value(param).toArray();
|
||||
}
|
||||
|
||||
inline QString getString(const QString& param) const
|
||||
{
|
||||
return decrypted.value(param).toString();
|
||||
}
|
||||
};
|
||||
|
||||
class BrowserAction
|
||||
{
|
||||
public:
|
||||
@ -48,10 +74,10 @@ private:
|
||||
QJsonObject handleGlobalAutoType(const QJsonObject& json, const QString& action);
|
||||
|
||||
private:
|
||||
QJsonObject buildMessage(const QString& nonce) const;
|
||||
QJsonObject buildResponse(const QString& action, const QJsonObject& message, const QString& nonce);
|
||||
QJsonObject buildResponse(const QString& action, const QString& nonce, const Parameters& params = {});
|
||||
QJsonObject getErrorReply(const QString& action, const int errorCode) const;
|
||||
QJsonObject decryptMessage(const QString& message, const QString& nonce);
|
||||
BrowserRequest decodeRequest(const QJsonObject& json);
|
||||
|
||||
private:
|
||||
static const int MaxUrlLength;
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2023 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
|
||||
@ -66,17 +66,24 @@ QJsonObject BrowserMessageBuilder::buildMessage(const QString& nonce) const
|
||||
}
|
||||
|
||||
QJsonObject BrowserMessageBuilder::buildResponse(const QString& action,
|
||||
const QJsonObject& message,
|
||||
const QString& nonce,
|
||||
const Parameters& params,
|
||||
const QString& publicKey,
|
||||
const QString& secretKey)
|
||||
{
|
||||
QJsonObject response;
|
||||
QString encryptedMessage = encryptMessage(message, nonce, publicKey, secretKey);
|
||||
auto message = buildMessage(nonce);
|
||||
|
||||
Parameters::const_iterator i;
|
||||
for (i = params.constBegin(); i != params.constEnd(); ++i) {
|
||||
message[i.key()] = QJsonValue::fromVariant(i.value());
|
||||
}
|
||||
|
||||
const auto encryptedMessage = encryptMessage(message, nonce, publicKey, secretKey);
|
||||
if (encryptedMessage.isEmpty()) {
|
||||
return getErrorReply(action, ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE);
|
||||
}
|
||||
|
||||
QJsonObject response;
|
||||
response["action"] = action;
|
||||
response["message"] = encryptedMessage;
|
||||
response["nonce"] = nonce;
|
||||
@ -127,7 +134,7 @@ QString BrowserMessageBuilder::encryptMessage(const QJsonObject& message,
|
||||
const QString& secretKey)
|
||||
{
|
||||
if (message.isEmpty() || nonce.isEmpty()) {
|
||||
return QString();
|
||||
return {};
|
||||
}
|
||||
|
||||
const QString reply(QJsonDocument(message).toJson());
|
||||
@ -135,7 +142,7 @@ QString BrowserMessageBuilder::encryptMessage(const QJsonObject& message,
|
||||
return encrypt(reply, nonce, publicKey, secretKey);
|
||||
}
|
||||
|
||||
return QString();
|
||||
return {};
|
||||
}
|
||||
|
||||
QJsonObject BrowserMessageBuilder::decryptMessage(const QString& message,
|
||||
@ -144,12 +151,12 @@ QJsonObject BrowserMessageBuilder::decryptMessage(const QString& message,
|
||||
const QString& secretKey)
|
||||
{
|
||||
if (message.isEmpty() || nonce.isEmpty()) {
|
||||
return QJsonObject();
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray ba = decrypt(message, nonce, publicKey, secretKey);
|
||||
if (ba.isEmpty()) {
|
||||
return QJsonObject();
|
||||
return {};
|
||||
}
|
||||
|
||||
return getJsonObject(ba);
|
||||
@ -174,7 +181,7 @@ QString BrowserMessageBuilder::encrypt(const QString& plaintext,
|
||||
e.resize(BrowserShared::NATIVEMSG_MAX_LENGTH);
|
||||
|
||||
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
|
||||
return QString();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (crypto_box_easy(e.data(), m.data(), m.size(), n.data(), ck.data(), sk.data()) == 0) {
|
||||
@ -182,7 +189,7 @@ QString BrowserMessageBuilder::encrypt(const QString& plaintext,
|
||||
return res.toBase64();
|
||||
}
|
||||
|
||||
return QString();
|
||||
return {};
|
||||
}
|
||||
|
||||
QByteArray BrowserMessageBuilder::decrypt(const QString& encrypted,
|
||||
@ -204,14 +211,14 @@ QByteArray BrowserMessageBuilder::decrypt(const QString& encrypted,
|
||||
d.resize(BrowserShared::NATIVEMSG_MAX_LENGTH);
|
||||
|
||||
if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
|
||||
return QByteArray();
|
||||
return {};
|
||||
}
|
||||
|
||||
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();
|
||||
return {};
|
||||
}
|
||||
|
||||
QString BrowserMessageBuilder::getBase64FromKey(const uchar* array, const uint len)
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2022 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2023 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
|
||||
@ -20,9 +20,12 @@
|
||||
|
||||
#include <QPair>
|
||||
#include <QString>
|
||||
#include <QVariant>
|
||||
|
||||
class QJsonObject;
|
||||
|
||||
typedef QMap<QString, QVariant> Parameters;
|
||||
|
||||
namespace
|
||||
{
|
||||
enum
|
||||
@ -55,11 +58,10 @@ public:
|
||||
static BrowserMessageBuilder* instance();
|
||||
|
||||
QPair<QString, QString> getKeyPair();
|
||||
|
||||
QJsonObject buildMessage(const QString& nonce) const;
|
||||
QJsonObject buildResponse(const QString& action,
|
||||
const QJsonObject& message,
|
||||
const QString& nonce,
|
||||
const Parameters& params,
|
||||
const QString& publicKey,
|
||||
const QString& secretKey);
|
||||
QJsonObject getErrorReply(const QString& action, const int errorCode) const;
|
||||
|
@ -314,23 +314,18 @@ QString BrowserService::getCurrentTotp(const QString& uuid)
|
||||
return {};
|
||||
}
|
||||
|
||||
QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl,
|
||||
const QString& realm,
|
||||
const StringPairList& keyList,
|
||||
const bool httpAuth)
|
||||
QJsonArray
|
||||
BrowserService::findEntries(const EntryParameters& entryParameters, const StringPairList& keyList, const bool httpAuth)
|
||||
{
|
||||
Q_UNUSED(dbid);
|
||||
const bool alwaysAllowAccess = browserSettings()->alwaysAllowAccess();
|
||||
const bool ignoreHttpAuth = browserSettings()->httpAuthPermission();
|
||||
const QString siteHost = QUrl(siteUrl).host();
|
||||
const QString formHost = QUrl(formUrl).host();
|
||||
const QString siteHost = QUrl(entryParameters.siteUrl).host();
|
||||
const QString formHost = QUrl(entryParameters.formUrl).host();
|
||||
|
||||
// Check entries for authorization
|
||||
QList<Entry*> pwEntriesToConfirm;
|
||||
QList<Entry*> pwEntries;
|
||||
for (auto* entry : searchEntries(siteUrl, formUrl, keyList)) {
|
||||
for (auto* entry : searchEntries(entryParameters.siteUrl, entryParameters.formUrl, keyList)) {
|
||||
auto entryCustomData = entry->customData();
|
||||
|
||||
if (!httpAuth
|
||||
@ -353,7 +348,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (checkAccess(entry, siteHost, formHost, realm)) {
|
||||
switch (checkAccess(entry, siteHost, formHost, entryParameters.realm)) {
|
||||
case Denied:
|
||||
continue;
|
||||
|
||||
@ -373,7 +368,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
||||
|
||||
// Confirm entries
|
||||
QList<Entry*> selectedEntriesToConfirm =
|
||||
confirmEntries(pwEntriesToConfirm, siteUrl, siteHost, formHost, realm, httpAuth);
|
||||
confirmEntries(pwEntriesToConfirm, entryParameters, siteHost, formHost, httpAuth);
|
||||
if (!selectedEntriesToConfirm.isEmpty()) {
|
||||
pwEntries.append(selectedEntriesToConfirm);
|
||||
}
|
||||
@ -388,7 +383,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
||||
}
|
||||
|
||||
// Sort results
|
||||
pwEntries = sortEntries(pwEntries, siteUrl, formUrl);
|
||||
pwEntries = sortEntries(pwEntries, entryParameters.siteUrl, entryParameters.formUrl);
|
||||
|
||||
// Fill the list
|
||||
QJsonArray result;
|
||||
@ -400,10 +395,9 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid,
|
||||
}
|
||||
|
||||
QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||
const QString& siteUrl,
|
||||
const EntryParameters& entryParameters,
|
||||
const QString& siteHost,
|
||||
const QString& formUrl,
|
||||
const QString& realm,
|
||||
const bool httpAuth)
|
||||
{
|
||||
if (pwEntriesToConfirm.isEmpty() || m_dialogActive) {
|
||||
@ -418,10 +412,10 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||
|
||||
connect(&accessControlDialog, &BrowserAccessControlDialog::disableAccess, [&](QTableWidgetItem* item) {
|
||||
auto entry = pwEntriesToConfirm[item->row()];
|
||||
denyEntry(entry, siteHost, formUrl, realm);
|
||||
denyEntry(entry, siteHost, formUrl, entryParameters.realm);
|
||||
});
|
||||
|
||||
accessControlDialog.setItems(pwEntriesToConfirm, siteUrl, httpAuth);
|
||||
accessControlDialog.setItems(pwEntriesToConfirm, entryParameters.siteUrl, httpAuth);
|
||||
|
||||
QList<Entry*> allowedEntries;
|
||||
auto ret = accessControlDialog.exec();
|
||||
@ -429,7 +423,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||
for (auto item : accessControlDialog.getSelectedEntries()) {
|
||||
auto entry = pwEntriesToConfirm[item->row()];
|
||||
if (accessControlDialog.remember()) {
|
||||
allowEntry(entry, siteHost, formUrl, realm);
|
||||
allowEntry(entry, siteHost, formUrl, entryParameters.realm);
|
||||
}
|
||||
allowedEntries.append(entry);
|
||||
}
|
||||
@ -442,10 +436,7 @@ QList<Entry*> BrowserService::confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||
return allowedEntries;
|
||||
}
|
||||
|
||||
void BrowserService::showPasswordGenerator(QLocalSocket* socket,
|
||||
const QString& incrementedNonce,
|
||||
const QString& publicKey,
|
||||
const QString& secretKey)
|
||||
void BrowserService::showPasswordGenerator(const KeyPairMessage& keyPairMessage)
|
||||
{
|
||||
if (!m_passwordGenerator) {
|
||||
m_passwordGenerator.reset(PasswordGeneratorWidget::popupGenerator());
|
||||
@ -454,7 +445,7 @@ void BrowserService::showPasswordGenerator(QLocalSocket* socket,
|
||||
if (!m_passwordGenerator->isPasswordGenerated()) {
|
||||
auto errorMessage = browserMessageBuilder()->getErrorReply("generate-password",
|
||||
ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
|
||||
m_browserHost->sendClientMessage(socket, errorMessage);
|
||||
m_browserHost->sendClientMessage(keyPairMessage.socket, errorMessage);
|
||||
}
|
||||
|
||||
m_passwordGenerator.reset();
|
||||
@ -466,12 +457,13 @@ void BrowserService::showPasswordGenerator(QLocalSocket* socket,
|
||||
&PasswordGeneratorWidget::appliedPassword,
|
||||
m_passwordGenerator.data(),
|
||||
[=](const QString& password) {
|
||||
QJsonObject message = browserMessageBuilder()->buildMessage(incrementedNonce);
|
||||
message["password"] = password;
|
||||
m_browserHost->sendClientMessage(
|
||||
socket,
|
||||
browserMessageBuilder()->buildResponse(
|
||||
"generate-password", message, incrementedNonce, publicKey, secretKey));
|
||||
const Parameters params{{"password", password}};
|
||||
m_browserHost->sendClientMessage(keyPairMessage.socket,
|
||||
browserMessageBuilder()->buildResponse("generate-password",
|
||||
keyPairMessage.nonce,
|
||||
params,
|
||||
keyPairMessage.publicKey,
|
||||
keyPairMessage.secretKey));
|
||||
hideWindow();
|
||||
});
|
||||
}
|
||||
@ -549,19 +541,13 @@ QString BrowserService::getKey(const QString& id)
|
||||
return db->metadata()->customData()->value(CustomData::BrowserKeyPrefix + id);
|
||||
}
|
||||
|
||||
void BrowserService::addEntry(const QString& dbid,
|
||||
const QString& login,
|
||||
const QString& password,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl,
|
||||
const QString& realm,
|
||||
void BrowserService::addEntry(const EntryParameters& entryParameters,
|
||||
const QString& group,
|
||||
const QString& groupUuid,
|
||||
const bool downloadFavicon,
|
||||
const QSharedPointer<Database>& selectedDb)
|
||||
{
|
||||
// TODO: select database based on this key id
|
||||
Q_UNUSED(dbid);
|
||||
auto db = selectedDb ? selectedDb : selectedDatabase();
|
||||
if (!db) {
|
||||
return;
|
||||
@ -569,11 +555,11 @@ void BrowserService::addEntry(const QString& dbid,
|
||||
|
||||
auto* entry = new Entry();
|
||||
entry->setUuid(QUuid::createUuid());
|
||||
entry->setTitle(QUrl(siteUrl).host());
|
||||
entry->setUrl(siteUrl);
|
||||
entry->setTitle(QUrl(entryParameters.siteUrl).host());
|
||||
entry->setUrl(entryParameters.siteUrl);
|
||||
entry->setIcon(KEEPASSXCBROWSER_DEFAULT_ICON);
|
||||
entry->setUsername(login);
|
||||
entry->setPassword(password);
|
||||
entry->setUsername(entryParameters.login);
|
||||
entry->setPassword(entryParameters.password);
|
||||
|
||||
// Select a group for the entry
|
||||
if (!group.isEmpty()) {
|
||||
@ -589,16 +575,16 @@ void BrowserService::addEntry(const QString& dbid,
|
||||
entry->setGroup(getDefaultEntryGroup(db));
|
||||
}
|
||||
|
||||
const QString host = QUrl(siteUrl).host();
|
||||
const QString submitHost = QUrl(formUrl).host();
|
||||
const QString host = QUrl(entryParameters.siteUrl).host();
|
||||
const QString submitHost = QUrl(entryParameters.formUrl).host();
|
||||
BrowserEntryConfig config;
|
||||
config.allow(host);
|
||||
|
||||
if (!submitHost.isEmpty()) {
|
||||
config.allow(submitHost);
|
||||
}
|
||||
if (!realm.isEmpty()) {
|
||||
config.setRealm(realm);
|
||||
if (!entryParameters.realm.isEmpty()) {
|
||||
config.setRealm(entryParameters.realm);
|
||||
}
|
||||
config.save(entry);
|
||||
|
||||
@ -607,15 +593,9 @@ void BrowserService::addEntry(const QString& dbid,
|
||||
}
|
||||
}
|
||||
|
||||
bool BrowserService::updateEntry(const QString& dbid,
|
||||
const QString& uuid,
|
||||
const QString& login,
|
||||
const QString& password,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl)
|
||||
bool BrowserService::updateEntry(const EntryParameters& entryParameters, const QString& uuid)
|
||||
{
|
||||
// TODO: select database based on this key id
|
||||
Q_UNUSED(dbid);
|
||||
auto db = selectedDatabase();
|
||||
if (!db) {
|
||||
return false;
|
||||
@ -624,7 +604,7 @@ bool BrowserService::updateEntry(const QString& dbid,
|
||||
Entry* entry = db->rootGroup()->findEntryByUuid(Tools::hexToUuid(uuid));
|
||||
if (!entry) {
|
||||
// If entry is not found for update, add a new one to the selected database
|
||||
addEntry(dbid, login, password, siteUrl, formUrl, "", "", "", db);
|
||||
addEntry(entryParameters, "", "", false, db);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -639,32 +619,32 @@ bool BrowserService::updateEntry(const QString& dbid,
|
||||
}
|
||||
}
|
||||
|
||||
QString username = entry->username();
|
||||
auto username = entry->username();
|
||||
if (username.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool result = false;
|
||||
if (username.compare(login, Qt::CaseSensitive) != 0
|
||||
|| entry->password().compare(password, Qt::CaseSensitive) != 0) {
|
||||
if (username.compare(entryParameters.login, Qt::CaseSensitive) != 0
|
||||
|| entry->password().compare(entryParameters.password, Qt::CaseSensitive) != 0) {
|
||||
MessageBox::Button dialogResult = MessageBox::No;
|
||||
if (!browserSettings()->alwaysAllowUpdate()) {
|
||||
raiseWindow();
|
||||
dialogResult = MessageBox::question(
|
||||
nullptr,
|
||||
tr("KeePassXC: Update Entry"),
|
||||
tr("Do you want to update the information in %1 - %2?").arg(QUrl(siteUrl).host(), username),
|
||||
MessageBox::Save | MessageBox::Cancel,
|
||||
MessageBox::Cancel,
|
||||
MessageBox::Raise);
|
||||
dialogResult = MessageBox::question(nullptr,
|
||||
tr("KeePassXC: Update Entry"),
|
||||
tr("Do you want to update the information in %1 - %2?")
|
||||
.arg(QUrl(entryParameters.siteUrl).host(), username),
|
||||
MessageBox::Save | MessageBox::Cancel,
|
||||
MessageBox::Cancel,
|
||||
MessageBox::Raise);
|
||||
}
|
||||
|
||||
if (browserSettings()->alwaysAllowUpdate() || dialogResult == MessageBox::Save) {
|
||||
entry->beginUpdate();
|
||||
if (!entry->attributes()->isReference(EntryAttributes::UserNameKey)) {
|
||||
entry->setUsername(login);
|
||||
entry->setUsername(entryParameters.login);
|
||||
}
|
||||
entry->setPassword(password);
|
||||
entry->setPassword(entryParameters.password);
|
||||
entry->endUpdate();
|
||||
result = true;
|
||||
}
|
||||
|
@ -34,6 +34,25 @@ enum
|
||||
max_length = 16 * 1024
|
||||
};
|
||||
|
||||
struct KeyPairMessage
|
||||
{
|
||||
QLocalSocket* socket;
|
||||
QString nonce;
|
||||
QString publicKey;
|
||||
QString secretKey;
|
||||
};
|
||||
|
||||
struct EntryParameters
|
||||
{
|
||||
QString dbid;
|
||||
QString login;
|
||||
QString password;
|
||||
QString realm;
|
||||
QString hash;
|
||||
QString siteUrl;
|
||||
QString formUrl;
|
||||
};
|
||||
|
||||
class DatabaseWidget;
|
||||
class BrowserHost;
|
||||
class BrowserAction;
|
||||
@ -59,35 +78,18 @@ public:
|
||||
QJsonObject getDatabaseGroups();
|
||||
QJsonObject createNewGroup(const QString& groupName);
|
||||
QString getCurrentTotp(const QString& uuid);
|
||||
void showPasswordGenerator(QLocalSocket* socket,
|
||||
const QString& nonce,
|
||||
const QString& publicKey,
|
||||
const QString& secretKey);
|
||||
void showPasswordGenerator(const KeyPairMessage& keyPairMessage);
|
||||
bool isPasswordGeneratorRequested() const;
|
||||
|
||||
void addEntry(const QString& dbid,
|
||||
const QString& login,
|
||||
const QString& password,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl,
|
||||
const QString& realm,
|
||||
void addEntry(const EntryParameters& entryParameters,
|
||||
const QString& group,
|
||||
const QString& groupUuid,
|
||||
const bool downloadFavicon,
|
||||
const QSharedPointer<Database>& selectedDb = {});
|
||||
bool updateEntry(const QString& dbid,
|
||||
const QString& uuid,
|
||||
const QString& login,
|
||||
const QString& password,
|
||||
const QString& siteUrl,
|
||||
const QString& formUrl);
|
||||
bool updateEntry(const EntryParameters& entryParameters, const QString& uuid);
|
||||
bool deleteEntry(const QString& uuid);
|
||||
QJsonArray findMatchingEntries(const QString& dbid,
|
||||
const QString& siteUrlStr,
|
||||
const QString& formUrlStr,
|
||||
const QString& realm,
|
||||
const StringPairList& keyList,
|
||||
const bool httpAuth = false);
|
||||
QJsonArray
|
||||
findEntries(const EntryParameters& entryParameters, const StringPairList& keyList, const bool httpAuth = false);
|
||||
void requestGlobalAutoType(const QString& search);
|
||||
|
||||
static const QString KEEPASSXCBROWSER_NAME;
|
||||
@ -130,10 +132,9 @@ private:
|
||||
QList<Entry*> searchEntries(const QString& siteUrl, const QString& formUrl, const StringPairList& keyList);
|
||||
QList<Entry*> sortEntries(QList<Entry*>& pwEntries, const QString& siteUrl, const QString& formUrl);
|
||||
QList<Entry*> confirmEntries(QList<Entry*>& pwEntriesToConfirm,
|
||||
const QString& siteUrl,
|
||||
const EntryParameters& entryParameters,
|
||||
const QString& siteHost,
|
||||
const QString& formUrl,
|
||||
const QString& realm,
|
||||
const bool httpAuth);
|
||||
QJsonObject prepareEntry(const Entry* entry);
|
||||
void allowEntry(Entry* entry, const QString& siteHost, const QString& formUrl, const QString& realm);
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2023 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
|
||||
@ -37,6 +37,7 @@ const QString SECRETKEY = "B8ei4ZjQJkWzZU2SK/tBsrYRwp+6ztEMf5GFQV+i0yI=";
|
||||
const QString SERVERPUBLICKEY = "lKnbLhrVCOqzEjuNoUz1xj9EZlz8xeO4miZBvLrUPVQ=";
|
||||
const QString SERVERSECRETKEY = "tbPQcghxfOgbmsnEqG2qMIj1W2+nh+lOJcNsHncaz1Q=";
|
||||
const QString NONCE = "zBKdvTjL5bgWaKMCTut/8soM/uoMrFoZ";
|
||||
const QString INCREMENTEDNONCE = "zRKdvTjL5bgWaKMCTut/8soM/uoMrFoZ";
|
||||
const QString CLIENTID = "testClient";
|
||||
|
||||
void TestBrowser::initTestCase()
|
||||
@ -107,7 +108,40 @@ void TestBrowser::testGetBase64FromKey()
|
||||
void TestBrowser::testIncrementNonce()
|
||||
{
|
||||
auto result = browserMessageBuilder()->incrementNonce(NONCE);
|
||||
QCOMPARE(result, QString("zRKdvTjL5bgWaKMCTut/8soM/uoMrFoZ"));
|
||||
QCOMPARE(result, INCREMENTEDNONCE);
|
||||
}
|
||||
|
||||
void TestBrowser::testBuildResponse()
|
||||
{
|
||||
const auto object = QJsonObject{{"test", true}};
|
||||
const QJsonArray arr = {QJsonObject{{"test", true}}};
|
||||
const auto val = QString("value1");
|
||||
|
||||
// Note: Passing a const QJsonObject will fail
|
||||
const Parameters params{
|
||||
{"test-param-1", val}, {"test-param-2", 2}, {"test-param-3", false}, {"object", object}, {"arr", arr}};
|
||||
|
||||
const auto action = QString("test-action");
|
||||
const auto message = browserMessageBuilder()->buildResponse(action, NONCE, params, PUBLICKEY, SERVERSECRETKEY);
|
||||
QVERIFY(!message.isEmpty());
|
||||
QCOMPARE(message["action"].toString(), action);
|
||||
QCOMPARE(message["nonce"].toString(), NONCE);
|
||||
|
||||
const auto decrypted =
|
||||
browserMessageBuilder()->decryptMessage(message["message"].toString(), NONCE, PUBLICKEY, SERVERSECRETKEY);
|
||||
QVERIFY(!decrypted.isEmpty());
|
||||
QCOMPARE(decrypted["test-param-1"].toString(), QString("value1"));
|
||||
QCOMPARE(decrypted["test-param-2"].toInt(), 2);
|
||||
QCOMPARE(decrypted["test-param-3"].toBool(), false);
|
||||
|
||||
const auto objectResult = decrypted["object"].toObject();
|
||||
QCOMPARE(objectResult["test"].toBool(), true);
|
||||
|
||||
const auto arrResult = decrypted["arr"].toArray();
|
||||
QCOMPARE(arrResult.size(), 1);
|
||||
|
||||
const auto firstArr = arrResult[0].toObject();
|
||||
QCOMPARE(firstArr["test"].toBool(), true);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2021 KeePassXC Team <team@keepassxc.org>
|
||||
* Copyright (C) 2023 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
|
||||
@ -36,7 +36,7 @@ private slots:
|
||||
void testDecryptMessage();
|
||||
void testGetBase64FromKey();
|
||||
void testIncrementNonce();
|
||||
|
||||
void testBuildResponse();
|
||||
void testTopLevelDomain();
|
||||
void testIsIpAddress();
|
||||
void testSortPriority();
|
||||
|
Loading…
Reference in New Issue
Block a user