amnezia-client/client/secure_qsettings.cpp
lunardunno 059257fc58
Merge branch 'dev' into check_sudo_permissions (#1441)
* refactoring: improved the performance of secure_settings

* bugfix: fixed textFields on PageSetupWizardCredentials

* bugfix: fixed scrolling by keys on PageSettingsApiServerInfo

* chore: hide site links for ios (#1374)

* chore: fixed log output with split tunneling info

* chore: hide "open logs folder" button for mobule platforms

* chore: fixed again log output with split tunneling info

* chore: bump version

* Install apparmor (#1379)

Install apparmor

* chore: returned the backup page for androidTV

* Enable PFS for Windows IKEv2

* refactoring: moved api info pages from ServerInfo

* refactoring: moved gateway interaction functions to a separate class

* bugfix: fixed storeEndpoint parsing

* chore: returned links for mobile platforms

* Update VPN protocol descriptions

* Update VPN description texts

* feature: added pages for subscription settings feature

* feature: added page for export api native configs

* feature: added error handling and minor ui fixes

* refactor: update ios build configuration to use automatic code signing and prebuilt OpenVPNAdapter framework

* feat: remove OpenVPNAdapter submodule

* feat: remove ios openvpn script and associated cmake configuration

* Update README.md

* Update README_RU.md

* Update README.md

fix link

* feature: added share vpn key to subscription settings page

* bugfix: fixed possible crush on android

* add timeouts in ipc client init

* apply timeouts only for Windows

* apply format to file

* refactoring: simplified the validity check of the config before connection

- improved project structure

* bugfix: fixed visability of share drawer

* feature: added 409 error handling from server response

* chore: fixed android build

* chore: fixed qr code display

* Rewrite timeouts using waitForSource

* feature: added error messages handler

* feature: added issued configs info parsing

* feature: added functionality to revoke api configs

* chore: added links to instructions

* chore: fixed qr code with vpnkey processing

* chore: fixed native config post processing

* chore: added link to android tv instruction

* change node to IpcProcessTun2SocksReplica

* chore: minor ui fixes

* Update Windows OpenSSL  (#1426)

* Update Windows OpenSSL to 3.0.16 and add shared library for QSslSocket plugin

* chore: update link to submodule 3rd-prebuild

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>

* chore: added 404 handling for revoke configs

- added revoke before remove api server for premium v2

* chore: added log to see proxy decrypt errors

* chore: minor ui fix

* chore: bump version

* bugfix: fixed mobile controllers initialization (#1436)

* bugfix: fixed mobile controllers initialization

* chore: bump version

* Merge pull request #1440 from amnezia-vpn/feature/subscription-settings-page

feature/subscription settings page

---------

Co-authored-by: vladimir.kuznetsov <nethiuswork@gmail.com>
Co-authored-by: pokamest <pokamest@gmail.com>
Co-authored-by: Mykola Baibuz <mykola.baibuz@gmail.com>
Co-authored-by: Yaroslav Yashin <yaroslav.yashin@gmail.com>
Co-authored-by: KsZnak <ksu@amnezia.org>
Co-authored-by: Cyril Anisimov <CyAn84@gmail.com>
2025-03-01 16:08:52 +04:00

304 lines
8.3 KiB
C++

#include "secure_qsettings.h"
#include "QAead.h"
#include "QBlockCipher.h"
#include "utilities.h"
#include <QDataStream>
#include <QDebug>
#include <QEventLoop>
#include <QIODevice>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRandomGenerator>
#include <QSharedPointer>
#include <QTimer>
using namespace QKeychain;
namespace {
constexpr const char *settingsKeyTag = "settingsKeyTag";
constexpr const char *settingsIvTag = "settingsIvTag";
constexpr const char *keyChainName = "AmneziaVPN-Keychain";
}
SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent)
: QObject { parent }, m_settings(organization, application, parent), encryptedKeys({ "Servers/serversList" })
{
bool encrypted = m_settings.value("Conf/encrypted").toBool();
// convert settings to encrypted for if updated to >= 2.1.0
if (encryptionRequired() && !encrypted) {
for (const QString &key : m_settings.allKeys()) {
if (encryptedKeys.contains(key)) {
const QVariant &val = value(key);
setValue(key, val);
}
}
m_settings.setValue("Conf/encrypted", true);
m_settings.sync();
}
}
QVariant SecureQSettings::value(const QString &key, const QVariant &defaultValue) const
{
QMutexLocker locker(&mutex);
if (m_cache.contains(key)) {
return m_cache.value(key);
}
if (!m_settings.contains(key))
return defaultValue;
QVariant retVal;
// check if value is not encrypted, v. < 2.0.x
retVal = m_settings.value(key);
if (retVal.isValid()) {
if (retVal.userType() == QMetaType::QByteArray && retVal.toByteArray().mid(0, magicString.size()) == magicString) {
if (getEncKey().isEmpty() || getEncIv().isEmpty()) {
qCritical() << "SecureQSettings::setValue Decryption requested, but key is empty";
return {};
}
QByteArray encryptedValue = retVal.toByteArray().mid(magicString.size());
QByteArray decryptedValue = decryptText(encryptedValue);
QDataStream ds(&decryptedValue, QIODevice::ReadOnly);
ds >> retVal;
if (!retVal.isValid()) {
qWarning() << "SecureQSettings::value settings decryption failed";
retVal = QVariant();
}
}
} else {
qWarning() << "SecureQSettings::value invalid QVariant value";
retVal = QVariant();
}
m_cache.insert(key, retVal);
return retVal;
}
void SecureQSettings::setValue(const QString &key, const QVariant &value)
{
QMutexLocker locker(&mutex);
if (encryptionRequired() && encryptedKeys.contains(key)) {
if (!getEncKey().isEmpty() && !getEncIv().isEmpty()) {
QByteArray decryptedValue;
{
QDataStream ds(&decryptedValue, QIODevice::WriteOnly);
ds << value;
}
QByteArray encryptedValue = encryptText(decryptedValue);
m_settings.setValue(key, magicString + encryptedValue);
} else {
qCritical() << "SecureQSettings::setValue Encryption required, but key is empty";
return;
}
} else {
m_settings.setValue(key, value);
}
m_cache.insert(key, value);
sync();
}
void SecureQSettings::remove(const QString &key)
{
QMutexLocker locker(&mutex);
m_settings.remove(key);
m_cache.remove(key);
sync();
}
void SecureQSettings::sync()
{
m_settings.sync();
}
QByteArray SecureQSettings::backupAppConfig() const
{
QJsonObject cfg;
const auto needToBackup = [this](const auto &key) {
for (const auto &item : m_fieldsToBackup)
{
if (key == "Conf/installationUuid")
{
return false;
}
if (key.startsWith(item))
{
return true;
}
}
return false;
};
for (const QString &key : m_settings.allKeys()) {
if (!needToBackup(key))
{
continue;
}
cfg.insert(key, QJsonValue::fromVariant(value(key)));
}
return QJsonDocument(cfg).toJson();
}
bool SecureQSettings::restoreAppConfig(const QByteArray &json)
{
QJsonObject cfg = QJsonDocument::fromJson(json).object();
if (cfg.isEmpty())
return false;
for (const QString &key : cfg.keys()) {
if (key == "Conf/installationUuid") {
continue;
}
setValue(key, cfg.value(key).toVariant());
}
sync();
return true;
}
QByteArray SecureQSettings::encryptText(const QByteArray &value) const
{
QSimpleCrypto::QBlockCipher cipher;
QByteArray result;
try {
result = cipher.encryptAesBlockCipher(value, getEncKey(), getEncIv());
} catch (...) { // todo change error handling in QSimpleCrypto?
qCritical() << "error when encrypting the settings value";
}
return result;
}
QByteArray SecureQSettings::decryptText(const QByteArray &ba) const
{
QSimpleCrypto::QBlockCipher cipher;
QByteArray result;
try {
result = cipher.decryptAesBlockCipher(ba, getEncKey(), getEncIv());
} catch (...) { // todo change error handling in QSimpleCrypto?
qCritical() << "error when decrypting the settings value";
}
return result;
}
bool SecureQSettings::encryptionRequired() const
{
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
// QtKeyChain failing on Linux
return false;
#endif
return true;
}
QByteArray SecureQSettings::getEncKey() const
{
// load keys from system key storage
m_key = getSecTag(settingsKeyTag);
if (m_key.isEmpty()) {
// Create new key
QSimpleCrypto::QBlockCipher cipher;
QByteArray key = cipher.generatePrivateSalt(32);
if (key.isEmpty()) {
qCritical() << "SecureQSettings::getEncKey Unable to generate new enc key";
}
setSecTag(settingsKeyTag, key);
// check
m_key = getSecTag(settingsKeyTag);
if (key != m_key) {
qCritical() << "SecureQSettings::getEncKey Unable to store key in keychain" << key.size() << m_key.size();
return {};
}
}
return m_key;
}
QByteArray SecureQSettings::getEncIv() const
{
// load keys from system key storage
m_iv = getSecTag(settingsIvTag);
if (m_iv.isEmpty()) {
// Create new IV
QSimpleCrypto::QBlockCipher cipher;
QByteArray iv = cipher.generatePrivateSalt(32);
if (iv.isEmpty()) {
qCritical() << "SecureQSettings::getEncIv Unable to generate new enc IV";
}
setSecTag(settingsIvTag, iv);
// check
m_iv = getSecTag(settingsIvTag);
if (iv != m_iv) {
qCritical() << "SecureQSettings::getEncIv Unable to store IV in keychain" << iv.size() << m_iv.size();
return {};
}
}
return m_iv;
}
QByteArray SecureQSettings::getSecTag(const QString &tag)
{
auto job = QSharedPointer<ReadPasswordJob>(new ReadPasswordJob(keyChainName), &QObject::deleteLater);
job->setAutoDelete(false);
job->setKey(tag);
QEventLoop loop;
job->connect(job.data(), &ReadPasswordJob::finished, job.data(), [&loop]() { loop.quit(); });
job->start();
loop.exec();
if (job->error()) {
qCritical() << "SecureQSettings::getSecTag Error:" << job->errorString();
}
return job->binaryData();
}
void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data)
{
auto job = QSharedPointer<WritePasswordJob>(new WritePasswordJob(keyChainName), &QObject::deleteLater);
job->setAutoDelete(false);
job->setKey(tag);
job->setBinaryData(data);
QEventLoop loop;
QTimer::singleShot(1000, &loop, SLOT(quit()));
job->connect(job.data(), &WritePasswordJob::finished, job.data(), [&loop]() { loop.quit(); });
job->start();
loop.exec();
if (job->error()) {
qCritical() << "SecureQSettings::setSecTag Error:" << job->errorString();
}
}
void SecureQSettings::clearSettings()
{
QMutexLocker locker(&mutex);
m_settings.clear();
m_cache.clear();
sync();
}