mirror of
https://github.com/amnezia-vpn/amnezia-client.git
synced 2026-05-08 14:33:23 +00:00
update: changed logic, added to settingsController
This commit is contained in:
@@ -1,137 +0,0 @@
|
||||
#include "protection.h"
|
||||
#include <QFile>
|
||||
#include <QCryptographicHash>
|
||||
#include <QDebug>
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <stdexcept>
|
||||
|
||||
static constexpr int KEY_SIZE = 32;
|
||||
static constexpr int SALT_SIZE = 16;
|
||||
static constexpr int IV_SIZE = 16;
|
||||
static constexpr int ITERATIONS = 100000;
|
||||
|
||||
Protector::Protector(QObject *parent) : QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QByteArray Protector::generateSalt()
|
||||
{
|
||||
QByteArray salt(SALT_SIZE, 0);
|
||||
RAND_bytes(reinterpret_cast<unsigned char *>(salt.data()), SALT_SIZE);
|
||||
return salt;
|
||||
}
|
||||
|
||||
QByteArray Protector::deriveKey(const QString &password, const QByteArray &salt)
|
||||
{
|
||||
QByteArray key(KEY_SIZE, 0);
|
||||
if (!PKCS5_PBKDF2_HMAC(password.toUtf8().constData(), password.size(),
|
||||
reinterpret_cast<const unsigned char *>(salt.constData()), salt.size(), ITERATIONS,
|
||||
EVP_sha256(), KEY_SIZE, reinterpret_cast<unsigned char *>(key.data()))) {
|
||||
throw std::runtime_error("PBKDF2 key derivation failed");
|
||||
}
|
||||
return key;
|
||||
}
|
||||
|
||||
void Protector::encryptFile(const QString &filePath, const QString &password)
|
||||
{
|
||||
try {
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
throw std::runtime_error("Cannot open file for reading");
|
||||
|
||||
QByteArray plain = file.readAll();
|
||||
file.close();
|
||||
|
||||
QByteArray salt = generateSalt();
|
||||
QByteArray iv(IV_SIZE, 0);
|
||||
RAND_bytes(reinterpret_cast<unsigned char *>(iv.data()), IV_SIZE);
|
||||
QByteArray key = deriveKey(password, salt);
|
||||
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||
QByteArray encrypted(plain.size() + EVP_MAX_BLOCK_LENGTH, 0);
|
||||
int len = 0, total = 0;
|
||||
|
||||
if (!EVP_EncryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<const unsigned char *>(key.constData()),
|
||||
reinterpret_cast<const unsigned char *>(iv.constData())))
|
||||
throw std::runtime_error("EncryptInit failed");
|
||||
|
||||
if (!EVP_EncryptUpdate(ctx, reinterpret_cast<unsigned char *>(encrypted.data()), &len,
|
||||
reinterpret_cast<const unsigned char *>(plain.constData()), plain.size()))
|
||||
throw std::runtime_error("EncryptUpdate failed");
|
||||
|
||||
total = len;
|
||||
|
||||
if (!EVP_EncryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(encrypted.data()) + len, &len))
|
||||
throw std::runtime_error("EncryptFinal failed");
|
||||
|
||||
total += len;
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
encrypted.truncate(total);
|
||||
|
||||
QByteArray finalData = salt + iv + encrypted;
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||
throw std::runtime_error("Cannot open file for writing");
|
||||
|
||||
file.write(finalData);
|
||||
file.close();
|
||||
|
||||
qInfo() << "File encrypted successfully:" << filePath;
|
||||
} catch (const std::exception &e) {
|
||||
qWarning() << "Encryption failed:" << e.what();
|
||||
}
|
||||
}
|
||||
|
||||
void Protector::decryptFile(const QString &filePath, const QString &password)
|
||||
{
|
||||
try {
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly))
|
||||
throw std::runtime_error("Cannot open file for reading");
|
||||
|
||||
QByteArray data = file.readAll();
|
||||
file.close();
|
||||
|
||||
if (data.size() < SALT_SIZE + IV_SIZE)
|
||||
throw std::runtime_error("Corrupted or invalid encrypted file");
|
||||
|
||||
QByteArray salt = data.left(SALT_SIZE);
|
||||
QByteArray iv = data.mid(SALT_SIZE, IV_SIZE);
|
||||
QByteArray cipher = data.mid(SALT_SIZE + IV_SIZE);
|
||||
|
||||
QByteArray key = deriveKey(password, salt);
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||
QByteArray plain(cipher.size(), 0);
|
||||
int len = 0, total = 0;
|
||||
|
||||
if (!EVP_DecryptInit_ex(ctx, EVP_aes_256_cbc(), nullptr, reinterpret_cast<const unsigned char *>(key.constData()),
|
||||
reinterpret_cast<const unsigned char *>(iv.constData())))
|
||||
throw std::runtime_error("DecryptInit failed");
|
||||
|
||||
if (!EVP_DecryptUpdate(ctx, reinterpret_cast<unsigned char *>(plain.data()), &len,
|
||||
reinterpret_cast<const unsigned char *>(cipher.constData()), cipher.size()))
|
||||
throw std::runtime_error("DecryptUpdate failed");
|
||||
|
||||
total = len;
|
||||
|
||||
if (!EVP_DecryptFinal_ex(ctx, reinterpret_cast<unsigned char *>(plain.data()) + len, &len)) {
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
throw std::runtime_error("Incorrect password or corrupted file");
|
||||
}
|
||||
|
||||
total += len;
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
plain.truncate(total);
|
||||
|
||||
if (!file.open(QIODevice::WriteOnly | QIODevice::Truncate))
|
||||
throw std::runtime_error("Cannot open file for writing");
|
||||
|
||||
file.write(plain);
|
||||
file.close();
|
||||
|
||||
qInfo() << "File decrypted successfully:" << filePath;
|
||||
} catch (const std::exception &e) {
|
||||
qWarning() << "Decryption failed:" << e.what();
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
#ifndef PROTECTOR_H
|
||||
#define PROTECTOR_H
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
|
||||
class Protector : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit Protector(QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE static void encryptFile(const QString &filePath, const QString &password);
|
||||
Q_INVOKABLE static void decryptFile(const QString &filePath, const QString &password);
|
||||
|
||||
private:
|
||||
static QByteArray deriveKey(const QString &password, const QByteArray &salt);
|
||||
static QByteArray generateSalt();
|
||||
};
|
||||
|
||||
#endif // PROTECTOR_H
|
||||
@@ -12,6 +12,11 @@
|
||||
#include <QRandomGenerator>
|
||||
#include <QSharedPointer>
|
||||
#include <QTimer>
|
||||
#include <QFile>
|
||||
|
||||
#include <openssl/evp.h>
|
||||
#include <openssl/rand.h>
|
||||
#include <openssl/err.h>
|
||||
|
||||
using namespace QKeychain;
|
||||
|
||||
@@ -19,6 +24,11 @@ namespace {
|
||||
constexpr const char *settingsKeyTag = "settingsKeyTag";
|
||||
constexpr const char *settingsIvTag = "settingsIvTag";
|
||||
constexpr const char *keyChainName = "AmneziaVPN-Keychain";
|
||||
|
||||
constexpr int SALT_LEN = 16;
|
||||
constexpr int IV_LEN = 16;
|
||||
constexpr int KEY_LEN = 32;
|
||||
constexpr int PBKDF2_ITER = 100000;
|
||||
}
|
||||
|
||||
SecureQSettings::SecureQSettings(const QString &organization, const QString &application, QObject *parent)
|
||||
@@ -295,6 +305,202 @@ void SecureQSettings::setSecTag(const QString &tag, const QByteArray &data)
|
||||
}
|
||||
}
|
||||
|
||||
void SecureQSettings::setPassword(const QString &pwd)
|
||||
{
|
||||
m_password = pwd;
|
||||
}
|
||||
|
||||
void SecureQSettings::setHint(const QString &hint)
|
||||
{
|
||||
m_hint = hint;
|
||||
}
|
||||
|
||||
QString SecureQSettings::getPassword() const
|
||||
{
|
||||
return m_password;
|
||||
}
|
||||
|
||||
QString SecureQSettings::getHint() const
|
||||
{
|
||||
return m_hint;
|
||||
}
|
||||
|
||||
static QString opensslErrString()
|
||||
{
|
||||
unsigned long e = ERR_get_error();
|
||||
if (!e)
|
||||
return QStringLiteral("Unknown OpenSSL error");
|
||||
char buf[256];
|
||||
ERR_error_string_n(e, buf, sizeof(buf));
|
||||
return QString::fromUtf8(buf);
|
||||
}
|
||||
|
||||
static bool deriveKey(const QByteArray &password, const QByteArray &salt, QByteArray &outKey, QString *err)
|
||||
{
|
||||
outKey.resize(KEY_LEN);
|
||||
const unsigned char *pw = reinterpret_cast<const unsigned char *>(password.constData());
|
||||
const unsigned char *s = reinterpret_cast<const unsigned char *>(salt.constData());
|
||||
int ok = PKCS5_PBKDF2_HMAC(reinterpret_cast<const char *>(pw), password.size(), s, salt.size(), PBKDF2_ITER,
|
||||
EVP_sha256(), KEY_LEN, reinterpret_cast<unsigned char *>(outKey.data()));
|
||||
if (!ok) {
|
||||
if (err)
|
||||
*err = opensslErrString();
|
||||
}
|
||||
return ok == 1;
|
||||
}
|
||||
|
||||
static bool aesCrypt(const QByteArray &in, const QByteArray &key, const QByteArray &iv, QByteArray &out, bool encrypt,
|
||||
QString *err)
|
||||
{
|
||||
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
|
||||
if (!ctx) {
|
||||
if (err)
|
||||
*err = "EVP_CIPHER_CTX_new failed";
|
||||
return false;
|
||||
}
|
||||
const EVP_CIPHER *cipher = EVP_aes_256_cbc();
|
||||
if (1
|
||||
!= EVP_CipherInit_ex(ctx, cipher, nullptr, reinterpret_cast<const unsigned char *>(key.constData()),
|
||||
reinterpret_cast<const unsigned char *>(iv.constData()), encrypt ? 1 : 0)) {
|
||||
if (err)
|
||||
*err = opensslErrString();
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return false;
|
||||
}
|
||||
|
||||
out.clear();
|
||||
out.resize(in.size() + EVP_CIPHER_block_size(cipher));
|
||||
int outlen1 = 0;
|
||||
if (1
|
||||
!= EVP_CipherUpdate(ctx, reinterpret_cast<unsigned char *>(out.data()), &outlen1,
|
||||
reinterpret_cast<const unsigned char *>(in.constData()), in.size())) {
|
||||
if (err)
|
||||
*err = opensslErrString();
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return false;
|
||||
}
|
||||
int outlen2 = 0;
|
||||
if (1 != EVP_CipherFinal_ex(ctx, reinterpret_cast<unsigned char *>(out.data()) + outlen1, &outlen2)) {
|
||||
if (err)
|
||||
*err = opensslErrString();
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return false;
|
||||
}
|
||||
out.resize(outlen1 + outlen2);
|
||||
EVP_CIPHER_CTX_free(ctx);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SecureQSettings::encryptFile(const QString &filePath, const QString &password, QString *error) const
|
||||
{
|
||||
QFile f(filePath);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
if (error)
|
||||
*error = QStringLiteral("Cannot open file for read: %1").arg(f.errorString());
|
||||
return false;
|
||||
}
|
||||
QByteArray plain = f.readAll();
|
||||
f.close();
|
||||
|
||||
if (plain.startsWith(magicString)) {
|
||||
if (error)
|
||||
*error = QStringLiteral("File already encrypted (magic found)");
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray salt(SALT_LEN, 0);
|
||||
QByteArray iv(IV_LEN, 0);
|
||||
QByteArray key;
|
||||
QByteArray cipher;
|
||||
QByteArray out;
|
||||
|
||||
if (1 != RAND_bytes(reinterpret_cast<unsigned char *>(salt.data()), SALT_LEN)
|
||||
|| 1 != RAND_bytes(reinterpret_cast<unsigned char *>(iv.data()), IV_LEN)) {
|
||||
if (error)
|
||||
*error = opensslErrString();
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!deriveKey(password.toUtf8(), salt, key, error))
|
||||
return false;
|
||||
|
||||
if (!aesCrypt(plain, key, iv, cipher, true, error))
|
||||
return false;
|
||||
|
||||
out.reserve(magicString.size() + SALT_LEN + IV_LEN + cipher.size());
|
||||
out += magicString;
|
||||
out += salt;
|
||||
out += iv;
|
||||
out += cipher;
|
||||
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
if (error)
|
||||
*error = QStringLiteral("Cannot open file for write: %1").arg(f.errorString());
|
||||
return false;
|
||||
}
|
||||
qint64 written = f.write(out);
|
||||
f.close();
|
||||
if (written != out.size()) {
|
||||
if (error)
|
||||
*error = QStringLiteral("Write failed or incomplete");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SecureQSettings::decryptFile(const QString &filePath, const QString &password, QString *error) const
|
||||
{
|
||||
QFile f(filePath);
|
||||
if (!f.open(QIODevice::ReadOnly)) {
|
||||
if (error)
|
||||
*error = QStringLiteral("Cannot open file for read: %1").arg(f.errorString());
|
||||
return false;
|
||||
}
|
||||
QByteArray blob = f.readAll();
|
||||
f.close();
|
||||
|
||||
if (!blob.startsWith(magicString)) {
|
||||
if (error)
|
||||
*error = QStringLiteral("File is not recognized as encrypted (magic missing)");
|
||||
return false;
|
||||
}
|
||||
|
||||
int pos = magicString.size();
|
||||
if (blob.size() < pos + SALT_LEN + IV_LEN) {
|
||||
if (error)
|
||||
*error = QStringLiteral("Encrypted file too small / corrupted");
|
||||
return false;
|
||||
}
|
||||
|
||||
QByteArray salt = blob.mid(pos, SALT_LEN);
|
||||
pos += SALT_LEN;
|
||||
QByteArray iv = blob.mid(pos, IV_LEN);
|
||||
pos += IV_LEN;
|
||||
QByteArray cipher = blob.mid(pos);
|
||||
QByteArray key;
|
||||
QByteArray plain;
|
||||
|
||||
if (!deriveKey(password.toUtf8(), salt, key, error))
|
||||
return false;
|
||||
|
||||
if (!aesCrypt(cipher, key, iv, plain, false, error))
|
||||
return false;
|
||||
|
||||
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
|
||||
if (error)
|
||||
*error = QStringLiteral("Cannot open file for write: %1").arg(f.errorString());
|
||||
return false;
|
||||
}
|
||||
qint64 written = f.write(plain);
|
||||
f.close();
|
||||
if (written != plain.size()) {
|
||||
if (error)
|
||||
*error = QStringLiteral("Write failed or incomplete");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void SecureQSettings::clearSettings()
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
|
||||
@@ -16,6 +16,9 @@ public:
|
||||
explicit SecureQSettings(const QString &organization, const QString &application = QString(),
|
||||
QObject *parent = nullptr);
|
||||
|
||||
Q_INVOKABLE bool encryptFile(const QString &filePath, const QString &password, QString *error = nullptr) const;
|
||||
Q_INVOKABLE bool decryptFile(const QString &filePath, const QString &password, QString *error = nullptr) const;
|
||||
|
||||
Q_INVOKABLE QVariant value(const QString &key, const QVariant &defaultValue = QVariant()) const;
|
||||
Q_INVOKABLE void setValue(const QString &key, const QVariant &value);
|
||||
void remove(const QString &key);
|
||||
@@ -35,6 +38,12 @@ public:
|
||||
static QByteArray getSecTag(const QString &tag);
|
||||
static void setSecTag(const QString &tag, const QByteArray &data);
|
||||
|
||||
void setPassword(const QString &pwd);
|
||||
void setHint(const QString &hint);
|
||||
|
||||
QString getPassword() const;
|
||||
QString getHint() const;
|
||||
|
||||
void clearSettings();
|
||||
|
||||
private:
|
||||
@@ -48,6 +57,9 @@ private:
|
||||
"Conf/", "Servers/",
|
||||
};
|
||||
|
||||
mutable QString m_password;
|
||||
mutable QString m_hint;
|
||||
|
||||
mutable QByteArray m_key;
|
||||
mutable QByteArray m_iv;
|
||||
|
||||
|
||||
@@ -94,6 +94,33 @@ public:
|
||||
setValue("Conf/startMinimized", enabled);
|
||||
}
|
||||
|
||||
bool isFileEncryption() const
|
||||
{
|
||||
return value("Sec/fileEncryption", false).toBool();
|
||||
}
|
||||
void setFileEncryption(bool enabled)
|
||||
{
|
||||
setValue("Sec/fileEncryption", enabled);
|
||||
}
|
||||
|
||||
QString getPassword() const
|
||||
{
|
||||
return m_settings.getPassword();
|
||||
}
|
||||
void setPassword(const QString &pwd)
|
||||
{
|
||||
m_settings.setPassword(pwd);
|
||||
}
|
||||
|
||||
QString getHint() const
|
||||
{
|
||||
return m_settings.getHint();
|
||||
}
|
||||
void setHint(const QString &hint)
|
||||
{
|
||||
m_settings.setHint(hint);
|
||||
}
|
||||
|
||||
bool isSaveLogs() const
|
||||
{
|
||||
return value("Conf/saveLogs", false).toBool();
|
||||
|
||||
@@ -296,6 +296,36 @@ void SettingsController::toggleStartMinimized(bool enable)
|
||||
emit startMinimizedChanged();
|
||||
}
|
||||
|
||||
bool SettingsController::isFileEncryptionEnabled()
|
||||
{
|
||||
return m_settings->isFileEncryption();
|
||||
}
|
||||
|
||||
void SettingsController::toggleFileEncryption(bool enable)
|
||||
{
|
||||
m_settings->setFileEncryption(enable);
|
||||
}
|
||||
|
||||
void SettingsController::setPassword(QString pwd)
|
||||
{
|
||||
m_settings->setPassword(pwd);
|
||||
}
|
||||
|
||||
QString SettingsController::getPassword()
|
||||
{
|
||||
return m_settings->getPassword();
|
||||
}
|
||||
|
||||
void SettingsController::setHint(QString hint)
|
||||
{
|
||||
m_settings->setHint(hint);
|
||||
}
|
||||
|
||||
QString SettingsController::getHint()
|
||||
{
|
||||
return m_settings->getHint();
|
||||
}
|
||||
|
||||
bool SettingsController::isScreenshotsEnabled()
|
||||
{
|
||||
return m_settings->isScreenshotsEnabled();
|
||||
|
||||
@@ -70,6 +70,14 @@ public slots:
|
||||
bool isStartMinimizedEnabled();
|
||||
void toggleStartMinimized(bool enable);
|
||||
|
||||
bool isFileEncryptionEnabled();
|
||||
void toggleFileEncryption(bool enable);
|
||||
|
||||
void setPassword(QString pwd);
|
||||
QString getPassword();
|
||||
void setHint(QString hint);
|
||||
QString getHint();
|
||||
|
||||
bool isScreenshotsEnabled();
|
||||
void toggleScreenshotsEnabled(bool enable);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user