Allow filtered taps if all overlay apps are whitelisted.

This commit is contained in:
akwizgran
2018-01-04 13:19:49 +00:00
parent 347c2f22c1
commit 7aebf92a6f
11 changed files with 200 additions and 59 deletions

View File

@@ -1,5 +1,7 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.content.SharedPreferences;
import org.briarproject.bramble.BrambleAndroidModule; import org.briarproject.bramble.BrambleAndroidModule;
import org.briarproject.bramble.BrambleCoreEagerSingletons; import org.briarproject.bramble.BrambleCoreEagerSingletons;
import org.briarproject.bramble.BrambleCoreModule; import org.briarproject.bramble.BrambleCoreModule;
@@ -89,6 +91,8 @@ public interface AndroidComponent
AndroidNotificationManager androidNotificationManager(); AndroidNotificationManager androidNotificationManager();
SharedPreferences sharedPreferences();
ScreenFilterMonitor screenFilterMonitor(); ScreenFilterMonitor screenFilterMonitor();
ConnectionRegistry connectionRegistry(); ConnectionRegistry connectionRegistry();

View File

@@ -1,6 +1,7 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.app.Application; import android.app.Application;
import android.content.SharedPreferences;
import org.briarproject.bramble.api.crypto.CryptoComponent; import org.briarproject.bramble.api.crypto.CryptoComponent;
import org.briarproject.bramble.api.crypto.PublicKey; import org.briarproject.bramble.api.crypto.PublicKey;
@@ -157,6 +158,11 @@ public class AppModule {
return devConfig; return devConfig;
} }
@Provides
SharedPreferences provideSharedPreferences(Application app) {
return app.getSharedPreferences("db", MODE_PRIVATE);
}
@Provides @Provides
@Singleton @Singleton
ReferenceManager provideReferenceManager() { ReferenceManager provideReferenceManager() {

View File

@@ -1,6 +1,8 @@
package org.briarproject.briar.android; package org.briarproject.briar.android;
import android.annotation.SuppressLint;
import android.app.Application; import android.app.Application;
import android.content.SharedPreferences;
import android.content.pm.PackageInfo; import android.content.pm.PackageInfo;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PackageManager.NameNotFoundException;
@@ -16,19 +18,23 @@ import java.io.InputStream;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory; import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.TreeSet;
import java.util.logging.Logger; import java.util.logging.Logger;
import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
import static android.Manifest.permission.SYSTEM_ALERT_WINDOW; import static android.Manifest.permission.SYSTEM_ALERT_WINDOW;
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED;
import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.GET_PERMISSIONS;
import static android.content.pm.PackageManager.GET_SIGNATURES; import static android.content.pm.PackageManager.GET_SIGNATURES;
import static android.os.Build.VERSION.SDK_INT;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@NotNullByDefault @NotNullByDefault
@@ -56,54 +62,75 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
"82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" + "82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" +
"0B145B6AA192858E79020103"; "0B145B6AA192858E79020103";
private static final String PREF_KEY_ALLOWED = "allowedOverlayApps";
private final PackageManager pm; private final PackageManager pm;
private final SharedPreferences prefs;
@Inject @Inject
ScreenFilterMonitorImpl(Application app) { ScreenFilterMonitorImpl(Application app, SharedPreferences prefs) {
pm = app.getPackageManager(); pm = app.getPackageManager();
this.prefs = prefs;
} }
@Override @Override
@UiThread @UiThread
public Set<String> getApps() { public Collection<AppDetails> getApps() {
Set<String> screenFilterApps = new TreeSet<>(); Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
Collections.emptySet());
List<AppDetails> apps = new ArrayList<>();
List<PackageInfo> packageInfos = List<PackageInfo> packageInfos =
pm.getInstalledPackages(GET_PERMISSIONS); pm.getInstalledPackages(GET_PERMISSIONS);
for (PackageInfo packageInfo : packageInfos) { for (PackageInfo packageInfo : packageInfos) {
if (isOverlayApp(packageInfo)) { if (!allowed.contains(packageInfo.packageName)
String name = pkgToString(packageInfo); && isOverlayApp(packageInfo)) {
if (name != null) { String name = getAppName(packageInfo);
screenFilterApps.add(name); apps.add(new AppDetails(name, packageInfo.packageName));
}
} }
} }
return screenFilterApps; Collections.sort(apps, (a, b) -> a.name.compareTo(b.name));
return apps;
} }
// Fetches the application name for a given package. @Override
@Nullable public void allowApps(Collection<String> packageNames) {
private String pkgToString(PackageInfo pkgInfo) { Set<String> allowed = prefs.getStringSet(PREF_KEY_ALLOWED,
Collections.emptySet());
Set<String> merged = new HashSet<>(allowed);
merged.addAll(packageNames);
prefs.edit().putStringSet(PREF_KEY_ALLOWED, merged).apply();
}
// Returns the application name for a given package, or the package name
// if no application name is available
private String getAppName(PackageInfo pkgInfo) {
CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo); CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo);
if (seq != null) { return seq == null ? pkgInfo.packageName : seq.toString();
return seq.toString();
}
return null;
} }
// Checks if an installed package is a user app using the permission. // Checks if an installed package is a user app using the permission.
private boolean isOverlayApp(PackageInfo packageInfo) { private boolean isOverlayApp(PackageInfo packageInfo) {
int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP; int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP;
// Ignore system apps // Ignore system apps
if ((packageInfo.applicationInfo.flags & mask) != 0) { if ((packageInfo.applicationInfo.flags & mask) != 0) return false;
return false;
}
// Ignore Play Services, it's effectively a system app // Ignore Play Services, it's effectively a system app
if (isPlayServices(packageInfo.packageName)) { if (isPlayServices(packageInfo.packageName)) return false;
return false;
}
// Get permissions // Get permissions
String[] requestedPermissions = packageInfo.requestedPermissions; String[] requestedPermissions = packageInfo.requestedPermissions;
if (requestedPermissions != null) { if (requestedPermissions == null) return false;
if (SDK_INT >= 16 && SDK_INT < 23) {
// Check whether the permission has been requested and granted
int[] flags = packageInfo.requestedPermissionsFlags;
if (flags == null || flags.length != requestedPermissions.length)
throw new AssertionError();
for (int i = 0; i < requestedPermissions.length; i++) {
if (requestedPermissions[i].equals(SYSTEM_ALERT_WINDOW)
&& (flags[i] & REQUESTED_PERMISSION_GRANTED) != 0) {
return true;
}
}
} else {
// Check whether the permission has been requested
for (String requestedPermission : requestedPermissions) { for (String requestedPermission : requestedPermissions) {
if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) { if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) {
return true; return true;
@@ -113,6 +140,7 @@ class ScreenFilterMonitorImpl implements ScreenFilterMonitor {
return false; return false;
} }
@SuppressLint("PackageManagerGetSignatures")
private boolean isPlayServices(String pkg) { private boolean isPlayServices(String pkg) {
if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false; if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false;
try { try {

View File

@@ -21,6 +21,7 @@ import org.briarproject.briar.android.forum.CreateForumActivity;
import org.briarproject.briar.android.forum.ForumActivity; import org.briarproject.briar.android.forum.ForumActivity;
import org.briarproject.briar.android.forum.ForumListFragment; import org.briarproject.briar.android.forum.ForumListFragment;
import org.briarproject.briar.android.forum.ForumModule; import org.briarproject.briar.android.forum.ForumModule;
import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.introduction.ContactChooserFragment; import org.briarproject.briar.android.introduction.ContactChooserFragment;
import org.briarproject.briar.android.introduction.IntroductionActivity; import org.briarproject.briar.android.introduction.IntroductionActivity;
import org.briarproject.briar.android.introduction.IntroductionMessageFragment; import org.briarproject.briar.android.introduction.IntroductionMessageFragment;
@@ -152,7 +153,9 @@ public interface ActivityComponent {
// Fragments // Fragments
void inject(AuthorNameFragment fragment); void inject(AuthorNameFragment fragment);
void inject(PasswordFragment fragment); void inject(PasswordFragment fragment);
void inject(DozeFragment fragment); void inject(DozeFragment fragment);
void inject(ContactListFragment fragment); void inject(ContactListFragment fragment);
@@ -189,4 +192,5 @@ public interface ActivityComponent {
void inject(SettingsFragment fragment); void inject(SettingsFragment fragment);
void inject(ScreenFilterDialogFragment fragment);
} }

View File

@@ -1,7 +1,6 @@
package org.briarproject.briar.android.activity; package org.briarproject.briar.android.activity;
import android.app.Activity; import android.app.Activity;
import android.content.SharedPreferences;
import org.briarproject.briar.android.controller.BriarController; import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.BriarControllerImpl; import org.briarproject.briar.android.controller.BriarControllerImpl;
@@ -19,7 +18,6 @@ import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
import dagger.Module; import dagger.Module;
import dagger.Provides; import dagger.Provides;
import static android.content.Context.MODE_PRIVATE;
import static org.briarproject.briar.android.BriarService.BriarServiceConnection; import static org.briarproject.briar.android.BriarService.BriarServiceConnection;
@Module @Module
@@ -57,12 +55,6 @@ public class ActivityModule {
return configController; return configController;
} }
@ActivityScope
@Provides
SharedPreferences provideSharedPreferences(Activity activity) {
return activity.getSharedPreferences("db", MODE_PRIVATE);
}
@ActivityScope @ActivityScope
@Provides @Provides
PasswordController providePasswordController( PasswordController providePasswordController(

View File

@@ -24,10 +24,11 @@ import org.briarproject.briar.android.fragment.ScreenFilterDialogFragment;
import org.briarproject.briar.android.widget.TapSafeFrameLayout; import org.briarproject.briar.android.widget.TapSafeFrameLayout;
import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener; import org.briarproject.briar.android.widget.TapSafeFrameLayout.OnTapFilteredListener;
import org.briarproject.briar.api.android.ScreenFilterMonitor; import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.Set;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject; import javax.inject.Inject;
@@ -132,16 +133,19 @@ public abstract class BaseActivity extends AppCompatActivity
.commit(); .commit();
} }
private void showScreenFilterWarning() { private boolean showScreenFilterWarning() {
if (dialogFrag != null && dialogFrag.isVisible()) return; // If the dialog is already visible, filter the tap
Set<String> apps = screenFilterMonitor.getApps(); if (dialogFrag != null && dialogFrag.isVisible()) return false;
if (apps.isEmpty()) return; Collection<AppDetails> apps = screenFilterMonitor.getApps();
dialogFrag = // If all overlay apps are allowed or system apps, allow the tap
ScreenFilterDialogFragment.newInstance(new ArrayList<>(apps)); if (apps.isEmpty()) return true;
dialogFrag = ScreenFilterDialogFragment.newInstance(apps);
dialogFrag.setCancelable(false); dialogFrag.setCancelable(false);
// Show dialog unless onSaveInstanceState() has been called, see #1112 // Show dialog unless onSaveInstanceState() has been called, see #1112
FragmentManager fm = getSupportFragmentManager(); FragmentManager fm = getSupportFragmentManager();
if (!fm.isStateSaved()) dialogFrag.show(fm, dialogFrag.getTag()); if (!fm.isStateSaved()) dialogFrag.show(fm, dialogFrag.getTag());
// Filter the tap
return false;
} }
@Override @Override
@@ -198,7 +202,10 @@ public abstract class BaseActivity extends AppCompatActivity
View decorView = getWindow().getDecorView(); View decorView = getWindow().getDecorView();
if (decorView instanceof ViewGroup) { if (decorView instanceof ViewGroup) {
Toolbar toolbar = findToolbar((ViewGroup) decorView); Toolbar toolbar = findToolbar((ViewGroup) decorView);
if (toolbar != null) toolbar.setFilterTouchesWhenObscured(true); if (toolbar != null) {
boolean filter = !screenFilterMonitor.getApps().isEmpty();
toolbar.setFilterTouchesWhenObscured(filter);
}
} }
} }
@@ -239,7 +246,7 @@ public abstract class BaseActivity extends AppCompatActivity
} }
@Override @Override
public void onTapFiltered() { public boolean shouldAllowTap() {
showScreenFilterWarning(); return showScreenFilterWarning();
} }
} }

View File

@@ -1,40 +1,86 @@
package org.briarproject.briar.android.fragment; package org.briarproject.briar.android.fragment;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog; import android.app.Dialog;
import android.os.Bundle; import android.os.Bundle;
import android.support.v4.app.DialogFragment; import android.support.v4.app.DialogFragment;
import android.support.v7.app.AlertDialog; import android.support.v7.app.AlertDialog;
import android.text.TextUtils; 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.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R; import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.BaseActivity;
import org.briarproject.briar.api.android.ScreenFilterMonitor;
import org.briarproject.briar.api.android.ScreenFilterMonitor.AppDetails;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import javax.annotation.Nullable; import javax.annotation.Nullable;
import javax.inject.Inject;
@NotNullByDefault @MethodsNotNullByDefault
@ParametersNotNullByDefault
public class ScreenFilterDialogFragment extends DialogFragment { public class ScreenFilterDialogFragment extends DialogFragment {
@Inject
ScreenFilterMonitor screenFilterMonitor;
public static ScreenFilterDialogFragment newInstance( public static ScreenFilterDialogFragment newInstance(
ArrayList<String> apps) { Collection<AppDetails> apps) {
ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment(); ScreenFilterDialogFragment frag = new ScreenFilterDialogFragment();
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putStringArrayList("apps", apps); ArrayList<String> appNames = new ArrayList<>();
for (AppDetails a : apps) appNames.add(a.name);
args.putStringArrayList("appNames", appNames);
ArrayList<String> packageNames = new ArrayList<>();
for (AppDetails a : apps) packageNames.add(a.packageName);
args.putStringArrayList("packageNames", packageNames);
frag.setArguments(args); frag.setArguments(args);
return frag; return frag;
} }
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
Activity activity = getActivity();
if (activity == null) throw new IllegalStateException();
((BaseActivity) activity).getActivityComponent().inject(this);
}
@Override @Override
public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity(), Activity activity = getActivity();
if (activity == null) throw new IllegalStateException();
AlertDialog.Builder builder = new AlertDialog.Builder(activity,
R.style.BriarDialogThemeNoFilter); R.style.BriarDialogThemeNoFilter);
builder.setTitle(R.string.screen_filter_title); builder.setTitle(R.string.screen_filter_title);
ArrayList<String> apps = getArguments().getStringArrayList("apps"); Bundle args = getArguments();
builder.setMessage(getString(R.string.screen_filter_body, if (args == null) throw new IllegalStateException();
TextUtils.join("\n", apps))); ArrayList<String> appNames = args.getStringArrayList("appNames");
builder.setNeutralButton(R.string.continue_button, ArrayList<String> packageNames =
(dialog, which) -> dialog.dismiss()); args.getStringArrayList("packageNames");
if (appNames == null || packageNames == null)
throw new IllegalStateException();
LayoutInflater inflater = activity.getLayoutInflater();
// See https://stackoverflow.com/a/24720976/6314875
@SuppressLint("InflateParams")
View dialogView = inflater.inflate(R.layout.dialog_screen_filter, null);
builder.setView(dialogView);
TextView message = dialogView.findViewById(R.id.screen_filter_message);
message.setText(getString(R.string.screen_filter_body,
TextUtils.join("\n", appNames)));
CheckBox allow = dialogView.findViewById(R.id.screen_filter_checkbox);
builder.setNeutralButton(R.string.continue_button, (dialog, which) -> {
if (allow.isChecked()) screenFilterMonitor.allowApps(packageNames);
dialog.dismiss();
});
return builder.create(); return builder.create();
} }
} }

View File

@@ -40,12 +40,12 @@ public class TapSafeFrameLayout extends FrameLayout {
@Override @Override
public boolean onFilterTouchEventForSecurity(MotionEvent e) { public boolean onFilterTouchEventForSecurity(MotionEvent e) {
boolean filter = (e.getFlags() & FLAG_WINDOW_IS_OBSCURED) != 0; boolean obscured = (e.getFlags() & FLAG_WINDOW_IS_OBSCURED) != 0;
if (filter && listener != null) listener.onTapFiltered(); if (obscured && listener != null) return listener.shouldAllowTap();
return !filter; else return !obscured;
} }
public interface OnTapFilteredListener { public interface OnTapFilteredListener {
void onTapFiltered(); boolean shouldAllowTap();
} }
} }

View File

@@ -2,10 +2,37 @@ package org.briarproject.briar.api.android;
import android.support.annotation.UiThread; import android.support.annotation.UiThread;
import java.util.Set; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.util.Collection;
@NotNullByDefault
public interface ScreenFilterMonitor { public interface ScreenFilterMonitor {
/**
* Returns the details of all apps that have requested the
* SYSTEM_ALERT_WINDOW permission, excluding system apps, Google Play
* Services, and any apps that have been allowed by calling
* {@link #allowApps(Collection)}.
*/
@UiThread @UiThread
Set<String> getApps(); Collection<AppDetails> getApps();
/**
* Allows the apps with the given package names to use overlay windows.
* They will not be returned by future calls to {@link #getApps()}.
*/
@UiThread
void allowApps(Collection<String> packageNames);
class AppDetails {
public final String name;
public final String packageName;
public AppDetails(String name, String packageName) {
this.name = name;
this.packageName = packageName;
}
}
} }

View File

@@ -0,0 +1,26 @@
<?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:orientation="vertical"
android:padding="@dimen/margin_large">
<ScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:id="@+id/screen_filter_message"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</ScrollView>
<CheckBox
android:id="@+id/screen_filter_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/margin_large"
android:text="@string/screen_filter_allow"/>
</LinearLayout>

View File

@@ -396,6 +396,7 @@
<!-- Screen Filters & Tapjacking --> <!-- Screen Filters & Tapjacking -->
<string name="screen_filter_title">Screen overlay detected</string> <string name="screen_filter_title">Screen overlay detected</string>
<string name="screen_filter_body">Another app is drawing on top of Briar. To protect your security, Briar will not respond to touches when another app is drawing on top.\n\nTry turning off the following apps when using Briar:\n\n%1$s</string> <string name="screen_filter_body">Another app is drawing on top of Briar. To protect your security, Briar will not respond to touches when another app is drawing on top.\n\nTry turning off the following apps when using Briar:\n\n%1$s</string>
<string name="screen_filter_allow">Allow these apps to draw on top</string>
<!-- Permission Requests --> <!-- Permission Requests -->
<string name="permission_camera_title">Camera permission</string> <string name="permission_camera_title">Camera permission</string>