Android - System Permissions
原文:http://developer.android.com/guide/topics/security/permissions.html
這篇翻的有點爛…
Android 是一個 privilege-separated(權限分離)的作業系統,其中每個應用程式使用不同的系統 ID 來執行(Linux user ID 和 group ID),其中一部份系統也有獨立的 ID。Linux 依此將每個應用程式從系統中彼此隔離。
其他劃分更細的安全功能由 Permission
的機制提供,他會強行限制 Process 可以執行的特定操作。每個 URI Permissions 允許特定的訪問去存取明確的資料區塊。
這篇文章會描述開發者如何讓應用程式使用 Android 提供的安全功能,Android Open Source Project 有提供 Android Security Overview 可以看看。
Security Architecture
Android 安全架構的設計重心在於-預設所有應用程式沒有 Permission 去做任何影響其他應用程式、系統或是使用者的操作,包含讀取或寫入使用者的個人資料(包含通訊錄和電子郵件)、讀取或寫入其他應用程式的檔案、執行網路訪問或是保持設備清醒(不進入休眠)等等。
因為每個 Android 應用程式是在沙盒程序中執行,應用程式必須明確的分享資料和資源,他們必須宣告他們需要的 Permissions 以要求基本沙盒沒有提供的額外功能。
應用程式靜態的宣告他們需要的 Permission,Android 系統會在安裝應用程式時即時要求使用者同意讓 APP 使用這些 Permission,Android 沒有動態的(在執行期中)產生 permissions 的機制,因為這樣會讓使用者經驗變的更複雜而影響安全性。
應用程式沙盒不依賴於編譯應用程式的技術。特別是 Dalvik VM 不是安全的邊界,任何 APP 可以執行 Native code(詳見 Android SDK)。任何類型的應用程式-JAVA、Native 或是混和的都是在沙盒中使用同樣的方式,而且彼此之間有相同程度的安全性。
(這句在工三小XD)
Application Signing
所有的 APK(.apk 檔案)必須簽署一個憑證,憑證的 Private key 在應用程式的開發者手上。這個憑證用來識別應用程式的作者,不需要由 Certificate Authority(CA,應該是指有公信力的第三方憑證機構)簽署,他允許 Android 應用程式使用自行簽核的憑證,用來讓系統能允許或拒絕應用程式訪問 Signature-level permissions(要有相同憑證的才能使用的 Permissions)、允許或拒絕應用程式請求給予和另一個應用程式相同的 Linux ID(要有相同的憑證才能使用同一個 Linux ID)。
User IDs and File Access
Android 在安裝的時候會給予每個 package 一個明確的 Linux User ID,當 package 在裝置上時這個 ID 都會保持不變,在不同的裝置上,相同的 package 可能會有不同的 UID,重要的是每個 package 在特定裝置上會有不同的 UID。
因為 Process 等級的安全措施,任兩個 Package 的程式通常不能在同一個 Process 中執行,因為他們要用不同的 Linux 使用者來執行。你可以在 AndroidManifest.xml
的 manifest
標籤中為每個 Package 使用 sharedUserId
屬性,讓他們分配到相同的 User ID。為了兩個 Packages 的安全性,他們被視為相同的應用程式,使用相同的 User ID 和檔案 Permission。請注意,為了保持安全,只有相同簽署的兩個應用程式可以使用相同的 User ID。
應用程式儲存的任何資料會被分配到該應用程式的 User ID 而不是一般的存取其他 Packages。當使用 getSharedPreferences(String, int)
、openFileOutput(String, int)
或是 openOrCreateDatabase(String, int, SQLiteDatabase.CursorFactory)
建立新檔案時,你可以使用 MODE_WORLD_READABLE
或/和 MODE_WORLD_WRITEABLE
標記來允許任何其他的 Package 去讀寫檔案。當這些標記被設定時,檔案的擁有者還是你的應用程式,但會被設定為全域讀寫的 Permissions ,讓其他應用程式可以看到他。
Using Permissions
基本的 Android 應用程式預設是沒有相關的 Permission,意思是他不能做任何事情,這樣會對使用者經驗或是裝置上的資料有不好的影響,要使用裝置上被保護的功能,你必須在 AndroidManifest.xml
加入 <uses-permission>
標籤來宣告應用程式需要的 Permissions。
例如,這個 APP 需要顯示剛收到的 SMS 訊息:<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.app.myapp" >
<uses-permission android:name="android.permission.RECEIVE_SMS" />
...
</manifest>
在 APP 安裝時,Package Installer 授予 APP 請求的 Permission,基於檢查 APP 宣告的這些 Permissions 和與使用者互動的簽名。使用者確認不是在應用程式執行時完成,APP 會在安裝時准許特定的 Permission,就可以使用他需要的功能,或是 Permission 沒有被允許,任何嘗試使用這些功能就會失敗,不會提示使用者。
通常 Permission failure 會有 SecurityException
的結果回傳給應用程式。但是不能保證每個地方都會發生。舉例來說 sendBroadcast(Intent)
方法會檢查資料傳送到每個 Receiver 的 Permissions,當這個方法結束時,如果 Permission failure 時,你將不會接到這個例外。然而在幾乎所有狀況下,Permission failure 將會記錄在 system log 中。
然而在一般的使用情況下(像是當 APP 從 Google Play Store 安裝),如果使用者沒有允許 APP 要求的 Permissions,APP 將會無法安裝。所以一般來說你不必擔心執行中失去 Permissions,因為 APP 已經安裝好了救代表所有你要求的 Permissions 已經得到了允許。
Permissions 由 Android 系統提供,可以在 Manifest.permission
找到。任何應用程式也可能強制定義他自己的 Permission,所以這邊沒辦法列出所有可用的 Permissions。
在你的程式操作過程中,特定的 Permission 可能會在很多地方被強制執行:
- 呼叫系統時阻止應用程式執行特定功能
- 當啟動 Activity 時防止應用程式啟動其他應用程式的 Activities
- 傳送或接收 Broadcasts 時,控制誰可以接收你的 Broadcast或是誰可以傳送 Broadcast 給你
- 當訪問和操作一個 Cotent Provider 時
- 綁定或啟動 Service 時
警告:隨著平台版本更新,可能會出現新的 Permission 限制,為了使用特定的 API,你的 APP 可能需要去要求之前版本不需要的 Permission。因為現存的 APP 可以自由的訪問那些 API,但 Android 可能會在 APP 的 Manifest 中要求新的 Permission 來避免 APP 在新的平台版本中壞掉。Android 基於
targetSdkVersion
屬性的值來決定 APP 是否需要 Permission,如果這個值低於 Permission 新增的版本,Android 會加入這個 Permission。舉例來說
WRITE_EXTERNAL_STORAGE
Permission 在 API Level 4 被加入,用來限制存取共享的儲存空間。如果你的targetSdkVersion
在 3 以下,這個 Permission 會在新的 Android 版本中被加入你的 APP。要小心這件事發生在你的 APP,你的 APP 會在 Google Play 的清單中列出這些 Permission 要求,就算你沒有明確的要求他們。
要避免這個狀況,並移除你沒有用到的預設 Permission,要盡可能的將
targetSdkVersion
設的更高,你可以在 Build.VERSION_CODES 文件中看到哪些版本新增了哪些 Permission。(白話一下:Android 版本更新以後,可能會對一些以前不需要要求 Permission 的功能加上新的 Permission 來做限制,在你的 APP 沒有更新的情況下,在新版本的 Android 中,不管你的 APP 有沒有去用這些 Permission 他都會自動要求這些新增的 Permission,以避免你的 APP 有用到這些功能卻沒有要求這些 Permission 而當掉,為了讓新版 Android 使用者安裝你的 APP 時不要被一堆存取權嚇到,要盡量的把
targetSdkVersion
設高一點來避免被自動加上這些新的 Permission。)
Declaring and Enforcing Permissions
要實施你自己的 Permission,你必須在 AndroidManifest.xml
中使用一個以上的 <permission>
標籤宣告他們。
舉例來說,可以用宣告 Permission 來讓 APP 控制誰可以啟動他的其中一個 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>
屬性是必須的,描述 Permission 潛在的風險並告訴系統 APP 要求這個 Permission 時該如何決定是否要允許,詳見文件。
<permissionGroup>
屬性是可選的,只是用來幫助系統對使用者顯示 Permissions。你通常會設定為一般系統群組(android.Manifest.permission_group
)或在一些罕見的狀況中用自己定義的群組,應該要優先使用現有的群組,因為這樣可以簡化讓使用者看到的 Permission UI。
注意 label
與 description
應該要用來描述 Permission。這些 string resources 可以在瀏覽 Permission 清單(android:label)時顯示給使用者看,或是顯示單一 Permission 的細節(android:description)。label
應該要寫的比較簡短,用幾個字描述這個 Permission 保護的關鍵部分。description
應該要用幾句話來描述這個 Permission 被允許去做什麼事。我們習慣用兩個句子來寫 description
,第一句描述 Permission,第二句警告使用者,如果允許這個 Permission 會發生什麼不好的事情。
這是為 CALL_PHONE Permission 撰寫 label
和 descript
的範例:<string name="permlab_callPhone">直接撥打電話</string>
<string name="permdesc_callPhone">允許應用程式在你沒有干涉的狀況下撥打電話。惡意的應用程式可能會讓你產生預料之外的帳單。注意這不能允許應用程式撥打緊急電話。</string>
你可以用應用程式設定或是 shell command adb shell pm list permissions
來看到目前系統中定義的 Permission。要用應用程式設定可以從 設定 > 應用程式 選擇一個 APP 並捲動到下面就能看到這個 APP 使用的 Permission。如果你是開發者,你可以用 adb 的 -s
選項來顯示 Permissions 看起來向下面這樣:$ 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
...
Enforcing Permissions in AndroidManifest.xml
限制訪問系統或應用程式中全部 Components 的高級 Permissions 可以透過 AndroidManifest.xml
來應用,在你想套用這個設定的 Component 的 android:permission
屬性中為 Permission 命名即可限制存取這個 Component。
Activity
Permissions(用於 <activity>
標籤)限制誰可以啟動相關的 Activity
。Permission 會在 Context.startActivity()
和 Activity.startActivityForResult()
時檢查,如果呼叫者沒有要求這些權限,SecurityException
的例外會被丟出來。
Service
(用於 <service>
標籤)限制誰可以啟動與綁定相關的 Service
。Permission 會在 Context.startService()
和 Context.stopService()
和 Context.bindService()
時檢查,如果呼叫者沒有要求這些權限,SecurityException
的例外會被丟出來。
BroadcastReceiver
(用於 <receiver>
標籤)限制誰可以送出廣播到特定的 Receiver
。Permission 會在 Context.sendBroadcast()
回傳,當系統常識傳送提交的廣播給特定 Recevier
時檢查,如果結果是 Permission Failure 不會給呼叫者丟出例外,就只會不傳送 Intent
而已。相同的,Permission 可以讓 Context.registerReceiver()
控制誰可以傳送廣播給程式中註冊的 Receiver。另一方面 Permission 也可以在呼叫 Context.sendBroadcast()
時限制哪個 BroadcastReceiver
物件可以允許接受廣播(詳見下面)。
ContentProvider
(用於 <provider>
標籤)限制誰可以在 ContentProvider
訪問資料。(Content Providers 有重要的額外安全措施叫做 URI Permisions
會在後面介紹)不像其他 Components,他有兩種獨立的 Permission 屬性,你可以設定 android:readPermission
來限制誰可以從 Provider 中讀取資料,也可以設定 android:writePermission
來限制誰可以寫入他。注意!如果 provider 同時被讀取和寫入 Permission 保護,只持有寫入 Permission 不代表你可以讀取 Provider。Permission 會在你第一次取回 Provider(如果你沒有讀取或寫入其中一種 Permission,會丟出一個 SecurityException 的例外)並且對他做操作時被檢查。使用 ContentResolver.insert()
、ContentResolver.update()
和 ContentResolver.delete()
需要寫入 Permission,在這些情況下沒去要求 Permission 就呼叫他們會出現丟出 SecurityException 的結果。
Enforcing Permissions when Sending Broadcasts
此外自訂的 Permission 可以傳送 Intents
去註冊 BroadcastReceiver
(如上面描述的),你也可以在傳送廣播時要求你訂立的 Permission。藉由傳入 Permission 的字串來呼叫 Context.sendBroadcast()
,你可以要求 Receiver
的 APP 必須要持有這個 Permission 才能接收你的廣播。
注意,Receiver
和 Broadcaster
可以要求 Permission。當他們要求 Permission 時 Permission 的檢查需要傳給 Intent
來傳遞給相關的目標。
Other Permission Enforcement
呼叫 Service 時可以用 Context.checkCallingPermission()
方法來自訂微調 Permission,傳入你希望得到的 Permission 字串,他會回傳一個整數來表示 Permission 在呼叫這個方法的 Process 中有沒有被允許(回傳 PERMISSION_GRANTED 表示有允許或 PERMISSION_DENIED 沒允許)。注意,這個方法只有正在執行從別的 Process 呼叫的情況下能使用,通常是透過一個從 Service 發佈的 IDL 介面或是其他方式提供給其他 Process。
這裡有一些實用的方法來檢查 Permission,如果你有其他 Process 的 Pid,你可以使用 Context 的方法 Context.checkPermission(String, int, int)
來檢查這個 Pid 的 Permission。如果你有其他 APP 的 Package Name,你可以直接使用 PackageManager 的方法 PackageManager.checkPermission(String, String)
來查出該 Package 是否有被允許特定的 Permission。
URI Permissions
到目前為止我們介紹的標準的 Permission 系統對於 Content Providers 是不足的,Content Provider 可能會想要保護自己的讀取和寫入 Permission,同時直接使用他的用戶端也需要特定的 URI 傳給其他應用程式讓他們操作這些資料。郵件應用程式的附件就是一個典型的例子,訪問郵件被 Permissions 保護,因為這是私密的用戶資料,然而如果有個連結到影像附件的 URI 要傳給 Image Viewer,Image Viewer 將會沒有 Permission 而無法開啟附件,因為他沒有理由持有訪問所有 e-mail 的 Permission。
解決這個問題的方案是 per-URI permissions:當啟動 Activity 或將結果回傳給 Activity,可以設定 Intent.FLAG_GRANT_READ_URI_PERMISSION
和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION
。這樣不管他是否有任何 Permission 去存取 Intent 對應的 Content Provider 中的資料,都能在 Intent 中讓收到的 Activity 允許 Permission 訪問特定資料的 URI。
這種機制允許一個常見的 Capability-style 模型-當使用者互動(打開附件、選擇一個聯絡人等)操作被臨時的 Fine-grained Permission 允許。這樣可以減少應用程式必須的 Permission 只留下與應用程式行為有關的行為。
微調 URI Permission 需要和 Content Provider 合作持有這些 URI。強烈建議 Content Providers 實做這個功能,宣告 android:grantUriPermissions
屬性或 <grant-uri-permissions> 標籤
。
更多資訊可以在這些方法的介紹中找到:
Context.grantUriPermission()
Context.revokeUriPermission()
Context.checkUriPermission()