Merge branch 'smooth-tor' into 'master'

Smooth Tor setup

This is a big reworking of the Tor support to make it really easy to setup, and to make .onion addresses work automatically even when "Use Tor" is off.

I needed to make the NetCipher v1.2.1 release to support this, hence it was originally WIP:.

See merge request !216
This commit is contained in:
Daniel Martí 2016-02-27 12:42:20 +00:00
commit 4ec9cee143
17 changed files with 149 additions and 59 deletions

View File

@ -1,3 +1,8 @@
* add simple "Use Tor" preference
* Enable TLS v1.2 for officials repo on all devices that support it
### 0.98.1 (2016-02-14) ### 0.98.1 (2016-02-14)
* Fix crash when entering only a space into the search dialog * Fix crash when entering only a space into the search dialog

View File

@ -17,6 +17,7 @@ dependencies {
compile 'com.google.zxing:core:3.2.1' compile 'com.google.zxing:core:3.2.1'
compile 'eu.chainfire:libsuperuser:1.0.0.201602011018' compile 'eu.chainfire:libsuperuser:1.0.0.201602011018'
compile 'cc.mvdan.accesspoint:library:0.1.3' compile 'cc.mvdan.accesspoint:library:0.1.3'
compile 'info.guardianproject.netcipher:netcipher:1.2.1'
compile 'commons-net:commons-net:3.4' compile 'commons-net:commons-net:3.4'
compile 'org.openhab.jmdns:jmdns:3.4.2' compile 'org.openhab.jmdns:jmdns:3.4.2'
compile('ch.acra:acra:4.8.2') { compile('ch.acra:acra:4.8.2') {
@ -71,6 +72,7 @@ if (!hasProperty('sourceDeps')) {
'eu.chainfire:libsuperuser:952c5fc82f9c31d31d2b6a7054ee267dac1685fb037a254888c73c48de661eaf', 'eu.chainfire:libsuperuser:952c5fc82f9c31d31d2b6a7054ee267dac1685fb037a254888c73c48de661eaf',
'cc.mvdan.accesspoint:library:dc89a085d6bc40381078b8dd7776b12bde0dbaf8ffbcddb17ec4ebc3edecc7ba', 'cc.mvdan.accesspoint:library:dc89a085d6bc40381078b8dd7776b12bde0dbaf8ffbcddb17ec4ebc3edecc7ba',
'commons-net:commons-net:38cf2eca826b8bcdb236fc1f2e79e0c6dd8e7e0f5c44a3b8e839a1065b2fbe2e', 'commons-net:commons-net:38cf2eca826b8bcdb236fc1f2e79e0c6dd8e7e0f5c44a3b8e839a1065b2fbe2e',
'info.guardianproject.netcipher:netcipher:611ec5bde9d799fd57e1efec5c375f9f460de2cdda98918541decc9a7d02f2ad',
'org.openhab.jmdns:jmdns:7a4b34b5606bbd2aff7fdfe629edcb0416fccd367fb59a099f210b9aba4f0bce', 'org.openhab.jmdns:jmdns:7a4b34b5606bbd2aff7fdfe629edcb0416fccd367fb59a099f210b9aba4f0bce',
'com.madgag.spongycastle:pkix:6aba9b2210907a3d46dd3dcac782bb3424185290468d102d5207ebdc9796a905', 'com.madgag.spongycastle:pkix:6aba9b2210907a3d46dd3dcac782bb3424185290468d102d5207ebdc9796a905',
'com.madgag.spongycastle:prov:029f26cd6b67c06ffa05702d426d472c141789001bcb15b7262ed86c868e5643', 'com.madgag.spongycastle:prov:029f26cd6b67c06ffa05702d426d472c141789001bcb15b7262ed86c868e5643',

View File

@ -10,6 +10,10 @@
-dontnote android.support.** -dontnote android.support.**
-dontnote **ILicensingService -dontnote **ILicensingService
# StrongHttpsClient and its support classes are totally unused, so the
# ch.boye.httpclientandroidlib.** classes are also unneeded
-dontwarn info.guardianproject.netcipher.client.**
# These libraries are known to break if minification is enabled on them. They # These libraries are known to break if minification is enabled on them. They
# use reflection to instantiate classes, for example. If the keep flags are # use reflection to instantiate classes, for example. If the keep flags are
# removed, proguard will strip classes which are required, which may result in # removed, proguard will strip classes which are required, which may result in

View File

@ -166,6 +166,8 @@
<string name="next">Next</string> <string name="next">Next</string>
<string name="skip">Skip</string> <string name="skip">Skip</string>
<string name="useTor">Use Tor</string>
<string name="useTorSummary">Force download traffic through Tor for increased privacy</string>
<string name="proxy">Proxy</string> <string name="proxy">Proxy</string>
<string name="enable_proxy_title">Enable HTTP Proxy</string> <string name="enable_proxy_title">Enable HTTP Proxy</string>
<string name="enable_proxy_summary">Configure HTTP Proxy for all network requests</string> <string name="enable_proxy_summary">Configure HTTP Proxy for all network requests</string>

View File

@ -48,6 +48,10 @@
android:title="@string/local_repo_name" /> android:title="@string/local_repo_name" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/proxy" > <PreferenceCategory android:title="@string/proxy" >
<CheckBoxPreference
android:key="useTor"
android:summary="@string/useTorSummary"
android:title="@string/useTor" />
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:key="enableProxy" android:key="enableProxy"

View File

@ -28,6 +28,7 @@ import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
@ -137,6 +138,7 @@ public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextL
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
FDroidApp.checkStartTor(this);
// AppDetails and RepoDetailsActivity set different NFC actions, so reset here // AppDetails and RepoDetailsActivity set different NFC actions, so reset here
NfcHelper.setAndroidBeam(this, getApplication().getPackageName()); NfcHelper.setAndroidBeam(this, getApplication().getPackageName());
checkForAddRepoIntent(getIntent()); checkForAddRepoIntent(getIntent());
@ -289,6 +291,10 @@ public class FDroid extends AppCompatActivity implements SearchView.OnQueryTextL
MenuItem btItem = menu.findItem(R.id.action_bluetooth_apk); MenuItem btItem = menu.findItem(R.id.action_bluetooth_apk);
btItem.setVisible(false); btItem.setVisible(false);
} }
if (Build.VERSION.SDK_INT < 10) {
MenuItem menuItem = menu.findItem(R.id.action_swap);
menuItem.setVisible(false);
}
SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE);
searchMenuItem = menu.findItem(R.id.action_search); searchMenuItem = menu.findItem(R.id.action_search);

View File

@ -63,6 +63,8 @@ import java.net.URLStreamHandlerFactory;
import java.security.Security; import java.security.Security;
import java.util.Locale; import java.util.Locale;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
import sun.net.www.protocol.bluetooth.Handler; import sun.net.www.protocol.bluetooth.Handler;
@ReportsCrashes(mailTo = "reports@f-droid.org", @ReportsCrashes(mailTo = "reports@f-droid.org",
@ -296,6 +298,8 @@ public class FDroidApp extends Application {
startService(new Intent(FDroidApp.this, WifiStateChangeService.class)); startService(new Intent(FDroidApp.this, WifiStateChangeService.class));
} }
}); });
configureTor(Preferences.get().isTorEnabled());
} }
@TargetApi(18) @TargetApi(18)
@ -352,4 +356,28 @@ public class FDroidApp extends Application {
} }
} }
} }
private static boolean useTor;
/**
* Set the proxy settings based on whether Tor should be enabled or not.
*/
public static void configureTor(boolean enabled) {
useTor = enabled;
if (useTor) {
NetCipher.useTor();
} else {
NetCipher.clearProxy();
}
}
public static void checkStartTor(Context context) {
if (useTor) {
OrbotHelper.requestStartTor(context);
}
}
public static boolean isUsingTor() {
return useTor;
}
} }

View File

@ -25,9 +25,11 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
private static final String TAG = "Preferences"; private static final String TAG = "Preferences";
private final Context context;
private final SharedPreferences preferences; private final SharedPreferences preferences;
private Preferences(Context context) { private Preferences(Context context) {
this.context = context;
preferences = PreferenceManager.getDefaultSharedPreferences(context); preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.registerOnSharedPreferenceChangeListener(this); preferences.registerOnSharedPreferenceChangeListener(this);
if (preferences.getString(PREF_LOCAL_REPO_NAME, null) == null) { if (preferences.getString(PREF_LOCAL_REPO_NAME, null) == null) {
@ -54,6 +56,7 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
public static final String PREF_LOCAL_REPO_NAME = "localRepoName"; public static final String PREF_LOCAL_REPO_NAME = "localRepoName";
public static final String PREF_LOCAL_REPO_HTTPS = "localRepoHttps"; public static final String PREF_LOCAL_REPO_HTTPS = "localRepoHttps";
public static final String PREF_LANGUAGE = "language"; public static final String PREF_LANGUAGE = "language";
public static final String PREF_USE_TOR = "useTor";
public static final String PREF_ENABLE_PROXY = "enableProxy"; public static final String PREF_ENABLE_PROXY = "enableProxy";
public static final String PREF_PROXY_HOST = "proxyHost"; public static final String PREF_PROXY_HOST = "proxyHost";
public static final String PREF_PROXY_PORT = "proxyPort"; public static final String PREF_PROXY_PORT = "proxyPort";
@ -161,6 +164,16 @@ public final class Preferences implements SharedPreferences.OnSharedPreferenceCh
return preferences.getString(PREF_LOCAL_REPO_NAME, getDefaultLocalRepoName()); return preferences.getString(PREF_LOCAL_REPO_NAME, getDefaultLocalRepoName());
} }
/**
* This preference's default is set dynamically based on whether Orbot is
* installed. If Orbot is installed, default to using Tor, the user can still override
*/
public boolean isTorEnabled() {
// TODO enable once Orbot can auto-start after first install
//return preferences.getBoolean(PREF_USE_TOR, OrbotHelper.requestStartTor(context));
return preferences.getBoolean(PREF_USE_TOR, false);
}
public boolean isProxyEnabled() { public boolean isProxyEnabled() {
return preferences.getBoolean(PREF_ENABLE_PROXY, DEFAULT_ENABLE_PROXY); return preferences.getBoolean(PREF_ENABLE_PROXY, DEFAULT_ENABLE_PROXY);
} }

View File

@ -2,6 +2,7 @@ package org.fdroid.fdroid.localrepo.type;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
@ -70,12 +71,13 @@ public abstract class SwapType {
} }
public void startInBackground() { public void startInBackground() {
new Thread() { new AsyncTask<Void, Void, Void>() {
@Override @Override
public void run() { protected Void doInBackground(Void... params) {
SwapType.this.start(); start();
return null;
} }
}.start(); }.execute();
} }
private void ensureRunning() { private void ensureRunning() {
@ -85,21 +87,23 @@ public abstract class SwapType {
} }
public void ensureRunningInBackground() { public void ensureRunningInBackground() {
new Thread() { new AsyncTask<Void, Void, Void>() {
@Override @Override
public void run() { protected Void doInBackground(Void... params) {
ensureRunning(); ensureRunning();
return null;
} }
}.start(); }.execute();
} }
public void stopInBackground() { public void stopInBackground() {
new Thread() { new AsyncTask<Void, Void, Void>() {
@Override @Override
public void run() { protected Void doInBackground(Void... params) {
SwapType.this.stop(); stop();
return null;
} }
}.start(); }.execute();
} }
} }

View File

@ -55,9 +55,6 @@ public class DownloaderFactory {
String macAddress = url.getHost().replace("-", ":"); String macAddress = url.getHost().replace("-", ":");
return new BluetoothDownloader(context, macAddress, url, destFile); return new BluetoothDownloader(context, macAddress, url, destFile);
} }
if (isOnionAddress(url)) {
return new TorHttpDownloader(context, url, destFile);
}
if (isLocalFile(url)) { if (isLocalFile(url)) {
return new LocalFileDownloader(context, url, destFile); return new LocalFileDownloader(context, url, destFile);
} }

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid.net; package org.fdroid.fdroid.net;
import android.content.Context; import android.content.Context;
import android.text.TextUtils;
import android.util.Log; import android.util.Log;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader; import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
@ -22,8 +23,11 @@ import java.net.Proxy;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.net.URL; import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLHandshakeException;
import info.guardianproject.netcipher.NetCipher;
public class HttpDownloader extends Downloader { public class HttpDownloader extends Downloader {
private static final String TAG = "HttpDownloader"; private static final String TAG = "HttpDownloader";
@ -52,6 +56,7 @@ public class HttpDownloader extends Downloader {
* checking out that follows redirects up to a certain point. I guess though the correct way * checking out that follows redirects up to a certain point. I guess though the correct way
* is probably to check for a loop (keep a list of all URLs redirected to and if you hit the * is probably to check for a loop (keep a list of all URLs redirected to and if you hit the
* same one twice, bail with an exception). * same one twice, bail with an exception).
*
* @throws IOException * @throws IOException
*/ */
@Override @Override
@ -92,19 +97,31 @@ public class HttpDownloader extends Downloader {
if (connection != null) { if (connection != null) {
return; return;
} }
Preferences prefs = Preferences.get(); if (isSwapUrl()) {
if (prefs.isProxyEnabled() && !isSwapUrl()) { // swap never works with a proxy, its unrouted IP on the same subnet
SocketAddress sa = new InetSocketAddress(prefs.getProxyHost(), prefs.getProxyPort());
Proxy proxy = new Proxy(Proxy.Type.HTTP, sa);
connection = (HttpURLConnection) sourceUrl.openConnection(proxy);
} else {
connection = (HttpURLConnection) sourceUrl.openConnection(); connection = (HttpURLConnection) sourceUrl.openConnection();
} else {
Preferences prefs = Preferences.get();
if (prefs.isProxyEnabled()) {
// if "Use Tor" is set, NetCipher will ignore these proxy settings
SocketAddress sa = new InetSocketAddress(prefs.getProxyHost(), prefs.getProxyPort());
NetCipher.setProxy(new Proxy(Proxy.Type.HTTP, sa));
}
connection = NetCipher.getHttpURLConnection(sourceUrl);
}
// workaround until NetCipher supports HTTPS SNI
// https://gitlab.com/fdroid/fdroidclient/issues/431
if (connection instanceof HttpsURLConnection
&& !TextUtils.equals(sourceUrl.getHost(), "f-droid.org")
&& !TextUtils.equals(sourceUrl.getHost(), "guardianproject.info")) {
((HttpsURLConnection) connection).setSSLSocketFactory(HttpsURLConnection.getDefaultSSLSocketFactory());
}
if (credentials != null) { if (credentials != null) {
credentials.authenticate(connection); credentials.authenticate(connection);
} }
} }
}
protected void doDownload() throws IOException, InterruptedException { protected void doDownload() throws IOException, InterruptedException {
if (wantToCheckCache()) { if (wantToCheckCache()) {

View File

@ -1,28 +0,0 @@
package org.fdroid.fdroid.net;
import android.content.Context;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URL;
public class TorHttpDownloader extends HttpDownloader {
TorHttpDownloader(Context context, URL url, File destFile)
throws FileNotFoundException, MalformedURLException {
super(context, url, destFile);
}
@Override
protected void setupConnection() throws IOException {
SocketAddress sa = new InetSocketAddress("127.0.0.1", 8118);
Proxy tor = new Proxy(Proxy.Type.HTTP, sa);
connection = (HttpURLConnection) sourceUrl.openConnection(tor);
}
}

View File

@ -87,13 +87,12 @@ public class WifiStateChangeService extends Service {
wifiInfo = wifiManager.getConnectionInfo(); wifiInfo = wifiManager.getConnectionInfo();
FDroidApp.ipAddressString = formatIpAddress(wifiInfo.getIpAddress()); FDroidApp.ipAddressString = formatIpAddress(wifiInfo.getIpAddress());
DhcpInfo dhcpInfo = wifiManager.getDhcpInfo(); DhcpInfo dhcpInfo = wifiManager.getDhcpInfo();
if (dhcpInfo == null) { if (dhcpInfo != null) {
return null;
}
String netmask = formatIpAddress(dhcpInfo.netmask); String netmask = formatIpAddress(dhcpInfo.netmask);
if (!TextUtils.isEmpty(FDroidApp.ipAddressString) && netmask != null) { if (!TextUtils.isEmpty(FDroidApp.ipAddressString) && netmask != null) {
FDroidApp.subnetInfo = new SubnetUtils(FDroidApp.ipAddressString, netmask).getInfo(); FDroidApp.subnetInfo = new SubnetUtils(FDroidApp.ipAddressString, netmask).getInfo();
} }
}
} else if (wifiState == WifiManager.WIFI_STATE_DISABLED } else if (wifiState == WifiManager.WIFI_STATE_DISABLED
|| wifiState == WifiManager.WIFI_STATE_DISABLING) { || wifiState == WifiManager.WIFI_STATE_DISABLING) {
// try once to see if its a hotspot // try once to see if its a hotspot

View File

@ -1,5 +1,6 @@
package org.fdroid.fdroid.net.bluetooth; package org.fdroid.fdroid.net.bluetooth;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
@ -21,6 +22,7 @@ public class BluetoothClient {
device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress); device = BluetoothAdapter.getDefaultAdapter().getRemoteDevice(macAddress);
} }
@TargetApi(10)
public BluetoothConnection openConnection() throws IOException { public BluetoothConnection openConnection() throws IOException {
BluetoothSocket socket = null; BluetoothSocket socket = null;

View File

@ -1,5 +1,6 @@
package org.fdroid.fdroid.net.bluetooth; package org.fdroid.fdroid.net.bluetooth;
import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothServerSocket; import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
@ -61,6 +62,7 @@ public class BluetoothServer extends Thread {
} }
} }
@TargetApi(10)
@Override @Override
public void run() { public void run() {

View File

@ -127,6 +127,7 @@ public class ManageReposActivity extends ActionBarActivity {
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();
FDroidApp.checkStartTor(this);
/* let's see if someone is trying to send us a new repo */ /* let's see if someone is trying to send us a new repo */
addRepoFromIntent(getIntent()); addRepoFromIntent(getIntent());

View File

@ -21,6 +21,9 @@ import org.fdroid.fdroid.PreferencesActivity;
import org.fdroid.fdroid.R; import org.fdroid.fdroid.R;
import org.fdroid.fdroid.installer.PrivilegedInstaller; import org.fdroid.fdroid.installer.PrivilegedInstaller;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
public class PreferencesFragment extends PreferenceFragment public class PreferencesFragment extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener { implements SharedPreferences.OnSharedPreferenceChangeListener {
@ -43,10 +46,16 @@ public class PreferencesFragment extends PreferenceFragment
Preferences.PREF_PROXY_PORT, Preferences.PREF_PROXY_PORT,
}; };
private static final int REQUEST_INSTALL_ORBOT = 0x1234;
private CheckBoxPreference enableProxyCheckPref;
private CheckBoxPreference useTorCheckPref;
@Override @Override
public void onCreate(Bundle savedInstanceState) { public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences); addPreferencesFromResource(R.xml.preferences);
useTorCheckPref = (CheckBoxPreference) findPreference(Preferences.PREF_USE_TOR);
enableProxyCheckPref = (CheckBoxPreference) findPreference(Preferences.PREF_ENABLE_PROXY);
} }
private void checkSummary(String key, int resId) { private void checkSummary(String key, int resId) {
@ -279,6 +288,29 @@ public class PreferencesFragment extends PreferenceFragment
initPrivilegedInstallerPreference(); initPrivilegedInstallerPreference();
initManagePrivilegedAppPreference(); initManagePrivilegedAppPreference();
// this pref's default is dynamically set based on whether Orbot is installed
boolean useTor = Preferences.get().isTorEnabled();
useTorCheckPref.setDefaultValue(useTor);
useTorCheckPref.setChecked(useTor);
useTorCheckPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(Preference preference, Object enabled) {
if ((Boolean) enabled) {
final Activity activity = getActivity();
enableProxyCheckPref.setEnabled(false);
if (OrbotHelper.isOrbotInstalled(activity)) {
NetCipher.useTor();
} else {
Intent intent = OrbotHelper.getOrbotInstallIntent(activity);
activity.startActivityForResult(intent, REQUEST_INSTALL_ORBOT);
}
} else {
enableProxyCheckPref.setEnabled(true);
NetCipher.clearProxy();
}
return true;
}
});
} }
@Override @Override