参考地址:https://blog.csdn.net/long375577908/article/details/78721010
有些项目不允许所有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)
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 3 | 第一处修改: 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文件的位置。