mirror of
https://github.com/keepassxreboot/keepassxc.git
synced 2025-01-28 07:17:08 -05:00
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
667 lines
23 KiB
C++
667 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();
|
|
}
|