APK安装时的过滤方式:包名白名单、证书认证

来源:互联网 发布:域名过户需要手续费吗 编辑:程序博客网 时间:2024/05/22 15:45

有些项目不允许所有APK都拥有安装权限,例如apk只能通过应用商城来安装或者升级,只允许某些特定的apk自升级,不允许pm install等。这就需要添加安装权限白名单来控制。

先介绍android中常用的几种安装方式,好针对这几种进行修改 
1、 直接调用安装接口。

Uri mPackageURI = Uri.fromFile(new File(Environment.getExternalStorageDirectory() + apkName));int installFlags = 0;PackageManager pm = getPackageManager();try{    PackageInfo pi = pm.getPackageInfo(packageName,    PackageManager.GET_UNINSTALLED_PACKAGES);    if(pi != null) {        installFlags |= PackageManager.REPLACE_EXISTING_PACKAGE;    }}catch (NameNotFoundException e){}PackageInstallObserver observer = new PackageInstallObserver();pm.installPackage(mPackageURI, observer, installFlags);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这种修改需要直接修改packageManagerService。对应下面的第一种方法。

2、通过Intent机制,调用packageInstaller进行安装。

String fileName = Environment.getExternalStorageDirectory() + apkName;Uri uri = Uri.fromFile(new File(fileName));Intent intent = new Intent(Intent.ACTION_VIEW);intent.setDataAndType(Uri, application/vnd.android.package-archive");startActivity(intent);
  • 1
  • 2
  • 3
  • 4
  • 5

因为应用是通过packageInstaller进行安装的,相当于隔了一层代理,所以在packageManagerService并无法判断正在调用安装的是哪个app,只能在packageInstaller中进行修改,参考下面的第二中方法。

3、通过命令进行安装 pm install,参考第三种方法修改。

1、packageManagerService修改

packageManagerService的修改,我们在其中添加接口及代码来控制apk安装。

1)增加以下函数:

     private boolean isWhiteListApp(String pkgName){        final File systemDir;        final File whitelistFile;        final ArrayList<String> whiteListApps = new ArrayList<String>();        systemDir = new File("/system/", "etc");        whitelistFile = new File(systemDir, "whitelistapps");        if (!whitelistFile.exists()) {            return false;        }        try {            whiteListApps.clear();            BufferedReader br = new BufferedReader(new FileReader(whitelistFile));            String line = br.readLine();            while (line != null) {                //Log.d(TAG, "whitelistapps readLine:" + line);                whiteListApps.add(line);                line = br.readLine();            }            br.close();        } catch (IOException e) {            Log.e(TAG, "IO Exception happened while reading whitelistapps");            e.printStackTrace();            return false;        }        Iterator<String> it = whiteListApps.iterator();        while (it.hasNext()) {            String whitelisItem = it.next();            if (pkgName.equals(whitelisItem)) {                return true;            }        }        return false;    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33

isWhiteListApp函数会去读取白名单文件/system/etc/whitelistapps,然后和我们传进来的包名进行匹配,在白名单中返回true,其他情况均返回false。

2)获取调用的包名判断是否在白名单中

接下来要在installPackageLI函数对调用安装的apk进行匹配,判断是否在白名单中,如果不在的话则提示错误。

    private void installPackageLI(InstallArgs args, PackageInstalledInfo res) {        ......        try {            pp.collectCertificates(pkg, parseFlags);            pp.collectManifestDigest(pkg);        } catch (PackageParserException e) {            res.setError("Failed collect during installPackageLI", e);            return;        }        // longroey++ start        if(!isWhiteListApp(pkg.packageName)) {            res.setError(PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,                    "app is not in the whitelist. packageName:" + pkg.packageName);            return;        }        // longroey++ end        /* If the installer passed in a manifest digest, compare it now. */        if (args.manifestDigest != null) {            if (DEBUG_INSTALL) {                final String parsedManifest = pkg.manifestDigest == null ? "null"                        : pkg.manifestDigest.toString();                Slog.d(TAG, "Comparing manifests: " + args.manifestDigest.toString() + " vs. "                        + parsedManifest);            }            if (!args.manifestDigest.equals(pkg.manifestDigest)) {                res.setError(INSTALL_FAILED_PACKAGE_CHANGED, "Manifest digest changed");                return;            }        } else if (DEBUG_INSTALL) {            final String parsedManifest = pkg.manifestDigest == null                    ? "null" : pkg.manifestDigest.toString();            Slog.d(TAG, "manifestDigest was not present, but parser got: " + parsedManifest);        }        ......    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53

3) 增加白名单
/system/etc/whitelistapps内容如下,在编译时可以在mk中修改拷贝到etc目录下,例如下面就是允许这三个包名有安装权限。

com.xxx.xxx1 
com.xxx.xxx2 
com.xxx.xxx3

2、packageInstaller的修改

还是参考packageManagerService的修改,增加isWhiteListApp函数,去读取白名单文件/system/etc/whitelistapps,然后进行包名匹配,在白名单中返回true,其他情况均返回false。

我们在packageInstaller的PackageInstallerActivity.java中增加以下修改// add for installer enable/disable ,不在白名单中的app,会直接提示不允许安装后退出。

    protected void onCreate(Bundle icicle) {        super.onCreate(icicle);        // get intent information        final Intent intent = getIntent();        mPackageURI = intent.getData();        mPm = getPackageManager();        final int uid = getOriginatingUid(intent);        String callingApp = mPm.getNameForUid(uid);        final File sourceFile = new File(mPackageURI.getPath());        PackageParser.Package parsed = PackageUtil.getPackageInfo(sourceFile);        mPkgInfo = PackageParser.generatePackageInfo(parsed, null,            PackageManager.GET_PERMISSIONS, 0, 0, null,            new PackageUserState());        // add for installer enable/disable         if (!isWhiteListApp(callingApp)) {            Toast.makeText(this, R.string.install_not_allow, Toast.LENGTH_LONG).show();            this.finish();        }        mOriginatingURI = intent.getParcelableExtra(Intent.EXTRA_ORIGINATING_URI); ...省略
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22

3、pm install的修改

禁止pm install,因为有些APK安装竟然是调用pm install命令去安装的。 
修改要在pm.java修改,修改方法和上面基本一致。 
可以看到,pm install其实调用的是run再去判断参数。

    public static void main(String[] args) {        new Pm().run(args);    }public void run(String[] args) { ...省略        if ("install".equals(op)) {            runInstall();            return;        } ...省略
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

那我们要添加的话,先获取app名,再和packageManagerService一样,增加isWhiteListApp去判断是不是要调用runInstall()就OK了。

String callingApp = "";try {    callingApp = mPm.getNameForUid(Binder.getCallingUid()); } catch(RemoteException re) {    Log.e("Pm", Log.getStackTraceString(new Throwable())); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6



APK安装时的过滤方式:包名白名单、证书认证

1.定义一些全局变量,文件位置:

Build.java (frameworks\base\core\java\android\os) 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
 * 包管理方式名称<br>
 *     whitelist: 白名单方式
 *     certificate: 证书认证方式
 *     none: 不进行管理
 */
public static String packageManage = "none";
/**
 * 允许 Launch 显示的 APP 及 APP 白名单
 */
public static String[] packageAllow = new String[]{ "com.baidu.searchbox",
                            "com.thinta.product.thintazlib",
                            "com.thinta.product.x4usertool"};
/**
 * 允许 Launch 显示的 APP的 证书存放路径
 */
public static String certificatePath = "/system/etc/security/media.zip";

 

2.修改安装APK过程,在安装过程添加验证

修改文件的位置:

PackageManagerService.java (frameworks\base\services\core\java\com\android\server\pm) 

首先添加一个函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
private static HashSet<X509Certificate> getTrustedCerts(File keystore)
        throws IOException, GeneralSecurityException {
        HashSet<X509Certificate> trusted = new HashSet<X509Certificate>();
        if (keystore == null) {
            return trusted;
        }
        ZipFile zip = new ZipFile(keystore);
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            Enumeration<? extends ZipEntry> entries = zip.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = entries.nextElement();
                InputStream is = zip.getInputStream(entry);
                try {
                    trusted.add((X509Certificate) cf.generateCertificate(is));
                finally {
                    is.close();
                }
            }
        finally {
            zip.close();
        }
        return trusted;
    }

修改的函数:private void installPackageLI(InstallArgs args, PackageInstalledInfo res) 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
第一处修改:
     if(Build.ThintaCust.packageManage.equals("certificate"))
            tmp_flags = PackageManager.GET_SIGNATURES;
        final int parseFlags = mDefParseFlags | PackageParser.PARSE_CHATTY
                | (forwardLocked ? PackageParser.PARSE_FORWARD_LOCK : 0)
                | (onSd ? PackageParser.PARSE_ON_SDCARD : 0) | tmp_flags;
 
第二处修改:
        if(Build.ThintaCust.packageManage.equals("none")){
            Log.d("XYP_DEBUG""packageManage = none  \n");
        }else if(Build.ThintaCust.packageManage.equals("whitelist")){
            Log.d("XYP_DEBUG""packageManage = whitelist  \n");
            List<String> list = Arrays.asList(Build.ThintaCust.packageAllow);
            if(list.contains(pkg.packageName)){
                Log.d("XYP_DEBUG""can install \n");
            }else{
                Log.d("XYP_DEBUG""forbid install \n");
                res.setError(PackageManager.INSTALL_FAILED_USER_RESTRICTED, "installPackageLI, forbid install");
                return;
            }
        }else if(Build.ThintaCust.packageManage.equals("certificate")){
            int verify_pass = 0;
            try{
                File file = new File(Build.ThintaCust.certificatePath);
                HashSet<X509Certificate> trusted = getTrustedCerts(file);
                CertificateFactory cf = CertificateFactory.getInstance("X.509");
 
                for (X509Certificate c : trusted) {
                    String tmp_public_key = c.getPublicKey().toString();
                    for(Signature sig : pkg.mSignatures)
                    {
                        X509Certificate cert = (X509Certificate)cf.generateCertificate(new ByteArrayInputStream(sig.toByteArray()));
                        String tmp_key = cert.getPublicKey().toString();
                        if(tmp_public_key.equals(tmp_key)){
                            verify_pass = 1;
                            break;
                        }
                    }
                    if(verify_pass == 1)
                        break;
                }
                if(verify_pass != 1){
                    Log.d("XYP_DEBUG""forbid install \n");
                    res.setError(PackageManager.INSTALL_FAILED_USER_RESTRICTED, "installPackageLI, forbid install");
                    return;
                }
            }catch(FileNotFoundException e){
                Log.d("XYP_DEBUG", e.toString());
            }catch(CertificateException e){
                Log.d("XYP_DEBUG", e.toString());
            }catch(IOException e){
                Log.d("XYP_DEBUG", e.toString());
            }catch(GeneralSecurityException e){
                Log.d("XYP_DEBUG", e.toString());
            }
        }

3.证书的压缩方式:

zip -r media.zip media.x509.pem

直接用命令把*.x509.pem 打包成zip文件,然后放到目标板的合适位置;

用第一步中的certificatePath指向存放该zip文件的位置。