Compare commits

..

1 Commits

Author SHA1 Message Date
Yaroslav Gurov
3ddd9c2f9c fix: handle undefined values properly 2026-06-13 22:45:41 +02:00
28 changed files with 166 additions and 797 deletions

3
.gitmodules vendored
View File

@@ -11,6 +11,9 @@
[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

View File

@@ -1,7 +1,7 @@
cmake_minimum_required(VERSION 3.25.0 FATAL_ERROR)
set(PROJECT AmneziaVPN)
set(AMNEZIAVPN_VERSION 4.8.19.0)
set(AMNEZIAVPN_VERSION 4.8.18.0)
project(${PROJECT} VERSION ${AMNEZIAVPN_VERSION}
DESCRIPTION "AmneziaVPN"
@@ -12,7 +12,7 @@ string(TIMESTAMP CURRENT_DATE "%Y-%m-%d")
set(RELEASE_DATE "${CURRENT_DATE}")
set(APP_MAJOR_VERSION ${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}.${CMAKE_PROJECT_VERSION_PATCH})
set(APP_ANDROID_VERSION_CODE 2129)
set(APP_ANDROID_VERSION_CODE 2128)
if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux")
set(MZ_PLATFORM_NAME "linux")

1
client/3rd/QSimpleCrypto vendored Submodule

View File

@@ -109,7 +109,6 @@ 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}
)

View File

@@ -792,16 +792,6 @@ class AmneziaActivity : QtActivity() {
else -> type = "*/*"
}
}
// Force system document picker to avoid third-party file managers
// that may lack storage permissions (common on Android TV devices)
val systemPickerPackage = listOf("com.google.android.documentsui", "com.android.documentsui")
.firstOrNull { pkg ->
try { packageManager.getPackageInfo(pkg, 0); true }
catch (_: PackageManager.NameNotFoundException) { false }
}
if (systemPickerPackage != null) {
`package` = systemPickerPackage
}
}
} else {
Intent(this@AmneziaActivity, TvFilePicker::class.java)
@@ -1074,11 +1064,13 @@ class AmneziaActivity : QtActivity() {
@Suppress("unused")
fun sendTouch(x: Float, y: Float) {
Log.v(TAG, "Send touch: $x, $y")
blockingCall {
findQtWindow(window.decorView)?.let {
Log.v(TAG, "Send touch to $it")
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN))
it.dispatchTouchEvent(createEvent(x, y, SystemClock.uptimeMillis(), MotionEvent.ACTION_UP))
}
}
}
private fun findQtWindow(view: View): View? {

View File

@@ -4,6 +4,7 @@ set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/Modules;${CMAKE_MODULE_PATH}")
add_subdirectory(${CLIENT_ROOT_DIR}/3rd/SortFilterProxyModel)
set(LIBS ${LIBS} SortFilterProxyModel)
include(${CLIENT_ROOT_DIR}/cmake/QSimpleCrypto.cmake)
include(${CLIENT_ROOT_DIR}/3rd/qrcodegen/qrcodegen.cmake)
@@ -109,6 +110,7 @@ include_directories(
${LIBSSH_INCLUDE_DIR}/include
${LIBSSH_ROOT_DIR}/include
${CLIENT_ROOT_DIR}/3rd/libssh/include
${CLIENT_ROOT_DIR}/3rd/QSimpleCrypto/src/include
${CLIENT_ROOT_DIR}/3rd/qtkeychain/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/qtkeychain
${CMAKE_CURRENT_BINARY_DIR}/3rd/libssh/include

View File

@@ -0,0 +1,21 @@
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
)

View File

@@ -23,11 +23,9 @@ set(HEADERS ${HEADERS}
${CMAKE_CURRENT_BINARY_DIR}/version.h
${CLIENT_ROOT_DIR}/core/sshclient.h
${CLIENT_ROOT_DIR}/core/networkUtilities.h
${CLIENT_ROOT_DIR}/core/payloadSender.h
${CLIENT_ROOT_DIR}/core/serialization/serialization.h
${CLIENT_ROOT_DIR}/core/serialization/transfer.h
${CLIENT_ROOT_DIR}/../common/logger/logger.h
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.h
${CLIENT_ROOT_DIR}/utils/qmlUtils.h
${CLIENT_ROOT_DIR}/core/api/apiUtils.h
${CLIENT_ROOT_DIR}/core/osSignalHandler.h
@@ -70,7 +68,6 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/protocols/vpnprotocol.cpp
${CLIENT_ROOT_DIR}/core/sshclient.cpp
${CLIENT_ROOT_DIR}/core/networkUtilities.cpp
${CLIENT_ROOT_DIR}/core/payloadSender.cpp
${CLIENT_ROOT_DIR}/core/serialization/outbound.cpp
${CLIENT_ROOT_DIR}/core/serialization/inbound.cpp
${CLIENT_ROOT_DIR}/core/serialization/ss.cpp
@@ -80,7 +77,6 @@ set(SOURCES ${SOURCES}
${CLIENT_ROOT_DIR}/core/serialization/vmess.cpp
${CLIENT_ROOT_DIR}/core/serialization/vmess_new.cpp
${CLIENT_ROOT_DIR}/../common/logger/logger.cpp
${CLIENT_ROOT_DIR}/../common/crypto/cryptoUtils.cpp
${CLIENT_ROOT_DIR}/utils/qmlUtils.cpp
${CLIENT_ROOT_DIR}/core/api/apiUtils.cpp
${CLIENT_ROOT_DIR}/core/osSignalHandler.cpp

View File

@@ -34,6 +34,7 @@ namespace apiDefs
constexpr QLatin1String serviceType("service_type");
constexpr QLatin1String cliVersion("cli_version");
constexpr QLatin1String cliName("cli_name");
constexpr QLatin1String supportedProtocols("supported_protocols");
constexpr QLatin1String vpnKey("vpn_key");
constexpr QLatin1String config("config");
@@ -51,7 +52,6 @@ namespace apiDefs
constexpr QLatin1String appLanguage("app_language");
constexpr QLatin1String availableCountries("available_countries");
constexpr QLatin1String availableProtocols("available_protocols");
constexpr QLatin1String activeDeviceCount("active_device_count");
constexpr QLatin1String maxDeviceCount("max_device_count");
constexpr QLatin1String subscriptionEndDate("subscription_end_date");

View File

@@ -12,13 +12,14 @@
#include <QPromise>
#include <QUrl>
#include <openssl/rsa.h>
#include "QBlockCipher.h"
#include "QRsa.h"
#include "amnezia_application.h"
#include "cryptoUtils.h"
#include "core/api/apiUtils.h"
#include "core/networkUtilities.h"
#include "settings.h"
#include "utilities.h"
#ifdef AMNEZIA_DESKTOP
#include "core/ipcclient.h"
@@ -68,24 +69,27 @@ namespace
bool decryptProxyUrlsPayload(const QByteArray &encryptedPayload, bool isDevEnvironment, QByteArray &decryptedPayload)
{
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();
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 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);
decryptedPayload = CryptoUtils::decryptAes256Cbc(ba, decKey, iv);
if (decryptedPayload.isEmpty()) {
return false;
QSimpleCrypto::QBlockCipher cipher;
decryptedPayload = cipher.decryptAesBlockCipher(ba, decKey, iv);
} else {
decryptedPayload = encryptedPayload;
}
} else {
decryptedPayload = encryptedPayload;
return true;
} catch (...) {
Utils::logException();
return false;
}
return true;
}
QStringList readCachedProxyUrls(const QByteArray &cachedProxyUrlsEncrypted, bool isDevEnvironment)
@@ -153,29 +157,40 @@ GatewayController::EncryptedRequestData GatewayController::prepareRequest(const
}
#endif
encRequestData.key = CryptoUtils::generateRandomBytes(32);
encRequestData.iv = CryptoUtils::generateRandomBytes(32);
encRequestData.salt = CryptoUtils::generateRandomBytes(8);
QSimpleCrypto::QBlockCipher blockCipher;
encRequestData.key = blockCipher.generatePrivateSalt(32);
encRequestData.iv = blockCipher.generatePrivateSalt(32);
encRequestData.salt = blockCipher.generatePrivateSalt(8);
QJsonObject keyPayload;
keyPayload[configKey::aesKey] = QString(encRequestData.key.toBase64());
keyPayload[configKey::aesIv] = QString(encRequestData.iv.toBase64());
keyPayload[configKey::aesSalt] = QString(encRequestData.salt.toBase64());
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;
}
QByteArray encryptedKeyPayload;
QByteArray encryptedApiPayload;
try {
QSimpleCrypto::QRsa rsa;
QByteArray encryptedKeyPayload = CryptoUtils::rsaEncrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
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 encryptedApiPayload = CryptoUtils::encryptAes256Cbc(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv);
encryptedKeyPayload = rsa.encrypt(QJsonDocument(keyPayload).toJson(), publicKey, RSA_PKCS1_PADDING);
EVP_PKEY_free(publicKey);
if (encryptedKeyPayload.isEmpty() || encryptedApiPayload.isEmpty()) {
encryptedApiPayload = blockCipher.encryptAesBlockCipher(QJsonDocument(apiPayload).toJson(), encRequestData.key, encRequestData.iv,
"", encRequestData.salt);
} catch (...) {
Utils::logException();
qCritical() << "error when encrypting the request body";
encRequestData.errorCode = ErrorCode::ApiConfigDecryptionError;
return encRequestData;
@@ -197,11 +212,11 @@ GatewayController::DecryptionResult GatewayController::tryDecryptResponseBody(co
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
QByteArray decrypted = CryptoUtils::decryptAes256Cbc(encryptedResponseBody, key, iv);
if (!decrypted.isEmpty()) {
result.decryptedBody = decrypted;
try {
QSimpleCrypto::QBlockCipher blockCipher;
result.decryptedBody = blockCipher.decryptAesBlockCipher(encryptedResponseBody, key, iv, "", salt);
result.isDecryptionSuccessful = true;
} else {
} catch (...) {
result.decryptedBody = encryptedResponseBody;
result.isDecryptionSuccessful = false;
}
@@ -320,6 +335,7 @@ 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();

View File

@@ -1,198 +0,0 @@
#include "payloadSender.h"
#include <QDebug>
#include <QEventLoop>
#include <QHostAddress>
#include <QJsonObject>
#include <QRandomGenerator>
#include <QRegularExpression>
#include <QTcpSocket>
#include <QTimer>
#include <QUdpSocket>
#include "core/networkUtilities.h"
#include "protocols/protocols_defs.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(config_key::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(config_key::sendPayloadProtocol).toString().toLower();
const int timeoutMs = entry.value(config_key::sendPayloadTimeoutMs).toInt();
const QByteArray payload = buildPayload(entry.value(config_key::sendPayloadData).toString());
const QByteArray expectedResponse = buildPayload(entry.value(config_key::sendPayloadExpectedResponse).toString());
QUdpSocket udpSocket;
QTcpSocket tcpSocket;
QAbstractSocket *socket = nullptr;
if (protocol == protoUdp) {
socket = &udpSocket;
} else if (protocol == protoTcp) {
socket = &tcpSocket;
} else {
qWarning() << "PayloadSender: skipping entry" << index << "- unsupported protocol" << protocol;
return;
}
const QString resolvedHost = NetworkUtilities::getIPAddress(host);
const QHostAddress hostAddress(resolvedHost.isEmpty() ? host : resolvedHost);
socket->connectToHost(hostAddress, port);
if (!socket->waitForConnected(timeoutMs)) {
qWarning() << "PayloadSender: entry" << index << "-" << protocol << "connect failed to" << endpoint << socket->errorString();
return;
}
bool writeOk = false;
if (protocol == protoUdp) {
writeOk = udpSocket.writeDatagram(payload, hostAddress, port) >= 0;
} else {
writeOk = tcpSocket.write(payload) == payload.size();
tcpSocket.flush();
}
if (!writeOk) {
qWarning() << "PayloadSender: entry" << index << "-" << protocol << "write failed to" << endpoint;
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;
}
}

View File

@@ -1,35 +0,0 @@
#ifndef PAYLOADSENDER_H
#define PAYLOADSENDER_H
#include <QByteArray>
#include <QJsonArray>
#include <QString>
/*
* Handles the optional "send_payload" block that may be attached to a server
* configuration received from the gateway. Before establishing the VPN tunnel
* the client sends the described UDP/TCP packet(s) to the given endpoint(s)
* (port-knocking / DPI warm-up) and, when an expected response is provided,
* waits for it within the given timeout.
*
* The payload and expected response use the AmneziaWG special-junk tag syntax:
* <b 0xHEX> - append the literal bytes decoded from the hex string
* <r N> - append N random bytes
* Unknown tags are ignored.
*/
class PayloadSender
{
public:
// Sends every entry of the "send_payload" array. A failed / mismatched /
// timed-out entry is skipped and processing continues with the next one.
// Always best-effort: never blocks the connection from proceeding.
static void sendAll(const QJsonArray &sendPayload);
// Exposed for reuse/testing: builds a byte buffer from the tag string.
static QByteArray buildPayload(const QString &tagString);
private:
static void sendEntry(const QJsonObject &entry, int index);
};
#endif // PAYLOADSENDER_H

View File

@@ -402,13 +402,13 @@ bool Daemon::parseConfig(const QJsonObject& obj, InterfaceConfig& config) {
if (const auto s1 = obj.value("S1"); !s1.isUndefined()) {
config.m_initPacketJunkSize = s1.toString();
}
if (const auto s2 = obj.value("S2"); !s2.isUndefined()) {
if (const auto s2 = obj.value("s2"); !s2.isUndefined()) {
config.m_responsePacketJunkSize = s2.toString();
}
if (const auto s3 = obj.value("S3"); !s3.isUndefined()) {
config.m_cookieReplyPacketJunkSize = s3.toString();
}
if (const auto s4 = obj.value("S4"); !s4.isUndefined()) {
if (const auto s4 = obj.value("s4"); !s4.isUndefined()) {
config.m_transportPacketJunkSize = s4.toString();
}

View File

@@ -1,6 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 525 B

View File

@@ -98,13 +98,6 @@ namespace amnezia
constexpr char configVersion[] = "config_version";
constexpr char sendPayload[] = "send_payload";
constexpr char sendPayloadEndpoint[] = "endpoint";
constexpr char sendPayloadTimeoutMs[] = "timeout_ms";
constexpr char sendPayloadProtocol[] = "protocol";
constexpr char sendPayloadData[] = "payload";
constexpr char sendPayloadExpectedResponse[] = "expected_response";
constexpr char splitTunnelSites[] = "splitTunnelSites";
constexpr char splitTunnelType[] = "splitTunnelType";

View File

@@ -8,7 +8,6 @@
<file>images/controls/app.svg</file>
<file>images/controls/archive-restore.svg</file>
<file>images/controls/arrow-left.svg</file>
<file>images/controls/arrow-left-right.svg</file>
<file>images/controls/arrow-right.svg</file>
<file>images/controls/bug.svg</file>
<file>images/controls/check.svg</file>

View File

@@ -1,6 +1,7 @@
#include "secure_qsettings.h"
#include "cryptoUtils.h"
#include "../client/3rd/QSimpleCrypto/src/include/QAead.h"
#include "../client/3rd/QSimpleCrypto/src/include/QBlockCipher.h"
#include "utilities.h"
#include <QDataStream>
#include <QDebug>
@@ -179,8 +180,11 @@ void SecureQSettings::clearSettings()
QByteArray SecureQSettings::encryptText(const QByteArray &value) const
{
QByteArray result = CryptoUtils::encryptAes256Cbc(value, getEncKey(), getEncIv());
if (result.isEmpty() && !value.isEmpty()) {
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;
@@ -188,8 +192,11 @@ QByteArray SecureQSettings::encryptText(const QByteArray &value) const
QByteArray SecureQSettings::decryptText(const QByteArray &ba) const
{
QByteArray result = CryptoUtils::decryptAes256Cbc(ba, getEncKey(), getEncIv());
if (result.isEmpty() && !ba.isEmpty()) {
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;
@@ -211,7 +218,8 @@ QByteArray SecureQSettings::getEncKey() const
if (m_key.isEmpty()) {
// Create new key
QByteArray key = CryptoUtils::generateRandomBytes(32);
QSimpleCrypto::QBlockCipher cipher;
QByteArray key = cipher.generatePrivateSalt(32);
if (key.isEmpty()) {
qCritical() << "SecureQSettings::getEncKey Unable to generate new enc key";
}
@@ -236,7 +244,8 @@ QByteArray SecureQSettings::getEncIv() const
if (m_iv.isEmpty()) {
// Create new IV
QByteArray iv = CryptoUtils::generateRandomBytes(32);
QSimpleCrypto::QBlockCipher cipher;
QByteArray iv = cipher.generatePrivateSalt(32);
if (iv.isEmpty()) {
qCritical() << "SecureQSettings::getEncIv Unable to generate new enc IV";
}

View File

@@ -227,10 +227,6 @@ namespace
serverConfig[config_key::containers] = newServerConfig.value(config_key::containers);
serverConfig[config_key::hostName] = newServerConfig.value(config_key::hostName);
if (newServerConfig.contains(config_key::sendPayload)) {
serverConfig[config_key::sendPayload] = newServerConfig.value(config_key::sendPayload);
}
if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
serverConfig[config_key::configVersion] = newServerConfig.value(config_key::configVersion);
serverConfig[config_key::description] = newServerConfig.value(config_key::description);
@@ -245,6 +241,9 @@ namespace
auto apiConfig = QJsonObject::fromVariantMap(map);
if (newServerConfig.value(config_key::configVersion).toInt() == apiDefs::ConfigSource::AmneziaGateway) {
apiConfig.insert(apiDefs::key::supportedProtocols,
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::supportedProtocols).toArray());
apiConfig.insert(apiDefs::key::serviceInfo,
QJsonDocument::fromJson(apiResponseBody).object().value(apiDefs::key::serviceInfo).toObject());
}
@@ -937,23 +936,6 @@ bool ApiConfigsController::updateServiceFromGateway(const int serverIndex, const
auto serverConfig = m_serversModel->getServerConfig(serverIndex);
auto apiConfig = serverConfig.value(configKey::apiConfig).toObject();
if (!newCountryCode.isEmpty()) {
const auto currentProtocol = apiConfig.value(configKey::serviceProtocol).toString();
const auto availableCountries = apiConfig.value(apiDefs::key::availableCountries).toArray();
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(currentProtocol)) {
apiConfig[configKey::serviceProtocol] = availableProtocols.first().toString();
}
break;
}
}
GatewayRequestData gatewayRequestData { QSysInfo::productType(),
QString(APP_VERSION),
m_settings->getAppLanguage().name().split("_").first(),

View File

@@ -6,13 +6,9 @@
#include <QApplication>
#endif
#include <QJsonArray>
#include "amnezia_application.h"
#include "utilities.h"
#include "core/controllers/vpnConfigurationController.h"
#include "core/payloadSender.h"
#include "protocols/protocols_defs.h"
#include "version.h"
ConnectionController::ConnectionController(const QSharedPointer<ServersModel> &serversModel,
@@ -65,12 +61,6 @@ void ConnectionController::openConnection()
auto dns = m_serversModel->getDnsPair(serverIndex);
auto vpnConfiguration = vpnConfigurationController.createVpnConfiguration(dns, serverConfig, containerConfig, container);
const QJsonArray sendPayload = serverConfig.value(config_key::sendPayload).toArray();
if (!sendPayload.isEmpty()) {
PayloadSender::sendAll(sendPayload);
}
emit connectToVpn(serverIndex, credentials, container, vpnConfiguration);
}

View File

@@ -73,6 +73,12 @@ 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 == apiDefs::ConfigType::AmneziaFreeV3) {
return false;
@@ -126,6 +132,10 @@ 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();
@@ -191,6 +201,7 @@ 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";

View File

@@ -20,6 +20,7 @@ public:
IsComponentVisibleRole,
IsSubscriptionRenewalAvailableRole,
HasExpiredWorkerRole,
IsProtocolSelectionSupportedRole,
IsSubscriptionExpiredRole,
IsSubscriptionExpiringSoonRole,
IsInAppPurchaseRole
@@ -55,6 +56,8 @@ private:
apiDefs::ConfigType configType;
QStringList supportedProtocols;
QString subscriptionDescription;
bool isInAppPurchase = false;

View File

@@ -1,8 +1,5 @@
#include "servers_model.h"
#include <QJsonArray>
#include <QStringList>
#include "core/api/apiDefs.h"
#include "core/controllers/serverController.h"
#include "core/networkUtilities.h"
@@ -166,26 +163,6 @@ QVariant ServersModel::data(const QModelIndex &index, int role) const
case ApiServerCountryCodeRole: {
return apiConfig.value(configKey::serverCountryCode).toString();
}
case ApiServiceProtocolRole: {
return apiConfig.value(configKey::serviceProtocol).toString();
}
case ApiAvailableProtocolsRole: {
const auto currentCountryCode = apiConfig.value(configKey::serverCountryCode).toString();
const auto availableCountries = apiConfig.value(configKey::availableCountries).toArray();
QStringList availableProtocols;
for (const auto &country : availableCountries) {
const auto countryObject = country.toObject();
if (countryObject.value(configKey::serverCountryCode).toString() != currentCountryCode) {
continue;
}
for (const auto &protocol : countryObject.value(apiDefs::key::availableProtocols).toArray()) {
availableProtocols.push_back(protocol.toString());
}
break;
}
return availableProtocols;
}
case HasAmneziaDns: {
QString primaryDns = server.value(config_key::dns1).toString();
return primaryDns == protocols::dns::amneziaDnsIp;
@@ -494,8 +471,6 @@ QHash<int, QByteArray> ServersModel::roleNames() const
roles[IsCountrySelectionAvailableRole] = "isCountrySelectionAvailable";
roles[ApiAvailableCountriesRole] = "apiAvailableCountries";
roles[ApiServerCountryCodeRole] = "apiServerCountryCode";
roles[ApiServiceProtocolRole] = "apiServiceProtocol";
roles[ApiAvailableProtocolsRole] = "apiAvailableProtocols";
roles[IsAdVisibleRole] = "isAdVisible";
roles[AdHeaderRole] = "adHeader";

View File

@@ -47,8 +47,6 @@ public:
IsCountrySelectionAvailableRole,
ApiAvailableCountriesRole,
ApiServerCountryCodeRole,
ApiServiceProtocolRole,
ApiAvailableProtocolsRole,
IsAdVisibleRole,
AdHeaderRole,
AdDescriptionRole,

View File

@@ -20,41 +20,6 @@ import "../Components"
PageType {
id: root
property var apiAvailableProtocols: []
property string apiCurrentProtocol: ""
readonly property bool isApiProtocolSelectionVisible: ServersModel.isDefaultServerFromApi && root.apiAvailableProtocols.length > 0
function updateApiProtocolState() {
if (ServersModel.isDefaultServerFromApi) {
root.apiAvailableProtocols = ServersModel.getDefaultServerData("apiAvailableProtocols")
root.apiCurrentProtocol = ServersModel.getDefaultServerData("apiServiceProtocol")
} 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: ServersModel
function onDefaultServerDefaultContainerChanged() {
root.updateApiProtocolState()
}
}
Connections {
target: Qt.application
@@ -244,10 +209,6 @@ PageType {
drawer.collapsedHeight = collapsed.implicitHeight
}
onImplicitHeightChanged: {
drawer.collapsedHeight = collapsed.implicitHeight
}
DividerType {
Layout.topMargin: 10
Layout.fillWidth: false
@@ -349,7 +310,7 @@ PageType {
objectName: "rowLayoutLabel"
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
Layout.topMargin: 8
Layout.bottomMargin: root.isApiProtocolSelectionVisible ? 8 : (drawer.isCollapsedStateActive ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16)
Layout.bottomMargin: drawer.isCollapsedStateActive ? 44 : ServersModel.isDefaultServerFromApi ? 61 : 16
spacing: 0
BasicButtonType {
@@ -403,61 +364,6 @@ 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 {
@@ -570,119 +476,4 @@ 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)
ServersModel.processedIndex = ServersModel.defaultIndex
ApiConfigsController.setCurrentProtocol(modelData)
if (!ApiConfigsController.updateServiceFromGateway(ServersModel.defaultIndex, "", "", true)) {
ApiConfigsController.setCurrentProtocol(root.apiCurrentProtocol)
}
root.updateApiProtocolState()
PageController.showBusyIndicator(false)
}
Keys.onEnterPressed: protocolDrawerRadioButton.clicked()
Keys.onReturnPressed: protocolDrawerRadioButton.clicked()
}
DividerType {
Layout.fillWidth: true
}
}
}
}
}
}
}

View File

@@ -251,9 +251,40 @@ PageType {
}
DividerType {
visible: (!root.isSubscriptionExpired && !root.isSubscriptionExpiringSoon
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase)
|| footer.isVisibleForAmneziaFree
visible: !root.isSubscriptionExpired && !root.isSubscriptionExpiringSoon
&& root.isSubscriptionRenewalAvailable && !root.isInAppPurchase
}
SwitcherType {
id: switcher
readonly property bool isVlessProtocol: ApiConfigsController.isVlessProtocol()
readonly property bool isProtocolSwitchBlocked: ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected
Layout.fillWidth: true
Layout.topMargin: 24
Layout.rightMargin: 16
Layout.leftMargin: 16
visible: ApiAccountInfoModel.data("isProtocolSelectionSupported")
enabled: !switcher.isProtocolSwitchBlocked
text: qsTr("Use VLESS protocol")
checked: switcher.isVlessProtocol
onToggled: function() {
if (ServersModel.isDefaultServerCurrentlyProcessed() && ConnectionController.isConnected) {
PageController.showNotificationMessage(qsTr("Cannot change protocol during active connection"))
} else {
PageController.showBusyIndicator(true)
ApiConfigsController.setCurrentProtocol(switcher.isVlessProtocol ? "awg" : "vless")
ApiConfigsController.updateServiceFromGateway(ServersModel.processedIndex, "", "", true)
PageController.showBusyIndicator(false)
}
}
}
DividerType {
visible: footer.isVisibleForAmneziaFree
}
WarningType {

View File

@@ -1,180 +0,0 @@
#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;
}

View File

@@ -1,35 +0,0 @@
#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

View File

@@ -29,6 +29,8 @@ elseif(LINUX)
set(AMNEZIA_XRAY_INCLUDE_DIR "${AMNEZIA_XRAY_ROOT_DIR}/linux/x86_64")
endif()
set(QSIMPLECRYPTO_DIR ${CMAKE_CURRENT_LIST_DIR}/../../client/3rd/QSimpleCrypto/src)
set(OPENSSL_ROOT_DIR "${CMAKE_CURRENT_LIST_DIR}/../../client/3rd-prebuilt/3rd-prebuilt/openssl/")
set(OPENSSL_LIBRARIES_DIR "${OPENSSL_ROOT_DIR}/lib")
@@ -63,11 +65,11 @@ set(OPENSSL_USE_STATIC_LIBS TRUE)
include_directories(
${AMNEZIA_XRAY_INCLUDE_DIR}
${OPENSSL_INCLUDE_DIR}
${QSIMPLECRYPTO_DIR}
)
set(HEADERS
${CMAKE_CURRENT_LIST_DIR}/../../client/utilities.h
${CMAKE_CURRENT_LIST_DIR}/../../common/crypto/cryptoUtils.h
${CMAKE_CURRENT_LIST_DIR}/../../client/secure_qsettings.h
${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.h
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipc.h
@@ -80,11 +82,16 @@ 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/utilities.cpp
${CMAKE_CURRENT_LIST_DIR}/../../common/crypto/cryptoUtils.cpp
${CMAKE_CURRENT_LIST_DIR}/../../client/secure_qsettings.cpp
${CMAKE_CURRENT_LIST_DIR}/../../client/core/networkUtilities.cpp
${CMAKE_CURRENT_LIST_DIR}/../../ipc/ipcserver.cpp
@@ -96,6 +103,11 @@ 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
@@ -334,7 +346,6 @@ 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}
)