mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 10:49:06 +01:00
Add tapjacking protection
* Set filterTouchesWhenObscured for all views * Warn the user if Apps using the SYSTEM_ALERT_WINDOW permission are installed * Warn the user if an App using the permission is installed while Briar is running Signed-off-by: goapunk <noobie@goapunks.net>
This commit is contained in:
@@ -28,6 +28,7 @@ import org.briarproject.briar.BriarCoreModule;
|
||||
import org.briarproject.briar.android.reporting.BriarReportSender;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.android.ReferenceManager;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
import org.briarproject.briar.api.blog.BlogManager;
|
||||
import org.briarproject.briar.api.blog.BlogPostFactory;
|
||||
import org.briarproject.briar.api.blog.BlogSharingManager;
|
||||
@@ -89,6 +90,8 @@ public interface AndroidComponent
|
||||
|
||||
AndroidNotificationManager androidNotificationManager();
|
||||
|
||||
ScreenFilterMonitor screenFilterMonitor();
|
||||
|
||||
ConnectionRegistry connectionRegistry();
|
||||
|
||||
ContactManager contactManager();
|
||||
|
||||
@@ -16,6 +16,7 @@ import org.briarproject.bramble.api.ui.UiCallback;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.briarproject.briar.api.android.AndroidNotificationManager;
|
||||
import org.briarproject.briar.api.android.ReferenceManager;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
|
||||
import java.io.File;
|
||||
import java.security.GeneralSecurityException;
|
||||
@@ -37,6 +38,8 @@ public class AppModule {
|
||||
static class EagerSingletons {
|
||||
@Inject
|
||||
AndroidNotificationManager androidNotificationManager;
|
||||
@Inject
|
||||
ScreenFilterMonitor screenFilterMonitor;
|
||||
}
|
||||
|
||||
private final Application application;
|
||||
@@ -166,4 +169,12 @@ public class AppModule {
|
||||
eventBus.addListener(notificationManager);
|
||||
return notificationManager;
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
ScreenFilterMonitor provideScreenFilterMonitor(
|
||||
LifecycleManager lifecycleManager, ScreenFilterMonitorImpl sfm) {
|
||||
lifecycleManager.registerService(sfm);
|
||||
return sfm;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,232 @@
|
||||
package org.briarproject.briar.android;
|
||||
|
||||
import android.app.Application;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.UiThread;
|
||||
import android.support.v7.preference.PreferenceManager;
|
||||
|
||||
import org.briarproject.bramble.api.lifecycle.Service;
|
||||
import org.briarproject.bramble.api.lifecycle.ServiceException;
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.bramble.api.system.AndroidExecutor;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.Callable;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class ScreenFilterMonitorImpl extends BroadcastReceiver
|
||||
implements Service,
|
||||
ScreenFilterMonitor {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(ScreenFilterMonitorImpl.class.getName());
|
||||
private static final String PREF_SCREEN_FILTER_APPS =
|
||||
"shownScreenFilterApps";
|
||||
private final Context appContext;
|
||||
private final AndroidExecutor androidExecutor;
|
||||
private final LinkedList<String> appNames = new LinkedList<>();
|
||||
private final PackageManager pm;
|
||||
private final SharedPreferences prefs;
|
||||
private final AtomicBoolean used = new AtomicBoolean(false);
|
||||
private final Set<String> apps = new HashSet<>();
|
||||
private final Set<String> shownApps;
|
||||
// Used solely for the UiThread
|
||||
private boolean serviceStarted = false;
|
||||
|
||||
@Inject
|
||||
ScreenFilterMonitorImpl(AndroidExecutor executor, Application app) {
|
||||
this.androidExecutor = executor;
|
||||
this.appContext = app;
|
||||
pm = appContext.getPackageManager();
|
||||
prefs = PreferenceManager.getDefaultSharedPreferences(appContext);
|
||||
shownApps = getShownScreenFilterApps();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startService() throws ServiceException {
|
||||
if (used.getAndSet(true)) throw new IllegalStateException();
|
||||
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() {
|
||||
IntentFilter intentFilter = new IntentFilter();
|
||||
intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
|
||||
intentFilter.addDataScheme("package");
|
||||
appContext.registerReceiver(ScreenFilterMonitorImpl.this,
|
||||
intentFilter);
|
||||
apps.addAll(getInstalledScreenFilterApps());
|
||||
serviceStarted = true;
|
||||
return null;
|
||||
}
|
||||
});
|
||||
try {
|
||||
f.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopService() throws ServiceException {
|
||||
Future<Void> f = androidExecutor.runOnUiThread(new Callable<Void>() {
|
||||
@Override
|
||||
public Void call() {
|
||||
serviceStarted = false;
|
||||
appContext.unregisterReceiver(ScreenFilterMonitorImpl.this);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
try {
|
||||
f.get();
|
||||
} catch (InterruptedException | ExecutionException e) {
|
||||
throw new ServiceException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getShownScreenFilterApps() {
|
||||
// res must not be modified
|
||||
Set<String> s =
|
||||
prefs.getStringSet(PREF_SCREEN_FILTER_APPS, null);
|
||||
HashSet<String> result = new HashSet<>();
|
||||
if (s != null) {
|
||||
result.addAll(s);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) {
|
||||
final String packageName =
|
||||
intent.getData().getEncodedSchemeSpecificPart();
|
||||
androidExecutor.runOnUiThread(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String pkg = isOverlayApp(packageName);
|
||||
if (pkg == null) {
|
||||
return;
|
||||
}
|
||||
apps.add(pkg);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public Set<String> getApps() {
|
||||
if (!serviceStarted) {
|
||||
apps.addAll(getInstalledScreenFilterApps());
|
||||
}
|
||||
TreeSet<String> buf = new TreeSet<>();
|
||||
if (apps.isEmpty()) {
|
||||
return buf;
|
||||
}
|
||||
buf.addAll(apps);
|
||||
buf.removeAll(shownApps);
|
||||
return buf;
|
||||
}
|
||||
|
||||
@Override
|
||||
@UiThread
|
||||
public void storeAppsAsShown(Collection<String> s, boolean persistent) {
|
||||
HashSet<String> buf = new HashSet(s);
|
||||
shownApps.addAll(buf);
|
||||
if (persistent && !s.isEmpty()) {
|
||||
buf.addAll(getShownScreenFilterApps());
|
||||
prefs.edit()
|
||||
.putStringSet(PREF_SCREEN_FILTER_APPS, buf)
|
||||
.apply();
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> getInstalledScreenFilterApps() {
|
||||
HashSet<String> screenFilterApps = new HashSet<>();
|
||||
List<PackageInfo> packageInfos =
|
||||
pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
|
||||
for (PackageInfo packageInfo : packageInfos) {
|
||||
if (isOverlayApp(packageInfo)) {
|
||||
String name = pkgToString(packageInfo);
|
||||
if (name != null) {
|
||||
screenFilterApps.add(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
return screenFilterApps;
|
||||
}
|
||||
|
||||
// Checks if pkg uses the SYSTEM_ALERT_WINDOW permission and if so
|
||||
// returns the app name.
|
||||
@Nullable
|
||||
private String isOverlayApp(String pkg) {
|
||||
try {
|
||||
PackageInfo pkgInfo =
|
||||
pm.getPackageInfo(pkg, PackageManager.GET_PERMISSIONS);
|
||||
if (isOverlayApp(pkgInfo)) {
|
||||
return pkgToString(pkgInfo);
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException ignored) {
|
||||
if (LOG.isLoggable(Level.WARNING)) {
|
||||
LOG.warning("Package name not found: " + pkg);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Fetch the application name for a given package.
|
||||
@Nullable
|
||||
private String pkgToString(PackageInfo pkgInfo) {
|
||||
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
|
||||
if (seq != null) {
|
||||
return seq.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// Checks if an installed pkg is a user app using the permission.
|
||||
private boolean isOverlayApp(PackageInfo packageInfo) {
|
||||
int mask = ApplicationInfo.FLAG_SYSTEM |
|
||||
ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||
// Ignore system apps
|
||||
if ((packageInfo.applicationInfo.flags & mask) != 0) {
|
||||
return false;
|
||||
}
|
||||
//Get Permissions
|
||||
String[] requestedPermissions =
|
||||
packageInfo.requestedPermissions;
|
||||
if (requestedPermissions != null) {
|
||||
for (String requestedPermission : requestedPermissions) {
|
||||
if (requestedPermission
|
||||
.equals(SYSTEM_ALERT_WINDOW)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,15 @@ import org.briarproject.briar.android.BriarApplication;
|
||||
import org.briarproject.briar.android.DestroyableContext;
|
||||
import org.briarproject.briar.android.controller.ActivityLifecycleController;
|
||||
import org.briarproject.briar.android.forum.ForumModule;
|
||||
import org.briarproject.briar.android.fragment.SFDialogFragment;
|
||||
import org.briarproject.briar.api.android.ScreenFilterMonitor;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import javax.inject.Inject;
|
||||
|
||||
import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
|
||||
import static android.view.inputmethod.InputMethodManager.SHOW_IMPLICIT;
|
||||
@@ -25,13 +29,16 @@ import static org.briarproject.briar.android.TestingConstants.PREVENT_SCREENSHOT
|
||||
|
||||
public abstract class BaseActivity extends AppCompatActivity
|
||||
implements DestroyableContext {
|
||||
|
||||
protected ActivityComponent activityComponent;
|
||||
|
||||
private final List<ActivityLifecycleController> lifecycleControllers =
|
||||
new ArrayList<>();
|
||||
private boolean destroyed = false;
|
||||
|
||||
@Inject
|
||||
protected ScreenFilterMonitor screenFilterMonitor;
|
||||
private SFDialogFragment dialogFrag;
|
||||
|
||||
public abstract void injectActivity(ActivityComponent component);
|
||||
|
||||
public void addLifecycleController(ActivityLifecycleController alc) {
|
||||
@@ -58,6 +65,7 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
for (ActivityLifecycleController alc : lifecycleControllers) {
|
||||
alc.onActivityCreate(this);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public ActivityComponent getActivityComponent() {
|
||||
@@ -89,6 +97,35 @@ public abstract class BaseActivity extends AppCompatActivity
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostResume() {
|
||||
super.onPostResume();
|
||||
showNewScreenFilterWarning();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
if (dialogFrag != null) {
|
||||
dialogFrag.dismiss();
|
||||
dialogFrag = null;
|
||||
}
|
||||
}
|
||||
|
||||
protected void showNewScreenFilterWarning() {
|
||||
final Set<String> apps = screenFilterMonitor.getApps();
|
||||
if (apps.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
dialogFrag = SFDialogFragment.newInstance(new ArrayList<>(apps));
|
||||
dialogFrag.setCancelable(false);
|
||||
dialogFrag.show(getSupportFragmentManager(), "SFDialog");
|
||||
}
|
||||
|
||||
public void rememberShownApps(ArrayList<String> s, boolean permanent) {
|
||||
screenFilterMonitor.storeAppsAsShown(s, permanent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDestroy() {
|
||||
super.onDestroy();
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package org.briarproject.briar.android.fragment;
|
||||
|
||||
import android.app.Dialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.DialogFragment;
|
||||
import android.support.v7.app.AlertDialog;
|
||||
import android.text.TextUtils;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.TextView;
|
||||
|
||||
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
|
||||
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
|
||||
import org.briarproject.briar.R;
|
||||
import org.briarproject.briar.android.activity.BaseActivity;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@MethodsNotNullByDefault
|
||||
@ParametersNotNullByDefault
|
||||
public class SFDialogFragment extends DialogFragment {
|
||||
|
||||
public static SFDialogFragment newInstance(ArrayList<String> apps) {
|
||||
SFDialogFragment frag = new SFDialogFragment();
|
||||
Bundle args = new Bundle();
|
||||
args.putStringArrayList("apps", apps);
|
||||
frag.setArguments(args);
|
||||
return frag;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
|
||||
AlertDialog.Builder builder =
|
||||
new AlertDialog.Builder(
|
||||
getActivity(),
|
||||
R.style.BriarDialogThemeNoFilter);
|
||||
builder.setTitle(R.string.screen_filter_title);
|
||||
LayoutInflater li = getActivity().getLayoutInflater();
|
||||
//Pass null here because it's an AlertDialog
|
||||
View v =
|
||||
li.inflate(R.layout.alert_dialog_checkbox, null,
|
||||
false);
|
||||
TextView t = (TextView) v.findViewById(R.id.alert_dialog_text);
|
||||
final ArrayList<String> apps =
|
||||
getArguments().getStringArrayList("apps");
|
||||
t.setText(getString(R.string.screen_filter_body, TextUtils
|
||||
.join("\n", apps)));
|
||||
final CheckBox cb =
|
||||
(CheckBox) v.findViewById(
|
||||
R.id.checkBox_screen_filter_reminder);
|
||||
builder.setNeutralButton(R.string.continue_button,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialog,
|
||||
int which) {
|
||||
((BaseActivity) getActivity())
|
||||
.rememberShownApps(apps, cb.isChecked());
|
||||
}
|
||||
});
|
||||
builder.setView(v);
|
||||
return builder.create();
|
||||
}
|
||||
}
|
||||
@@ -83,6 +83,10 @@ public class SplashScreenActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void showNewScreenFilterWarning() {
|
||||
}
|
||||
|
||||
private void enableStrictMode() {
|
||||
if (TESTING) {
|
||||
ThreadPolicy.Builder threadPolicy = new ThreadPolicy.Builder();
|
||||
|
||||
@@ -120,5 +120,5 @@ public class UiUtils {
|
||||
public static String getBulbTransitionName(ContactId c) {
|
||||
return "bulb" + c.getInt();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
package org.briarproject.briar.api.android;
|
||||
|
||||
import android.support.annotation.UiThread;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
|
||||
public interface ScreenFilterMonitor {
|
||||
|
||||
@UiThread
|
||||
Set<String> getApps();
|
||||
|
||||
@UiThread
|
||||
void storeAppsAsShown(Collection<String> s, boolean persistent);
|
||||
}
|
||||
40
briar-android/src/main/res/layout/alert_dialog_checkbox.xml
Normal file
40
briar-android/src/main/res/layout/alert_dialog_checkbox.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:filterTouchesWhenObscured="false"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:fadeScrollbars="false"
|
||||
android:paddingLeft="20dp"
|
||||
android:paddingRight="20dp"
|
||||
android:paddingStart="20dp"
|
||||
android:paddingTop="20dp"
|
||||
android:theme="@style/BriarTheme">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/alert_dialog_text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="3dp"
|
||||
android:paddingRight="6dp"
|
||||
android:text="TextView"
|
||||
android:textAppearance="@style/BriarTextBody"
|
||||
android:theme="@+theme/BriarDialogTheme"/>
|
||||
</ScrollView>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkBox_screen_filter_reminder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="15dp"
|
||||
android:layout_marginStart="15dp"
|
||||
android:layout_weight="0"
|
||||
android:filterTouchesWhenObscured="false"
|
||||
android:text="@string/checkbox_dont_show_again"
|
||||
android:textAppearance="@style/BriarTextBody"/>
|
||||
</LinearLayout>
|
||||
@@ -380,4 +380,10 @@
|
||||
<!-- Sign Out -->
|
||||
<string name="progress_title_logout">Signing out of Briar…</string>
|
||||
|
||||
<!-- Screen Filters & Tapjacking -->
|
||||
<string name="screen_filter_title">Screen filter detected</string>
|
||||
<string name="screen_filter_body">The following apps have permission to draw over other apps:\n\n%1$s \n\nBriar will not respond to touches when another app is drawing over it.
|
||||
If you experience any problems, try turning off these apps when using Briar.\n</string>
|
||||
<string name="checkbox_dont_show_again">Don\'t warn me again for these apps</string>
|
||||
|
||||
</resources>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
<item name="android:textColorTertiaryInverse">@color/briar_text_tertiary_inverse</item>
|
||||
<item name="android:textColorLink">@color/briar_text_link</item>
|
||||
<item name="android:windowAnimationStyle">@style/ActivityAnimation</item>
|
||||
<item name="android:filterTouchesWhenObscured">true</item>
|
||||
|
||||
<!-- These fix a long-standing UI bug in the support preference library -->
|
||||
<item name="preferenceTheme">@style/PreferenceThemeOverlay.v14.Material</item>
|
||||
@@ -47,6 +48,12 @@
|
||||
<item name="android:textColorTertiaryInverse">@color/briar_text_tertiary_inverse</item>
|
||||
<item name="android:textColorLink">@color/briar_text_link</item>
|
||||
<item name="android:windowAnimationStyle">@style/DialogAnimation</item>
|
||||
<item name="android:filterTouchesWhenObscured">true</item>
|
||||
</style>
|
||||
|
||||
<!-- Use this with care. Only used for the screen filter warning dialog -->
|
||||
<style name="BriarDialogThemeNoFilter" parent="BriarDialogTheme">
|
||||
<item name="android:filterTouchesWhenObscured">false</item>
|
||||
</style>
|
||||
|
||||
<style name="DialogAnimation" parent="@android:style/Animation.Dialog">
|
||||
|
||||
Reference in New Issue
Block a user