mirror of
				https://github.com/keepassxreboot/keepassxc.git
				synced 2025-10-31 06:35:00 -04:00 
			
		
		
		
	 e40f10657d
			
		
	
	
		e40f10657d
		
	
	
	
	
		
			
			* Pass correct entropy amount to keepassxc-browser instead of amount of bits for both password and passphrase. * Rename json key from "login" to "entropy" (keeping "login" key for backwards compatibility). * Also make some changes to entropy calculation methods: - Rename PassphraseGenerator::calculateEntropy to estimateEntropy - Rename PasswordGenerator::calculateEntropy to estimateEntropy
		
			
				
	
	
		
			666 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			666 lines
		
	
	
	
		
			23 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /*
 | |
|  *  Copyright (C) 2017 Sami Vänttinen <sami.vanttinen@protonmail.com>
 | |
|  *  Copyright (C) 2017 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
 | |
|  *  the Free Software Foundation, either version 3 of the License, or
 | |
|  *  (at your option) any later version.
 | |
|  *
 | |
|  *  This program is distributed in the hope that it will be useful,
 | |
|  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 | |
|  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 | |
|  *  GNU General Public License for more details.
 | |
|  *
 | |
|  *  You should have received a copy of the GNU General Public License
 | |
|  *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 | |
|  */
 | |
| 
 | |
| #include "BrowserAction.h"
 | |
| #include "BrowserSettings.h"
 | |
| #include "NativeMessagingBase.h"
 | |
| #include "config-keepassx.h"
 | |
| 
 | |
| #include <QJsonDocument>
 | |
| #include <QJsonParseError>
 | |
| #include <sodium.h>
 | |
| #include <sodium/crypto_box.h>
 | |
| #include <sodium/randombytes.h>
 | |
| 
 | |
| BrowserAction::BrowserAction(BrowserService& browserService)
 | |
|     : m_mutex(QMutex::Recursive)
 | |
|     , m_browserService(browserService)
 | |
|     , m_associated(false)
 | |
| {
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::readResponse(const QJsonObject& json)
 | |
| {
 | |
|     if (json.isEmpty()) {
 | |
|         return getErrorReply("", ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED);
 | |
|     }
 | |
| 
 | |
|     bool triggerUnlock = false;
 | |
|     const QString trigger = json.value("triggerUnlock").toString();
 | |
|     if (!trigger.isEmpty() && trigger.compare("true", Qt::CaseSensitive) == 0) {
 | |
|         triggerUnlock = true;
 | |
|     }
 | |
| 
 | |
|     const QString action = json.value("action").toString();
 | |
|     if (action.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
 | |
|     }
 | |
| 
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     if (action.compare("change-public-keys", Qt::CaseSensitive) != 0 && !m_browserService.isDatabaseOpened()) {
 | |
|         if (m_clientPublicKey.isEmpty()) {
 | |
|             return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
 | |
|         } else if (!m_browserService.openDatabase(triggerUnlock)) {
 | |
|             return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return handleAction(json);
 | |
| }
 | |
| 
 | |
| // Private functions
 | |
| ///////////////////////
 | |
| 
 | |
| QJsonObject BrowserAction::handleAction(const QJsonObject& json)
 | |
| {
 | |
|     QString action = json.value("action").toString();
 | |
| 
 | |
|     if (action.compare("change-public-keys", Qt::CaseSensitive) == 0) {
 | |
|         return handleChangePublicKeys(json, action);
 | |
|     } else if (action.compare("get-databasehash", Qt::CaseSensitive) == 0) {
 | |
|         return handleGetDatabaseHash(json, action);
 | |
|     } else if (action.compare("associate", Qt::CaseSensitive) == 0) {
 | |
|         return handleAssociate(json, action);
 | |
|     } else if (action.compare("test-associate", Qt::CaseSensitive) == 0) {
 | |
|         return handleTestAssociate(json, action);
 | |
|     } else if (action.compare("get-logins", Qt::CaseSensitive) == 0) {
 | |
|         return handleGetLogins(json, action);
 | |
|     } else if (action.compare("generate-password", Qt::CaseSensitive) == 0) {
 | |
|         return handleGeneratePassword(json, action);
 | |
|     } else if (action.compare("set-login", Qt::CaseSensitive) == 0) {
 | |
|         return handleSetLogin(json, action);
 | |
|     } else if (action.compare("lock-database", Qt::CaseSensitive) == 0) {
 | |
|         return handleLockDatabase(json, action);
 | |
|     } else if (action.compare("get-database-groups", Qt::CaseSensitive) == 0) {
 | |
|         return handleGetDatabaseGroups(json, action);
 | |
|     } else if (action.compare("create-new-group", Qt::CaseSensitive) == 0) {
 | |
|         return handleCreateNewGroup(json, action);
 | |
|     }
 | |
| 
 | |
|     // Action was not recognized
 | |
|     return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::handleChangePublicKeys(const QJsonObject& json, const QString& action)
 | |
| {
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     const QString nonce = json.value("nonce").toString();
 | |
|     const QString clientPublicKey = json.value("publicKey").toString();
 | |
| 
 | |
|     if (clientPublicKey.isEmpty() || nonce.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED);
 | |
|     }
 | |
| 
 | |
|     m_associated = false;
 | |
|     unsigned char pk[crypto_box_PUBLICKEYBYTES];
 | |
|     unsigned char sk[crypto_box_SECRETKEYBYTES];
 | |
|     crypto_box_keypair(pk, sk);
 | |
| 
 | |
|     const QString publicKey = getBase64FromKey(pk, crypto_box_PUBLICKEYBYTES);
 | |
|     const QString secretKey = getBase64FromKey(sk, crypto_box_SECRETKEYBYTES);
 | |
|     if (publicKey.isEmpty() || secretKey.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED);
 | |
|     }
 | |
| 
 | |
|     m_clientPublicKey = clientPublicKey;
 | |
|     m_publicKey = publicKey;
 | |
|     m_secretKey = secretKey;
 | |
| 
 | |
|     QJsonObject response = buildMessage(incrementNonce(nonce));
 | |
|     response["action"] = action;
 | |
|     response["publicKey"] = publicKey;
 | |
| 
 | |
|     return response;
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::handleGetDatabaseHash(const QJsonObject& json, const QString& action)
 | |
| {
 | |
|     const QString hash = getDatabaseHash();
 | |
|     const QString nonce = json.value("nonce").toString();
 | |
|     const QString encrypted = json.value("message").toString();
 | |
|     const QJsonObject decrypted = decryptMessage(encrypted, nonce);
 | |
| 
 | |
|     if (decrypted.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
 | |
|     }
 | |
| 
 | |
|     if (hash.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
 | |
|     }
 | |
| 
 | |
|     QString command = decrypted.value("action").toString();
 | |
|     if (!command.isEmpty() && command.compare("get-databasehash", Qt::CaseSensitive) == 0) {
 | |
|         const QString newNonce = incrementNonce(nonce);
 | |
| 
 | |
|         QJsonObject message = buildMessage(newNonce);
 | |
|         message["hash"] = hash;
 | |
|         return buildResponse(action, message, newNonce);
 | |
|     }
 | |
| 
 | |
|     return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::handleAssociate(const QJsonObject& json, const QString& action)
 | |
| {
 | |
|     const QString hash = getDatabaseHash();
 | |
|     const QString nonce = json.value("nonce").toString();
 | |
|     const QString encrypted = json.value("message").toString();
 | |
|     const QJsonObject decrypted = decryptMessage(encrypted, nonce);
 | |
| 
 | |
|     if (decrypted.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
 | |
|     }
 | |
| 
 | |
|     const QString key = decrypted.value("key").toString();
 | |
|     if (key.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
 | |
|     }
 | |
| 
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     if (key.compare(m_clientPublicKey, Qt::CaseSensitive) == 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 = m_browserService.storeKey((idKey.isEmpty() ? key : idKey));
 | |
|         if (id.isEmpty()) {
 | |
|             return getErrorReply(action, ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED);
 | |
|         }
 | |
| 
 | |
|         m_associated = true;
 | |
|         const QString newNonce = incrementNonce(nonce);
 | |
| 
 | |
|         QJsonObject message = buildMessage(newNonce);
 | |
|         message["hash"] = hash;
 | |
|         message["id"] = id;
 | |
|         return buildResponse(action, message, newNonce);
 | |
|     }
 | |
| 
 | |
|     return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::handleTestAssociate(const QJsonObject& json, const QString& action)
 | |
| {
 | |
|     const QString hash = getDatabaseHash();
 | |
|     const QString nonce = json.value("nonce").toString();
 | |
|     const QString encrypted = json.value("message").toString();
 | |
|     const QJsonObject decrypted = decryptMessage(encrypted, nonce);
 | |
| 
 | |
|     if (decrypted.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
 | |
|     }
 | |
| 
 | |
|     const QString responseKey = decrypted.value("key").toString();
 | |
|     const QString id = decrypted.value("id").toString();
 | |
|     if (responseKey.isEmpty() || id.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_DATABASE_NOT_OPENED);
 | |
|     }
 | |
| 
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     const QString key = m_browserService.getKey(id);
 | |
|     if (key.isEmpty() || key.compare(responseKey, Qt::CaseSensitive) != 0) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
 | |
|     }
 | |
| 
 | |
|     m_associated = true;
 | |
|     const QString newNonce = incrementNonce(nonce);
 | |
| 
 | |
|     QJsonObject message = buildMessage(newNonce);
 | |
|     message["hash"] = hash;
 | |
|     message["id"] = id;
 | |
| 
 | |
|     return buildResponse(action, message, newNonce);
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::handleGetLogins(const QJsonObject& json, const QString& action)
 | |
| {
 | |
|     const QString hash = getDatabaseHash();
 | |
|     const QString nonce = json.value("nonce").toString();
 | |
|     const QString encrypted = json.value("message").toString();
 | |
| 
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     if (!m_associated) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
 | |
|     }
 | |
| 
 | |
|     const QJsonObject decrypted = decryptMessage(encrypted, nonce);
 | |
|     if (decrypted.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
 | |
|     }
 | |
| 
 | |
|     const QString url = decrypted.value("url").toString();
 | |
|     if (url.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_NO_URL_PROVIDED);
 | |
|     }
 | |
| 
 | |
|     const QJsonArray keys = decrypted.value("keys").toArray();
 | |
| 
 | |
|     StringPairList keyList;
 | |
|     for (const QJsonValue val : keys) {
 | |
|         const QJsonObject keyObject = val.toObject();
 | |
|         keyList.push_back(qMakePair(keyObject.value("id").toString(), keyObject.value("key").toString()));
 | |
|     }
 | |
| 
 | |
|     const QString id = decrypted.value("id").toString();
 | |
|     const QString submit = decrypted.value("submitUrl").toString();
 | |
|     const QString auth = decrypted.value("httpAuth").toString();
 | |
|     const bool httpAuth = auth.compare("true", Qt::CaseSensitive) == 0 ? true : false;
 | |
|     const QJsonArray users = m_browserService.findMatchingEntries(id, url, submit, "", keyList, httpAuth);
 | |
| 
 | |
|     if (users.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_NO_LOGINS_FOUND);
 | |
|     }
 | |
| 
 | |
|     const QString newNonce = incrementNonce(nonce);
 | |
| 
 | |
|     QJsonObject message = buildMessage(newNonce);
 | |
|     message["count"] = users.count();
 | |
|     message["entries"] = users;
 | |
|     message["hash"] = hash;
 | |
|     message["id"] = id;
 | |
| 
 | |
|     return buildResponse(action, message, newNonce);
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::handleGeneratePassword(const QJsonObject& json, const QString& action)
 | |
| {
 | |
|     auto nonce = json.value("nonce").toString();
 | |
|     auto password = browserSettings()->generatePassword();
 | |
| 
 | |
|     if (nonce.isEmpty() || password.isEmpty()) {
 | |
|         return QJsonObject();
 | |
|     }
 | |
| 
 | |
|     // For backwards compatibility
 | |
|     password["login"] = password["entropy"];
 | |
| 
 | |
|     QJsonArray arr;
 | |
|     arr.append(password);
 | |
| 
 | |
|     const QString newNonce = incrementNonce(nonce);
 | |
| 
 | |
|     QJsonObject message = buildMessage(newNonce);
 | |
|     message["entries"] = arr;
 | |
| 
 | |
|     return buildResponse(action, message, newNonce);
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::handleSetLogin(const QJsonObject& json, const QString& action)
 | |
| {
 | |
|     const QString hash = getDatabaseHash();
 | |
|     const QString nonce = json.value("nonce").toString();
 | |
|     const QString encrypted = json.value("message").toString();
 | |
| 
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     if (!m_associated) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
 | |
|     }
 | |
| 
 | |
|     const QJsonObject decrypted = decryptMessage(encrypted, nonce);
 | |
|     if (decrypted.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
 | |
|     }
 | |
| 
 | |
|     const QString url = decrypted.value("url").toString();
 | |
|     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 realm;
 | |
| 
 | |
|     BrowserService::ReturnValue result = BrowserService::ReturnValue::Success;
 | |
|     if (uuid.isEmpty()) {
 | |
|         m_browserService.addEntry(id, login, password, url, submitUrl, realm, group, groupUuid);
 | |
|     } else {
 | |
|         result = m_browserService.updateEntry(id, uuid, login, password, url, submitUrl);
 | |
|     }
 | |
| 
 | |
|     const QString newNonce = incrementNonce(nonce);
 | |
| 
 | |
|     QJsonObject message = buildMessage(newNonce);
 | |
|     message["count"] = QJsonValue::Null;
 | |
|     message["entries"] = QJsonValue::Null;
 | |
|     message["error"] = getReturnValue(result);
 | |
|     message["hash"] = hash;
 | |
| 
 | |
|     return buildResponse(action, message, newNonce);
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::handleLockDatabase(const QJsonObject& json, const QString& action)
 | |
| {
 | |
|     const QString hash = getDatabaseHash();
 | |
|     const QString nonce = json.value("nonce").toString();
 | |
|     const QString encrypted = json.value("message").toString();
 | |
|     const QJsonObject decrypted = decryptMessage(encrypted, nonce);
 | |
| 
 | |
|     if (decrypted.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
 | |
|     }
 | |
| 
 | |
|     if (hash.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
 | |
|     }
 | |
| 
 | |
|     QString command = decrypted.value("action").toString();
 | |
|     if (!command.isEmpty() && command.compare("lock-database", Qt::CaseSensitive) == 0) {
 | |
|         QMutexLocker locker(&m_mutex);
 | |
|         m_browserService.lockDatabase();
 | |
| 
 | |
|         const QString newNonce = incrementNonce(nonce);
 | |
|         QJsonObject message = buildMessage(newNonce);
 | |
| 
 | |
|         return buildResponse(action, message, newNonce);
 | |
|     }
 | |
| 
 | |
|     return getErrorReply(action, ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED);
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::handleGetDatabaseGroups(const QJsonObject& json, const QString& action)
 | |
| {
 | |
|     const QString hash = getDatabaseHash();
 | |
|     const QString nonce = json.value("nonce").toString();
 | |
|     const QString encrypted = json.value("message").toString();
 | |
| 
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     if (!m_associated) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
 | |
|     }
 | |
| 
 | |
|     const QJsonObject decrypted = decryptMessage(encrypted, nonce);
 | |
|     if (decrypted.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
 | |
|     }
 | |
| 
 | |
|     QString command = decrypted.value("action").toString();
 | |
|     if (command.isEmpty() || command.compare("get-database-groups", Qt::CaseSensitive) != 0) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
 | |
|     }
 | |
| 
 | |
|     const QJsonObject groups = m_browserService.getDatabaseGroups();
 | |
|     if (groups.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_NO_GROUPS_FOUND);
 | |
|     }
 | |
| 
 | |
|     const QString newNonce = incrementNonce(nonce);
 | |
| 
 | |
|     QJsonObject message = buildMessage(newNonce);
 | |
|     message["groups"] = groups;
 | |
| 
 | |
|     return buildResponse(action, message, newNonce);
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::handleCreateNewGroup(const QJsonObject& json, const QString& action)
 | |
| {
 | |
|     const QString hash = getDatabaseHash();
 | |
|     const QString nonce = json.value("nonce").toString();
 | |
|     const QString encrypted = json.value("message").toString();
 | |
| 
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     if (!m_associated) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_ASSOCIATION_FAILED);
 | |
|     }
 | |
| 
 | |
|     const QJsonObject decrypted = decryptMessage(encrypted, nonce);
 | |
|     if (decrypted.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE);
 | |
|     }
 | |
| 
 | |
|     QString command = decrypted.value("action").toString();
 | |
|     if (command.isEmpty() || command.compare("create-new-group", Qt::CaseSensitive) != 0) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_INCORRECT_ACTION);
 | |
|     }
 | |
| 
 | |
|     QString group = decrypted.value("groupName").toString();
 | |
|     const QJsonObject newGroup = m_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 = incrementNonce(nonce);
 | |
| 
 | |
|     QJsonObject message = buildMessage(newNonce);
 | |
|     message["name"] = newGroup["name"];
 | |
|     message["uuid"] = newGroup["uuid"];
 | |
| 
 | |
|     return buildResponse(action, message, newNonce);
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::getErrorReply(const QString& action, const int errorCode) const
 | |
| {
 | |
|     QJsonObject response;
 | |
|     response["action"] = action;
 | |
|     response["errorCode"] = QString::number(errorCode);
 | |
|     response["error"] = getErrorMessage(errorCode);
 | |
|     return response;
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::buildMessage(const QString& nonce) const
 | |
| {
 | |
|     QJsonObject message;
 | |
|     message["version"] = KEEPASSXC_VERSION;
 | |
|     message["success"] = "true";
 | |
|     message["nonce"] = nonce;
 | |
|     return message;
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::buildResponse(const QString& action, const QJsonObject& message, const QString& nonce)
 | |
| {
 | |
|     QJsonObject response;
 | |
|     QString encryptedMessage = encryptMessage(message, nonce);
 | |
|     if (encryptedMessage.isEmpty()) {
 | |
|         return getErrorReply(action, ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE);
 | |
|     }
 | |
| 
 | |
|     response["action"] = action;
 | |
|     response["message"] = encryptedMessage;
 | |
|     response["nonce"] = nonce;
 | |
|     return response;
 | |
| }
 | |
| 
 | |
| QString BrowserAction::getErrorMessage(const int errorCode) const
 | |
| {
 | |
|     switch (errorCode) {
 | |
|     case ERROR_KEEPASS_DATABASE_NOT_OPENED:
 | |
|         return QObject::tr("Database not opened");
 | |
|     case ERROR_KEEPASS_DATABASE_HASH_NOT_RECEIVED:
 | |
|         return QObject::tr("Database hash not available");
 | |
|     case ERROR_KEEPASS_CLIENT_PUBLIC_KEY_NOT_RECEIVED:
 | |
|         return QObject::tr("Client public key not received");
 | |
|     case ERROR_KEEPASS_CANNOT_DECRYPT_MESSAGE:
 | |
|         return QObject::tr("Cannot decrypt message");
 | |
|     case ERROR_KEEPASS_ACTION_CANCELLED_OR_DENIED:
 | |
|         return QObject::tr("Action cancelled or denied");
 | |
|     case ERROR_KEEPASS_CANNOT_ENCRYPT_MESSAGE:
 | |
|         return QObject::tr("Message encryption failed.");
 | |
|     case ERROR_KEEPASS_ASSOCIATION_FAILED:
 | |
|         return QObject::tr("KeePassXC association failed, try again");
 | |
|     case ERROR_KEEPASS_ENCRYPTION_KEY_UNRECOGNIZED:
 | |
|         return QObject::tr("Encryption key is not recognized");
 | |
|     case ERROR_KEEPASS_INCORRECT_ACTION:
 | |
|         return QObject::tr("Incorrect action");
 | |
|     case ERROR_KEEPASS_EMPTY_MESSAGE_RECEIVED:
 | |
|         return QObject::tr("Empty message received");
 | |
|     case ERROR_KEEPASS_NO_URL_PROVIDED:
 | |
|         return QObject::tr("No URL provided");
 | |
|     case ERROR_KEEPASS_NO_LOGINS_FOUND:
 | |
|         return QObject::tr("No logins found");
 | |
|     case ERROR_KEEPASS_NO_GROUPS_FOUND:
 | |
|         return QObject::tr("No groups found");
 | |
|     case ERROR_KEEPASS_CANNOT_CREATE_NEW_GROUP:
 | |
|         return QObject::tr("Cannot create new group");
 | |
|     default:
 | |
|         return QObject::tr("Unknown error");
 | |
|     }
 | |
| }
 | |
| 
 | |
| QString BrowserAction::getReturnValue(const BrowserService::ReturnValue returnValue) const
 | |
| {
 | |
|     switch (returnValue) {
 | |
|     case BrowserService::ReturnValue::Success:
 | |
|         return QString("success");
 | |
|     case BrowserService::ReturnValue::Error:
 | |
|         return QString("error");
 | |
|     case BrowserService::ReturnValue::Canceled:
 | |
|         return QString("canceled");
 | |
|     }
 | |
|     return QString("error");
 | |
| }
 | |
| 
 | |
| QString BrowserAction::getDatabaseHash()
 | |
| {
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     QByteArray hash =
 | |
|         QCryptographicHash::hash(
 | |
|             (m_browserService.getDatabaseRootUuid() + m_browserService.getDatabaseRecycleBinUuid()).toUtf8(),
 | |
|             QCryptographicHash::Sha256)
 | |
|             .toHex();
 | |
|     return QString(hash);
 | |
| }
 | |
| 
 | |
| QString BrowserAction::encryptMessage(const QJsonObject& message, const QString& nonce)
 | |
| {
 | |
|     if (message.isEmpty() || nonce.isEmpty()) {
 | |
|         return QString();
 | |
|     }
 | |
| 
 | |
|     const QString reply(QJsonDocument(message).toJson());
 | |
|     if (!reply.isEmpty()) {
 | |
|         return encrypt(reply, nonce);
 | |
|     }
 | |
| 
 | |
|     return QString();
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::decryptMessage(const QString& message, const QString& nonce)
 | |
| {
 | |
|     if (message.isEmpty() || nonce.isEmpty()) {
 | |
|         return QJsonObject();
 | |
|     }
 | |
| 
 | |
|     QByteArray ba = decrypt(message, nonce);
 | |
|     if (ba.isEmpty()) {
 | |
|         return QJsonObject();
 | |
|     }
 | |
| 
 | |
|     return getJsonObject(ba);
 | |
| }
 | |
| 
 | |
| QString BrowserAction::encrypt(const QString& plaintext, const QString& nonce)
 | |
| {
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     const QByteArray ma = plaintext.toUtf8();
 | |
|     const QByteArray na = base64Decode(nonce);
 | |
|     const QByteArray ca = base64Decode(m_clientPublicKey);
 | |
|     const QByteArray sa = base64Decode(m_secretKey);
 | |
| 
 | |
|     std::vector<unsigned char> m(ma.cbegin(), ma.cend());
 | |
|     std::vector<unsigned char> n(na.cbegin(), na.cend());
 | |
|     std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
 | |
|     std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
 | |
| 
 | |
|     std::vector<unsigned char> e;
 | |
|     e.resize(NATIVE_MSG_MAX_LENGTH);
 | |
| 
 | |
|     if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
 | |
|         return QString();
 | |
|     }
 | |
| 
 | |
|     if (crypto_box_easy(e.data(), m.data(), m.size(), n.data(), ck.data(), sk.data()) == 0) {
 | |
|         QByteArray res = getQByteArray(e.data(), (crypto_box_MACBYTES + ma.length()));
 | |
|         return res.toBase64();
 | |
|     }
 | |
| 
 | |
|     return QString();
 | |
| }
 | |
| 
 | |
| QByteArray BrowserAction::decrypt(const QString& encrypted, const QString& nonce)
 | |
| {
 | |
|     QMutexLocker locker(&m_mutex);
 | |
|     const QByteArray ma = base64Decode(encrypted);
 | |
|     const QByteArray na = base64Decode(nonce);
 | |
|     const QByteArray ca = base64Decode(m_clientPublicKey);
 | |
|     const QByteArray sa = base64Decode(m_secretKey);
 | |
| 
 | |
|     std::vector<unsigned char> m(ma.cbegin(), ma.cend());
 | |
|     std::vector<unsigned char> n(na.cbegin(), na.cend());
 | |
|     std::vector<unsigned char> ck(ca.cbegin(), ca.cend());
 | |
|     std::vector<unsigned char> sk(sa.cbegin(), sa.cend());
 | |
| 
 | |
|     std::vector<unsigned char> d;
 | |
|     d.resize(NATIVE_MSG_MAX_LENGTH);
 | |
| 
 | |
|     if (m.empty() || n.empty() || ck.empty() || sk.empty()) {
 | |
|         return QByteArray();
 | |
|     }
 | |
| 
 | |
|     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();
 | |
| }
 | |
| 
 | |
| QString BrowserAction::getBase64FromKey(const uchar* array, const uint len)
 | |
| {
 | |
|     return getQByteArray(array, len).toBase64();
 | |
| }
 | |
| 
 | |
| QByteArray BrowserAction::getQByteArray(const uchar* array, const uint len) const
 | |
| {
 | |
|     QByteArray qba;
 | |
|     qba.reserve(len);
 | |
|     for (uint i = 0; i < len; ++i) {
 | |
|         qba.append(static_cast<char>(array[i]));
 | |
|     }
 | |
|     return qba;
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::getJsonObject(const uchar* pArray, const uint len) const
 | |
| {
 | |
|     QByteArray arr = getQByteArray(pArray, len);
 | |
|     QJsonParseError err;
 | |
|     QJsonDocument doc(QJsonDocument::fromJson(arr, &err));
 | |
|     return doc.object();
 | |
| }
 | |
| 
 | |
| QJsonObject BrowserAction::getJsonObject(const QByteArray& ba) const
 | |
| {
 | |
|     QJsonParseError err;
 | |
|     QJsonDocument doc(QJsonDocument::fromJson(ba, &err));
 | |
|     return doc.object();
 | |
| }
 | |
| 
 | |
| QByteArray BrowserAction::base64Decode(const QString& str)
 | |
| {
 | |
|     return QByteArray::fromBase64(str.toUtf8());
 | |
| }
 | |
| 
 | |
| QString BrowserAction::incrementNonce(const QString& nonce)
 | |
| {
 | |
|     const QByteArray nonceArray = base64Decode(nonce);
 | |
|     std::vector<unsigned char> n(nonceArray.cbegin(), nonceArray.cend());
 | |
| 
 | |
|     sodium_increment(n.data(), n.size());
 | |
|     return getQByteArray(n.data(), n.size()).toBase64();
 | |
| }
 |