summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2023-04-06 01:08:05 +0200
committercyBerta <cyberta@riseup.net>2023-04-13 16:47:13 +0200
commit939901a89abb169648423473056260335d3af639 (patch)
tree7e49ac928013cdf5e7979c3a9384fb06f0b0f192 /app
parentf6017ab12d0c472ab4f22e81d9a768ad2510b134 (diff)
first pass on obfs4-hop pt integration
Diffstat (limited to 'app')
-rw-r--r--app/src/main/java/de/blinkt/openvpn/VpnProfile.java17
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java20
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java20
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java56
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java6
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4HopConnection.java48
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java9
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java5
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java120
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java242
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java49
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java72
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java21
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java25
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java18
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientInterface.java9
-rw-r--r--app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java5
-rw-r--r--app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java104
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/base/models/GatewayJsonTest.java2
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/base/models/TransportTest.java68
-rw-r--r--app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java41
-rw-r--r--app/src/test/resources/ptdemo_obfs4hop_kcp_gateways.json178
-rw-r--r--app/src/test/resources/ptdemo_obfs4hop_misconfigured_udp_gateway.json178
-rw-r--r--app/src/test/resources/ptdemo_obfs4hop_tcp_gateways.json178
24 files changed, 1286 insertions, 205 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 3428e0c4..9baac195 100644
--- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -5,8 +5,6 @@
package de.blinkt.openvpn;
-import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
-import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE;
import static se.leap.bitmaskclient.base.utils.ConfigHelper.stringEqual;
@@ -191,7 +189,7 @@ public class VpnProfile implements Serializable, Cloneable {
private int mProfileVersion;
public boolean mBlockUnusedAddressFamilies = true;
public String mGatewayIp;
- private final boolean mUseObfs4;
+ private final int mTransportType;
public VpnProfile(String name, Connection.TransportType transportType) {
mUuid = UUID.randomUUID();
@@ -200,7 +198,7 @@ public class VpnProfile implements Serializable, Cloneable {
mConnections = new Connection[1];
mLastUsed = System.currentTimeMillis();
- mUseObfs4 = transportType == OBFS4;
+ mTransportType = transportType.toInt();
}
public static String openVpnEscape(String unescaped) {
@@ -266,7 +264,7 @@ public class VpnProfile implements Serializable, Cloneable {
if (obj instanceof VpnProfile) {
VpnProfile vp = (VpnProfile) obj;
return stringEqual(vp.mGatewayIp, mGatewayIp) &&
- vp.mUseObfs4 == mUseObfs4;
+ vp.mTransportType == mTransportType;
}
return false;
}
@@ -297,15 +295,12 @@ public class VpnProfile implements Serializable, Cloneable {
}
public boolean usePluggableTransports() {
- return mUseObfs4;
+ Connection.TransportType type = Connection.TransportType.fromInt(mTransportType);
+ return type != null && type.isPluggableTransport();
}
public Connection.TransportType getTransportType() {
- if (mUseObfs4) {
- return OBFS4;
- } else {
- return OPENVPN;
- }
+ return Connection.TransportType.fromInt(mTransportType);
}
public String getName() {
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 6063ea53..e8d333e3 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/ConfigParser.java
@@ -27,6 +27,7 @@ import java.util.Vector;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.connection.Connection;
import de.blinkt.openvpn.core.connection.Obfs4Connection;
+import de.blinkt.openvpn.core.connection.Obfs4HopConnection;
import de.blinkt.openvpn.core.connection.OpenvpnConnection;
import se.leap.bitmaskclient.pluggableTransports.Obfs4Options;
@@ -807,8 +808,23 @@ public class ConfigParser {
e.printStackTrace();
return null;
}
- else
- conn = transportType.getMetaType() == PT ? new Obfs4Connection(obfs4Options) : new OpenvpnConnection();
+ else {
+ switch (transportType) {
+ case OBFS4:
+ conn = new Obfs4Connection(obfs4Options);
+ break;
+ case OBFS4_HOP:
+ conn = new Obfs4HopConnection(obfs4Options);
+ break;
+ case OPENVPN:
+ conn = new OpenvpnConnection();
+ break;
+ default:
+ throw new ConfigParseError("Unexpected transport type: " + transportType);
+
+ }
+
+ }
Vector<String> port = getOption("port", 1, 1);
if (port != null) {
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 0ae7639e..6adffda1 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -12,7 +12,6 @@ import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PROFILE;
import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn;
import android.Manifest.permission;
-import android.annotation.TargetApi;
import android.app.Notification;
import android.content.Intent;
import android.content.IntentFilter;
@@ -41,7 +40,6 @@ import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Collection;
-import java.util.Locale;
import java.util.Vector;
import de.blinkt.openvpn.VpnProfile;
@@ -53,7 +51,8 @@ import se.leap.bitmaskclient.R;
import se.leap.bitmaskclient.eip.EipStatus;
import se.leap.bitmaskclient.eip.VpnNotificationManager;
import se.leap.bitmaskclient.firewall.FirewallManager;
-import se.leap.bitmaskclient.pluggableTransports.ObfsVpnClient;
+import se.leap.bitmaskclient.pluggableTransports.PtClientBuilder;
+import se.leap.bitmaskclient.pluggableTransports.PtClientInterface;
import se.leap.bitmaskclient.pluggableTransports.ShapeshifterClient;
@@ -91,7 +90,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
private Runnable mOpenVPNThread;
private VpnNotificationManager notificationManager;
private ShapeshifterClient shapeshifter;
- private ObfsVpnClient obfsVpnClient;
+ private PtClientInterface obfsVpnClient;
private FirewallManager firewallManager;
private final IBinder mBinder = new IOpenVPNServiceInternal.Stub() {
@@ -415,18 +414,19 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
stopOldOpenVPNProcess();
// An old running VPN should now be exited
mStarting = false;
-
- if (mProfile.usePluggableTransports() && connection instanceof Obfs4Connection) {
- Obfs4Connection obfs4Connection = (Obfs4Connection) connection;
+ Connection.TransportType transportType = connection.getTransportType();
+ if (mProfile.usePluggableTransports() && transportType.isPluggableTransport()) {
if (useObfsVpn()) {
if (obfsVpnClient != null && obfsVpnClient.isStarted()) {
obfsVpnClient.stop();
}
- obfsVpnClient = new ObfsVpnClient(obfs4Connection.getDispatcherOptions());
+ obfsVpnClient = PtClientBuilder.getPtClient(connection);
int runningSocksPort = obfsVpnClient.start();
- connection.setProxyPort(String.valueOf(runningSocksPort));
+ if (connection.getTransportType() == Connection.TransportType.OBFS4) {
+ connection.setProxyPort(String.valueOf(runningSocksPort));
+ }
} else if (shapeshifter == null) {
- shapeshifter = new ShapeshifterClient(obfs4Connection.getDispatcherOptions());
+ shapeshifter = new ShapeshifterClient(((Obfs4Connection) connection).getObfs4Options());
shapeshifter.start();
}
}
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
index 137b3f16..6dc78a30 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java
@@ -5,6 +5,8 @@
package de.blinkt.openvpn.core.connection;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.*;
+
import android.text.TextUtils;
import com.google.gson.annotations.JsonAdapter;
@@ -42,7 +44,7 @@ public abstract class Connection implements Serializable, Cloneable {
TCP("tcp"),
KCP("kcp");
- String protocol;
+ final String protocol;
TransportProtocol(String transportProtocol) {
this.protocol = transportProtocol;
@@ -55,11 +57,12 @@ public abstract class Connection implements Serializable, Cloneable {
}
public enum TransportType {
OBFS4("obfs4"),
+ OBFS4_HOP("obfs4Hop"),
OPENVPN("openvpn"),
PT("metaTransport");
- String transport;
+ final String transport;
TransportType(String transportType) {
this.transport = transportType;
@@ -70,12 +73,57 @@ public abstract class Connection implements Serializable, Cloneable {
return transport;
}
+ public int toInt() {
+ switch (this) {
+ case PT:
+ return 0;
+ case OPENVPN:
+ return 1;
+ case OBFS4:
+ return 2;
+ case OBFS4_HOP:
+ return 3;
+ default:
+ return -1;
+ }
+ }
+
+ public static TransportType fromString(String value) {
+ switch (value) {
+ case "obfs4":
+ return OBFS4;
+ case "obfs4-hop":
+ return OBFS4_HOP;
+ case "metaTransport":
+ return PT;
+ case "openvpn":
+ return OPENVPN;
+ default:
+ throw new IllegalArgumentException(value + " is not a valid value for TransportType.");
+ }
+ }
+
+ public static TransportType fromInt(int value) {
+ switch (value) {
+ case 0:
+ return PT;
+ case 1:
+ return OPENVPN;
+ case 2:
+ return OBFS4;
+ case 3:
+ return OBFS4_HOP;
+ default:
+ return null;
+ }
+ }
+
public boolean isPluggableTransport() {
- return this == OBFS4 || this == PT;
+ return this == OBFS4 || this == OBFS4_HOP || this == PT;
}
public TransportType getMetaType() {
- if (this == OBFS4 || this == PT) {
+ if (this == OBFS4 || this == OBFS4_HOP || this == PT) {
return PT;
}
return OPENVPN;
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
index c77c23fd..73bfccdc 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4Connection.java
@@ -19,8 +19,8 @@ public class Obfs4Connection extends Connection {
public Obfs4Connection(Obfs4Options options) {
if (useObfsVpn()) {
- setServerName(options.remoteIP);
- setServerPort(options.remotePort);
+ setServerName(options.gatewayIP);
+ setServerPort(options.transport.getPorts()[0]);
setProxyName(ObfsVpnClient.SOCKS_IP);
setProxyPort(String.valueOf(ObfsVpnClient.SOCKS_PORT.get()));
setProxyType(ProxyType.SOCKS5);
@@ -53,7 +53,7 @@ public class Obfs4Connection extends Connection {
}
- public Obfs4Options getDispatcherOptions() {
+ public Obfs4Options getObfs4Options() {
return options;
}
diff --git a/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4HopConnection.java b/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4HopConnection.java
new file mode 100644
index 00000000..f983ae20
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/connection/Obfs4HopConnection.java
@@ -0,0 +1,48 @@
+package de.blinkt.openvpn.core.connection;
+
+import se.leap.bitmaskclient.pluggableTransports.HoppingObfsVpnClient;
+import se.leap.bitmaskclient.pluggableTransports.Obfs4Options;
+
+
+/**
+ * Created by cyberta on 08.03.19.
+ */
+
+public class Obfs4HopConnection extends Connection {
+
+ private static final String TAG = Obfs4HopConnection.class.getName();
+ private Obfs4Options options;
+
+ public Obfs4HopConnection(Obfs4Options options) {
+ setServerName(HoppingObfsVpnClient.IP);
+ setServerPort(String.valueOf(HoppingObfsVpnClient.PORT));
+ setProxyName("");
+ setProxyPort("");
+ setProxyType(ProxyType.NONE);
+
+
+ setUseUdp(true);
+ setProxyAuthUser(null);
+ setProxyAuthPassword(null);
+ setUseProxyAuth(false);
+ this.options = options;
+ }
+
+ @Override
+ public Connection clone() throws CloneNotSupportedException {
+ Obfs4HopConnection connection = (Obfs4HopConnection) super.clone();
+ connection.options = this.options;
+ return connection;
+ }
+
+ @Override
+ public TransportType getTransportType() {
+ return TransportType.OBFS4_HOP;
+ }
+
+
+ public Obfs4Options getObfs4Options() {
+ return options;
+ }
+
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
index ee5bd2a7..57467974 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Constants.java
@@ -179,10 +179,19 @@ public interface Constants {
String PORTS = "ports";
String PROTOCOLS = "protocols";
String UDP = "udp";
+ String TCP = "tcp";
+ String KCP = "kcp";
String CAPABILITIES = "capabilities";
String TRANSPORT = "transport";
String TYPE = "type";
String OPTIONS = "options";
+ String IAT_MODE = "iatMode";
+ String CERT = "cert";
+ String CERTS = "certs";
+ String ENDPOINTS = "endpoints";
+ String PORT_SEED = "port_seed";
+ String PORT_COUNT = "port_count";
+ String EXPERIMENTAL = "experimental";
String VERSION = "version";
String NAME = "name";
String TIMEZONE = "timezone";
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
index 62fb1fd2..57653263 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Provider.java
@@ -19,6 +19,7 @@ package se.leap.bitmaskclient.base.models;
import static de.blinkt.openvpn.core.connection.Connection.TransportProtocol.KCP;
import static de.blinkt.openvpn.core.connection.Connection.TransportProtocol.TCP;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP;
import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES;
import static se.leap.bitmaskclient.base.models.Constants.GATEWAYS;
import static se.leap.bitmaskclient.base.models.Constants.LOCATIONS;
@@ -184,13 +185,13 @@ public final class Provider implements Parcelable {
public boolean supportsPluggableTransports() {
if (useObfsVpn()) {
- return supportsTransports(new Pair[]{new Pair<>(OBFS4, TCP), new Pair<>(OBFS4, KCP)});
+ return supportsTransports(new Pair[]{new Pair<>(OBFS4, TCP), new Pair<>(OBFS4, KCP), new Pair<>(OBFS4_HOP, TCP), new Pair<>(OBFS4_HOP, KCP)});
}
return supportsTransports(new Pair[]{new Pair<>(OBFS4, TCP)});
}
public boolean supportsExperimentalPluggableTransports() {
- return supportsTransports(new Pair[]{new Pair<>(OBFS4, KCP)});
+ return supportsTransports(new Pair[]{new Pair<>(OBFS4, KCP), new Pair<>(OBFS4_HOP, TCP), new Pair<>(OBFS4_HOP, KCP)});
}
private boolean supportsTransports(Pair<TransportType, TransportProtocol>[] transportTypes) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java b/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java
index 90a033dd..7d9b61a7 100644
--- a/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java
+++ b/app/src/main/java/se/leap/bitmaskclient/base/models/Transport.java
@@ -1,21 +1,57 @@
package se.leap.bitmaskclient.base.models;
+import androidx.annotation.Nullable;
+
+import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
+import com.google.gson.annotations.SerializedName;
import org.json.JSONObject;
-public class Transport {
+import java.io.Serializable;
+
+import de.blinkt.openvpn.core.connection.Connection;
+
+public class Transport implements Serializable {
private String type;
private String[] protocols;
+ @Nullable
private String[] ports;
+ @Nullable
private Options options;
public Transport(String type, String[] protocols, String[] ports, String cert) {
+ this(type, protocols, ports, new Options(cert, "0"));
+ }
+
+ public Transport(String type, String[] protocols, String[] ports, Options options) {
this.type = type;
this.protocols = protocols;
this.ports = ports;
- this.options = new Options(cert);
+ this.options = options;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public Connection.TransportType getTransportType() {
+ return Connection.TransportType.fromString(type);
+ }
+
+ public String[] getProtocols() {
+ return protocols;
+ }
+
+ @Nullable
+ public String[] getPorts() {
+ return ports;
+ }
+
+ @Nullable
+ public Options getOptions() {
+ return options;
}
@Override
@@ -25,16 +61,65 @@ public class Transport {
public static Transport fromJson(JSONObject json) {
GsonBuilder builder = new GsonBuilder();
- return builder.create().fromJson(json.toString(), Transport.class);
+ return builder.
+ setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).
+ create().
+ fromJson(json.toString(), Transport.class);
}
- public static class Options {
+ public static class Options implements Serializable {
+ @Nullable
private String cert;
+ @SerializedName("iatMode")
private String iatMode;
- public Options(String cert) {
+ @Nullable
+ private Endpoint[] endpoints;
+
+ private boolean experimental;
+
+ private int portSeed;
+
+ private int portCount;
+
+
+ public Options(String cert, String iatMode) {
this.cert = cert;
- this.iatMode = "0";
+ this.iatMode = iatMode;
+ }
+
+ public Options(String iatMode, Endpoint[] endpoints, int portSeed, int portCount, boolean experimental) {
+ this.iatMode = iatMode;
+ this.endpoints = endpoints;
+ this.portSeed = portSeed;
+ this.portCount = portCount;
+ this.experimental = experimental;
+ }
+
+ @Nullable
+ public String getCert() {
+ return cert;
+ }
+
+ public String getIatMode() {
+ return iatMode;
+ }
+
+ @Nullable
+ public Endpoint[] getEndpoints() {
+ return endpoints;
+ }
+
+ public boolean isExperimental() {
+ return experimental;
+ }
+
+ public int getPortSeed() {
+ return portSeed;
+ }
+
+ public int getPortCount() {
+ return portCount;
}
@Override
@@ -44,6 +129,29 @@ public class Transport {
}
+ public static class Endpoint implements Serializable {
+ private String ip;
+ private String cert;
+
+ public Endpoint(String ip, String cert) {
+ this.ip = ip;
+ this.cert = cert;
+ }
+
+ @Override
+ public String toString() {
+ return new Gson().toJson(this);
+ }
+
+ public String getIp() {
+ return ip;
+ }
+
+ public String getCert() {
+ return cert;
+ }
+ }
+
}
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 141f6274..7229f7ff 100644
--- a/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
+++ b/app/src/main/java/se/leap/bitmaskclient/eip/VpnConfigGenerator.java
@@ -17,19 +17,19 @@
package se.leap.bitmaskclient.eip;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
-import static de.blinkt.openvpn.core.connection.Connection.TransportType.PT;
import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES;
import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS;
import static se.leap.bitmaskclient.base.models.Constants.IP_ADDRESS6;
-import static se.leap.bitmaskclient.base.models.Constants.OPTIONS;
+import static se.leap.bitmaskclient.base.models.Constants.KCP;
import static se.leap.bitmaskclient.base.models.Constants.PORTS;
import static se.leap.bitmaskclient.base.models.Constants.PROTOCOLS;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_PRIVATE_KEY;
import static se.leap.bitmaskclient.base.models.Constants.PROVIDER_VPN_CERTIFICATE;
import static se.leap.bitmaskclient.base.models.Constants.REMOTE;
+import static se.leap.bitmaskclient.base.models.Constants.TCP;
import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT;
-import static se.leap.bitmaskclient.base.models.Constants.TYPE;
import static se.leap.bitmaskclient.base.models.Constants.UDP;
import static se.leap.bitmaskclient.base.utils.ConfigHelper.ObfsVpnHelper.useObfsVpn;
import static se.leap.bitmaskclient.pluggableTransports.ShapeshifterClient.DISPATCHER_IP;
@@ -54,14 +54,16 @@ import de.blinkt.openvpn.core.VpnStatus;
import de.blinkt.openvpn.core.connection.Connection;
import de.blinkt.openvpn.core.connection.Connection.TransportType;
import se.leap.bitmaskclient.base.models.Provider;
+import se.leap.bitmaskclient.base.models.Transport;
import se.leap.bitmaskclient.base.utils.ConfigHelper;
+import se.leap.bitmaskclient.pluggableTransports.HoppingObfsVpnClient;
import se.leap.bitmaskclient.pluggableTransports.Obfs4Options;
public class VpnConfigGenerator {
private final JSONObject generalConfiguration;
private final JSONObject gateway;
private final JSONObject secrets;
- private JSONObject obfs4Transport;
+ HashMap<TransportType, Transport> transports = new HashMap<>();
private final int apiVersion;
private final boolean preferUDP;
private final boolean experimentalTransports;
@@ -113,19 +115,14 @@ public class VpnConfigGenerator {
public void checkCapabilities() throws ConfigParser.ConfigParseError {
try {
-
if (apiVersion >= 3) {
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.toString())) {
- obfs4Transport = transport;
- break;
- }
+ Transport transport = Transport.fromJson(supportedTransports.getJSONObject(i));
+ transports.put(transport.getTransportType(), transport);
}
}
-
- } catch (JSONException e) {
+ } catch (Exception e) {
throw new ConfigParser.ConfigParseError("Api version ("+ apiVersion +") did not match required JSON fields");
}
}
@@ -141,11 +138,15 @@ public class VpnConfigGenerator {
e.printStackTrace();
}
}
- if (supportsObfs4()) {
- try {
- profiles.put(OBFS4, createProfile(OBFS4));
- } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) {
- e.printStackTrace();
+ if (apiVersion >= 3) {
+ for (TransportType transportType : transports.keySet()) {
+ if (transportType.isPluggableTransport()) {
+ try {
+ profiles.put(transportType, createProfile(transportType));
+ } catch (ConfigParser.ConfigParseError | NumberFormatException | JSONException | IOException e) {
+ e.printStackTrace();
+ }
+ }
}
}
if (profiles.isEmpty()) {
@@ -155,10 +156,9 @@ public class VpnConfigGenerator {
}
private boolean supportsOpenvpn() {
- return !useObfuscationPinning && !gatewayConfiguration(OPENVPN).isEmpty();
- }
- private boolean supportsObfs4(){
- return obfs4Transport != null || useObfuscationPinning;
+ return !useObfuscationPinning &&
+ ((apiVersion >= 3 && transports.containsKey(OPENVPN)) ||
+ (apiVersion < 3 && !gatewayConfiguration(OPENVPN).isEmpty()));
}
private String getConfigurationString(TransportType transportType) {
@@ -176,11 +176,8 @@ public class VpnConfigGenerator {
String configuration = getConfigurationString(transportType);
ConfigParser icsOpenvpnConfigParser = new ConfigParser();
icsOpenvpnConfigParser.parseConfig(new StringReader(configuration));
- if (transportType == OBFS4) {
- JSONArray protocols = obfs4Transport.getJSONArray(PROTOCOLS);
- // FIXME: currently only one protocol per obfs4 bridge is supported in this client
- String protocol = protocols.optString(0);
- icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(obfs4Transport, protocol.equalsIgnoreCase("kcp")));
+ if (transportType == OBFS4 || transportType == OBFS4_HOP) {
+ icsOpenvpnConfigParser.setObfs4Options(getObfs4Options(transportType));
}
VpnProfile profile = icsOpenvpnConfigParser.convertProfile(transportType);
@@ -192,21 +189,19 @@ public class VpnConfigGenerator {
return profile;
}
- private Obfs4Options getObfs4Options(JSONObject transportJson, boolean useUdp) throws JSONException {
- JSONObject transportOptions = transportJson.getJSONObject(OPTIONS);
- String iatMode = transportOptions.getString("iatMode");
- String cert = transportOptions.getString("cert");
- String port = transportJson.getJSONArray(PORTS).getString(0);
+ private Obfs4Options getObfs4Options(TransportType transportType) throws JSONException {
String ip = gateway.getString(IP_ADDRESS);
- boolean udp = useUdp;
-
+ Transport transport;
if (useObfuscationPinning) {
- cert = obfuscationPinningCert;
- port = obfuscationPinningPort;
+ transport = new Transport(OBFS4.toString(),
+ new String[]{obfuscationPinningKCP ? KCP : TCP},
+ new String[]{obfuscationPinningPort},
+ obfuscationPinningCert);
ip = obfuscationPinningIP;
- udp = obfuscationPinningKCP;
+ } else {
+ transport = transports.get(transportType);
}
- return new Obfs4Options(ip, port, cert, iatMode, udp);
+ return new Obfs4Options(ip, transport);
}
private String generalConfiguration() {
@@ -254,8 +249,7 @@ public class VpnConfigGenerator {
new String[]{ipAddress} :
new String[]{ipAddress6, ipAddress};
- JSONArray transports = capabilities.getJSONArray(TRANSPORT);
- gatewayConfigMinApiv3(transportType, stringBuilder, ipAddresses, transports);
+ gatewayConfigMinApiv3(transportType, stringBuilder, ipAddresses);
break;
}
} catch (JSONException e) {
@@ -271,11 +265,11 @@ public class VpnConfigGenerator {
return remotes;
}
- private void gatewayConfigMinApiv3(TransportType transportType, StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException {
- if (transportType.getMetaType() == PT) {
- ptGatewayConfigMinApiv3(stringBuilder, ipAddresses, transportType, transports);
+ private void gatewayConfigMinApiv3(TransportType transportType, StringBuilder stringBuilder, String[] ipAddresses) throws JSONException {
+ if (transportType.isPluggableTransport()) {
+ ptGatewayConfigMinApiv3(stringBuilder, ipAddresses, transports.get(transportType));
} else {
- ovpnGatewayConfigMinApi3(stringBuilder, ipAddresses, transports);
+ ovpnGatewayConfigMinApi3(stringBuilder, ipAddresses, transports.get(OPENVPN));
}
}
@@ -294,19 +288,16 @@ public class VpnConfigGenerator {
}
}
- private void ovpnGatewayConfigMinApi3(StringBuilder stringBuilder, String[] ipAddresses, JSONArray transports) throws JSONException {
- String port;
- String protocol;
- JSONObject openvpnTransport = getTransport(transports, OPENVPN);
- JSONArray ports = openvpnTransport.getJSONArray(PORTS);
- JSONArray protocols = openvpnTransport.getJSONArray(PROTOCOLS);
+ private void ovpnGatewayConfigMinApi3(StringBuilder stringBuilder, String[] ipAddresses, Transport transport) {
+ if (transport.getProtocols() == null || transport.getPorts() == null) {
+ VpnStatus.logError("Misconfigured provider: missing details for transport openvpn on gateway " + ipAddresses[0]);
+ return;
+ }
if (preferUDP) {
StringBuilder udpRemotes = new StringBuilder();
StringBuilder tcpRemotes = new StringBuilder();
- for (int i = 0; i < protocols.length(); i++) {
- protocol = protocols.optString(i);
- for (int j = 0; j < ports.length(); j++) {
- port = ports.optString(j);
+ for (String protocol : transport.getProtocols()) {
+ for (String port : transport.getPorts()) {
for (String ipAddress : ipAddresses) {
String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine;
if (UDP.equals(protocol)) {
@@ -320,10 +311,8 @@ public class VpnConfigGenerator {
stringBuilder.append(udpRemotes.toString());
stringBuilder.append(tcpRemotes.toString());
} else {
- for (int j = 0; j < ports.length(); j++) {
- port = ports.getString(j);
- for (int k = 0; k < protocols.length(); k++) {
- protocol = protocols.optString(k);
+ for (String protocol : transport.getProtocols()) {
+ for (String port : transport.getPorts()) {
for (String ipAddress : ipAddresses) {
String newRemote = REMOTE + " " + ipAddress + " " + port + " " + protocol + newLine;
stringBuilder.append(newRemote);
@@ -333,31 +322,18 @@ public class VpnConfigGenerator {
}
}
- private JSONObject getTransport(JSONArray transports, TransportType transportType) throws JSONException {
- JSONObject selectedTransport = new JSONObject();
- for (int i = 0; i < transports.length(); i++) {
- JSONObject transport = transports.getJSONObject(i);
- if (transport.getString(TYPE).equals(transportType.toString())) {
- selectedTransport = transport;
- break;
- }
- }
- return selectedTransport;
- }
-
private boolean isAllowedProtocol(TransportType transportType, String protocol) {
switch (transportType) {
case OPENVPN:
- return "tcp".equals(protocol) || "udp".equals(protocol);
+ return TCP.equals(protocol) || UDP.equals(protocol);
+ case OBFS4_HOP:
case OBFS4:
- return "tcp".equals(protocol) || "kcp".equals(protocol);
+ return TCP.equals(protocol) || KCP.equals(protocol);
}
return false;
}
- private void ptGatewayConfigMinApiv3(StringBuilder stringBuilder, String[] ipAddresses, TransportType transportType, JSONArray transports) throws JSONException {
- JSONObject ptTransport = getTransport(transports, transportType);
- JSONArray ptProtocols = ptTransport.getJSONArray(PROTOCOLS);
+ private void ptGatewayConfigMinApiv3(StringBuilder stringBuilder, String[] ipAddresses, Transport transport) {
//for now only use ipv4 gateway the syntax route remote_host 255.255.255.255 net_gateway is not yet working
// https://community.openvpn.net/openvpn/ticket/1161
@@ -381,63 +357,97 @@ public class VpnConfigGenerator {
}
if (ipAddress == null) {
- VpnStatus.logError("No matching IPv4 address found to configure obfs4.");
+ VpnStatus.logError("Misconfigured provider: No matching IPv4 address found to configure obfs4.");
return;
}
- if (!useObfuscationPinning) {
- // check if at least one openvpn protocol is TCP, openvpn in UDP is currently not supported for obfs4,
- // however on the wire UDP might be used
- boolean hasOpenvpnTcp = false;
- JSONObject openvpnTransport = getTransport(transports, OPENVPN);
- JSONArray gatewayProtocols = openvpnTransport.getJSONArray(PROTOCOLS);
- for (int i = 0; i < gatewayProtocols.length(); i++) {
- String protocol = gatewayProtocols.getString(i);
- if (protocol.contains("tcp")) {
- hasOpenvpnTcp = true;
- break;
- }
- }
- if (!hasOpenvpnTcp) {
- VpnStatus.logError("obfs4 currently only allows openvpn in TCP mode! Skipping obfs4 config for ip " + ipAddress);
- return;
- }
- }
-
- boolean hasAllowedPTProtocol = false;
- for (int i = 0; i < ptProtocols.length(); i++) {
- String protocol = ptProtocols.getString(i);
- if (isAllowedProtocol(transportType, protocol)) {
- hasAllowedPTProtocol = true;
- break;
- }
+ if (!openvpnModeSupportsPt(transport, ipAddress) || !hasPTAllowedProtocol(transport, ipAddress)) {
+ return;
}
- if (!hasAllowedPTProtocol) {
- VpnStatus.logError("Misconfigured provider: wrong protocol defined in " + transportType.toString()+ " transport JSON.");
+ TransportType transportType = transport.getTransportType();
+ if (transportType == OBFS4 && transport.getPorts() == null) {
+ VpnStatus.logError("Misconfigured provider: no ports defined in " + transport.getType() + " transport JSON for gateway " + ipAddress);
return;
}
- JSONArray ports = ptTransport.getJSONArray(PORTS);
- if (ports.isNull(0)){
- VpnStatus.logError("Misconfigured provider: no ports defined in " + transportType.toString()+ " transport JSON.");
+ if (transportType == OBFS4_HOP &&
+ (transport.getOptions() == null || transport.getOptions().getEndpoints() == null || transport.getOptions().getPortCount() == 0)) {
+ VpnStatus.logError("Misconfigured provider: missing properties for transport " + transport.getType() + " on gateway " + ipAddress);
return;
}
- String route = "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine;
- String remote;
+ stringBuilder.append(getRouteString(ipAddress, transport));
+ stringBuilder.append(getRemoteString(ipAddress, transport));
+ }
+
+ public String getRemoteString(String ipAddress, Transport transport) {
if (useObfsVpn()) {
if (useObfuscationPinning) {
- remote = REMOTE + " " + obfuscationPinningIP + " " + obfuscationPinningPort + " tcp" + newLine;
- route = "route " + obfuscationPinningIP + " 255.255.255.255 net_gateway" + newLine;
- } else {
- remote = REMOTE + " " + ipAddress + " " + ports.getString(0) + " tcp" + newLine;
+ return REMOTE + " " + obfuscationPinningIP + " " + obfuscationPinningPort + " tcp" + newLine;
}
- } else {
- remote = REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " tcp" + newLine;
+ switch (transport.getTransportType()) {
+ case OBFS4:
+ return REMOTE + " " + ipAddress + " " + transport.getPorts()[0] + " tcp" + newLine;
+ case OBFS4_HOP:
+ return REMOTE + " " + HoppingObfsVpnClient.IP + " " + HoppingObfsVpnClient.PORT + " udp" + newLine;
+ default:
+ VpnStatus.logError("Unexpected pluggable transport type " + transport.getType() + " for gateway " + ipAddress);
+ return "";
+ }
+ }
+ return REMOTE + " " + DISPATCHER_IP + " " + DISPATCHER_PORT + " tcp" + newLine;
+ }
+
+ public String getRouteString(String ipAddress, Transport transport) {
+ if (useObfuscationPinning) {
+ return "route " + obfuscationPinningIP + " 255.255.255.255 net_gateway" + newLine;
+ }
+ if (transport.getTransportType() == OBFS4) {
+ return "route " + ipAddress + " 255.255.255.255 net_gateway" + newLine;
+ }
+ return newLine;
+ }
+
+ // While openvpn in TCP mode is required for obfs4, openvpn in UDP mode is required for obfs4-hop
+ private boolean openvpnModeSupportsPt(Transport transport, String ipAddress) {
+ if (useObfuscationPinning) {
+ // we don't know if the manually pinned bridge points to a openvpn gateway with the right
+ // configuration, so we assume yes
+ return true;
}
- stringBuilder.append(route);
- stringBuilder.append(remote);
+ Transport openvpnTransport = transports.get(OPENVPN);
+ if (openvpnTransport == null) {
+ return false;
+ }
+
+ String[] protocols = openvpnTransport.getProtocols();
+ if (protocols == null) {
+ VpnStatus.logError("Misconfigured provider: Protocol array is missing for openvpn gateway " + ipAddress);
+ return false;
+ }
+
+ String requiredProtocol = transport.getTransportType() == OBFS4_HOP ? UDP : TCP;
+ for (String protocol : protocols) {
+ if (protocol.equals(requiredProtocol)) {
+ return true;
+ }
+ }
+
+ VpnStatus.logError("Misconfigured provider: " + transport.getTransportType().toString() + " currently only allows openvpn in " + requiredProtocol + " mode! Skipping config for ip " + ipAddress);
+ return false;
+ }
+
+ private boolean hasPTAllowedProtocol(Transport transport, String ipAddress) {
+ String[] ptProtocols = transport.getProtocols();
+ for (String protocol : ptProtocols) {
+ if (isAllowedProtocol(transport.getTransportType(), protocol)) {
+ return true;
+ }
+ }
+
+ VpnStatus.logError("Misconfigured provider: wrong protocol defined in " + transport.getType() + " transport JSON for gateway " + ipAddress);
+ return false;
}
private String secretsConfiguration() {
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java
new file mode 100644
index 00000000..e885166a
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingConfig.java
@@ -0,0 +1,49 @@
+package se.leap.bitmaskclient.pluggableTransports;
+
+import androidx.annotation.NonNull;
+
+import com.google.gson.FieldNamingPolicy;
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import se.leap.bitmaskclient.base.models.Transport;
+
+public class HoppingConfig {
+ final boolean kcp;
+ final String proxyAddr;
+ final String[] remotes;
+ final String[] certs;
+ final int portSeed;
+ final int portCount;
+ final int minHopSeconds;
+ final int hopJitter;
+
+ public HoppingConfig(boolean kcp,
+ String proxyAddr,
+ Transport transport,
+ int minHopSeconds,
+ int hopJitter) {
+ this.kcp = kcp;
+ this.proxyAddr = proxyAddr;
+ Transport.Endpoint[] endpoints = transport.getOptions().getEndpoints();
+ this.remotes = new String[endpoints.length];
+ this.certs = new String[endpoints.length];
+ for (int i = 0; i < remotes.length; i++) {
+ remotes[i] = endpoints[i].getIp();
+ certs[i] = endpoints[i].getCert();
+ }
+ this.portSeed = transport.getOptions().getPortSeed();
+ this.portCount = transport.getOptions().getPortCount();
+ this.minHopSeconds = minHopSeconds;
+ this.hopJitter = hopJitter;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ Gson gson = new GsonBuilder()
+ .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
+ .create();
+ return gson.toJson(this);
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java
new file mode 100644
index 00000000..1b19213f
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/HoppingObfsVpnClient.java
@@ -0,0 +1,72 @@
+package se.leap.bitmaskclient.pluggableTransports;
+
+import static de.blinkt.openvpn.core.connection.Connection.TransportProtocol.KCP;
+
+import client.Client;
+import client.HopClient;
+import de.blinkt.openvpn.core.VpnStatus;
+import se.leap.bitmaskclient.base.models.Constants;
+
+public class HoppingObfsVpnClient implements PtClientInterface {
+
+ public static final int PORT = 8080;
+ public static final String IP = "127.0.0.1";
+
+ public final HopClient client;
+
+ public HoppingObfsVpnClient(Obfs4Options options) throws IllegalStateException {
+
+ //FIXME: use a different strategy here
+ //Basically we would want to track if the more performant transport protocol (KCP?/TCP?) usage was successful
+ //if so, we stick to it, otherwise we flip the flag
+ boolean kcp = Constants.KCP.equals(options.transport.getProtocols()[0]);
+
+ if (options.transport.getOptions().getEndpoints() == null) {
+ throw new IllegalStateException("No Endpoints for hopping pt detected!");
+ }
+
+ HoppingConfig hoppingConfig = new HoppingConfig(kcp,IP+":"+PORT, options.transport, 10, 10);
+ try {
+ client = Client.newFFIHopClient(hoppingConfig.toString());
+ } catch (Exception e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public int start() {
+ try {
+ client.setEventLogger(this);
+ return client.start() ? PORT : 0;
+ } catch (Exception e) {
+ e.printStackTrace();
+ return 0;
+ }
+ }
+
+ @Override
+ public void stop() {
+ try {
+ client.stop();
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ client.setEventLogger(null);
+ }
+ }
+
+ @Override
+ public boolean isStarted() {
+ return client.isStarted();
+ }
+
+ @Override
+ public void error(String s) {
+ VpnStatus.logError("[hopping-obfs4] " + s);
+ }
+
+ @Override
+ public void log(String state, String message) {
+ VpnStatus.logDebug("[hopping-obfs4] " + state + ": " + message);
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java
index b96f88ca..0dd81eb8 100644
--- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/Obfs4Options.java
@@ -2,20 +2,15 @@ package se.leap.bitmaskclient.pluggableTransports;
import java.io.Serializable;
+import se.leap.bitmaskclient.base.models.Transport;
+
public class Obfs4Options implements Serializable {
- public String cert;
- public String iatMode;
- public String remoteIP;
- public String remotePort;
- // openvpn is still using tcp, obfs4 is wrapped in kcp, if udp == true
- public boolean udp;
+ public String gatewayIP;
+ public Transport transport;
- public Obfs4Options(String remoteIP, String remotePort, String cert, String iatMode, boolean udp) {
- this.cert = cert;
- this.iatMode = iatMode;
- this.remoteIP = remoteIP;
- this.remotePort = remotePort;
- this.udp = udp;
+ public Obfs4Options(String gatewayIP,
+ Transport transport) {
+ this.gatewayIP = gatewayIP;
+ this.transport = transport;
}
-
}
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java
index f6c8837e..9d5ddcf9 100644
--- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ObfsVpnClient.java
@@ -1,5 +1,7 @@
package se.leap.bitmaskclient.pluggableTransports;
+import static se.leap.bitmaskclient.base.models.Constants.KCP;
+
import android.util.Log;
import java.util.Observable;
@@ -7,12 +9,12 @@ import java.util.Observer;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
-import client.Client_;
+import client.Client;
import de.blinkt.openvpn.core.ConnectionStatus;
import de.blinkt.openvpn.core.VpnStatus;
import se.leap.bitmaskclient.eip.EipStatus;
-public class ObfsVpnClient implements Observer, client.EventLogger {
+public class ObfsVpnClient implements Observer, PtClientInterface {
public static final AtomicInteger SOCKS_PORT = new AtomicInteger(4430);
public static final String SOCKS_IP = "127.0.0.1";
@@ -27,9 +29,17 @@ public class ObfsVpnClient implements Observer, client.EventLogger {
private final client.Client_ obfsVpnClient;
private final Object LOCK = new Object();
- public ObfsVpnClient(Obfs4Options options) {
- obfsVpnClient = new Client_(options.udp, SOCKS_IP+":"+SOCKS_PORT.get(), options.cert);
- obfsVpnClient.setEventLogger(this);
+ public ObfsVpnClient(Obfs4Options options) throws IllegalStateException{
+ //FIXME: use a different strategy here
+ //Basically we would want to track if the more performant transport protocol (KCP?/TCP?) usage was successful
+ //if so, we stick to it, otherwise we flip the flag
+ boolean kcp = KCP.equals(options.transport.getProtocols()[0]);
+
+ if (options.transport.getOptions().getCert() == null) {
+ throw new IllegalStateException("No cert found to establish a obfs4 connection");
+ }
+
+ obfsVpnClient = Client.newClient(kcp, SOCKS_IP+":"+SOCKS_PORT.get(), options.transport.getOptions().getCert());
}
/**
@@ -38,6 +48,7 @@ public class ObfsVpnClient implements Observer, client.EventLogger {
*/
public int start() {
synchronized (LOCK) {
+ obfsVpnClient.setEventLogger(this);
Log.d(TAG, "aquired LOCK");
new Thread(this::startSync).start();
waitUntilStarted();
@@ -46,6 +57,7 @@ public class ObfsVpnClient implements Observer, client.EventLogger {
return SOCKS_PORT.get();
}
+ // We're waiting here until the obfsvpn client has found a unbound port and started
private void waitUntilStarted() {
int count = -1;
try {
@@ -88,6 +100,8 @@ public class ObfsVpnClient implements Observer, client.EventLogger {
} catch (Exception e) {
e.printStackTrace();
VpnStatus.logError("[obfsvpn] " + e.getLocalizedMessage());
+ } finally {
+ obfsVpnClient.setEventLogger(null);
}
pendingNetworkErrorHandling.set(false);
Log.d(TAG, "stopping obfsVpnClient releasing LOCK ...");
@@ -98,6 +112,7 @@ public class ObfsVpnClient implements Observer, client.EventLogger {
return obfsVpnClient.isStarted();
}
+ // TODO: register observer!
@Override
public void update(Observable observable, Object arg) {
if (observable instanceof EipStatus) {
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java
new file mode 100644
index 00000000..945e3d7a
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientBuilder.java
@@ -0,0 +1,18 @@
+package se.leap.bitmaskclient.pluggableTransports;
+
+import de.blinkt.openvpn.core.connection.Connection;
+import de.blinkt.openvpn.core.connection.Obfs4Connection;
+import de.blinkt.openvpn.core.connection.Obfs4HopConnection;
+
+public class PtClientBuilder {
+ public static PtClientInterface getPtClient(Connection connection) throws IllegalStateException {
+ switch (connection.getTransportType()) {
+ case OBFS4:
+ return new ObfsVpnClient(((Obfs4Connection) connection).getObfs4Options());
+ case OBFS4_HOP:
+ return new HoppingObfsVpnClient(((Obfs4HopConnection) connection).getObfs4Options());
+ default:
+ throw new IllegalStateException("Unexpected pluggable transport " + connection.getTransportType());
+ }
+ }
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientInterface.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientInterface.java
new file mode 100644
index 00000000..28d19a97
--- /dev/null
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/PtClientInterface.java
@@ -0,0 +1,9 @@
+package se.leap.bitmaskclient.pluggableTransports;
+
+import client.EventLogger;
+
+public interface PtClientInterface extends EventLogger {
+ int start();
+ void stop();
+ boolean isStarted();
+}
diff --git a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java
index f1eb0f1b..102dcf35 100644
--- a/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java
+++ b/app/src/main/java/se/leap/bitmaskclient/pluggableTransports/ShapeshifterClient.java
@@ -42,6 +42,7 @@ public class ShapeshifterClient implements Observer {
private int retry = 0;
private final Handler reconnectHandler;
+ @Deprecated
public class ShapeshifterLogger implements shapeshifter.Logger {
@Override
public void log(String s) {
@@ -71,8 +72,8 @@ public class ShapeshifterClient implements Observer {
private void setup(Obfs4Options options) {
shapeShifter.setSocksAddr(DISPATCHER_IP+":"+DISPATCHER_PORT);
- shapeShifter.setTarget(options.remoteIP+":"+options.remotePort);
- shapeShifter.setCert(options.cert);
+ shapeShifter.setTarget(options.gatewayIP +":"+options.transport.getPorts()[0]);
+ shapeShifter.setCert(options.transport.getOptions().getCert());
}
public void setOptions(Obfs4Options options) {
diff --git a/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java b/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java
index 5d3c0826..e5f9a9ca 100644
--- a/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java
+++ b/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java
@@ -3,11 +3,12 @@ package de.blinkt.openvpn;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertTrue;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
+import static se.leap.bitmaskclient.base.models.Constants.UDP;
import org.json.JSONException;
import org.json.JSONObject;
@@ -17,10 +18,12 @@ import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PrepareForTest;
import org.powermock.modules.junit4.PowerMockRunner;
+import java.util.Arrays;
import java.util.UUID;
import de.blinkt.openvpn.core.connection.Obfs4Connection;
import de.blinkt.openvpn.core.connection.OpenvpnConnection;
+import se.leap.bitmaskclient.base.models.Transport;
import se.leap.bitmaskclient.base.utils.ConfigHelper;
import se.leap.bitmaskclient.pluggableTransports.Obfs4Options;
@@ -28,10 +31,14 @@ import se.leap.bitmaskclient.pluggableTransports.Obfs4Options;
@PrepareForTest({UUID.class, ConfigHelper.ObfsVpnHelper.class})
public class VpnProfileTest {
- private static final String OPENVPNCONNECTION_PROFILE = "{\"mCipher\":\"\",\"mProfileVersion\":7,\"mLastUsed\":0,\"mCheckRemoteCN\":true,\"mVerb\":\"1\",\"mRemoteRandom\":false,\"mRoutenopull\":false,\"mConnectRetry\":\"2\",\"mAllowedAppsVpn\":[],\"mUserEditable\":true,\"mUseUdp\":true,\"mAllowedAppsVpnAreDisallowed\":true,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mUseCustomConfig\":false,\"mUseFloat\":false,\"mUseDefaultRoute\":true,\"mConnectRetryMaxTime\":\"300\",\"mNobind\":true,\"mVersion\":0,\"mConnectRetryMax\":\"-1\",\"mOverrideDNS\":false,\"mAuth\":\"\",\"mTunMtu\":0,\"mUseObfs4\":false,\"mPassword\":\"\",\"mTLSAuthDirection\":\"\",\"mKeyPassword\":\"\",\"mCustomConfigOptions\":\"\",\"mName\":\"mockProfile\",\"mExpectTLSCert\":false,\"mUsername\":\"\",\"mAllowLocalLAN\":false,\"mDataCiphers\":\"\",\"mSearchDomain\":\"blinkt.de\",\"mTemporaryProfile\":false,\"mUseTLSAuth\":false,\"mRemoteCN\":\"\",\"mCustomRoutesv6\":\"\",\"mPersistTun\":false,\"mX509AuthType\":3,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mServerName\":\"openvpn.example.com\",\"mMssFix\":0,\"mPushPeerInfo\":false,\"mAuthenticationType\":2,\"mBlockUnusedAddressFamilies\":true,\"mServerPort\":\"1194\",\"mUseDefaultRoutev6\":true,\"mConnections\":[{\"mCustomConfiguration\":\"\",\"mUseUdp\":false,\"mServerName\":\"openvpn.example.com\",\"mProxyType\":\"NONE\",\"mProxyPort\":\"8080\",\"mUseCustomConfig\":false,\"mConnectTimeout\":0,\"mProxyName\":\"proxy.example.com\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.OpenvpnConnection\",\"mServerPort\":\"1194\",\"mEnabled\":true}],\"mUseLzo\":false,\"mAllowAppVpnBypass\":false,\"mUsePull\":true,\"mUseRandomHostname\":false,\"mAuthRetry\":0}";
- private static final String OBFS4CONNECTION_PROFILE = "{\"mCipher\":\"\",\"mProfileVersion\":7,\"mLastUsed\":0,\"mCheckRemoteCN\":true,\"mVerb\":\"1\",\"mRemoteRandom\":false,\"mRoutenopull\":false,\"mConnectRetry\":\"2\",\"mAllowedAppsVpn\":[],\"mUserEditable\":true,\"mUseUdp\":true,\"mAllowedAppsVpnAreDisallowed\":true,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mUseCustomConfig\":false,\"mUseFloat\":false,\"mUseDefaultRoute\":true,\"mConnectRetryMaxTime\":\"300\",\"mNobind\":true,\"mVersion\":0,\"mConnectRetryMax\":\"-1\",\"mOverrideDNS\":false,\"mAuth\":\"\",\"mTunMtu\":0,\"mUseObfs4\":true,\"mPassword\":\"\",\"mTLSAuthDirection\":\"\",\"mKeyPassword\":\"\",\"mCustomConfigOptions\":\"\",\"mName\":\"mockProfile\",\"mExpectTLSCert\":false,\"mUsername\":\"\",\"mAllowLocalLAN\":false,\"mDataCiphers\":\"\",\"mSearchDomain\":\"blinkt.de\",\"mTemporaryProfile\":false,\"mUseTLSAuth\":false,\"mRemoteCN\":\"\",\"mCustomRoutesv6\":\"\",\"mPersistTun\":false,\"mX509AuthType\":3,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mServerName\":\"openvpn.example.com\",\"mMssFix\":0,\"mPushPeerInfo\":false,\"mAuthenticationType\":2,\"mBlockUnusedAddressFamilies\":true,\"mServerPort\":\"1194\",\"mUseDefaultRoutev6\":true,\"mConnections\":[{\"mCustomConfiguration\":\"\",\"mServerName\":\"127.0.0.1\",\"mProxyType\":\"NONE\",\"mConnectTimeout\":0,\"mServerPort\":\"4430\",\"mUseUdp\":false,\"mProxyPort\":\"\",\"mUseCustomConfig\":false,\"options\":{\"udp\":false,\"remoteIP\":\"192.168.0.1\",\"iatMode\":\"1\",\"remotePort\":\"1234\",\"cert\":\"CERT\"},\"mProxyName\":\"\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.Obfs4Connection\",\"mEnabled\":true}],\"mUseLzo\":false,\"mAllowAppVpnBypass\":false,\"mUsePull\":true,\"mUseRandomHostname\":false,\"mAuthRetry\":0}";
- private static final String OBFS4CONNECTION_PROFILE_OBFSVPN = "{\"mCipher\":\"\",\"mProfileVersion\":7,\"mLastUsed\":0,\"mCheckRemoteCN\":true,\"mVerb\":\"1\",\"mRemoteRandom\":false,\"mRoutenopull\":false,\"mConnectRetry\":\"2\",\"mAllowedAppsVpn\":[],\"mUserEditable\":true,\"mUseUdp\":true,\"mAllowedAppsVpnAreDisallowed\":true,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mUseCustomConfig\":false,\"mUseFloat\":false,\"mUseDefaultRoute\":true,\"mConnectRetryMaxTime\":\"300\",\"mNobind\":true,\"mVersion\":0,\"mConnectRetryMax\":\"-1\",\"mOverrideDNS\":false,\"mAuth\":\"\",\"mTunMtu\":0,\"mUseObfs4\":true,\"mPassword\":\"\",\"mTLSAuthDirection\":\"\",\"mKeyPassword\":\"\",\"mCustomConfigOptions\":\"\",\"mName\":\"mockProfile\",\"mExpectTLSCert\":false,\"mUsername\":\"\",\"mAllowLocalLAN\":false,\"mDataCiphers\":\"\",\"mSearchDomain\":\"blinkt.de\",\"mTemporaryProfile\":false,\"mUseTLSAuth\":false,\"mRemoteCN\":\"\",\"mCustomRoutesv6\":\"\",\"mPersistTun\":false,\"mX509AuthType\":3,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mServerName\":\"openvpn.example.com\",\"mMssFix\":0,\"mPushPeerInfo\":false,\"mAuthenticationType\":2,\"mBlockUnusedAddressFamilies\":true,\"mServerPort\":\"1194\",\"mUseDefaultRoutev6\":true,\"mConnections\":[{\"mCustomConfiguration\":\"\",\"mServerName\":\"192.168.0.1\",\"mProxyType\":\"SOCKS5\",\"mConnectTimeout\":0,\"mServerPort\":\"1234\",\"mUseUdp\":false,\"mProxyPort\":\"4430\",\"mUseCustomConfig\":false,\"options\":{\"udp\":false,\"remoteIP\":\"192.168.0.1\",\"iatMode\":\"1\",\"remotePort\":\"1234\",\"cert\":\"CERT\"},\"mProxyName\":\"127.0.0.1\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.Obfs4Connection\",\"mEnabled\":true}],\"mUseLzo\":false,\"mAllowAppVpnBypass\":false,\"mUsePull\":true,\"mUseRandomHostname\":false,\"mAuthRetry\":0}";
- private static final String OBFS4CONNECTION_PROFILE_OBFSVPN_KCP = "{\"mCipher\":\"\",\"mProfileVersion\":7,\"mLastUsed\":0,\"mCheckRemoteCN\":true,\"mVerb\":\"1\",\"mRemoteRandom\":false,\"mRoutenopull\":false,\"mConnectRetry\":\"2\",\"mAllowedAppsVpn\":[],\"mUserEditable\":true,\"mUseUdp\":true,\"mAllowedAppsVpnAreDisallowed\":true,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mUseCustomConfig\":false,\"mUseFloat\":false,\"mUseDefaultRoute\":true,\"mConnectRetryMaxTime\":\"300\",\"mNobind\":true,\"mVersion\":0,\"mConnectRetryMax\":\"-1\",\"mOverrideDNS\":false,\"mAuth\":\"\",\"mTunMtu\":0,\"mUseObfs4\":true,\"mPassword\":\"\",\"mTLSAuthDirection\":\"\",\"mKeyPassword\":\"\",\"mCustomConfigOptions\":\"\",\"mName\":\"mockProfile\",\"mExpectTLSCert\":false,\"mUsername\":\"\",\"mAllowLocalLAN\":false,\"mDataCiphers\":\"\",\"mSearchDomain\":\"blinkt.de\",\"mTemporaryProfile\":false,\"mUseTLSAuth\":false,\"mRemoteCN\":\"\",\"mCustomRoutesv6\":\"\",\"mPersistTun\":false,\"mX509AuthType\":3,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mServerName\":\"openvpn.example.com\",\"mMssFix\":0,\"mPushPeerInfo\":false,\"mAuthenticationType\":2,\"mBlockUnusedAddressFamilies\":true,\"mServerPort\":\"1194\",\"mUseDefaultRoutev6\":true,\"mConnections\":[{\"mCustomConfiguration\":\"\",\"mServerName\":\"192.168.0.1\",\"mProxyType\":\"SOCKS5\",\"mConnectTimeout\":0,\"mServerPort\":\"1234\",\"mUseUdp\":false,\"mProxyPort\":\"4430\",\"mUseCustomConfig\":false,\"options\":{\"udp\":true,\"remoteIP\":\"192.168.0.1\",\"iatMode\":\"1\",\"remotePort\":\"1234\",\"cert\":\"CERT\"},\"mProxyName\":\"127.0.0.1\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.Obfs4Connection\",\"mEnabled\":true}],\"mUseLzo\":false,\"mAllowAppVpnBypass\":false,\"mUsePull\":true,\"mUseRandomHostname\":false,\"mAuthRetry\":0}\n";
+ private static final String OPENVPNCONNECTION_PROFILE = "{\"mCipher\":\"\",\"mProfileVersion\":7,\"mLastUsed\":0,\"mCheckRemoteCN\":true,\"mVerb\":\"1\",\"mRemoteRandom\":false,\"mRoutenopull\":false,\"mConnectRetry\":\"2\",\"mAllowedAppsVpn\":[],\"mUserEditable\":true,\"mUseUdp\":true,\"mAllowedAppsVpnAreDisallowed\":true,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mUseCustomConfig\":false,\"mUseFloat\":false,\"mUseDefaultRoute\":true,\"mConnectRetryMaxTime\":\"300\",\"mNobind\":true,\"mVersion\":0,\"mConnectRetryMax\":\"-1\",\"mOverrideDNS\":false,\"mAuth\":\"\",\"mTunMtu\":0,\"mPassword\":\"\",\"mTLSAuthDirection\":\"\",\"mKeyPassword\":\"\",\"mCustomConfigOptions\":\"\",\"mName\":\"mockProfile\",\"mExpectTLSCert\":false,\"mUsername\":\"\",\"mAllowLocalLAN\":false,\"mDataCiphers\":\"\",\"mSearchDomain\":\"blinkt.de\",\"mTemporaryProfile\":false,\"mUseTLSAuth\":false,\"mRemoteCN\":\"\",\"mCustomRoutesv6\":\"\",\"mPersistTun\":false,\"mX509AuthType\":3,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mServerName\":\"openvpn.example.com\",\"mMssFix\":0,\"mPushPeerInfo\":false,\"mAuthenticationType\":2,\"mBlockUnusedAddressFamilies\":true,\"mServerPort\":\"1194\",\"mUseDefaultRoutev6\":true,\"mConnections\":[{\"mCustomConfiguration\":\"\",\"mUseUdp\":false,\"mServerName\":\"openvpn.example.com\",\"mProxyType\":\"NONE\",\"mProxyPort\":\"8080\",\"mUseCustomConfig\":false,\"mConnectTimeout\":0,\"mProxyName\":\"proxy.example.com\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.OpenvpnConnection\",\"mServerPort\":\"1194\",\"mEnabled\":true}],\"mUseLzo\":false,\"mTransportType\":1,\"mAllowAppVpnBypass\":false,\"mUsePull\":true,\"mUseRandomHostname\":false,\"mAuthRetry\":0}";
+ private static final String OBFS4CONNECTION_PROFILE = "{\"mCipher\":\"\",\"mProfileVersion\":7,\"mLastUsed\":0,\"mCheckRemoteCN\":true,\"mVerb\":\"1\",\"mRemoteRandom\":false,\"mRoutenopull\":false,\"mConnectRetry\":\"2\",\"mAllowedAppsVpn\":[],\"mUserEditable\":true,\"mUseUdp\":true,\"mAllowedAppsVpnAreDisallowed\":true,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mUseCustomConfig\":false,\"mUseFloat\":false,\"mUseDefaultRoute\":true,\"mConnectRetryMaxTime\":\"300\",\"mNobind\":true,\"mVersion\":0,\"mConnectRetryMax\":\"-1\",\"mOverrideDNS\":false,\"mAuth\":\"\",\"mTunMtu\":0,\"mPassword\":\"\",\"mTLSAuthDirection\":\"\",\"mKeyPassword\":\"\",\"mCustomConfigOptions\":\"\",\"mName\":\"mockProfile\",\"mExpectTLSCert\":false,\"mUsername\":\"\",\"mAllowLocalLAN\":false,\"mDataCiphers\":\"\",\"mSearchDomain\":\"blinkt.de\",\"mTemporaryProfile\":false,\"mUseTLSAuth\":false,\"mRemoteCN\":\"\",\"mCustomRoutesv6\":\"\",\"mPersistTun\":false,\"mX509AuthType\":3,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mServerName\":\"openvpn.example.com\",\"mMssFix\":0,\"mPushPeerInfo\":false,\"mAuthenticationType\":2,\"mBlockUnusedAddressFamilies\":true,\"mServerPort\":\"1194\",\"mUseDefaultRoutev6\":true,\"mConnections\":[{\"mCustomConfiguration\":\"\",\"mServerName\":\"127.0.0.1\",\"mProxyType\":\"NONE\",\"mConnectTimeout\":0,\"mServerPort\":\"4430\",\"mUseUdp\":false,\"mProxyPort\":\"\",\"mUseCustomConfig\":false,\"options\":{\"gatewayIP\":\"192.168.0.1\",\"transport\":{\"options\":{\"portCount\":0,\"iatMode\":\"0\",\"cert\":\"CERT\",\"experimental\":false,\"portSeed\":0},\"type\":\"obfs4\",\"protocols\":[\"tcp\"],\"ports\":[\"1234\"]}},\"mProxyName\":\"\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.Obfs4Connection\",\"mEnabled\":true}],\"mUseLzo\":false,\"mTransportType\":2,\"mAllowAppVpnBypass\":false,\"mUsePull\":true,\"mUseRandomHostname\":false,\"mAuthRetry\":0}";
+ private static final String OBFS4CONNECTION_PROFILE_OBFSVPN = "{\"mCipher\":\"\",\"mProfileVersion\":7,\"mLastUsed\":0,\"mCheckRemoteCN\":true,\"mVerb\":\"1\",\"mRemoteRandom\":false,\"mRoutenopull\":false,\"mConnectRetry\":\"2\",\"mAllowedAppsVpn\":[],\"mUserEditable\":true,\"mUseUdp\":true,\"mAllowedAppsVpnAreDisallowed\":true,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mUseCustomConfig\":false,\"mUseFloat\":false,\"mUseDefaultRoute\":true,\"mConnectRetryMaxTime\":\"300\",\"mNobind\":true,\"mVersion\":0,\"mConnectRetryMax\":\"-1\",\"mOverrideDNS\":false,\"mAuth\":\"\",\"mTunMtu\":0,\"mPassword\":\"\",\"mTLSAuthDirection\":\"\",\"mKeyPassword\":\"\",\"mCustomConfigOptions\":\"\",\"mName\":\"mockProfile\",\"mExpectTLSCert\":false,\"mUsername\":\"\",\"mAllowLocalLAN\":false,\"mDataCiphers\":\"\",\"mSearchDomain\":\"blinkt.de\",\"mTemporaryProfile\":false,\"mUseTLSAuth\":false,\"mRemoteCN\":\"\",\"mCustomRoutesv6\":\"\",\"mPersistTun\":false,\"mX509AuthType\":3,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mServerName\":\"openvpn.example.com\",\"mMssFix\":0,\"mPushPeerInfo\":false,\"mAuthenticationType\":2,\"mBlockUnusedAddressFamilies\":true,\"mServerPort\":\"1194\",\"mUseDefaultRoutev6\":true,\"mConnections\":[{\"mCustomConfiguration\":\"\",\"mServerName\":\"192.168.0.1\",\"mProxyType\":\"SOCKS5\",\"mConnectTimeout\":0,\"mServerPort\":\"1234\",\"mUseUdp\":false,\"mProxyPort\":\"4430\",\"mUseCustomConfig\":false,\"options\":{\"gatewayIP\":\"192.168.0.1\",\"transport\":{\"options\":{\"portCount\":0,\"iatMode\":\"1\",\"cert\":\"CERT\",\"experimental\":false,\"portSeed\":0},\"type\":\"obfs4\",\"protocols\":[\"tcp\"],\"ports\":[\"1234\"]}},\"mProxyName\":\"127.0.0.1\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.Obfs4Connection\",\"mEnabled\":true}],\"mUseLzo\":false,\"mTransportType\":2,\"mAllowAppVpnBypass\":false,\"mUsePull\":true,\"mUseRandomHostname\":false,\"mAuthRetry\":0}";
+ private static final String OBFS4CONNECTION_PROFILE_OBFSVPN_KCP = "{\"mCipher\":\"\",\"mProfileVersion\":7,\"mLastUsed\":0,\"mCheckRemoteCN\":true,\"mVerb\":\"1\",\"mRemoteRandom\":false,\"mRoutenopull\":false,\"mConnectRetry\":\"2\",\"mAllowedAppsVpn\":[],\"mUserEditable\":true,\"mUseUdp\":true,\"mAllowedAppsVpnAreDisallowed\":true,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mUseCustomConfig\":false,\"mUseFloat\":false,\"mUseDefaultRoute\":true,\"mConnectRetryMaxTime\":\"300\",\"mNobind\":true,\"mVersion\":0,\"mConnectRetryMax\":\"-1\",\"mOverrideDNS\":false,\"mAuth\":\"\",\"mTunMtu\":0,\"mPassword\":\"\",\"mTLSAuthDirection\":\"\",\"mKeyPassword\":\"\",\"mCustomConfigOptions\":\"\",\"mName\":\"mockProfile\",\"mExpectTLSCert\":false,\"mUsername\":\"\",\"mAllowLocalLAN\":false,\"mDataCiphers\":\"\",\"mSearchDomain\":\"blinkt.de\",\"mTemporaryProfile\":false,\"mUseTLSAuth\":false,\"mRemoteCN\":\"\",\"mCustomRoutesv6\":\"\",\"mPersistTun\":false,\"mX509AuthType\":3,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mServerName\":\"openvpn.example.com\",\"mMssFix\":0,\"mPushPeerInfo\":false,\"mAuthenticationType\":2,\"mBlockUnusedAddressFamilies\":true,\"mServerPort\":\"1194\",\"mUseDefaultRoutev6\":true,\"mConnections\":[{\"mCustomConfiguration\":\"\",\"mServerName\":\"192.168.0.1\",\"mProxyType\":\"SOCKS5\",\"mConnectTimeout\":0,\"mServerPort\":\"1234\",\"mUseUdp\":false,\"mProxyPort\":\"4430\",\"mUseCustomConfig\":false,\"options\":{\"gatewayIP\":\"192.168.0.1\",\"transport\":{\"options\":{\"portCount\":0,\"iatMode\":\"1\",\"cert\":\"CERT\",\"experimental\":false,\"portSeed\":0},\"type\":\"obfs4\",\"protocols\":[\"kcp\"],\"ports\":[\"1234\"]}},\"mProxyName\":\"127.0.0.1\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.Obfs4Connection\",\"mEnabled\":true}],\"mUseLzo\":false,\"mTransportType\":2,\"mAllowAppVpnBypass\":false,\"mUsePull\":true,\"mUseRandomHostname\":false,\"mAuthRetry\":0}\n";
+
+ private static final String OBFS4CONNECTION_PROFILE_OBFSVPN_HOP = "{\"mCipher\":\"\",\"mProfileVersion\":7,\"mLastUsed\":0,\"mCheckRemoteCN\":true,\"mVerb\":\"1\",\"mRemoteRandom\":false,\"mRoutenopull\":false,\"mConnectRetry\":\"2\",\"mAllowedAppsVpn\":[],\"mUserEditable\":true,\"mUseUdp\":true,\"mAllowedAppsVpnAreDisallowed\":true,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mUseCustomConfig\":false,\"mUseFloat\":false,\"mUseDefaultRoute\":true,\"mConnectRetryMaxTime\":\"300\",\"mNobind\":true,\"mVersion\":0,\"mConnectRetryMax\":\"-1\",\"mOverrideDNS\":false,\"mAuth\":\"\",\"mTunMtu\":0,\"mPassword\":\"\",\"mTLSAuthDirection\":\"\",\"mKeyPassword\":\"\",\"mCustomConfigOptions\":\"\",\"mName\":\"mockProfile\",\"mExpectTLSCert\":false,\"mUsername\":\"\",\"mAllowLocalLAN\":false,\"mDataCiphers\":\"\",\"mSearchDomain\":\"blinkt.de\",\"mTemporaryProfile\":false,\"mUseTLSAuth\":false,\"mRemoteCN\":\"\",\"mCustomRoutesv6\":\"\",\"mPersistTun\":false,\"mX509AuthType\":3,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mServerName\":\"openvpn.example.com\",\"mMssFix\":0,\"mPushPeerInfo\":false,\"mAuthenticationType\":2,\"mBlockUnusedAddressFamilies\":true,\"mServerPort\":\"1194\",\"mUseDefaultRoutev6\":true,\"mConnections\":[{\"mCustomConfiguration\":\"\",\"mServerName\":\"192.168.0.1\",\"mProxyType\":\"SOCKS5\",\"mConnectTimeout\":0,\"mServerPort\":\"1234\",\"mUseUdp\":false,\"mProxyPort\":\"4430\",\"mUseCustomConfig\":false,\"options\":{\"gatewayIP\":\"192.168.0.1\",\"transport\":{\"options\":{\"portCount\":100,\"endpoints\":[{\"ip\":\"1.1.1.1\",\"cert\":\"CERT1\"},{\"ip\":\"2.2.2.2\",\"cert\":\"CERT2\"}],\"iatMode\":\"1\",\"experimental\":true,\"portSeed\":200},\"type\":\"obfs4\",\"protocols\":[\"tcp\"],\"ports\":[\"1234\"]}},\"mProxyName\":\"127.0.0.1\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.Obfs4Connection\",\"mEnabled\":true}],\"mUseLzo\":false,\"mTransportType\":3,\"mAllowAppVpnBypass\":false,\"mUsePull\":true,\"mUseRandomHostname\":false,\"mAuthRetry\":0}\n";
+
+ private static final String OBFS4CONNECTION_PROFILE_OBFSVPN_HOP_KCP = "{\"mCipher\":\"\",\"mProfileVersion\":7,\"mLastUsed\":0,\"mCheckRemoteCN\":true,\"mVerb\":\"1\",\"mRemoteRandom\":false,\"mRoutenopull\":false,\"mConnectRetry\":\"2\",\"mAllowedAppsVpn\":[],\"mUserEditable\":true,\"mUseUdp\":true,\"mAllowedAppsVpnAreDisallowed\":true,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mUseCustomConfig\":false,\"mUseFloat\":false,\"mUseDefaultRoute\":true,\"mConnectRetryMaxTime\":\"300\",\"mNobind\":true,\"mVersion\":0,\"mConnectRetryMax\":\"-1\",\"mOverrideDNS\":false,\"mAuth\":\"\",\"mTunMtu\":0,\"mPassword\":\"\",\"mTLSAuthDirection\":\"\",\"mKeyPassword\":\"\",\"mCustomConfigOptions\":\"\",\"mName\":\"mockProfile\",\"mExpectTLSCert\":false,\"mUsername\":\"\",\"mAllowLocalLAN\":false,\"mDataCiphers\":\"\",\"mSearchDomain\":\"blinkt.de\",\"mTemporaryProfile\":false,\"mUseTLSAuth\":false,\"mRemoteCN\":\"\",\"mCustomRoutesv6\":\"\",\"mPersistTun\":false,\"mX509AuthType\":3,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mServerName\":\"openvpn.example.com\",\"mMssFix\":0,\"mPushPeerInfo\":false,\"mAuthenticationType\":2,\"mBlockUnusedAddressFamilies\":true,\"mServerPort\":\"1194\",\"mUseDefaultRoutev6\":true,\"mConnections\":[{\"mCustomConfiguration\":\"\",\"mServerName\":\"192.168.0.1\",\"mProxyType\":\"SOCKS5\",\"mConnectTimeout\":0,\"mServerPort\":\"1234\",\"mUseUdp\":false,\"mProxyPort\":\"4430\",\"mUseCustomConfig\":false,\"options\":{\"gatewayIP\":\"192.168.0.1\",\"transport\":{\"options\":{\"portCount\":100,\"endpoints\":[{\"ip\":\"1.1.1.1\",\"cert\":\"CERT1\"},{\"ip\":\"2.2.2.2\",\"cert\":\"CERT2\"}],\"iatMode\":\"1\",\"experimental\":true,\"portSeed\":2500},\"type\":\"obfs4\",\"protocols\":[\"kcp\"],\"ports\":[\"1234\"]}},\"mProxyName\":\"127.0.0.1\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.Obfs4Connection\",\"mEnabled\":true}],\"mUseLzo\":false,\"mTransportType\":3,\"mAllowAppVpnBypass\":false,\"mUsePull\":true,\"mUseRandomHostname\":false,\"mAuthRetry\":0}\n";
@Before
public void setup() {
@@ -71,7 +78,9 @@ public class VpnProfileTest {
when(ConfigHelper.ObfsVpnHelper.useObfsVpn()).thenReturn(false);
VpnProfile mockVpnProfile = new VpnProfile("mockProfile", OBFS4);
- mockVpnProfile.mConnections[0] = new Obfs4Connection(new Obfs4Options("192.168.0.1", "1234", "CERT", "1", false));
+
+ Transport transport = new Transport(OBFS4.toString(), new String[]{"tcp"}, new String[]{"1234"}, "CERT");
+ mockVpnProfile.mConnections[0] = new Obfs4Connection(new Obfs4Options("192.168.0.1", transport));
mockVpnProfile.mLastUsed = 0;
String s = mockVpnProfile.toJson();
System.out.println(s);
@@ -88,7 +97,9 @@ public class VpnProfileTest {
public void toJson_obfs4_obfsvpn() throws JSONException {
when(ConfigHelper.ObfsVpnHelper.useObfsVpn()).thenReturn(true);
VpnProfile mockVpnProfile = new VpnProfile("mockProfile", OBFS4);
- mockVpnProfile.mConnections[0] = new Obfs4Connection(new Obfs4Options("192.168.0.1", "1234", "CERT", "1", false));
+ Transport.Options options = new Transport.Options("CERT", "1");
+ Transport transport = new Transport(OBFS4.toString(), new String[]{"tcp"}, new String[]{"1234"}, options);
+ mockVpnProfile.mConnections[0] = new Obfs4Connection(new Obfs4Options("192.168.0.1", transport));
mockVpnProfile.mLastUsed = 0;
String s = mockVpnProfile.toJson();
System.out.println(s);
@@ -106,7 +117,9 @@ public class VpnProfileTest {
when(ConfigHelper.ObfsVpnHelper.useObfsVpn()).thenReturn(true);
VpnProfile mockVpnProfile = new VpnProfile("mockProfile", OBFS4);
- mockVpnProfile.mConnections[0] = new Obfs4Connection(new Obfs4Options("192.168.0.1", "1234", "CERT", "1", true));
+ Transport.Options options = new Transport.Options("CERT", "1");
+ Transport transport = new Transport(OBFS4.toString(), new String[]{"kcp"}, new String[]{"1234"}, options);
+ mockVpnProfile.mConnections[0] = new Obfs4Connection(new Obfs4Options("192.168.0.1", transport));
mockVpnProfile.mLastUsed = 0;
String s = mockVpnProfile.toJson();
System.out.println(s);
@@ -120,6 +133,49 @@ public class VpnProfileTest {
}
@Test
+ public void toJson_obfs4hop_kcp() throws JSONException {
+ when(ConfigHelper.ObfsVpnHelper.useObfsVpn()).thenReturn(true);
+
+ VpnProfile mockVpnProfile = new VpnProfile("mockProfile", OBFS4_HOP);
+
+ Transport.Options options = new Transport.Options("1", new Transport.Endpoint[]{new Transport.Endpoint("1.1.1.1", "CERT1"), new Transport.Endpoint("2.2.2.2", "CERT2")}, 2500, 100, true);
+ Transport transport = new Transport(OBFS4.toString(), new String[]{"kcp"}, new String[]{"1234"}, options);
+ mockVpnProfile.mConnections[0] = new Obfs4Connection(new Obfs4Options("192.168.0.1", transport));
+
+ mockVpnProfile.mLastUsed = 0;
+ String s = mockVpnProfile.toJson();
+ System.out.println(s);
+
+ //ignore UUID in comparison -> set it to fixed value
+ JSONObject actual = new JSONObject(s);
+ actual.put("mUuid", "9d295ca2-3789-48dd-996e-f731dbf50fdc");
+ JSONObject expectation = new JSONObject(OBFS4CONNECTION_PROFILE_OBFSVPN_HOP_KCP);
+
+ assertEquals(expectation.toString(),actual.toString());
+ }
+
+ @Test
+ public void toJson_obfs4hop() throws JSONException {
+ when(ConfigHelper.ObfsVpnHelper.useObfsVpn()).thenReturn(true);
+
+ VpnProfile mockVpnProfile = new VpnProfile("mockProfile", OBFS4_HOP);
+ Transport.Options options = new Transport.Options("1", new Transport.Endpoint[]{new Transport.Endpoint("1.1.1.1", "CERT1"), new Transport.Endpoint("2.2.2.2", "CERT2")}, 200, 100, true);
+ Transport transport = new Transport(OBFS4.toString(), new String[]{"tcp"}, new String[]{"1234"}, options);
+ mockVpnProfile.mConnections[0] = new Obfs4Connection(new Obfs4Options("192.168.0.1", transport));
+
+ mockVpnProfile.mLastUsed = 0;
+ String s = mockVpnProfile.toJson();
+ System.out.println(s);
+
+ //ignore UUID in comparison -> set it to fixed value
+ JSONObject actual = new JSONObject(s);
+ actual.put("mUuid", "9d295ca2-3789-48dd-996e-f731dbf50fdc");
+ JSONObject expectation = new JSONObject(OBFS4CONNECTION_PROFILE_OBFSVPN_HOP);
+
+ assertEquals(expectation.toString(),actual.toString());
+ }
+
+ @Test
public void fromJson_obfs4() {
when(ConfigHelper.ObfsVpnHelper.useObfsVpn()).thenReturn(false);
@@ -130,11 +186,12 @@ public class VpnProfileTest {
assertFalse(mockVpnProfile.mConnections[0].isUseUdp());
Obfs4Connection obfs4Connection = (Obfs4Connection) mockVpnProfile.mConnections[0];
assertEquals(OBFS4, obfs4Connection.getTransportType());
- assertFalse(obfs4Connection.getDispatcherOptions().udp);
- assertEquals("CERT", obfs4Connection.getDispatcherOptions().cert);
- assertEquals("1", obfs4Connection.getDispatcherOptions().iatMode);
- assertEquals("192.168.0.1", obfs4Connection.getDispatcherOptions().remoteIP);
- assertEquals("1234", obfs4Connection.getDispatcherOptions().remotePort);
+ assertFalse(Arrays.asList(obfs4Connection.getObfs4Options().transport.getProtocols()).contains(UDP));
+ assertEquals("CERT", obfs4Connection.getObfs4Options().transport.getOptions().getCert());
+ assertEquals("0", obfs4Connection.getObfs4Options().transport.getOptions().getIatMode());
+ assertEquals("192.168.0.1", obfs4Connection.getObfs4Options().gatewayIP);
+ assertEquals("1234", obfs4Connection.getObfs4Options().transport.getPorts()[0]);
+ assertEquals(1, obfs4Connection.getObfs4Options().transport.getPorts().length);
}
@Test
@@ -148,11 +205,12 @@ public class VpnProfileTest {
assertFalse(mockVpnProfile.mConnections[0].isUseUdp());
Obfs4Connection obfs4Connection = (Obfs4Connection) mockVpnProfile.mConnections[0];
assertEquals(OBFS4, obfs4Connection.getTransportType());
- assertFalse(obfs4Connection.getDispatcherOptions().udp);
- assertEquals("CERT", obfs4Connection.getDispatcherOptions().cert);
- assertEquals("1", obfs4Connection.getDispatcherOptions().iatMode);
- assertEquals("192.168.0.1", obfs4Connection.getDispatcherOptions().remoteIP);
- assertEquals("1234", obfs4Connection.getDispatcherOptions().remotePort);
+ assertEquals("tcp", obfs4Connection.getObfs4Options().transport.getProtocols()[0]);
+ assertEquals("CERT", obfs4Connection.getObfs4Options().transport.getOptions().getCert());
+ assertEquals("1", obfs4Connection.getObfs4Options().transport.getOptions().getIatMode());
+ assertEquals("192.168.0.1", obfs4Connection.getObfs4Options().gatewayIP);
+ assertEquals("1234", obfs4Connection.getObfs4Options().transport.getPorts()[0]);
+ assertEquals(1, obfs4Connection.getObfs4Options().transport.getPorts().length);
}
@Test
@@ -166,10 +224,10 @@ public class VpnProfileTest {
assertFalse(mockVpnProfile.mConnections[0].isUseUdp());
Obfs4Connection obfs4Connection = (Obfs4Connection) mockVpnProfile.mConnections[0];
assertEquals(OBFS4, obfs4Connection.getTransportType());
- assertTrue(obfs4Connection.getDispatcherOptions().udp);
- assertEquals("CERT", obfs4Connection.getDispatcherOptions().cert);
- assertEquals("1", obfs4Connection.getDispatcherOptions().iatMode);
- assertEquals("192.168.0.1", obfs4Connection.getDispatcherOptions().remoteIP);
- assertEquals("1234", obfs4Connection.getDispatcherOptions().remotePort);
+ assertEquals("kcp", obfs4Connection.getObfs4Options().transport.getProtocols()[0]);
+ assertEquals("CERT", obfs4Connection.getObfs4Options().transport.getOptions().getCert());
+ assertEquals("1", obfs4Connection.getObfs4Options().transport.getOptions().getIatMode());
+ assertEquals("192.168.0.1", obfs4Connection.getObfs4Options().gatewayIP);
+ assertEquals("1234", obfs4Connection.getObfs4Options().transport.getPorts()[0]);
}
} \ No newline at end of file
diff --git a/app/src/test/java/se/leap/bitmaskclient/base/models/GatewayJsonTest.java b/app/src/test/java/se/leap/bitmaskclient/base/models/GatewayJsonTest.java
index 61275378..801f98ad 100644
--- a/app/src/test/java/se/leap/bitmaskclient/base/models/GatewayJsonTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/base/models/GatewayJsonTest.java
@@ -12,7 +12,7 @@ public class GatewayJsonTest {
@Test
public void testToString() {
- String gatewayJSON = "{\"location\":\"Unknown Location\",\"ip_address\":\"1.2.3.4\",\"host\":\"pinned.obfuscation.proxy\",\"capabilities\":{\"adblock\":false,\"filter_dns\":false,\"limited\":false,\"transport\":[{\"type\":\"obfs4\",\"protocols\":[\"tcp\"],\"ports\":[\"1194\"],\"options\":{\"cert\":\"xxxxxxx\",\"iatMode\":\"0\"}}],\"user_ips\":false}}";
+ String gatewayJSON = "{\"location\":\"Unknown Location\",\"ip_address\":\"1.2.3.4\",\"host\":\"pinned.obfuscation.proxy\",\"capabilities\":{\"adblock\":false,\"filter_dns\":false,\"limited\":false,\"transport\":[{\"type\":\"obfs4\",\"protocols\":[\"tcp\"],\"ports\":[\"1194\"],\"options\":{\"cert\":\"xxxxxxx\",\"iatMode\":\"0\",\"experimental\":false,\"portSeed\":0,\"portCount\":0}}],\"user_ips\":false}}";
Connection.TransportType transportType = OBFS4;
Transport[] transports = new Transport[]{
diff --git a/app/src/test/java/se/leap/bitmaskclient/base/models/TransportTest.java b/app/src/test/java/se/leap/bitmaskclient/base/models/TransportTest.java
new file mode 100644
index 00000000..37dfc161
--- /dev/null
+++ b/app/src/test/java/se/leap/bitmaskclient/base/models/TransportTest.java
@@ -0,0 +1,68 @@
+package se.leap.bitmaskclient.base.models;
+
+import static se.leap.bitmaskclient.base.models.Constants.CAPABILITIES;
+import static se.leap.bitmaskclient.base.models.Constants.TRANSPORT;
+
+import junit.framework.TestCase;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+
+import de.blinkt.openvpn.core.connection.Connection;
+import se.leap.bitmaskclient.testutils.TestSetupHelper;
+
+public class TransportTest extends TestCase {
+
+ private JSONObject gateway;
+
+ public void test_obfs4_fromJson() throws IOException, JSONException {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_tcp_gateways.json"))).getJSONArray("gateways").getJSONObject(2);
+ JSONObject obfs4Transport = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT).getJSONObject(1);
+ Transport transport = Transport.fromJson(obfs4Transport);
+ assertEquals("obfs4", transport.getType());
+ assertEquals("0", transport.getOptions().getIatMode());
+ assertEquals("kcp", transport.getProtocols()[0]);
+ assertEquals(1, transport.getProtocols().length);
+ assertEquals("23050", transport.getPorts()[0]);
+ assertEquals(1, transport.getPorts().length);
+ assertEquals("XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1", transport.getOptions().getCert());
+ assertNull(transport.getOptions().getEndpoints());
+ assertEquals(0, transport.getOptions().getPortCount());
+ assertEquals(0, transport.getOptions().getPortSeed());
+ assertFalse(transport.getOptions().isExperimental());
+ }
+
+ public void test_obfs4hop_fromJson() throws IOException, JSONException {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_tcp_gateways.json"))).getJSONArray("gateways").getJSONObject(2);
+ JSONObject obfs4Transport = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT).getJSONObject(2);
+ Transport transport = Transport.fromJson(obfs4Transport);
+ assertEquals("obfs4-hop", transport.getType());
+ assertEquals(Connection.TransportType.OBFS4_HOP, transport.getTransportType());
+ assertEquals("tcp", transport.getProtocols()[0]);
+ assertEquals(1, transport.getProtocols().length);
+ assertNull(transport.getPorts());
+ assertNull(transport.getOptions().getCert());
+ assertNotNull(transport.getOptions().getEndpoints());
+ assertEquals(2, transport.getOptions().getEndpoints().length);
+ assertEquals("CERT1", transport.getOptions().getEndpoints()[0].getCert());
+ assertEquals("CERT2", transport.getOptions().getEndpoints()[1].getCert());
+ assertEquals("1.1.1.1", transport.getOptions().getEndpoints()[0].getIp());
+ assertEquals("2.2.2.2", transport.getOptions().getEndpoints()[1].getIp());
+ assertTrue(transport.getOptions().isExperimental());
+ }
+
+ public void test_openvpn_fromJson() throws IOException, JSONException {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_tcp_gateways.json"))).getJSONArray("gateways").getJSONObject(2);
+ JSONObject obfs4Transport = gateway.getJSONObject(CAPABILITIES).getJSONArray(TRANSPORT).getJSONObject(0);
+ Transport transport = Transport.fromJson(obfs4Transport);
+ assertEquals("openvpn", transport.getType());
+ assertEquals(2, transport.getProtocols().length);
+ assertEquals("tcp", transport.getProtocols()[0]);
+ assertEquals("udp", transport.getProtocols()[1]);
+ assertEquals(1, transport.getPorts().length);
+ assertEquals("1195", transport.getPorts()[0]);
+ assertNull(transport.getOptions());
+ }
+} \ No newline at end of file
diff --git a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java
index 6b1c5bf3..99e71f05 100644
--- a/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java
+++ b/app/src/test/java/se/leap/bitmaskclient/eip/VpnConfigGeneratorTest.java
@@ -9,6 +9,7 @@ import static org.mockito.Mockito.mock;
import static org.powermock.api.mockito.PowerMockito.mockStatic;
import static org.powermock.api.mockito.PowerMockito.when;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4_HOP;
import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
import static se.leap.bitmaskclient.base.models.Constants.OPENVPN_CONFIGURATION;
import static se.leap.bitmaskclient.testutils.MockHelper.mockTextUtils;
@@ -34,6 +35,7 @@ import java.util.HashMap;
import de.blinkt.openvpn.VpnProfile;
import de.blinkt.openvpn.core.connection.Connection;
import de.blinkt.openvpn.core.connection.Obfs4Connection;
+import de.blinkt.openvpn.core.connection.Obfs4HopConnection;
import se.leap.bitmaskclient.base.utils.ConfigHelper;
import se.leap.bitmaskclient.testutils.MockHelper;
import se.leap.bitmaskclient.testutils.TestSetupHelper;
@@ -1696,9 +1698,8 @@ public class VpnConfigGeneratorTest {
configuration.preferUDP = true;
vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration);
HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
- assertTrue(vpnProfiles.containsKey(OBFS4) && ((Obfs4Connection)vpnProfiles.get(OBFS4).mConnections[0]).getDispatcherOptions().udp);
+ assertTrue(vpnProfiles.containsKey(OBFS4) && ((Obfs4Connection)vpnProfiles.get(OBFS4).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("kcp"));
assertTrue(vpnProfiles.containsKey(OPENVPN));
-
}
@Test
@@ -1715,16 +1716,20 @@ public class VpnConfigGeneratorTest {
}
@Test
- public void testGenerateVpnProfile_ObfuscationPinningEnabled_obfs4AndOpenvpnProfile () throws Exception {
+ public void testGenerateVpnProfile_ObfuscationPinningNotEnabled_obfs4AndOpenvpnProfile () throws Exception {
gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_kcp_gateways.json"))).getJSONArray("gateways").getJSONObject(1);
generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_kcp_gateways.json"))).getJSONObject(OPENVPN_CONFIGURATION);
VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration();
configuration.apiVersion = 3;
- configuration.useObfuscationPinning = true;
+ configuration.useObfuscationPinning = false;
+ configuration.obfuscationProxyPort = "443";
+ configuration.obfuscationProxyIP = "5.6.7.8";
+ configuration.obfuscationProxyCert = "asdfasdf";
+ configuration.obfuscationProxyKCP = true;
configuration.remoteGatewayIP = "1.2.3.4";
vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration);
HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
- assertFalse("has no openvpn profile", vpnProfiles.containsKey(OPENVPN));
+ assertTrue("has openvpn profile", vpnProfiles.containsKey(OPENVPN));
assertTrue("has obfs4 profile", vpnProfiles.containsKey(OBFS4));
assertTrue("bridge is running KCP", vpnProfiles.get(OBFS4).mGatewayIp.equals("1.2.3.4"));
@@ -1747,7 +1752,7 @@ public class VpnConfigGeneratorTest {
assertFalse("has openvpn profile", vpnProfiles.containsKey(OPENVPN));
assertTrue("has obfs4 profile", vpnProfiles.containsKey(OBFS4));
assertTrue("bridge is pinned one", vpnProfiles.get(OBFS4).getTransportType() == OBFS4 && !vpnProfiles.get(OBFS4).mConnections[0].isUseUdp() );
- assertTrue("bridge is running KCP", ((Obfs4Connection) vpnProfiles.get(OBFS4).mConnections[0]).getDispatcherOptions().udp == false);
+ assertTrue("bridge is running TCP", ((Obfs4Connection) vpnProfiles.get(OBFS4).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("tcp"));
}
@Test
@@ -1768,7 +1773,29 @@ public class VpnConfigGeneratorTest {
assertFalse("has openvpn profile", vpnProfiles.containsKey(OPENVPN));
assertTrue("has no obfs4 profile", vpnProfiles.containsKey(OBFS4));
assertTrue("bridge is pinned one", vpnProfiles.get(OBFS4).getTransportType() == OBFS4 && vpnProfiles.get(OBFS4).mGatewayIp.equals("1.2.3.4"));
- assertTrue("bridge is running KCP", ((Obfs4Connection) vpnProfiles.get(OBFS4).mConnections[0]).getDispatcherOptions().udp == true);
+ assertTrue("bridge is running KCP", ((Obfs4Connection) vpnProfiles.get(OBFS4).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("kcp"));
+ }
+ @Test
+ public void testGenerateVpnProfile_obfs4hop_tcp () throws Exception {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_tcp_gateways.json"))).getJSONArray("gateways").getJSONObject(2);
+ generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_tcp_gateways.json"))).getJSONObject(OPENVPN_CONFIGURATION);
+ VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration();
+ configuration.apiVersion = 3;
+ vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration);
+ HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
+ assertTrue(vpnProfiles.containsKey(OBFS4_HOP) && ((Obfs4HopConnection)vpnProfiles.get(OBFS4_HOP).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("tcp"));
+ assertTrue(vpnProfiles.containsKey(OPENVPN));
}
+ @Test
+ public void testGenerateVpnProfile_obfs4hop_kcp () throws Exception {
+ gateway = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_kcp_gateways.json"))).getJSONArray("gateways").getJSONObject(2);
+ generalConfig = new JSONObject(TestSetupHelper.getInputAsString(getClass().getClassLoader().getResourceAsStream("ptdemo_obfs4hop_kcp_gateways.json"))).getJSONObject(OPENVPN_CONFIGURATION);
+ VpnConfigGenerator.Configuration configuration = new VpnConfigGenerator.Configuration();
+ configuration.apiVersion = 3;
+ vpnConfigGenerator = new VpnConfigGenerator(generalConfig, secrets, gateway, configuration);
+ HashMap<Connection.TransportType, VpnProfile> vpnProfiles = vpnConfigGenerator.generateVpnProfiles();
+ assertTrue(vpnProfiles.containsKey(OBFS4_HOP) && ((Obfs4HopConnection)vpnProfiles.get(OBFS4_HOP).mConnections[0]).getObfs4Options().transport.getProtocols()[0].equals("kcp"));
+ assertTrue(vpnProfiles.containsKey(OPENVPN));
+ }
} \ No newline at end of file
diff --git a/app/src/test/resources/ptdemo_obfs4hop_kcp_gateways.json b/app/src/test/resources/ptdemo_obfs4hop_kcp_gateways.json
new file mode 100644
index 00000000..a56bc571
--- /dev/null
+++ b/app/src/test/resources/ptdemo_obfs4hop_kcp_gateways.json
@@ -0,0 +1,178 @@
+{
+ "gateways":[
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "23049"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"pt.demo.bitmask.net",
+ "ip_address":"37.218.247.60",
+ "location":"Amsterdam"
+ },
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4-hop",
+ "protocols":[
+ "kcp"
+ ],
+ "options": {
+ "iatMode": "0",
+ "endpoints": [
+ {
+ "ip": "1.1.1.1",
+ "cert": "/ntRNI6JYP7R6kGKldibKWj0aCsv96Hdu/jSGncPy+rcverCLI7Emod+vRkz61hM7F/udA"
+ },
+ {
+ "ip": "2.2.2.2",
+ "cert": "/ntRNI6JYP7R6kGKldibKWj0aCsv96Hdu/jSGncPy+rcverCLI7Emod+vRkz61hM7F/udA"
+ }
+ ],
+ "port_seed": 94,
+ "port_count": 100,
+ "experimental": true
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"moscow.bitmask.net",
+ "ip_address":"3.21.247.89",
+ "location":"moscow"
+ },
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp",
+ "udp"
+ ],
+
+ "ports":[
+ "1195"
+ ]
+ },
+ {
+ "type":"obfs4",
+ "protocols":[
+ "kcp"
+ ],
+ "ports":[
+ "23050"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"obfs4-hop",
+ "protocols":[
+ "kcp"
+ ],
+ "options": {
+ "iatMode": "0",
+ "endpoints": [
+ {
+ "ip": "1.1.1.1",
+ "cert": "/ntRNI6JYP7R6kGKldibKWj0aCsv96Hdu/jSGncPy+rcverCLI7Emod+vRkz61hM7F/udA"
+ },
+ {
+ "ip": "2.2.2.2",
+ "cert": "/ntRNI6JYP7R6kGKldibKWj0aCsv96Hdu/jSGncPy+rcverCLI7Emod+vRkz61hM7F/udA"
+ }
+ ],
+ "port_seed": 94,
+ "port_count": 100,
+ "experimental": true
+ }
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"manila.bitmask.net",
+ "ip_address":"37.12.247.10",
+ "location":"manila"
+ }
+ ],
+ "locations":{
+ "Amsterdam":{
+ "country_code":"NL",
+ "hemisphere":"N",
+ "name":"Amsterdam",
+ "timezone":"-1"
+ },
+ "moscow": {
+ "country_code": "RU",
+ "hemisphere": "N",
+ "name": "Moscow",
+ "timezone": "+3"
+ },
+ "manila": {
+ "country_code": "PH",
+ "hemisphere": "N",
+ "name": "Manila",
+ "timezone": "+8"
+ }
+ },
+ "openvpn_configuration":{
+ "auth":"SHA1",
+ "cipher":"AES-256-CBC",
+ "keepalive":"10 30",
+ "tls-cipher":"DHE-RSA-AES128-SHA",
+ "tun-ipv6":true,
+ "dev" : "tun",
+ "sndbuf" : "0",
+ "rcvbuf" : "0",
+ "nobind" : true,
+ "persist-key" : true,
+ "key-direction" : "1",
+ "verb" : "3"
+ },
+ "serial":2,
+ "version":3
+} \ No newline at end of file
diff --git a/app/src/test/resources/ptdemo_obfs4hop_misconfigured_udp_gateway.json b/app/src/test/resources/ptdemo_obfs4hop_misconfigured_udp_gateway.json
new file mode 100644
index 00000000..a56bc571
--- /dev/null
+++ b/app/src/test/resources/ptdemo_obfs4hop_misconfigured_udp_gateway.json
@@ -0,0 +1,178 @@
+{
+ "gateways":[
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "23049"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"pt.demo.bitmask.net",
+ "ip_address":"37.218.247.60",
+ "location":"Amsterdam"
+ },
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4-hop",
+ "protocols":[
+ "kcp"
+ ],
+ "options": {
+ "iatMode": "0",
+ "endpoints": [
+ {
+ "ip": "1.1.1.1",
+ "cert": "/ntRNI6JYP7R6kGKldibKWj0aCsv96Hdu/jSGncPy+rcverCLI7Emod+vRkz61hM7F/udA"
+ },
+ {
+ "ip": "2.2.2.2",
+ "cert": "/ntRNI6JYP7R6kGKldibKWj0aCsv96Hdu/jSGncPy+rcverCLI7Emod+vRkz61hM7F/udA"
+ }
+ ],
+ "port_seed": 94,
+ "port_count": 100,
+ "experimental": true
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"moscow.bitmask.net",
+ "ip_address":"3.21.247.89",
+ "location":"moscow"
+ },
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp",
+ "udp"
+ ],
+
+ "ports":[
+ "1195"
+ ]
+ },
+ {
+ "type":"obfs4",
+ "protocols":[
+ "kcp"
+ ],
+ "ports":[
+ "23050"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"obfs4-hop",
+ "protocols":[
+ "kcp"
+ ],
+ "options": {
+ "iatMode": "0",
+ "endpoints": [
+ {
+ "ip": "1.1.1.1",
+ "cert": "/ntRNI6JYP7R6kGKldibKWj0aCsv96Hdu/jSGncPy+rcverCLI7Emod+vRkz61hM7F/udA"
+ },
+ {
+ "ip": "2.2.2.2",
+ "cert": "/ntRNI6JYP7R6kGKldibKWj0aCsv96Hdu/jSGncPy+rcverCLI7Emod+vRkz61hM7F/udA"
+ }
+ ],
+ "port_seed": 94,
+ "port_count": 100,
+ "experimental": true
+ }
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"manila.bitmask.net",
+ "ip_address":"37.12.247.10",
+ "location":"manila"
+ }
+ ],
+ "locations":{
+ "Amsterdam":{
+ "country_code":"NL",
+ "hemisphere":"N",
+ "name":"Amsterdam",
+ "timezone":"-1"
+ },
+ "moscow": {
+ "country_code": "RU",
+ "hemisphere": "N",
+ "name": "Moscow",
+ "timezone": "+3"
+ },
+ "manila": {
+ "country_code": "PH",
+ "hemisphere": "N",
+ "name": "Manila",
+ "timezone": "+8"
+ }
+ },
+ "openvpn_configuration":{
+ "auth":"SHA1",
+ "cipher":"AES-256-CBC",
+ "keepalive":"10 30",
+ "tls-cipher":"DHE-RSA-AES128-SHA",
+ "tun-ipv6":true,
+ "dev" : "tun",
+ "sndbuf" : "0",
+ "rcvbuf" : "0",
+ "nobind" : true,
+ "persist-key" : true,
+ "key-direction" : "1",
+ "verb" : "3"
+ },
+ "serial":2,
+ "version":3
+} \ No newline at end of file
diff --git a/app/src/test/resources/ptdemo_obfs4hop_tcp_gateways.json b/app/src/test/resources/ptdemo_obfs4hop_tcp_gateways.json
new file mode 100644
index 00000000..6a748a4f
--- /dev/null
+++ b/app/src/test/resources/ptdemo_obfs4hop_tcp_gateways.json
@@ -0,0 +1,178 @@
+{
+ "gateways":[
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "23049"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"pt.demo.bitmask.net",
+ "ip_address":"37.218.247.60",
+ "location":"Amsterdam"
+ },
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"obfs4-hop",
+ "protocols":[
+ "tcp"
+ ],
+ "options": {
+ "iatMode": "0",
+ "endpoints": [
+ {
+ "ip": "1.1.1.1",
+ "cert": "/ntRNI6JYP7R6kGKldibKWj0aCsv96Hdu/jSGncPy+rcverCLI7Emod+vRkz61hM7F/udA"
+ },
+ {
+ "ip": "2.2.2.2",
+ "cert": "/ntRNI6JYP7R6kGKldibKWj0aCsv96Hdu/jSGncPy+rcverCLI7Emod+vRkz61hM7F/udA"
+ }
+ ],
+ "port_seed": 94,
+ "port_count": 100,
+ "experimental": true
+ }
+ },
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp"
+ ],
+ "ports":[
+ "1195"
+ ]
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"moscow.bitmask.net",
+ "ip_address":"3.21.247.89",
+ "location":"moscow"
+ },
+ {
+ "capabilities":{
+ "adblock":false,
+ "filter_dns":false,
+ "limited":false,
+ "transport":[
+ {
+ "type":"openvpn",
+ "protocols":[
+ "tcp",
+ "udp"
+ ],
+
+ "ports":[
+ "1195"
+ ]
+ },
+ {
+ "type":"obfs4",
+ "protocols":[
+ "kcp"
+ ],
+ "ports":[
+ "23050"
+ ],
+ "options": {
+ "cert": "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX1",
+ "iatMode": "0"
+ }
+ },
+ {
+ "type":"obfs4-hop",
+ "protocols":[
+ "tcp"
+ ],
+ "options": {
+ "iatMode": "0",
+ "endpoints": [
+ {
+ "ip": "1.1.1.1",
+ "cert": "CERT1"
+ },
+ {
+ "ip": "2.2.2.2",
+ "cert": "CERT2"
+ }
+ ],
+ "port_seed": 94,
+ "port_count": 100,
+ "experimental": true
+ }
+ }
+ ],
+ "user_ips":false
+ },
+ "host":"manila.bitmask.net",
+ "ip_address":"37.12.247.10",
+ "location":"manila"
+ }
+ ],
+ "locations":{
+ "Amsterdam":{
+ "country_code":"NL",
+ "hemisphere":"N",
+ "name":"Amsterdam",
+ "timezone":"-1"
+ },
+ "moscow": {
+ "country_code": "RU",
+ "hemisphere": "N",
+ "name": "Moscow",
+ "timezone": "+3"
+ },
+ "manila": {
+ "country_code": "PH",
+ "hemisphere": "N",
+ "name": "Manila",
+ "timezone": "+8"
+ }
+ },
+ "openvpn_configuration":{
+ "auth":"SHA1",
+ "cipher":"AES-256-CBC",
+ "keepalive":"10 30",
+ "tls-cipher":"DHE-RSA-AES128-SHA",
+ "tun-ipv6":true,
+ "dev" : "tun",
+ "sndbuf" : "0",
+ "rcvbuf" : "0",
+ "nobind" : true,
+ "persist-key" : true,
+ "key-direction" : "1",
+ "verb" : "3"
+ },
+ "serial":2,
+ "version":3
+} \ No newline at end of file