summaryrefslogtreecommitdiff
path: root/app/src/main/java
diff options
context:
space:
mode:
Diffstat (limited to 'app/src/main/java')
-rw-r--r--app/src/main/java/de/blinkt/openvpn/VpnProfile.java33
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java56
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ConnectionInterface.java15
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java13
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java44
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java16
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java207
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java83
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/connection/OpenvpnConnection.java13
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/Constants.java18
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/StartActivity.java85
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java73
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java2
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java41
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java177
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/BinaryInstaller.java204
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java229
17 files changed, 1186 insertions, 123 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 7b9003aa..9f18b8ed 100644
--- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -5,6 +5,11 @@
package de.blinkt.openvpn;
+import de.blinkt.openvpn.core.connection.Connection;
+import de.blinkt.openvpn.core.connection.OpenvpnConnection;
+import se.leap.bitmaskclient.R;
+import se.leap.bitmaskclient.BuildConfig;
+
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
@@ -189,7 +194,7 @@ public class VpnProfile implements Serializable, Cloneable {
mProfileVersion = CURRENT_PROFILE_VERSION;
mConnections = new Connection[1];
- mConnections[0] = new Connection();
+ mConnections[0] = new OpenvpnConnection();
mLastUsed = System.currentTimeMillis();
}
@@ -314,8 +319,8 @@ public class VpnProfile implements Serializable, Cloneable {
}
if (mProfileVersion < 7) {
for (Connection c : mConnections)
- if (c.mProxyType == null)
- c.mProxyType = Connection.ProxyType.NONE;
+ if (c.getProxyType() == null)
+ c.setProxyType(Connection.ProxyType.NONE);
}
mProfileVersion = CURRENT_PROFILE_VERSION;
@@ -324,12 +329,12 @@ public class VpnProfile implements Serializable, Cloneable {
private void moveOptionsToConnection() {
mConnections = new Connection[1];
- Connection conn = new Connection();
+ Connection conn = new OpenvpnConnection();
- conn.mServerName = mServerName;
- conn.mServerPort = mServerPort;
- conn.mUseUdp = mUseUdp;
- conn.mCustomConfiguration = "";
+ conn.setServerName(mServerName);
+ conn.setServerPort(mServerPort);
+ conn.setUseUdp(mUseUdp);
+ conn.setCustomConfiguration("");
mConnections[0] = conn;
@@ -425,7 +430,7 @@ public class VpnProfile implements Serializable, Cloneable {
if (canUsePlainRemotes) {
for (Connection conn : mConnections) {
- if (conn.mEnabled) {
+ if (conn.isEnabled()) {
cfg.append(conn.getConnectionBlock(configForOvpn3));
}
}
@@ -586,7 +591,7 @@ public class VpnProfile implements Serializable, Cloneable {
if (mAuthenticationType != TYPE_STATICKEYS) {
if (mCheckRemoteCN) {
if (mRemoteCN == null || mRemoteCN.equals(""))
- cfg.append("verify-x509-name ").append(openVpnEscape(mConnections[0].mServerName)).append(" name\n");
+ cfg.append("verify-x509-name ").append(openVpnEscape(mConnections[0].getServerName())).append(" name\n");
else
switch (mX509AuthType) {
@@ -660,7 +665,7 @@ public class VpnProfile implements Serializable, Cloneable {
if (!canUsePlainRemotes) {
cfg.append("# Connection Options are at the end to allow global options (and global custom options) to influence connection blocks\n");
for (Connection conn : mConnections) {
- if (conn.mEnabled) {
+ if (conn.isEnabled()) {
cfg.append("<connection>\n");
cfg.append(conn.getConnectionBlock(configForOvpn3));
cfg.append("</connection>\n");
@@ -985,7 +990,7 @@ public class VpnProfile implements Serializable, Cloneable {
boolean noRemoteEnabled = true;
for (Connection c : mConnections) {
- if (c.mEnabled)
+ if (c.isEnabled())
noRemoteEnabled = false;
}
@@ -1000,12 +1005,12 @@ public class VpnProfile implements Serializable, Cloneable {
return R.string.openvpn3_pkcs12;
}
for (Connection conn : mConnections) {
- if (conn.mProxyType == Connection.ProxyType.ORBOT || conn.mProxyType == Connection.ProxyType.SOCKS5)
+ if (conn.getProxyType() == Connection.ProxyType.ORBOT || conn.getProxyType() == Connection.ProxyType.SOCKS5)
return R.string.openvpn3_socksproxy;
}
}
for (Connection c : mConnections) {
- if (c.mProxyType == Connection.ProxyType.ORBOT) {
+ if (c.getProxyType() == Connection.ProxyType.ORBOT) {
if (usesExtraProxyOptions())
return R.string.error_orbot_and_proxy_options;
if (!OrbotHelper.checkTorReceier(context))
diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
index 0148bfb7..0e9b1bc4 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
@@ -6,16 +6,24 @@
package de.blinkt.openvpn.core;
import android.os.Build;
-import android.support.v4.util.Pair;
import android.text.TextUtils;
+import android.support.v4.util.Pair;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.io.StringReader;
-import java.util.*;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Vector;
import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.connection.Connection;
+import de.blinkt.openvpn.core.connection.OpenvpnConnection;
//! Openvpn Config FIle Parser, probably not 100% accurate but close enough
@@ -142,9 +150,9 @@ public class ConfigParser {
String data = VpnProfile.getEmbeddedContent(inlinedata);
String[] parts = data.split("\n");
if (parts.length >= 2) {
- c.mProxyAuthUser = parts[0];
- c.mProxyAuthPassword = parts[1];
- c.mUseProxyAuth = true;
+ c.setProxyAuthUser(parts[0]);
+ c.setProxyAuthPassword(parts[1]);
+ c.setUseProxyAuth(true);
}
}
@@ -605,7 +613,7 @@ public class ConfigParser {
}
- if (getOption("nobind", 0, 0) != null)
+ if (getOption("nobind", 0, 1) != null)
np.mNobind = true;
if (getOption("persist-tun", 0, 0) != null)
@@ -713,8 +721,8 @@ public class ConfigParser {
throw new ConfigParseError(String.format("Unknown protocol %s in proto-force", protoToDisable));
for (Connection conn : np.mConnections)
- if (conn.mUseUdp == disableUDP)
- conn.mEnabled = false;
+ if (conn.isUseUdp() == disableUDP)
+ conn.setEnabled(false);
}
// Parse OpenVPN Access Server extra
@@ -763,27 +771,27 @@ public class ConfigParser {
return null;
}
else
- conn = new Connection();
+ conn = new OpenvpnConnection();
Vector<String> port = getOption("port", 1, 1);
if (port != null) {
- conn.mServerPort = port.get(1);
+ conn.setServerPort(port.get(1));
}
Vector<String> rport = getOption("rport", 1, 1);
if (rport != null) {
- conn.mServerPort = rport.get(1);
+ conn.setServerPort(rport.get(1));
}
Vector<String> proto = getOption("proto", 1, 1);
if (proto != null) {
- conn.mUseUdp = isUdpProto(proto.get(1));
+ conn.setUseUdp(isUdpProto(proto.get(1)));
}
Vector<String> connectTimeout = getOption("connect-timeout", 1, 1);
if (connectTimeout != null) {
try {
- conn.mConnectTimeout = Integer.parseInt(connectTimeout.get(1));
+ conn.setConnectTimeout(Integer.parseInt(connectTimeout.get(1)));
} catch (NumberFormatException nfe) {
throw new ConfigParseError(String.format("Argument to connect-timeout (%s) must to be an integer: %s",
connectTimeout.get(1), nfe.getLocalizedMessage()));
@@ -797,16 +805,16 @@ public class ConfigParser {
if (proxy != null) {
if (proxy.get(0).equals("socks-proxy")) {
- conn.mProxyType = Connection.ProxyType.SOCKS5;
+ conn.setProxyType(Connection.ProxyType.SOCKS5);
// socks defaults to 1080, http always sets port
- conn.mProxyPort = "1080";
+ conn.setProxyPort("1080");
} else {
- conn.mProxyType = Connection.ProxyType.HTTP;
+ conn.setProxyType(Connection.ProxyType.HTTP);
}
- conn.mProxyName = proxy.get(1);
+ conn.setProxyName(proxy.get(1));
if (proxy.size() >= 3)
- conn.mProxyPort = proxy.get(2);
+ conn.setProxyPort(proxy.get(2));
}
Vector<String> httpproxyauthhttp = getOption("http-proxy-user-pass", 1, 1);
@@ -823,15 +831,15 @@ public class ConfigParser {
// Assume that we need custom options if connectionDefault are set or in the connection specific set
for (Map.Entry<String, Vector<Vector<String>>> option : options.entrySet()) {
if (connDefault != null || connectionOptionsSet.contains(option.getKey())) {
- conn.mCustomConfiguration += getOptionStrings(option.getValue());
+ conn.setCustomConfiguration(conn.getCustomConfiguration() + getOptionStrings(option.getValue()));
optionsToRemove.add(option.getKey());
}
}
for (String o: optionsToRemove)
options.remove(o);
- if (!(conn.mCustomConfiguration == null || "".equals(conn.mCustomConfiguration.trim())))
- conn.mUseCustomConfig = true;
+ if (!(conn.getCustomConfiguration() == null || "".equals(conn.getCustomConfiguration().trim())))
+ conn.setUseCustomConfig(true);
// Make remotes empty to simplify code
if (remotes == null)
@@ -849,11 +857,11 @@ public class ConfigParser {
}
switch (remote.size()) {
case 4:
- connections[i].mUseUdp = isUdpProto(remote.get(3));
+ connections[i].setUseUdp(isUdpProto(remote.get(3)));
case 3:
- connections[i].mServerPort = remote.get(2);
+ connections[i].setServerPort(remote.get(2));
case 2:
- connections[i].mServerName = remote.get(1);
+ connections[i].setServerName(remote.get(1));
}
i++;
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/ConnectionInterface.java b/app/src/main/java/de/blinkt/openvpn/core/ConnectionInterface.java
new file mode 100644
index 00000000..70b4b4ec
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/ConnectionInterface.java
@@ -0,0 +1,15 @@
+package de.blinkt.openvpn.core;
+
+import java.io.Serializable;
+
+/**
+ * Created by cyberta on 11.03.19.
+ */
+
+public interface ConnectionInterface {
+
+ String getConnectionBlock(boolean isOpenVPN3);
+ boolean usesExtraProxyOptions();
+ boolean isOnlyRemote();
+ int getTimeout();
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
index 6b633c34..a66b7b51 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/NativeUtils.java
@@ -20,6 +20,8 @@ public class NativeUtils {
{
if (isRoboUnitTest())
return "ROBO";
+ else if (isUnitTest())
+ return "JUNIT";
else
return getJNIAPI();
}
@@ -34,7 +36,7 @@ public class NativeUtils {
public static native double[] getOpenSSLSpeed(String algorithm, int testnum);
static {
- if (!isRoboUnitTest()) {
+ if (!isRoboUnitTest() && !isUnitTest()) {
System.loadLibrary("opvpnutil");
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.JELLY_BEAN)
System.loadLibrary("jbcrypto");
@@ -44,4 +46,13 @@ public class NativeUtils {
public static boolean isRoboUnitTest() {
return "robolectric".equals(Build.FINGERPRINT);
}
+
+ public static boolean isUnitTest() {
+ try {
+ Class.forName("se.leap.bitmaskclient.testutils.MockHelper");
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
index 82c4e1df..55a92cb0 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -42,9 +42,13 @@ import java.util.Vector;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.VpnStatus.ByteCountListener;
import de.blinkt.openvpn.core.VpnStatus.StateListener;
+import de.blinkt.openvpn.core.connection.Connection;
import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.VpnNotificationManager;
+import se.leap.bitmaskclient.pluggableTransports.Dispatcher;
+import de.blinkt.openvpn.core.connection.Obfs4Connection;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_CONNECTED;
import static de.blinkt.openvpn.core.ConnectionStatus.LEVEL_WAITING_FOR_USER_INPUT;
import static de.blinkt.openvpn.core.NetworkSpace.IpAddress;
@@ -52,6 +56,7 @@ import static se.leap.bitmaskclient.Constants.PROVIDER_PROFILE;
public class OpenVPNService extends VpnService implements StateListener, Callback, ByteCountListener, IOpenVPNServiceInternal, VpnNotificationManager.VpnServiceCallback {
+ public static final String TAG = OpenVPNService.class.getSimpleName();
public static final String START_SERVICE = "de.blinkt.openvpn.START_SERVICE";
public static final String START_SERVICE_STICKY = "de.blinkt.openvpn.START_SERVICE_STICKY";
public static final String ALWAYS_SHOW_NOTIFICATION = "de.blinkt.openvpn.NOTIFICATION_ALWAYS_VISIBLE";
@@ -85,6 +90,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
private Toast mlastToast;
private Runnable mOpenVPNThread;
private VpnNotificationManager notificationManager;
+ private Dispatcher dispatcher;
private static final int PRIORITY_MIN = -2;
private static final int PRIORITY_DEFAULT = 0;
@@ -242,6 +248,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
if(isVpnRunning()) {
if (getManagement() != null && getManagement().stopVPN(replaceConnection)) {
if (!replaceConnection) {
+ if (dispatcher.isRunning()) {
+ dispatcher.stop();
+ }
VpnStatus.updateStateString("NOPROCESS", "VPN STOPPED", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED);
}
return true;
@@ -249,6 +258,9 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
return false;
} else {
if (!replaceConnection) {
+ if (dispatcher.isRunning()) {
+ dispatcher.stop();
+ }
VpnStatus.updateStateString("NOPROCESS", "VPN STOPPED", R.string.state_noprocess, ConnectionStatus.LEVEL_NOTCONNECTED);
return true;
}
@@ -366,6 +378,36 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
/**
* see change above (l. 292 ff)
*/
+ //TODO: investigate how connections[n] with n>0 get called during vpn setup (on connection refused?)
+ // Do we need to check if there's any obfs4 connection in mProfile.mConnections and start
+ // the dispatcher here? Can we start the dispatcher at a later point of execution, e.g. when
+ // connections[n], n>0 gets choosen?
+
+ VpnStatus.logInfo("Setting up dispatcher.");
+ Connection connection = mProfile.mConnections[0];
+
+ if (connection.getTransportType() == OBFS4) {
+ Obfs4Connection obfs4Connection = (Obfs4Connection) connection;
+ dispatcher = new Dispatcher(this,
+ obfs4Connection.getmObfs4RemoteProxyName(),
+ obfs4Connection.getmObfs4RemoteProxyPort(),
+ obfs4Connection.getmObfs4Certificate(),
+ obfs4Connection.getmObfs4IatMode());
+ dispatcher.initSync();
+
+ if (dispatcher.getPort() != null && dispatcher.getPort().length() > 0) {
+ connection.setServerPort(dispatcher.getPort());
+ Log.d(TAG, "Dispatcher running. Profile server name and port: " +
+ connection.getServerName() + ":" + connection.getServerPort());
+ VpnStatus.logInfo("Dispatcher running. Profile server name and port: " +
+ connection.getServerName() + ":" + connection.getServerPort());
+ } else {
+ Log.e(TAG, "Cannot initialize dispatcher for obfs4 connection. Shutting down.");
+ VpnStatus.logError("Cannot initialize dispatcher for obfs4 connection. Shutting down.");
+ }
+ }
+
+
VpnStatus.logInfo(R.string.building_configration);
VpnStatus.updateStateString("VPN_GENERATE_CONFIG", "", R.string.building_configration, ConnectionStatus.LEVEL_START);
@@ -743,7 +785,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
boolean profileUsesOrBot = false;
for (Connection c : mProfile.mConnections) {
- if (c.mProxyType == Connection.ProxyType.ORBOT)
+ if (c.getProxyType() == Connection.ProxyType.ORBOT)
profileUsesOrBot = true;
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
index 4f7a5bda..91cc66bc 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java
@@ -15,9 +15,10 @@ import android.os.Handler;
import android.os.ParcelFileDescriptor;
import android.support.annotation.NonNull;
import android.support.annotation.RequiresApi;
-import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
+
+import de.blinkt.openvpn.core.connection.Connection;
import se.leap.bitmaskclient.R;
import de.blinkt.openvpn.VpnProfile;
@@ -452,10 +453,10 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
if (mProfile.mConnections.length > connectionEntryNumber) {
Connection connection = mProfile.mConnections[connectionEntryNumber];
- proxyType = connection.mProxyType;
- proxyname = connection.mProxyName;
- proxyport = connection.mProxyPort;
- proxyUseAuth = connection.mUseProxyAuth;
+ proxyType = connection.getProxyType();
+ proxyname = connection.getProxyName();
+ proxyport = connection.getProxyPort();
+ proxyUseAuth = connection.isUseProxyAuth();
// Use transient variable to remember http user/password
mCurrentProxyConnection = connection;
@@ -696,8 +697,8 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
} else if (needed.equals("HTTP Proxy")) {
if( mCurrentProxyConnection != null) {
- pw = mCurrentProxyConnection.mProxyAuthPassword;
- username = mCurrentProxyConnection.mProxyAuthUser;
+ pw = mCurrentProxyConnection.getProxyAuthPassword();
+ username = mCurrentProxyConnection.getProxyAuthUser();
}
}
if (pw != null) {
@@ -782,7 +783,6 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement {
boolean stopSucceed = stopOpenVPN();
if (stopSucceed) {
mShuttingDown = true;
-
}
return stopSucceed;
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java b/app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java
new file mode 100644
index 00000000..f333a13e
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java
@@ -0,0 +1,207 @@
+/*
+ * Copyright (c) 2012-2016 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.core.connection;
+
+import android.text.TextUtils;
+
+import java.io.Serializable;
+import java.util.Locale;
+
+public abstract class Connection implements Serializable, Cloneable {
+ private String mServerName = "openvpn.example.com";
+ private String mServerPort = "1194";
+ private boolean mUseUdp = true;
+ private String mCustomConfiguration = "";
+ private boolean mUseCustomConfig = false;
+ private boolean mEnabled = true;
+ private int mConnectTimeout = 0;
+ private static final int CONNECTION_DEFAULT_TIMEOUT = 120;
+ private ProxyType mProxyType = ProxyType.NONE;
+ private String mProxyName = "proxy.example.com";
+ private String mProxyPort = "8080";
+
+ private boolean mUseProxyAuth;
+ private String mProxyAuthUser = null;
+ private String mProxyAuthPassword = null;
+
+ public enum ProxyType {
+ NONE,
+ HTTP,
+ SOCKS5,
+ ORBOT
+ }
+
+ public enum TransportType {
+ OBFS4,
+ OPENVPN
+ }
+
+ private static final long serialVersionUID = 92031902903829089L;
+
+
+ public String getConnectionBlock(boolean isOpenVPN3) {
+ String cfg = "";
+
+ // Server Address
+ cfg += "remote ";
+ cfg += mServerName;
+ cfg += " ";
+ cfg += mServerPort;
+ if (mUseUdp)
+ cfg += " udp\n";
+ else
+ cfg += " tcp-client\n";
+
+ if (mConnectTimeout != 0)
+ cfg += String.format(Locale.US, " connect-timeout %d\n", mConnectTimeout);
+
+ // OpenVPN 2.x manages proxy connection via management interface
+ if ((isOpenVPN3 || usesExtraProxyOptions()) && mProxyType == ProxyType.HTTP)
+ {
+ cfg+=String.format(Locale.US,"http-proxy %s %s\n", mProxyName, mProxyPort);
+ if (mUseProxyAuth)
+ cfg+=String.format(Locale.US, "<http-proxy-user-pass>\n%s\n%s\n</http-proxy-user-pass>\n", mProxyAuthUser, mProxyAuthPassword);
+ }
+ if (usesExtraProxyOptions() && mProxyType == ProxyType.SOCKS5) {
+ cfg+=String.format(Locale.US,"socks-proxy %s %s\n", mProxyName, mProxyPort);
+ }
+
+ if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) {
+ cfg += mCustomConfiguration;
+ cfg += "\n";
+ }
+
+
+ return cfg;
+ }
+
+ public boolean usesExtraProxyOptions() {
+ return (mUseCustomConfig && mCustomConfiguration.contains("http-proxy-option "));
+ }
+
+
+ @Override
+ public Connection clone() throws CloneNotSupportedException {
+ return (Connection) super.clone();
+ }
+
+ public boolean isOnlyRemote() {
+ return TextUtils.isEmpty(mCustomConfiguration) || !mUseCustomConfig;
+ }
+
+ public int getTimeout() {
+ if (mConnectTimeout <= 0)
+ return CONNECTION_DEFAULT_TIMEOUT;
+ else
+ return mConnectTimeout;
+ }
+
+ public String getServerName() {
+ return mServerName;
+ }
+
+ public void setServerName(String mServerName) {
+ this.mServerName = mServerName;
+ }
+
+ public String getServerPort() {
+ return mServerPort;
+ }
+
+ public void setServerPort(String serverPort) {
+ this.mServerPort = serverPort;
+ }
+
+ public boolean isUseUdp() {
+ return mUseUdp;
+ }
+
+ public void setUseUdp(boolean useUdp) {
+ this.mUseUdp = useUdp;
+ }
+
+ public String getCustomConfiguration() {
+ return mCustomConfiguration;
+ }
+
+ public void setCustomConfiguration(String customConfiguration) {
+ this.mCustomConfiguration = customConfiguration;
+ }
+
+ public boolean isUseCustomConfig() {
+ return mUseCustomConfig;
+ }
+
+ public void setUseCustomConfig(boolean useCustomConfig) {
+ this.mUseCustomConfig = useCustomConfig;
+ }
+
+ public boolean isEnabled() {
+ return mEnabled;
+ }
+
+ public void setEnabled(boolean enabled) {
+ this.mEnabled = enabled;
+ }
+
+ public int getConnectTimeout() {
+ return mConnectTimeout;
+ }
+
+ public void setConnectTimeout(int connectTimeout) {
+ this.mConnectTimeout = connectTimeout;
+ }
+
+ public ProxyType getProxyType() {
+ return mProxyType;
+ }
+
+ public void setProxyType(ProxyType proxyType) {
+ this.mProxyType = proxyType;
+ }
+
+ public String getProxyName() {
+ return mProxyName;
+ }
+
+ public void setProxyName(String proxyName) {
+ this.mProxyName = proxyName;
+ }
+
+ public String getProxyPort() {
+ return mProxyPort;
+ }
+
+ public void setProxyPort(String proxyPort) {
+ this.mProxyPort = proxyPort;
+ }
+
+ public boolean isUseProxyAuth() {
+ return mUseProxyAuth;
+ }
+
+ public void setUseProxyAuth(boolean useProxyAuth) {
+ this.mUseProxyAuth = useProxyAuth;
+ }
+
+ public String getProxyAuthUser() {
+ return mProxyAuthUser;
+ }
+
+ public void setProxyAuthUser(String proxyAuthUser) {
+ this.mProxyAuthUser = proxyAuthUser;
+ }
+
+ public String getProxyAuthPassword() {
+ return mProxyAuthPassword;
+ }
+
+ public void setProxyAuthPassword(String proxyAuthPassword) {
+ this.mProxyAuthPassword = proxyAuthPassword;
+ }
+
+ public abstract TransportType getTransportType();
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java b/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java
new file mode 100644
index 00000000..790b8b1a
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java
@@ -0,0 +1,83 @@
+package de.blinkt.openvpn.core.connection;
+
+import org.json.JSONObject;
+
+/**
+ * Created by cyberta on 08.03.19.
+ */
+
+public class Obfs4Connection extends Connection {
+
+ private static final String TAG = Obfs4Connection.class.getName();
+
+
+ private String mObfs4RemoteProxyName = "";
+ private String mObfs4RemoteProxyPort = "";
+ private String mObfs4Certificate = "";
+ private String mObfs4IatMode = "";
+
+ public Obfs4Connection() {
+ setDefaults();
+ }
+
+ public Obfs4Connection(Connection connection) {
+ mObfs4RemoteProxyName = connection.getServerName();
+ setConnectTimeout(connection.getConnectTimeout());
+ setCustomConfiguration(connection.getCustomConfiguration());
+ setUseCustomConfig(connection.isUseCustomConfig());
+
+ setDefaults();
+ }
+
+ private void setDefaults() {
+ setUseUdp(false);
+ setServerName("127.0.0.1");
+ setServerPort("");
+ setProxyName("");
+ setProxyPort("");
+ setProxyAuthUser(null);
+ setProxyAuthPassword(null);
+ setProxyType(ProxyType.NONE);
+ setUseProxyAuth(false);
+ }
+
+ public void setTransportOptions(JSONObject jsonObject) {
+ mObfs4Certificate = jsonObject.optString("cert");
+ mObfs4IatMode = jsonObject.optString("iat-mode");
+ }
+
+ @Override
+ public Connection clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+
+ @Override
+ public TransportType getTransportType() {
+ return TransportType.OBFS4;
+ }
+
+ public String getmObfs4RemoteProxyName() {
+ return mObfs4RemoteProxyName;
+ }
+
+ public void setObfs4RemoteProxyName(String mObfs4RemoteProxyName) {
+ this.mObfs4RemoteProxyName = mObfs4RemoteProxyName;
+ }
+
+ public String getmObfs4RemoteProxyPort() {
+ return mObfs4RemoteProxyPort;
+ }
+
+ public void setObfs4RemoteProxyPort(String mObfs4RemoteProxyPort) {
+ this.mObfs4RemoteProxyPort = mObfs4RemoteProxyPort;
+ }
+
+ public String getmObfs4Certificate() {
+ return mObfs4Certificate;
+ }
+
+ public String getmObfs4IatMode() {
+ return mObfs4IatMode;
+ }
+
+}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/connection/OpenvpnConnection.java b/app/src/main/java/de/blinkt/openvpn/core/connection/OpenvpnConnection.java
new file mode 100644
index 00000000..3a3fd0c3
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/connection/OpenvpnConnection.java
@@ -0,0 +1,13 @@
+package de.blinkt.openvpn.core.connection;
+
+/**
+ * Created by cyberta on 11.03.19.
+ */
+
+public class OpenvpnConnection extends Connection {
+
+ @Override
+ public TransportType getTransportType() {
+ return TransportType.OPENVPN;
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/Constants.java b/app/src/main/java/se/leap/bitmaskclient/Constants.java
index 42df6d1d..7503d29f 100644
--- a/app/src/main/java/se/leap/bitmaskclient/Constants.java
+++ b/app/src/main/java/se/leap/bitmaskclient/Constants.java
@@ -113,4 +113,22 @@ public interface Constants {
String FIRST_TIME_USER_DATE = "first_time_user_date";
+ //////////////////////////////////////////////
+ // JSON KEYS
+ /////////////////////////////////////////////
+ String IP_ADDRESS = "ip_address";
+ String REMOTE = "remote";
+ String PORTS = "ports";
+ String PROTOCOLS = "protocols";
+ String CAPABILITIES = "capabilities";
+ String TRANSPORT = "transport";
+ String TYPE = "type";
+ String OPTIONS = "options";
+ String VERSION = "version";
+ String NAME = "name";
+ String TIMEZONE = "timezone";
+ String LOCATIONS = "locations";
+ String LOCATION = "location";
+ String OPENVPN_CONFIGURATION = "openvpn_configuration";
+ String GATEWAYS = "gateways";
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java
index d8aca351..945429fd 100644
--- a/app/src/main/java/se/leap/bitmaskclient/StartActivity.java
+++ b/app/src/main/java/se/leap/bitmaskclient/StartActivity.java
@@ -25,20 +25,36 @@ import android.support.annotation.IntDef;
import android.support.annotation.Nullable;
import android.util.Log;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import de.blinkt.openvpn.core.Preferences;
import de.blinkt.openvpn.core.VpnStatus;
import se.leap.bitmaskclient.eip.EipCommand;
+import se.leap.bitmaskclient.utils.PreferenceHelper;
+import static se.leap.bitmaskclient.BuildConfig.useDemoConfig;
import static se.leap.bitmaskclient.Constants.APP_ACTION_CONFIGURE_ALWAYS_ON_PROFILE;
import static se.leap.bitmaskclient.Constants.EIP_RESTART_ON_BOOT;
import static se.leap.bitmaskclient.Constants.PREFERENCES_APP_VERSION;
+import static se.leap.bitmaskclient.Constants.PROVIDER_CONFIGURED;
import static se.leap.bitmaskclient.Constants.PROVIDER_EIP_DEFINITION;
import static se.leap.bitmaskclient.Constants.PROVIDER_KEY;
+import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY;
+import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.Constants.REQUEST_CODE_CONFIGURE_LEAP;
import static se.leap.bitmaskclient.Constants.SHARED_PREFERENCES;
import static se.leap.bitmaskclient.MainActivity.ACTION_SHOW_VPN_FRAGMENT;
+import static se.leap.bitmaskclient.Provider.CA_CERT;
+import static se.leap.bitmaskclient.Provider.MAIN_URL;
import static se.leap.bitmaskclient.utils.ConfigHelper.isDefaultBitmask;
import static se.leap.bitmaskclient.utils.PreferenceHelper.getSavedProviderFromSharedPreferences;
import static se.leap.bitmaskclient.utils.PreferenceHelper.providerInSharedPreferences;
@@ -90,6 +106,10 @@ public class StartActivity extends Activity{
// initialize app necessities
VpnStatus.initLogCache(getApplicationContext().getCacheDir());
+ if (useDemoConfig) {
+ demoSetup();
+ }
+
prepareEIP();
}
@@ -162,8 +182,8 @@ public class StartActivity extends Activity{
}
private void prepareEIP() {
- boolean provider_exists = providerInSharedPreferences(preferences);
- if (provider_exists) {
+ boolean providerExists = providerInSharedPreferences(preferences);
+ if (providerExists) {
Provider provider = getSavedProviderFromSharedPreferences(preferences);
if(!provider.isConfigured()) {
configureLeapProvider();
@@ -216,4 +236,65 @@ public class StartActivity extends Activity{
finish();
}
+ private String getInputAsString(InputStream fileAsInputStream) throws IOException {
+ BufferedReader br = new BufferedReader(new InputStreamReader(fileAsInputStream));
+ StringBuilder sb = new StringBuilder();
+ String line = br.readLine();
+ while (line != null) {
+ sb.append(line);
+ line = br.readLine();
+ }
+
+ return sb.toString();
+ }
+
+ private void demoSetup() {
+ try {
+ //set demo data
+ String demoEipServiceJson = getInputAsString(getAssets().open("ptdemo.bitmask.eip-service.json"));
+ String secrets = getInputAsString(getAssets().open("ptdemo.bitmask.secrets.json"));
+ String provider = getInputAsString(getAssets().open("ptdemo.bitmask.net.json"));
+
+ Log.d(TAG, "setup provider: " + provider);
+ Log.d(TAG, "setup eip json: " + demoEipServiceJson);
+ JSONObject secretsJson = new JSONObject(secrets);
+
+ preferences.edit().putString(PROVIDER_EIP_DEFINITION+".demo.bitmask.net", demoEipServiceJson).
+ putString(PROVIDER_EIP_DEFINITION, demoEipServiceJson).
+ putString(CA_CERT, secretsJson.getString(CA_CERT)).
+ putString(PROVIDER_PRIVATE_KEY, secretsJson.getString(PROVIDER_PRIVATE_KEY)).
+ putString(PROVIDER_VPN_CERTIFICATE, secretsJson.getString(PROVIDER_VPN_CERTIFICATE)).
+ putString(Provider.KEY, provider).
+ putString(MAIN_URL, "https://demo.bitmask.net").
+ putBoolean(PROVIDER_CONFIGURED, true).commit();
+
+ PreferenceHelper.getSavedProviderFromSharedPreferences(preferences);
+ ProviderObservable.getInstance().updateProvider(PreferenceHelper.getSavedProviderFromSharedPreferences(preferences));
+
+ // remove last used profiles
+ SharedPreferences prefs = Preferences.getDefaultSharedPreferences(this);
+ SharedPreferences.Editor prefsedit = prefs.edit();
+ prefsedit.remove("lastConnectedProfile").commit();
+ File f = new File(this.getCacheDir().getAbsolutePath() + "/android.conf");
+ if (f.exists()) {
+ Log.d(TAG, "android.conf exists -> delete:" + f.delete());
+ }
+
+ File filesDirectory = new File(this.getFilesDir().getAbsolutePath());
+ if (filesDirectory.exists() && filesDirectory.isDirectory()) {
+ File[] filesInDirectory = filesDirectory.listFiles();
+ for (File file : filesInDirectory) {
+ Log.d(TAG, "delete profile: " + file.getName() + ": "+ file.delete());
+
+ }
+ } else Log.d(TAG, "file folder doesn't exist");
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ }
+
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
index 55ade1ae..b1554af0 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/Gateway.java
@@ -22,11 +22,17 @@ import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
-import java.io.StringReader;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.ConfigParser;
+import static se.leap.bitmaskclient.Constants.LOCATION;
+import static se.leap.bitmaskclient.Constants.LOCATIONS;
+import static se.leap.bitmaskclient.Constants.NAME;
+import static se.leap.bitmaskclient.Constants.OPENVPN_CONFIGURATION;
+import static se.leap.bitmaskclient.Constants.TIMEZONE;
+import static se.leap.bitmaskclient.Constants.VERSION;
+
/**
* Gateway provides objects defining gateways and their metadata.
* Each instance contains a VpnProfile for OpenVPN specific data and member
@@ -34,6 +40,7 @@ import de.blinkt.openvpn.core.ConfigParser;
*
* @author Sean Leonard <meanderingcode@aetherislands.net>
* @author Parménides GV <parmegv@sdf.org>
+ * @author cyberta
*/
public class Gateway {
@@ -44,50 +51,57 @@ public class Gateway {
private JSONObject secrets;
private JSONObject gateway;
- private String mName;
+ private String name;
private int timezone;
- private VpnProfile mVpnProfile;
+ private int apiVersion;
+ private VpnProfile vpnProfile;
/**
* Build a gateway object from a JSON OpenVPN gateway definition in eip-service.json
* and create a VpnProfile belonging to it.
*/
- public Gateway(JSONObject eip_definition, JSONObject secrets, JSONObject gateway) {
+ public Gateway(JSONObject eipDefinition, JSONObject secrets, JSONObject gateway) {
this.gateway = gateway;
this.secrets = secrets;
- generalConfiguration = getGeneralConfiguration(eip_definition);
- timezone = getTimezone(eip_definition);
- mName = locationAsName(eip_definition);
-
- mVpnProfile = createVPNProfile();
- mVpnProfile.mName = mName;
+ generalConfiguration = getGeneralConfiguration(eipDefinition);
+ timezone = getTimezone(eipDefinition);
+ name = locationAsName(eipDefinition);
+ apiVersion = getApiVersion(eipDefinition);
+ vpnProfile = createVPNProfile();
+ if (vpnProfile != null) {
+ vpnProfile.mName = name;
+ }
}
- private JSONObject getGeneralConfiguration(JSONObject eip_definition) {
+ private JSONObject getGeneralConfiguration(JSONObject eipDefinition) {
try {
- return eip_definition.getJSONObject("openvpn_configuration");
+ return eipDefinition.getJSONObject(OPENVPN_CONFIGURATION);
} catch (JSONException e) {
return new JSONObject();
}
}
- private int getTimezone(JSONObject eip_definition) {
- JSONObject location = getLocationInfo(eip_definition);
- return location.optInt("timezone");
+ private int getTimezone(JSONObject eipDefinition) {
+ JSONObject location = getLocationInfo(eipDefinition);
+ return location.optInt(TIMEZONE);
+ }
+
+ private int getApiVersion(JSONObject eipDefinition) {
+ return eipDefinition.optInt(VERSION);
}
- private String locationAsName(JSONObject eip_definition) {
- JSONObject location = getLocationInfo(eip_definition);
- return location.optString("name");
+ private String locationAsName(JSONObject eipDefinition) {
+ JSONObject location = getLocationInfo(eipDefinition);
+ return location.optString(NAME);
}
private JSONObject getLocationInfo(JSONObject eipDefinition) {
try {
- JSONObject locations = eipDefinition.getJSONObject("locations");
+ JSONObject locations = eipDefinition.getJSONObject(LOCATIONS);
- return locations.getJSONObject(gateway.getString("location"));
+ return locations.getJSONObject(gateway.getString(LOCATION));
} catch (JSONException e) {
return new JSONObject();
}
@@ -98,18 +112,9 @@ public class Gateway {
*/
private VpnProfile createVPNProfile() {
try {
- ConfigParser cp = new ConfigParser();
-
- VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway);
- String configuration = vpnConfigurationGenerator.generate();
-
- cp.parseConfig(new StringReader(configuration));
- return cp.convertProfile();
- } catch (ConfigParser.ConfigParseError e) {
- // FIXME We didn't get a VpnProfile! Error handling! and log level
- e.printStackTrace();
- return null;
- } catch (IOException e) {
+ VpnConfigGenerator vpnConfigurationGenerator = new VpnConfigGenerator(generalConfiguration, secrets, gateway, apiVersion);
+ return vpnConfigurationGenerator.generateVpnProfile();
+ } catch (ConfigParser.ConfigParseError | IOException | CloneNotSupportedException | JSONException e) {
// FIXME We didn't get a VpnProfile! Error handling! and log level
e.printStackTrace();
return null;
@@ -117,11 +122,11 @@ public class Gateway {
}
public String getName() {
- return mName;
+ return name;
}
public VpnProfile getProfile() {
- return mVpnProfile;
+ return vpnProfile;
}
public int getTimezone() {
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java
index 2bd666bf..0ba0f207 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaySelector.java
@@ -36,7 +36,7 @@ public class GatewaySelector {
}
}
- Log.e(TAG, "There are less than " + nClosest + " Gateways available.");
+ Log.e(TAG, "There are less than " + (nClosest + 1) + " Gateways available.");
return null;
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
index 060843fd..c650938c 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/GatewaysManager.java
@@ -30,11 +30,18 @@ import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.connection.Connection;
import se.leap.bitmaskclient.Provider;
import se.leap.bitmaskclient.utils.PreferenceHelper;
+import static se.leap.bitmaskclient.Constants.CAPABILITIES;
+import static se.leap.bitmaskclient.Constants.GATEWAYS;
import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY;
import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.Constants.TRANSPORT;
+import static se.leap.bitmaskclient.Constants.TYPE;
+import static se.leap.bitmaskclient.Constants.VERSION;
/**
* @author parmegv
@@ -88,10 +95,11 @@ public class GatewaysManager {
*/
void fromEipServiceJson(JSONObject eipDefinition) {
try {
- JSONArray gatewaysDefined = eipDefinition.getJSONArray("gateways");
+ JSONArray gatewaysDefined = eipDefinition.getJSONArray(GATEWAYS);
+ int apiVersion = eipDefinition.getInt(VERSION);
for (int i = 0; i < gatewaysDefined.length(); i++) {
JSONObject gw = gatewaysDefined.getJSONObject(i);
- if (isOpenVpnGateway(gw)) {
+ if (isOpenVpnGateway(gw, apiVersion)) {
JSONObject secrets = secretsConfiguration();
Gateway aux = new Gateway(eipDefinition, secrets, gw);
if (!gateways.contains(aux)) {
@@ -110,12 +118,29 @@ public class GatewaysManager {
* @param gateway to check
* @return true if gateway is an OpenVpn gateway otherwise false
*/
- private boolean isOpenVpnGateway(JSONObject gateway) {
- try {
- String transport = gateway.getJSONObject("capabilities").getJSONArray("transport").toString();
- return transport.contains("openvpn");
- } catch (JSONException e) {
- return false;
+ private boolean isOpenVpnGateway(JSONObject gateway, int apiVersion) {
+ switch (apiVersion) {
+ default:
+ case 1:
+ try {
+ String transport = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT).toString();
+ return transport.contains("openvpn");
+ } catch (JSONException e) {
+ return false;
+ }
+ case 2:
+ try {
+ JSONArray transports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT);
+ for (int i = 0; i < transports.length(); i++) {
+ JSONObject transport = transports.getJSONObject(i);
+ if (transport.optString(TYPE).equals("openvpn")) {
+ return true;
+ }
+ }
+ return false;
+ } catch (JSONException e) {
+ return false;
+ }
}
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
index 6f0ccf18..7f09d21e 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
@@ -20,48 +20,133 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
+import java.io.IOException;
+import java.io.StringReader;
import java.util.Iterator;
+import de.blinkt.openvpn.VpnProfile;
+import de.blinkt.openvpn.core.ConfigParser;
+import de.blinkt.openvpn.core.connection.Connection;
+import de.blinkt.openvpn.core.connection.Obfs4Connection;
import se.leap.bitmaskclient.Provider;
+import static se.leap.bitmaskclient.Constants.CAPABILITIES;
+import static se.leap.bitmaskclient.Constants.IP_ADDRESS;
+import static se.leap.bitmaskclient.Constants.OPTIONS;
+import static se.leap.bitmaskclient.Constants.PORTS;
+import static se.leap.bitmaskclient.Constants.PROTOCOLS;
import static se.leap.bitmaskclient.Constants.PROVIDER_PRIVATE_KEY;
import static se.leap.bitmaskclient.Constants.PROVIDER_VPN_CERTIFICATE;
+import static se.leap.bitmaskclient.Constants.REMOTE;
+import static se.leap.bitmaskclient.Constants.TRANSPORT;
+import static se.leap.bitmaskclient.Constants.TYPE;
public class VpnConfigGenerator {
- private JSONObject general_configuration;
+ private JSONObject generalConfiguration;
private JSONObject gateway;
private JSONObject secrets;
+ private JSONObject obfs4Transport;
+ private int apiVersion;
+
+ private ConfigParser icsOpenvpnConfigParser = new ConfigParser();
+
public final static String TAG = VpnConfigGenerator.class.getSimpleName();
private final String newLine = System.getProperty("line.separator"); // Platform new line
- public VpnConfigGenerator(JSONObject general_configuration, JSONObject secrets, JSONObject gateway) {
- this.general_configuration = general_configuration;
+ public VpnConfigGenerator(JSONObject generalConfiguration, JSONObject secrets, JSONObject gateway, int apiVersion) {
+ this.generalConfiguration = generalConfiguration;
this.gateway = gateway;
this.secrets = secrets;
+ this.apiVersion = apiVersion;
+ checkCapabilities();
}
- public String generate() {
- return
- generalConfiguration()
+ public void checkCapabilities() {
+
+ try {
+ switch (apiVersion) {
+ case 2:
+ JSONArray supportedTransports = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT);
+ for (int i = 0; i < supportedTransports.length(); i++) {
+ JSONObject transport = supportedTransports.getJSONObject(i);
+ if (transport.getString(TYPE).equals("obfs4")) {
+ obfs4Transport = transport;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public VpnProfile generateVpnProfile() throws IllegalStateException,
+ IOException,
+ ConfigParser.ConfigParseError,
+ CloneNotSupportedException,
+ JSONException,
+ NumberFormatException {
+
+ VpnProfile profile = createOvpnProfile();
+ if (supportsObfs4()) {
+ addPluggableTransportConnections(profile);
+ }
+ return profile;
+ }
+
+ private boolean supportsObfs4(){
+ return obfs4Transport != null;
+ }
+
+ private void addPluggableTransportConnections(VpnProfile profile) throws JSONException, CloneNotSupportedException {
+ JSONArray ports = obfs4Transport.getJSONArray(PORTS);
+ Connection[] updatedConnections = new Connection[profile.mConnections.length + ports.length()];
+
+ for (int i = 0; i < ports.length(); i++) {
+ String port = ports.getString(i);
+ Obfs4Connection obfs4Connection = new Obfs4Connection();
+ obfs4Connection.setObfs4RemoteProxyName(gateway.getString(IP_ADDRESS));
+ obfs4Connection.setObfs4RemoteProxyPort(port);
+ obfs4Connection.setTransportOptions(obfs4Transport.optJSONObject(OPTIONS));
+ updatedConnections[i] = obfs4Connection;
+ }
+ int k = 0;
+ for (int i = ports.length(); i < updatedConnections.length; i++, k++) {
+ updatedConnections[i] = profile.mConnections[k].clone();
+ }
+ profile.mConnections = updatedConnections;
+ }
+
+ private String getConfigurationString() {
+ return generalConfiguration()
+ newLine
- + gatewayConfiguration()
+ + ovpnGatewayConfiguration()
+ newLine
+ secretsConfiguration()
+ newLine
+ androidCustomizations();
}
+ private VpnProfile createOvpnProfile() throws IOException, ConfigParser.ConfigParseError {
+ String configuration = getConfigurationString();
+ icsOpenvpnConfigParser.parseConfig(new StringReader(configuration));
+ return icsOpenvpnConfigParser.convertProfile();
+ }
+
private String generalConfiguration() {
String commonOptions = "";
try {
- Iterator keys = general_configuration.keys();
+ Iterator keys = generalConfiguration.keys();
while (keys.hasNext()) {
String key = keys.next().toString();
commonOptions += key + " ";
- for (String word : String.valueOf(general_configuration.get(key)).split(" "))
+ for (String word : String.valueOf(generalConfiguration.get(key)).split(" "))
commonOptions += word + " ";
commonOptions += newLine;
@@ -76,41 +161,73 @@ public class VpnConfigGenerator {
return commonOptions;
}
- private String gatewayConfiguration() {
+ private String ovpnGatewayConfiguration() {
String remotes = "";
- String ipAddressKeyword = "ip_address";
- String remoteKeyword = "remote";
- String portsKeyword = "ports";
- String protocolKeyword = "protocols";
- String capabilitiesKeyword = "capabilities";
-
+ StringBuilder stringBuilder = new StringBuilder();
try {
- String ip_address = gateway.getString(ipAddressKeyword);
- JSONObject capabilities = gateway.getJSONObject(capabilitiesKeyword);
- JSONArray ports = capabilities.getJSONArray(portsKeyword);
- for (int i = 0; i < ports.length(); i++) {
- String port_specific_remotes = "";
- int port = ports.getInt(i);
- JSONArray protocols = capabilities.getJSONArray(protocolKeyword);
- for (int j = 0; j < protocols.length(); j++) {
- String protocol = protocols.optString(j);
- String new_remote = remoteKeyword + " " + ip_address + " " + port + " " + protocol + newLine;
-
- port_specific_remotes += new_remote;
- }
- remotes += port_specific_remotes;
+ String ipAddress = gateway.getString(IP_ADDRESS);
+ JSONObject capabilities = gateway.getJSONObject(CAPABILITIES);
+ JSONArray transports = capabilities.getJSONArray(TRANSPORT);
+ switch (apiVersion) {
+ default:
+ case 1:
+ ovpnGatewayConfigApiv1(stringBuilder, ipAddress, capabilities);
+ break;
+ case 2:
+ ovpnGatewayConfigApiv2(stringBuilder, ipAddress, transports);
+ break;
}
} catch (JSONException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
+
+ remotes = stringBuilder.toString();
if (remotes.endsWith(newLine)) {
remotes = remotes.substring(0, remotes.lastIndexOf(newLine));
}
return remotes;
}
+ private void ovpnGatewayConfigApiv1(StringBuilder stringBuilder, String ipAddress, JSONObject capabilities) throws JSONException {
+ int port;
+ String protocol;
+
+ JSONArray ports = capabilities.getJSONArray(PORTS);
+ for (int i = 0; i < ports.length(); i++) {
+ port = ports.getInt(i);
+ JSONArray protocols = capabilities.getJSONArray(PROTOCOLS);
+ for (int j = 0; j < protocols.length(); j++) {
+ protocol = protocols.optString(j);
+ String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine;
+ stringBuilder.append(newRemote);
+ }
+ }
+ }
+
+ private void ovpnGatewayConfigApiv2(StringBuilder stringBuilder, String ipAddress, JSONArray transports) throws JSONException {
+ String port;
+ String protocol;
+ for (int i = 0; i < transports.length(); i++) {
+ JSONObject transport = transports.getJSONObject(i);
+ if (!transport.getString(TYPE).equals("openvpn")) {
+ continue;
+ }
+ JSONArray ports = transport.getJSONArray(PORTS);
+ for (int j = 0; j < ports.length(); j++) {
+ port = ports.getString(j);
+ JSONArray protocols = transport.getJSONArray(PROTOCOLS);
+ for (int k = 0; k < protocols.length(); k++) {
+ protocol = protocols.optString(k);
+ String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine;
+ stringBuilder.append(newRemote);
+ }
+ }
+ }
+ }
+
+
private String secretsConfiguration() {
try {
String ca =
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/BinaryInstaller.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/BinaryInstaller.java
new file mode 100644
index 00000000..0d6aa61e
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/BinaryInstaller.java
@@ -0,0 +1,204 @@
+/* Copyright (c) 2009, Nathan Freitas, Orbot / The Guardian Project - http://openideals.com/guardian */
+/* See LICENSE for licensing information */
+
+package se.leap.bitmaskclient.pluggableTransports;
+
+import android.content.Context;
+import android.util.Log;
+
+import java.io.DataInputStream;
+import java.io.DataOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.concurrent.TimeoutException;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+public class BinaryInstaller {
+
+ File installFolder;
+ Context context;
+
+ public BinaryInstaller(Context context, File installFolder)
+ {
+ this.installFolder = installFolder;
+
+ this.context = context;
+ }
+
+ public void deleteDirectory(File file) {
+ if( file.exists() ) {
+ if (file.isDirectory()) {
+ File[] files = file.listFiles();
+ for(int i=0; i<files.length; i++) {
+ if(files[i].isDirectory()) {
+ deleteDirectory(files[i]);
+ }
+ else {
+ files[i].delete();
+ }
+ }
+ }
+
+ file.delete();
+ }
+ }
+
+ private final static String COMMAND_RM_FORCE = "rm -f ";
+ private final static String MP3_EXT = ".mp3";
+ //
+ /*
+ * Extract the resources from the APK file using ZIP
+ */
+ public File installResource (String basePath, String assetKey, boolean overwrite) throws IOException, FileNotFoundException, TimeoutException
+ {
+
+ InputStream is;
+ File outFile;
+
+ outFile = new File(installFolder, assetKey);
+
+ if (outFile.exists() && (!overwrite)) {
+ Log.d("BINARY_INSTALLER", "Binary already exists! Using " + outFile.getCanonicalPath());
+ return outFile;
+ }
+
+ deleteDirectory(installFolder);
+ installFolder.mkdirs();
+
+ Log.d("BINARY_INSTALLER", "Search asset in " + basePath + "/" + assetKey);
+
+ is = context.getAssets().open(basePath + '/' + assetKey);
+ streamToFile(is,outFile, false, false);
+ setExecutable(outFile);
+
+ Log.d("BINARY_INSTALLER", "Asset copied from " + basePath + "/" + assetKey + " to: " + outFile.getCanonicalPath());
+
+ return outFile;
+ }
+
+
+ private final static int FILE_WRITE_BUFFER_SIZE = 1024*8;
+ /*
+ * Write the inputstream contents to the file
+ */
+ public static boolean streamToFile(InputStream stm, File outFile, boolean append, boolean zip) throws IOException
+
+ {
+ byte[] buffer = new byte[FILE_WRITE_BUFFER_SIZE];
+
+ int bytecount;
+
+ OutputStream stmOut = new FileOutputStream(outFile.getAbsolutePath(), append);
+ ZipInputStream zis = null;
+
+ if (zip)
+ {
+ zis = new ZipInputStream(stm);
+ ZipEntry ze = zis.getNextEntry();
+ stm = zis;
+
+ }
+
+ while ((bytecount = stm.read(buffer)) > 0)
+ {
+
+ stmOut.write(buffer, 0, bytecount);
+
+ }
+
+ stmOut.close();
+ stm.close();
+
+ if (zis != null)
+ zis.close();
+
+
+ return true;
+
+ }
+
+ //copy the file from inputstream to File output - alternative impl
+ public static boolean copyFile (InputStream is, File outputFile)
+ {
+
+ try {
+ if (outputFile.exists())
+ outputFile.delete();
+
+ boolean newFile = outputFile.createNewFile();
+ DataOutputStream out = new DataOutputStream(new FileOutputStream(outputFile));
+ DataInputStream in = new DataInputStream(is);
+
+ int b = -1;
+ byte[] data = new byte[1024];
+
+ while ((b = in.read(data)) != -1) {
+ out.write(data);
+ }
+
+ if (b == -1); //rejoice
+
+ //
+ out.flush();
+ out.close();
+ in.close();
+ // chmod?
+
+ return newFile;
+
+
+ } catch (IOException ex) {
+ Log.e("Binaryinstaller", "error copying binary", ex);
+ return false;
+ }
+
+ }
+
+ /**
+ * Copies a raw resource file, given its ID to the given location
+ * @param ctx context
+ * @param resid resource id
+ * @param file destination file
+ * @param mode file permissions (E.g.: "755")
+ * @throws IOException on error
+ * @throws InterruptedException when interrupted
+ */
+ public static void copyRawFile(Context ctx, int resid, File file, String mode, boolean isZipd) throws IOException, InterruptedException
+ {
+ final String abspath = file.getAbsolutePath();
+ // Write the iptables binary
+ final FileOutputStream out = new FileOutputStream(file);
+ InputStream is = ctx.getResources().openRawResource(resid);
+
+ if (isZipd)
+ {
+ ZipInputStream zis = new ZipInputStream(is);
+ ZipEntry ze = zis.getNextEntry();
+ is = zis;
+ }
+
+ byte buf[] = new byte[1024];
+ int len;
+ while ((len = is.read(buf)) > 0) {
+ out.write(buf, 0, len);
+ }
+ out.close();
+ is.close();
+ // Change the permissions
+ Runtime.getRuntime().exec("chmod "+mode+" "+abspath).waitFor();
+ }
+
+
+ private void setExecutable(File fileBin) {
+ fileBin.setReadable(true);
+ fileBin.setExecutable(true);
+ fileBin.setWritable(false);
+ fileBin.setWritable(true, true);
+ }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java
new file mode 100644
index 00000000..ac846fd9
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Dispatcher.java
@@ -0,0 +1,229 @@
+/**
+ * Copyright (c) 2019 LEAP Encryption Access Project and contributers
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+package se.leap.bitmaskclient.pluggableTransports;
+
+import android.content.Context;
+import android.support.annotation.WorkerThread;
+import android.util.Log;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStreamWriter;
+import java.util.StringTokenizer;
+
+
+/**
+ * Created by cyberta on 22.02.19.
+ */
+
+public class Dispatcher {
+ private static final String ASSET_KEY = "piedispatcher";
+ private static final String TAG = Dispatcher.class.getName();
+ private final String remoteIP;
+ private final String remotePort;
+ private final String certificate;
+ private final String iatMode;
+ private File fileDispatcher;
+ private Context context;
+ private String port = "";
+ private Thread dispatcherThread = null;
+ private int dipatcherPid = -1;
+
+ public Dispatcher(Context context, String remoteIP, String remotePort, String certificate, String iatMode) {
+ this.context = context.getApplicationContext();
+ this.remoteIP = remoteIP;
+ this.remotePort = remotePort;
+ this.certificate = certificate;
+ this.iatMode = iatMode;
+ }
+
+ @WorkerThread
+ public void initSync() {
+ try {
+ fileDispatcher = installDispatcher();
+
+ // start dispatcher
+ dispatcherThread = new Thread(() -> {
+ try {
+ StringBuilder dispatcherLog = new StringBuilder();
+ String dispatcherCommand = fileDispatcher.getCanonicalPath() +
+ " -transparent" +
+ " -client" +
+ " -state " + context.getFilesDir().getCanonicalPath() + "/state" +
+ " -target " + remoteIP + ":" + remotePort +
+ " -transports obfs4" +
+ " -options \"" + String.format("{\\\"cert\\\": \\\"%s\\\", \\\"iatMode\\\": \\\"%s\\\"}\"", certificate, iatMode) +
+ " -logLevel DEBUG -enableLogging";
+
+ runBlockingCmd(new String[]{dispatcherCommand}, dispatcherLog);
+ } catch (IOException e) {
+ e.printStackTrace();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ });
+ dispatcherThread.start();
+
+ // get pid of dispatcher
+ StringBuilder log = new StringBuilder();
+ String pidCommand = "ps | grep " + fileDispatcher.getCanonicalPath();
+ runBlockingCmd(new String[]{pidCommand}, log);
+ String output = log.toString();
+ StringTokenizer st = new StringTokenizer(output, " ");
+ st.nextToken(); // proc owner
+ dipatcherPid = Integer.parseInt(st.nextToken().trim());
+
+ // get open port of dispatcher
+ String getPortCommand = "cat " + context.getFilesDir().getCanonicalPath() + "/state/dispatcher.log | grep \"obfs4 - registered listener\"";
+ long timeout = System.currentTimeMillis() + 5000;
+ int i = 1;
+ while (this.port.length() == 0 && System.currentTimeMillis() < timeout) {
+ log = new StringBuilder();
+ Log.d(TAG, i + ". try to get port");
+ runBlockingCmd(new String[]{getPortCommand}, log);
+ output = log.toString();
+ if (output.length() > 0) {
+ Log.d(TAG, "dispatcher log: \n =================\n" + output);
+ }
+
+ String dispatcherLog[] = output.split(" ");
+ if (dispatcherLog.length > 0) {
+ String localAddressAndPort = dispatcherLog[dispatcherLog.length - 1];
+ if (localAddressAndPort.contains(":")) {
+ this.port = localAddressAndPort.split(":")[1].replace(System.getProperty("line.separator"), "");
+ Log.d(TAG, "local port is: " + this.port);
+ }
+ }
+ i += 1;
+ }
+
+ } catch(Exception e){
+ if (dispatcherThread.isAlive()) {
+ Log.e(TAG, e.getMessage() + ". Shutting down Dispatcher thread.");
+ stop();
+ }
+ }
+ }
+
+ public String getPort() {
+ return port;
+ }
+
+ public void stop() {
+ Log.d(TAG, "Shutting down Dispatcher thread.");
+ if (dispatcherThread != null && dispatcherThread.isAlive()) {
+ try {
+ killProcess(dipatcherPid);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ dispatcherThread.interrupt();
+ }
+ }
+
+ private void killProcess(int pid) throws Exception {
+ String killPid = "kill -9 " + pid;
+ runCmd(new String[]{killPid}, null, false);
+ }
+
+ public boolean isRunning() {
+ return dispatcherThread != null && dispatcherThread.isAlive();
+ }
+
+ private File installDispatcher(){
+ File fileDispatcher = null;
+ BinaryInstaller bi = new BinaryInstaller(context,context.getFilesDir());
+
+ String arch = System.getProperty("os.arch");
+ if (arch.contains("arm"))
+ arch = "arm";
+ else
+ arch = "x86";
+
+ try {
+ fileDispatcher = bi.installResource(arch, ASSET_KEY, false);
+ } catch (Exception ioe) {
+ Log.d(TAG,"Couldn't install dispatcher: " + ioe);
+ }
+
+ return fileDispatcher;
+ }
+
+ @WorkerThread
+ private void runBlockingCmd(String[] cmds, StringBuilder log) throws Exception {
+ runCmd(cmds, log, true);
+ }
+
+ @WorkerThread
+ private int runCmd(String[] cmds, StringBuilder log,
+ boolean waitFor) throws Exception {
+
+ int exitCode = -1;
+ Process proc = Runtime.getRuntime().exec("sh");
+ OutputStreamWriter out = new OutputStreamWriter(proc.getOutputStream());
+
+ try {
+ for (String cmd : cmds) {
+ Log.d(TAG, "executing CMD: " + cmd);
+ out.write(cmd);
+ out.write("\n");
+ }
+
+ out.flush();
+ out.write("exit\n");
+ out.flush();
+ } catch (IOException e) {
+ e.printStackTrace();
+ } finally {
+ out.close();
+ }
+
+ if (waitFor) {
+ // Consume the "stdout"
+ InputStreamReader reader = new InputStreamReader(proc.getInputStream());
+ readToLogString(reader, log);
+
+ // Consume the "stderr"
+ reader = new InputStreamReader(proc.getErrorStream());
+ readToLogString(reader, log);
+
+ try {
+ exitCode = proc.waitFor();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ return exitCode;
+ }
+
+ private void readToLogString(InputStreamReader reader, StringBuilder log) throws IOException {
+ final char buf[] = new char[10];
+ int read = 0;
+ try {
+ while ((read = reader.read(buf)) != -1) {
+ if (log != null)
+ log.append(buf, 0, read);
+ }
+ } catch (IOException e) {
+ reader.close();
+ throw new IOException(e);
+ }
+ reader.close();
+ }
+}