Linux IPSec initial

This commit is contained in:
Mykola Baibuz 2024-08-01 21:37:56 +03:00
parent 90912f9231
commit b0b185027e
15 changed files with 313 additions and 10 deletions

View file

@ -306,6 +306,15 @@ endif()
if(LINUX AND NOT ANDROID) if(LINUX AND NOT ANDROID)
set(LIBS ${LIBS} -static-libstdc++ -static-libgcc -ldl) set(LIBS ${LIBS} -static-libstdc++ -static-libgcc -ldl)
link_directories(${CMAKE_CURRENT_LIST_DIR}/platforms/linux) 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() endif()
if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID)) if(WIN32 OR (APPLE AND NOT IOS) OR (LINUX AND NOT ANDROID))

View file

@ -64,6 +64,26 @@ QString Ikev2Configurator::createConfig(const ServerCredentials &credentials, Do
return ""; 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); return genIkev2Config(connData);
} }
@ -73,6 +93,7 @@ QString Ikev2Configurator::genIkev2Config(const ConnectionData &connData)
config[config_key::hostName] = connData.host; config[config_key::hostName] = connData.host;
config[config_key::userName] = connData.clientId; config[config_key::userName] = connData.clientId;
config[config_key::cert] = QString(connData.clientCert.toBase64()); config[config_key::cert] = QString(connData.clientCert.toBase64());
config[config_key::cacert] = QString(connData.caCert);
config[config_key::password] = connData.password; config[config_key::password] = connData.password;
return QJsonDocument(config).toJson(); return QJsonDocument(config).toJson();
@ -115,3 +136,22 @@ QString Ikev2Configurator::genStrongSwanConfig(const ConnectionData &connData)
return config; return config;
} }
QString Ikev2Configurator::processConfigWithLocalSettings(const QPair<QString, QString> &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<QString, QString> &dns, const bool isApiConfig,
QString &protocolConfigString)
{
processConfigWithDnsSettings(dns, protocolConfigString);
QJsonObject json;
json[config_key::config] = protocolConfigString;
return QJsonDocument(json).toJson();
}

View file

@ -27,6 +27,10 @@ public:
QString genIkev2Config(const ConnectionData &connData); QString genIkev2Config(const ConnectionData &connData);
QString genMobileConfig(const ConnectionData &connData); QString genMobileConfig(const ConnectionData &connData);
QString genStrongSwanConfig(const ConnectionData &connData); QString genStrongSwanConfig(const ConnectionData &connData);
QString genIPSecConfig(const ConnectionData &connData);
QString processConfigWithLocalSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
QString processConfigWithExportSettings(const QPair<QString, QString> &dns, const bool isApiConfig, QString &protocolConfigString);
ConnectionData prepareIkev2Config(const ServerCredentials &credentials, ConnectionData prepareIkev2Config(const ServerCredentials &credentials,
DockerContainer container, ErrorCode &errorCode); DockerContainer container, ErrorCode &errorCode);

View file

@ -277,7 +277,7 @@ Proto ContainerProps::defaultProtocol(DockerContainer c)
bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c) bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
{ {
#ifdef Q_OS_WINDOWS #if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX)
return true; return true;
#elif defined(Q_OS_IOS) #elif defined(Q_OS_IOS)
@ -309,13 +309,6 @@ bool ContainerProps::isSupportedByCurrentPlatform(DockerContainer c)
case DockerContainer::SSXray: return true; case DockerContainer::SSXray: return true;
default: return false; default: return false;
} }
#elif defined(Q_OS_LINUX)
switch (c) {
case DockerContainer::Ipsec: return false;
default: return true;
}
#else #else
return false; return false;
#endif #endif

View file

@ -50,6 +50,7 @@ QString amnezia::scriptName(ProtocolScriptType type)
case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf"); case ProtocolScriptType::wireguard_template: return QLatin1String("template.conf");
case ProtocolScriptType::awg_template: return QLatin1String("template.conf"); case ProtocolScriptType::awg_template: return QLatin1String("template.conf");
case ProtocolScriptType::xray_template: return QLatin1String("template.json"); case ProtocolScriptType::xray_template: return QLatin1String("template.json");
case ProtocolScriptType::ipsec_template: return QLatin1String("template.conf");
default: return QString(); default: return QString();
} }
} }

View file

@ -28,7 +28,8 @@ enum ProtocolScriptType {
openvpn_template, openvpn_template,
wireguard_template, wireguard_template,
awg_template, awg_template,
xray_template xray_template,
ipsec_template
}; };

View file

@ -0,0 +1,101 @@
#include <QCoreApplication>
#include <QFileInfo>
#include <QProcess>
#include <QThread>
#include <chrono>
#include "logger.h"
#include "ikev2_vpn_protocol_linux.h"
#include "utilities.h"
#include "core/ipcclient.h"
#include <openssl/pkcs12.h>
#include <openssl/bio.h>
#include <openssl/pem.h>
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;
}

View file

@ -0,0 +1,49 @@
#ifndef IKEV2_VPN_PROTOCOL_LINUX_H
#define IKEV2_VPN_PROTOCOL_LINUX_H
#include <QObject>
#include <QProcess>
#include <QString>
#include <QTemporaryFile>
#include <QTimer>
#include "vpnprotocol.h"
#include <string>
#include <memory>
#include <atomic>
#include <thread>
#include <condition_variable>
#include <mutex>
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

View file

@ -24,6 +24,7 @@ namespace amnezia
constexpr char description[] = "description"; constexpr char description[] = "description";
constexpr char name[] = "name"; constexpr char name[] = "name";
constexpr char cert[] = "cert"; constexpr char cert[] = "cert";
constexpr char cacert[] = "cacert";
constexpr char config[] = "config"; constexpr char config[] = "config";
constexpr char containers[] = "containers"; constexpr char containers[] = "containers";

View file

@ -16,6 +16,10 @@
#include "ikev2_vpn_protocol_windows.h" #include "ikev2_vpn_protocol_windows.h"
#endif #endif
#ifdef Q_OS_LINUX
#include "ikev2_vpn_protocol_linux.h"
#endif
VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent) VpnProtocol::VpnProtocol(const QJsonObject &configuration, QObject *parent)
: QObject(parent), : QObject(parent),
m_connectionState(Vpn::ConnectionState::Unknown), m_connectionState(Vpn::ConnectionState::Unknown),
@ -106,7 +110,7 @@ QString VpnProtocol::vpnGateway() const
VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration) VpnProtocol *VpnProtocol::factory(DockerContainer container, const QJsonObject &configuration)
{ {
switch (container) { switch (container) {
#if defined(Q_OS_WINDOWS) #if defined(Q_OS_WINDOWS) || defined(Q_OS_LINUX)
case DockerContainer::Ipsec: return new Ikev2Protocol(configuration); case DockerContainer::Ipsec: return new Ikev2Protocol(configuration);
#endif #endif
#if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)) #if defined(Q_OS_WINDOWS) || defined(Q_OS_MACX) || (defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID))

View file

@ -236,5 +236,6 @@
<file>server_scripts/socks5_proxy/Dockerfile</file> <file>server_scripts/socks5_proxy/Dockerfile</file>
<file>server_scripts/socks5_proxy/configure_container.sh</file> <file>server_scripts/socks5_proxy/configure_container.sh</file>
<file>server_scripts/socks5_proxy/start.sh</file> <file>server_scripts/socks5_proxy/start.sh</file>
<file>server_scripts/ipsec/template.conf</file>
</qresource> </qresource>
</RCC> </RCC>

View file

@ -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

View file

@ -32,5 +32,11 @@ class IpcInterface
SLOT( bool enablePeerTraffic( const QJsonObject &configStr) ); SLOT( bool enablePeerTraffic( const QJsonObject &configStr) );
SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) ); SLOT( bool enableKillSwitch( const QJsonObject &excludeAddr, int vpnAdapterIndex) );
SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) ); SLOT( bool updateResolvers(const QString& ifname, const QList<QHostAddress>& 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) );
}; };

View file

@ -5,6 +5,7 @@
#include <QLocalSocket> #include <QLocalSocket>
#include <QFileInfo> #include <QFileInfo>
#include "qjsonarray.h"
#include "router.h" #include "router.h"
#include "logger.h" #include "logger.h"
@ -308,6 +309,62 @@ bool IpcServer::disableKillSwitch()
return true; 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) bool IpcServer::enablePeerTraffic(const QJsonObject &configStr)
{ {
#ifdef Q_OS_WIN #ifdef Q_OS_WIN

View file

@ -35,6 +35,12 @@ public:
virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override; virtual bool enableKillSwitch(const QJsonObject &excludeAddr, int vpnAdapterIndex) override;
virtual bool disableKillSwitch() override; virtual bool disableKillSwitch() override;
virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& resolvers) override; virtual bool updateResolvers(const QString& ifname, const QList<QHostAddress>& 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: private:
int m_localpid = 0; int m_localpid = 0;