Files
amnezia-client/client/ui/controllers/systemController.cpp
T

508 lines
14 KiB
C++
Raw Normal View History

2023-09-06 22:20:59 +05:00
#include "systemController.h"
#include <QDebug>
2023-09-06 22:20:59 +05:00
#include <QDesktopServices>
#include <QDir>
#include <QEventLoop>
#include <QFile>
#include <QFileInfo>
#include <QQuickItem>
#include <QStandardPaths>
#include <QUrl>
2023-09-09 01:29:28 +05:00
#include <QtConcurrent>
2023-09-06 22:20:59 +05:00
#include <openssl/err.h>
#include <openssl/evp.h>
#include <openssl/rand.h>
namespace
{
constexpr int SALT_LEN = 16;
2026-03-19 14:01:46 +02:00
constexpr int IV_LEN = 12;
constexpr int KEY_LEN = 32;
2026-03-17 16:15:53 +02:00
constexpr int TAG_LEN = 16;
2026-03-19 14:01:46 +02:00
constexpr int PBKDF2_ITER = 100000;
2026-03-17 16:15:53 +02:00
const QByteArray magicString { "EncData" };
}
2023-09-06 22:20:59 +05:00
#ifdef Q_OS_ANDROID
#include "platforms/android/android_controller.h"
#endif
#if defined(Q_OS_IOS) || defined(MACOS_NE)
#include "platforms/ios/ios_controller.h"
2023-09-06 22:20:59 +05:00
#include <CoreFoundation/CoreFoundation.h>
#endif
SystemController::SystemController(QObject *parent)
: QObject(parent)
2023-09-06 22:20:59 +05:00
{
}
bool SystemController::saveFile(const QString &fileName, const QString &data)
2023-09-06 22:20:59 +05:00
{
#if defined Q_OS_ANDROID
AndroidController::instance()->saveFile(fileName, data);
return true;
#endif
return saveFile(fileName, data.toUtf8());
}
bool SystemController::saveFile(const QString &fileName, const QByteArray &data)
{
#if defined Q_OS_ANDROID
AndroidController::instance()->saveFile(fileName, QString::fromUtf8(data));
return true;
2023-09-06 22:20:59 +05:00
#endif
#ifdef Q_OS_IOS
QUrl fileUrl = QDir::tempPath() + "/" + fileName;
QFile file(fileUrl.toString());
#else
QFile file(fileName);
2023-09-06 22:20:59 +05:00
#endif
if (!file.open(QIODevice::WriteOnly)) {
qWarning() << "SystemController::saveFile: cannot open" << fileName;
return false;
}
if (file.write(data) != data.size()) {
qWarning() << "SystemController::saveFile: write failed" << fileName;
file.close();
return false;
}
2023-09-06 22:20:59 +05:00
file.close();
#ifdef Q_OS_IOS
QStringList filesToSend;
filesToSend.append(fileUrl.toString());
return IosController::Instance()->shareText(filesToSend);
2023-09-06 22:20:59 +05:00
#else
QFileInfo fi(fileName);
2024-05-25 12:57:48 +03:00
#ifdef Q_OS_MAC
const auto url = "file://" + fi.absoluteDir().absolutePath();
#else
const auto url = fi.absoluteDir().absolutePath();
#endif
#ifndef MACOS_NE
2024-05-25 12:57:48 +03:00
QDesktopServices::openUrl(url);
2023-09-06 22:20:59 +05:00
#endif
return true;
#endif
2023-09-06 22:20:59 +05:00
}
2024-12-31 04:16:52 +01:00
bool SystemController::readFile(const QString &fileName, QByteArray &data)
{
#ifdef Q_OS_ANDROID
int fd = AndroidController::instance()->getFd(fileName);
if (fd == -1) return false;
QFile file;
if(!file.open(fd, QIODevice::ReadOnly)) return false;
data = file.readAll();
AndroidController::instance()->closeFd();
#else
QFile file(fileName);
if (!file.open(QIODevice::ReadOnly)) return false;
data = file.readAll();
#endif
return true;
}
bool SystemController::readFile(const QString &fileName, QString &data)
{
QByteArray byteArray;
if(!readFile(fileName, byteArray)) return false;
data = byteArray;
return true;
}
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)
{
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) {
qDebug() << opensslErrString();
}
return ok == 1;
}
2026-03-17 16:15:53 +02:00
static bool aesCrypt(const QByteArray &in, const QByteArray &key, const QByteArray &iv, QByteArray &out,
QByteArray &tag, bool encrypt)
{
2026-03-13 13:30:03 +02:00
std::unique_ptr<EVP_CIPHER_CTX, void (*)(EVP_CIPHER_CTX *)> ctx { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
if (!ctx) {
qDebug() << "EVP_CIPHER_CTX_new failed";
return false;
}
2026-03-13 13:30:03 +02:00
2026-03-17 16:15:53 +02:00
const EVP_CIPHER *cipher = EVP_aes_256_gcm();
2026-03-13 13:30:03 +02:00
2026-03-17 16:15:53 +02:00
if (1 != EVP_CipherInit_ex(ctx.get(), cipher, nullptr, nullptr, nullptr, encrypt ? 1 : 0)) {
qDebug() << opensslErrString();
return false;
}
2026-03-17 16:15:53 +02:00
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_IVLEN, iv.size(), nullptr)) {
qDebug() << opensslErrString();
return false;
}
2026-03-13 13:30:03 +02:00
2026-03-17 16:15:53 +02:00
if (1 != EVP_CipherInit_ex(ctx.get(), nullptr, nullptr, reinterpret_cast<const unsigned char *>(key.constData()),
reinterpret_cast<const unsigned char *>(iv.constData()), -1)) {
qDebug() << opensslErrString();
return false;
}
2026-03-13 13:30:03 +02:00
2026-03-17 16:15:53 +02:00
out.clear();
out.resize(in.size());
int outlen = 0;
if (1 != EVP_CipherUpdate(ctx.get(), reinterpret_cast<unsigned char *>(out.data()), &outlen,
reinterpret_cast<const unsigned char *>(in.constData()), in.size())) {
qDebug() << opensslErrString();
return false;
}
2026-03-13 13:30:03 +02:00
2026-03-17 16:15:53 +02:00
int tmplen = 0;
if (encrypt) {
if (1 != EVP_CipherFinal_ex(ctx.get(), reinterpret_cast<unsigned char *>(out.data()) + outlen, &tmplen)) {
qDebug() << opensslErrString();
return false;
}
out.resize(outlen + tmplen);
tag.resize(TAG_LEN);
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_GET_TAG, TAG_LEN, tag.data())) {
qDebug() << opensslErrString();
return false;
}
} else {
if (1 != EVP_CIPHER_CTX_ctrl(ctx.get(), EVP_CTRL_GCM_SET_TAG, tag.size(), const_cast<char *>(tag.constData()))) {
qDebug() << opensslErrString();
return false;
}
if (1 != EVP_CipherFinal_ex(ctx.get(), reinterpret_cast<unsigned char *>(out.data()) + outlen, &tmplen)) {
qDebug() << "Authentication failed: " << opensslErrString();
return false;
}
out.resize(outlen + tmplen);
}
return true;
}
bool SystemController::encryptFile(const QString &filePath, const QString &password, const QString &hint)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
2026-03-17 16:15:53 +02:00
qDebug() << "Cannot open file for read: " << f.errorString();
return false;
}
2026-03-17 16:15:53 +02:00
QByteArray content = f.readAll();
f.close();
if (content.startsWith(magicString)) {
2026-03-17 16:15:53 +02:00
qDebug() << "File already encrypted";
return false;
}
QByteArray salt(SALT_LEN, 0);
QByteArray iv(IV_LEN, 0);
QByteArray key;
QByteArray cipher;
2026-03-17 16:15:53 +02:00
QByteArray tag;
if (1 != RAND_bytes(reinterpret_cast<unsigned char *>(salt.data()), SALT_LEN)
|| 1 != RAND_bytes(reinterpret_cast<unsigned char *>(iv.data()), IV_LEN)) {
qDebug() << opensslErrString();
return false;
}
if (!deriveKey(password.toUtf8(), salt, key))
return false;
2026-03-17 16:15:53 +02:00
if (!aesCrypt(content, key, iv, cipher, tag, true))
return false;
2026-03-17 16:15:53 +02:00
QByteArray out;
QByteArray hintBytes = hint.toUtf8();
quint32 hintLen = static_cast<quint32>(hintBytes.size());
out += magicString;
2026-03-17 16:15:53 +02:00
out.append(reinterpret_cast<const char *>(&hintLen), sizeof(hintLen));
out += hintBytes;
out += salt;
out += iv;
2026-03-17 16:15:53 +02:00
out += tag;
out += cipher;
if (!f.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
2026-03-17 16:15:53 +02:00
qDebug() << "Cannot open file for write: " << f.errorString();
return false;
}
2026-03-17 16:15:53 +02:00
if (f.write(out) != out.size()) {
qDebug() << "Write failed";
f.close();
return false;
}
2026-03-17 16:15:53 +02:00
f.close();
return true;
}
QByteArray SystemController::getDecryptedData(const QString &filePath, const QString &password)
{
2026-03-17 16:15:53 +02:00
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << "Cannot open file: " << f.errorString();
return {};
}
QByteArray content = f.readAll();
f.close();
if (!content.startsWith(magicString)) {
qDebug() << "Invalid file format (magic missing)";
return {};
}
int pos = magicString.size();
2026-03-17 16:15:53 +02:00
if (content.size() < pos + static_cast<int>(sizeof(quint32))) {
qDebug() << "Corrupted file (no hint length)";
return {};
}
quint32 hintLen = 0;
2026-03-17 16:15:53 +02:00
memcpy(&hintLen, content.constData() + pos, sizeof(quint32));
pos += sizeof(quint32);
2026-03-17 16:15:53 +02:00
if (content.size() < pos + static_cast<int>(hintLen) + SALT_LEN + IV_LEN + TAG_LEN) {
qDebug() << "Corrupted file (invalid sizes)";
return {};
}
pos += hintLen;
2026-03-17 16:15:53 +02:00
QByteArray salt = content.mid(pos, SALT_LEN);
pos += SALT_LEN;
QByteArray iv = content.mid(pos, IV_LEN);
pos += IV_LEN;
QByteArray tag = content.mid(pos, TAG_LEN);
pos += TAG_LEN;
QByteArray cipher = content.mid(pos);
QByteArray key;
2026-03-17 16:15:53 +02:00
if (!deriveKey(password.toUtf8(), salt, key)) {
qDebug() << "Key derivation failed";
return {};
}
2026-03-17 16:15:53 +02:00
QByteArray plain;
if (!aesCrypt(cipher, key, iv, plain, tag, false)) {
qDebug() << "Decryption failed (wrong password or corrupted data)";
return {};
}
2026-03-17 16:15:53 +02:00
return plain;
}
bool SystemController::isFileEncrypted(const QString &filePath)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << "Cannot open file for read: %1", f.errorString();
return false;
}
QByteArray data = f.readAll();
f.close();
if (!data.startsWith(magicString)) {
qDebug() << "File is not recognized as encrypted (magic missing)";
return false;
}
return true;
}
bool SystemController::isPasswordValid(const QString &filePath, const QString &password)
{
QFile f(filePath);
if (!f.open(QIODevice::ReadOnly)) {
qDebug() << f.errorString();
return false;
}
QByteArray content = f.readAll();
f.close();
2026-03-17 16:15:53 +02:00
if (!content.startsWith(magicString))
return false;
int pos = magicString.size();
2026-03-17 16:15:53 +02:00
quint32 hintLen = 0;
memcpy(&hintLen, content.constData() + pos, sizeof(quint32));
pos += sizeof(quint32);
pos += hintLen;
2026-03-17 16:15:53 +02:00
QByteArray salt = content.mid(pos, SALT_LEN);
pos += SALT_LEN;
QByteArray iv = content.mid(pos, IV_LEN);
pos += IV_LEN;
QByteArray tag = content.mid(pos, TAG_LEN);
pos += TAG_LEN;
QByteArray cipher = content.mid(pos);
QByteArray key;
if (!deriveKey(password.toUtf8(), salt, key))
return false;
QByteArray plain;
2026-03-17 16:15:53 +02:00
bool ok = aesCrypt(cipher, key, iv, plain, tag, false);
2026-03-17 16:15:53 +02:00
if (!ok)
qDebug() << "Wrong password";
2026-03-17 16:15:53 +02:00
return ok;
}
QString SystemController::readHint(const QString &filePath)
{
2026-03-13 13:30:03 +02:00
if (filePath.isEmpty())
return "";
QByteArray data;
readFile(filePath, data);
2026-03-17 16:15:53 +02:00
if (!data.startsWith(magicString)) {
qDebug() << "Not an encrypted file";
return {};
}
int pos = magicString.size();
if (data.size() < pos + static_cast<int>(sizeof(quint32))) {
qDebug() << "Corrupted file (no hint length)";
return {};
}
quint32 hintLen = 0;
memcpy(&hintLen, data.constData() + pos, sizeof(quint32));
pos += sizeof(quint32);
if (data.size() < pos + static_cast<int>(hintLen)) {
qDebug() << "Corrupted file (hint truncated)";
return {};
}
return QString::fromUtf8(data.constData() + pos, hintLen);
}
QString SystemController::getFileName(const QString &acceptLabel, const QString &nameFilter,
const QString &selectedFile, const bool isSaveMode, const QString &defaultSuffix)
2023-09-06 22:20:59 +05:00
{
QString fileName;
2023-12-26 16:23:05 +03:00
#ifdef Q_OS_ANDROID
Q_ASSERT(!isSaveMode);
return AndroidController::instance()->openFile(nameFilter);
#endif
2023-09-06 22:20:59 +05:00
#ifdef Q_OS_IOS
fileName = IosController::Instance()->openFile();
if (fileName.isEmpty()) {
return fileName;
}
2023-09-09 01:29:28 +05:00
2023-09-06 22:20:59 +05:00
CFURLRef url = CFURLCreateWithFileSystemPath(
kCFAllocatorDefault,
CFStringCreateWithCharacters(0, reinterpret_cast<const UniChar *>(fileName.unicode()), fileName.length()),
kCFURLPOSIXPathStyle, 0);
if (!CFURLStartAccessingSecurityScopedResource(url)) {
qDebug() << "Could not access path " << QUrl::fromLocalFile(fileName).toString();
}
return fileName;
#endif
QObject *mainFileDialog = m_qmlRoot->findChild<QObject>("mainFileDialog").parent();
if (!mainFileDialog) {
return "";
}
mainFileDialog->setProperty("acceptLabel", QVariant::fromValue(acceptLabel));
mainFileDialog->setProperty("nameFilters", QVariant::fromValue(QStringList(nameFilter)));
mainFileDialog->setProperty("defaultSuffix", QVariant::fromValue(defaultSuffix));
mainFileDialog->setProperty("isSaveMode", QVariant::fromValue(isSaveMode));
if (!selectedFile.isEmpty()) {
mainFileDialog->setProperty("selectedFile", QVariant::fromValue(QUrl(selectedFile)));
}
QMetaObject::invokeMethod(mainFileDialog, "open");
bool isFileDialogAccepted = false;
QEventLoop wait;
QObject::connect(this, &SystemController::fileDialogClosed, [&wait, &isFileDialogAccepted](const bool isAccepted) {
isFileDialogAccepted = isAccepted;
wait.quit();
});
wait.exec();
QObject::disconnect(this, &SystemController::fileDialogClosed, nullptr, nullptr);
if (!isFileDialogAccepted) {
return "";
}
fileName = mainFileDialog->property("selectedFile").toString();
2023-09-06 22:20:59 +05:00
return QUrl(fileName).toLocalFile();
}
void SystemController::setQmlRoot(QObject *qmlRoot)
{
m_qmlRoot = qmlRoot;
}
2024-09-09 14:36:33 +03:00
bool SystemController::isAuthenticated()
{
#ifdef Q_OS_ANDROID
return AndroidController::instance()->requestAuthentication();
#else
return true;
#endif
}
2024-12-31 04:16:52 +01:00
void SystemController::sendTouch(float x, float y)
{
#ifdef Q_OS_ANDROID
AndroidController::instance()->sendTouch(x, y);
#endif
}