summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorcyBerta <cyberta@riseup.net>2021-01-18 12:32:47 +0100
committercyBerta <cyberta@riseup.net>2021-01-18 12:32:47 +0100
commit71fcf8577611d3163ea81307b04db6ba57950bb7 (patch)
treecdfc1f9d2cd92e1a8bd67f87bcb0266a9fdcae1e
parent7e03f3066127d73270baa0fcba3a01a40d802feb (diff)
fix de-/serialization of Connection objects. Fixes VPN auto-restart on reboot due to always-on system settings or if system killed app due to low memory
-rw-r--r--app/src/main/java/de/blinkt/openvpn/VpnProfile.java14
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java1
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/connection/Connection.java3
-rw-r--r--app/src/main/java/de/blinkt/openvpn/core/connection/ConnectionAdapter.java35
-rw-r--r--app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java90
5 files changed, 137 insertions, 6 deletions
diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
index 41b5ddb5..922eee58 100644
--- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
+++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java
@@ -15,12 +15,14 @@ import android.os.Build;
import android.preference.PreferenceManager;
import android.security.KeyChain;
import android.security.KeyChainException;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import android.text.TextUtils;
import android.util.Base64;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
import org.spongycastle.util.io.pem.PemObject;
import org.spongycastle.util.io.pem.PemWriter;
@@ -63,8 +65,7 @@ import de.blinkt.openvpn.core.VPNLaunchHelper;
import de.blinkt.openvpn.core.VpnStatus;
import de.blinkt.openvpn.core.X509Utils;
import de.blinkt.openvpn.core.connection.Connection;
-import de.blinkt.openvpn.core.connection.Obfs4Connection;
-import de.blinkt.openvpn.core.connection.OpenvpnConnection;
+import de.blinkt.openvpn.core.connection.ConnectionAdapter;
import se.leap.bitmaskclient.BuildConfig;
import se.leap.bitmaskclient.R;
@@ -1159,8 +1160,9 @@ public class VpnProfile implements Serializable, Cloneable {
public static VpnProfile fromJson(String json) {
try {
- Gson gson = new Gson();
- return gson.fromJson(json, VpnProfile.class);
+ GsonBuilder builder = new GsonBuilder();
+ builder.registerTypeAdapter(Connection.class, new ConnectionAdapter());
+ return builder.create().fromJson(json, VpnProfile.class);
} catch (Exception e) {
e.printStackTrace();
}
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 0d4a8037..2c1a65dc 100644
--- a/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/app/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -333,6 +333,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
} else {
/* The intent is null when we are set as always-on or the service has been restarted. */
+ Log.d(TAG, "Starting VPN due to isAlwaysOn system settings or app crash.");
mProfile = VpnStatus.getLastConnectedVpnProfile(this);
VpnStatus.logInfo(R.string.service_restarted);
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 a318e55d..4cb9c0c7 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
@@ -7,9 +7,12 @@ package de.blinkt.openvpn.core.connection;
import android.text.TextUtils;
+import com.google.gson.annotations.JsonAdapter;
+
import java.io.Serializable;
import java.util.Locale;
+@JsonAdapter(ConnectionAdapter.class)
public abstract class Connection implements Serializable, Cloneable {
private String mServerName = "openvpn.example.com";
private String mServerPort = "1194";
diff --git a/app/src/main/java/de/blinkt/openvpn/core/connection/ConnectionAdapter.java b/app/src/main/java/de/blinkt/openvpn/core/connection/ConnectionAdapter.java
new file mode 100644
index 00000000..335ef34c
--- /dev/null
+++ b/app/src/main/java/de/blinkt/openvpn/core/connection/ConnectionAdapter.java
@@ -0,0 +1,35 @@
+package de.blinkt.openvpn.core.connection;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import java.lang.reflect.Type;
+
+// Adapter for Gson used to serialize and deserialize abstract Connection class, adds a property about the implemented class
+public class ConnectionAdapter implements JsonSerializer<Connection>, JsonDeserializer<Connection> {
+
+ public final static String META_TYPE = ConnectionAdapter.class.getSimpleName() + ".META_TYPE";
+ @Override
+ public Connection deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
+ JsonObject jsonObject = json.getAsJsonObject();
+ String className = jsonObject.get(META_TYPE).getAsString();
+ try {
+ Class<?> clz = Class.forName(className);
+ return context.deserialize(json, clz);
+ } catch (ClassNotFoundException e) {
+ throw new JsonParseException(e);
+ }
+ }
+
+ @Override
+ public JsonElement serialize(Connection src, Type typeOfSrc, JsonSerializationContext context) {
+ JsonElement json = context.serialize(src, src.getClass());
+ json.getAsJsonObject().addProperty(META_TYPE, src.getClass().getCanonicalName());
+ return json;
+ }
+}
diff --git a/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java b/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java
new file mode 100644
index 00000000..e7cb8062
--- /dev/null
+++ b/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java
@@ -0,0 +1,90 @@
+package de.blinkt.openvpn;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.junit.Before;
+import org.junit.Test;
+import org.powermock.core.classloader.annotations.PrepareForTest;
+
+import java.util.UUID;
+
+import de.blinkt.openvpn.core.connection.Obfs4Connection;
+import de.blinkt.openvpn.core.connection.OpenvpnConnection;
+import se.leap.bitmaskclient.pluggableTransports.Obfs4Options;
+
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.OBFS4;
+import static de.blinkt.openvpn.core.connection.Connection.TransportType.OPENVPN;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.powermock.api.mockito.PowerMockito.mockStatic;
+
+@PrepareForTest({UUID.class})
+public class VpnProfileTest {
+
+ private static final String OPENVPNCONNECTION_PROFILE = "{\"mAuthenticationType\":2,\"mName\":\"mockProfile\",\"mTLSAuthDirection\":\"\",\"mUseLzo\":false,\"mUseTLSAuth\":false,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mOverrideDNS\":false,\"mSearchDomain\":\"blinkt.de\",\"mUseDefaultRoute\":true,\"mUsePull\":true,\"mCheckRemoteCN\":true,\"mExpectTLSCert\":false,\"mRemoteCN\":\"\",\"mPassword\":\"\",\"mUsername\":\"\",\"mRoutenopull\":false,\"mUseRandomHostname\":false,\"mUseFloat\":false,\"mUseCustomConfig\":false,\"mCustomConfigOptions\":\"\",\"mVerb\":\"1\",\"mCipher\":\"\",\"mDataCiphers\":\"\",\"mNobind\":true,\"mUseDefaultRoutev6\":true,\"mCustomRoutesv6\":\"\",\"mKeyPassword\":\"\",\"mPersistTun\":false,\"mConnectRetryMax\":\"-1\",\"mConnectRetry\":\"2\",\"mConnectRetryMaxTime\":\"300\",\"mUserEditable\":true,\"mAuth\":\"\",\"mX509AuthType\":3,\"mAllowLocalLAN\":false,\"mMssFix\":0,\"mConnections\":[{\"mServerName\":\"openvpn.example.com\",\"mServerPort\":\"1194\",\"mUseUdp\":false,\"mCustomConfiguration\":\"\",\"mUseCustomConfig\":false,\"mEnabled\":true,\"mConnectTimeout\":0,\"mProxyType\":\"NONE\",\"mProxyName\":\"proxy.example.com\",\"mProxyPort\":\"8080\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.OpenvpnConnection\"}],\"mRemoteRandom\":false,\"mAllowedAppsVpn\":[],\"mAllowedAppsVpnAreDisallowed\":true,\"mAllowAppVpnBypass\":false,\"mAuthRetry\":0,\"mTunMtu\":0,\"mPushPeerInfo\":false,\"mVersion\":0,\"mLastUsed\":0,\"mServerName\":\"openvpn.example.com\",\"mServerPort\":\"1194\",\"mUseUdp\":true,\"mTemporaryProfile\":false,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mProfileVersion\":7,\"mBlockUnusedAddressFamilies\":true,\"mUsePluggableTransports\":false}";
+ private static final String OBFS4CONNECTION_PROFILE = "{\"mAuthenticationType\":2,\"mName\":\"mockProfile\",\"mTLSAuthDirection\":\"\",\"mUseLzo\":false,\"mUseTLSAuth\":false,\"mDNS1\":\"8.8.8.8\",\"mDNS2\":\"8.8.4.4\",\"mOverrideDNS\":false,\"mSearchDomain\":\"blinkt.de\",\"mUseDefaultRoute\":true,\"mUsePull\":true,\"mCheckRemoteCN\":true,\"mExpectTLSCert\":false,\"mRemoteCN\":\"\",\"mPassword\":\"\",\"mUsername\":\"\",\"mRoutenopull\":false,\"mUseRandomHostname\":false,\"mUseFloat\":false,\"mUseCustomConfig\":false,\"mCustomConfigOptions\":\"\",\"mVerb\":\"1\",\"mCipher\":\"\",\"mDataCiphers\":\"\",\"mNobind\":true,\"mUseDefaultRoutev6\":true,\"mCustomRoutesv6\":\"\",\"mKeyPassword\":\"\",\"mPersistTun\":false,\"mConnectRetryMax\":\"-1\",\"mConnectRetry\":\"2\",\"mConnectRetryMaxTime\":\"300\",\"mUserEditable\":true,\"mAuth\":\"\",\"mX509AuthType\":3,\"mAllowLocalLAN\":false,\"mMssFix\":0,\"mConnections\":[{\"options\":{\"cert\":\"CERT\",\"iatMode\":\"1\",\"remoteIP\":\"192.168.0.1\",\"remotePort\":\"1234\"},\"mServerName\":\"127.0.0.1\",\"mServerPort\":\"4430\",\"mUseUdp\":false,\"mCustomConfiguration\":\"\",\"mUseCustomConfig\":false,\"mEnabled\":true,\"mConnectTimeout\":0,\"mProxyType\":\"NONE\",\"mProxyName\":\"\",\"mProxyPort\":\"\",\"mUseProxyAuth\":false,\"ConnectionAdapter.META_TYPE\":\"de.blinkt.openvpn.core.connection.Obfs4Connection\"}],\"mRemoteRandom\":false,\"mAllowedAppsVpn\":[],\"mAllowedAppsVpnAreDisallowed\":true,\"mAllowAppVpnBypass\":false,\"mAuthRetry\":0,\"mTunMtu\":0,\"mPushPeerInfo\":false,\"mVersion\":0,\"mLastUsed\":0,\"mServerName\":\"openvpn.example.com\",\"mServerPort\":\"1194\",\"mUseUdp\":true,\"mTemporaryProfile\":false,\"mUuid\":\"9d295ca2-3789-48dd-996e-f731dbf50fdc\",\"mProfileVersion\":7,\"mBlockUnusedAddressFamilies\":true,\"mUsePluggableTransports\":true}";
+ @Before
+ public void setup() {
+ mockStatic(UUID.class);
+ }
+
+ @Test
+ public void toJson_openvpn() throws JSONException {
+ VpnProfile mockVpnProfile = new VpnProfile("mockProfile", OPENVPN);
+ mockVpnProfile.mConnections[0] = new OpenvpnConnection();
+ mockVpnProfile.mConnections[0].setUseUdp(false);
+ mockVpnProfile.mLastUsed = 0;
+ String s = mockVpnProfile.toJson();
+
+ //ignore UUID in comparison -> set it to static value
+ JSONObject actual = new JSONObject(s);
+ actual.put("mUuid", "9d295ca2-3789-48dd-996e-f731dbf50fdc");
+ JSONObject expectation = new JSONObject(OPENVPNCONNECTION_PROFILE);
+
+ assertEquals(expectation.toString(), actual.toString());
+ }
+
+ @Test
+ public void fromJson_openvpn() {
+ VpnProfile mockVpnProfile = VpnProfile.fromJson(OPENVPNCONNECTION_PROFILE);
+ assertNotNull(mockVpnProfile);
+ assertNotNull(mockVpnProfile.mConnections);
+ assertNotNull(mockVpnProfile.mConnections[0]);
+ assertFalse(mockVpnProfile.mConnections[0].isUseUdp());
+ OpenvpnConnection openvpnConnection = (OpenvpnConnection) mockVpnProfile.mConnections[0];
+ assertEquals(openvpnConnection.getTransportType(), OPENVPN);
+ }
+
+ @Test
+ public void toJson_obfs4() throws JSONException {
+ VpnProfile mockVpnProfile = new VpnProfile("mockProfile", OBFS4);
+ mockVpnProfile.mConnections[0] = new Obfs4Connection(new Obfs4Options("192.168.0.1", "1234", "CERT", "1"));
+ mockVpnProfile.mConnections[0].setUseUdp(false);
+ mockVpnProfile.mLastUsed = 0;
+ String s = mockVpnProfile.toJson();
+ System.out.println(s);
+
+ //ignore UUID in comparison -> set it to 123
+ JSONObject actual = new JSONObject(s);
+ actual.put("mUuid", "9d295ca2-3789-48dd-996e-f731dbf50fdc");
+ JSONObject expectation = new JSONObject(OBFS4CONNECTION_PROFILE);
+
+ assertEquals(expectation.toString(),actual.toString());
+ }
+
+ @Test
+ public void fromJson_obfs4() {
+ VpnProfile mockVpnProfile = VpnProfile.fromJson(OBFS4CONNECTION_PROFILE);
+ assertNotNull(mockVpnProfile);
+ assertNotNull(mockVpnProfile.mConnections);
+ assertNotNull(mockVpnProfile.mConnections[0]);
+ assertFalse(mockVpnProfile.mConnections[0].isUseUdp());
+ Obfs4Connection obfs4Connection = (Obfs4Connection) mockVpnProfile.mConnections[0];
+ assertEquals(obfs4Connection.getTransportType(), OBFS4);
+ assertEquals(obfs4Connection.getDispatcherOptions().cert, "CERT");
+ assertEquals(obfs4Connection.getDispatcherOptions().iatMode, "1");
+ assertEquals(obfs4Connection.getDispatcherOptions().remoteIP, "192.168.0.1");
+ assertEquals(obfs4Connection.getDispatcherOptions().remotePort, "1234");
+ }
+} \ No newline at end of file