diff --git a/CHANGELOG.md b/CHANGELOG.md index e5b668f57..f67c07974 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/F-Droid/build.gradle b/F-Droid/build.gradle index 1ad545715..92cabd569 100644 --- a/F-Droid/build.gradle +++ b/F-Droid/build.gradle @@ -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', diff --git a/F-Droid/proguard-rules.pro b/F-Droid/proguard-rules.pro index 4784add4a..65c2e1e09 100644 --- a/F-Droid/proguard-rules.pro +++ b/F-Droid/proguard-rules.pro @@ -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 diff --git a/F-Droid/res/values/strings.xml b/F-Droid/res/values/strings.xml index bd55734cb..e60b30152 100644 --- a/F-Droid/res/values/strings.xml +++ b/F-Droid/res/values/strings.xml @@ -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> diff --git a/F-Droid/res/xml/preferences.xml b/F-Droid/res/xml/preferences.xml index 165b05864..43cb8b184 100644 --- a/F-Droid/res/xml/preferences.xml +++ b/F-Droid/res/xml/preferences.xml @@ -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" diff --git a/F-Droid/src/org/fdroid/fdroid/FDroid.java b/F-Droid/src/org/fdroid/fdroid/FDroid.java index 11f2cc73d..eea6abf93 100644 --- a/F-Droid/src/org/fdroid/fdroid/FDroid.java +++ b/F-Droid/src/org/fdroid/fdroid/FDroid.java @@ -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); diff --git a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java index 969d4ff56..f90207aa1 100644 --- a/F-Droid/src/org/fdroid/fdroid/FDroidApp.java +++ b/F-Droid/src/org/fdroid/fdroid/FDroidApp.java @@ -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; + } } diff --git a/F-Droid/src/org/fdroid/fdroid/Preferences.java b/F-Droid/src/org/fdroid/fdroid/Preferences.java index d97d210da..c8570e6d4 100644 --- a/F-Droid/src/org/fdroid/fdroid/Preferences.java +++ b/F-Droid/src/org/fdroid/fdroid/Preferences.java @@ -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); } diff --git a/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java b/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java index 1a2ee1a78..8e45aafc8 100644 --- a/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java +++ b/F-Droid/src/org/fdroid/fdroid/localrepo/type/SwapType.java @@ -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(); } } diff --git a/F-Droid/src/org/fdroid/fdroid/net/DownloaderFactory.java b/F-Droid/src/org/fdroid/fdroid/net/DownloaderFactory.java index b43227008..fa012d59f 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/DownloaderFactory.java +++ b/F-Droid/src/org/fdroid/fdroid/net/DownloaderFactory.java @@ -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); } diff --git a/F-Droid/src/org/fdroid/fdroid/net/HttpDownloader.java b/F-Droid/src/org/fdroid/fdroid/net/HttpDownloader.java index 9cfa0310c..aaaddea13 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/HttpDownloader.java +++ b/F-Droid/src/org/fdroid/fdroid/net/HttpDownloader.java @@ -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"; @@ -32,7 +36,7 @@ public class HttpDownloader extends Downloader { protected HttpURLConnection connection; private Credentials credentials; - private int statusCode = -1; + private int statusCode = -1; HttpDownloader(Context context, URL url, File destFile) throws FileNotFoundException, MalformedURLException { @@ -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,17 +97,29 @@ 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(); - if (credentials != null) { - credentials.authenticate(connection); + } 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); } } @@ -159,4 +176,4 @@ public class HttpDownloader extends Downloader { public void close() { connection.disconnect(); } -} \ No newline at end of file +} diff --git a/F-Droid/src/org/fdroid/fdroid/net/TorHttpDownloader.java b/F-Droid/src/org/fdroid/fdroid/net/TorHttpDownloader.java deleted file mode 100644 index 50bb50fa5..000000000 --- a/F-Droid/src/org/fdroid/fdroid/net/TorHttpDownloader.java +++ /dev/null @@ -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); - } -} diff --git a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java index ccf162460..8c3e3ef69 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java +++ b/F-Droid/src/org/fdroid/fdroid/net/WifiStateChangeService.java @@ -87,12 +87,11 @@ public class WifiStateChangeService extends Service { wifiInfo = wifiManager.getConnectionInfo(); FDroidApp.ipAddressString = formatIpAddress(wifiInfo.getIpAddress()); DhcpInfo dhcpInfo = wifiManager.getDhcpInfo(); - if (dhcpInfo == null) { - return null; - } - String netmask = formatIpAddress(dhcpInfo.netmask); - if (!TextUtils.isEmpty(FDroidApp.ipAddressString) && netmask != null) { - FDroidApp.subnetInfo = new SubnetUtils(FDroidApp.ipAddressString, netmask).getInfo(); + 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) { diff --git a/F-Droid/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java b/F-Droid/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java index 44ec1a0d3..ccb907c8d 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java +++ b/F-Droid/src/org/fdroid/fdroid/net/bluetooth/BluetoothClient.java @@ -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; diff --git a/F-Droid/src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java b/F-Droid/src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java index 3c39917f9..848192731 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java +++ b/F-Droid/src/org/fdroid/fdroid/net/bluetooth/BluetoothServer.java @@ -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() { diff --git a/F-Droid/src/org/fdroid/fdroid/views/ManageReposActivity.java b/F-Droid/src/org/fdroid/fdroid/views/ManageReposActivity.java index b28449847..cc081f817 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/ManageReposActivity.java +++ b/F-Droid/src/org/fdroid/fdroid/views/ManageReposActivity.java @@ -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()); diff --git a/F-Droid/src/org/fdroid/fdroid/views/fragments/PreferencesFragment.java b/F-Droid/src/org/fdroid/fdroid/views/fragments/PreferencesFragment.java index 8e15e6e2f..576aaafa7 100644 --- a/F-Droid/src/org/fdroid/fdroid/views/fragments/PreferencesFragment.java +++ b/F-Droid/src/org/fdroid/fdroid/views/fragments/PreferencesFragment.java @@ -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