From 6c0bf4d7a52dd6e60f368e454dab7c91487c97ff Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Mon, 23 Apr 2018 00:05:22 +0200 Subject: Implement HTTP User/Password Authentication via UI (closes #861, #856) --- main/build.gradle | 8 +- .../java/de/blinkt/openvpn/core/ConfigParser.java | 17 +- .../java/de/blinkt/openvpn/core/Connection.java | 9 +- .../openvpn/core/OpenVpnManagementThread.java | 40 +- .../openvpn/fragments/ConnectionsAdapter.java | 61 ++- main/src/main/res/layout/server_card.xml | 583 +++++++++++---------- main/src/main/res/values/strings.xml | 2 +- .../de/blinkt/openvpn/core/TestConfigParser.java | 16 +- 8 files changed, 437 insertions(+), 299 deletions(-) diff --git a/main/build.gradle b/main/build.gradle index cf677e5d..eb4566d8 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -19,10 +19,10 @@ repositories { } dependencies { - implementation 'com.android.support.constraint:constraint-layout:1.0.2' - implementation 'com.android.support:support-annotations:27.1.0' - implementation 'com.android.support:cardview-v7:27.1.0' - implementation 'com.android.support:recyclerview-v7:27.1.0' + implementation 'com.android.support.constraint:constraint-layout:1.1.0' + implementation 'com.android.support:support-annotations:27.1.1' + implementation 'com.android.support:cardview-v7:27.1.1' + implementation 'com.android.support:recyclerview-v7:27.1.1' // compile 'ch.acra:acra:4.5.0' implementation 'com.github.PhilJay:MPAndroidChart:v3.0.2' diff --git a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java index 883edf53..10fcb12a 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java +++ b/main/src/main/java/de/blinkt/openvpn/core/ConfigParser.java @@ -332,6 +332,7 @@ public class ConfigParser { "http-proxy-option", "socks-proxy", "socks-proxy-retry", + "http-proxy-user-pass", "explicit-exit-notify", }; @@ -810,7 +811,9 @@ public class ConfigParser { conn.mProxyPort = proxy.get(2); } - + Vector httpproxyauthhttp = getOption("http-proxy-user-pass", 1, 1); + if (httpproxyauthhttp!=null) + useEmbbedHttpAuth(conn, httpproxyauthhttp.get(1)); // Parse remote config @@ -900,6 +903,16 @@ public class ConfigParser { } } + static public void useEmbbedHttpAuth(Connection c, String inlinedata) { + String data = VpnProfile.getEmbeddedContent(inlinedata); + String[] parts = data.split("\n"); + if (parts.length >= 2) { + c.mProxyAuthUser = parts[0]; + c.mProxyAuthPassword = parts[1]; + c.mUseProxyAuth = true; + } + } + private void checkIgnoreAndInvalidOptions(VpnProfile np) throws ConfigParseError { for (String option : unsupportedOptions) if (options.containsKey(option)) @@ -950,7 +963,7 @@ public class ConfigParser { if (!ignoreThisOption(optionsline)) { // Check if option had been inlined and inline again if (optionsline.size() == 2 && - ("extra-certs".equals(optionsline.get(0)) || "http-proxy-user-pass".equals(optionsline.get(0)))) { + "extra-certs".equals(optionsline.get(0))) { custom += VpnProfile.insertFileData(optionsline.get(0), optionsline.get(1)); } else { for (String arg : optionsline) diff --git a/main/src/main/java/de/blinkt/openvpn/core/Connection.java b/main/src/main/java/de/blinkt/openvpn/core/Connection.java index 88748d1d..5f24ecb4 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/Connection.java +++ b/main/src/main/java/de/blinkt/openvpn/core/Connection.java @@ -23,6 +23,10 @@ public class Connection implements Serializable, Cloneable { public String mProxyName = "proxy.example.com"; public String mProxyPort = "8080"; + public boolean mUseProxyAuth; + public String mProxyAuthUser = null; + public String mProxyAuthPassword = null; + public enum ProxyType { NONE, HTTP, @@ -53,13 +57,16 @@ public class Connection implements Serializable, Cloneable { if (isOpenVPN3 && mProxyType == ProxyType.HTTP) { cfg+=String.format(Locale.US,"http-proxy %s %s\n", mProxyName, mProxyPort); + if (mUseProxyAuth) + cfg+=String.format(Locale.US, "\n%s\n%s\n\n", mProxyAuthUser, mProxyAuthPassword); } - if (!TextUtils.isEmpty(mCustomConfiguration) && mUseCustomConfig) { cfg += mCustomConfiguration; cfg += "\n"; } + + return cfg; } diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java index c46fe727..e711d8ff 100644 --- a/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java +++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVpnManagementThread.java @@ -60,7 +60,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { private Runnable orbotStatusTimeOutRunnable = new Runnable() { @Override public void run() { - sendProxyCMD(Connection.ProxyType.SOCKS5, "127.0.0.1", Integer.toString(OrbotHelper.SOCKS_PROXY_PORT_DEFAULT)); + sendProxyCMD(Connection.ProxyType.SOCKS5, "127.0.0.1", Integer.toString(OrbotHelper.SOCKS_PROXY_PORT_DEFAULT), false); OrbotHelper.get(mOpenVPNService).removeStatusCallback(statusCallback); } @@ -86,7 +86,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { @Override public void onOrbotReady(Intent intent, String socksHost, int socksPort) { mResumeHandler.removeCallbacks(orbotStatusTimeOutRunnable); - sendProxyCMD(Connection.ProxyType.SOCKS5, socksHost, Integer.toString(socksPort)); + sendProxyCMD(Connection.ProxyType.SOCKS5, socksHost, Integer.toString(socksPort), false); OrbotHelper.get(mOpenVPNService).removeStatusCallback(this); } @@ -95,6 +95,7 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { VpnStatus.logWarning("Orbot integration for external applications is disabled. Waiting %ds before connecting to the default port. Enable external app integration in Orbot or use Socks v5 config instead of Orbot to avoid this delay."); } }; + private transient Connection mCurrentProxyConnection; public OpenVpnManagementThread(VpnProfile profile, OpenVPNService openVpnService) { mProfile = profile; @@ -428,14 +429,20 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { int connectionEntryNumber = Integer.parseInt(args[0]) - 1; String proxyport = null; String proxyname = null; + boolean proxyUseAuth = false; if (mProfile.mConnections.length > connectionEntryNumber) { Connection connection = mProfile.mConnections[connectionEntryNumber]; proxyType = connection.mProxyType; proxyname = connection.mProxyName; proxyport = connection.mProxyPort; + proxyUseAuth = connection.mUseProxyAuth; + + // Use transient variable to remember http user/password + mCurrentProxyConnection = connection; + } else { - VpnStatus.logError(String.format(Locale.ENGLISH, "OpenVPN is asking for a proxy of an unknonwn connection entry (%d)", connectionEntryNumber)); + VpnStatus.logError(String.format(Locale.ENGLISH, "OpenVPN is asking for a proxy of an unknown connection entry (%d)", connectionEntryNumber)); } // atuo detection of proxy @@ -446,6 +453,8 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { proxyType = Connection.ProxyType.HTTP; proxyname = isa.getHostName(); proxyport = String.valueOf(isa.getPort()); + proxyUseAuth = false; + } } @@ -471,18 +480,20 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { orbotHelper.sendOrbotStartAndStatusBroadcast(); } else { - sendProxyCMD(proxyType, proxyname, proxyport); + sendProxyCMD(proxyType, proxyname, proxyport, proxyUseAuth); } } - private void sendProxyCMD(Connection.ProxyType proxyType, String proxyname, String proxyport) { + private void sendProxyCMD(Connection.ProxyType proxyType, String proxyname, String proxyport, boolean usePwAuth) { if (proxyType != Connection.ProxyType.NONE && proxyname != null) { VpnStatus.logInfo(R.string.using_proxy, proxyname, proxyname); - String proxycmd = String.format(Locale.ENGLISH, "proxy %s %s %s\n", + String pwstr = usePwAuth ? " auto" : ""; + + String proxycmd = String.format(Locale.ENGLISH, "proxy %s %s %s%s\n", proxyType == Connection.ProxyType.HTTP ? "HTTP" : "SOCKS", - proxyname, proxyport); + proxyname, proxyport, pwstr); managmentCommand(proxycmd); } else { managmentCommand("proxy NONE\n"); @@ -653,17 +664,26 @@ public class OpenVpnManagementThread implements Runnable, OpenVPNManagement { } String pw = null; + String username = null; if (needed.equals("Private Key")) { pw = mProfile.getPasswordPrivateKey(); } else if (needed.equals("Auth")) { pw = mProfile.getPasswordAuth(); + username = mProfile.mUsername; - String usercmd = String.format("username '%s' %s\n", - needed, VpnProfile.openVpnEscape(mProfile.mUsername)); - managmentCommand(usercmd); + } else if (needed.equals("HTTP Proxy")) { + if( mCurrentProxyConnection != null) { + pw = mCurrentProxyConnection.mProxyAuthPassword; + username = mCurrentProxyConnection.mProxyAuthUser; + } } if (pw != null) { + if (username !=null) { + String usercmd = String.format("username '%s' %s\n", + needed, VpnProfile.openVpnEscape(username)); + managmentCommand(usercmd); + } String cmd = String.format("password '%s' %s\n", needed, VpnProfile.openVpnEscape(pw)); managmentCommand(cmd); } else { diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/ConnectionsAdapter.java b/main/src/main/java/de/blinkt/openvpn/fragments/ConnectionsAdapter.java index bb930eaf..9c4c80de 100644 --- a/main/src/main/java/de/blinkt/openvpn/fragments/ConnectionsAdapter.java +++ b/main/src/main/java/de/blinkt/openvpn/fragments/ConnectionsAdapter.java @@ -13,12 +13,7 @@ import android.text.TextWatcher; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.CheckBox; -import android.widget.EditText; -import android.widget.ImageButton; -import android.widget.RadioGroup; -import android.widget.SeekBar; -import android.widget.Switch; +import android.widget.*; import java.util.Arrays; @@ -97,6 +92,10 @@ public class ConnectionsAdapter extends RecyclerView.Adapter { + mRemoteSwitch.setOnCheckedChangeListener((CompoundButton buttonView, boolean isChecked) -> { if (mConnection != null) { mConnection.mEnabled = isChecked; mConnectionsAdapter.displayWarningIfNoneEnabled(); @@ -251,6 +263,14 @@ public class ConnectionsAdapter extends RecyclerView.Adapter + { + if (mConnection != null) { + mConnection.mUseProxyAuth = isChecked; + setVisibilityProxyServer(this, mConnection); + } + }); + mCustomOptionText.addTextChangedListener(new OnTextChangedWatcher() { @Override public void afterTextChanged(Editable s) { @@ -305,6 +325,33 @@ public class ConnectionsAdapter extends RecyclerView.Adapter { + if (mConnection != null) { + mConnection.mUseProxyAuth = isChecked; + } + }); + + mProxyAuthPassword.addTextChangedListener(new OnTextChangedWatcher() { + @Override + public void afterTextChanged(Editable s) { + if (mConnection != null) { + mConnection.mProxyAuthPassword = s.toString(); + } + } + }); + + + mProxyAuthUser.addTextChangedListener(new OnTextChangedWatcher() { + @Override + public void afterTextChanged(Editable s) { + if (mConnection != null) { + mConnection.mProxyAuthUser = s.toString(); + } + } + }); + + + mCustomOptionText.addTextChangedListener(new OnTextChangedWatcher() { @Override public void afterTextChanged(Editable s) { diff --git a/main/src/main/res/layout/server_card.xml b/main/src/main/res/layout/server_card.xml index 40655d1c..5d13cff0 100644 --- a/main/src/main/res/layout/server_card.xml +++ b/main/src/main/res/layout/server_card.xml @@ -4,356 +4,397 @@ --> + xmlns:card_view="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_margin="@dimen/stdpadding"> + android:id="@+id/card_view" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + card_view:cardCornerRadius="10dp"> + xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:animateLayoutChanges="true" + android:padding="5dp"> + android:id="@+id/port_label" + style="@style/item" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:text="@string/port" + android:textAppearance="?android:attr/textAppearanceSmall"/> + android:layout_toLeftOf="@id/port_label" + android:layout_toStartOf="@id/port_label" + android:id="@+id/server_label" + style="@style/item" + android:text="@string/address" + android:textAppearance="?android:attr/textAppearanceSmall"/> + android:layout_below="@id/port_label" + android:inputType="numberDecimal" + android:text="1194" + android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium" + tools:ignore="HardcodedText"/> + android:id="@+id/servername" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_below="@id/server_label" + android:layout_toLeftOf="@id/portnumber" + android:layout_toStartOf="@id/portnumber" + android:inputType="textUri" + android:singleLine="true" + tools:text="openvpn.blinkt.de" + android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium" + tools:ignore="HardcodedText"/> + android:id="@+id/protocol" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/servername" + android:paddingTop="10dp" + android:text="@string/protocol"/> - - + android:layout_below="@id/protocol" + android:orientation="horizontal" + android:paddingStart="20dp" + android:paddingEnd="20dp" + android:paddingRight="20dp" + android:paddingLeft="20dp"> + + + android:layout_width="20dp" + android:layout_height="wrap_content"/> + android:id="@+id/tcp_proto" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="TCP" + tools:ignore="HardcodedText"/> - - - - + android:id="@+id/proxy_label" + android:layout_below="@id/udptcpradiogroup" + android:text="@string/proxy" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + /> - + android:layout_below="@id/proxy_label" + android:orientation="horizontal" + android:paddingStart="20dp" + android:paddingEnd="20dp" + android:paddingRight="20dp" + android:paddingLeft="20dp"> + + android:layout_width="10dp" + android:layout_height="wrap_content"/> + android:id="@+id/proxy_http" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="HTTP" + tools:ignore="HardcodedText"/> + android:layout_width="10dp" + android:layout_height="wrap_content"/> + android:id="@+id/proxy_socks" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Socksv5" + tools:ignore="HardcodedText"/> + android:layout_width="10dp" + android:layout_height="wrap_content"/> + android:id="@+id/proxy_orbot" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/tor_orbot" + /> + + android:id="@+id/proxyport_label" + style="@style/item" + android:layout_below="@id/proxyradiogroup" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentEnd="true" + android:layout_alignParentRight="true" + android:text="@string/port" + android:textAppearance="?android:attr/textAppearanceSmall"/> + android:layout_below="@id/proxyradiogroup" + android:layout_toLeftOf="@id/proxyport_label" + android:layout_toStartOf="@id/proxyport_label" + android:id="@+id/proxyserver_label" + android:paddingStart="20dp" + android:paddingLeft="20dp" + style="@style/item" + android:text="@string/address" + android:textAppearance="?android:attr/textAppearanceSmall" + tools:ignore="RtlSymmetry"/> + android:layout_below="@id/proxyport_label" + android:inputType="numberDecimal" + android:text="8080" + android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium" + tools:ignore="HardcodedText"/> + android:id="@+id/proxyname" + android:layout_marginLeft="20dp" + android:layout_marginStart="20dp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentLeft="true" + android:layout_alignParentStart="true" + android:layout_below="@id/proxyserver_label" + android:layout_toLeftOf="@id/proxyport" + android:layout_toStartOf="@id/proxyport" + android:inputType="textUri" + android:singleLine="true" + tools:text="proxy.blinkt.de" + android:textAppearance="@android:style/TextAppearance.DeviceDefault.Medium" + tools:ignore="HardcodedText"/> + + + + + + + + + + + + + + + + + + + android:id="@+id/connect_timeout_label" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/proxyauthlayout" + android:paddingTop="10dp" + android:text="@string/connect_timeout"/> + android:id="@+id/connect_timeout" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentRight="true" + android:layout_alignParentEnd="true" + android:layout_below="@+id/connect_timeout_label" + android:ems="3" + tools:text="232" + android:gravity="end" + android:inputType="numberDecimal"/> - + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:id="@+id/connect_silder" + android:max="300" + android:layout_alignBottom="@id/connect_timeout" + android:layout_toLeftOf="@id/connect_timeout" + android:layout_toStartOf="@id/connect_timeout" + android:layout_below="@id/connect_timeout_label"/> + android:id="@+id/use_customoptions" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/connect_timeout" + android:paddingTop="10dp" + android:text="@string/custom_connection_options"/> - - + android:layout_below="@id/use_customoptions" + android:orientation="vertical" + android:paddingRight="10dp" + android:paddingEnd="10dp" + android:paddingLeft="10dp" + android:paddingStart="10dp"> + + + android:id="@+id/customoptions" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:inputType="textMultiLine" + android:lines="5"/> + android:id="@+id/remoteSwitch" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignEnd="@+id/portnumber" + android:layout_alignRight="@+id/portnumber" + android:layout_below="@+id/portnumber" + android:layout_gravity="right|bottom" + android:text="@string/enabled_connection_entry"/> + android:id="@+id/remove_connection" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignEnd="@+id/portnumber" + android:layout_alignRight="@+id/portnumber" + android:layout_below="@id/remoteSwitch" + android:layout_margin="12dp" + android:background="@drawable/ic_menu_delete_grey" + android:contentDescription="@string/remove_connection_entry" + android:padding="12dp"/> diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml index de436325..2426e501 100755 --- a/main/src/main/res/values/strings.xml +++ b/main/src/main/res/values/strings.xml @@ -473,5 +473,5 @@ Orbot application cannot be found. Please install Orbot or use manual Socks v5 integration. Remote API OpenVPN for Android supports two remote APIs, a sophisticated API using AIDL (remoteEXample in the git repository) and a simple one using Intents. <p>Examples using adb shell and the intents. Replace profilname with your profile name<p><p> adb shell am start-activity -a android.intent.action.MAIN de.blinkt.openvpn/.api.DisconnectVPN<p> adb shell am start-activity -a android.intent.action.MAIN -e de.blinkt.openvpn.api.profileName Blinkt de.blinkt.openvpn/.api.ConnectVPN - + Enable Proxy Authentication diff --git a/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.java b/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.java index 6615a8f0..3e5c9895 100644 --- a/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.java +++ b/main/src/test/java/de/blinkt/openvpn/core/TestConfigParser.java @@ -40,7 +40,8 @@ public class TestConfigParser { ConfigParser cp = new ConfigParser(); cp.parseConfig(new StringReader(miniconfig + httpproxypass)); VpnProfile p = cp.convertProfile(); - Assert.assertTrue(p.mCustomConfigOptions.contains(httpproxypass)); + Assert.assertFalse(p.mCustomConfigOptions.contains(httpproxypass)); + } @@ -124,9 +125,18 @@ public class TestConfigParser { ConfigParser cp = new ConfigParser(); cp.parseConfig(new StringReader(proxy)); VpnProfile vp = cp.convertProfile(); - String config = vp.getConfigFile(RuntimeEnvironment.application, false); + String config = vp.getConfigFile(RuntimeEnvironment.application, true); Assert.assertTrue(config.contains("username12")); Assert.assertTrue(config.contains("http-proxy 1.2.3.4")); - } + + config = vp.getConfigFile(RuntimeEnvironment.application, false); + + Assert.assertFalse(config.contains("username12")); + Assert.assertFalse(config.contains("http-proxy 1.2.3.4")); + + Assert.assertTrue(vp.mConnections[0].mUseProxyAuth); + Assert.assertEquals(vp.mConnections[0].mProxyAuthUser, "username12"); + Assert.assertEquals(vp.mConnections[0].mProxyAuthPassword, "password34"); + } } -- cgit v1.2.3