一个权限主要包含三个方面的信息:权限的名称;属于的权限组;保护级别。一个权限组是指把权限按照功能分红的不一样的集合。每个权限组包含若干具体权限,例如在 COST_MONEY 组中包含 android.permission.SEND_SMS , android.permission.CALL_PHONE 等和费用相关的权限。java
每一个权限经过 protectionLevel 来标识保护级别: normal , dangerous , signature , signatureorsystem 。不一样的保护级别表明了程序要使用此权限时的认证方式。 normal 的权限只要申请了就可使用; dangerous 的权限在安装时须要用户确认才可使用; signature 和 signatureorsystem 的权限须要使用者的 app 和系统使用同一个数字证书。linux
Package 的权限信息主要 经过在 AndroidManifest.xml 中经过一些标签来指定。如 <permission> 标签, <permission-group> 标签 <permission-tree> 等标签。若是 package 须要申请使用某个权限,那么须要使用 <use-permission> 标签来指android
permission 的初始化,是指 permission 的向系统申请,系统进行检测并受权,并创建相应的数据结构。绝大多数的状况下 permission 都是从一个 package 中扫描所得,而这发生在 package 安装和升级的时候。通常有以下几种 安装入口:算法
n packageInstaller , package 被下载安装时会触发使用。 packageInstaller 会经过 AppSecurityPermissions 来检查 dangerous 的权限,并对用户给出提示。数组
n pm 命令 。数据结构
n adb install 。最终仍是 调用 pm install 来安装 apk 包。app
n 拷贝即安装。 PackageManagerService 中使用 AppDirObserver 对 /data/app/ 进行监视 ,若是有拷贝即触发安装。ide
这些安装方式 最终都会经过调用 PackageManagerService 中的函数来完成程序的安装。函数
第一步,从 AndroidManifest.xml 中提取 permission 信息。主要提取以下信息:工具
² shared uid
指定与其它 package 共享同一个 uid 。
² permission
提取 permissions 标签指定属性。它使用 permissionInfo 来描述一个权限的基本信息。须要指定 protectedLevel 信息,并指定所属 group 信息。它将被添加到这个 package 的 permissions 这个 list 结构中。
² permission-tree
提取 permissions-tree 标签属性。 permissions-tree 也经过 permissionInfo 来描述,并被添加到 package 的 permissions 这个 list 结构中。 permission-tree 只是一个名字空间,用来向其中动态添加一些所谓 Dynamic 的 permission ,这些 permission 能够动态修改。这些 permission 名称要以 permission-tree 的名称开头。它自己不是一种权限,没有 protectedLevel 和所属 group 。只是保存了所属的 packge 和权限名(带有 package 前缀的)。
² permission-group
定义 permission 组信息,用 PermissionGroup 表示。自己不表明一个权限,会添加进入 package 的 permissionGroups 这个 list 中。
² uses-permission
定义了 package 须要申请的权限名。将权限名添加到 package 的 requestedPermissions 这个 list 中。
² adopt-permissions
将该标签指定的 name 存入 package 的 mAdoptPermissions 这个 list 中。 Name 指定了这个 package 须要从 name 指定的 package 进行权限领养。在 system package 进行升级时使用。
第二步。获取 Package 中的证书,验证,并将签名信息保存在 Package 结构中。
1. 若是该 package 来自 system img (系统 app ),那么只须要从该 Package 的 AndroidManifest.xml 中获取签名信息,而无需验证其完整性。可是若是这个 package 与其它 package 共享一个 uid ,那么这个共享 uid 对应的 sharedUser 中保存的签名与之不一致,那么签名验证失败。
2. 若是是普通的 package ,那么须要提取证书和签名信息,并对文件的完成性进行验证。
第三步。若是是普通的 package ,那么清除 package 的 mAdoptPermissions 字段信息(系统 package 升级才使用)。
第四步。若是在 AndroidManifest.xml 中指定了 shared user ,那么先查看全局 list 中( mSharedUsers )是否该 uid 对应的 SharedUserSetting 数据结构,若没有则新分配一个 uid ,建立 SharedUserSetting 并保存到全局全局 list ( mSharedUsers )中。
mUserIds 保存了系统中已经分配的 uid 对应的 SharedUserSetting 结构。每次分配时老是从第一个开始轮询,找到第一个空闲的位置 i ,而后加上 FIRST_APPLICATION_UID 便可。
第五步。建立 PackageSettings 数据结构。并将 PackageSettings 与 SharedUserSetting 进行绑定。其中 PackageSettings 保存了 SharedUserSetting 结构;而 SharedUserSetting 中会使用 PackageSettings 中的签名信息填充本身内部的签名信息,并将 PackageSettings 添加到一个队列中,表示 PackageSettings 为其中的共享者之一。
在建立时,首先会以 packageName 去全局数据结构 mPackages 中查询是否已经有对应的 PackageSettings 数据结构存在。若是已经存在 PackageSettings 数据结构(好比这个 package 已经被 uninstall ,可是尚未删除数据,此时 package 结构已经被释放)。那么比较该 package 中的签名信息(从 AndroidManifest 中扫描获得)与 PackageSettings 中的签名信息是否匹配。若是不匹配可是为 system package ,那么信任此 package ,并将 package 中的签名信息更新到已有的 PackageSettings 中去,同时若是这个 package 与其它 package 共享了 uid ,并且 shared uid 中保存的签名信息与当前 package 不符,那么签名也验证失败。
第六步。若是 mAdoptPermissions 字段不为空,那么处理 permission 的领养(从指定的 package 对应的 PackageSettings 中,将权限的拥有者修改成当前 package ,通常在 system app 升级的时候才发生,在此以前须要验证当被领养的 package 已经被卸载,即检查 package 数据结构是否存在)。
第七步。添加自定义权限。将 package 中定义的 permissionGroup 添加到全局的列表 mPermissionGroups 中去;将 package 中定义的 permissions 添加到全局的列表中去(若是是 permission-tree 类型,那么添加到 mSettings.mPermissionTrees ,若是是通常的 permission 添加到 mSettings.mPermissions 中)。
第八步。清除不一致的 permission 信息。
1. 清除不一致的 permission-tree 信息。若是该 permission-tree 的 packageSettings 字段为空,说明还未对该 package 进行过解析(若代码执行到此处时 packageSettings 确定已经被建立过),将其 remove 掉。若是 packageSettings 不为空,可是对应的 package 数据结构为空(说明该 package 已经被卸载,但数据还有保留),或者 package 数据结构中根本不含有这个 permission-tree ,那么将这个 permission-tree 清除。
2. 清除不一致的 permission 信息。若是 packageSettings 或者 package 结构为空(未解析该 package 或者被卸载,但数据有保留),或者 package 中根本没有定义该 permission ,那么将该 permission 清除。
第九步。对每个 package 进行轮询,并进行 permission 受权。
1. 对申请的权限进行检查,并更新 grantedPermissions 列表
2. 若是其没有设置 shared user id ,那么将其 gids 初始化为 mGlobalGids ,它从 permission.xml 中读取。
3. 遍历全部申请的权限,进行以下检查
1 )若是是该权限是 normal 或者 dangerous 的。经过检查。
2 )若是权限须要签名验证。若是签名验证经过。还须要进行以下检查
* 若是程序升级,并且是 system package 。那么是否授予该权限要看原来的 package 是否被授予了该权限。若是被授予了,那么经过检查,不然不经过。
* 若是是新安装的。那么检查经过。
4. 若是 3 中检查经过,那么将这个 permission 添加到 package 的 grantedPermissions 列表中,表示这个 permission 申请成功( granted )。申请成功的同时会将这个申请到的 permission 的 gids 添加到这个 package 的 gids 中去。
5. 将 permissionsFixed 字段标准为 ture ,表示这个 packge 的 permission 进行过修正。后续将禁止对非 system 的 app 的权限进行再次修正。
PackageManagerService 提供了 addPermission/ removePermission 接口用来动态添加和删除一些权限。可是这些权限必须是所谓的动态权限( BasePermission.TYPE_DYNAMIC )。
一个 Package 若是要添加 Dynamic permissions ,首先必需要在 manifest 中申明 <permission-tree> 标签,它其实是一个权限的名字空间(例如,“ com.foo.far ”这个权限就是 permission-tree “com.foo ”的成员),自己不是一个权限。一个 Package 只能为本身的 permission-tree 或者拥有相同的 uid 的 package 添加或者删除权限。
Package 不可以经过这种接口去修改在 manifest 中静态申请的权限,不然抛出异常。
首先查找这个 permission 在全局 permission 列表 mSettings.mPermissions 中是否存在。若是存在,并且类型为 BasePermission.TYPE_DYNAMIC 那么根据传入的权限信息修改全局表中的权限信息,并触发 permissions.xml 的持久化。
若是在全局的 permission 列表 mSettings.mPermissions 中没有找到,先找到这个 permission 所在 permissionTree ,而后添加到全局 permission 列表 mSettings.mPermissions 中去,并触发 permissions.xml 的持久化。
下面两个 接口 主要用于 Uri permission 的管理 (其实如今 ActivityManagerService 中)。
// 为指定的 uid 和 targetPkg 添加对某个 content Uri 的读或者写权限。
public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri, int mode) throws RemoteException;
// 清除全部经过 grantUriPermission 对某个 Uri 授予的权限。
public void revokeUriPermission(IApplicationThread caller, Uri uri, int mode) throws RemoteException;
grantUriPermission 主要的实现过程分析。
grantUriPermission 分析:
1. 验证 caller 的 ProcessRecord 和 targetPkg 不为空。不然检测不经过。
2. 验证所请求的 mode 为 Intent.FLAG_GRANT_READ_URI_PERMISSION 或者为 Intent.FLAG_GRANT_WRITE_URI_PERMISSION ,不然不经过。
3. 确保参数 Uri 是一个 content Uri 。不然,则检测不经过。
4. 经过 Uri 获得目标 ContentProvider ,若是不存在,则检测不经过。
5. 从 PackageManagerService 中得到 targetPkg 对应的 uid 。
6. 检查 target uid 所对应的 package 是否真正须要这个权限?
先判断要申请的是读仍是写权限,而后查看对应的 ContentProvider 中对应的 readPermission writePermission 字段是否保存了权限名称。 若是该字段不为空,则以 target uid 和该权限名去PackageManagerService 中去查找该 uid 是否被 granted 了该权限。若是已经得到了该权限,那么无需再去为这个 Activity 去申请这个 Uri 权限了,返回。否者继续执行以下操做。
7. 检查这个 ContentProvider 的 grantUriPermissions 开关变量,是否容许对其它 package 进行权限的 grant 操做。若是禁止,那么抛出异常。
8. 检查这个 ContentProvider 是否设置了 Uri 的过滤类型 uriPermissionPatterns ,若是设置了过滤类型,则将须要申请权限的 Uri 与之匹配。匹配不一样过,则抛出异常。
9. 检查调用者本身是否有权限访问这个 Uri 。若是没有,抛出异常。
10. 从 mGrantedUriPermissions 中取得 target uid 对应的 HashMap<Uri, UriPermission> 数据结构。用 target uid 和 Uri 生成 UriPermission 并保存在 mGrantedUriPermissions 中。
revokeUriPermission 实现分析。
找到该 Uri 对应的 ContentProvider ,而后删除 mGrantedUriPermissions 中与 Uri 对应的全部权限。
这里的动态检查是指是 package 在程序运行过程当中进行某些操做或者数据访问时才进行的 check ,与之对应的是应用程序安装或者升级时 PackageManagerService 经过扫描包中的静态权限信息相对应。
系统与权限 检查 相关的机制的实现主要集中在 PackageManagerService 和 ActivityManagerService 中。 ActivityManagerService 主要负责的是底层的 uid 层次的身份检查; PackageManagerService 则维护了 uid 到本身拥有的和被授予的权限的一张表。在经过 ActivityManagerService 的身份检查后, PackageManagerService 根据请求者的 uid 来查看这张表,判断其是否具备相应的权限。
除此以外, per-URI permission 机制的实现也须要一张表,它维护在 ActivityManagerService 中,它创建了从 content URI 到被受权访问这个 URI 的 component 之间的映射。可是它也须要借助 PackageManagerService 的机制来辅助实现。
Android framework 中提供了一些接口用来对外来的访问(包括本身)进行权限检查 。 这些接口 主要经过 ContextWrapper 提供,具体实如今 ContextImpl 中 。若是 package 接受到外来访问者的操做请求,那么能够调用这些接口进行权限检查。通常状况下能够把这些接口的检查接口分为两种,一种是返回错误,另外一种是抛出异常。
主要包含以下几组:
n permission 和 uid 检查 API
下面这一组接口主要用来检查某个调用(或者是其它 package 或者是本身)是否拥有访问某个 permission 的权限。参数中 pid 和 uid 能够指定,若是没有指定,那么 framework 会经过 Binder 来获取调用者的 uid 和 pid 信息,加以填充。返回值为 PackageManager.PERMISSION_GRANTED 或者 PackageManager.PERMISSION_DENIED 。
public int checkPermission(String permission, int pid, int uid) // 检查某个 uid 和 pid 是否有 permission 权限
public int checkCallingPermission(String permission) // 检查调用者是否有 permission 权限,若是调用者是本身那么返回 PackageManager.PERMISSION_DENIED
public int checkCallingOrSelfPermission(String permission) // 检查本身或者其它调用者是否有 permission 权限
下面这一组和上面相似,若是遇到检查不经过时,会抛出异常,打印消息 。
public void enforcePermission(String permission, int pid, int uid, String message)
public void enforceCallingPermission(String permission, String message)
public void enforceCallingOrSelfPermission(String permission, String message)
n per-URI 检查 API
为某个 package 添加访问 content Uri 的读或者写权限。
public void grantUriPermission(String toPackage, Uri uri, int modeFlags)
public void revokeUriPermission(Uri uri, int modeFlags)
检查某个 pid 和 uid 的 package 是否拥有 uri 的读写权限,返回值表示是否被 granted 。
public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags)
public int checkCallingUriPermission(Uri uri, int modeFlags)
public int checkCallingOrSelfUriPermission(Uri uri, int modeFlags)
public int checkUriPermission(Uri uri, String readPermission,String writePermission, int pid, int uid, int modeFlags)
检查某个 pid 和 uid 的 package 是否拥有 uri 的读写权限,若是失败则抛出异常,打印消息 。
public void enforceUriPermission(Uri uri, int pid, int uid, int modeFlags, String message)
public void enforceCallingUriPermission(Uri uri, int modeFlags, String message)
public void enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message)
public void enforceUriPermission(Uri uri, String readPermission, String writePermission,int pid, int uid, int modeFlags, String message)
ContextImpl.java 中提供的 API ,其实都是由 ActivityManagerService 中的以下几个接口进行的封装。
public int checkPermission(String permission, int pid, int uid) throws RemoteException; // 主要用于通常的 permission 检查
public int checkUriPermission(Uri uri, int pid, int uid, int mode) throws RemoteException; // 主要用于 Content Uri 的 permission 检查
n checkPermission 的实现分析
1. 若是传入的 permission 名称为 null ,那么返回 PackageManager.PERMISSION_DENIED 。
2. 判断调用者 uid 是否符合要求 。
1 ) 若是 uid 为 0 ,说明是 root 权限的进程,对权限不做控制。
2 ) 若是 uid 为 system server 进程的 uid ,说明是 system server ,对权限不做控制。
3 ) 若是是 ActivityManager 进程自己,对权限不做控制。
4 )若是调用者 uid 与参数传入的 req uid 不一致,那么返回 PackageManager.PERMISSION_DENIED 。
3. 若是经过 2 的检查后,再 调用 PackageManagerService.checkUidPermission ,判断 这个 uid 是否拥有相应的权限,分析以下 。
1 ) 首先它经过调用 getUserIdLP ,去 PackageManagerService.Setting.mUserIds 数组中,根据 uid 查找 uid (也就是 package )的权限列表。一旦找到,就表示有相应的权限。
2 ) 若是没有找到,那么再去 PackageManagerService.mSystemPermissions 中找。这些信息是启动时,从 /system/etc/permissions/platform.xml 中读取的。这里记录了一些系统级的应用的 uid 对应的 permission 。
3 )返回结果 。
n 一样 checkUriPermission 的实现 主要在 ActivityManagerService 中,分析以下:
1. 若是 uid 为 0 ,说明是 root 用户,那么不控制权限。
2. 不然,在 ActivityManagerService 维护的 mGrantedUriPermissions 这个表中查找这个 uid 是否含有这个权限,若是有再检查其请求的是读仍是写权限。
关于签名机制,其实分两个阶段。
包扫描阶段须要进行完整性和证书的验证。普通 package 的签名和证书是必需要先通过验证的。具体作法是对 manifest 下面的几个文件进行完整性检查。完整性检查包括这个 jar 包中的全部文件。若是是系统 package 的话,只须要使用 AndroidMenifest.xml 这个文件去提取签名和验证信息就能够了。
在权限建立阶段。若是该 package 来自 system img (系统 app ),那么 trust it ,并且使用新的签名信息去替换就的信息。前提是若是这个 package 与其它 package 共享一个 uid ,那么这个共享 uid 对应的 sharedUser 中保存的签名与之不一致,那么签名验证失败。有些时候系卸载一个 app ,可是不删除数据,那么其 PackageSettings 信息会保留,其中会保存签名信息。这样再安装是就会出现不一致。
android 中系统和 app 都是须要签名的。能够本身经过 development/tools/make_key 来生成公钥和私钥。
android 源代码中提供了工具 ./out/host/linux-x86/framework/signapk.jar 来进行手动签名。签名的主要做用在于限制对于程序的修改仅限于同一来源。系统中主要有两个地方会检查。若是是程序升级的安装,则要检查新旧程序的签名证书是否一致,若是不一致则会安装失败;对于申请权限的 protectedlevel 为 signature 或者 signatureorsystem 的,会检查权限申请者和权限声明者的证书是不是一致的。签名相关文件能够从 apk 包中的 META-INF 目录下找到。
signapk.jar 的源代码在 build/tools/signapk ,签名主要有如下几步:
l 将除去 CERT.RSA , CERT.SF , MANIFEST.MF 的全部文件生成 SHA1 签名
首先将除了 CERT.RSA , CERT.SF , MANIFEST.MF 以外的全部非目录文件分别用 SHA-1 计算摘要信息,而后使用 base64 进行编码,存入 MANIFEST.MF 中。 若是 MANIFEST.MF 不存在,则须要建立。存放格式是 entry name 以及对应的摘要
l 根据 以前计算的 SHA1 摘要信息,以及 私钥生成 一系列的 signature 并写入 CERT.SF
对 整个 MANIFEST.MF 进行 SHA1 计算,并将摘要信息存入 CERT.SF 中 。而后对以前计算的全部摘要信息使用 SHA1 再次计算数字签名,并写入 CERT.SF 中。
l 把公钥和签名信息写入 CERT.RST
把以前整个的签名输出文件 使用私有密钥计算签名。同时将签名结果,以及以前声称的公钥信息写入 CERT.RSA 中保存。
安装时对一个 package 的签名验证的主要逻辑在 JarVerifier.java 文件的 verifyCertificate 函数中实现。 其主要的思路是经过提取 cert.rsa 中的证书和签名信息,获取签名算法等信息,而后按照以前对 apk 签名的方法进行计算,比较获得的签名和摘要信息与 apk 中保存的匹配。
第一步。提取证书信息,并对 cert.sf 进行完整性验证。
1. 先找到是否有 DSA 和 RSA 文件 ,若是找到则对其进行 decode ,而后读取其中的全部的证书列表(这些证书会被保存在 Package 信息中,供后续使用)。
2. 读取这个文件中的签名数据信息块列表,只取第一个签名数据块。读取其中的发布者和证书序列号。
3. 根据证书序列号,去匹配以前获得的全部证书,找到与之匹配的证书。
4. 从以前获得的签名数据块中读取签名算法和编码方式等信息
5. 读取 cert.sf 文件,并计算整个的签名,与数据块中的签名(编码格式的)进行比较,若是相同则完整性校验成功。
第二步。使用 cert.sf 中的摘要信息,验证 MANIFEST.MF 的完整性。
在 cert.sf 中提取 SHA1-Digest-Manifest 或者 SHA1-Digest 开头的签名 数据块 ( -Digest-Manifest 这个是整个 MANIFEST.MF 的摘要 信息,其它的是 jar 包中其它文件的摘要信息 ), 并逐个对这些数据块 进行验证。验证的方法是,现将 cert.sf 看作是不少的 entries ,每一个 entries 包含了一些基本信息,如这个 entry 中使用的摘要算法( SHA1 等),对 jar 包中的哪一个文件计算了摘要,摘要结果是什么。 处理时先找到每一个摘要数据开中的文件信息,而后从 jar 包中读取,而后使用 -Digest 以前的摘要算法进行计算,若是计算结果与摘要数据块中保存的信息的相匹配,那么就完成验证。