support HTTPS:// for local repo in a preference

Allow the local repo to use HTTPS:// instead of HTTP://.  This is currently
default off since handling the self-signed certificate is not currently
graceful.  In the future, the SPKI that AndroidPinning uses should be
included in the repo meta data, then when someone marks a repo as trusted,
that local repo's SPKI should be added to the list of trusted keys in
AndroidPinning.

fixes #2960 https://dev.guardianproject.info/issues/2960
This commit is contained in:
Hans-Christoph Steiner 2014-05-22 19:51:00 -04:00
parent 5f2efbb72a
commit b7339e9423
8 changed files with 85 additions and 38 deletions

View File

@ -39,6 +39,9 @@
<string name="local_repo_bonjour_off">Do not advertise your local repo.</string> <string name="local_repo_bonjour_off">Do not advertise your local repo.</string>
<string name="local_repo_name">Name of your Local Repo</string> <string name="local_repo_name">Name of your Local Repo</string>
<string name="local_repo_name_summary">The advertised title of your local repo: %s</string> <string name="local_repo_name_summary">The advertised title of your local repo: %s</string>
<string name="local_repo_https">Use Private Connection</string>
<string name="local_repo_https_on">Use encrypted HTTPS:// connection for local repo</string>
<string name="local_repo_https_off">Use insecure HTTP:// connection for local repo</string>
<string name="search_results">Search Results</string> <string name="search_results">Search Results</string>
<string name="app_details">App Details</string> <string name="app_details">App Details</string>

View File

@ -51,6 +51,10 @@
<EditTextPreference <EditTextPreference
android:key="localRepoName" android:key="localRepoName"
android:title="@string/local_repo_name" /> android:title="@string/local_repo_name" />
<CheckBoxPreference
android:defaultValue="true"
android:key="localRepoHttps"
android:title="@string/local_repo_https" />
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/other"> <PreferenceCategory android:title="@string/other">
<CheckBoxPreference android:title="@string/cache_downloaded" <CheckBoxPreference android:title="@string/cache_downloaded"

View File

@ -46,6 +46,7 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
public static final String PREF_SYSTEM_INSTALLER = "systemInstaller"; public static final String PREF_SYSTEM_INSTALLER = "systemInstaller";
public static final String PREF_LOCAL_REPO_BONJOUR = "localRepoBonjour"; public static final String PREF_LOCAL_REPO_BONJOUR = "localRepoBonjour";
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";
private static final boolean DEFAULT_COMPACT_LAYOUT = false; private static final boolean DEFAULT_COMPACT_LAYOUT = false;
private static final boolean DEFAULT_ROOTED = true; private static final boolean DEFAULT_ROOTED = true;
@ -53,6 +54,7 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
private static final boolean DEFAULT_ROOT_INSTALLER = false; private static final boolean DEFAULT_ROOT_INSTALLER = false;
private static final boolean DEFAULT_SYSTEM_INSTALLER = false; private static final boolean DEFAULT_SYSTEM_INSTALLER = false;
private static final boolean DEFAULT_LOCAL_REPO_BONJOUR = true; private static final boolean DEFAULT_LOCAL_REPO_BONJOUR = true;
private static final boolean DEFAULT_LOCAL_REPO_HTTPS = false;
private boolean compactLayout = DEFAULT_COMPACT_LAYOUT; private boolean compactLayout = DEFAULT_COMPACT_LAYOUT;
private boolean filterAppsRequiringRoot = DEFAULT_ROOTED; private boolean filterAppsRequiringRoot = DEFAULT_ROOTED;
@ -64,6 +66,7 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
private List<ChangeListener> updateHistoryListeners = new ArrayList<ChangeListener>(); private List<ChangeListener> updateHistoryListeners = new ArrayList<ChangeListener>();
private List<ChangeListener> localRepoBonjourListeners = new ArrayList<ChangeListener>(); private List<ChangeListener> localRepoBonjourListeners = new ArrayList<ChangeListener>();
private List<ChangeListener> localRepoNameListeners = new ArrayList<ChangeListener>(); private List<ChangeListener> localRepoNameListeners = new ArrayList<ChangeListener>();
private List<ChangeListener> localRepoHttpsListeners = new ArrayList<ChangeListener>();
private boolean isInitialized(String key) { private boolean isInitialized(String key) {
return initialized.containsKey(key) && initialized.get(key); return initialized.containsKey(key) && initialized.get(key);
@ -89,6 +92,10 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
return preferences.getBoolean(PREF_LOCAL_REPO_BONJOUR, DEFAULT_LOCAL_REPO_BONJOUR); return preferences.getBoolean(PREF_LOCAL_REPO_BONJOUR, DEFAULT_LOCAL_REPO_BONJOUR);
} }
public boolean isLocalRepoHttpsEnabled() {
return preferences.getBoolean(PREF_LOCAL_REPO_HTTPS, DEFAULT_LOCAL_REPO_HTTPS);
}
private String getDefaultLocalRepoName() { private String getDefaultLocalRepoName() {
return (Build.BRAND + " " + Build.MODEL + String.valueOf(new Random().nextInt(9999))) return (Build.BRAND + " " + Build.MODEL + String.valueOf(new Random().nextInt(9999)))
.replaceAll(" ", "-"); .replaceAll(" ", "-");
@ -178,6 +185,10 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
for ( ChangeListener listener : localRepoNameListeners ) { for ( ChangeListener listener : localRepoNameListeners ) {
listener.onPreferenceChange(); listener.onPreferenceChange();
} }
} else if (key.equals(PREF_LOCAL_REPO_HTTPS)) {
for ( ChangeListener listener : localRepoHttpsListeners ) {
listener.onPreferenceChange();
}
} }
} }
@ -205,6 +216,14 @@ public class Preferences implements SharedPreferences.OnSharedPreferenceChangeLi
localRepoNameListeners.remove(listener); localRepoNameListeners.remove(listener);
} }
public void registerLocalRepoHttpsListeners(ChangeListener listener) {
localRepoHttpsListeners.add(listener);
}
public void unregisterLocalRepoHttpsListeners(ChangeListener listener) {
localRepoHttpsListeners.remove(listener);
}
public static interface ChangeListener { public static interface ChangeListener {
public void onPreferenceChange(); public void onPreferenceChange();
} }

View File

@ -56,6 +56,7 @@ public class PreferencesActivity extends PreferenceActivity implements
Preferences.PREF_IGN_TOUCH, Preferences.PREF_IGN_TOUCH,
Preferences.PREF_LOCAL_REPO_BONJOUR, Preferences.PREF_LOCAL_REPO_BONJOUR,
Preferences.PREF_LOCAL_REPO_NAME, Preferences.PREF_LOCAL_REPO_NAME,
Preferences.PREF_LOCAL_REPO_HTTPS,
Preferences.PREF_CACHE_APK, Preferences.PREF_CACHE_APK,
Preferences.PREF_EXPERT, Preferences.PREF_EXPERT,
Preferences.PREF_ROOT_INSTALLER, Preferences.PREF_ROOT_INSTALLER,
@ -154,6 +155,10 @@ public class PreferencesActivity extends PreferenceActivity implements
} else if (key.equals(Preferences.PREF_LOCAL_REPO_NAME)) { } else if (key.equals(Preferences.PREF_LOCAL_REPO_NAME)) {
textSummary(key, R.string.local_repo_name_summary); textSummary(key, R.string.local_repo_name_summary);
} else if (key.equals(Preferences.PREF_LOCAL_REPO_HTTPS)) {
onoffSummary(key, R.string.local_repo_https_on,
R.string.local_repo_https_off);
} else if (key.equals(Preferences.PREF_CACHE_APK)) { } else if (key.equals(Preferences.PREF_CACHE_APK)) {
onoffSummary(key, R.string.cache_downloaded_on, onoffSummary(key, R.string.cache_downloaded_on,
R.string.cache_downloaded_off); R.string.cache_downloaded_off);

View File

@ -5,15 +5,12 @@ import android.annotation.SuppressLint;
import android.app.*; import android.app.*;
import android.content.*; import android.content.*;
import android.os.*; import android.os.*;
import android.preference.PreferenceManager;
import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationCompat;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.util.Log; import android.util.Log;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.*;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.Preferences.ChangeListener; import org.fdroid.fdroid.Preferences.ChangeListener;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.net.LocalHTTPD; import org.fdroid.fdroid.net.LocalHTTPD;
import org.fdroid.fdroid.net.WifiStateChangeService; import org.fdroid.fdroid.net.WifiStateChangeService;
import org.fdroid.fdroid.views.LocalRepoActivity; import org.fdroid.fdroid.views.LocalRepoActivity;
@ -91,6 +88,23 @@ public class LocalRepoService extends Service {
} }
}; };
private ChangeListener localRepoHttpsChangeListener = new ChangeListener() {
@Override
public void onPreferenceChange() {
Log.i("localRepoHttpsChangeListener", "onPreferenceChange");
if (localHttpd.isAlive()) {
new AsyncTask<Void, Void, Void>() {
@Override
protected Void doInBackground(Void... params) {
stopNetworkServices();
startNetworkServices();
return null;
}
}.execute();
}
}
};
@Override @Override
public void onCreate() { public void onCreate() {
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
@ -137,23 +151,25 @@ public class LocalRepoService extends Service {
startWebServer(); startWebServer();
if (Preferences.get().isLocalRepoBonjourEnabled()) if (Preferences.get().isLocalRepoBonjourEnabled())
registerMDNSService(); registerMDNSService();
Preferences.get().registerLocalRepoHttpsListeners(localRepoHttpsChangeListener);
} }
private void stopNetworkServices() { private void stopNetworkServices() {
Preferences.get().unregisterLocalRepoHttpsListeners(localRepoHttpsChangeListener);
unregisterMDNSService(); unregisterMDNSService();
stopWebServer(); stopWebServer();
} }
private void startWebServer() { private void startWebServer() {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
Runnable webServer = new Runnable() { Runnable webServer = new Runnable() {
// Tell Eclipse this is not a leak because of Looper use. // Tell Eclipse this is not a leak because of Looper use.
@SuppressLint("HandlerLeak") @SuppressLint("HandlerLeak")
@Override @Override
public void run() { public void run() {
localHttpd = new LocalHTTPD(getFilesDir(), localHttpd = new LocalHTTPD(
prefs.getBoolean("use_https", false)); LocalRepoService.this,
getFilesDir(),
Preferences.get().isLocalRepoHttpsEnabled());
Looper.prepare(); // must be run before creating a Handler Looper.prepare(); // must be run before creating a Handler
webServerThreadHandler = new Handler() { webServerThreadHandler = new Handler() {
@ -200,11 +216,16 @@ public class LocalRepoService extends Service {
final HashMap<String, String> values = new HashMap<String, String>(); final HashMap<String, String> values = new HashMap<String, String>();
values.put("path", "/fdroid/repo"); values.put("path", "/fdroid/repo");
values.put("name", repoName); values.put("name", repoName);
// TODO set type based on "use HTTPS" pref
values.put("fingerprint", FDroidApp.repo.fingerprint); values.put("fingerprint", FDroidApp.repo.fingerprint);
values.put("type", "fdroidrepo"); String type;
pairService = ServiceInfo.create("_http._tcp.local.", if (Preferences.get().isLocalRepoHttpsEnabled()) {
repoName, FDroidApp.port, 0, 0, values); values.put("type", "fdroidrepos");
type = "_https._tcp.local.";
} else {
values.put("type", "fdroidrepo");
type = "_http._tcp.local.";
}
pairService = ServiceInfo.create(type, repoName, FDroidApp.port, 0, 0, values);
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override

View File

@ -1,39 +1,33 @@
package org.fdroid.fdroid.net; package org.fdroid.fdroid.net;
import android.content.Context;
import android.util.Log; import android.util.Log;
import android.webkit.MimeTypeMap; import android.webkit.MimeTypeMap;
import fi.iki.elonen.NanoHTTPD; import fi.iki.elonen.NanoHTTPD;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.localrepo.LocalRepoKeyStore;
import java.io.File; import java.io.*;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.util.Arrays; import java.util.*;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import javax.net.ssl.SSLServerSocketFactory; import javax.net.ssl.SSLServerSocketFactory;
public class LocalHTTPD extends NanoHTTPD { public class LocalHTTPD extends NanoHTTPD {
private static final String TAG = LocalHTTPD.class.getCanonicalName(); private static final String TAG = LocalHTTPD.class.getCanonicalName();
private final Context context;
private final File webRoot; private final File webRoot;
private final boolean logRequests; private final boolean logRequests;
public LocalHTTPD(File webRoot, boolean useHttps) { public LocalHTTPD(Context context, File webRoot, boolean useHttps) {
super(FDroidApp.ipAddressString, FDroidApp.port); super(FDroidApp.ipAddressString, FDroidApp.port);
this.logRequests = false; this.logRequests = false;
this.webRoot = webRoot; this.webRoot = webRoot;
this.context = context;
if (useHttps) if (useHttps)
enableHTTPS(); enableHTTPS();
} }
@ -91,7 +85,15 @@ public class LocalHTTPD extends NanoHTTPD {
} }
private void enableHTTPS() { private void enableHTTPS() {
// TODO copy implementation from Kerplapp try {
LocalRepoKeyStore localRepoKeyStore = LocalRepoKeyStore.get(context);
SSLServerSocketFactory factory = NanoHTTPD.makeSSLSocketFactory(
localRepoKeyStore.getKeyStore(),
localRepoKeyStore.getKeyManagers());
makeSecure(factory);
} catch (IOException e) {
e.printStackTrace();
}
} }
private Response respond(Map<String, String> headers, String uri) { private Response respond(Map<String, String> headers, String uri) {
@ -305,7 +307,8 @@ public class LocalHTTPD extends NanoHTTPD {
} }
for (String directory : directories) { for (String directory : directories) {
String dir = directory + "/"; String dir = directory + "/";
msg.append("<li><a rel=\"directory\" href=\"").append(encodeUriBetweenSlashes(uri + dir)) msg.append("<li><a rel=\"directory\" href=\"")
.append(encodeUriBetweenSlashes(uri + dir))
.append("\"><span class=\"dirname\">").append(dir) .append("\"><span class=\"dirname\">").append(dir)
.append("</span></a></b></li>"); .append("</span></a></b></li>");
} }

View File

@ -3,12 +3,10 @@ package org.fdroid.fdroid.net;
import android.app.Service; import android.app.Service;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.net.wifi.WifiInfo; import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager; import android.net.wifi.WifiManager;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.IBinder; import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.util.Log; import android.util.Log;
@ -59,9 +57,7 @@ public class WifiStateChangeService extends Service {
FDroidApp.bssid = wifiInfo.getBSSID(); FDroidApp.bssid = wifiInfo.getBSSID();
String scheme; String scheme;
// TODO move this to Preferences.get().isHttpsEnabled(); if (Preferences.get().isLocalRepoHttpsEnabled())
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(WifiStateChangeService.this);
if (prefs.getBoolean("use_https", false))
scheme = "https"; scheme = "https";
else else
scheme = "http"; scheme = "http";

View File

@ -5,7 +5,6 @@ import android.app.Activity;
import android.content.*; import android.content.*;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.content.LocalBroadcastManager; import android.support.v4.content.LocalBroadcastManager;
import android.util.Log; import android.util.Log;
import android.view.View; import android.view.View;
@ -13,9 +12,7 @@ import android.view.View.OnClickListener;
import android.widget.Button; import android.widget.Button;
import android.widget.TextView; import android.widget.TextView;
import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.*;
import org.fdroid.fdroid.QrGenAsyncTask;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.net.WifiStateChangeService; import org.fdroid.fdroid.net.WifiStateChangeService;
public class QrWizardDownloadActivity extends Activity { public class QrWizardDownloadActivity extends Activity {
@ -62,9 +59,8 @@ public class QrWizardDownloadActivity extends Activity {
}; };
private void resetNetworkInfo() { private void resetNetworkInfo() {
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
String qrString = ""; String qrString = "";
if (prefs.getBoolean("use_https", false)) if (Preferences.get().isLocalRepoHttpsEnabled())
qrString += "https"; qrString += "https";
else else
qrString += "http"; qrString += "http";