moira 2013-01-03
这篇文档描述了应用程序开发者如何使用Android提供的安全特性。Android Open Source Project提供了一个更全面的Android SecurityOverview。
Android是一个privilege-separated(权力分开的)操作系统,每个应用程序都有一个明确的系统标识(Linux用户ID和组ID)。部分的系统也分离成不同的身份。Linux因此将每个应用程序从系统和其它中隔离开。
另外的--通过“permission”机制来提供安全特点来强行限制一个特定的process能够执行的特定的操作,并且每个URI permissions为特定的数据提供点对点的访问。
Android安全架构的中心设计点并不是应用程序,默认的情况下,有权限执行任何操作会对其它的应用程序或操作系统或者用户造成不利。这包括了读写用户的私有数据(例如通讯录或者emails),读或写其它应用程序的文件,执行网络访问,让设备一直亮着等等。
因为Android通过沙箱隔离每个应用程序,应用程序必须明确地分享资源和数据。他们通过声明需要的权限来获得基本的沙箱没有提供的额外的能力来实现这个。应用程序静态地声明它们要求的权限,并且Android系统在应用程序安装的时候提示用户。Android没有机制来动态地授权(在运行时),因为它使得用户体验变得复杂并且不安全。
应用程序沙箱不依赖于建立一个应用程序的技术。特别地Dalvik VM 不是一个安全的界线,并且任何应用程序可以运行本地代码(参考the AndroidNDK)。所有的应用程序类型--Java的,本地的和混合的--都被沙箱以相同的方式隔离并且有相同的安全度数。
所有的Android应用程序(.apk文件)必须通过一个证书来签名,这个证书有一个私有的key,只有开发者知道。这个证书标识着应用程序的作者。这个证书不需要被一个认证中心所签名:它是完全允许的和典型的,对Android应用程序来使用自我签名的证书。在Android中使用证书的目的是区别应用程序开发者。这允许系统准许或者否定应用程序访问signature-levelpermissions和准许或否定一个应用程序的request to be giventhe same Linux identity作为另一个应用程序。
在安装的时候,Android给每个包一个确切的Linux用户ID。这个包存在于设备上的生命期间,这个身份一直是常量。在一个不同的设备上,同样的包可能有一个不同的UID;重要的是在一个给定的设备上,每个包有一个明确的UID。
因为安全实施发生在process级别,任意两个包的代码不能正常地运行在同一个process,因为它们需要以不同的Linux用户来运行。你可以使用每个包中AndroidManifest.xml的manifest标签中的sharedUserId属性来把它们分配给同一个user ID。这样做是为了安全的目的,两个包有相同的user ID和文件权限,会被当做同一个应用程序来对待。注意,为了保持安全,只有两个应用程序有相同的签名(并且要求相同的sharedUserId)才会被给予相同的user ID。
一个应用程序存储的任何数据都会被指定应用程序的user ID,并且不能被其它应用程序正常访问。当使用getSharedPreferences(String, int),openFileOutput(String, int)或openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)创建一个新的文件,你可以使用MODE_WORLD_READABLE和/或MODE_WORLD_WRITEABLE flags来允许其它任何应用程序来读/写这个文件。当设置了这些flags,这个文件仍然属于你的应用程序,但是他的全局读写权限被适当地设置了,因此其他的应用程序可以看见它。
一个基本的Android应用程序默认情况下没有权限,也就意味着它不会做出任何对用户体验或者设备上的数据不利的事情。利用设备的保护功能,你必须在你的AndroidManifest.xml中包含一个或者多个<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>
在应用程序安装的时候,这个应用程序所要求的权限被包的安装者(package installer)授予,基于对,声明了那些权限和/或与用户的交互应用程序的签名的核对。当一个应用程序正在运行的时候没有对用户进行检查:它不是在安装的时候授予一个特殊的权限,并且能够使用所需的功能,就是这个权限没有被授予,任何使用这个功能的企图都会失败并且没有对用户进行提示。
很多时候一个权限失败将会导致一个SecurityException被抛给应用程序。然而,这并不保证在每个地方都会发生。例如,当数据被递交给每个receiver的时候,sendBroadcast(Intent)方法核对权限,在这个方法调用已经返回之后,因此如果有权限失败,你却不会收到一个异常。在几乎所有情况下,然而,一个权限失败会被打印到系统日志。
Android系统提供的权限可以在Manifest.permission中被找到。任何应用程序也可以定义和实施它自己的权限,所以这不是一个所有可能的权限的综合列表。
在你的应用程序的操作当中,一个特殊的权限可能会在许多地方被实施:
在一个电话进入系统的时候,阻止一个应用程序执行某些函数。
当启动一个activity,阻止应用程序启动其它应用程序的activities。
发送和接收broadcasts,控制谁可以接收你的broadcast或者谁可以发送一个broadcast给你。
当在一个content provider上进行访问和操作。
绑定或者启动一个service。
注意:随着时间的推移,新的限制可能会被添加到平台,例如,为了使用某些APIs,你的应用程序必须请求一个它之前不需要的权限。因为已经存在的应用程序假设可以随意地访问那些APIs,Android可能应用应用程序manifest中要求的新的权限来避免打破新平台版本上的应用程序。Android基于<a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#target">targetSdkVersion</a>
属性提供的值,决定一个应用程序是否可能需要权限。如果这个值低于权限被添加的版本,那么Android添加这个权限。
例如,WRITE_EXTERNAL_STORAGE权限在API level 4中被添加来限制对共享存储空间的访问。如果你的<a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#target">targetSdkVersion</a>
是3或者更低,这个权限在Android的更新的版本中会被添加到你的程序。
注意,如果这发生在你的应用程序,你的列在Google Play中的应用程序将会显示这些必要的权限,哪怕你的应用程序没有要求这些。
为了避免这种情况并移除这些你不需要的默认的权限,总是尽量将你的<a href="http://developer.android.com/guide/topics/manifest/uses-sdk-element.html#target">targetSdkVersion</a>
更新到最新的版本。你可以参考Build.VERSION_CODES文档来看看每个版本都添加了哪些权限。
为了实施你自己的权限,首先你要使用一个或多个<permission>标签在你的AndroidManifest.xml文件中声明它们。
例如,一个应用程序想要控制谁可以启动它的一个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)就是自己来定义(这里翻译不好:or in more rare cases to one defined byyourself)。使用一个已经存在的组更好,因为对用户来说简化了权限的UI。
注意label和description应该被这个权限支持。当用户查看权限列表(android:label)或者查看每个单独的权限详细(android:description)的时候,这些字符串资源会被显示给用户。label应该简短,一些简短的文字来描述权限的功能。description应该是一些句子来描述这个权限允许宿主做什么。我们习惯用两句来描述description。首先描述权限,第二警告用户如果被授权了会发生什么不好的事情。
下面是一个描述CALL_PHONE权限的label和description的例子:
<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>
你可以通过Setting应用程序和shell命令“adb shell pm list permissions”来查看系统当前定义的权限。使用Setting应用程序,到Settings>Applications。选择一个应用程序,滚动并查看这个应用程序所使用的权限。对开发者,adb“-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的应用程序,的访问。所有这些要求是在目标组件上包含一个android:permission属性,命名这个将要被用来控制访问的权限。
<a href="http://developer.android.com/reference/android/app/Activity.html">Activity</a>
权限(应用<activity>标签)限制谁可以启动相关的activity。这个权限在Context.startActivity()和Activity.startActivityForResult()的时候被核对;如果调用者没有要求这个权限,那么SecurityException会被抛出。
<a href="http://developer.android.com/reference/android/app/Service.html">Service</a>
权限(应用<service>标签)限制谁可以启动和绑定相关的service。这个权限在Context.startService(),Context.stopService()和Context.bindService()的时候被核对;如果调用者没有要求这个权限,那么SecurityException会被抛出。
<a href="http://developer.android.com/reference/android/content/BroadcastReceiver.html">BroadcastReceiver</a>
权限(应用<receiver>标签)限制谁可以发送broadcasts到相关的receiver。这个权限在Context.sendBroadcast()方法返回以后被核对,当系统尝试递交提交的broadcast给指定的receiver的时候。因此,一个权限失败不会导致一个异常被抛出给调用者;它只是不会递交给intent。同样地,Context.registerReceiver()方法可以支持一个权限,来控制谁可以广播给一个动态注册的receiver。另一条路,一个权限可以被应用,当调用Context.sendBroadcast()方法来限制哪个BroadcastReceiver 对象被允许来接收broadcast(参看下面)。
<a href="http://developer.android.com/reference/android/content/ContentProvider.html">ContentProvider</a>
权限(应用<provider>标签)限制谁可以访问一个ContentProvider中的数据。(Content Provider有一个重要的额外的安全设备提供给它们,叫做URI permissions,后面会描述。)不像其它的组件,有两个不同的权限属性,你可以设置android:readPermission限制谁可以从provider读取,android:writePermission限制谁可以向他写入数据。注意如果一个provider同时有了读和写的权限,只拥有写的权限并不意味着你可以从一个provider中读取数据。当你第一次检索一个provider的时候权限会被核对(如果你没有任何一个权限,一个SecurityException 会被抛出),并且当你在provider上执行操作也是一样。使用ContentResolver.query()方法要求拥有读的权限,使用ContentResolver.insert(),ContentResolver.update(),ContentResolver.delete()要求有写的权限。所有的这些情况中,没有要求的权限会导致一个SecurityException被抛出。
除了强迫谁可以发送Intents给一个注册了的BroadcastReceiver(正如上面描述的)权限,当你发送一个broadcast的时候,你也可以指定一个要求的权限。通过调用Context.sendBroadcast()方法伴随着一个权限字符串,你要求一个receiver的应用程序必须有这个权限来接收你的broadcast。
注意,一个receiver和一个broadcaster都能够要求一个权限。当这发生时,权限核对必须同时被Intent通过,才能被递交到目标。
当对一个service进行任何的调用的时候,任意的权限能够被实施。这是通过Context.checkCallingPermission()方法来完成的。调用的时候伴随着一个目标权限字符串,它将返回一恶搞整型值来标识权限是否被授权给当前的调用process。注意,当你正在执行一个来自其它process的调用的时候,这才会被使用,(这里翻译有问题:Note that this canonly be used when you are executing a call coming in from another process,usually through an IDL interface published from a service or in some other waygiven to another process.)
有许多有用的方式来核对权限。如果你有另一个process的pid,你可以使用Context方法Context.checkPermission(String, int, int)来通过pid来核对权限。如果你有另一个应用程序的包名,你可以使用PackageManager的方法PackageManager.checkPermission(String, String)来找出特定的包是否被授予了一个特定的权限。
到目前为止,所描述的标准的权限系统,对使用content providers来说,通常是不够的。一个content provider可能想要通过读写权限来保护自己,当他的直接clients也需要处理特定的URIs来为其它的应用程序来操作。一个典型的例子是一个邮件应用程序中的附件。对邮件的访问应该被权限保护,因为这是敏感的用户数据。然而,如果一个图像附件的URI被交给了一个图像查看器,这个图像查看器就没有权限打开这个附件,因为它没有理由拥有一个访问所有email的权限。
这个问题的解决方式是 per-URI权限:当启动一个activity或者返回一个结果给一个activity,调用者可以设置Intent.FLAG_GRANT_READ_URI_PERMISSION和/或Intent.FLAG_GRANT_WRITE_URI_PERMISSION。这授予复杂接收的activity权限,来访问Intent中特定的数据URI,无论它是否有对应Intent中的访问content provider中数据的权限。
这里翻译有问题:This mechanism allows a common capability-style model where user interaction(opening an attachment, selecting a contact from a list, etc) drives ad-hocgranting of fine-grained permission.这是减少应用程序需要的权限,只需要哪些与他们的行为直接相关的权限,的关键的地方。
这里翻译有问题:The granting of fine-grained URI permissions does, however, require somecooperation with the content provider holding those URIs.这是强烈建议:content provider实现这个能力,并且通过android:grantUriPermissions属性或<grant-uri-permissions>标签声明它们支持它。
更多信息可以在Context.grantUriPermission(),Context.revokeUriPermission()和Context.checkUriPermission()中被找到。