diff --git a/briar-android/src/main/java/org/briarproject/briar/android/ScreenFilterMonitorImpl.java b/briar-android/src/main/java/org/briarproject/briar/android/ScreenFilterMonitorImpl.java index cb195108af4f941db686df4c512cb05cd6601b31..8ec4b3508fb77ad84a709427eb91eda8f03749f2 100644 --- a/briar-android/src/main/java/org/briarproject/briar/android/ScreenFilterMonitorImpl.java +++ b/briar-android/src/main/java/org/briarproject/briar/android/ScreenFilterMonitorImpl.java @@ -6,10 +6,10 @@ 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.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.Signature; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.v7.preference.PreferenceManager; @@ -19,11 +19,16 @@ 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.bramble.util.StringUtils; import org.briarproject.briar.api.android.ScreenFilterMonitor; +import java.io.ByteArrayInputStream; +import java.io.InputStream; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.util.Collection; import java.util.HashSet; -import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.TreeSet; @@ -31,32 +36,57 @@ 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; +import static android.content.Intent.ACTION_PACKAGE_ADDED; +import static android.content.Intent.EXTRA_REPLACING; +import static android.content.pm.ApplicationInfo.FLAG_SYSTEM; +import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; +import static android.content.pm.PackageManager.GET_PERMISSIONS; +import static android.content.pm.PackageManager.GET_SIGNATURES; +import static java.util.logging.Level.WARNING; @MethodsNotNullByDefault @ParametersNotNullByDefault public class ScreenFilterMonitorImpl extends BroadcastReceiver - implements Service, - ScreenFilterMonitor { + implements Service, ScreenFilterMonitor { private static final Logger LOG = Logger.getLogger(ScreenFilterMonitorImpl.class.getName()); private static final String PREF_SCREEN_FILTER_APPS = "shownScreenFilterApps"; + + /* + * Ignore Play Services if it uses this package name and public key - it's + * effectively a system app, but not flagged as such on older systems + */ + private static final String PLAY_SERVICES_PACKAGE = + "com.google.android.gms"; + private static final String PLAY_SERVICES_PUBLIC_KEY = + "30820120300D06092A864886F70D01010105000382010D0030820108" + + "0282010100AB562E00D83BA208AE0A966F124E29DA11F2AB56D08F58" + + "E2CCA91303E9B754D372F640A71B1DCB130967624E4656A7776A9219" + + "3DB2E5BFB724A91E77188B0E6A47A43B33D9609B77183145CCDF7B2E" + + "586674C9E1565B1F4C6A5955BFF251A63DABF9C55C27222252E875E4" + + "F8154A645F897168C0B1BFC612EABF785769BB34AA7984DC7E2EA276" + + "4CAE8307D8C17154D7EE5F64A51A44A602C249054157DC02CD5F5C0E" + + "55FBEF8519FBE327F0B1511692C5A06F19D18385F5C4DBC2D6B93F68" + + "CC2979C70E18AB93866B3BD5DB8999552A0E3B4C99DF58FB918BEDC1" + + "82BA35E003C1B4B10DD244A8EE24FFFD333872AB5221985EDAB0FC0D" + + "0B145B6AA192858E79020103"; + 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); + + // The following must only be accessed on the UI thread private final Set<String> apps = new HashSet<>(); private final Set<String> shownApps; - // Used solely for the UiThread private boolean serviceStarted = false; @Inject @@ -75,7 +105,7 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver @Override public Void call() { IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(ACTION_PACKAGE_ADDED); intentFilter.addDataScheme("package"); appContext.registerReceiver(ScreenFilterMonitorImpl.this, intentFilter); @@ -109,9 +139,8 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver } private Set<String> getShownScreenFilterApps() { - // res must not be modified - Set<String> s = - prefs.getStringSet(PREF_SCREEN_FILTER_APPS, null); + // Result 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); @@ -121,7 +150,7 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver @Override public void onReceive(Context context, Intent intent) { - if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { + if (!intent.getBooleanExtra(EXTRA_REPLACING, false)) { final String packageName = intent.getData().getEncodedSchemeSpecificPart(); androidExecutor.runOnUiThread(new Runnable() { @@ -155,7 +184,7 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver @Override @UiThread public void storeAppsAsShown(Collection<String> s, boolean persistent) { - HashSet<String> buf = new HashSet(s); + HashSet<String> buf = new HashSet<>(s); shownApps.addAll(buf); if (persistent && !s.isEmpty()) { buf.addAll(getShownScreenFilterApps()); @@ -168,7 +197,7 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver private Set<String> getInstalledScreenFilterApps() { HashSet<String> screenFilterApps = new HashSet<>(); List<PackageInfo> packageInfos = - pm.getInstalledPackages(PackageManager.GET_PERMISSIONS); + pm.getInstalledPackages(GET_PERMISSIONS); for (PackageInfo packageInfo : packageInfos) { if (isOverlayApp(packageInfo)) { String name = pkgToString(packageInfo); @@ -180,25 +209,22 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver return screenFilterApps; } - // Checks if pkg uses the SYSTEM_ALERT_WINDOW permission and if so + // Checks if a package 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); + PackageInfo pkgInfo = pm.getPackageInfo(pkg, GET_PERMISSIONS); if (isOverlayApp(pkgInfo)) { return pkgToString(pkgInfo); } - } catch (PackageManager.NameNotFoundException ignored) { - if (LOG.isLoggable(Level.WARNING)) { - LOG.warning("Package name not found: " + pkg); - } + } catch (NameNotFoundException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); } return null; } - // Fetch the application name for a given package. + // Fetches the application name for a given package. @Nullable private String pkgToString(PackageInfo pkgInfo) { CharSequence seq = pm.getApplicationLabel(pkgInfo.applicationInfo); @@ -208,25 +234,49 @@ public class ScreenFilterMonitorImpl extends BroadcastReceiver return null; } - // Checks if an installed pkg is a user app using the permission. + // Checks if an installed package is a user app using the permission. private boolean isOverlayApp(PackageInfo packageInfo) { - int mask = ApplicationInfo.FLAG_SYSTEM | - ApplicationInfo.FLAG_UPDATED_SYSTEM_APP; + int mask = FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP; // Ignore system apps if ((packageInfo.applicationInfo.flags & mask) != 0) { return false; } - //Get Permissions - String[] requestedPermissions = - packageInfo.requestedPermissions; + // Ignore Play Services, it's effectively a system app + if (isPlayServices(packageInfo.packageName)) { + return false; + } + // Get permissions + String[] requestedPermissions = packageInfo.requestedPermissions; if (requestedPermissions != null) { for (String requestedPermission : requestedPermissions) { - if (requestedPermission - .equals(SYSTEM_ALERT_WINDOW)) { + if (requestedPermission.equals(SYSTEM_ALERT_WINDOW)) { return true; } } } return false; } + + private boolean isPlayServices(String pkg) { + if (!PLAY_SERVICES_PACKAGE.equals(pkg)) return false; + try { + PackageInfo sigs = pm.getPackageInfo(pkg, GET_SIGNATURES); + // The genuine Play Services app should have a single signature + Signature[] signatures = sigs.signatures; + if (signatures == null || signatures.length != 1) return false; + // Extract the public key from the signature + CertificateFactory certFactory = + CertificateFactory.getInstance("X509"); + byte[] signatureBytes = signatures[0].toByteArray(); + InputStream in = new ByteArrayInputStream(signatureBytes); + X509Certificate cert = + (X509Certificate) certFactory.generateCertificate(in); + byte[] publicKeyBytes = cert.getPublicKey().getEncoded(); + String publicKey = StringUtils.toHexString(publicKeyBytes); + return PLAY_SERVICES_PUBLIC_KEY.equals(publicKey); + } catch (NameNotFoundException | CertificateException e) { + if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); + return false; + } + } }