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

View File

@ -10,6 +10,10 @@
-dontnote android.support.**
-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
# use reflection to instantiate classes, for example. If the keep flags are
# 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="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="enable_proxy_title">Enable HTTP Proxy</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" />
</PreferenceCategory>
<PreferenceCategory android:title="@string/proxy" >
<CheckBoxPreference
android:key="useTor"
android:summary="@string/useTorSummary"
android:title="@string/useTor" />
<CheckBoxPreference
android:defaultValue="false"
android:key="enableProxy"

View File

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

View File

@ -63,6 +63,8 @@ import java.net.URLStreamHandlerFactory;
import java.security.Security;
import java.util.Locale;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
import sun.net.www.protocol.bluetooth.Handler;
@ReportsCrashes(mailTo = "reports@f-droid.org",
@ -296,6 +298,8 @@ public class FDroidApp extends Application {
startService(new Intent(FDroidApp.this, WifiStateChangeService.class));
}
});
configureTor(Preferences.get().isTorEnabled());
}
@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 final Context context;
private final SharedPreferences preferences;
private Preferences(Context context) {
this.context = context;
preferences = PreferenceManager.getDefaultSharedPreferences(context);
preferences.registerOnSharedPreferenceChangeListener(this);
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_HTTPS = "localRepoHttps";
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_PROXY_HOST = "proxyHost";
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());
}
/**
* 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() {
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.Intent;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.v4.content.LocalBroadcastManager;
@ -70,12 +71,13 @@ public abstract class SwapType {
}
public void startInBackground() {
new Thread() {
new AsyncTask<Void, Void, Void>() {
@Override
public void run() {
SwapType.this.start();
protected Void doInBackground(Void... params) {
start();
return null;
}
}.start();
}.execute();
}
private void ensureRunning() {
@ -85,21 +87,23 @@ public abstract class SwapType {
}
public void ensureRunningInBackground() {
new Thread() {
new AsyncTask<Void, Void, Void>() {
@Override
public void run() {
protected Void doInBackground(Void... params) {
ensureRunning();
return null;
}
}.start();
}.execute();
}
public void stopInBackground() {
new Thread() {
new AsyncTask<Void, Void, Void>() {
@Override
public void run() {
SwapType.this.stop();
protected Void doInBackground(Void... params) {
stop();
return null;
}
}.start();
}.execute();
}
}

View File

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

View File

@ -1,6 +1,7 @@
package org.fdroid.fdroid.net;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import com.nostra13.universalimageloader.core.download.BaseImageDownloader;
@ -22,8 +23,11 @@ import java.net.Proxy;
import java.net.SocketAddress;
import java.net.URL;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLHandshakeException;
import info.guardianproject.netcipher.NetCipher;
public class HttpDownloader extends Downloader {
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
* 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).
*
* @throws IOException
*/
@Override
@ -92,19 +97,31 @@ public class HttpDownloader extends Downloader {
if (connection != null) {
return;
}
Preferences prefs = Preferences.get();
if (prefs.isProxyEnabled() && !isSwapUrl()) {
SocketAddress sa = new InetSocketAddress(prefs.getProxyHost(), prefs.getProxyPort());
Proxy proxy = new Proxy(Proxy.Type.HTTP, sa);
connection = (HttpURLConnection) sourceUrl.openConnection(proxy);
} else {
if (isSwapUrl()) {
// swap never works with a proxy, its unrouted IP on the same subnet
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) {
credentials.authenticate(connection);
}
}
}
protected void doDownload() throws IOException, InterruptedException {
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();
FDroidApp.ipAddressString = formatIpAddress(wifiInfo.getIpAddress());
DhcpInfo dhcpInfo = wifiManager.getDhcpInfo();
if (dhcpInfo == null) {
return null;
}
if (dhcpInfo != null) {
String netmask = formatIpAddress(dhcpInfo.netmask);
if (!TextUtils.isEmpty(FDroidApp.ipAddressString) && netmask != null) {
FDroidApp.subnetInfo = new SubnetUtils(FDroidApp.ipAddressString, netmask).getInfo();
}
}
} else if (wifiState == WifiManager.WIFI_STATE_DISABLED
|| wifiState == WifiManager.WIFI_STATE_DISABLING) {
// try once to see if its a hotspot

View File

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

View File

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

View File

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

View File

@ -21,6 +21,9 @@ import org.fdroid.fdroid.PreferencesActivity;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.installer.PrivilegedInstaller;
import info.guardianproject.netcipher.NetCipher;
import info.guardianproject.netcipher.proxy.OrbotHelper;
public class PreferencesFragment extends PreferenceFragment
implements SharedPreferences.OnSharedPreferenceChangeListener {
@ -43,10 +46,16 @@ public class PreferencesFragment extends PreferenceFragment
Preferences.PREF_PROXY_PORT,
};
private static final int REQUEST_INSTALL_ORBOT = 0x1234;
private CheckBoxPreference enableProxyCheckPref;
private CheckBoxPreference useTorCheckPref;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
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) {
@ -279,6 +288,29 @@ public class PreferencesFragment extends PreferenceFragment
initPrivilegedInstallerPreference();
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