exclude ROM apps from default swap app listing

Apps that are built as part of the ROM and signed by the platform keys
should very rarely be swapped.  This removes them from the default
list by comparing the signing keys.

This filter is deliberately only included on the list function and not on
the search function.  If people want to share system apps, they'll be able
to find them with the search function, but the system apps won't show up
by default.

https://source.android.com/devices/tech/ota/sign_builds#certificates-keys

closes #440
This commit is contained in:
Hans-Christoph Steiner 2018-04-16 16:47:15 +02:00
parent 59ec2a7751
commit c42d7164cf
2 changed files with 63 additions and 1 deletions

View File

@ -18,6 +18,7 @@ import org.fdroid.fdroid.data.Schema.InstalledAppTable;
import org.fdroid.fdroid.data.Schema.InstalledAppTable.Cols;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
public class InstalledAppProvider extends FDroidProvider {
@ -81,6 +82,20 @@ public class InstalledAppProvider extends FDroidProvider {
private static final UriMatcher MATCHER = new UriMatcher(-1);
/**
* Built-in apps that are signed by the various Android ROM keys.
*
* @see <a href="https://source.android.com/devices/tech/ota/sign_builds#certificates-keys">Certificates and private keys</a>
*/
private static final String[] SYSTEM_PACKAGES = {
"android", // platform key
"com.android.email", // test/release key
"com.android.contacts", // shared key
"com.android.providers.downloads", // media key
};
private static String[] systemSignatures;
static {
MATCHER.addURI(getAuthority(), null, CODE_LIST);
MATCHER.addURI(getAuthority(), PATH_SEARCH + "/*", CODE_SEARCH);
@ -117,6 +132,36 @@ public class InstalledAppProvider extends FDroidProvider {
return packageName; // all else fails, return packageName
}
/**
* Add SQL selection statement to exclude {@link InstalledApp}s that were
* signed by the platform/shared/media/testkey keys.
*
* @see <a href="https://source.android.com/devices/tech/ota/sign_builds#certificates-keys">Certificates and private keys</a>
*/
private QuerySelection selectNotSystemSignature(QuerySelection selection) {
if (systemSignatures == null) {
Log.i(TAG, "selectNotSystemSignature: systemSignature == null, querying for it");
HashSet<String> signatures = new HashSet<>();
for (String packageName : SYSTEM_PACKAGES) {
Cursor cursor = query(InstalledAppProvider.getAppUri(packageName), new String[]{Cols.SIGNATURE},
null, null, null);
if (cursor != null) {
if (cursor.moveToFirst()) {
signatures.add(cursor.getString(cursor.getColumnIndex(Cols.SIGNATURE)));
}
cursor.close();
}
}
systemSignatures = signatures.toArray(new String[signatures.size()]);
}
Log.i(TAG, "excluding InstalledApps signed by system signatures");
for (String systemSignature : systemSignatures) {
selection = selection.add("NOT " + Cols.SIGNATURE + " IN (?)", new String[]{systemSignature});
}
return selection;
}
@Override
protected String getTableName() {
return InstalledAppTable.NAME;
@ -185,6 +230,7 @@ public class InstalledAppProvider extends FDroidProvider {
QuerySelection selection = new QuerySelection(customSelection, selectionArgs);
switch (MATCHER.match(uri)) {
case CODE_LIST:
selection = selectNotSystemSignature(selection);
break;
case CODE_SINGLE:

View File

@ -23,6 +23,8 @@ import rx.subjects.PublishSubject;
import java.io.File;
import java.io.FilenameFilter;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -146,7 +148,10 @@ public class InstalledAppProviderService extends IntentService {
* Make sure that {@link InstalledAppProvider}, our database of installed apps,
* is in sync with what the {@link PackageManager} tells us is installed. Once
* completed, the relevant {@link android.content.ContentProvider}s will be
* notified of any changes to installed statuses.
* notified of any changes to installed statuses. The packages are processed
* in alphabetically order so that "{@code android}" is processed first. That
* is always present and signed by the system key, so it is the source of the
* system key for comparing all packages.
* <p>
* The installed app cache could get out of sync, e.g. if F-Droid crashed/ or
* ran out of battery half way through responding to {@link Intent#ACTION_PACKAGE_ADDED}.
@ -169,6 +174,12 @@ public class InstalledAppProviderService extends IntentService {
List<PackageInfo> packageInfoList = context.getPackageManager()
.getInstalledPackages(PackageManager.GET_SIGNATURES);
Collections.sort(packageInfoList, new Comparator<PackageInfo>() {
@Override
public int compare(PackageInfo o1, PackageInfo o2) {
return o1.packageName.compareTo(o2.packageName);
}
});
for (PackageInfo packageInfo : packageInfoList) {
if (cachedInfo.containsKey(packageInfo.packageName)) {
if (packageInfo.lastUpdateTime < 1262300400000L // 2010-01-01 00:00
@ -314,6 +325,11 @@ public class InstalledAppProviderService extends IntentService {
context.getContentResolver().delete(uri, null, null);
}
/**
* Get the fingerprint used to represent an APK signing key in F-Droid.
* This is a custom fingerprint algorithm that was kind of accidentally
* created, but is still in use.
*/
private static String getPackageSig(PackageInfo info) {
if (info == null || info.signatures == null || info.signatures.length < 1) {
return "";