support extended 'uses-permissions' tags in APKs

<uses-permissions/> tags can have min and max SDK to take effect.  This is
not supported currently, and it necessary especially with the privileged
installer so it can properly represent the permissions that an APK is
requesting.

For example:
<uses-permission
  android:name="android.permission.MANAGE_ACCOUNTS"
  android:maxSdkVersion="22" />
<uses-permission-sdk-23
  android:name="android.permission.CAMERA" />
<uses-permission-sdk-23
  android:name="android.permission.CALL_PHONE"
  android:maxSdkVersion="23" />
This commit is contained in:
Hans-Christoph Steiner 2016-10-10 11:26:04 +02:00
parent 2350b4e694
commit 6f0c9ff88a
9 changed files with 570 additions and 79 deletions

View File

@ -0,0 +1,125 @@
<?xml version="1.0" encoding="utf-8"?>
<fdroid>
<repo name="F-Droid" icon="fdroid-icon.png" maxage="14"
pubkey="3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"
timestamp="1467169032" url="http://f-droid.org/repo" version="16">
<description>
This is just a test of the extended permissions attributes.
</description>
</repo>
<application id="urzip.at.or.at.urzip">
<id>at.bitfire.davdroid</id>
<added>2013-10-13</added>
<lastupdated>2016-06-26</lastupdated>
<name>DAVdroid</name>
<summary>Contacts and Calendar sync</summary>
<icon>at.bitfire.davdroid.107.png</icon>
<desc>apk generated from urzip to test extended permissions</desc>
<license>GPLv3</license>
<categories>Internet</categories>
<category>Internet</category>
<web>https://davdroid.bitfire.at/</web>
<source>https://davdroid.bitfire.at/source/</source>
<tracker>https://davdroid.bitfire.at/forums/</tracker>
<changelog>https://gitlab.com/bitfireAT/davdroid/tags</changelog>
<donate>https://davdroid.bitfire.at/donate/</donate>
<bitcoin>1KSCy7RHztKuhW9fLLaUYqdwdC2iwbejZU</bitcoin>
<flattr>2100160</flattr>
<marketversion>1.1.1.2</marketversion>
<marketvercode>107</marketvercode>
<package>
<version>1.3.2-FAKE</version>
<versioncode>117</versioncode>
<apkname>org.fdroid.extendedpermissionstest.apk</apkname>
<srcname>at.bitfire.davdroid_116_src.tar.gz</srcname>
<hash type="sha256">f1aa02257e99c167d2ea9b0e9525c3ce7c181fe2e7f4dd00b65dd81ed2e27a62
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3298864</size>
<sdkver>14</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-09-22</added>
<permissions>
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,WRITE_CONTACTS,ACCESS_WIFI_STATE,REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS,READ_CALENDAR,READ_SYNC_STATS
</permissions>
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission-sdk-23 name="android.permission.CAMERA" />
<uses-permission-sdk-23 name="android.permission.CALL_PHONE" maxSdkVersion="23" />
</package>
<package>
<version>1.3.1-ose</version>
<versioncode>116</versioncode>
<apkname>at.bitfire.davdroid_116.apk</apkname>
<srcname>at.bitfire.davdroid_116_src.tar.gz</srcname>
<hash type="sha256">f1aa02257e99c167d2ea9b0e9525c3ce7c181fe2e7f4dd00b65dd81ed2e27a62
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3298864</size>
<sdkver>14</sdkver>
<targetSdkVersion>24</targetSdkVersion>
<added>2016-09-21</added>
<permissions>
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,org.dmfs.permission.READ_TASKS,WRITE_CONTACTS,ACCESS_WIFI_STATE,REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS,READ_CALENDAR,org.dmfs.permission.WRITE_TASKS
</permissions>
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
</package>
<package>
<version>1.1.1.2</version>
<versioncode>107</versioncode>
<apkname>at.bitfire.davdroid_107.apk</apkname>
<srcname>at.bitfire.davdroid_107_src.tar.gz</srcname>
<hash type="sha256">9a616f2e97bf8cf012baf896f95667dea4e3ce3252b31c5715073638a9fcc3d4
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3134363</size>
<sdkver>14</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-06-26</added>
<permissions>
org.dmfs.permission.READ_TASKS,READ_EXTERNAL_STORAGE,WRITE_CONTACTS,GET_ACCOUNTS,AUTHENTICATE_ACCOUNTS,WRITE_EXTERNAL_STORAGE,READ_CALENDAR,ACCESS_WIFI_STATE,org.dmfs.permission.WRITE_TASKS,ACCESS_NETWORK_STATE,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,INTERNET,MANAGE_ACCOUNTS,WRITE_SYNC_SETTINGS
</permissions>
</package>
<package>
<version>1.1.1.1</version>
<versioncode>105</versioncode>
<apkname>at.bitfire.davdroid_105.apk</apkname>
<srcname>at.bitfire.davdroid_105_src.tar.gz</srcname>
<hash type="sha256">4a0408c61536a1cc1028cea4273adbde2e57dfa2b12d93c3b52f4c3d095e2849
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3131567</size>
<sdkver>14</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-06-24</added>
<permissions>
org.dmfs.permission.READ_TASKS,READ_EXTERNAL_STORAGE,WRITE_CONTACTS,GET_ACCOUNTS,AUTHENTICATE_ACCOUNTS,WRITE_EXTERNAL_STORAGE,READ_CALENDAR,ACCESS_WIFI_STATE,org.dmfs.permission.WRITE_TASKS,ACCESS_NETWORK_STATE,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,INTERNET,MANAGE_ACCOUNTS,WRITE_SYNC_SETTINGS
</permissions>
</package>
<package>
<version>1.1.1</version>
<versioncode>104</versioncode>
<apkname>at.bitfire.davdroid_104.apk</apkname>
<srcname>at.bitfire.davdroid_104_src.tar.gz</srcname>
<hash type="sha256">09ba34996177efe8b1498a93fe6521ab84efab3bccb0f42449116e80b59e5b56
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3131367</size>
<sdkver>14</sdkver>
<targetSdkVersion>23</targetSdkVersion>
<added>2016-06-22</added>
<permissions>
org.dmfs.permission.READ_TASKS,READ_EXTERNAL_STORAGE,WRITE_CONTACTS,GET_ACCOUNTS,AUTHENTICATE_ACCOUNTS,WRITE_EXTERNAL_STORAGE,READ_CALENDAR,ACCESS_WIFI_STATE,org.dmfs.permission.WRITE_TASKS,ACCESS_NETWORK_STATE,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,INTERNET,MANAGE_ACCOUNTS,WRITE_SYNC_SETTINGS
</permissions>
</package>
</application>
</fdroid>

View File

@ -21,18 +21,32 @@ package org.fdroid.fdroid.installer;
import android.app.Instrumentation;
import android.net.Uri;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;
import android.util.Log;
import org.fdroid.fdroid.AssetUtils;
import org.fdroid.fdroid.RepoXMLHandler;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.compat.FileCompatTest;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.mock.RepoDetails;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@ -40,10 +54,7 @@ import static org.junit.Assert.fail;
* This test checks the ApkVerifier by parsing a repo from permissionsRepo.xml
* and checking the listed permissions against the ones specified in apks' AndroidManifest,
* which have been specifically generated for this test.
* - the apk file name must match the package name in the xml
* - the versionName of listed apks inside the repo have either a good or bad outcome.
* this must be defined in GOOD_VERSION_NAMES and BAD_VERSION_NAMES.
* <p/>
* <p>
* NOTE: This androidTest cannot run as a Robolectric test because the
* required methods from PackageManger are not included in Robolectric's Android API.
* java.lang.NoClassDefFoundError: java/util/jar/StrictJarFile
@ -51,11 +62,14 @@ import static org.junit.Assert.fail;
*/
@RunWith(AndroidJUnit4.class)
public class ApkVerifierTest {
public static final String TAG = "ApkVerifierTest";
Instrumentation instrumentation;
File sdk14Apk;
File minMaxApk;
private File extendedPermissionsApk;
private File extendedPermsXml;
@Before
public void setUp() {
@ -71,8 +85,18 @@ public class ApkVerifierTest {
"org.fdroid.permissions.minmax.apk",
dir
);
extendedPermissionsApk = AssetUtils.copyAssetToDir(instrumentation.getContext(),
"org.fdroid.extendedpermissionstest.apk",
dir
);
extendedPermsXml = AssetUtils.copyAssetToDir(instrumentation.getContext(),
"extendedPerms.xml",
dir
);
assertTrue(sdk14Apk.exists());
assertTrue(minMaxApk.exists());
assertTrue(extendedPermissionsApk.exists());
assertTrue(extendedPermsXml.exists());
}
@Test
@ -80,7 +104,7 @@ public class ApkVerifierTest {
Apk apk = new Apk();
apk.packageName = "org.fdroid.permissions.sdk14";
apk.targetSdkVersion = 14;
apk.requestedPermissions = new String[]{
String[] noPrefixPermissions = new String[]{
"AUTHENTICATE_ACCOUNTS",
"MANAGE_ACCOUNTS",
"READ_PROFILE",
@ -98,9 +122,12 @@ public class ApkVerifierTest {
"WRITE_CALL_LOG", // implied-permission!
"READ_CALL_LOG", // implied-permission!
};
for (int i = 0; i < noPrefixPermissions.length; i++) {
noPrefixPermissions[i] = RepoXMLHandler.fdroidToAndroidPermission(noPrefixPermissions[i]);
}
apk.requestedPermissions = noPrefixPermissions;
Uri uri = Uri.fromFile(sdk14Apk);
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
try {
@ -111,6 +138,31 @@ public class ApkVerifierTest {
}
}
@Test(expected = ApkVerifier.ApkPermissionUnequalException.class)
public void testWithMinMax()
throws ApkVerifier.ApkPermissionUnequalException, ApkVerifier.ApkVerificationException {
Apk apk = new Apk();
apk.packageName = "org.fdroid.permissions.minmax";
apk.targetSdkVersion = 24;
ArrayList<String> permissionsList = new ArrayList<>();
permissionsList.add("android.permission.READ_CALENDAR");
if (Build.VERSION.SDK_INT <= 18) {
permissionsList.add("android.permission.WRITE_EXTERNAL_STORAGE");
}
if (Build.VERSION.SDK_INT >= 23) {
permissionsList.add("android.permission.ACCESS_FINE_LOCATION");
}
apk.requestedPermissions = permissionsList.toArray(new String[permissionsList.size()]);
Uri uri = Uri.fromFile(minMaxApk);
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
apkVerifier.verifyApk();
permissionsList.add("ADDITIONAL_PERMISSION");
apk.requestedPermissions = permissionsList.toArray(new String[permissionsList.size()]);
apkVerifier.verifyApk();
}
@Test
public void testWithPrefix() {
Apk apk = new Apk();
@ -151,8 +203,9 @@ public class ApkVerifierTest {
* Additional permissions are okay. The user is simply
* warned about a permission that is not used inside the apk
*/
@Test
public void testAdditionalPermission() {
@Test(expected = ApkVerifier.ApkPermissionUnequalException.class)
public void testAdditionalPermission()
throws ApkVerifier.ApkPermissionUnequalException, ApkVerifier.ApkVerificationException {
Apk apk = new Apk();
apk.packageName = "org.fdroid.permissions.sdk14";
apk.targetSdkVersion = 14;
@ -173,19 +226,12 @@ public class ApkVerifierTest {
"android.permission.WRITE_SYNC_SETTINGS",
"android.permission.WRITE_CALL_LOG", // implied-permission!
"android.permission.READ_CALL_LOG", // implied-permission!
"NEW_PERMISSION",
"android.permission.FAKE_NEW_PERMISSION",
};
Uri uri = Uri.fromFile(sdk14Apk);
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
try {
apkVerifier.verifyApk();
} catch (ApkVerifier.ApkVerificationException | ApkVerifier.ApkPermissionUnequalException e) {
e.printStackTrace();
fail(e.getMessage());
}
apkVerifier.verifyApk();
}
/**
@ -231,4 +277,73 @@ public class ApkVerifierTest {
}
}
@Test
public void testExtendedPerms() throws IOException,
ApkVerifier.ApkPermissionUnequalException, ApkVerifier.ApkVerificationException {
RepoDetails actualDetails = getFromFile(extendedPermsXml);
HashSet<String> expectedSet = new HashSet<>(Arrays.asList(new String[]{
"android.permission.ACCESS_NETWORK_STATE",
"android.permission.ACCESS_WIFI_STATE",
"android.permission.INTERNET",
"android.permission.READ_SYNC_STATS",
"android.permission.READ_SYNC_SETTINGS",
"android.permission.WRITE_SYNC_SETTINGS",
"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS",
"android.permission.READ_CONTACTS",
"android.permission.WRITE_CONTACTS",
"android.permission.READ_CALENDAR",
"android.permission.WRITE_CALENDAR",
}));
if (Build.VERSION.SDK_INT <= 18) {
expectedSet.add("android.permission.READ_EXTERNAL_STORAGE");
expectedSet.add("android.permission.WRITE_EXTERNAL_STORAGE");
}
if (Build.VERSION.SDK_INT <= 22) {
expectedSet.add("android.permission.GET_ACCOUNTS");
expectedSet.add("android.permission.AUTHENTICATE_ACCOUNTS");
expectedSet.add("android.permission.MANAGE_ACCOUNTS");
}
if (Build.VERSION.SDK_INT >= 23) {
expectedSet.add("android.permission.CAMERA");
if (Build.VERSION.SDK_INT <= 23) {
expectedSet.add("android.permission.CALL_PHONE");
}
}
Apk apk = actualDetails.apks.get(0);
HashSet<String> actualSet = new HashSet<>(Arrays.asList(apk.requestedPermissions));
for (String permission : expectedSet) {
if (!actualSet.contains(permission)) {
Log.i(TAG, permission + " in expected but not actual! (android-"
+ Build.VERSION.SDK_INT + ")");
}
}
for (String permission : actualSet) {
if (!expectedSet.contains(permission)) {
Log.i(TAG, permission + " in actual but not expected! (android-"
+ Build.VERSION.SDK_INT + ")");
}
}
String[] expectedPermissions = expectedSet.toArray(new String[expectedSet.size()]);
assertTrue(ApkVerifier.requestedPermissionsEqual(expectedPermissions, apk.requestedPermissions));
String[] badPermissions = Arrays.copyOf(expectedPermissions, expectedPermissions.length + 1);
assertFalse(ApkVerifier.requestedPermissionsEqual(badPermissions, apk.requestedPermissions));
badPermissions[badPermissions.length - 1] = "notarealpermission";
assertFalse(ApkVerifier.requestedPermissionsEqual(badPermissions, apk.requestedPermissions));
Uri uri = Uri.fromFile(extendedPermissionsApk);
ApkVerifier apkVerifier = new ApkVerifier(instrumentation.getContext(), uri, apk);
apkVerifier.verifyApk();
}
@NonNull
private RepoDetails getFromFile(File indexFile) throws IOException {
InputStream inputStream = null;
try {
inputStream = new FileInputStream(indexFile);
return RepoDetails.getFromFile(inputStream, Repo.PUSH_REQUEST_IGNORE);
} finally {
Utils.closeQuietly(inputStream);
}
}
}

View File

@ -19,6 +19,7 @@
package org.fdroid.fdroid;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@ -32,7 +33,9 @@ import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.regex.Pattern;
/**
* Parses the index.xml into Java data structures.
@ -57,7 +60,14 @@ public class RepoXMLHandler extends DefaultHandler {
private String repoDescription;
private String repoName;
// the X.509 signing certificate stored in the header of index.xml
/**
* Set of requested permissions per package/APK
*/
private final HashSet<String> requestedPermissionsSet = new HashSet<>();
/**
* the X.509 signing certificate stored in the header of index.xml
*/
private String repoSigningCert;
private final StringBuilder curchars = new StringBuilder();
@ -89,6 +99,9 @@ public class RepoXMLHandler extends DefaultHandler {
if ("application".equals(localName) && curapp != null) {
onApplicationParsed();
} else if ("package".equals(localName) && curapk != null && curapp != null) {
int size = requestedPermissionsSet.size();
curapk.requestedPermissions = requestedPermissionsSet.toArray(new String[size]);
requestedPermissionsSet.clear();
apksList.add(curapk);
curapk = null;
} else if ("repo".equals(localName)) {
@ -157,8 +170,8 @@ public class RepoXMLHandler extends DefaultHandler {
case ApkTable.Cols.ADDED_DATE:
curapk.added = Utils.parseDate(str, null);
break;
case ApkTable.Cols.REQUESTED_PERMISSIONS:
curapk.requestedPermissions = Utils.parseCommaSeparatedString(str);
case "permissions": // together with <uses-permissions* makes ApkTable.Cols.REQUESTED_PERMISSIONS
addCommaSeparatedPermissions(str);
break;
case ApkTable.Cols.FEATURES:
curapk.features = Utils.parseCommaSeparatedString(str);
@ -248,6 +261,42 @@ public class RepoXMLHandler extends DefaultHandler {
}
}
private static final Pattern OLD_FDROID_PERMISSION = Pattern.compile("[A-Z_]+");
/**
* It appears that the default Android permissions in android.Manifest.permissions
* are prefixed with "android.permission." and then the constant name.
* FDroid just includes the constant name in the apk list, so we prefix it
* with "android.permission."
*
* @see <a href="https://gitlab.com/fdroid/fdroidserver/blob/1afa8cfc/update.py#L91">
* More info into index - size, permissions, features, sdk version</a>
*/
public static String fdroidToAndroidPermission(String permission) {
if (OLD_FDROID_PERMISSION.matcher(permission).matches()) {
return "android.permission." + permission;
}
return permission;
}
private void addRequestedPermission(String permission) {
requestedPermissionsSet.add(permission);
}
private void addCommaSeparatedPermissions(String permissions) {
String[] array = Utils.parseCommaSeparatedString(permissions);
if (array != null) {
for (String permission : array) {
requestedPermissionsSet.add(fdroidToAndroidPermission(permission));
}
}
}
private void removeRequestedPermission(String permission) {
requestedPermissionsSet.remove(permission);
}
private void onApplicationParsed() {
receiver.receiveApp(curapp, apksList);
curapp = null;
@ -308,6 +357,24 @@ public class RepoXMLHandler extends DefaultHandler {
} else if ("hash".equals(localName) && curapk != null) {
currentApkHashType = attributes.getValue("", "type");
} else if ("uses-permission".equals(localName) && curapk != null) {
String maxSdkVersion = attributes.getValue("maxSdkVersion");
if (maxSdkVersion == null || Build.VERSION.SDK_INT <= Integer.valueOf(maxSdkVersion)) {
addRequestedPermission(attributes.getValue("name"));
} else {
removeRequestedPermission(attributes.getValue("name"));
}
} else if ("uses-permission-sdk-23".equals(localName) && curapk != null) {
String maxSdkVersion = attributes.getValue("maxSdkVersion");
if (Build.VERSION.SDK_INT >= 23 &&
(maxSdkVersion == null || Build.VERSION.SDK_INT <= Integer.valueOf(maxSdkVersion))) {
addRequestedPermission(attributes.getValue("name"));
} else {
removeRequestedPermission(attributes.getValue("name"));
}
} else if ("uses-feature".equals(localName) && curapk != null) {
System.out.println("TODO startElement " + uri + " " + localName + " " + qName);
// TODO
}
curchars.setLength(0);
}

View File

@ -7,11 +7,11 @@ import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import org.fdroid.fdroid.RepoXMLHandler;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.Schema.ApkTable.Cols;
import java.io.File;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
@ -132,7 +132,7 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
apkName = cursor.getString(i);
break;
case Cols.REQUESTED_PERMISSIONS:
requestedPermissions = Utils.parseCommaSeparatedString(cursor.getString(i));
requestedPermissions = convertToRequestedPermissions(cursor.getString(i));
break;
case Cols.NATIVE_CODE:
nativecode = Utils.parseCommaSeparatedString(cursor.getString(i));
@ -235,44 +235,6 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
return new File(App.getObbDir(packageName), obbPatchFile);
}
public ArrayList<String> getFullPermissionList() {
if (this.requestedPermissions == null) {
return new ArrayList<>();
}
ArrayList<String> permissionsFull = new ArrayList<>();
for (String perm : this.requestedPermissions) {
permissionsFull.add(fdroidToAndroidPermission(perm));
}
return permissionsFull;
}
public String[] getFullPermissionsArray() {
ArrayList<String> fullPermissions = getFullPermissionList();
return fullPermissions.toArray(new String[fullPermissions.size()]);
}
public HashSet<String> getFullPermissionsSet() {
return new HashSet<>(getFullPermissionList());
}
/**
* It appears that the default Android permissions in android.Manifest.permissions
* are prefixed with "android.permission." and then the constant name.
* FDroid just includes the constant name in the apk list, so we prefix it
* with "android.permission."
*
* @see <a href="https://gitlab.com/fdroid/fdroidserver/blob/1afa8cfc/update.py#L91">
* More info into index - size, permissions, features, sdk version</a>
*/
private static String fdroidToAndroidPermission(String permission) {
if (!permission.contains(".")) {
return "android.permission." + permission;
}
return permission;
}
@Override
public String toString() {
return toContentValues().toString();
@ -393,4 +355,17 @@ public class Apk extends ValueObject implements Comparable<Apk>, Parcelable {
return new Apk[size];
}
};
private String[] convertToRequestedPermissions(String permissionsFromDb) {
String[] array = Utils.parseCommaSeparatedString(permissionsFromDb);
if (array != null) {
HashSet<String> requestedPermissionsSet = new HashSet<>();
for (String permission : array) {
requestedPermissionsSet.add(RepoXMLHandler.fdroidToAndroidPermission(permission));
}
return requestedPermissionsSet.toArray(new String[requestedPermissionsSet.size()]);
}
return null;
}
}

View File

@ -80,18 +80,10 @@ class ApkVerifier {
}
// verify permissions, important for unattended installer
HashSet<String> localPermissions = getLocalPermissionsSet(localApkInfo);
HashSet<String> expectedPermissions = expectedApk.getFullPermissionsSet();
Utils.debugLog(TAG, "localPermissions: " + localPermissions);
Utils.debugLog(TAG, "expectedPermissions: " + expectedPermissions);
// NOTE: Some permissions could have a maxSdkVersion < current sdk version
// and are thus not parsed by pm.getPackageArchiveInfo().
// Thus, containsAll() instead of equals() is used!
// See also https://gitlab.com/fdroid/fdroidclient/issues/703
if (!expectedPermissions.containsAll(localPermissions)) {
throw new ApkPermissionUnequalException(
"Permissions of the apk file are not a true subset of the permissions listed by the repo," +
" i.e., some permissions have not been shown to the user!");
Utils.debugLog(TAG, "localPermissions: " + TextUtils.join("\n", localApkInfo.requestedPermissions));
Utils.debugLog(TAG, "expectedPermissions: " + TextUtils.join("\n", expectedApk.requestedPermissions));
if (!requestedPermissionsEqual(expectedApk.requestedPermissions, localApkInfo.requestedPermissions)) {
throw new ApkPermissionUnequalException("Permissions in APK and index.xml do not match!");
}
int localTargetSdkVersion = localApkInfo.applicationInfo.targetSdkVersion;
@ -106,13 +98,24 @@ class ApkVerifier {
}
}
private HashSet<String> getLocalPermissionsSet(PackageInfo localApkInfo) {
String[] localPermissions = localApkInfo.requestedPermissions;
if (localPermissions == null) {
return new HashSet<>();
/**
* Compares to sets of APK permissions to see if they are an exact match. The
* data format is {@link String} arrays but they are in effect sets. This is the
* same data format as {@link android.content.pm.PackageInfo#requestedPermissions}
*/
public static boolean requestedPermissionsEqual(String[] expected, String[] actual) {
if (expected == null && actual == null) {
return true;
}
return new HashSet<>(Arrays.asList(localApkInfo.requestedPermissions));
if (expected == null || actual == null) {
return false;
}
if (expected.length != actual.length) {
return false;
}
HashSet<String> expectedSet = new HashSet<>(Arrays.asList(expected));
HashSet<String> actualSet = new HashSet<>(Arrays.asList(actual));
return expectedSet.equals(actualSet);
}
public static class ApkVerificationException extends Exception {

View File

@ -39,7 +39,7 @@ public class AppDiff {
pkgInfo = new PackageInfo();
pkgInfo.packageName = apk.packageName;
pkgInfo.applicationInfo = new ApplicationInfo();
pkgInfo.requestedPermissions = apk.getFullPermissionsArray();
pkgInfo.requestedPermissions = apk.requestedPermissions;
init();
}

View File

@ -69,6 +69,17 @@ public class RepoXMLHandlerTest {
ShadowLog.stream = System.out;
}
@Test
public void testExtendedPerms() throws IOException {
Repo expectedRepo = new Repo();
expectedRepo.name = "F-Droid";
expectedRepo.signingCertificate = "3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef";
expectedRepo.description = "This is just a test of the extended permissions attributes.";
expectedRepo.timestamp = 1467169032;
RepoDetails actualDetails = getFromFile("extendedPerms.xml");
handlerTestSuite(expectedRepo, actualDetails, 2, 6, 14, 16);
}
@Test
public void testObbIndex() throws IOException {
writeResourceToObbDir("main.1101613.obb.main.twoversions.obb");

View File

@ -0,0 +1,195 @@
<?xml version="1.0" encoding="utf-8"?>
<fdroid>
<repo name="F-Droid" icon="fdroid-icon.png" maxage="14"
pubkey="3082035e30820246a00302010202044c49cd00300d06092a864886f70d01010505003071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b73301e170d3130303732333137313032345a170d3337313230383137313032345a3071310b300906035504061302554b3110300e06035504081307556e6b6e6f776e3111300f0603550407130857657468657262793110300e060355040a1307556e6b6e6f776e3110300e060355040b1307556e6b6e6f776e311930170603550403131043696172616e2047756c746e69656b7330820122300d06092a864886f70d01010105000382010f003082010a028201010096d075e47c014e7822c89fd67f795d23203e2a8843f53ba4e6b1bf5f2fd0e225938267cfcae7fbf4fe596346afbaf4070fdb91f66fbcdf2348a3d92430502824f80517b156fab00809bdc8e631bfa9afd42d9045ab5fd6d28d9e140afc1300917b19b7c6c4df4a494cf1f7cb4a63c80d734265d735af9e4f09455f427aa65a53563f87b336ca2c19d244fcbba617ba0b19e56ed34afe0b253ab91e2fdb1271f1b9e3c3232027ed8862a112f0706e234cf236914b939bcf959821ecb2a6c18057e070de3428046d94b175e1d89bd795e535499a091f5bc65a79d539a8d43891ec504058acb28c08393b5718b57600a211e803f4a634e5c57f25b9b8c4422c6fd90203010001300d06092a864886f70d0101050500038201010008e4ef699e9807677ff56753da73efb2390d5ae2c17e4db691d5df7a7b60fc071ae509c5414be7d5da74df2811e83d3668c4a0b1abc84b9fa7d96b4cdf30bba68517ad2a93e233b042972ac0553a4801c9ebe07bf57ebe9a3b3d6d663965260e50f3b8f46db0531761e60340a2bddc3426098397fda54044a17e5244549f9869b460ca5e6e216b6f6a2db0580b480ca2afe6ec6b46eedacfa4aa45038809ece0c5978653d6c85f678e7f5a2156d1bedd8117751e64a4b0dcd140f3040b021821a8d93aed8d01ba36db6c82372211fed714d9a32607038cdfd565bd529ffc637212aaa2c224ef22b603eccefb5bf1e085c191d4b24fe742b17ab3f55d4e6f05ef"
timestamp="1467169032" url="http://f-droid.org/repo" version="16">
<description>
This is just a test of the extended permissions attributes.
</description>
</repo>
<application id="at.bitfire.davdroid">
<id>at.bitfire.davdroid</id>
<added>2013-10-13</added>
<lastupdated>2016-09-21</lastupdated>
<name>DAVdroid</name>
<summary>Contacts and Calendar sync</summary>
<icon>at.bitfire.davdroid.116.png</icon>
<desc>
<p>DAVdroid is a CalDAV/CardDAV synchronisation adapter for Android 4+ devices.</p>
<p>Use it with your own server (like<a href="https://owncloud.org/">OwnCloud</a>,<a
href="http://baikal-server.com/">Baïkal</a>,
<a href="http://www.davical.org/">DAViCal</a>
or<a href="http://radicale.org/">radiCALe</a>) or with a trusted hoster to keep your
contacts and events under your control.
</p>
<p>Integrates natively in Android calendar/contact apps. See homepage for configuration
details, including info about self-signed certificates.
</p>
<p>For a comparison of server software, see the<a
href="https://wiki.debian.org/Groupware">
Debian wiki</a>.
</p>
</desc>
<license>GPLv3</license>
<categories>Internet</categories>
<category>Internet</category>
<web>https://davdroid.bitfire.at/</web>
<source>https://davdroid.bitfire.at/source/</source>
<tracker>https://davdroid.bitfire.at/forums/</tracker>
<changelog>https://gitlab.com/bitfireAT/davdroid/tags</changelog>
<donate>https://davdroid.bitfire.at/donate/</donate>
<bitcoin>1KSCy7RHztKuhW9fLLaUYqdwdC2iwbejZU</bitcoin>
<flattr>2100160</flattr>
<marketversion>1.3.1-ose</marketversion>
<marketvercode>116</marketvercode>
<package>
<version>1.3.1-ose</version>
<versioncode>116</versioncode>
<apkname>at.bitfire.davdroid_116.apk</apkname>
<srcname>at.bitfire.davdroid_116_src.tar.gz</srcname>
<hash type="sha256">f1aa02257e99c167d2ea9b0e9525c3ce7c181fe2e7f4dd00b65dd81ed2e27a62
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3298864</size>
<sdkver>14</sdkver>
<targetSdkVersion>24</targetSdkVersion>
<added>2016-09-21</added>
<permissions>
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,org.dmfs.permission.READ_TASKS,WRITE_CONTACTS,ACCESS_WIFI_STATE,REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS,READ_CALENDAR,org.dmfs.permission.WRITE_TASKS
</permissions>
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
</package>
<package>
<version>1.3-ose</version>
<versioncode>114</versioncode>
<apkname>at.bitfire.davdroid_114.apk</apkname>
<srcname>at.bitfire.davdroid_114_src.tar.gz</srcname>
<hash type="sha256">aaf956539aad7400269997bf1a6689f191b592e70146ffe5484312f9375df9d9
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3295326</size>
<sdkver>14</sdkver>
<targetSdkVersion>24</targetSdkVersion>
<added>2016-09-05</added>
<permissions>
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,org.dmfs.permission.READ_TASKS,WRITE_CONTACTS,ACCESS_WIFI_STATE,REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS,READ_CALENDAR,org.dmfs.permission.WRITE_TASKS
</permissions>
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
</package>
<package>
<version>1.2.3-ose</version>
<versioncode>112</versioncode>
<apkname>at.bitfire.davdroid_112.apk</apkname>
<srcname>at.bitfire.davdroid_112_src.tar.gz</srcname>
<hash type="sha256">045480c571f4dfabb23a5efb803b594c432c20ab33b6eb575b4476a8df22bb05
</hash>
<sig>03542175324d067b4c36582242f8aecc</sig>
<size>3172611</size>
<sdkver>14</sdkver>
<targetSdkVersion>24</targetSdkVersion>
<added>2016-08-12</added>
<permissions>
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,org.dmfs.permission.READ_TASKS,WRITE_CONTACTS,ACCESS_WIFI_STATE,REQUEST_IGNORE_BATTERY_OPTIMIZATIONS,WRITE_CALENDAR,READ_CONTACTS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS,READ_CALENDAR,org.dmfs.permission.WRITE_TASKS
</permissions>
<uses-permission name="android.permission.READ_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.WRITE_EXTERNAL_STORAGE" maxSdkVersion="18" />
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
</package>
</application>
<application id="com.murrayc.galaxyzoo.app">
<id>com.murrayc.galaxyzoo.app</id>
<added>2014-11-23</added>
<lastupdated>2016-09-02</lastupdated>
<name>Galaxy Zoo</name>
<summary>Help to classify galaxies</summary>
<icon>com.murrayc.galaxyzoo.app.64.png</icon>
<desc>
<p>Classify
<a href="http://www.galaxyzoo.org/">Galaxy Zoo</a>
subjects. Official approved by the<a href="https://www.zooniverse.org/">Zooniverse
project</a>.
</p>
<p>Asks you questions about a picture of a galaxy, with each question depending on the
previous question. This "Citizen Science" helps astronomers to analyze the huge
amount of images of galaxies provided, for instance, by the Hubble Space Telescope.
</p>
</desc>
<license>GPLv3</license>
<categories>Science &amp; Education</categories>
<category>Science &amp; Education</category>
<web />
<source>https://github.com/murraycu/android-galaxyzoo</source>
<tracker>https://github.com/murraycu/android-galaxyzoo/issues</tracker>
<marketversion>1.64</marketversion>
<marketvercode>64</marketvercode>
<package>
<version>1.64</version>
<versioncode>64</versioncode>
<apkname>com.murrayc.galaxyzoo.app_64.apk</apkname>
<srcname>com.murrayc.galaxyzoo.app_64_src.tar.gz</srcname>
<hash type="sha256">6f10487c8ef84078232aafe115228c6252ef982a228666454005b8d62c27fe42
</hash>
<sig>f1a84be1ce965e270f64318cfdb41861</sig>
<size>4095584</size>
<sdkver>11</sdkver>
<targetSdkVersion>24</targetSdkVersion>
<added>2016-09-02</added>
<permissions>
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,USE_CREDENTIALS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS
</permissions>
<uses-permission name="android.permission.USE_CREDENTIALS" maxSdkVersion="22" />
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
</package>
<package>
<version>1.61</version>
<versioncode>61</versioncode>
<apkname>com.murrayc.galaxyzoo.app_61.apk</apkname>
<srcname>com.murrayc.galaxyzoo.app_61_src.tar.gz</srcname>
<hash type="sha256">c35bc15885d57a2ddfcb2c702c9147b63b63a9765a6eeacf98d6a1534f6f8ff2
</hash>
<sig>f1a84be1ce965e270f64318cfdb41861</sig>
<size>4588315</size>
<sdkver>11</sdkver>
<targetSdkVersion>24</targetSdkVersion>
<added>2016-08-26</added>
<permissions>
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,USE_CREDENTIALS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS
</permissions>
<uses-permission name="android.permission.USE_CREDENTIALS" maxSdkVersion="22" />
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
</package>
<package>
<version>1.59</version>
<versioncode>59</versioncode>
<apkname>com.murrayc.galaxyzoo.app_59.apk</apkname>
<srcname>com.murrayc.galaxyzoo.app_59_src.tar.gz</srcname>
<hash type="sha256">719d1440dd2f16ad29b45edba5b43262f22ef68cee0c6b478e40fe8eb0e53227
</hash>
<sig>f1a84be1ce965e270f64318cfdb41861</sig>
<size>4116175</size>
<sdkver>11</sdkver>
<targetSdkVersion>24</targetSdkVersion>
<added>2016-08-25</added>
<permissions>
READ_EXTERNAL_STORAGE,WRITE_SYNC_SETTINGS,ACCESS_NETWORK_STATE,WRITE_EXTERNAL_STORAGE,USE_CREDENTIALS,READ_SYNC_SETTINGS,MANAGE_ACCOUNTS,INTERNET,AUTHENTICATE_ACCOUNTS,GET_ACCOUNTS
</permissions>
<uses-permission name="android.permission.USE_CREDENTIALS" maxSdkVersion="22" />
<uses-permission name="android.permission.GET_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.AUTHENTICATE_ACCOUNTS" maxSdkVersion="22" />
<uses-permission name="android.permission.MANAGE_ACCOUNTS" maxSdkVersion="22" />
</package>
</application>
</fdroid>