From eaa209bc3ab6060758bbba18a210691268784766 Mon Sep 17 00:00:00 2001 From: albexk Date: Tue, 28 Nov 2023 22:27:00 +0300 Subject: [PATCH] Add OpenVpn over Cloak module --- client/android/build.gradle.kts | 1 + client/android/cloak/build.gradle.kts | 18 +++++ client/android/cloak/src/main/kotlin/Cloak.kt | 70 +++++++++++++++++++ .../amnezia/vpn/protocol/openvpn/OpenVpn.kt | 25 ++++--- .../protocolApi/src/main/kotlin/Exceptions.kt | 2 +- client/android/settings.gradle.kts | 1 + .../src/org/amnezia/vpn/AmneziaVpnService.kt | 9 +-- 7 files changed, 110 insertions(+), 16 deletions(-) create mode 100644 client/android/cloak/build.gradle.kts create mode 100644 client/android/cloak/src/main/kotlin/Cloak.kt diff --git a/client/android/build.gradle.kts b/client/android/build.gradle.kts index 58f241af..ee6e8e27 100644 --- a/client/android/build.gradle.kts +++ b/client/android/build.gradle.kts @@ -89,6 +89,7 @@ dependencies { implementation(project(":wireguard")) implementation(project(":awg")) implementation(project(":openvpn")) + implementation(project(":cloak")) implementation(libs.androidx.core) implementation(libs.androidx.activity) implementation(libs.androidx.security.crypto) diff --git a/client/android/cloak/build.gradle.kts b/client/android/cloak/build.gradle.kts new file mode 100644 index 00000000..99f1264a --- /dev/null +++ b/client/android/cloak/build.gradle.kts @@ -0,0 +1,18 @@ +plugins { + id(libs.plugins.android.library.get().pluginId) + id(libs.plugins.kotlin.android.get().pluginId) +} + +kotlin { + jvmToolchain(17) +} + +android { + namespace = "org.amnezia.vpn.protocol.cloak" +} + +dependencies { + compileOnly(project(":utils")) + compileOnly(project(":protocolApi")) + implementation(project(":openvpn")) +} diff --git a/client/android/cloak/src/main/kotlin/Cloak.kt b/client/android/cloak/src/main/kotlin/Cloak.kt new file mode 100644 index 00000000..cb41f992 --- /dev/null +++ b/client/android/cloak/src/main/kotlin/Cloak.kt @@ -0,0 +1,70 @@ +package org.amnezia.vpn.protocol.cloak + +import android.util.Base64 +import net.openvpn.ovpn3.ClientAPI_Config +import org.amnezia.vpn.protocol.openvpn.OpenVpn +import org.json.JSONObject + +/** + * Config Example: + * { + * "protocol": "cloak", + * "description": "Server 1", + * "dns1": "1.1.1.1", + * "dns2": "1.0.0.1", + * "hostName": "100.100.100.0", + * "splitTunnelSites": [ + * ], + * "splitTunnelType": 0, + * "openvpn_config_data": { + * "config": "openVpnConfig" + * } + * "cloak_config_data": { + * "BrowserSig": "chrome", + * "EncryptionMethod": "aes-gcm", + * "NumConn": 1, + * "ProxyMethod": "openvpn", + * "PublicKey": "PublicKey=", + * "RemoteHost": "100.100.100.0", + * "RemotePort": "443", + * "ServerName": "servername", + * "StreamTimeout": 300, + * "Transport": "direct", + * "UID": "UID=" + * } + * } + */ + +class Cloak : OpenVpn() { + + override fun parseConfig(config: JSONObject): ClientAPI_Config { + val openVpnConfig = ClientAPI_Config() + + val openVpnConfigStr = config.getJSONObject("openvpn_config_data").getString("config") + val cloakConfigJson = checkCloakJson(config.getJSONObject("cloak_config_data")) + val cloakConfigStr = Base64.encodeToString(cloakConfigJson.toString().toByteArray(), Base64.DEFAULT) + + val configStr = "$openVpnConfigStr\n\n$cloakConfigStr\n\n" + + openVpnConfig.usePluggableTransports = true + openVpnConfig.content = configStr + return openVpnConfig + } + + private fun checkCloakJson(cloakConfigJson: JSONObject): JSONObject { + // todo: strange method + if (!cloakConfigJson.has("NumConn")) cloakConfigJson.put("NumConn", 1) + if (!cloakConfigJson.has("ProxyMethod")) cloakConfigJson.put("ProxyMethod", "openvpn") + if (cloakConfigJson.has("port")) { + val port = cloakConfigJson["port"] + cloakConfigJson.remove("port") + cloakConfigJson.put("RemotePort", port) + } + if (cloakConfigJson.has("remote")) { + val remote = cloakConfigJson["remote"] + cloakConfigJson.remove("remote") + cloakConfigJson.put("RemoteHost", remote) + } + return cloakConfigJson + } +} diff --git a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt index 8cbb7922..d22625b5 100644 --- a/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt +++ b/client/android/openvpn/src/main/kotlin/org/amnezia/vpn/protocol/openvpn/OpenVpn.kt @@ -8,6 +8,7 @@ import org.amnezia.vpn.protocol.BadConfigException import org.amnezia.vpn.protocol.Protocol import org.amnezia.vpn.protocol.ProtocolState import org.amnezia.vpn.protocol.Statistics +import org.amnezia.vpn.protocol.VpnException import org.amnezia.vpn.protocol.VpnStartException import org.amnezia.vpn.util.NetworkUtils import org.json.JSONObject @@ -29,10 +30,10 @@ import org.json.JSONObject * } */ -class OpenVpn : Protocol() { +open class OpenVpn : Protocol() { private lateinit var context: Context - private var openVpnClient: OpenVpnClient? = null + protected var openVpnClient: OpenVpnClient? = null override val statistics: Statistics get() { @@ -54,6 +55,7 @@ class OpenVpn : Protocol() { override fun startVpn(config: JSONObject, vpnBuilder: Builder, protect: (Int) -> Boolean) { val configBuilder = OpenVpnConfig.Builder() + openVpnClient = OpenVpnClient( configBuilder, state, @@ -61,12 +63,18 @@ class OpenVpn : Protocol() { makeEstablish(configBuilder, vpnBuilder), protect ) + try { - parseConfig(config) openVpnClient?.let { client -> + val openVpnConfig = parseConfig(config) + val evalConfig = client.eval_config(openVpnConfig) + if (evalConfig.error) { + throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}") + } + val status = client.connect() if (status.error) { - throw VpnStartException("OpenVpn connect() error: ${status.status}: ${status.message}") + throw VpnException("OpenVpn connect() error: ${status.status}: ${status.message}") } } } catch (e: Exception) { @@ -80,15 +88,10 @@ class OpenVpn : Protocol() { openVpnClient = null } - private fun parseConfig(config: JSONObject) { + protected open fun parseConfig(config: JSONObject): ClientAPI_Config { val openVpnConfig = ClientAPI_Config() openVpnConfig.content = config.getJSONObject("openvpn_config_data").getString("config") - openVpnClient?.let { client -> - val evalConfig = client.eval_config(openVpnConfig) - if (evalConfig.error) { - throw BadConfigException("OpenVPN config parse error: ${evalConfig.message}") - } - } + return openVpnConfig } private fun makeEstablish(configBuilder: OpenVpnConfig.Builder, vpnBuilder: Builder): () -> Int = diff --git a/client/android/protocolApi/src/main/kotlin/Exceptions.kt b/client/android/protocolApi/src/main/kotlin/Exceptions.kt index 40fa965a..739a327c 100644 --- a/client/android/protocolApi/src/main/kotlin/Exceptions.kt +++ b/client/android/protocolApi/src/main/kotlin/Exceptions.kt @@ -3,7 +3,7 @@ package org.amnezia.vpn.protocol sealed class ProtocolException(message: String? = null, cause: Throwable? = null) : Exception(message, cause) class LoadLibraryException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause) -class VpnNotAuthorizedException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause) class BadConfigException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause) class VpnStartException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause) +class VpnException(message: String? = null, cause: Throwable? = null) : ProtocolException(message, cause) diff --git a/client/android/settings.gradle.kts b/client/android/settings.gradle.kts index 8bfe25da..b947168e 100644 --- a/client/android/settings.gradle.kts +++ b/client/android/settings.gradle.kts @@ -35,6 +35,7 @@ include(":protocolApi") include(":wireguard") include(":awg") include(":openvpn") +include(":cloak") // get values from gradle or local properties val androidBuildToolsVersion: String by gradleProperties diff --git a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt index b739ed49..2ae307f5 100644 --- a/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt +++ b/client/android/src/org/amnezia/vpn/AmneziaVpnService.kt @@ -21,13 +21,10 @@ import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.SupervisorJob -import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.cancelAndJoin import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.launch -import kotlinx.coroutines.runBlocking -import kotlinx.coroutines.withTimeout import org.amnezia.vpn.protocol.BadConfigException import org.amnezia.vpn.protocol.LoadLibraryException import org.amnezia.vpn.protocol.Protocol @@ -38,8 +35,10 @@ import org.amnezia.vpn.protocol.ProtocolState.DISCONNECTING import org.amnezia.vpn.protocol.ProtocolState.UNKNOWN import org.amnezia.vpn.protocol.Statistics import org.amnezia.vpn.protocol.Status +import org.amnezia.vpn.protocol.VpnException import org.amnezia.vpn.protocol.VpnStartException import org.amnezia.vpn.protocol.awg.Awg +import org.amnezia.vpn.protocol.cloak.Cloak import org.amnezia.vpn.protocol.openvpn.OpenVpn import org.amnezia.vpn.protocol.putStatistics import org.amnezia.vpn.protocol.putStatus @@ -83,7 +82,8 @@ class AmneziaVpnService : VpnService() { protocol = null when (e) { is IllegalArgumentException, - is VpnStartException -> onError(e.message ?: e.toString()) + is VpnStartException, + is VpnException -> onError(e.message ?: e.toString()) is JSONException, is BadConfigException -> onError("VPN config format error: ${e.message}") @@ -314,6 +314,7 @@ class AmneziaVpnService : VpnService() { "wireguard" -> Wireguard() "awg" -> Awg() "openvpn" -> OpenVpn() + "cloak" -> Cloak() else -> throw IllegalArgumentException("Failed to load $protocolName protocol") }.apply { initialize(applicationContext, protocolState) }