From e401e8f4bce5bad43b877bf05f3db8d145a6b22d Mon Sep 17 00:00:00 2001 From: Jonathan White Date: Thu, 11 Jan 2024 07:07:21 -0500 Subject: [PATCH] Browser Integration code cleanup (#8489) Co-authored-by: varjolintu --- src/browser/BrowserAction.cpp | 385 +++++++++++--------------- src/browser/BrowserAction.h | 34 ++- src/browser/BrowserMessageBuilder.cpp | 31 ++- src/browser/BrowserMessageBuilder.h | 8 +- src/browser/BrowserService.cpp | 108 +++----- src/browser/BrowserService.h | 49 ++-- tests/TestBrowser.cpp | 38 ++- tests/TestBrowser.h | 4 +- 8 files changed, 321 insertions(+), 336 deletions(-) diff --git a/src/browser/BrowserAction.cpp b/src/browser/BrowserAction.cpp index 9e555a43f..b8b5b9879 100644 --- a/src/browser/BrowserAction.cpp +++ b/src/browser/BrowserAction.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 KeePassXC Team + * Copyright (C) 2023 KeePassXC Team * * 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 #include -#include #include 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)}; } diff --git a/src/browser/BrowserAction.h b/src/browser/BrowserAction.h index 49c66b644..5013f3d3b 100644 --- a/src/browser/BrowserAction.h +++ b/src/browser/BrowserAction.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 KeePassXC Team + * Copyright (C) 2023 KeePassXC Team * * 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 +#include #include -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; diff --git a/src/browser/BrowserMessageBuilder.cpp b/src/browser/BrowserMessageBuilder.cpp index e537205df..4069ae29e 100644 --- a/src/browser/BrowserMessageBuilder.cpp +++ b/src/browser/BrowserMessageBuilder.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 KeePassXC Team + * Copyright (C) 2023 KeePassXC Team * * 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::length(reinterpret_cast(d.data()))); } - return QByteArray(); + return {}; } QString BrowserMessageBuilder::getBase64FromKey(const uchar* array, const uint len) diff --git a/src/browser/BrowserMessageBuilder.h b/src/browser/BrowserMessageBuilder.h index 9dc4f27f0..c8605ab58 100644 --- a/src/browser/BrowserMessageBuilder.h +++ b/src/browser/BrowserMessageBuilder.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2022 KeePassXC Team + * Copyright (C) 2023 KeePassXC Team * * 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 #include +#include class QJsonObject; +typedef QMap Parameters; + namespace { enum @@ -55,11 +58,10 @@ public: static BrowserMessageBuilder* instance(); QPair 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; diff --git a/src/browser/BrowserService.cpp b/src/browser/BrowserService.cpp index 633ff7a08..e00210d88 100644 --- a/src/browser/BrowserService.cpp +++ b/src/browser/BrowserService.cpp @@ -313,23 +313,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 pwEntriesToConfirm; QList pwEntries; - for (auto* entry : searchEntries(siteUrl, formUrl, keyList)) { + for (auto* entry : searchEntries(entryParameters.siteUrl, entryParameters.formUrl, keyList)) { auto entryCustomData = entry->customData(); if (!httpAuth @@ -352,7 +347,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid, continue; } - switch (checkAccess(entry, siteHost, formHost, realm)) { + switch (checkAccess(entry, siteHost, formHost, entryParameters.realm)) { case Denied: continue; @@ -372,7 +367,7 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid, // Confirm entries QList selectedEntriesToConfirm = - confirmEntries(pwEntriesToConfirm, siteUrl, siteHost, formHost, realm, httpAuth); + confirmEntries(pwEntriesToConfirm, entryParameters, siteHost, formHost, httpAuth); if (!selectedEntriesToConfirm.isEmpty()) { pwEntries.append(selectedEntriesToConfirm); } @@ -387,7 +382,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; @@ -399,10 +394,9 @@ QJsonArray BrowserService::findMatchingEntries(const QString& dbid, } QList BrowserService::confirmEntries(QList& pwEntriesToConfirm, - const QString& siteUrl, + const EntryParameters& entryParameters, const QString& siteHost, const QString& formUrl, - const QString& realm, const bool httpAuth) { if (pwEntriesToConfirm.isEmpty() || m_dialogActive) { @@ -417,10 +411,10 @@ QList BrowserService::confirmEntries(QList& 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 allowedEntries; auto ret = accessControlDialog.exec(); @@ -428,7 +422,7 @@ QList BrowserService::confirmEntries(QList& 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); } @@ -441,10 +435,7 @@ QList BrowserService::confirmEntries(QList& 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(m_currentDatabaseWidget)); @@ -453,7 +444,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(); @@ -465,12 +456,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(); }); } @@ -575,19 +567,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& selectedDb) { // TODO: select database based on this key id - Q_UNUSED(dbid); auto db = selectedDb ? selectedDb : selectedDatabase(); if (!db) { return; @@ -595,11 +581,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()) { @@ -615,16 +601,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); @@ -633,15 +619,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; @@ -650,7 +630,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; } @@ -665,32 +645,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( - m_currentDatabaseWidget, - 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; } diff --git a/src/browser/BrowserService.h b/src/browser/BrowserService.h index e0f871b4e..040bed495 100644 --- a/src/browser/BrowserService.h +++ b/src/browser/BrowserService.h @@ -33,6 +33,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; @@ -58,36 +77,19 @@ 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; bool isUrlIdentical(const QString& first, const QString& second) 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& 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 void convertAttributesToCustomData(QSharedPointer db); @@ -131,10 +133,9 @@ private: QList searchEntries(const QString& siteUrl, const QString& formUrl, const StringPairList& keyList); QList sortEntries(QList& pwEntries, const QString& siteUrl, const QString& formUrl); QList confirmEntries(QList& 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); diff --git a/tests/TestBrowser.cpp b/tests/TestBrowser.cpp index d19c77067..77cd4d071 100644 --- a/tests/TestBrowser.cpp +++ b/tests/TestBrowser.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 KeePassXC Team + * Copyright (C) 2023 KeePassXC Team * * 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); } /** diff --git a/tests/TestBrowser.h b/tests/TestBrowser.h index 58cce16f2..7093a5065 100644 --- a/tests/TestBrowser.h +++ b/tests/TestBrowser.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2021 KeePassXC Team + * Copyright (C) 2023 KeePassXC Team * * 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();