[转]android 安全与权限(Security and Permissions)(翻译)
下面转字网上的一篇翻译,修改了几处不同顺和翻译错误的地方翻译
这里主要讲android中安全体系和permission的作用,并不是linux层的
原文地址
安全与权限
Android是一个权限分离的操作系统,每个应用以唯一的身份标识(Linux用户ID和组ID)运行。系统的不同部分也分成不同的身份。因而Linux把应用之间以及应用与系统之间相互隔离起来。
附加细粒度的安全功能是通过一个“许可”的机制,限定特定的进程能够执行指定的操作以及给予对每一个资源点对点的访问的URI许可。
安全体系结构
Android安全体系结构设计中心是没有任何一个应用程序在默认情况下可以执行对其他应用程序、操作系统或者用户有害的操作。其中包括读写用户的私有数据(例如联系人或者电子邮件),读写其他应用程序的文件,进行网络访问或者唤醒设备,等等。
由于内核让每个应用程序运行在独立的沙盒中,应用程序必须明确的分配资源和数据。他们通过声明他们所需要的但是沙盒没有提供的权限来做这些。应用程序静态的声明他们所需要的权限,android系统在程序安装时提示用户同意它们获取这些权限。Android没有准许动态权限机制,因为它会使用户体验复杂对安全不利。
内核独自地为彼此的沙盒应用负责。特别Dalvik虚拟机不是一个安全的边界,而且任何的应用程序都能够运行本地代码(查看Android NDK)。所有类型的应用程序——java、native和混合的——均用相同的方式置于砂箱中并且有着相同的安全等级。
应用程序签名
所有的Android应用程序(apk文件)必须使用一个开发人员掌握私钥的证书进行签名。证书用于识别应用程序的作者。该证书并不需要由证书颁发机构进行签名:它是非常宽松的,典型的Android应用程序使用自签名的证书。Android证书的目的是区分应用程序的作者。这样就可以允许操作系统授予或者拒绝应用程序使用签名级别的权限和操作系统授予或者拒绝应用程序请求和其他应用程序相同的Linux身份。
用户ID和文件访问
在安装的时候,Android给予每个程序包不同的Linux用户ID。软件包在设备上的生命周期中身份标识保持恒定不变。在不同的设备上,相同的软件包可能会有一个不同的UID;重要的是每个包都有不同的身份标识在给定的设备上。
因为安全在进程级别上实现,两个软件包的代码不能够正常的运行在同一个进程中,因为他们需要以不同的Linux用户运行。可以使用每个程序包的AndroidManifest.xml中的manifest标签中的shareUserId属性将它们分配相同的用户ID。这样做是为了安全,把两个应用程序包看作同一个应用程序,拥有同样的用户ID和文件权限。注意:为了保持安全性,只有具有相同签名(并请求同样的sharedUserId)的应用程序才会分配相同的的User ID。
任何由应用程序存储的数据将被赋予应用程序的用户ID,正常情况不能被其它应用程序访问。当使用getSharedPreferences(String, int), openFileOutput(String, int), 或 openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)创建一个新的文件时,你可以使用MODE_WORLD_READABLE或MODE_WORLD_WRITEABLE标记允许任何其他应用程序来读/写入文件。当设置这些标记,该文件仍然为创建文件的应用程序所拥有,但是它全局的读写权限已经被设置,所以任何其他应用程序可以看到它。
使用权限
一个Android应用程序没有任何权限,这意味着它不能做任何会对用户体验或设备上的任何数据造成不利影响的操作。利用该设备的保护功能,必须在AndroidManifest.xmlone文件中的一个或多个<uses-permission>标签上声明应用程序所需要的权限。
例如,一个应用程序需要监控接收短信会指定:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.android.app.myapp" > <uses-permission android:name="android.permission.RECEIVE_SMS" /> ... </manifest>
在应用程序安装时,应用程序所需的权限被安装者基于签名的应用程序所声明的权限交互的进行授权。应用程序运行时不会进行权限检查:它要么在安装后被给予一个特殊的权限,并且可以使用它期望的权限,要么就不被授予权限,并且任何试图使用这些权限将会失败并没有用户提示。
通常一个请求权限的失败将导致应用程序抛出一个SecurityException异常。但是并不能够保证一定会发生。例如,sendBroadcast(Intent)方法在所有数据被投递到接收者,当方法返回后才会进行对于数据的权限检查,所以假如权限异常也不能接收到任何权限异常。几乎所有情况下,权限异常将会记录在日志中。
所有Android系统提供的权限可以在Manifest.permission中找到。(frameworks\base\core\res\AndroidManifest.xml)
任何应用程序也可以定义并执行其自己的权限,所以这不是一个对于所有可能的权限的全面的清单。
在你的程序执行期间,一个特别的permission可能会在多处执行:
1.当调用系统调用时,阻止应用程序执行特定的函数。
2.当启动一个Activity时,阻止应用程序启动其他应用程序的Activity。
3.在发送或接受广播时,控制谁可以接受你的广播或者谁可以向你发送广播。
4.谁可以访问或操作一个特定的内容提供者。
5.绑定或者启动一项服务。
声明和实施许可
为了执行你自己的权限,你必须首先在你的AndroidManifest.xml中使用一个或多个<permission>标签声明它们。
例如,一个应用程序想要控制谁能够开始它的一个Activity,可以声明此操作的权限如下:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.me.app.myapp" > <permission android:name="com.me.app.myapp.permission.DEADLY_ACTIVITY" android:label="@string/permlab_deadlyActivity" android:description="@string/permdesc_deadlyActivity" android:permissionGroup="android.permission-group.COST_MONEY" android:protectionLevel="dangerous" /> ... </manifest>
<protectionLevel>属性是必须的,它告诉系统户当其它应用程序需要该权限或者谁可以持有该权限时怎样通知用。
<permissionGroup>属性是可选的,用于帮助系统展示的权限给用户。您通常会希望将它设置为一个标准的系统组(在android.Manifest.permission_group中)或者在更多罕见的情况下由自己进行定义。它优先使用现有的组,用于简化权限UI展示给用户。
请注意,这两个标签和描述应提供许可。用户在查看的权限列表(android:label)或单个权限( android:description)的细节时,这些内容被展现。标签应该简洁的介绍权限保护的关键功能。用几个简单的句子描述拥有该权限可以做什么。我们的惯例是用两个句子,第一句描述权限,第二句警告用户当授权该权限后会发生什么。
这里是一个CALL_PHONE权限的标签和描述的的例子:
<string name="permlab_callPhone">directly call phone numbers</string> <string name="permdesc_callPhone">Allows the application to call phone numbers without your intervention. Malicious applications may cause unexpected calls on your phone bill. Note that this does not allow the application to call emergency numbers.</string>
可以通过shell命令 adb shell pm list permissions来查看现在系统上的权限定义。特别地,-s选项可以用简单的表格形式来给用户呈现权限。
$ adb shell pm list permissions -s All Permissions: Network communication: view Wi-Fi state, create Bluetooth connections, full Internet access, view network state Your location: access extra location provider commands, fine (GPS) location, mock location sources for testing, coarse (network-based) location Services that cost you money: send SMS messages, directly call phone numbers ...
在AndroidManifest.xml中实施许可
用于限制进入系统或应用程序的组件的高级别许可可以在AndroidManifest.xml中实现。所有这些都可以通过在相应的组件中包含 android:permission 属性,命名该许可以使其被用以控制进入的权限。
Activity许可(应用于<activity>标签)用于限制谁才可以启动相关的Activity。该权限在Context.startActivity()和Activity.startActivityForResult()期间被检查;如果调用方不具有这个必需的权限,那么将会从此次调用中抛出SecurityException。
Service许可(应用于<service>标签)用于限制谁才可以启动或绑定该service。在Context.startService() , Context.stopService()和Context.bindService()调用的时候会进行权限检查。如果调用方没有所需的权限,则会抛出一个SecurityException。
BroadcastReceiver许可(应用于<receiver>标签)用于限制谁才可以向相关的接收器发送广播。权限检查会在Context.sendBroadcast() 返回时进行,由系统去发送已经提交的广播给相应的Receiver。最终,一个permission failure不会再返回给调用方一个异常;它只是不会去实现该Intent而已。同样地,Context.registerReceiver()也可以有自己permission用于限制谁才可以向一个在程序中注册的receiver发送广播。另一种方式是,一个permission也可以提供给Context.sendBroadcast() 用以限制哪一个BroadcastReceiver才可以接收该广播。(见下文)
ContentProvider许可(应用于<provider>标签)用于限制谁才可以访问ContentProvider提供的数据。(Content providers有一套额外的安全机制叫做URI permissions,这些在稍后讨论)不像其他组件,它有两个单独的权限属性,你可以设置:android:readPermission由于限制谁才能够读,android:writePermission用于限制谁才能够写。需要注意的是如果provider同时被读写许可,如果这时只有写许可并不意味着你就可以读取provider中的数据了。当你第一次检索内容提供者时,(假如你没有任何权限时,抛出SecurityException),和当你完成操作时权限被检查。使用ContentResolver.query()需要持有读许可;使用ContentResolver.insert(),ContentResolver.update(),ContentResolver.delete()需要写许可。在所有这些情况下,没有所需的权限将会导致抛出SecurityException。
发送广播时实施许可
除了之前说过的许可(用于限制谁才可以发送广播给相应的BroadcastReceiver),你还可以在发送广播的时候指定一个许可。在调用Context.sendBroadcast()的时候使用一个permission string,你就可以要求接收器的宿主程序必须有相应的许可。
值得注意的是接收器和广播都可以要求许可。当这种情况发生时,这两种permission检查都需要通过后才会将相应的intent发送给相关的目的地。
其他权限实施
在调用service的过程中可以设置任意细化的许可。这是通过Context.checkCallingPermission()方法来完成的。调用的时候使用一个想得到的permission string,并且当该权限获批的时候可以返回给调用方一个Integer(没有获批也会返回一个Integer)。需要注意的是这种情况只能发生在来自另一个进程的调用,通常是一个service发布的IDL接口或者是其他方式提供给其他的进程。
Android提供了很多其他有效的方法用于检查许可。如果你有另一个进程的pid,你就可以通过Context.checkPermission(String, int, int)去针对那个pid去检查许可。如果你有另一个应用程序的包名,你可以直接用PackageManager的方法PackageManager.checkPermission(String, String)来确定该包是否已经拥有了相应的权限。
URI许可
到目前为止我们讨论的标准的permission系统对于内容提供者(content provider)来说是不够的。一个内容提供者可能想保护它的读写权限,而同时与它对应的直属客户端也需要将特定的URI传递给其它应用程序,以便对该URI进行操作。一个典型的例子是邮件应用程序的附件。访问邮件需要使用permission来保护,因为这些是敏感的用户数据。然而,如果有一个指向图片附件的URI需要传递给图片浏览器,那个图片浏览器是不会有访问附件的权利的,因为它不可能拥有所有的邮件的访问权限。
针对这个问题的解决方案就是per-URI permission:当启动一个activity或者给一个activity返回结果的时候,调用方可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这赋予接收活动(activity)访问该意图(Intent)指定的URI的权限,而不论它是否有权限进入该意图对应的内容提供者。
这种机制允许一个通常的capability-style模型,以用户交互(如打开一个附件, 从列表中选择一个联系人)来驱动细化的特别授权。这是一个很关键的能力,可以减少应用程序所需要的权限,只留下和程序行为直接相关的权限。
这些URI permission的获取需要内容提供者(包含那些URI)的配合。强烈推荐在内容提供者中实现这种能力,并通过android:grantUriPermissions或者<grant-uri-permissions>标签来声明支持。
更多的信息可以参考Context.grantUriPermission(), Context.revokeUriPermission()和Context.checkUriPermission()方法。