From 71fcf8577611d3163ea81307b04db6ba57950bb7 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 18 Jan 2021 12:32:47 +0100 Subject: 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 --- .../main/java/de/blinkt/openvpn/VpnProfile.java | 14 ++-- .../de/blinkt/openvpn/core/OpenVPNService.java | 1 + .../blinkt/openvpn/core/connection/Connection.java | 3 + .../openvpn/core/connection/ConnectionAdapter.java | 35 +++++++++ .../java/de/blinkt/openvpn/VpnProfileTest.java | 90 ++++++++++++++++++++++ 5 files changed, 137 insertions(+), 6 deletions(-) create mode 100644 app/src/main/java/de/blinkt/openvpn/core/connection/ConnectionAdapter.java create mode 100644 app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java 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, JsonDeserializer { + + 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 -- cgit v1.2.3 From f296fc83eaf2c8f2c07cfc20fe87c8805a324ff2 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 18 Jan 2021 12:33:43 +0100 Subject: remove deprecated code --- .../main/java/de/blinkt/openvpn/VpnProfile.java | 29 ---------------------- .../openvpn/core/connection/Obfs4Connection.java | 12 --------- 2 files changed, 41 deletions(-) diff --git a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java index 922eee58..f2da0838 100644 --- a/app/src/main/java/de/blinkt/openvpn/VpnProfile.java +++ b/app/src/main/java/de/blinkt/openvpn/VpnProfile.java @@ -304,21 +304,6 @@ public class VpnProfile implements Serializable, Cloneable { @Deprecated public void upgradeProfile() { - if (mProfileVersion < 2) { - /* default to the behaviour the OS used */ - mAllowLocalLAN = Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT; - } - - if (mProfileVersion < 4) { - moveOptionsToConnection(); - mAllowedAppsVpnAreDisallowed = true; - } - if (mAllowedAppsVpn == null) - mAllowedAppsVpn = new HashSet<>(); - - if (mConnections == null) - mConnections = new Connection[0]; - if (mProfileVersion < 6) { if (TextUtils.isEmpty(mProfileCreator)) mUserEditable = true; @@ -333,20 +318,6 @@ public class VpnProfile implements Serializable, Cloneable { } - @Deprecated - private void moveOptionsToConnection() { - mConnections = new Connection[1]; - Connection conn = mUsePluggableTransports ? new Obfs4Connection() : new OpenvpnConnection(); - - conn.setServerName(mServerName); - conn.setServerPort(mServerPort); - conn.setUseUdp(mUseUdp); - conn.setCustomConfiguration(""); - - mConnections[0] = conn; - - } - public String getConfigFile(Context context, boolean configForOvpn3) { File cacheDir = context.getCacheDir(); 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 a2f86e05..c780f487 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 @@ -27,18 +27,6 @@ public class Obfs4Connection extends Connection { this.options = options; } - @Deprecated - public Obfs4Connection() { - setUseUdp(false); - setServerName(DISPATCHER_IP); - setServerPort(DISPATCHER_PORT); - setProxyName(""); - setProxyPort(""); - setProxyAuthUser(null); - setProxyAuthPassword(null); - setProxyType(ProxyType.NONE); - setUseProxyAuth(false); } - @Override public Connection clone() throws CloneNotSupportedException { Obfs4Connection connection = (Obfs4Connection) super.clone(); -- cgit v1.2.3 From bb6679c4c04e974ddb064b4cb5cee268b67b8ce1 Mon Sep 17 00:00:00 2001 From: cyBerta Date: Mon, 18 Jan 2021 12:41:55 +0100 Subject: fix comment --- app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java b/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java index e7cb8062..e8a93b75 100644 --- a/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java +++ b/app/src/test/java/de/blinkt/openvpn/VpnProfileTest.java @@ -37,7 +37,7 @@ public class VpnProfileTest { mockVpnProfile.mLastUsed = 0; String s = mockVpnProfile.toJson(); - //ignore UUID in comparison -> set it to static value + //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(OPENVPNCONNECTION_PROFILE); @@ -65,7 +65,7 @@ public class VpnProfileTest { String s = mockVpnProfile.toJson(); System.out.println(s); - //ignore UUID in comparison -> set it to 123 + //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); -- cgit v1.2.3