From 90912f9231ec558708daa29671fd8e0e4d864e40 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 26 Jul 2024 00:55:13 +0300 Subject: [PATCH 01/20] Fix Windows IPsec --- .../protocols/ikev2_vpn_protocol_windows.cpp | 26 ++++++++++--------- .../PageSetupWizardProtocolSettings.qml | 3 ++- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/client/protocols/ikev2_vpn_protocol_windows.cpp b/client/protocols/ikev2_vpn_protocol_windows.cpp index 5c471e22..ac5966cb 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.cpp +++ b/client/protocols/ikev2_vpn_protocol_windows.cpp @@ -10,6 +10,7 @@ #include "ikev2_vpn_protocol_windows.h" #include "utilities.h" + static Ikev2Protocol* self = nullptr; static std::mutex rasDialFuncMutex; @@ -80,10 +81,10 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE case RASCS_AuthNotify: //qDebug()<<__FUNCTION__ << __LINE__; if (dwError != 0) { - //qDebug() << "have error" << dwError; + qDebug() << "have error" << dwError; setConnectionState(Vpn::ConnectionState::Disconnected); } else { - //qDebug() << "RASCS_AuthNotify but no error" << dwError; + qDebug() << "RASCS_AuthNotify but no error" << dwError; } break; case RASCS_AuthRetry: @@ -193,16 +194,16 @@ ErrorCode Ikev2Protocol::start() return ErrorCode::AmneziaServiceConnectionFailed; } - certInstallProcess->waitForSource(1000); + certInstallProcess->waitForSource(); if (!certInstallProcess->isInitialized()) { qWarning() << "IpcProcess replica is not connected!"; setLastError(ErrorCode::AmneziaServiceConnectionFailed); return ErrorCode::AmneziaServiceConnectionFailed; } certInstallProcess->setProgram(PermittedProcess::CertUtil); - QStringList arguments({"-f" , "-importpfx", - "-p", m_config[config_key::password].toString(), - certFile.fileName(), "NoExport" + QString password = QString("-p %1").arg(m_config[config_key::password].toString()); + QStringList arguments({"-f", "-importpfx", password, + QDir::toNativeSeparators(certFile.fileName()), "NoExport" }); certInstallProcess->setArguments(arguments); @@ -227,9 +228,8 @@ ErrorCode Ikev2Protocol::start() } { - auto adapterConfigProcess = new QProcess; - - adapterConfigProcess->setProgram("powershell"); + QProcess adapterConfigProcess; + adapterConfigProcess.setProgram("powershell"); QString arguments = QString("-command \"Set-VpnConnectionIPsecConfiguration\" " "-ConnectionName '%1' " "-AuthenticationTransformConstants GCMAES128 " @@ -240,10 +240,11 @@ ErrorCode Ikev2Protocol::start() "-DHGroup Group14 " "-PassThru -Force\"") .arg(tunnelName()); - adapterConfigProcess->setNativeArguments(arguments); - adapterConfigProcess->start(); - adapterConfigProcess->waitForFinished(5000); + adapterConfigProcess.setNativeArguments(arguments); + + adapterConfigProcess.start(); + adapterConfigProcess.waitForFinished(5000); } //*/ { @@ -299,6 +300,7 @@ bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){ auto ret = RasDial(NULL, NULL, &RasDialParams, 0, &RasDialFuncCallback, &hRasConn); + if (ret == ERROR_SUCCESS){ return true; } diff --git a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml index d3e4aa4a..d0841f67 100644 --- a/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml +++ b/client/ui/qml/Pages2/PageSetupWizardProtocolSettings.qml @@ -263,7 +263,8 @@ PageType { clickedFunc: function() { if (!port.textField.acceptableInput && - ContainerProps.containerTypeToString(dockerContainer) !== "torwebsite") { + ContainerProps.containerTypeToString(dockerContainer) !== "torwebsite" && + ContainerProps.containerTypeToString(dockerContainer) !== "ikev2") { port.errorText = qsTr("The port must be in the range of 1 to 65535") return } From b0b185027e377ecf3fd13e8ad5ba60b6a9a482b3 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Thu, 1 Aug 2024 21:37:56 +0300 Subject: [PATCH 02/20] Linux IPSec initial --- client/CMakeLists.txt | 9 ++ client/configurators/ikev2_configurator.cpp | 40 +++++++ client/configurators/ikev2_configurator.h | 4 + client/containers/containers_defs.cpp | 9 +- client/core/scripts_registry.cpp | 1 + client/core/scripts_registry.h | 3 +- client/protocols/ikev2_vpn_protocol_linux.cpp | 101 ++++++++++++++++++ client/protocols/ikev2_vpn_protocol_linux.h | 49 +++++++++ client/protocols/protocols_defs.h | 1 + client/protocols/vpnprotocol.cpp | 6 +- client/resources.qrc | 1 + client/server_scripts/ipsec/template.conf | 30 ++++++ ipc/ipc_interface.rep | 6 ++ ipc/ipcserver.cpp | 57 ++++++++++ ipc/ipcserver.h | 6 ++ 15 files changed, 313 insertions(+), 10 deletions(-) create mode 100644 client/protocols/ikev2_vpn_protocol_linux.cpp create mode 100644 client/protocols/ikev2_vpn_protocol_linux.h create mode 100644 client/server_scripts/ipsec/template.conf diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 1fc28b82..baad1b9a 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -306,6 +306,15 @@ endif() if(LINUX AND NOT ANDROID) set(LIBS ${LIBS} -static-libstdc++ -static-libgcc -ldl) link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux) + + set(HEADERS ${HEADERS} + ${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_linux.h + ) + + set(SOURCES ${SOURCES} + ${CMAKE_CURRENT_LIST_DIR}/protocols/ikev2_vpn_protocol_linux.cpp + ) + endif() if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) diff --git a/client/configurators/ikev2_configurator.cpp b/client/configurators/ikev2_configurator.cpp index 894a0e3d..fea17f49 100644 --- a/client/configurators/ikev2_configurator.cpp +++ b/client/configurators/ikev2_configurator.cpp @@ -64,6 +64,26 @@ QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, Do return ""; } +#if defined(Q_OS_LINUX) + QString config = m_serverController->replaceVars(amnezia::scriptData(ProtocolScriptType::ipsec_template, container), + m_serverController->genVarsForScript(credentials, container, containerConfig)); + + config.replace("$CLIENT_NAME", connData.clientId); + config.replace("$UUID1", QUuid::createUuid().toString()); + config.replace("$SERVER_ADDR", connData.host); + + QJsonObject jConfig; + jConfig[config_key::config] = config; + + jConfig[config_key::hostName] = connData.host; + jConfig[config_key::userName] = connData.clientId; + jConfig[config_key::cert] = QString(connData.clientCert.toBase64()); + jConfig[config_key::cacert] = QString(connData.caCert); + jConfig[config_key::password] = connData.password; + + return QJsonDocument(jConfig).toJson(); +#endif + return genIkev2Config(connData); } @@ -73,6 +93,7 @@ QString Ikev2Configurator::genIkev2Config(const ConnectionData &connData) config[config_key::hostName] = connData.host; config[config_key::userName] = connData.clientId; config[config_key::cert] = QString(connData.clientCert.toBase64()); + config[config_key::cacert] = QString(connData.caCert); config[config_key::password] = connData.password; return QJsonDocument(config).toJson(); @@ -115,3 +136,22 @@ QString Ikev2Configurator::genStrongSwanConfig(const ConnectionData &connData) return config; } + +QString Ikev2Configurator::processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString) +{ + processConfigWithDnsSettings(dns, protocolConfigString); + + QJsonObject json; + json[config_key::config] = protocolConfigString; + return QJsonDocument(json).toJson(); +} + +QString Ikev2Configurator::processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, + QString &protocolConfigString) +{ + processConfigWithDnsSettings(dns, protocolConfigString); + QJsonObject json; + json[config_key::config] = protocolConfigString; + return QJsonDocument(json).toJson(); +} diff --git a/client/configurators/ikev2_configurator.h b/client/configurators/ikev2_configurator.h index e3a85216..673e5139 100644 --- a/client/configurators/ikev2_configurator.h +++ b/client/configurators/ikev2_configurator.h @@ -27,6 +27,10 @@ public: QString genIkev2Config(const ConnectionData &connData); QString genMobileConfig(const ConnectionData &connData); QString genStrongSwanConfig(const ConnectionData &connData); + QString genIPSecConfig(const ConnectionData &connData); + + QString processConfigWithLocalSettings(const QPair &dns, const bool isApiConfig, QString &protocolConfigString); + QString processConfigWithExportSettings(const QPair &dns, const bool isApiConfig, QString &protocolConfigString); ConnectionData prepareIkev2Config(const ServerCredentials &credentials, DockerContainer container, ErrorCode &errorCode); diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 91d4b067..cdf14db8 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -277,7 +277,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c) bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) { -#ifdef Q_OS_WINDOWS +#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) return true; #elif defined(Q_OS_IOS) @@ -309,13 +309,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) case DockerContainer::SSXray: return true; default: return false; } - -#elif defined(Q_OS_LINUX) - switch (c) { - case DockerContainer::Ipsec: return false; - default: return true; - } - #else return false; #endif diff --git a/client/core/scripts_registry.cpp b/client/core/scripts_registry.cpp index 95b5df4a..15089992 100644 --- a/client/core/scripts_registry.cpp +++ b/client/core/scripts_registry.cpp @@ -50,6 +50,7 @@ QString amnezia::scriptName(ProtocolScriptType type) case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); case ProtocolScriptType::awg_template: return QLatin1String("template.conf"); case ProtocolScriptType::xray_template: return QLatin1String("template.json"); + case ProtocolScriptType::ipsec_template: return QLatin1String("template.conf"); default: return QString(); } } diff --git a/client/core/scripts_registry.h b/client/core/scripts_registry.h index d952dafb..ced7eb00 100644 --- a/client/core/scripts_registry.h +++ b/client/core/scripts_registry.h @@ -28,7 +28,8 @@ enum ProtocolScriptType { openvpn_template, wireguard_template, awg_template, - xray_template + xray_template, + ipsec_template }; diff --git a/client/protocols/ikev2_vpn_protocol_linux.cpp b/client/protocols/ikev2_vpn_protocol_linux.cpp new file mode 100644 index 00000000..9465036b --- /dev/null +++ b/client/protocols/ikev2_vpn_protocol_linux.cpp @@ -0,0 +1,101 @@ +#include +#include +#include + +#include + +#include + +#include "logger.h" +#include "ikev2_vpn_protocol_linux.h" +#include "utilities.h" +#include "core/ipcclient.h" +#include +#include +#include + + +static Ikev2Protocol* self = nullptr; + + +Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) : + VpnProtocol(configuration, parent) +{ + self = this; + readIkev2Configuration(configuration); +} + +Ikev2Protocol::~Ikev2Protocol() +{ + qDebug() << "IpsecProtocol::~IpsecProtocol()"; + disconnect_vpn(); + Ikev2Protocol::stop(); +} + +void Ikev2Protocol::stop() +{ + setConnectionState(Vpn::ConnectionState::Disconnected); + qDebug() << "IpsecProtocol::stop()"; +} + + +void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) +{ + QJsonObject ikev2_data = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); + m_config = QJsonDocument::fromJson(ikev2_data.value(config_key::config).toString().toUtf8()).object(); + +} + +ErrorCode Ikev2Protocol::start() +{ + STACK_OF(X509) *certstack = sk_X509_new_null(); + BIO *p12 = BIO_new(BIO_s_mem()); + + EVP_PKEY *pkey; + X509 *cert; + + BIO_write(p12, QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()), + QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()).size()); + + PKCS12 *pkcs12 = d2i_PKCS12_bio(p12, NULL); + PKCS12_parse(pkcs12, m_config[config_key::password].toString().toStdString().c_str(), &pkey, &cert, &certstack); + BIO *bio = BIO_new(BIO_s_mem()); + PEM_write_bio_X509(bio, cert); + + BUF_MEM *mem = NULL; + BIO_get_mem_ptr(bio, &mem); + + std::string pem(mem->data, mem->length); + qDebug() << pem; + + QString alias(pem.c_str()); + + IpcClient::Interface()->writeIPsecUserCert(alias, m_config[config_key::userName].toString()); + IpcClient::Interface()->writeIPsecConfig(m_config[config_key::config].toString()); + IpcClient::Interface()->writeIPsecCaCert(m_config[config_key::cacert].toString(), m_config[config_key::userName].toString()); + IpcClient::Interface()->writeIPsecPrivate(m_config[config_key::cert].toString(), m_config[config_key::userName].toString()); + IpcClient::Interface()->writeIPsecPrivatePass(m_config[config_key::password].toString(), m_config[config_key::userName].toString()); + + + setConnectionState(Vpn::ConnectionState::Connected); + return ErrorCode::NoError; +} +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool Ikev2Protocol::create_new_vpn(const QString & vpn_name, + const QString & serv_addr){ + qDebug() << "Ikev2Protocol::create_new_vpn()"; + return true; +} +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name){ + + return false; +} +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){ + return false; +} +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool Ikev2Protocol::disconnect_vpn(){ + return true; +} diff --git a/client/protocols/ikev2_vpn_protocol_linux.h b/client/protocols/ikev2_vpn_protocol_linux.h new file mode 100644 index 00000000..11ca2140 --- /dev/null +++ b/client/protocols/ikev2_vpn_protocol_linux.h @@ -0,0 +1,49 @@ +#ifndef IKEV2_VPN_PROTOCOL_LINUX_H +#define IKEV2_VPN_PROTOCOL_LINUX_H + +#include +#include +#include +#include +#include + +#include "vpnprotocol.h" + +#include +#include +#include +#include +#include +#include + +class Ikev2Protocol : public VpnProtocol +{ + Q_OBJECT + +public: + explicit Ikev2Protocol(const QJsonObject& configuration, QObject* parent = nullptr); + virtual ~Ikev2Protocol() override; + + ErrorCode start() override; + void stop() override; + + static QString tunnelName() { return "AmneziaVPN IKEv2"; } + + +private: + void readIkev2Configuration(const QJsonObject &configuration); + +private: + QJsonObject m_config; + + + bool create_new_vpn(const QString & vpn_name, + const QString & serv_addr); + bool delete_vpn_connection(const QString &vpn_name); + + bool connect_to_vpn(const QString & vpn_name); + bool disconnect_vpn(); +}; + + +#endif // IKEV2_VPN_PROTOCOL_LINUX_H diff --git a/client/protocols/protocols_defs.h b/client/protocols/protocols_defs.h index 56be0d7d..39d0b3aa 100644 --- a/client/protocols/protocols_defs.h +++ b/client/protocols/protocols_defs.h @@ -24,6 +24,7 @@ namespace amnezia constexpr char description[] = "description"; constexpr char name[] = "name"; constexpr char cert[] = "cert"; + constexpr char cacert[] = "cacert"; constexpr char config[] = "config"; constexpr char containers[] = "containers"; diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index 056089b8..765e86ab 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -16,6 +16,10 @@ #include "ikev2_vpn_protocol_windows.h" #endif +#ifdef Q_OS_LINUX +#include "ikev2_vpn_protocol_linux.h" +#endif + VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent) : QObject(parent), m_connectionState(Vpn::ConnectionState::Unknown), @@ -106,7 +110,7 @@ QString VpnProtocol::vpnGateway() const VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) { switch (container) { -#if defined(Q_OS_WINDOWS) +#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) case DockerContainer::Ipsec: return new Ikev2Protocol(configuration); #endif #if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) diff --git a/client/resources.qrc b/client/resources.qrc index 98f04802..a12b0805 100644 --- a/client/resources.qrc +++ b/client/resources.qrc @@ -236,5 +236,6 @@ server_scripts/socks5_proxy/Dockerfile server_scripts/socks5_proxy/configure_container.sh server_scripts/socks5_proxy/start.sh + server_scripts/ipsec/template.conf diff --git a/client/server_scripts/ipsec/template.conf b/client/server_scripts/ipsec/template.conf new file mode 100644 index 00000000..53fa44bd --- /dev/null +++ b/client/server_scripts/ipsec/template.conf @@ -0,0 +1,30 @@ +config setup + charondebug="ike 1, knl 1, cfg 0" + uniqueids=no + +conn ikev2-vpn + auto=add + compress=no + type=tunnel + keyexchange=ikev2 + fragmentation=yes + forceencaps=yes + dpdaction=clear + dpddelay=300s + rekey=no + left=%any + leftid=$CLIENT_NAME + leftcert=$CLIENT_NAME.crt + leftsendcert=always + leftsubnet=0.0.0.0/0 + right=%any + rightid=%any + rightauth=rsa + rightsourceip=$IPSEC_VPN_L2TP_NET + rightdns=$PRIMARY_DNS,$SECONDARY_DNS + rightsendcert=never + eap_identity=%identity + ike=aes256-sha1-modp1024,aes128-sha1-modp1024 + esp=aes256-sha1,aes256-sha2_512 + + diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index 79f2d042..02e8c524 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -32,5 +32,11 @@ class IpcInterface SLOT( bool enablePeerTraffic( const QJsonObject &configStr) ); SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) ); SLOT( bool updateResolvers(const QString& ifname, const QList& resolvers) ); + + SLOT( bool writeIPsecCaCert(QString cacert, QString uuid) ); + SLOT( bool writeIPsecPrivate(QString privKey, QString uuid) ); + SLOT( bool writeIPsecConfig(QString config) ); + SLOT( bool writeIPsecUserCert(QString usercert, QString uuid) ); + SLOT( bool writeIPsecPrivatePass(QString pass, QString uuid) ); }; diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index c734912b..50138458 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -5,6 +5,7 @@ #include #include +#include "qjsonarray.h" #include "router.h" #include "logger.h" @@ -308,6 +309,62 @@ bool IpcServer::disableKillSwitch() return true; } +bool IpcServer::writeIPsecConfig(QString config) +{ + qDebug() << "IPSEC: IPSec config file"; + QString configFile = QString("/etc/ipsec.conf"); + QFile ipSecConfFile(configFile); + if (ipSecConfFile.open(QIODevice::WriteOnly)) { + ipSecConfFile.write(config.toUtf8()); + ipSecConfFile.close(); + } +} + +bool IpcServer::writeIPsecUserCert(QString usercert, QString uuid) +{ + qDebug() << "IPSEC: Write user cert " << uuid; + QString certName = QString("/etc/ipsec.d/certs/%1.crt").arg(uuid); + QFile userCertFile(certName); + if (userCertFile.open(QIODevice::WriteOnly)) { + userCertFile.write(usercert.toUtf8()); + userCertFile.close(); + } +} + +bool IpcServer::writeIPsecCaCert(QString cacert, QString uuid) +{ + qDebug() << "IPSEC: Write CA cert user " << uuid; + QString certName = QString("/etc/ipsec.d/cacerts/%1.crt").arg(uuid); + QFile caCertFile(certName); + if (caCertFile.open(QIODevice::WriteOnly)) { + caCertFile.write(cacert.toUtf8()); + caCertFile.close(); + } +} + +bool IpcServer::writeIPsecPrivate(QString privKey, QString uuid) +{ + qDebug() << "IPSEC: User private key " << uuid; + QString privateKey = QString("/etc/ipsec.d/private/%1.p12").arg(uuid); + QFile pKeyFile(privateKey); + if (pKeyFile.open(QIODevice::WriteOnly)) { + pKeyFile.write(QByteArray::fromBase64(privKey.toUtf8())); + pKeyFile.close(); + } +} + + +bool IpcServer::writeIPsecPrivatePass(QString pass, QString uuid) +{ + qDebug() << "IPSEC: User private key " << uuid; + QFile secretsFile("/etc/ipsec.secrets"); + QString P12 = QString(": P12 %1.p12 \"%2\" \n").arg(uuid, pass); + if (secretsFile.open(QIODevice::WriteOnly | QIODevice::Append)) { + secretsFile.write(P12.toUtf8()); + secretsFile.close(); + } +} + bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) { #ifdef Q_OS_WIN diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index bd474481..43ab3210 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -35,6 +35,12 @@ public: virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override; virtual bool disableKillSwitch() override; virtual bool updateResolvers(const QString& ifname, const QList& resolvers) override; + virtual bool writeIPsecCaCert(QString cacert, QString uuid) override; + virtual bool writeIPsecPrivate(QString privKey, QString uuid) override; + virtual bool writeIPsecConfig(QString config) override; + virtual bool writeIPsecUserCert(QString usercert, QString uuid) override; + virtual bool writeIPsecPrivatePass(QString pass, QString uuid) override; + private: int m_localpid = 0; From 89d4c18e87a5713aec597a14c6f1ae4529b97601 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 18 Aug 2024 01:46:06 +0300 Subject: [PATCH 03/20] Update IPSec configs templates --- .../ipsec/configure_container.sh | 1 + client/server_scripts/ipsec/template.conf | 21 ++++++++----------- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/client/server_scripts/ipsec/configure_container.sh b/client/server_scripts/ipsec/configure_container.sh index 76c4dfaf..0a103198 100644 --- a/client/server_scripts/ipsec/configure_container.sh +++ b/client/server_scripts/ipsec/configure_container.sh @@ -242,6 +242,7 @@ conn ikev2-cp dpdtimeout=120 dpdaction=clear auto=add + authby=rsa-sha1 ikev2=insist rekey=no pfs=no diff --git a/client/server_scripts/ipsec/template.conf b/client/server_scripts/ipsec/template.conf index 53fa44bd..d8cf6b1f 100644 --- a/client/server_scripts/ipsec/template.conf +++ b/client/server_scripts/ipsec/template.conf @@ -4,7 +4,6 @@ config setup conn ikev2-vpn auto=add - compress=no type=tunnel keyexchange=ikev2 fragmentation=yes @@ -12,19 +11,17 @@ conn ikev2-vpn dpdaction=clear dpddelay=300s rekey=no - left=%any leftid=$CLIENT_NAME leftcert=$CLIENT_NAME.crt + leftdns=$PRIMARY_DNS,$SECONDARY_DNS leftsendcert=always - leftsubnet=0.0.0.0/0 - right=%any - rightid=%any - rightauth=rsa - rightsourceip=$IPSEC_VPN_L2TP_NET - rightdns=$PRIMARY_DNS,$SECONDARY_DNS - rightsendcert=never - eap_identity=%identity - ike=aes256-sha1-modp1024,aes128-sha1-modp1024 - esp=aes256-sha1,aes256-sha2_512 + leftsourceip=%config + right=$SERVER_IP_ADDRESS + rightsubnet=0.0.0.0/0 + rightsendcert=never + eap_identity=%identity + encapsulation=yes + ike=aes256-sha256-modp2048,aes256-sha1-modp1024,3des-sha1-modp1024 + esp=aes256-sha256,aes256-sha1,3des-sha1 From 654d219e7eede94069d43cc8889fb519d44f2a16 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 18 Aug 2024 13:53:38 +0300 Subject: [PATCH 04/20] Fix Win Build for IPSec protocol --- ipc/ipcserver.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 50138458..cd0bcf57 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -311,6 +311,7 @@ bool IpcServer::disableKillSwitch() bool IpcServer::writeIPsecConfig(QString config) { +#ifdef Q_OS_LINUX qDebug() << "IPSEC: IPSec config file"; QString configFile = QString("/etc/ipsec.conf"); QFile ipSecConfFile(configFile); @@ -318,10 +319,13 @@ bool IpcServer::writeIPsecConfig(QString config) ipSecConfFile.write(config.toUtf8()); ipSecConfFile.close(); } +#endif + return true; } bool IpcServer::writeIPsecUserCert(QString usercert, QString uuid) { +#ifdef Q_OS_LINUX qDebug() << "IPSEC: Write user cert " << uuid; QString certName = QString("/etc/ipsec.d/certs/%1.crt").arg(uuid); QFile userCertFile(certName); @@ -329,10 +333,13 @@ bool IpcServer::writeIPsecUserCert(QString usercert, QString uuid) userCertFile.write(usercert.toUtf8()); userCertFile.close(); } +#endif + return true; } bool IpcServer::writeIPsecCaCert(QString cacert, QString uuid) { +#ifdef Q_OS_LINUX qDebug() << "IPSEC: Write CA cert user " << uuid; QString certName = QString("/etc/ipsec.d/cacerts/%1.crt").arg(uuid); QFile caCertFile(certName); @@ -340,10 +347,13 @@ bool IpcServer::writeIPsecCaCert(QString cacert, QString uuid) caCertFile.write(cacert.toUtf8()); caCertFile.close(); } +#endif + return true; } bool IpcServer::writeIPsecPrivate(QString privKey, QString uuid) { +#ifdef Q_OS_LINUX qDebug() << "IPSEC: User private key " << uuid; QString privateKey = QString("/etc/ipsec.d/private/%1.p12").arg(uuid); QFile pKeyFile(privateKey); @@ -351,11 +361,14 @@ bool IpcServer::writeIPsecPrivate(QString privKey, QString uuid) pKeyFile.write(QByteArray::fromBase64(privKey.toUtf8())); pKeyFile.close(); } +#endif + return true; } bool IpcServer::writeIPsecPrivatePass(QString pass, QString uuid) { +#ifdef Q_OS_LINUX qDebug() << "IPSEC: User private key " << uuid; QFile secretsFile("/etc/ipsec.secrets"); QString P12 = QString(": P12 %1.p12 \"%2\" \n").arg(uuid, pass); @@ -363,6 +376,8 @@ bool IpcServer::writeIPsecPrivatePass(QString pass, QString uuid) secretsFile.write(P12.toUtf8()); secretsFile.close(); } +#endif + return true; } bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) From 09c58cb39ef995976a45a3946e709c81f76d799d Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Mon, 19 Aug 2024 18:46:53 +0300 Subject: [PATCH 05/20] Fix certwrite for Win IPSec --- client/protocols/ikev2_vpn_protocol_windows.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/protocols/ikev2_vpn_protocol_windows.cpp b/client/protocols/ikev2_vpn_protocol_windows.cpp index ac5966cb..56cf5f6d 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.cpp +++ b/client/protocols/ikev2_vpn_protocol_windows.cpp @@ -172,7 +172,8 @@ void Ikev2Protocol::newConnectionStateEventReceived(UINT unMsg, tagRASCONNSTATE void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) { - m_config = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); + QJsonObject ikev2_data = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); + m_config = QJsonDocument::fromJson(ikev2_data.value(config_key::config).toString().toUtf8()).object(); } ErrorCode Ikev2Protocol::start() @@ -201,6 +202,7 @@ ErrorCode Ikev2Protocol::start() return ErrorCode::AmneziaServiceConnectionFailed; } certInstallProcess->setProgram(PermittedProcess::CertUtil); + QString password = QString("-p %1").arg(m_config[config_key::password].toString()); QStringList arguments({"-f", "-importpfx", password, QDir::toNativeSeparators(certFile.fileName()), "NoExport" From 2d3b9c2752b1e250307237e0cac44addf96f4c09 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 20 Aug 2024 13:44:33 +0300 Subject: [PATCH 06/20] Windows import PFX changes --- client/protocols/ikev2_vpn_protocol_windows.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/protocols/ikev2_vpn_protocol_windows.cpp b/client/protocols/ikev2_vpn_protocol_windows.cpp index 56cf5f6d..10bd4e97 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.cpp +++ b/client/protocols/ikev2_vpn_protocol_windows.cpp @@ -203,10 +203,12 @@ ErrorCode Ikev2Protocol::start() } certInstallProcess->setProgram(PermittedProcess::CertUtil); - QString password = QString("-p %1").arg(m_config[config_key::password].toString()); + QString password = QString("-p \"%1\"").arg(m_config[config_key::password].toString()); + QStringList arguments({"-f", "-importpfx", password, QDir::toNativeSeparators(certFile.fileName()), "NoExport" }); + certInstallProcess->setArguments(arguments); certInstallProcess->start(); From fb63cdf7e9e91aa78ba37618b353e7de158fa947 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 20 Aug 2024 22:45:06 +0300 Subject: [PATCH 07/20] Fix work with PKCS12 TempFile --- .../protocols/ikev2_vpn_protocol_windows.cpp | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/client/protocols/ikev2_vpn_protocol_windows.cpp b/client/protocols/ikev2_vpn_protocol_windows.cpp index 10bd4e97..ed6fb174 100644 --- a/client/protocols/ikev2_vpn_protocol_windows.cpp +++ b/client/protocols/ikev2_vpn_protocol_windows.cpp @@ -181,11 +181,13 @@ ErrorCode Ikev2Protocol::start() QByteArray cert = QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()); setConnectionState(Vpn::ConnectionState::Connecting); - QTemporaryFile certFile; - certFile.setAutoRemove(false); - certFile.open(); - certFile.write(cert); - certFile.close(); + QTemporaryFile * certFile = new QTemporaryFile; + certFile->setAutoRemove(false); + certFile->open(); + QString m_filename = certFile->fileName(); + certFile->write(cert); + certFile->close(); + delete certFile; { auto certInstallProcess = IpcClient::CreatePrivilegedProcess(); @@ -203,14 +205,11 @@ ErrorCode Ikev2Protocol::start() } certInstallProcess->setProgram(PermittedProcess::CertUtil); - QString password = QString("-p \"%1\"").arg(m_config[config_key::password].toString()); - - QStringList arguments({"-f", "-importpfx", password, - QDir::toNativeSeparators(certFile.fileName()), "NoExport" + QStringList arguments({"-f", "-importpfx", "-p", m_config[config_key::password].toString(), + QDir::toNativeSeparators(m_filename), "NoExport" }); certInstallProcess->setArguments(arguments); - certInstallProcess->start(); } // /* From a96f9dc18af6e98b79ac9e190ca8d8a71725c964 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 24 Aug 2024 00:57:47 +0300 Subject: [PATCH 08/20] Start and Stop for Linux tunnel --- client/protocols/ikev2_vpn_protocol_linux.cpp | 19 ++++---- ipc/ipc_interface.rep | 6 ++- ipc/ipcserver.cpp | 45 +++++++++++++++++-- ipc/ipcserver.h | 5 ++- 4 files changed, 60 insertions(+), 15 deletions(-) diff --git a/client/protocols/ikev2_vpn_protocol_linux.cpp b/client/protocols/ikev2_vpn_protocol_linux.cpp index 9465036b..6bf3205c 100644 --- a/client/protocols/ikev2_vpn_protocol_linux.cpp +++ b/client/protocols/ikev2_vpn_protocol_linux.cpp @@ -28,13 +28,13 @@ Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) Ikev2Protocol::~Ikev2Protocol() { qDebug() << "IpsecProtocol::~IpsecProtocol()"; - disconnect_vpn(); Ikev2Protocol::stop(); } void Ikev2Protocol::stop() { setConnectionState(Vpn::ConnectionState::Disconnected); + Ikev2Protocol::disconnect_vpn(); qDebug() << "IpsecProtocol::stop()"; } @@ -74,9 +74,10 @@ ErrorCode Ikev2Protocol::start() IpcClient::Interface()->writeIPsecConfig(m_config[config_key::config].toString()); IpcClient::Interface()->writeIPsecCaCert(m_config[config_key::cacert].toString(), m_config[config_key::userName].toString()); IpcClient::Interface()->writeIPsecPrivate(m_config[config_key::cert].toString(), m_config[config_key::userName].toString()); - IpcClient::Interface()->writeIPsecPrivatePass(m_config[config_key::password].toString(), m_config[config_key::userName].toString()); - + IpcClient::Interface()->writeIPsecPrivatePass(m_config[config_key::password].toString(), m_config[config_key::hostName].toString(), + m_config[config_key::userName].toString()); + connect_to_vpn("ikev2-vpn"); setConnectionState(Vpn::ConnectionState::Connected); return ErrorCode::NoError; } @@ -92,10 +93,12 @@ bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name){ return false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){ - return false; -} -//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -bool Ikev2Protocol::disconnect_vpn(){ +bool Ikev2Protocol::connect_to_vpn(const QString &vpn_name) { + IpcClient::Interface()->startIPsec(vpn_name); + return true; +} +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool Ikev2Protocol::disconnect_vpn() { + IpcClient::Interface()->stopIPsec("ikev2-vpn"); return true; } diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index 02e8c524..f29425e0 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -37,6 +37,10 @@ class IpcInterface SLOT( bool writeIPsecPrivate(QString privKey, QString uuid) ); SLOT( bool writeIPsecConfig(QString config) ); SLOT( bool writeIPsecUserCert(QString usercert, QString uuid) ); - SLOT( bool writeIPsecPrivatePass(QString pass, QString uuid) ); + SLOT( bool writeIPsecPrivatePass(QString pass, QString host, QString uuid) ); + + SLOT( bool stopIPsec(QString tunnelName) ); + SLOT( bool startIPsec(QString tunnelName) ); + }; diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index cd0bcf57..7034465c 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -182,6 +182,7 @@ void IpcServer::StartRoutingIpv6() { Router::StartRoutingIpv6(); } + void IpcServer::StopRoutingIpv6() { Router::StopRoutingIpv6(); @@ -201,7 +202,6 @@ void IpcServer::setLogsEnabled(bool enabled) } } - bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterIndex) { #ifdef Q_OS_WIN @@ -288,7 +288,6 @@ bool IpcServer::enableKillSwitch(const QJsonObject &configStr, int vpnAdapterInd MacOSFirewall::setAnchorEnabled(QStringLiteral("310.blockDNS"), true); MacOSFirewall::setAnchorTable(QStringLiteral("310.blockDNS"), true, QStringLiteral("dnsaddr"), dnsServers); #endif - return true; } @@ -309,6 +308,44 @@ bool IpcServer::disableKillSwitch() return true; } +bool IpcServer::startIPsec(QString tunnelName) +{ + QProcess process; + QStringList commands; + commands << "ipsec" << "up" << QString("%1").arg(tunnelName); + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not start ipsec tunnel!\n"; + return false; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not start ipsec tunnel\n"; + return false; + } + commands.clear(); +} + +bool IpcServer::stopIPsec(QString tunnelName) +{ + QProcess process; + QStringList commands; + commands << "ipsec" << "down" << QString("%1").arg(tunnelName); + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not stop ipsec tunnel\n"; + return false; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not stop ipsec tunnel\n"; + return false; + } + commands.clear(); +} + bool IpcServer::writeIPsecConfig(QString config) { #ifdef Q_OS_LINUX @@ -366,12 +403,12 @@ bool IpcServer::writeIPsecPrivate(QString privKey, QString uuid) } -bool IpcServer::writeIPsecPrivatePass(QString pass, QString uuid) +bool IpcServer::writeIPsecPrivatePass(QString pass, QString host, QString uuid) { #ifdef Q_OS_LINUX qDebug() << "IPSEC: User private key " << uuid; QFile secretsFile("/etc/ipsec.secrets"); - QString P12 = QString(": P12 %1.p12 \"%2\" \n").arg(uuid, pass); + QString P12 = QString("%any %1 : P12 %2.p12 \"%3\" \n").arg(host, uuid, pass); if (secretsFile.open(QIODevice::WriteOnly | QIODevice::Append)) { secretsFile.write(P12.toUtf8()); secretsFile.close(); diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index 43ab3210..63b195d3 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -39,8 +39,9 @@ public: virtual bool writeIPsecPrivate(QString privKey, QString uuid) override; virtual bool writeIPsecConfig(QString config) override; virtual bool writeIPsecUserCert(QString usercert, QString uuid) override; - virtual bool writeIPsecPrivatePass(QString pass, QString uuid) override; - + virtual bool writeIPsecPrivatePass(QString pass, QString host, QString uuid) override; + virtual bool stopIPsec(QString tunnelName) override; + virtual bool startIPsec(QString tunnelName) override; private: int m_localpid = 0; From 63c569c3d27cd85927c7ae87f6af93f1c951329f Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 25 Aug 2024 00:26:32 +0300 Subject: [PATCH 09/20] Setup routing for Linux IPSec --- client/protocols/ikev2_vpn_protocol_linux.cpp | 25 ++++++++++++++++++- client/protocols/ikev2_vpn_protocol_linux.h | 2 ++ ipc/ipcserver.cpp | 24 ++++++++++++++++++ 3 files changed, 50 insertions(+), 1 deletion(-) diff --git a/client/protocols/ikev2_vpn_protocol_linux.cpp b/client/protocols/ikev2_vpn_protocol_linux.cpp index 6bf3205c..692531db 100644 --- a/client/protocols/ikev2_vpn_protocol_linux.cpp +++ b/client/protocols/ikev2_vpn_protocol_linux.cpp @@ -6,6 +6,8 @@ #include +#include "core/networkUtilities.h" + #include "logger.h" #include "ikev2_vpn_protocol_linux.h" #include "utilities.h" @@ -23,6 +25,11 @@ Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) { self = this; readIkev2Configuration(configuration); + m_routeGateway = NetworkUtilities::getGatewayAndIface(); + m_vpnGateway = "192.168.43.10"; + m_vpnLocalAddress = "192.168.43.10"; + m_remoteAddress = configuration.value(amnezia::config_key::hostName).toString(); + m_routeMode = configuration.value(amnezia::config_key::splitTunnelType).toInt(); } Ikev2Protocol::~Ikev2Protocol() @@ -43,7 +50,6 @@ void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) { QJsonObject ikev2_data = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); m_config = QJsonDocument::fromJson(ikev2_data.value(config_key::config).toString().toUtf8()).object(); - } ErrorCode Ikev2Protocol::start() @@ -95,10 +101,27 @@ bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name){ //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bool Ikev2Protocol::connect_to_vpn(const QString &vpn_name) { IpcClient::Interface()->startIPsec(vpn_name); + + QThread::msleep(3000); + +#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) + // killSwitch toggle + if (QVariant(m_config.value(config_key::killSwitchOption).toString()).toBool()) { + IpcClient::Interface()->enableKillSwitch(m_config, 0); + } +#endif + if (m_routeMode == 0) { + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress); + } + IpcClient::Interface()->StopRoutingIpv6(); return true; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bool Ikev2Protocol::disconnect_vpn() { IpcClient::Interface()->stopIPsec("ikev2-vpn"); + IpcClient::Interface()->disableKillSwitch(); + IpcClient::Interface()->StartRoutingIpv6(); return true; } diff --git a/client/protocols/ikev2_vpn_protocol_linux.h b/client/protocols/ikev2_vpn_protocol_linux.h index 11ca2140..b4e2039d 100644 --- a/client/protocols/ikev2_vpn_protocol_linux.h +++ b/client/protocols/ikev2_vpn_protocol_linux.h @@ -35,6 +35,8 @@ private: private: QJsonObject m_config; + QString m_remoteAddress; + int m_routeMode; bool create_new_vpn(const QString & vpn_name, diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 7034465c..6500af69 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -310,6 +310,25 @@ bool IpcServer::disableKillSwitch() bool IpcServer::startIPsec(QString tunnelName) { +#ifdef Q_OS_LINUX +/* QProcess processSystemd; + QStringList commandsSystemd; + commandsSystemd << "systemctl" << "restart" << "ipsec"; + processSystemd.start("sudo", commandsSystemd); + if (!processSystemd.waitForStarted(1000)) + { + qDebug().noquote() << "Could not start ipsec tunnel!\n"; + return false; + } + else if (!processSystemd.waitForFinished(2000)) + { + qDebug().noquote() << "Could not start ipsec tunnel\n"; + return false; + } + commandsSystemd.clear(); + + QThread::msleep(2000); +*/ QProcess process; QStringList commands; commands << "ipsec" << "up" << QString("%1").arg(tunnelName); @@ -325,10 +344,13 @@ bool IpcServer::startIPsec(QString tunnelName) return false; } commands.clear(); +#endif + return true; } bool IpcServer::stopIPsec(QString tunnelName) { +#ifdef Q_OS_LINUX QProcess process; QStringList commands; commands << "ipsec" << "down" << QString("%1").arg(tunnelName); @@ -344,6 +366,8 @@ bool IpcServer::stopIPsec(QString tunnelName) return false; } commands.clear(); +#endif + return true; } bool IpcServer::writeIPsecConfig(QString config) From ad61ef0b2232606a1fd95f30d880e511de491996 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Thu, 29 Aug 2024 00:23:53 +0300 Subject: [PATCH 10/20] Cleanup ipsec.secrets from duplicates --- ipc/ipcserver.cpp | 37 ++++++++++++++++++++++++++++++++++--- 1 file changed, 34 insertions(+), 3 deletions(-) diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 6500af69..bcf78216 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -431,12 +431,43 @@ bool IpcServer::writeIPsecPrivatePass(QString pass, QString host, QString uuid) { #ifdef Q_OS_LINUX qDebug() << "IPSEC: User private key " << uuid; - QFile secretsFile("/etc/ipsec.secrets"); - QString P12 = QString("%any %1 : P12 %2.p12 \"%3\" \n").arg(host, uuid, pass); - if (secretsFile.open(QIODevice::WriteOnly | QIODevice::Append)) { + const QString secretsFilename = "/etc/ipsec.secrets"; + QStringList lines; + + { + QFile secretsFile(secretsFilename); + if (secretsFile.open(QIODevice::ReadOnly | QIODevice::Text)) + { + QTextStream edit(&secretsFile); + while (!edit.atEnd()) lines.push_back(edit.readLine()); + } + secretsFile.close(); + } + + for (auto iter = lines.begin(); iter!=lines.end();) + { + if (iter->contains(host)) + { + iter = lines.erase(iter); + } + else + { + ++iter; + } + } + + { + QFile secretsFile(secretsFilename); + if (secretsFile.open(QIODevice::WriteOnly | QIODevice::Text)) + { + QTextStream edit(&secretsFile); + for (int i=0; i Date: Thu, 29 Aug 2024 23:18:21 +0300 Subject: [PATCH 11/20] Restart IPSec service before VPN connect --- client/protocols/ikev2_vpn_protocol_linux.cpp | 5 ++--- ipc/ipcserver.cpp | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/client/protocols/ikev2_vpn_protocol_linux.cpp b/client/protocols/ikev2_vpn_protocol_linux.cpp index 692531db..57b0953f 100644 --- a/client/protocols/ikev2_vpn_protocol_linux.cpp +++ b/client/protocols/ikev2_vpn_protocol_linux.cpp @@ -45,7 +45,6 @@ void Ikev2Protocol::stop() qDebug() << "IpsecProtocol::stop()"; } - void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) { QJsonObject ikev2_data = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); @@ -89,12 +88,12 @@ ErrorCode Ikev2Protocol::start() } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bool Ikev2Protocol::create_new_vpn(const QString & vpn_name, - const QString & serv_addr){ + const QString & serv_addr) { qDebug() << "Ikev2Protocol::create_new_vpn()"; return true; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name){ +bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name) { return false; } diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index bcf78216..13418d3a 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -311,7 +311,7 @@ bool IpcServer::disableKillSwitch() bool IpcServer::startIPsec(QString tunnelName) { #ifdef Q_OS_LINUX -/* QProcess processSystemd; + QProcess processSystemd; QStringList commandsSystemd; commandsSystemd << "systemctl" << "restart" << "ipsec"; processSystemd.start("sudo", commandsSystemd); @@ -327,8 +327,8 @@ bool IpcServer::startIPsec(QString tunnelName) } commandsSystemd.clear(); - QThread::msleep(2000); -*/ + QThread::msleep(5000); + QProcess process; QStringList commands; commands << "ipsec" << "up" << QString("%1").arg(tunnelName); From 052261c2b4a3e58642679cf3b726c801660cbea7 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 30 Aug 2024 21:44:47 +0300 Subject: [PATCH 12/20] Get Linux IPSec tunnel status --- client/protocols/ikev2_vpn_protocol_linux.cpp | 64 ++++++++++++++----- ipc/ipc_interface.rep | 2 + ipc/ipcserver.cpp | 28 +++++++- ipc/ipcserver.h | 1 + 4 files changed, 78 insertions(+), 17 deletions(-) diff --git a/client/protocols/ikev2_vpn_protocol_linux.cpp b/client/protocols/ikev2_vpn_protocol_linux.cpp index 57b0953f..2fc85150 100644 --- a/client/protocols/ikev2_vpn_protocol_linux.cpp +++ b/client/protocols/ikev2_vpn_protocol_linux.cpp @@ -71,8 +71,6 @@ ErrorCode Ikev2Protocol::start() BIO_get_mem_ptr(bio, &mem); std::string pem(mem->data, mem->length); - qDebug() << pem; - QString alias(pem.c_str()); IpcClient::Interface()->writeIPsecUserCert(alias, m_config[config_key::userName].toString()); @@ -83,7 +81,54 @@ ErrorCode Ikev2Protocol::start() m_config[config_key::userName].toString()); connect_to_vpn("ikev2-vpn"); - setConnectionState(Vpn::ConnectionState::Connected); + + if (!IpcClient::Interface()) { + return ErrorCode::AmneziaServiceConnectionFailed; + } + + QString connectionStatus; + + auto futureResult = IpcClient::Interface()->getTunnelStatus("ikev2-vpn"); + futureResult.waitForFinished(); + + if (futureResult.returnValue().isEmpty()) { + auto futureResult = IpcClient::Interface()->getTunnelStatus("ikev2-vpn"); + futureResult.waitForFinished(); + } + + connectionStatus = futureResult.returnValue(); + + if (connectionStatus.contains("ESTABLISHED")) { + QStringList lines = connectionStatus.split('\n'); + for (auto iter = lines.begin(); iter!=lines.end(); iter++) + { + if (iter->contains("0.0.0.0/0")) { + + m_routeGateway = iter->split("===", Qt::SkipEmptyParts).first(); + m_routeGateway = m_routeGateway.split(" ").at(2); + m_routeGateway = m_routeGateway.split("/").first(); + qDebug() << "m_routeGateway " << m_routeGateway; + + // killSwitch toggle + if (QVariant(m_config.value(config_key::killSwitchOption).toString()).toBool()) { + IpcClient::Interface()->enableKillSwitch(m_config, 0); + } + + if (m_routeMode == 0) { + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress); + } + + IpcClient::Interface()->StopRoutingIpv6(); + + } + } + setConnectionState(Vpn::ConnectionState::Connected); + } else { + setConnectionState(Vpn::ConnectionState::Disconnected); + } + return ErrorCode::NoError; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -102,19 +147,6 @@ bool Ikev2Protocol::connect_to_vpn(const QString &vpn_name) { IpcClient::Interface()->startIPsec(vpn_name); QThread::msleep(3000); - -#if defined(Q_OS_LINUX) || defined(Q_OS_MACOS) - // killSwitch toggle - if (QVariant(m_config.value(config_key::killSwitchOption).toString()).toBool()) { - IpcClient::Interface()->enableKillSwitch(m_config, 0); - } -#endif - if (m_routeMode == 0) { - IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); - IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1"); - IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_remoteAddress); - } - IpcClient::Interface()->StopRoutingIpv6(); return true; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/ipc/ipc_interface.rep b/ipc/ipc_interface.rep index f29425e0..c180cb87 100644 --- a/ipc/ipc_interface.rep +++ b/ipc/ipc_interface.rep @@ -42,5 +42,7 @@ class IpcInterface SLOT( bool stopIPsec(QString tunnelName) ); SLOT( bool startIPsec(QString tunnelName) ); + SLOT( QString getTunnelStatus(QString tunnelName) ); + }; diff --git a/ipc/ipcserver.cpp b/ipc/ipcserver.cpp index 13418d3a..f2a2da4f 100644 --- a/ipc/ipcserver.cpp +++ b/ipc/ipcserver.cpp @@ -467,11 +467,37 @@ bool IpcServer::writeIPsecPrivatePass(QString pass, QString host, QString uuid) secretsFile.write(P12.toUtf8()); secretsFile.close(); } - #endif return true; } +QString IpcServer::getTunnelStatus(QString tunnelName) +{ +#ifdef Q_OS_LINUX + QProcess process; + QStringList commands; + commands << "ipsec" << "status" << QString("%1").arg(tunnelName); + process.start("sudo", commands); + if (!process.waitForStarted(1000)) + { + qDebug().noquote() << "Could not stop ipsec tunnel\n"; + return ""; + } + else if (!process.waitForFinished(2000)) + { + qDebug().noquote() << "Could not stop ipsec tunnel\n"; + return ""; + } + commands.clear(); + + + QString status = process.readAll(); + return status; +#endif + return QString(); + +} + bool IpcServer::enablePeerTraffic(const QJsonObject &configStr) { #ifdef Q_OS_WIN diff --git a/ipc/ipcserver.h b/ipc/ipcserver.h index 63b195d3..67c6f777 100644 --- a/ipc/ipcserver.h +++ b/ipc/ipcserver.h @@ -42,6 +42,7 @@ public: virtual bool writeIPsecPrivatePass(QString pass, QString host, QString uuid) override; virtual bool stopIPsec(QString tunnelName) override; virtual bool startIPsec(QString tunnelName) override; + virtual QString getTunnelStatus(QString tunnelName) override; private: int m_localpid = 0; From 948ab4cf718b25a66732a7fbdf9e31a4ad895e61 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 30 Aug 2024 22:10:39 +0300 Subject: [PATCH 13/20] Set local IPSec VPN address --- client/protocols/ikev2_vpn_protocol_linux.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/client/protocols/ikev2_vpn_protocol_linux.cpp b/client/protocols/ikev2_vpn_protocol_linux.cpp index 2fc85150..7c6904c2 100644 --- a/client/protocols/ikev2_vpn_protocol_linux.cpp +++ b/client/protocols/ikev2_vpn_protocol_linux.cpp @@ -107,8 +107,10 @@ ErrorCode Ikev2Protocol::start() m_routeGateway = iter->split("===", Qt::SkipEmptyParts).first(); m_routeGateway = m_routeGateway.split(" ").at(2); m_routeGateway = m_routeGateway.split("/").first(); + m_vpnLocalAddress = m_routeGateway; qDebug() << "m_routeGateway " << m_routeGateway; + // killSwitch toggle if (QVariant(m_config.value(config_key::killSwitchOption).toString()).toBool()) { IpcClient::Interface()->enableKillSwitch(m_config, 0); @@ -145,7 +147,6 @@ bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name) { //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bool Ikev2Protocol::connect_to_vpn(const QString &vpn_name) { IpcClient::Interface()->startIPsec(vpn_name); - QThread::msleep(3000); return true; } From 4147632a62867180793ec221d734dd48ed902eb5 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Fri, 30 Aug 2024 22:15:51 +0300 Subject: [PATCH 14/20] Fix Android build --- client/protocols/vpnprotocol.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index 765e86ab..40b22dca 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -110,7 +110,7 @@ QString VpnProtocol::vpnGateway() const VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) { switch (container) { -#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) +#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) case DockerContainer::Ipsec: return new Ikev2Protocol(configuration); #endif #if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) From 6f0e9a136b4309cfeb1d5da65e730f150d004cfa Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sat, 31 Aug 2024 14:06:32 -0700 Subject: [PATCH 15/20] MacOS IPSec initial --- AmneziaVPN.entitlements | 14 + client/cmake/macos.cmake | 2 + client/containers/containers_defs.cpp | 2 +- client/protocols/ikev2_vpn_protocol_mac.h | 62 +++ client/protocols/ikev2_vpn_protocol_mac.mm | 491 +++++++++++++++++++++ client/protocols/vpnprotocol.cpp | 10 +- 6 files changed, 576 insertions(+), 5 deletions(-) create mode 100644 AmneziaVPN.entitlements create mode 100644 client/protocols/ikev2_vpn_protocol_mac.h create mode 100644 client/protocols/ikev2_vpn_protocol_mac.mm diff --git a/AmneziaVPN.entitlements b/AmneziaVPN.entitlements new file mode 100644 index 00000000..10636b1b --- /dev/null +++ b/AmneziaVPN.entitlements @@ -0,0 +1,14 @@ + + + + + com.apple.developer.networking.networkextension + + packet-tunnel-provider + + com.apple.developer.networking.vpn.api + + allow-vpn + + + diff --git a/client/cmake/macos.cmake b/client/cmake/macos.cmake index 7b7cd381..17b6387a 100644 --- a/client/cmake/macos.cmake +++ b/client/cmake/macos.cmake @@ -25,10 +25,12 @@ set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15) set(HEADERS ${HEADERS} ${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.h + ${CMAKE_CURRENT_SOURCE_DIR}/protocols/ikev2_vpn_protocol_mac.h ) set(SOURCES ${SOURCES} ${CMAKE_CURRENT_SOURCE_DIR}/ui/macos_util.mm + ${CMAKE_CURRENT_SOURCE_DIR}/protocols/ikev2_vpn_protocol_mac.mm ) set(ICON_FILE ${CMAKE_CURRENT_SOURCE_DIR}/images/app.icns) diff --git a/client/containers/containers_defs.cpp b/client/containers/containers_defs.cpp index 7647c166..b7df2ab5 100644 --- a/client/containers/containers_defs.cpp +++ b/client/containers/containers_defs.cpp @@ -294,7 +294,7 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) #elif defined(Q_OS_MAC) switch (c) { case DockerContainer::WireGuard: return true; - case DockerContainer::Ipsec: return false; + case DockerContainer::Ipsec: return true; default: return true; } diff --git a/client/protocols/ikev2_vpn_protocol_mac.h b/client/protocols/ikev2_vpn_protocol_mac.h new file mode 100644 index 00000000..8d2e52f1 --- /dev/null +++ b/client/protocols/ikev2_vpn_protocol_mac.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include + + +#include "openvpnprotocol.h" + + +class Ikev2Protocol : public VpnProtocol +{ + Q_OBJECT +public: + explicit Ikev2Protocol(const QJsonObject& configuration, QObject* parent = nullptr); + virtual ~Ikev2Protocol() override; + + void readIkev2Configuration(const QJsonObject &configuration); + bool create_new_vpn(const QString &vpn_name, const QString &serv_addr); + bool delete_vpn_connection(const QString &vpn_name); + bool connect_to_vpn(const QString & vpn_name); + bool disconnect_vpn(); + void closeWindscribeActiveConnection(); + + ErrorCode start() override; + void stop() override; + + static QString tunnelName() { return "AmneziaVPN IKEv2"; } + +private slots: + void handleNotificationImpl(int status); + +private: + enum {STATE_DISCONNECTED, STATE_START_CONNECT, STATE_START_DISCONNECTING, STATE_CONNECTED, STATE_DISCONNECTING_AUTH_ERROR, STATE_DISCONNECTING_ANY_ERROR}; + + int state_; + + bool bConnected_; + mutable QRecursiveMutex mutex_; + void *notificationId_; + bool isStateConnectingAfterClick_; + bool isDisconnectClicked_; + + QString overrideDnsIp_; + + QJsonObject m_config; + + static constexpr int STATISTICS_UPDATE_PERIOD = 1000; + QTimer statisticsTimer_; + QString ipsecAdapterName_; + + int prevConnectionStatus_; + bool isPrevConnectionStatusInitialized_; + + // True if startConnect() method was called and NEVPNManager emitted notification NEVPNStatusConnecting. + // False otherwise. + bool isConnectingStateReachedAfterStartingConnection_; + + void handleNotification(void *notification); + bool isFailedAuthError(QMap &logs); + bool isSocketError(QMap &logs); + bool setCustomDns(const QString &overrideDnsIpAddress); +}; diff --git a/client/protocols/ikev2_vpn_protocol_mac.mm b/client/protocols/ikev2_vpn_protocol_mac.mm new file mode 100644 index 00000000..0c55c310 --- /dev/null +++ b/client/protocols/ikev2_vpn_protocol_mac.mm @@ -0,0 +1,491 @@ +#include "ikev2_vpn_protocol_mac.h" + + + +#include +#include +#include +#include +#import +#import +#include + +#include +#include +#include +#include + +#include +#include +#include +#include + +static NSString * const IKEv1ServiceName = @"AmneziaVPN"; +static NSString * const IKEv2ServiceName = @"AmneziaVPN IKEv2"; + +static Ikev2Protocol* self = nullptr; + + +Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) : + VpnProtocol(configuration, parent) +{ + qDebug() << "IpsecProtocol::IpsecProtocol()"; + self = this; + readIkev2Configuration(configuration); +} + +Ikev2Protocol::~Ikev2Protocol() +{ + qDebug() << "IpsecProtocol::~IpsecProtocol()"; + disconnect_vpn(); + Ikev2Protocol::stop(); +} + +void Ikev2Protocol::stop() +{ + setConnectionState(Vpn::ConnectionState::Disconnected); + qDebug() << "IpsecProtocol::stop()"; +} + + +void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) +{ + qDebug() << "IpsecProtocol::readIkev2Configuration"; + QJsonObject ikev2_data = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); + m_config = QJsonDocument::fromJson(ikev2_data.value(config_key::config).toString().toUtf8()).object(); +} + +CFDataRef CreatePersistentRefForIdentity(SecIdentityRef identity) +{ + CFTypeRef persistent_ref = NULL; + const void *keys[] = { kSecReturnPersistentRef, kSecValueRef }; + const void *values[] = { kCFBooleanTrue, identity }; + CFDictionaryRef dict = CFDictionaryCreate(NULL, keys, values, + sizeof(keys) / sizeof(*keys), NULL, NULL); + + + if (SecItemCopyMatching(dict, &persistent_ref) != 0) { + SecItemAdd(dict, &persistent_ref); + } + + if (dict) + CFRelease(dict); + + return (CFDataRef)persistent_ref; +} + + +ErrorCode Ikev2Protocol::start() +{ + + qDebug() << "IpsecProtocol::start"; + + static QMutex mutexLocal; + mutexLocal.lock(); + + setConnectionState(Vpn::ConnectionState::Disconnected); + NEVPNManager *manager = [NEVPNManager sharedManager]; + + NSString *nsUsername = m_config.value(amnezia::config_key::hostName).toString().toNSString(); + NSString *nsIp = m_config.value(amnezia::config_key::hostName).toString().toNSString(); + NSString *nsRemoteId = m_config.value(amnezia::config_key::hostName).toString().toNSString(); + + [manager loadFromPreferencesWithCompletionHandler:^(NSError *err) + { + mutexLocal.lock(); + + if (err) + { + qDebug() << "First load vpn preferences failed:" << QString::fromNSString(err.localizedDescription); + setConnectionState(Vpn::ConnectionState::Disconnected); + mutexLocal.unlock(); + } + else + { + + NSData *output = NULL; + + BIO *ibio, *obio = NULL; + BUF_MEM *bptr; + + + STACK_OF(X509) *certstack = sk_X509_new_null(); + BIO *p12 = BIO_new(BIO_s_mem()); + + EVP_PKEY *pkey; + X509 *cert; + + BIO_write(p12, QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()), + QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()).size()); + + PKCS12 *pkcs12 = d2i_PKCS12_bio(p12, NULL); + PKCS12_parse(pkcs12, m_config[config_key::password].toString().toStdString().c_str(), &pkey, &cert, &certstack); + + // We output everything in PEM + obio = BIO_new(BIO_s_mem()); + + // TODO: support protecting the private key with a PEM passphrase + if (pkey) + { + PEM_write_bio_PrivateKey(obio, pkey, NULL, NULL, 0, NULL, NULL); + } + + if (cert) + { + PEM_write_bio_X509(obio, cert); + } + + if (certstack && sk_X509_num(certstack)) + { + for (int i = 0; i < sk_X509_num(certstack); i++) + PEM_write_bio_X509_AUX(obio, sk_X509_value(certstack, i)); + } + + BIO_get_mem_ptr(obio, &bptr); + + output = [NSData dataWithBytes: bptr->data length: bptr->length]; + + NSData *PKCS12Data = [[NSData alloc] initWithBase64EncodedString:m_config[config_key::cert].toString().toNSString() options:0] ; + + CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); + OSStatus ret = SecPKCS12Import( + (__bridge CFDataRef)output, + (__bridge CFDictionaryRef)@{(id)kSecImportExportPassphrase:@""}, + &items); + + if (ret != errSecSuccess) { + qDebug() << "import err ret " << ret; + } + + NSDictionary *firstItem = [(__bridge_transfer NSArray *)items firstObject]; + SecIdentityRef identity = (__bridge SecIdentityRef)(firstItem[(__bridge id)kSecImportItemIdentity]); + + NEVPNProtocolIKEv2 *protocol = [[NEVPNProtocolIKEv2 alloc] init]; + protocol.serverAddress = nsIp; + protocol.certificateType = NEVPNIKEv2CertificateTypeRSA; + + protocol.remoteIdentifier = m_config.value(amnezia::config_key::hostName).toString().toNSString(); + + protocol.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate; + protocol.identityReference = (__bridge NSData *)CreatePersistentRefForIdentity(identity); + + protocol.useExtendedAuthentication = YES; + protocol.enablePFS = YES; + + protocol.IKESecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithmAES256; + protocol.IKESecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup19; + protocol.IKESecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithmSHA256; + protocol.IKESecurityAssociationParameters.lifetimeMinutes = 1440; + + protocol.childSecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithmAES256; + protocol.childSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup19; + protocol.childSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithmSHA256; + protocol.childSecurityAssociationParameters.lifetimeMinutes = 1440; + + [manager setEnabled:YES]; + [manager setProtocolConfiguration:(protocol)]; + [manager setOnDemandEnabled:NO]; + [manager setLocalizedDescription:@"Amnezia VPN"]; + + NSString *strProtocol = [NSString stringWithFormat:@"{Protocol: %@", protocol]; + qDebug() << QString::fromNSString(strProtocol); + + // do config stuff + [manager saveToPreferencesWithCompletionHandler:^(NSError *err) + { + if (err) + { + qDebug() << "First save vpn preferences failed:" << QString::fromNSString(err.localizedDescription); + setConnectionState(Vpn::ConnectionState::Disconnected); + mutexLocal.unlock(); + } + else + { + // load and save preferences again, otherwise Mac bug (https://forums.developer.apple.com/thread/25928) + [manager loadFromPreferencesWithCompletionHandler:^(NSError *err) + { + if (err) + { + qDebug() << "Second load vpn preferences failed:" << QString::fromNSString(err.localizedDescription); + setConnectionState(Vpn::ConnectionState::Disconnected); + mutexLocal.unlock(); + } + else + { + [manager saveToPreferencesWithCompletionHandler:^(NSError *err) + { + if (err) + { + qDebug() << "Second Save vpn preferences failed:" << QString::fromNSString(err.localizedDescription); + setConnectionState(Vpn::ConnectionState::Disconnected); + mutexLocal.unlock(); + } + else + { + notificationId_ = [[NSNotificationCenter defaultCenter] addObserverForName: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection queue: nil usingBlock: ^ (NSNotification *notification) + { + this->handleNotification(notification); + }]; + + qDebug() << "NEVPNConnection current status:" << (int)manager.connection.status; + + NSError *startError; + [manager.connection startVPNTunnelAndReturnError:&startError]; + if (startError) + { + qDebug() << "Error starting ikev2 connection:" << QString::fromNSString(startError.localizedDescription); + [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; + setConnectionState(Vpn::ConnectionState::Disconnected); + } + mutexLocal.unlock(); + } + }]; + } + }]; + } + }]; + } + }]; + + // waitConditionLocal.wait(&mutexLocal); + mutexLocal.unlock(); + + setConnectionState(Vpn::ConnectionState::Connected); + return ErrorCode::NoError; +} +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool Ikev2Protocol::create_new_vpn(const QString & vpn_name, + const QString & serv_addr){ + qDebug() << "Ikev2Protocol::create_new_vpn()"; + return true; +} +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name){ + + return false; +} +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){ + return false; +} +//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +bool Ikev2Protocol::disconnect_vpn() { + + QMutexLocker locker(&mutex_); + + NEVPNManager *manager = [NEVPNManager sharedManager]; + + // #713: If user had started connecting to IKev2 on Mac and quickly started after this connecting to Wireguard + + // then manager.connection.status doesn't have time to change to NEVPNStatusConnecting + // and remains NEVPNStatusDisconnected as it was before connection tries. + // Then we should check below isConnectingStateReachedAfterStartingConnection_ flag to be sure that connecting started. + // Without this check we will start connecting to the Wireguard when IKEv2 connecting process hasn't finished yet. + if (manager.connection.status == NEVPNStatusDisconnected && isConnectingStateReachedAfterStartingConnection_) + { + [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; + setConnectionState(Vpn::ConnectionState::Disconnected); + } + else + { + [manager.connection stopVPNTunnel]; + } + + return true; +} + + +void Ikev2Protocol::closeWindscribeActiveConnection() +{ + static QWaitCondition waitCondition; + static QMutex mutex; + + mutex.lock(); + + NEVPNManager *manager = [NEVPNManager sharedManager]; + if (manager) + { + [manager loadFromPreferencesWithCompletionHandler:^(NSError *err) + { + mutex.lock(); + if (!err) + { + NEVPNConnection * connection = [manager connection]; + if (connection.status == NEVPNStatusConnected || connection.status == NEVPNStatusConnecting) + { + if ([manager.localizedDescription isEqualToString:@"Amnezia VPN"] == YES) + { + qDebug() << "Previous IKEv2 connection is active. Stop it."; + [connection stopVPNTunnel]; + } + } + } + waitCondition.wakeAll(); + mutex.unlock(); + }]; + } + waitCondition.wait(&mutex); + mutex.unlock(); +} + +void Ikev2Protocol::handleNotificationImpl(int status) +{ + QMutexLocker locker(&mutex_); + + NEVPNManager *manager = [NEVPNManager sharedManager]; + + if (status == NEVPNStatusInvalid) + { + qDebug() << "Connection status changed: NEVPNStatusInvalid"; + [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; + setConnectionState(Vpn::ConnectionState::Disconnected); + + } + else if (status == NEVPNStatusDisconnected) + { + qDebug() << "Connection status changed: NEVPNStatusDisconnected"; + + if (state_ == STATE_DISCONNECTING_ANY_ERROR) + { + [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; + // state_ = STATE_DISCONNECTED; + // emit error(IKEV_FAILED_TO_CONNECT); + setConnectionState(Vpn::ConnectionState::Disconnected); + } + else if (state_ != STATE_DISCONNECTED) + { + + [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; + // state_ = STATE_DISCONNECTED; + setConnectionState(Vpn::ConnectionState::Disconnected); + } + } + else if (status == NEVPNStatusConnecting) + { + isConnectingStateReachedAfterStartingConnection_ = true; + qDebug() << "Connection status changed: NEVPNStatusConnecting"; + } + else if (status == NEVPNStatusConnected) + { + if (!overrideDnsIp_.isEmpty()) { + if (!setCustomDns(overrideDnsIp_)) { + qDebug() << "Failed to set custom DNS ip for ikev2"; + } + } + + qDebug() << "Connection status changed: NEVPNStatusConnected"; + + setConnectionState(Vpn::ConnectionState::Connected); + // note: route gateway not used for ikev2 in AdapterGatewayInfo + // AdapterGatewayInfo cai; + // ipsecAdapterName_ = NetworkUtils_mac::lastConnectedNetworkInterfaceName(); + // cai.setAdapterName(ipsecAdapterName_); + // cai.setAdapterIp(NetworkUtils_mac::ipAddressByInterfaceName(ipsecAdapterName_)); + //cai.setDnsServers(NetworkUtils_mac::getDnsServersForInterface(ipsecAdapterName_)); + } + else if (status == NEVPNStatusReasserting) + { + qDebug() << "Connection status changed: NEVPNStatusReasserting"; + setConnectionState(Vpn::ConnectionState::Connecting); + } + else if (status == NEVPNStatusDisconnecting) + { + qDebug() << "Connection status changed: NEVPNStatusDisconnecting"; + setConnectionState(Vpn::ConnectionState::Disconnecting); + /* if (state_ == STATE_START_CONNECT) + { + QMap logs = networkExtensionLog_.collectNext(); + for (QMap::iterator it = logs.begin(); it != logs.end(); ++it) + { + qDebug() << it.value(); + } + if (isSocketError(logs)) + { + state_ = STATE_DISCONNECTING_ANY_ERROR; + } + else + { + if (isFailedAuthError(logs)) + { + state_ = STATE_DISCONNECTING_AUTH_ERROR; + } + else + { + state_ = STATE_DISCONNECTING_ANY_ERROR; + } + } + }*/ + } + + prevConnectionStatus_ = status; + isPrevConnectionStatusInitialized_ = true; +} + + +void Ikev2Protocol::handleNotification(void *notification) +{ + QMutexLocker locker(&mutex_); + NSNotification *nsNotification = (NSNotification *)notification; + NEVPNConnection *connection = nsNotification.object; + QMetaObject::invokeMethod(this, "handleNotificationImpl", Q_ARG(int, (int)connection.status)); +} + +bool Ikev2Protocol::isFailedAuthError(QMap &logs) +{ + for (QMap::iterator it = logs.begin(); it != logs.end(); ++it) + { + if (it.value().contains("Failed", Qt::CaseInsensitive) && it.value().contains("IKE", Qt::CaseInsensitive) && it.value().contains("Auth", Qt::CaseInsensitive)) + { + if (!(it.value().contains("Failed", Qt::CaseInsensitive) && it.value().contains("IKEv2 socket", Qt::CaseInsensitive))) + { + return true; + } + } + } + return false; +} + +bool Ikev2Protocol::isSocketError(QMap &logs) +{ + for (QMap::iterator it = logs.begin(); it != logs.end(); ++it) + { + if (it.value().contains("Failed", Qt::CaseInsensitive) && it.value().contains("initialize", Qt::CaseInsensitive) && it.value().contains("socket", Qt::CaseInsensitive)) + { + return true; + } + } + return false; +} + +bool Ikev2Protocol::setCustomDns(const QString &overrideDnsIpAddress) +{ + // get list of entries of interest + // QStringList networkServices = NetworkUtils_mac::getListOfDnsNetworkServiceEntries(); + + // filter list to only ikev2 entries + QStringList dnsNetworkServices; + // for (const QString &service : networkServices) + // if (MacUtils::dynamicStoreEntryHasKey(service, "ConfirmedServiceID")) + // dnsNetworkServices.append(service); + + qDebug() << "Applying custom 'while connected' DNS change to network services: " << dnsNetworkServices; + + if (dnsNetworkServices.isEmpty()) { + qDebug() << "No network services to configure 'while connected' DNS"; + return false; + } + + // change DNS on each entry + bool successAll = true; + for (const QString &service : dnsNetworkServices) { + // if (!helper_->setDnsOfDynamicStoreEntry(overrideDnsIpAddress, service)) { + // successAll = false; + // qDebug() << "Failed to set network service DNS: " << service; + // break; + // } + } + + return successAll; +} + + diff --git a/client/protocols/vpnprotocol.cpp b/client/protocols/vpnprotocol.cpp index 40b22dca..7524b483 100644 --- a/client/protocols/vpnprotocol.cpp +++ b/client/protocols/vpnprotocol.cpp @@ -17,7 +17,11 @@ #endif #ifdef Q_OS_LINUX -#include "ikev2_vpn_protocol_linux.h" + #include "ikev2_vpn_protocol_linux.h" +#endif + +#ifdef Q_OS_MACX + #include "ikev2_vpn_protocol_mac.h" #endif VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent) @@ -110,9 +114,6 @@ QString VpnProtocol::vpnGateway() const VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) { switch (container) { -#if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID) - case DockerContainer::Ipsec: return new Ikev2Protocol(configuration); -#endif #if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) case DockerContainer::OpenVpn: return new OpenVpnProtocol(configuration); case DockerContainer::Cloak: return new OpenVpnOverCloakProtocol(configuration); @@ -121,6 +122,7 @@ VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject & case DockerContainer::Awg: return new WireguardProtocol(configuration); case DockerContainer::Xray: return new XrayProtocol(configuration); case DockerContainer::SSXray: return new XrayProtocol(configuration); + case DockerContainer::Ipsec: return new Ikev2Protocol(configuration); #endif default: return nullptr; } From f0e66e4ecf2368210be485223165f783745b9a55 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Sun, 1 Sep 2024 13:28:18 -0700 Subject: [PATCH 16/20] Certificate selection --- client/protocols/ikev2_vpn_protocol_mac.mm | 33 +++++++++++++--------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/client/protocols/ikev2_vpn_protocol_mac.mm b/client/protocols/ikev2_vpn_protocol_mac.mm index 0c55c310..6752e6f7 100644 --- a/client/protocols/ikev2_vpn_protocol_mac.mm +++ b/client/protocols/ikev2_vpn_protocol_mac.mm @@ -62,8 +62,7 @@ CFDataRef CreatePersistentRefForIdentity(SecIdentityRef identity) const void *values[] = { kCFBooleanTrue, identity }; CFDictionaryRef dict = CFDictionaryCreate(NULL, keys, values, sizeof(keys) / sizeof(*keys), NULL, NULL); - - + if (SecItemCopyMatching(dict, &persistent_ref) != 0) { SecItemAdd(dict, &persistent_ref); } @@ -74,6 +73,19 @@ CFDataRef CreatePersistentRefForIdentity(SecIdentityRef identity) return (CFDataRef)persistent_ref; } +NSData *searchKeychainCopyMatching(const char *certName) +{ + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + [dict setObject:(__bridge id)kSecClassCertificate forKey:(__bridge id)kSecClass]; + [dict setObject:[NSString stringWithUTF8String:certName] forKey:(__bridge id)kSecAttrLabel]; + [dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; + [dict setObject:@YES forKey:(__bridge id)kSecReturnPersistentRef]; + + CFTypeRef result = NULL; + SecItemCopyMatching((__bridge CFDictionaryRef)dict, &result); + + return (NSData *)result; +} ErrorCode Ikev2Protocol::start() { @@ -86,10 +98,6 @@ ErrorCode Ikev2Protocol::start() setConnectionState(Vpn::ConnectionState::Disconnected); NEVPNManager *manager = [NEVPNManager sharedManager]; - NSString *nsUsername = m_config.value(amnezia::config_key::hostName).toString().toNSString(); - NSString *nsIp = m_config.value(amnezia::config_key::hostName).toString().toNSString(); - NSString *nsRemoteId = m_config.value(amnezia::config_key::hostName).toString().toNSString(); - [manager loadFromPreferencesWithCompletionHandler:^(NSError *err) { mutexLocal.lock(); @@ -108,7 +116,6 @@ ErrorCode Ikev2Protocol::start() BIO *ibio, *obio = NULL; BUF_MEM *bptr; - STACK_OF(X509) *certstack = sk_X509_new_null(); BIO *p12 = BIO_new(BIO_s_mem()); @@ -161,15 +168,14 @@ ErrorCode Ikev2Protocol::start() SecIdentityRef identity = (__bridge SecIdentityRef)(firstItem[(__bridge id)kSecImportItemIdentity]); NEVPNProtocolIKEv2 *protocol = [[NEVPNProtocolIKEv2 alloc] init]; - protocol.serverAddress = nsIp; + protocol.serverAddress = m_config.value(amnezia::config_key::hostName).toString().toNSString(); protocol.certificateType = NEVPNIKEv2CertificateTypeRSA; protocol.remoteIdentifier = m_config.value(amnezia::config_key::hostName).toString().toNSString(); - protocol.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate; - protocol.identityReference = (__bridge NSData *)CreatePersistentRefForIdentity(identity); - - protocol.useExtendedAuthentication = YES; + protocol.identityReference = searchKeychainCopyMatching(m_config.value(amnezia::config_key::userName).toString().toLocal8Bit().data()); + + protocol.useExtendedAuthentication = NO; protocol.enablePFS = YES; protocol.IKESecurityAssociationParameters.encryptionAlgorithm = NEVPNIKEv2EncryptionAlgorithmAES256; @@ -339,12 +345,11 @@ void Ikev2Protocol::handleNotificationImpl(int status) qDebug() << "Connection status changed: NEVPNStatusInvalid"; [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; setConnectionState(Vpn::ConnectionState::Disconnected); - } else if (status == NEVPNStatusDisconnected) { qDebug() << "Connection status changed: NEVPNStatusDisconnected"; - + setConnectionState(Vpn::ConnectionState::Disconnected); if (state_ == STATE_DISCONNECTING_ANY_ERROR) { [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; From a144d495ee0e9f859fa7aef2cea522286d38a907 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Mon, 2 Sep 2024 13:58:33 -0700 Subject: [PATCH 17/20] Killswitch and Splittunnel for MacOS IPSec --- client/core/networkUtilities.cpp | 116 ++++++++++++ client/core/networkUtilities.h | 13 +- client/protocols/ikev2_vpn_protocol_mac.h | 23 +-- client/protocols/ikev2_vpn_protocol_mac.mm | 205 ++++++--------------- 4 files changed, 184 insertions(+), 173 deletions(-) diff --git a/client/core/networkUtilities.cpp b/client/core/networkUtilities.cpp index 7ffd4c41..aff08bc1 100644 --- a/client/core/networkUtilities.cpp +++ b/client/core/networkUtilities.cpp @@ -29,6 +29,13 @@ #include #include #include + #include + #include + #include + #include + #include + #include + #include #endif #include @@ -460,3 +467,112 @@ QString NetworkUtilities::getGatewayAndIface() return gateway; #endif } + +#if defined(Q_OS_MAC) +QString NetworkUtilities::ipAddressByInterfaceName(const QString &interfaceName) +{ + struct ifaddrs *ifaddr, *ifa; + char host[NI_MAXHOST]; + + if (getifaddrs(&ifaddr) == -1) + { + return ""; + } + + for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next) + { + if (ifa->ifa_addr == NULL) + { + continue; + } + int family = ifa->ifa_addr->sa_family; + QString iname = QString::fromStdString(ifa->ifa_name); + + if (family == AF_INET && iname == interfaceName) + { + int s = getnameinfo(ifa->ifa_addr, + (family == AF_INET) ? sizeof(struct sockaddr_in) : + sizeof(struct sockaddr_in6), + host, NI_MAXHOST, + NULL, 0, NI_NUMERICHOST); + if (s != 0) + { + continue; + } + + return QString::fromStdString(host); + } + } + + freeifaddrs(ifaddr); + return ""; +} + +QString NetworkUtilities::lastConnectedNetworkInterfaceName() +{ + QString ifname(""); + + struct ifaddrs * interfaces = NULL; + struct ifaddrs * temp_addr = NULL; + + if( getifaddrs(&interfaces) == 0 ) + { + //Loop through linked list of interfaces + temp_addr = interfaces; + while( temp_addr != NULL ) + { + if( temp_addr->ifa_addr->sa_family == AF_INET ) + { + QString tname = temp_addr->ifa_name; + if( tname.startsWith("utun") ) + ifname = tname; + else if( tname.startsWith("ipsec") ) + ifname = tname; + else if( tname.startsWith("ppp") ) + ifname = tname; + } + + temp_addr = temp_addr->ifa_next; + } + + freeifaddrs(interfaces); + } + return ifname; +} + +QString execCmd(const QString &cmd) +{ + char buffer[1024]; + QString result = ""; + FILE* pipe = popen(cmd.toStdString().c_str(), "r"); + if (!pipe) return ""; + while (!feof(pipe)) + { + if (fgets(buffer, 1024, pipe) != NULL) + { + result += buffer; + } + } + pclose(pipe); + return result; +} + +QStringList NetworkUtilities::getListOfDnsNetworkServiceEntries() +{ + QStringList result; + QString command = "echo 'list' | scutil | grep /Network/Service | grep DNS"; + QString cmdOutput = execCmd(command).trimmed(); + // qDebug() << "Raw result: " << cmdOutput; + + QStringList lines = cmdOutput.split('\n'); + for (QString line : lines) + { + if (line.contains("=")) + { + QString entry = line.mid(line.indexOf("=")+1).trimmed(); + result.append(entry); + } + } + return result; +} +#endif diff --git a/client/core/networkUtilities.h b/client/core/networkUtilities.h index 3057b852..2c5c7109 100644 --- a/client/core/networkUtilities.h +++ b/client/core/networkUtilities.h @@ -18,19 +18,24 @@ public: static QString getGatewayAndIface(); // Returns the Interface Index that could Route to dst static int AdapterIndexTo(const QHostAddress& dst); - + static QRegularExpression ipAddressRegExp(); static QRegularExpression ipAddressPortRegExp(); static QRegExp ipAddressWithSubnetRegExp(); static QRegExp ipNetwork24RegExp(); static QRegExp ipPortRegExp(); static QRegExp domainRegExp(); - + static QString netMaskFromIpWithSubnet(const QString ip); static QString ipAddressFromIpWithSubnet(const QString ip); - + static QStringList summarizeRoutes(const QStringList &ips, const QString cidr); - + +#if defined(Q_OS_MAC) + static QString ipAddressByInterfaceName(const QString &interfaceName); + static QString lastConnectedNetworkInterfaceName(); + static QStringList getListOfDnsNetworkServiceEntries(); +#endif }; #endif // NETWORKUTILITIES_H diff --git a/client/protocols/ikev2_vpn_protocol_mac.h b/client/protocols/ikev2_vpn_protocol_mac.h index 8d2e52f1..b2ef4dcf 100644 --- a/client/protocols/ikev2_vpn_protocol_mac.h +++ b/client/protocols/ikev2_vpn_protocol_mac.h @@ -20,7 +20,6 @@ public: bool connect_to_vpn(const QString & vpn_name); bool disconnect_vpn(); void closeWindscribeActiveConnection(); - ErrorCode start() override; void stop() override; @@ -30,31 +29,15 @@ private slots: void handleNotificationImpl(int status); private: - enum {STATE_DISCONNECTED, STATE_START_CONNECT, STATE_START_DISCONNECTING, STATE_CONNECTED, STATE_DISCONNECTING_AUTH_ERROR, STATE_DISCONNECTING_ANY_ERROR}; - - int state_; - - bool bConnected_; mutable QRecursiveMutex mutex_; void *notificationId_; - bool isStateConnectingAfterClick_; - bool isDisconnectClicked_; - - QString overrideDnsIp_; - QJsonObject m_config; + QJsonObject m_ikev2_config; - static constexpr int STATISTICS_UPDATE_PERIOD = 1000; - QTimer statisticsTimer_; QString ipsecAdapterName_; - - int prevConnectionStatus_; - bool isPrevConnectionStatusInitialized_; - - // True if startConnect() method was called and NEVPNManager emitted notification NEVPNStatusConnecting. - // False otherwise. + bool isConnectingStateReachedAfterStartingConnection_; - + void handleNotification(void *notification); bool isFailedAuthError(QMap &logs); bool isSocketError(QMap &logs); diff --git a/client/protocols/ikev2_vpn_protocol_mac.mm b/client/protocols/ikev2_vpn_protocol_mac.mm index 6752e6f7..cfea857c 100644 --- a/client/protocols/ikev2_vpn_protocol_mac.mm +++ b/client/protocols/ikev2_vpn_protocol_mac.mm @@ -1,7 +1,6 @@ #include "ikev2_vpn_protocol_mac.h" - - +#include #include #include #include @@ -20,16 +19,15 @@ #include #include -static NSString * const IKEv1ServiceName = @"AmneziaVPN"; static NSString * const IKEv2ServiceName = @"AmneziaVPN IKEv2"; static Ikev2Protocol* self = nullptr; - Ikev2Protocol::Ikev2Protocol(const QJsonObject &configuration, QObject* parent) : VpnProtocol(configuration, parent) { qDebug() << "IpsecProtocol::IpsecProtocol()"; + m_routeGateway = NetworkUtilities::getGatewayAndIface(); self = this; readIkev2Configuration(configuration); } @@ -38,6 +36,7 @@ Ikev2Protocol::~Ikev2Protocol() { qDebug() << "IpsecProtocol::~IpsecProtocol()"; disconnect_vpn(); + QThread::msleep(1000); Ikev2Protocol::stop(); } @@ -47,12 +46,13 @@ void Ikev2Protocol::stop() qDebug() << "IpsecProtocol::stop()"; } - void Ikev2Protocol::readIkev2Configuration(const QJsonObject &configuration) { qDebug() << "IpsecProtocol::readIkev2Configuration"; - QJsonObject ikev2_data = configuration.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); - m_config = QJsonDocument::fromJson(ikev2_data.value(config_key::config).toString().toUtf8()).object(); + m_config = configuration; + auto ikev2_data = m_config.value(ProtocolProps::key_proto_config_data(Proto::Ikev2)).toObject(); + m_ikev2_config = QJsonDocument::fromJson(ikev2_data.value(config_key::config).toString().toUtf8()).object(); + } CFDataRef CreatePersistentRefForIdentity(SecIdentityRef identity) @@ -75,16 +75,16 @@ CFDataRef CreatePersistentRefForIdentity(SecIdentityRef identity) NSData *searchKeychainCopyMatching(const char *certName) { - NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; - [dict setObject:(__bridge id)kSecClassCertificate forKey:(__bridge id)kSecClass]; - [dict setObject:[NSString stringWithUTF8String:certName] forKey:(__bridge id)kSecAttrLabel]; - [dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; - [dict setObject:@YES forKey:(__bridge id)kSecReturnPersistentRef]; + NSMutableDictionary *dict = [[NSMutableDictionary alloc] init]; + [dict setObject:(__bridge id)kSecClassCertificate forKey:(__bridge id)kSecClass]; + [dict setObject:[NSString stringWithUTF8String:certName] forKey:(__bridge id)kSecAttrLabel]; + [dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit]; + [dict setObject:@YES forKey:(__bridge id)kSecReturnPersistentRef]; - CFTypeRef result = NULL; - SecItemCopyMatching((__bridge CFDictionaryRef)dict, &result); + CFTypeRef result = NULL; + SecItemCopyMatching((__bridge CFDictionaryRef)dict, &result); - return (NSData *)result; + return (NSData *)result; } ErrorCode Ikev2Protocol::start() @@ -122,11 +122,11 @@ ErrorCode Ikev2Protocol::start() EVP_PKEY *pkey; X509 *cert; - BIO_write(p12, QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()), - QByteArray::fromBase64(m_config[config_key::cert].toString().toUtf8()).size()); + BIO_write(p12, QByteArray::fromBase64(m_ikev2_config[config_key::cert].toString().toUtf8()), + QByteArray::fromBase64(m_ikev2_config[config_key::cert].toString().toUtf8()).size()); PKCS12 *pkcs12 = d2i_PKCS12_bio(p12, NULL); - PKCS12_parse(pkcs12, m_config[config_key::password].toString().toStdString().c_str(), &pkey, &cert, &certstack); + PKCS12_parse(pkcs12, m_ikev2_config[config_key::password].toString().toStdString().c_str(), &pkey, &cert, &certstack); // We output everything in PEM obio = BIO_new(BIO_s_mem()); @@ -152,7 +152,7 @@ ErrorCode Ikev2Protocol::start() output = [NSData dataWithBytes: bptr->data length: bptr->length]; - NSData *PKCS12Data = [[NSData alloc] initWithBase64EncodedString:m_config[config_key::cert].toString().toNSString() options:0] ; + NSData *PKCS12Data = [[NSData alloc] initWithBase64EncodedString:m_ikev2_config[config_key::cert].toString().toNSString() options:0]; CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); OSStatus ret = SecPKCS12Import( @@ -168,12 +168,12 @@ ErrorCode Ikev2Protocol::start() SecIdentityRef identity = (__bridge SecIdentityRef)(firstItem[(__bridge id)kSecImportItemIdentity]); NEVPNProtocolIKEv2 *protocol = [[NEVPNProtocolIKEv2 alloc] init]; - protocol.serverAddress = m_config.value(amnezia::config_key::hostName).toString().toNSString(); + protocol.serverAddress = m_ikev2_config.value(amnezia::config_key::hostName).toString().toNSString(); protocol.certificateType = NEVPNIKEv2CertificateTypeRSA; - protocol.remoteIdentifier = m_config.value(amnezia::config_key::hostName).toString().toNSString(); + protocol.remoteIdentifier = m_ikev2_config.value(amnezia::config_key::hostName).toString().toNSString(); protocol.authenticationMethod = NEVPNIKEAuthenticationMethodCertificate; - protocol.identityReference = searchKeychainCopyMatching(m_config.value(amnezia::config_key::userName).toString().toLocal8Bit().data()); + protocol.identityReference = searchKeychainCopyMatching(m_ikev2_config.value(amnezia::config_key::userName).toString().toLocal8Bit().data()); protocol.useExtendedAuthentication = NO; protocol.enablePFS = YES; @@ -187,15 +187,17 @@ ErrorCode Ikev2Protocol::start() protocol.childSecurityAssociationParameters.diffieHellmanGroup = NEVPNIKEv2DiffieHellmanGroup19; protocol.childSecurityAssociationParameters.integrityAlgorithm = NEVPNIKEv2IntegrityAlgorithmSHA256; protocol.childSecurityAssociationParameters.lifetimeMinutes = 1440; - + [manager setEnabled:YES]; [manager setProtocolConfiguration:(protocol)]; [manager setOnDemandEnabled:NO]; [manager setLocalizedDescription:@"Amnezia VPN"]; +#ifdef QT_DEBUG NSString *strProtocol = [NSString stringWithFormat:@"{Protocol: %@", protocol]; qDebug() << QString::fromNSString(strProtocol); - +#endif + // do config stuff [manager saveToPreferencesWithCompletionHandler:^(NSError *err) { @@ -253,7 +255,6 @@ ErrorCode Ikev2Protocol::start() } }]; - // waitConditionLocal.wait(&mutexLocal); mutexLocal.unlock(); setConnectionState(Vpn::ConnectionState::Connected); @@ -261,24 +262,22 @@ ErrorCode Ikev2Protocol::start() } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bool Ikev2Protocol::create_new_vpn(const QString & vpn_name, - const QString & serv_addr){ + const QString & serv_addr) { qDebug() << "Ikev2Protocol::create_new_vpn()"; return true; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name){ +bool Ikev2Protocol::delete_vpn_connection(const QString &vpn_name) { return false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name){ +bool Ikev2Protocol::connect_to_vpn(const QString & vpn_name) { return false; } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ bool Ikev2Protocol::disconnect_vpn() { - - QMutexLocker locker(&mutex_); - + NEVPNManager *manager = [NEVPNManager sharedManager]; // #713: If user had started connecting to IKev2 on Mac and quickly started after this connecting to Wireguard @@ -303,17 +302,12 @@ bool Ikev2Protocol::disconnect_vpn() { void Ikev2Protocol::closeWindscribeActiveConnection() { - static QWaitCondition waitCondition; - static QMutex mutex; - - mutex.lock(); NEVPNManager *manager = [NEVPNManager sharedManager]; if (manager) { [manager loadFromPreferencesWithCompletionHandler:^(NSError *err) { - mutex.lock(); if (!err) { NEVPNConnection * connection = [manager connection]; @@ -326,12 +320,9 @@ void Ikev2Protocol::closeWindscribeActiveConnection() } } } - waitCondition.wakeAll(); - mutex.unlock(); }]; } - waitCondition.wait(&mutex); - mutex.unlock(); + } void Ikev2Protocol::handleNotificationImpl(int status) @@ -349,44 +340,44 @@ void Ikev2Protocol::handleNotificationImpl(int status) else if (status == NEVPNStatusDisconnected) { qDebug() << "Connection status changed: NEVPNStatusDisconnected"; + IpcClient::Interface()->disableKillSwitch(); + setConnectionState(Vpn::ConnectionState::Disconnected); - if (state_ == STATE_DISCONNECTING_ANY_ERROR) - { - [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; - // state_ = STATE_DISCONNECTED; - // emit error(IKEV_FAILED_TO_CONNECT); - setConnectionState(Vpn::ConnectionState::Disconnected); - } - else if (state_ != STATE_DISCONNECTED) - { + [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; - [[NSNotificationCenter defaultCenter] removeObserver: (id)notificationId_ name: (NSString *)NEVPNStatusDidChangeNotification object: manager.connection]; - // state_ = STATE_DISCONNECTED; - setConnectionState(Vpn::ConnectionState::Disconnected); - } } else if (status == NEVPNStatusConnecting) { isConnectingStateReachedAfterStartingConnection_ = true; + setConnectionState(Vpn::ConnectionState::Connecting); qDebug() << "Connection status changed: NEVPNStatusConnecting"; } else if (status == NEVPNStatusConnected) { - if (!overrideDnsIp_.isEmpty()) { - if (!setCustomDns(overrideDnsIp_)) { - qDebug() << "Failed to set custom DNS ip for ikev2"; - } + qDebug() << "Connection status changed: NEVPNStatusConnected"; + + QString ipsecAdapterName_ = NetworkUtilities::lastConnectedNetworkInterfaceName(); + m_vpnLocalAddress = NetworkUtilities::ipAddressByInterfaceName(ipsecAdapterName_); + m_vpnGateway = m_vpnLocalAddress; + + QList dnsAddr; + dnsAddr.push_back(QHostAddress(m_config.value(config_key::dns1).toString())); + dnsAddr.push_back(QHostAddress(m_config.value(config_key::dns2).toString())); + + IpcClient::Interface()->updateResolvers(ipsecAdapterName_, dnsAddr); + + if (QVariant(m_config.value(config_key::killSwitchOption).toString()).toBool()) { + qDebug() << "enable killswitch"; + IpcClient::Interface()->enableKillSwitch(m_config, 0); } - qDebug() << "Connection status changed: NEVPNStatusConnected"; - + if (m_config.value(amnezia::config_key::splitTunnelType).toInt() == 0) { + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1"); + IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_config.value(amnezia::config_key::hostName).toString()); + } + setConnectionState(Vpn::ConnectionState::Connected); - // note: route gateway not used for ikev2 in AdapterGatewayInfo - // AdapterGatewayInfo cai; - // ipsecAdapterName_ = NetworkUtils_mac::lastConnectedNetworkInterfaceName(); - // cai.setAdapterName(ipsecAdapterName_); - // cai.setAdapterIp(NetworkUtils_mac::ipAddressByInterfaceName(ipsecAdapterName_)); - //cai.setDnsServers(NetworkUtils_mac::getDnsServersForInterface(ipsecAdapterName_)); } else if (status == NEVPNStatusReasserting) { @@ -397,33 +388,8 @@ void Ikev2Protocol::handleNotificationImpl(int status) { qDebug() << "Connection status changed: NEVPNStatusDisconnecting"; setConnectionState(Vpn::ConnectionState::Disconnecting); - /* if (state_ == STATE_START_CONNECT) - { - QMap logs = networkExtensionLog_.collectNext(); - for (QMap::iterator it = logs.begin(); it != logs.end(); ++it) - { - qDebug() << it.value(); - } - if (isSocketError(logs)) - { - state_ = STATE_DISCONNECTING_ANY_ERROR; - } - else - { - if (isFailedAuthError(logs)) - { - state_ = STATE_DISCONNECTING_AUTH_ERROR; - } - else - { - state_ = STATE_DISCONNECTING_ANY_ERROR; - } - } - }*/ } - prevConnectionStatus_ = status; - isPrevConnectionStatusInitialized_ = true; } @@ -435,62 +401,3 @@ void Ikev2Protocol::handleNotification(void *notification) QMetaObject::invokeMethod(this, "handleNotificationImpl", Q_ARG(int, (int)connection.status)); } -bool Ikev2Protocol::isFailedAuthError(QMap &logs) -{ - for (QMap::iterator it = logs.begin(); it != logs.end(); ++it) - { - if (it.value().contains("Failed", Qt::CaseInsensitive) && it.value().contains("IKE", Qt::CaseInsensitive) && it.value().contains("Auth", Qt::CaseInsensitive)) - { - if (!(it.value().contains("Failed", Qt::CaseInsensitive) && it.value().contains("IKEv2 socket", Qt::CaseInsensitive))) - { - return true; - } - } - } - return false; -} - -bool Ikev2Protocol::isSocketError(QMap &logs) -{ - for (QMap::iterator it = logs.begin(); it != logs.end(); ++it) - { - if (it.value().contains("Failed", Qt::CaseInsensitive) && it.value().contains("initialize", Qt::CaseInsensitive) && it.value().contains("socket", Qt::CaseInsensitive)) - { - return true; - } - } - return false; -} - -bool Ikev2Protocol::setCustomDns(const QString &overrideDnsIpAddress) -{ - // get list of entries of interest - // QStringList networkServices = NetworkUtils_mac::getListOfDnsNetworkServiceEntries(); - - // filter list to only ikev2 entries - QStringList dnsNetworkServices; - // for (const QString &service : networkServices) - // if (MacUtils::dynamicStoreEntryHasKey(service, "ConfirmedServiceID")) - // dnsNetworkServices.append(service); - - qDebug() << "Applying custom 'while connected' DNS change to network services: " << dnsNetworkServices; - - if (dnsNetworkServices.isEmpty()) { - qDebug() << "No network services to configure 'while connected' DNS"; - return false; - } - - // change DNS on each entry - bool successAll = true; - for (const QString &service : dnsNetworkServices) { - // if (!helper_->setDnsOfDynamicStoreEntry(overrideDnsIpAddress, service)) { - // successAll = false; - // qDebug() << "Failed to set network service DNS: " << service; - // break; - // } - } - - return successAll; -} - - From 8c94f70edfc1494d408edebae25ee2d265b91843 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 3 Sep 2024 05:48:01 -0700 Subject: [PATCH 18/20] Update killswitch interface list --- deploy/data/macos/pf/amn.200.allowVPN.conf | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/deploy/data/macos/pf/amn.200.allowVPN.conf b/deploy/data/macos/pf/amn.200.allowVPN.conf index 6e1b74bc..fe4f5407 100644 --- a/deploy/data/macos/pf/amn.200.allowVPN.conf +++ b/deploy/data/macos/pf/amn.200.allowVPN.conf @@ -1,9 +1,11 @@ # Exempt the tunnel interface(s) used by the VPN connection -utunInterfaces = "{ \ - utun0, utun1, utun2, utun3, utun4, utun5, utun6, utun7, utun8, utun9, utun10, \ - utun11, utun12, utun13, utun14, utun15, utun16, utun17, utun18, utun19, utun20, \ - utun21, utun22, utun23, utun24, utun25, utun26, utun27, utun28, utun29, utun30 \ +utunInterfaces = "{ \ + utun0, utun1, utun2, utun3, utun4, utun5, utun6, utun7, utun8, utun9, utun10, \ + utun11, utun12, utun13, utun14, utun15, utun16, utun17, utun18, utun19, utun20, \ + utun21, utun22, utun23, utun24, utun25, utun26, utun27, utun28, utun29, utun30, \ + ipsec0, ipsec1, ipsec2, ipsec3, ipsec4, ipsec5, ipsec6, ipsec7, ipsec8, ipsec9, \ + ipsec10, ipsec11, ipsec12, ipsec13, ipsec14, ipsec15, ipsec16, ipsec17, ipsec18, ipsec19 \ }" pass out on $utunInterfaces flags any no state From 898f497f8ed96573f44ccbfba5494763d248b6ae Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 3 Sep 2024 05:52:42 -0700 Subject: [PATCH 19/20] Disable split tunnel for MacOS IPSec --- client/protocols/ikev2_vpn_protocol_mac.mm | 6 ------ 1 file changed, 6 deletions(-) diff --git a/client/protocols/ikev2_vpn_protocol_mac.mm b/client/protocols/ikev2_vpn_protocol_mac.mm index cfea857c..a749882b 100644 --- a/client/protocols/ikev2_vpn_protocol_mac.mm +++ b/client/protocols/ikev2_vpn_protocol_mac.mm @@ -371,12 +371,6 @@ void Ikev2Protocol::handleNotificationImpl(int status) IpcClient::Interface()->enableKillSwitch(m_config, 0); } - if (m_config.value(amnezia::config_key::splitTunnelType).toInt() == 0) { - IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "0.0.0.0/1"); - IpcClient::Interface()->routeAddList(m_vpnGateway, QStringList() << "128.0.0.0/1"); - IpcClient::Interface()->routeAddList(m_routeGateway, QStringList() << m_config.value(amnezia::config_key::hostName).toString()); - } - setConnectionState(Vpn::ConnectionState::Connected); } else if (status == NEVPNStatusReasserting) From f72684e4d4949c8ad76cbc43c99ba6b1ca1845d0 Mon Sep 17 00:00:00 2001 From: Mykola Baibuz Date: Tue, 3 Sep 2024 12:02:49 -0700 Subject: [PATCH 20/20] Add MacOS sign entitlements --- client/cmake/macos.cmake | 5 ++++- .../macos/app/AmneziaVPN.entitlements | 0 2 files changed, 4 insertions(+), 1 deletion(-) rename AmneziaVPN.entitlements => client/macos/app/AmneziaVPN.entitlements (100%) diff --git a/client/cmake/macos.cmake b/client/cmake/macos.cmake index 17b6387a..57c2f028 100644 --- a/client/cmake/macos.cmake +++ b/client/cmake/macos.cmake @@ -18,7 +18,10 @@ set(LIBS ${LIBS} ${FW_NETWORK_EXTENSION} ) -set_target_properties(${PROJECT} PROPERTIES MACOSX_BUNDLE TRUE) +set_target_properties(${PROJECT} PROPERTIES + MACOSX_BUNDLE TRUE + XCODE_ATTRIBUTE_CODE_SIGN_ENTITLEMENTS "${CMAKE_CURRENT_SOURCE_DIR}/macos/app/AmneziaVPN.entitlements" +) set(CMAKE_OSX_ARCHITECTURES "x86_64" CACHE INTERNAL "" FORCE) set(CMAKE_OSX_DEPLOYMENT_TARGET 10.15) diff --git a/AmneziaVPN.entitlements b/client/macos/app/AmneziaVPN.entitlements similarity index 100% rename from AmneziaVPN.entitlements rename to client/macos/app/AmneziaVPN.entitlements