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

有些项目不允许所有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);

这种修改需要直接修改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);

因为应用是通过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;
    }

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);
        }
 
        ......
 
    }

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);
 ...省略

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;
        }
 ...省略

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

String callingApp = "";
try {
    callingApp = mPm.getNameForUid(Binder.getCallingUid());
 } catch(RemoteException re) {
    Log.e("Pm", Log.getStackTraceString(new Throwable())); 
}

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

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

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

/**
  * 包管理方式名称<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) 

首先添加一个函数:

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) 

第一处修改:

      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文件的位置。 

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值