chore: merge with upstream (#2826)

* feat: add cryptoUtils

* feat: move protocol selection to home page

* feat: add connection warmup
This commit is contained in:
vkamn authored and GitHub committed 2026-07-13 14:26:00 +08:00
1 parent 4b4563818e
commit e4127f47f3
31 files changed
+781 -172

No files matched your search

+1 -1
View File
@@ -501,7 +501,7 @@ jobs:
- name: 'Setup xcode'
uses: maxim-lobanov/setup-xcode@v1
with:
xcode-version: '16.2.0'
xcode-version: '26.6.0'
- name: 'Install Qt'
uses: jurplel/install-qt-action@v4
-3
View File
@@ -7,9 +7,6 @@
[submodule "client/3rd/amneziawg-apple"]
path = client/3rd/amneziawg-apple
url = https://github.com/amnezia-vpn/amneziawg-apple
[submodule "client/3rd/QSimpleCrypto"]
path = client/3rd/QSimpleCrypto
url = https://github.com/amnezia-vpn/QSimpleCrypto.git
[submodule "client/3rd/qtgamepad"]
path = client/3rd/qtgamepad
url = https://github.com/amnezia-vpn/qtgamepad.git
+1
View File
@@ -103,6 +103,7 @@ include(${CMAKE_CURRENT_LIST_DIR}/cmake/sources.cmake)
include_directories(
${CMAKE_CURRENT_LIST_DIR}/../ipc
${CMAKE_CURRENT_LIST_DIR}/../common/logger
${CMAKE_CURRENT_LIST_DIR}/../common/crypto
${CMAKE_CURRENT_LIST_DIR}
${CMAKE_CURRENT_BINARY_DIR}
)
-2
View File
@@ -4,7 +4,6 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel ${CMAKE_BINARY_DIR}/3rd/SortFilterProxyModel)
set(LIBS ${LIBS} SortFilterProxyModel)
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
@@ -40,7 +39,6 @@ endif()
set(LIBS ${LIBS} qt6keychain)
include_directories(
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
)
-21
View File
@@ -1,21 +0,0 @@
set(CLIENT_ROOT_DIR ${CMAKE_CURRENT_LIST_DIR}/..)
set(QSIMPLECRYPTO_DIR ${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src)
include_directories(${QSIMPLECRYPTO_DIR})
set(HEADERS ${HEADERS}
${QSIMPLECRYPTO_DIR}/include/QAead.h
${QSIMPLECRYPTO_DIR}/include/QBlockCipher.h
${QSIMPLECRYPTO_DIR}/include/QRsa.h
${QSIMPLECRYPTO_DIR}/include/QSimpleCrypto_global.h
${QSIMPLECRYPTO_DIR}/include/QX509.h
${QSIMPLECRYPTO_DIR}/include/QX509Store.h
)
set(SOURCES ${SOURCES}
${QSIMPLECRYPTO_DIR}/sources/QAead.cpp
${QSIMPLECRYPTO_DIR}/sources/QBlockCipher.cpp
${QSIMPLECRYPTO_DIR}/sources/QRsa.cpp
${QSIMPLECRYPTO_DIR}/sources/QX509.cpp
${QSIMPLECRYPTO_DIR}/sources/QX509Store.cpp
)
+4
View File
@@ -57,9 +57,11 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.h
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.h
${CLIENT_ROOT_DIR}/core/utils/payloadSender.h
${CLIENT_ROOT_DIR}/core/utils/serialization/serialization.h
${CLIENT_ROOT_DIR}/core/utils/serialization/transfer.h
${CLIENT_ROOT_DIR}/../common/logger/logger.h
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.h
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/utils/osSignalHandler.h
@@ -132,6 +134,7 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/protocols/vpnProtocol.cpp
${CLIENT_ROOT_DIR}/core/utils/selfhosted/sshClient.cpp
${CLIENT_ROOT_DIR}/core/utils/networkUtilities.cpp
${CLIENT_ROOT_DIR}/core/utils/payloadSender.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/outbound.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/inbound.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/ss.cpp
@@ -141,6 +144,7 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess.cpp
${CLIENT_ROOT_DIR}/core/utils/serialization/vmess_new.cpp
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.cpp
${CLIENT_ROOT_DIR}/ui/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/utils/serverConfigUtils.cpp
@@ -198,9 +198,6 @@ void SubscriptionController::updateApiConfigInJson(QJsonObject &serverConfigJson
if (serverConfigJson.value(configKey::configVersion).toInt() == serverConfigUtils::ConfigSource::AmneziaGateway) {
QJsonObject responseObj = QJsonDocument::fromJson(apiResponseBody).object();
if (responseObj.contains(apiDefs::key::supportedProtocols)) {
apiConfig.insert(apiDefs::key::supportedProtocols, responseObj.value(apiDefs::key::supportedProtocols).toArray());
}
if (responseObj.contains(apiDefs::key::serviceInfo)) {
apiConfig.insert(apiDefs::key::serviceInfo, responseObj.value(apiDefs::key::serviceInfo).toObject());
}
@@ -418,6 +415,23 @@ ErrorCode SubscriptionController::updateServiceFromGateway(const QString &server
}
const bool isTestPurchase = apiV2->apiConfig.isTestPurchase;
QString serviceProtocol = apiV2->serviceProtocol();
if (!newCountryCode.isEmpty()) {
const auto availableCountries = apiV2->apiConfig.availableCountries;
for (const auto &country : availableCountries) {
const auto countryObject = country.toObject();
if (countryObject.value(apiDefs::key::serverCountryCode).toString() != newCountryCode) {
continue;
}
const auto availableProtocols = countryObject.value(apiDefs::key::availableProtocols).toArray();
if (!availableProtocols.isEmpty() && !availableProtocols.contains(serviceProtocol)) {
serviceProtocol = availableProtocols.first().toString();
}
break;
}
}
ProtocolData protocolData = generateProtocolData(serviceProtocol);
QJsonObject authDataJson = apiV2->authData.toJson();
@@ -752,6 +766,36 @@ bool SubscriptionController::isVlessProtocol(const QString &serverId) const
return apiV2.has_value() && apiV2->serviceProtocol() == "vless";
}
QString SubscriptionController::currentProtocol(const QString &serverId) const
{
auto apiV2 = m_serversRepository->apiV2Config(serverId);
return apiV2.has_value() ? apiV2->serviceProtocol() : QString();
}
QStringList SubscriptionController::availableProtocols(const QString &serverId) const
{
auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (!apiV2.has_value()) {
return {};
}
const auto currentCountryCode = apiV2->apiConfig.serverCountryCode;
const auto availableCountries = apiV2->apiConfig.availableCountries;
QStringList protocols;
for (const auto &country : availableCountries) {
const auto countryObject = country.toObject();
if (countryObject.value(apiDefs::key::serverCountryCode).toString() != currentCountryCode) {
continue;
}
for (const auto &protocol : countryObject.value(apiDefs::key::availableProtocols).toArray()) {
protocols.push_back(protocol.toString());
}
break;
}
return protocols;
}
ErrorCode SubscriptionController::processAppStorePurchase(const QString &userCountryCode, const QString &serviceType,
const QString &serviceProtocol, const QString &productId,
int *duplicateServerIndex)
@@ -86,6 +86,8 @@ public:
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
bool isVlessProtocol(const QString &serverId) const;
QString currentProtocol(const QString &serverId) const;
QStringList availableProtocols(const QString &serverId) const;
ErrorCode getAccountInfo(const QString &serverId, QJsonObject &accountInfo);
QFuture<QPair<ErrorCode, QString>> getRenewalLink(const QString &serverId);
@@ -6,6 +6,7 @@
#include "core/utils/protocolEnum.h"
#include "core/protocols/protocolUtils.h"
#include "core/utils/constants/configKeys.h"
#include "core/utils/payloadSender.h"
#include "core/utils/utilities.h"
#include "core/utils/serverConfigUtils.h"
#include "version.h"
@@ -218,6 +219,11 @@ ErrorCode ConnectionController::openConnection(const QString &serverId)
return errorCode;
}
const auto apiV2 = m_serversRepository->apiV2Config(serverId);
if (apiV2.has_value() && !apiV2->sendPayload.isEmpty()) {
PayloadSender::sendAll(apiV2->sendPayload);
}
emit openConnectionRequested(serverId, container, vpnConfiguration);
return ErrorCode::NoError;
}
+34 -50
View File
@@ -12,15 +12,14 @@
#include <QPromise>
#include <QUrl>
#include "QBlockCipher.h"
#include "QRsa.h"
#include <openssl/rsa.h>
#include "amneziaApplication.h"
#include "core/repositories/secureAppSettingsRepository.h"
#include "core/utils/api/apiUtils.h"
#include "core/utils/constants/apiKeys.h"
#include "core/utils/networkUtilities.h"
#include "core/utils/utilities.h"
#include "cryptoUtils.h"
#ifdef AMNEZIA_DESKTOP
#include "core/utils/ipcClient.h"
@@ -63,27 +62,24 @@ namespace
bool decryptProxyUrlsPayload(const QByteArray &encryptedPayload, bool isDevEnvironment, QByteArray &decryptedPayload)
{
try {
QByteArray key = isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
if (!isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray h = hash.result().toHex();
QByteArray key = isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
if (!isDevEnvironment) {
QCryptographicHash hash(QCryptographicHash::Sha512);
hash.addData(key);
QByteArray h = hash.result().toHex();
QByteArray decKey = QByteArray::fromHex(h.left(64));
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encryptedPayload);
QByteArray decKey = QByteArray::fromHex(h.left(64));
QByteArray iv = QByteArray::fromHex(h.mid(64, 32));
QByteArray ba = QByteArray::fromBase64(encryptedPayload);
QSimpleCrypto::QBlockCipher cipher;
decryptedPayload = cipher.decryptAesBlockCipher(ba, decKey, iv);
} else {
decryptedPayload = encryptedPayload;
decryptedPayload = CryptoUtils::decryptAes256Cbc(ba, decKey, iv);
if (decryptedPayload.isEmpty()) {
return false;
}
return true;
} catch (...) {
Utils::logException();
return false;
} else {
decryptedPayload = encryptedPayload;
}
return true;
}
QStringList readCachedProxyUrls(const QByteArray &cachedProxyUrlsEncrypted, bool isDevEnvironment)
@@ -151,40 +147,29 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
}
#endif
QSimpleCrypto::QBlockCipher blockCipher;
encRequestData.key = blockCipher.generatePrivateSalt(32);
encRequestData.iv = blockCipher.generatePrivateSalt(32);
encRequestData.salt = blockCipher.generatePrivateSalt(8);
encRequestData.key = CryptoUtils::generateRandomBytes(32);
encRequestData.iv = CryptoUtils::generateRandomBytes(32);
encRequestData.salt = CryptoUtils::generateRandomBytes(8);
QJsonObject keyPayload;
keyPayload[apiDefs::key::aesKey] = QString(encRequestData.key.toBase64());
keyPayload[apiDefs::key::aesIv] = QString(encRequestData.iv.toBase64());
keyPayload[apiDefs::key::aesSalt] = QString(encRequestData.salt.toBase64());
QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload;
try {
QSimpleCrypto::QRsa rsa;
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
EVP_PKEY *publicKey = CryptoUtils::loadPublicKeyFromPem(rsaKey);
if (publicKey == nullptr) {
qCritical() << "error loading public key from environment variables";
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
return encRequestData;
}
EVP_PKEY *publicKey = nullptr;
try {
QByteArray rsaKey = m_isDevEnvironment ? DEV_AGW_PUBLIC_KEY : PROD_AGW_PUBLIC_KEY;
QSimpleCrypto::QRsa rsa;
publicKey = rsa.getPublicKeyFromByteArray(rsaKey);
} catch (...) {
Utils::logException();
qCritical() << "error loading public key from environment variables";
encRequestData.errorCode = ErrorCode::ApiMissingAgwPublicKey;
return encRequestData;
}
QByteArray encryptedKeyPayload = CryptoUtils::rsaEncrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
QByteArray encryptedApiPayload = CryptoUtils::encryptAes256Cbc(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv);
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
"", encRequestData.salt);
} catch (...) {
Utils::logException();
if (encryptedKeyPayload.isEmpty() || encryptedApiPayload.isEmpty()) {
qCritical() << "error when encrypting the request body";
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
return encRequestData;
@@ -206,11 +191,11 @@ GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(co
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
try {
QSimpleCrypto::QBlockCipher blockCipher;
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
QByteArray decrypted = CryptoUtils::decryptAes256Cbc(encryptedResponseBody, key, iv);
if (!decrypted.isEmpty()) {
result.decryptedBody = decrypted;
result.isDecryptionSuccessful = true;
} catch (...) {
} else {
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
}
@@ -329,7 +314,6 @@ QFuture<QPair<ErrorCode, QByteArray>> GatewayController::postAsync(const QString
}
if (!decryptionResult.isDecryptionSuccessful) {
Utils::logException();
qCritical() << "error when decrypting the request body";
promise->addResult(qMakePair(ErrorCode::ApiConfigDecryptionError, QByteArray()));
promise->finish();
-5
View File
@@ -144,10 +144,6 @@ QJsonObject ApiConfig::toJson() const
obj[apiDefs::key::availableCountries] = availableCountries;
}
if (!supportedProtocols.isEmpty()) {
obj[apiDefs::key::supportedProtocols] = supportedProtocols;
}
QJsonObject serviceInfoObj = serviceInfo.toJson();
if (!serviceInfoObj.isEmpty()) {
obj[apiDefs::key::serviceInfo] = serviceInfoObj;
@@ -198,7 +194,6 @@ ApiConfig ApiConfig::fromJson(const QJsonObject& json)
config.issuedConfigs = json.value(apiDefs::key::issuedConfigs).toInt(0);
config.availableCountries = json.value(apiDefs::key::availableCountries).toArray();
config.supportedProtocols = json.value(apiDefs::key::supportedProtocols).toArray();
QJsonObject serviceInfoObj = json.value(apiDefs::key::serviceInfo).toObject();
if (!serviceInfoObj.isEmpty()) {
-1
View File
@@ -34,7 +34,6 @@ struct ApiConfig
int maxDeviceCount;
int issuedConfigs;
QJsonArray availableCountries;
QJsonArray supportedProtocols;
struct ServiceInfo {
bool isAdVisible = false;
+6 -1
View File
@@ -121,7 +121,11 @@ QJsonObject ApiV2ServerConfig::toJson() const
if (!dns2.isEmpty()) {
obj[configKey::dns2] = dns2;
}
if (!sendPayload.isEmpty()) {
obj[configKey::sendPayload] = sendPayload;
}
if (crc > 0) {
obj[configKey::crc] = crc;
}
@@ -165,6 +169,7 @@ ApiV2ServerConfig ApiV2ServerConfig::fromJson(const QJsonObject& json)
config.dns1 = json.value(configKey::dns1).toString();
config.dns2 = json.value(configKey::dns2).toString();
config.sendPayload = json.value(configKey::sendPayload).toArray();
config.crc = json.value(configKey::crc).toInt(0);
@@ -1,6 +1,7 @@
#ifndef APIV2SERVERCONFIG_H
#define APIV2SERVERCONFIG_H
#include <QJsonArray>
#include <QJsonObject>
#include <QMap>
#include <QPair>
@@ -28,6 +29,7 @@ struct ApiV2ServerConfig {
DockerContainer defaultContainer;
QString dns1;
QString dns2;
QJsonArray sendPayload;
QString name;
bool nameOverriddenByUser = false;
+1 -1
View File
@@ -18,8 +18,8 @@ namespace apiDefs
constexpr QLatin1String stackType("stack_type");
constexpr QLatin1String cliVersion("cli_version");
constexpr QLatin1String cliName("cli_name");
constexpr QLatin1String supportedProtocols("supported_protocols");
constexpr QLatin1String availableCountries("available_countries");
constexpr QLatin1String availableProtocols("available_protocols");
constexpr QLatin1String installationUuid("installation_uuid");
constexpr QLatin1String uuid("installation_uuid");
constexpr QLatin1String osVersion("os_version");
+7
View File
@@ -25,6 +25,13 @@ namespace amnezia
constexpr QLatin1String config("config");
constexpr QLatin1String configVersion("config_version");
constexpr QLatin1String sendPayload("send_payload");
constexpr QLatin1String sendPayloadEndpoint("endpoint");
constexpr QLatin1String sendPayloadTimeoutMs("timeout_ms");
constexpr QLatin1String sendPayloadProtocol("protocol");
constexpr QLatin1String sendPayloadData("payload");
constexpr QLatin1String sendPayloadExpectedResponse("expected_response");
constexpr QLatin1String containers("containers");
constexpr QLatin1String container("container");
constexpr QLatin1String defaultContainer("defaultContainer");
+193
View File
@@ -0,0 +1,193 @@
#include "payloadSender.h"
#include <QDebug>
#include <QEventLoop>
#include <QHostAddress>
#include <QJsonObject>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QTcpSocket>
#include <QTimer>
#include <QUdpSocket>
#include "core/utils/constants/configKeys.h"
#include "core/utils/networkUtilities.h"
using namespace amnezia;
namespace
{
constexpr char protoUdp[] = "udp";
constexpr char protoTcp[] = "tcp";
QByteArray randomBytes(int count)
{
if (count <= 0) {
return {};
}
QByteArray bytes(count, Qt::Uninitialized);
for (int i = 0; i < count; ++i) {
bytes[i] = static_cast<char>(QRandomGenerator::global()->bounded(256));
}
return bytes;
}
bool parseEndpoint(const QString &endpoint, QString &host, quint16 &port)
{
const int separatorIndex = endpoint.lastIndexOf(QLatin1Char(':'));
if (separatorIndex <= 0 || separatorIndex == endpoint.size() - 1) {
return false;
}
host = endpoint.left(separatorIndex);
bool ok = false;
const uint parsedPort = endpoint.mid(separatorIndex + 1).toUInt(&ok);
if (!ok || parsedPort == 0 || parsedPort > 65535) {
return false;
}
port = static_cast<quint16>(parsedPort);
return true;
}
enum class ExchangeResult { Sent, Matched, Mismatch, Timeout };
ExchangeResult waitForResponse(QAbstractSocket &socket, const QByteArray &expectedResponse, int timeoutMs)
{
if (expectedResponse.isEmpty()) {
return ExchangeResult::Sent;
}
QByteArray response;
QEventLoop loop;
QTimer timer;
timer.setSingleShot(true);
QObject::connect(&timer, &QTimer::timeout, &loop, &QEventLoop::quit);
QObject::connect(&socket, &QIODevice::readyRead, &loop, [&]() {
response.append(socket.readAll());
if (response.size() >= expectedResponse.size()) {
loop.quit();
}
});
timer.start(timeoutMs);
loop.exec();
if (response.isEmpty()) {
return ExchangeResult::Timeout;
}
return response == expectedResponse ? ExchangeResult::Matched : ExchangeResult::Mismatch;
}
}
QByteArray PayloadSender::buildPayload(const QString &tagString)
{
QByteArray result;
if (tagString.isEmpty()) {
return result;
}
static const QRegularExpression tagRegExp(QStringLiteral("<([^>]*)>"));
QRegularExpressionMatchIterator it = tagRegExp.globalMatch(tagString);
while (it.hasNext()) {
const QRegularExpressionMatch match = it.next();
const QStringList parts = match.captured(1).simplified().split(QLatin1Char(' '), Qt::SkipEmptyParts);
if (parts.isEmpty()) {
continue;
}
const QString tag = parts.at(0).toLower();
if (tag == QLatin1String("b")) {
if (parts.size() < 2) {
continue;
}
QString hex = parts.at(1);
if (hex.startsWith(QLatin1String("0x"), Qt::CaseInsensitive)) {
hex = hex.mid(2);
}
result.append(QByteArray::fromHex(hex.toLatin1()));
} else if (tag == QLatin1String("r")) {
if (parts.size() < 2) {
continue;
}
bool ok = false;
const int count = parts.at(1).toInt(&ok);
if (ok) {
result.append(randomBytes(count));
}
} else {
qWarning() << "PayloadSender: unknown payload tag" << tag;
}
}
return result;
}
void PayloadSender::sendAll(const QJsonArray &sendPayload)
{
for (int i = 0; i < sendPayload.size(); ++i) {
const QJsonValue value = sendPayload.at(i);
if (!value.isObject()) {
continue;
}
sendEntry(value.toObject(), i);
}
}
void PayloadSender::sendEntry(const QJsonObject &entry, int index)
{
const QString endpoint = entry.value(configKey::sendPayloadEndpoint).toString();
QString host;
quint16 port = 0;
if (!parseEndpoint(endpoint, host, port)) {
qWarning() << "PayloadSender: skipping entry" << index << "- invalid endpoint" << endpoint;
return;
}
const QString protocol = entry.value(configKey::sendPayloadProtocol).toString().toLower();
const int timeoutMs = entry.value(configKey::sendPayloadTimeoutMs).toInt();
const QByteArray payload = buildPayload(entry.value(configKey::sendPayloadData).toString());
const QByteArray expectedResponse = buildPayload(entry.value(configKey::sendPayloadExpectedResponse).toString());
const QString resolvedHost = NetworkUtilities::getIPAddress(host);
const QHostAddress hostAddress(resolvedHost.isEmpty() ? host : resolvedHost);
QUdpSocket udpSocket;
QTcpSocket tcpSocket;
QAbstractSocket *socket = nullptr;
if (protocol == protoUdp) {
socket = &udpSocket;
if (udpSocket.writeDatagram(payload, hostAddress, port) < 0) {
qWarning() << "PayloadSender: entry" << index << "- udp write failed to" << endpoint << udpSocket.errorString();
return;
}
} else if (protocol == protoTcp) {
socket = &tcpSocket;
tcpSocket.connectToHost(hostAddress, port);
if (!tcpSocket.waitForConnected(timeoutMs)) {
qWarning() << "PayloadSender: entry" << index << "- tcp connect failed to" << endpoint << tcpSocket.errorString();
return;
}
if (tcpSocket.write(payload) != payload.size()) {
qWarning() << "PayloadSender: entry" << index << "- tcp write failed to" << endpoint << tcpSocket.errorString();
return;
}
tcpSocket.flush();
} else {
qWarning() << "PayloadSender: skipping entry" << index << "- unsupported protocol" << protocol;
return;
}
switch (waitForResponse(*socket, expectedResponse, timeoutMs)) {
case ExchangeResult::Sent:
qInfo() << "PayloadSender: entry" << index << "-" << protocol << "payload sent to" << endpoint;
break;
case ExchangeResult::Matched:
qInfo() << "PayloadSender: entry" << index << "-" << protocol << "expected response received from" << endpoint;
break;
case ExchangeResult::Mismatch:
qWarning() << "PayloadSender: entry" << index << "-" << protocol << "response mismatch from" << endpoint;
break;
case ExchangeResult::Timeout:
qWarning() << "PayloadSender: entry" << index << "-" << protocol << "response timeout from" << endpoint;
break;
}
}
+18
View File
@@ -0,0 +1,18 @@
#ifndef PAYLOADSENDER_H
#define PAYLOADSENDER_H
#include <QByteArray>
#include <QJsonArray>
#include <QString>
class PayloadSender
{
public:
static void sendAll(const QJsonArray &sendPayload);
static QByteArray buildPayload(const QString &tagString);
private:
static void sendEntry(const QJsonObject &entry, int index);
};
#endif // PAYLOADSENDER_H
@@ -0,0 +1,6 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M17 11L21 7L17 3" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M21 7H9" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7 21L3 17L7 13" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M15 17H3" stroke="#D7D8DB" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 525 B

+1
View File
@@ -10,6 +10,7 @@
<file>controls/app.svg</file>
<file>controls/archive-restore.svg</file>
<file>controls/arrow-left.svg</file>
<file>controls/arrow-left-right.svg</file>
<file>controls/arrow-right.svg</file>
<file>controls/bug.svg</file>
<file>controls/check.svg</file>
+7 -16
View File
@@ -1,8 +1,7 @@
#include "secureQSettings.h"
#include "../client/3rd/QSimpleCrypto/src/include/QAead.h"
#include "../client/3rd/QSimpleCrypto/src/include/QBlockCipher.h"
#include "core/utils/utilities.h"
#include "cryptoUtils.h"
#include <QDataStream>
#include <QDebug>
#include <QEventLoop>
@@ -180,11 +179,8 @@ void SecureQSettings::clearSettings()
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?
QByteArray result = CryptoUtils::encryptAes256Cbc(value, getEncKey(), getEncIv());
if (result.isEmpty() && !value.isEmpty()) {
qCritical() << "error when encrypting the settings value";
}
return result;
@@ -192,11 +188,8 @@ QByteArray SecureQSettings::encryptText(const QByteArray &value) const
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?
QByteArray result = CryptoUtils::decryptAes256Cbc(ba, getEncKey(), getEncIv());
if (result.isEmpty() && !ba.isEmpty()) {
qCritical() << "error when decrypting the settings value";
}
return result;
@@ -225,8 +218,7 @@ QByteArray SecureQSettings::getEncKey() const
if (m_key.isEmpty()) {
// Create new key
QSimpleCrypto::QBlockCipher cipher;
QByteArray key = cipher.generatePrivateSalt(32);
QByteArray key = CryptoUtils::generateRandomBytes(32);
if (key.isEmpty()) {
qCritical() << "SecureQSettings::getEncKey Unable to generate new enc key";
}
@@ -254,8 +246,7 @@ QByteArray SecureQSettings::getEncIv() const
if (m_iv.isEmpty()) {
// Create new IV
QSimpleCrypto::QBlockCipher cipher;
QByteArray iv = cipher.generatePrivateSalt(32);
QByteArray iv = CryptoUtils::generateRandomBytes(32);
if (iv.isEmpty()) {
qCritical() << "SecureQSettings::getEncIv Unable to generate new enc IV";
}
@@ -508,6 +508,18 @@ bool SubscriptionUiController::isVlessProtocol(const QString &serverId)
}
QString SubscriptionUiController::currentProtocol(const QString &serverId)
{
return m_subscriptionController->currentProtocol(serverId);
}
QStringList SubscriptionUiController::availableProtocols(const QString &serverId)
{
return m_subscriptionController->availableProtocols(serverId);
}
void SubscriptionUiController::removeApiConfig(const QString &serverId)
{
m_subscriptionController->removeApiConfig(serverId);
@@ -57,6 +57,8 @@ public slots:
void setCurrentProtocol(const QString &serverId, const QString &protocolName);
bool isVlessProtocol(const QString &serverId);
QString currentProtocol(const QString &serverId);
QStringList availableProtocols(const QString &serverId);
bool isCaptchaAwaitingUser() const;
void onCaptchaSolved(const QString &captchaId, const QString &solution);
@@ -73,12 +73,6 @@ QVariant ApiAccountInfoModel::data(const QModelIndex &index, int role) const
}
return false;
}
case IsProtocolSelectionSupportedRole: {
if (m_accountInfoData.supportedProtocols.size() > 1) {
return true;
}
return false;
}
case IsSubscriptionExpiredRole: {
if (m_accountInfoData.configType == serverConfigUtils::ConfigType::AmneziaFreeV3) {
return false;
@@ -132,10 +126,6 @@ void ApiAccountInfoModel::updateModel(const QJsonObject &accountInfoObject, cons
accountInfoData.subscriptionDescription = accountInfoObject.value(apiDefs::key::subscriptionDescription).toString();
accountInfoData.isRenewalAvailable = accountInfoObject.value(apiDefs::key::isRenewalAvailable).toBool(false);
for (const auto &protocol : accountInfoObject.value(apiDefs::key::supportedProtocols).toArray()) {
accountInfoData.supportedProtocols.push_back(protocol.toString());
}
m_accountInfoData = accountInfoData;
m_supportInfo = accountInfoObject.value(apiDefs::key::supportInfo).toObject();
@@ -201,7 +191,6 @@ QHash<int, QByteArray> ApiAccountInfoModel::roleNames() const
roles[IsComponentVisibleRole] = "isComponentVisible";
roles[IsSubscriptionRenewalAvailableRole] = "isSubscriptionRenewalAvailable";
roles[HasExpiredWorkerRole] = "hasExpiredWorker";
roles[IsProtocolSelectionSupportedRole] = "isProtocolSelectionSupported";
roles[IsSubscriptionExpiredRole] = "isSubscriptionExpired";
roles[IsSubscriptionExpiringSoonRole] = "isSubscriptionExpiringSoon";
roles[IsInAppPurchaseRole] = "isInAppPurchase";
@@ -22,7 +22,6 @@ public:
IsComponentVisibleRole,
IsSubscriptionRenewalAvailableRole,
HasExpiredWorkerRole,
IsProtocolSelectionSupportedRole,
IsSubscriptionExpiredRole,
IsSubscriptionExpiringSoonRole,
IsInAppPurchaseRole
@@ -58,8 +57,6 @@ private:
serverConfigUtils::ConfigType configType;
QStringList supportedProtocols;
QString subscriptionDescription;
bool isInAppPurchase = false;
+210 -1
View File
@@ -21,6 +21,41 @@ PageType {
property var containersDropDownRef: null
property var apiAvailableProtocols: []
property string apiCurrentProtocol: ""
readonly property bool isApiProtocolSelectionVisible: ServersUiController.isDefaultServerFromApi && root.apiAvailableProtocols.length > 0
function updateApiProtocolState() {
if (ServersUiController.isDefaultServerFromApi) {
root.apiAvailableProtocols = SubscriptionUiController.availableProtocols(ServersUiController.defaultServerId)
root.apiCurrentProtocol = SubscriptionUiController.currentProtocol(ServersUiController.defaultServerId)
} else {
root.apiAvailableProtocols = []
root.apiCurrentProtocol = ""
}
}
function protocolDisplayName(protocol) {
switch (protocol) {
case "awg": return "AmneziaWG"
case "vless": return "VLESS"
default: return protocol
}
}
Component.onCompleted: {
root.updateApiProtocolState()
}
Connections {
target: ServersUiController
function onDefaultServerIdChanged() {
root.updateApiProtocolState()
}
}
Connections {
target: Qt.application
@@ -210,6 +245,10 @@ PageType {
drawer.collapsedHeight = collapsed.implicitHeight
}
onImplicitHeightChanged: {
drawer.collapsedHeight = collapsed.implicitHeight
}
DividerType {
Layout.topMargin: 10
Layout.fillWidth: false
@@ -311,7 +350,7 @@ PageType {
objectName: "rowLayoutLabel"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: 8
Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : ServersUiController.isDefaultServerFromApi ? 61 : 16
Layout.bottomMargin: root.isApiProtocolSelectionVisible ? 8 : (drawer.isCollapsedStateActive ? 44 : ServersUiController.isDefaultServerFromApi ? 61 : 16)
spacing: 0
BasicButtonType {
@@ -365,6 +404,61 @@ PageType {
}
}
}
RowLayout {
objectName: "protocolRowLayout"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : 61
spacing: 0
visible: root.isApiProtocolSelectionVisible
BasicButtonType {
id: protocolButton
objectName: "protocolButton"
enabled: root.apiAvailableProtocols.length > 1
hoverEnabled: enabled
implicitHeight: 36
leftPadding: 16
rightPadding: 16
defaultColor: AmneziaStyle.color.transparent
hoveredColor: AmneziaStyle.color.translucentWhite
pressedColor: AmneziaStyle.color.sheerWhite
disabledColor: AmneziaStyle.color.transparent
textColor: AmneziaStyle.color.mutedGray
buttonTextLabel.lineHeight: 16
buttonTextLabel.font.pixelSize: 13
buttonTextLabel.font.weight: 400
text: root.apiAvailableProtocols.length > 1
? root.protocolDisplayName(root.apiCurrentProtocol)
: root.protocolDisplayName(root.apiAvailableProtocols[0])
leftImageSource: "qrc:/images/controls/arrow-left-right.svg"
leftImageColor: AmneziaStyle.color.mutedGray
rightImageSource: enabled ? "qrc:/images/controls/chevron-down.svg" : ""
Keys.onEnterPressed: this.clicked()
Keys.onReturnPressed: this.clicked()
onClicked: {
if (ConnectionController.isConnectionInProgress) {
PageController.showNotificationMessage(qsTr("Unable change protocol while trying to make an active connection"))
return
}
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
return
}
protocolSelectionDrawer.openTriggered()
}
}
}
}
ColumnLayout {
@@ -479,4 +573,119 @@ PageType {
}
}
}
DrawerType2 {
id: protocolSelectionDrawer
objectName: "protocolSelectionDrawer"
anchors.fill: parent
expandedStateContent: Item {
id: protocolDrawerContainer
implicitHeight: root.height * 0.5
Component.onCompleted: {
protocolSelectionDrawer.expandedHeight = protocolDrawerContainer.implicitHeight
}
ColumnLayout {
id: protocolDrawerHeader
anchors.top: parent.top
anchors.left: parent.left
anchors.right: parent.right
anchors.topMargin: 16
BackButtonType {
id: protocolDrawerBackButton
Layout.fillWidth: true
backButtonImage: "qrc:/images/controls/arrow-left.svg"
backButtonFunction: function() { protocolSelectionDrawer.closeTriggered() }
}
Header2Type {
Layout.fillWidth: true
Layout.topMargin: 16
Layout.leftMargin: 16
Layout.rightMargin: 16
headerText: qsTr("VPN protocol")
}
}
ListViewType {
id: protocolDrawerListView
anchors.top: protocolDrawerHeader.bottom
anchors.left: parent.left
anchors.right: parent.right
anchors.bottom: parent.bottom
anchors.topMargin: 16
model: root.apiAvailableProtocols
ButtonGroup {
id: protocolDrawerButtonGroup
}
delegate: Item {
implicitWidth: protocolDrawerListView.width
implicitHeight: protocolDrawerDelegate.implicitHeight
ColumnLayout {
id: protocolDrawerDelegate
anchors.fill: parent
anchors.leftMargin: 16
anchors.rightMargin: 16
VerticalRadioButton {
id: protocolDrawerRadioButton
Layout.fillWidth: true
text: root.protocolDisplayName(modelData)
ButtonGroup.group: protocolDrawerButtonGroup
checkable: !ConnectionController.isConnected
checked: modelData === root.apiCurrentProtocol
onClicked: {
protocolSelectionDrawer.closeTriggered()
if (modelData === root.apiCurrentProtocol) {
return
}
if (ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
return
}
PageController.showBusyIndicator(true)
ServersUiController.setProcessedServerId(ServersUiController.defaultServerId)
SubscriptionUiController.setCurrentProtocol(ServersUiController.defaultServerId, modelData)
if (!SubscriptionUiController.updateServiceFromGateway(ServersUiController.defaultServerId, "", "", true)) {
SubscriptionUiController.setCurrentProtocol(ServersUiController.defaultServerId, root.apiCurrentProtocol)
}
root.updateApiProtocolState()
PageController.showBusyIndicator(false)
}
Keys.onEnterPressed: protocolDrawerRadioButton.clicked()
Keys.onReturnPressed: protocolDrawerRadioButton.clicked()
}
DividerType {
Layout.fillWidth: true
}
}
}
}
}
}
}
@@ -259,41 +259,9 @@ PageType {
}
DividerType {
visible: !root.isSubscriptionExpired && !root.isSubscriptionExpiringSoon
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase
}
SwitcherType {
id: switcher
readonly property bool isVlessProtocol: SubscriptionUiController.isVlessProtocol(ServersUiController.processedServerId)
readonly property bool isProtocolSwitchBlocked: ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
Layout.bottomMargin: 24
visible: ApiAccountInfoModel.data("isProtocolSelectionSupported")
enabled: !switcher.isProtocolSwitchBlocked
text: qsTr("Use VLESS protocol")
checked: switcher.isVlessProtocol
onToggled: function() {
if (ServersUiController.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
} else {
PageController.showBusyIndicator(true)
SubscriptionUiController.setCurrentProtocol(ServersUiController.processedServerId, switcher.isVlessProtocol ? "awg" : "vless")
SubscriptionUiController.updateServiceFromGateway(ServersUiController.processedServerId, "", "", true)
PageController.showBusyIndicator(false)
}
}
}
DividerType {
visible: footer.isVisibleForAmneziaFree
visible: (!root.isSubscriptionExpired && !root.isSubscriptionExpiringSoon
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase)
|| footer.isVisibleForAmneziaFree
}
WarningType {
+180
View File
@@ -0,0 +1,180 @@
#include "cryptoUtils.h"
#include <memory>
#include <QDebug>
#include <openssl/err.h>
#include <openssl/pem.h>
#include <openssl/rand.h>
namespace
{
constexpr int kAesKeySize = 32; // AES-256
constexpr int kAesIvSize = 16; // CBC block size
constexpr int kAesBlockSize = 16;
QByteArray lastOpenSslError()
{
return QByteArray(ERR_error_string(ERR_get_error(), nullptr));
}
}
QByteArray CryptoUtils::generateRandomBytes(int size)
{
if (size <= 0) {
qCritical() << "CryptoUtils::generateRandomBytes invalid size" << size;
return {};
}
QByteArray buffer(size, Qt::Uninitialized);
if (RAND_bytes(reinterpret_cast<unsigned char *>(buffer.data()), size) != 1) {
qCritical() << "CryptoUtils::generateRandomBytes RAND_bytes failed:" << lastOpenSslError();
return {};
}
return buffer;
}
QByteArray CryptoUtils::encryptAes256Cbc(const QByteArray &data, const QByteArray &key, const QByteArray &iv)
{
if (key.size() != kAesKeySize || iv.size() < kAesIvSize) {
qCritical() << "CryptoUtils::encryptAes256Cbc invalid key/iv size" << key.size() << iv.size();
return {};
}
std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> ctx { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
if (!ctx) {
qCritical() << "CryptoUtils::encryptAes256Cbc EVP_CIPHER_CTX_new failed:" << lastOpenSslError();
return {};
}
if (EVP_EncryptInit_ex(ctx.get(), EVP_aes_256_cbc(), nullptr, reinterpret_cast<const unsigned char *>(key.constData()),
reinterpret_cast<const unsigned char *>(iv.constData()))
!= 1) {
qCritical() << "CryptoUtils::encryptAes256Cbc EVP_EncryptInit_ex failed:" << lastOpenSslError();
return {};
}
QByteArray cipherText(data.size() + kAesBlockSize, Qt::Uninitialized);
int length = 0;
int cipherTextLength = 0;
if (EVP_EncryptUpdate(ctx.get(), reinterpret_cast<unsigned char *>(cipherText.data()), &length,
reinterpret_cast<const unsigned char *>(data.constData()), data.size())
!= 1) {
qCritical() << "CryptoUtils::encryptAes256Cbc EVP_EncryptUpdate failed:" << lastOpenSslError();
return {};
}
cipherTextLength = length;
if (EVP_EncryptFinal_ex(ctx.get(), reinterpret_cast<unsigned char *>(cipherText.data()) + cipherTextLength, &length) != 1) {
qCritical() << "CryptoUtils::encryptAes256Cbc EVP_EncryptFinal_ex failed:" << lastOpenSslError();
return {};
}
cipherTextLength += length;
cipherText.resize(cipherTextLength);
return cipherText;
}
QByteArray CryptoUtils::decryptAes256Cbc(const QByteArray &data, const QByteArray &key, const QByteArray &iv)
{
if (key.size() != kAesKeySize || iv.size() < kAesIvSize) {
qCritical() << "CryptoUtils::decryptAes256Cbc invalid key/iv size" << key.size() << iv.size();
return {};
}
std::unique_ptr<EVP_CIPHER_CTX, decltype(&EVP_CIPHER_CTX_free)> ctx { EVP_CIPHER_CTX_new(), EVP_CIPHER_CTX_free };
if (!ctx) {
qCritical() << "CryptoUtils::decryptAes256Cbc EVP_CIPHER_CTX_new failed:" << lastOpenSslError();
return {};
}
if (EVP_DecryptInit_ex(ctx.get(), EVP_aes_256_cbc(), nullptr, reinterpret_cast<const unsigned char *>(key.constData()),
reinterpret_cast<const unsigned char *>(iv.constData()))
!= 1) {
qCritical() << "CryptoUtils::decryptAes256Cbc EVP_DecryptInit_ex failed:" << lastOpenSslError();
return {};
}
QByteArray plainText(data.size() + kAesBlockSize, Qt::Uninitialized);
int length = 0;
int plainTextLength = 0;
if (EVP_DecryptUpdate(ctx.get(), reinterpret_cast<unsigned char *>(plainText.data()), &length,
reinterpret_cast<const unsigned char *>(data.constData()), data.size())
!= 1) {
qCritical() << "CryptoUtils::decryptAes256Cbc EVP_DecryptUpdate failed:" << lastOpenSslError();
return {};
}
plainTextLength = length;
if (EVP_DecryptFinal_ex(ctx.get(), reinterpret_cast<unsigned char *>(plainText.data()) + plainTextLength, &length) != 1) {
qCritical() << "CryptoUtils::decryptAes256Cbc EVP_DecryptFinal_ex failed:" << lastOpenSslError();
return {};
}
plainTextLength += length;
plainText.resize(plainTextLength);
return plainText;
}
EVP_PKEY *CryptoUtils::loadPublicKeyFromPem(const QByteArray &pem)
{
std::unique_ptr<BIO, decltype(&BIO_free)> bio { BIO_new_mem_buf(pem.constData(), pem.size()), BIO_free };
if (!bio) {
qCritical() << "CryptoUtils::loadPublicKeyFromPem BIO_new_mem_buf failed:" << lastOpenSslError();
return nullptr;
}
EVP_PKEY *key = PEM_read_bio_PUBKEY(bio.get(), nullptr, nullptr, nullptr);
if (!key) {
qCritical() << "CryptoUtils::loadPublicKeyFromPem PEM_read_bio_PUBKEY failed:" << lastOpenSslError();
return nullptr;
}
return key;
}
QByteArray CryptoUtils::rsaEncrypt(const QByteArray &data, EVP_PKEY *publicKey, int padding)
{
if (!publicKey) {
qCritical() << "CryptoUtils::rsaEncrypt public key is null";
return {};
}
std::unique_ptr<EVP_PKEY_CTX, decltype(&EVP_PKEY_CTX_free)> ctx { EVP_PKEY_CTX_new(publicKey, nullptr), EVP_PKEY_CTX_free };
if (!ctx) {
qCritical() << "CryptoUtils::rsaEncrypt EVP_PKEY_CTX_new failed:" << lastOpenSslError();
return {};
}
if (EVP_PKEY_encrypt_init(ctx.get()) != 1) {
qCritical() << "CryptoUtils::rsaEncrypt EVP_PKEY_encrypt_init failed:" << lastOpenSslError();
return {};
}
if (EVP_PKEY_CTX_set_rsa_padding(ctx.get(), padding) != 1) {
qCritical() << "CryptoUtils::rsaEncrypt EVP_PKEY_CTX_set_rsa_padding failed:" << lastOpenSslError();
return {};
}
const auto *plainData = reinterpret_cast<const unsigned char *>(data.constData());
std::size_t cipherTextLength = 0;
if (EVP_PKEY_encrypt(ctx.get(), nullptr, &cipherTextLength, plainData, data.size()) != 1) {
qCritical() << "CryptoUtils::rsaEncrypt couldn't determine buffer length:" << lastOpenSslError();
return {};
}
QByteArray cipherText(static_cast<int>(cipherTextLength), Qt::Uninitialized);
if (EVP_PKEY_encrypt(ctx.get(), reinterpret_cast<unsigned char *>(cipherText.data()), &cipherTextLength, plainData, data.size())
!= 1) {
qCritical() << "CryptoUtils::rsaEncrypt EVP_PKEY_encrypt failed:" << lastOpenSslError();
return {};
}
cipherText.resize(static_cast<int>(cipherTextLength));
return cipherText;
}
+35
View File
@@ -0,0 +1,35 @@
#ifndef CRYPTOUTILS_H
#define CRYPTOUTILS_H
#include <QByteArray>
#include <openssl/evp.h>
// Thin wrapper around OpenSSL EVP used for local settings encryption and for
// the API-gateway request/response envelope. Replaces the unmaintained
// QSimpleCrypto library. All functions report failures by returning an empty
// QByteArray (or nullptr) and logging, without throwing.
//
// Wire compatibility note: encryptAes256Cbc/decryptAes256Cbc reproduce the exact
// behaviour of QSimpleCrypto::QBlockCipher (AES-256-CBC, PKCS7 padding, first
// 16 bytes of the IV buffer), so data written by previous app versions stays
// readable and gateway requests keep the same format.
namespace CryptoUtils
{
// Cryptographically secure random bytes of the requested size.
QByteArray generateRandomBytes(int size);
// AES-256-CBC with PKCS7 padding. key must be 32 bytes, iv must be at least
// 16 bytes (only the first 16 are used, matching the legacy behaviour).
QByteArray encryptAes256Cbc(const QByteArray &data, const QByteArray &key, const QByteArray &iv);
QByteArray decryptAes256Cbc(const QByteArray &data, const QByteArray &key, const QByteArray &iv);
// Loads an RSA public key from PEM bytes. Returns nullptr on failure.
// The caller owns the result and must free it with EVP_PKEY_free().
EVP_PKEY *loadPublicKeyFromPem(const QByteArray &pem);
// RSA encryption with the given OpenSSL padding (e.g. RSA_PKCS1_PADDING).
QByteArray rsaEncrypt(const QByteArray &data, EVP_PKEY *publicKey, int padding);
}
#endif // CRYPTOUTILS_H
+3 -17
View File
@@ -18,14 +18,9 @@ qt_standard_project_setup()
configure_file(${CMAKE_SOURCE_DIR}/version.h.in ${CMAKE_CURRENT_BINARY_DIR}/version.h)
set(QSIMPLECRYPTO_DIR ${CMAKE_CURRENT_LIST_DIR}/../../client/3rd/QSimpleCrypto/src)
include_directories(
${QSIMPLECRYPTO_DIR}
)
set(HEADERS
${CMAKE_CURRENT_LIST_DIR}/../../client/core/utils/utilities.h
${CMAKE_CURRENT_LIST_DIR}/../../common/crypto/cryptoUtils.h
${CMAKE_CURRENT_LIST_DIR}/../../client/secureQSettings.h
${CMAKE_CURRENT_LIST_DIR}/../../client/core/utils/networkUtilities.h
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h
@@ -38,16 +33,11 @@ set(HEADERS
${CMAKE_CURRENT_LIST_DIR}/systemservice.h
${CMAKE_CURRENT_LIST_DIR}/xray.h
${CMAKE_CURRENT_BINARY_DIR}/version.h
${QSIMPLECRYPTO_DIR}/include/QAead.h
${QSIMPLECRYPTO_DIR}/include/QBlockCipher.h
${QSIMPLECRYPTO_DIR}/include/QRsa.h
${QSIMPLECRYPTO_DIR}/include/QSimpleCrypto_global.h
${QSIMPLECRYPTO_DIR}/include/QX509.h
${QSIMPLECRYPTO_DIR}/include/QX509Store.h
)
set(SOURCES
${CMAKE_CURRENT_LIST_DIR}/../../client/core/utils/utilities.cpp
${CMAKE_CURRENT_LIST_DIR}/../../common/crypto/cryptoUtils.cpp
${CMAKE_CURRENT_LIST_DIR}/../../client/secureQSettings.cpp
${CMAKE_CURRENT_LIST_DIR}/../../client/core/utils/networkUtilities.cpp
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp
@@ -59,11 +49,6 @@ set(SOURCES
${CMAKE_CURRENT_LIST_DIR}/killswitch.cpp
${CMAKE_CURRENT_LIST_DIR}/systemservice.cpp
${CMAKE_CURRENT_LIST_DIR}/xray.cpp
${QSIMPLECRYPTO_DIR}/sources/QAead.cpp
${QSIMPLECRYPTO_DIR}/sources/QBlockCipher.cpp
${QSIMPLECRYPTO_DIR}/sources/QRsa.cpp
${QSIMPLECRYPTO_DIR}/sources/QX509.cpp
${QSIMPLECRYPTO_DIR}/sources/QX509Store.cpp
)
# Mozilla headres
@@ -297,6 +282,7 @@ include_directories(
${CMAKE_CURRENT_LIST_DIR}/../../client
${CMAKE_CURRENT_LIST_DIR}/../../ipc
${CMAKE_CURRENT_LIST_DIR}/../../common/logger
${CMAKE_CURRENT_LIST_DIR}/../../common/crypto
${CMAKE_CURRENT_BINARY_DIR}
)