0
0
mirror of https://github.com/keepassxreboot/keepassxc.git synced 2024-09-20 04:12:15 +02:00

Browser Integration code cleanup (#8489)

Co-authored-by: varjolintu <sami.vanttinen@protonmail.com>
This commit is contained in:
Sami Vänttinen 2023-02-18 22:52:31 +02:00 committed by GitHub
parent ba15981700
commit 4a30417f76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 321 additions and 336 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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