android permission許可權與安全機制解析(上)

Shawn_Dut發表於2016-11-24

  總結整理了一下android許可權相關的知識,由於篇幅過長,分為兩篇部落格來寫,上篇部落格主要是詳解許可權和安全,下篇主要是介紹android6.0許可權適配問題:

  android permission許可權與安全機制解析(下)

uses-permission

 用法為

 為了保證application的正常執行,需要系統授予app的許可權宣告。這個許可權是在使用者安裝應用的時候授予的。

 android:name的值可以是其他app通過宣告的(用於兩個應用之間的互動),也可以是系統的許可權名稱,例如

android.permission.CAMERA或android.permission.READ_CONTACTS等等。需要注意的一點是uses-permission的許可權要求說明,可能會引起app在Android Market中的過濾。

android:maxSdkVersion用來標註該許可權所支援的最大api版本號,如果當從某個特定版本時,不需要該許可權時就可以加上該限制。

 系統許可權列表有很多,各自的用途也不一樣,有幾個連結可以參考一下:

 blog.csdn.net/zjl5211314/…

 developer.android.com/reference/a…

 developer.android.com/guide/topic…

 developer.android.com/guide/topic…

自定義permission


permission標籤



    android:icon=”drawable resource”
   android:label=”string resource”
   android:name=”string”
   android:permissionGroup=”string”
   android:protectionLevel=[“normal” | “dangerous” |
            “signature” | “signatureOrSystem”] /> <
  android:description:對許可權的描述,比lable更加的詳細,介紹該許可權的相關使用情況,比如當使用者被詢問是否給其他應用該許可權時。注意描述應該使用的是string資源,而不是直接使用string串。

  android:icon:用來標識該許可權的一個圖示。

  android:label:許可權的一個給使用者展示的簡短描述。方便的來說,這個可以直接使用一個string字串來表示,但是如果要釋出的話,還是應該使用string資源來表示。

  android:name:許可權的唯一名字,由於獨立性,一般都是使用包名加許可權名,該屬性是必須的,其他的可選,未寫的系統會指定預設值。

  android:permissionGroup: 許可權所屬許可權組的名稱,並且需要在這個或其他應用中使用標籤提前宣告該名稱,如果沒有宣告,該許可權就不屬於該組。

  android:protectionLevel:許可權的等級


  • normal

  • 低風險許可權,只要申請了就可以使用(在AndroidManifest.xml中新增標籤),安裝時不需要使用者確認;
  • dangerous

  • 高風險許可權,安裝時需要使用者的確認才可使用;
  • signature

  • 只有當申請許可權的應用程式的數字簽名與宣告此許可權的應用程式的數字簽名相同時(如果是申請系統許可權,則需要與系統簽名相同),才能將許可權授給它;
  • signatureOrSystem

  • 簽名相同,或者申請許可權的應用為系統應用(在system image中),與signature類似,只是增加了rom中自帶的app的宣告 ,儘量不要使用該選項,因為signature已經適合絕大部分的情況。

  對於普通和危險級別的許可權,我們稱之為低階許可權,應用申請即授予。其他兩級許可權,我們稱之為高階許可權或系統許可權。當應用試圖在沒有許可權的情況下做受限操作,應用將被系統殺掉以警示。系統應用可以使用任何許可權。許可權的宣告者可無條件使用該許可權。

  下面通過指定一個BroadcastReceiver的許可權來實驗,首先建立了兩個app:app A ,app B 。app A中註冊了一個BroadcastReceiver ,app B 傳送訊息,app A的manifest檔案:






















複製程式碼


  app B 的manifest 檔案內容















複製程式碼


這樣app B 給app A 傳送訊息,A就可以收到了,若未在app B的manifest檔案中宣告使用相應的許可權,app B傳送的訊息,A是收不到的。 另外,也可在app B 的manifest檔案中宣告許可權時,新增android:protectionLevel=“signature”,指定app B只能接收到使用同一證照籤名的app 傳送的訊息。


permission-tree標籤



      android:label=”string resource”
    android:name=”string” />
    

  該標籤包含於在< manifest >標籤中。
  該標籤宣告許可權樹的基礎名稱。 應用程式擁有樹中的所有名稱。 可以通過呼叫 PackageManager.addPermission() 在許可權樹中動態新增新的許可權。 樹中的名稱以句點(’.’)分隔。 比如,假定基礎名稱為com.example.project.taxes,則可加入類似以下格式的許可權:

  com.example.project.taxes.CALCULATE
  com.example.project.taxes.deductions.MAKE_SOME_UP
  com.example.project.taxes.deductions.EXAGGERATE
注意本元素並不是宣告許可權,而只是為後續要加入的許可權定義一個名稱空間。

  android:icon
  代表樹中所有許可權的圖示。 本屬性必須設為對 Drawable 資源的引用。
  android:label
  供使用者閱讀的許可權組名稱。 為了方便起見可以將其直接設為字串, 但在應用程式準備釋出時,應該設為對字串的引用,以便對其進行本地化。
  android:name
  許可權樹的基礎名稱,用作樹中所有許可權的字首。 為了保證名稱的唯一性,應該採用 Java 風格的域名規則。 名稱的路徑必須至少包含兩個句點分割的欄位 — 比如:com.example.base 可以,但 com.example 就不行。

permission-group標籤

  Android在定義 permission 時, 為每個Permission都進行了分組,一個許可權主要包含三個方面的資訊:許可權的名稱;屬於的許可權組;保護級別。一個許可權組是指把許可權按照功能分成的不同的集合。每一個許可權組包含若干具體 許可權,例如在 COST_MONEY 組中包含 android.permission.SEND_SMS , android.permission.CALL_PHONE 等和費用相關的許可權。具體如下SDK所示:
  developer.android.com/reference/a…
  再來看看原始碼(在frameworks/base/core/res /AndroidManifest.xml):


    




複製程式碼


  可以看到,這邊先定義了一個permissiongroup : android.permission-group.COST_MONEY, 然後又定義了兩個permission :android.permission.SEND_SMS 和 android.permission.CALL_PHONE , 需要注意的是,這兩個許可權中都有一個android:permissionGroup屬性,這個屬性就指定了這個許可權所屬的PermissionGroup。

  android:description
  這個屬性用於給許可權組定義一個使用者可讀的說明性文字。這個文字應該比標籤更長、更詳細。這個屬性不應該直接使用字串,而應該使用字串引用。
  android:icon
  這個屬性定義了一個代表許可權的圖示,這個屬性應該使用資原始檔來定義。
  android:label
  這個屬性給許可權組定義了一個使用者可讀的名稱。
  android:name
  這個屬性定義了許可權組的名稱,它是能夠分配給元素的permissionGroup屬性的名稱。
  


安全機制


元件許可權


  Android是一個許可權分離的系統,這是利用Linux已有的許可權管理機制,通過為每一個Application分配不同的uid和gid,從而使得不同的Application之間的私有資料和訪問(native以及java層通過這種sandbox機制,都可以)達到隔離的目的 。與此同時,Android 還在此基礎上進行擴充套件,提供了permission機制,它主要是用來對Application 可以執行的某些具體操作進行許可權細分和訪問控制,同時提供了per-URI permission 機制,用來提供對某些特定的資料塊進行ad-hoc方式的訪問。
  通過AndroidManifest.xml檔案可以設定高階許可權,以限制訪問系統的所有元件或者使用應用程式。所有的這些請求都包含在你所需要的元件中的 android:permission屬性,命名這個許可權可以控制訪問此元件。


  1. Activity 許可權 (使用標籤) 限制能夠啟動與Activity許可權相關聯的元件或應用程式。在Context.startActivity()和Activity.startActivityForResult()期間檢查;

  2. Service 許可權(應用標籤)限制啟動、繫結關聯服務的元件或應用程式。此許可權在Context.startService(),Context.stopService() 和 Context.bindService() 期間要經過檢查;

  3. BroadcastReceiver 許可權(應用標籤)能夠為相關聯的接收者元件或應用程式設定限制。在 Context.sendBroadcast() 返回後此許可權將被檢查,同時系統設法將廣播遞送至相關接收者。因此,許可權失敗將會導致拋回給呼叫者一個異常;它將不能遞送到目的地。為了接收你的廣播,你請求一個接收器的應用程式必須持有要求的那個許可權(如上面例子所示),而且sendBroadcast的相關幾個函式中也可以加入permission引數用來指定只帶有該permission的接受者可以接受該廣播。

  4. ContentProvider 許可權(使用 標籤)用於限制能夠訪問 ContentProvider 中的資料的元件或應用程式。如果呼叫者沒有請求許可權,那麼會為呼叫丟擲一個安全異常( SecurityException )。資料庫本身的讀寫可以處理多執行緒問題,但是資料的先後可以考慮同步問題,設定android:multiprocess=”true”屬性來保證資料的正確性

  對於元件而言,除了使用許可權限制與外部互動的實體外,還有一個屬性也具有該功能,那就是android:exported,這個屬性用於指示該服務是否能夠被其他應用程式元件呼叫或跟它互動。如果設定為true,則能夠被呼叫或互動,否則不能。設定為false時,只有同一個應用程式的元件或帶有相同使用者ID的應用程式才能啟動或繫結該服務。

  它的預設值依賴於該服務所包含的過濾器。沒有過濾器則意味著該服務只能通過指定明確的類名來呼叫,這樣就是說該服務只能在應用程式的內部使用(因為其他外部使用者不會知道該服務的類名),因此這種情況下,這個屬性的預設值是false。另一方面,如果至少包含了一個過濾器,則意味著該服務可以給外部的其他應用提供服務,因此預設值是true。

許可權檢測

  首先是root使用者和system使用者擁有所有的介面呼叫許可權,然後對於其它使用者可以使用以下這幾個函式來檢測 :

  PackageManager.checkPermission (String permName, String pkgName)

  用來檢測一個package中是否授予了指定permission。

  Context.checkCallingOrSelfPermission (String permission)

  用來檢測自己或者呼叫程式中是否授予了指定permission。

  Context.checkCallingOrSelfUriPermission (Uri uri, int modeFlags)

  用來檢測自己或者呼叫程式中是否授予了一個uri通過modeFlags指定的permission。
  Context.checkCallingPermission (String permission)
   檢查正在處理的呼叫者程式是否授予指定permission 許可權,如果呼叫者是自己那麼返回 。
  Context.checkCallingUriPermission (Uri uri, int modeFlags)
  用來檢測呼叫程式中是否授予了一個uri通過modeFlags指定的permission。
  Context.checkPermission (String permission, int pid, int uid)
  用來檢測指定uid和pid的程式中是否授予了指定的permission。

  checkSelfPermission (String permission)
  23版本api新增,用來檢測自己是否授予了指定permission

  Context.checkUriPermission (Uri uri, int pid, int uid, int modeFlags)
  用來檢測指定uid和pid的程式中是否授予了一個uri通過modeFlags指定的permission。

  Context.checkUriPermission (Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags)

  比上面的方法多了一個檢測permission的功能,相當於同時呼叫checkPermission(String, int, int)和checkUriPermission(Uri, int, int, int)。
-------------
   下面這一組和上面一一對應,區別就在於如果遇到檢查不通過時,會丟擲異常,列印訊息 :
  Context.enforceCallingOrSelfPermission(String permission, String message)
  Context.enforceCallingOrSelfUriPermission(Uri uri, int modeFlags, String message)
  Context.enforceCallingPermission (String permission, String message)
  Context.enforceCallingUriPermission (Uri uri, int modeFlags, String message)
  Context.enforcePermission (String permission, int pid, int uid, String message)

  Context.enforceUriPermission (Uri uri, int pid, int uid, int modeFlags, String message)

  Context.enforceUriPermission (Uri uri, String readPermission, String writePermission, int pid, int uid, int modeFlags, String message)
-----------
  Context.grantUriPermission (String toPackage, Uri uri, int modeFlags)
  為指定package新增訪問指定uri 的讀或者寫許可權
  Context.revokeUriPermission (Uri uri, int modeFlags)
  為指定package刪除訪問指定uri 的讀或者寫許可權。
  以上函式中check開頭的,只做檢查。enforce開頭的,不單檢查,沒有許可權的還會丟擲異常。
  checkPermission相關函式

  1. 如果傳入的 permission 名稱為 null ,那麼返回PackageManager.PERMISSION_DENIED 。

  2. 判斷呼叫者uid是否符合要求 。

  • 如果uid為0,說明是root許可權的程式,對許可權不做控制。

  • 如果uid為system server程式的uid,說明是system server,對許可權不作控制。
  • 如果是 ActivityManager 程式本身,對許可權不作控制。
  • 如果呼叫者uid與引數傳入的req uid不一致,那麼返回PackageManager.PERMISSION_DENIED。
  • 如果通過 2 的檢查後,再呼叫 PackageManagerService.checkUidPermission ,判斷這個uid是否擁有相應的許可權,分析如下 :
    • 首先它通過呼叫getUserIdLP,去PackageManagerService.Setting.mUserIds陣列中,根據uid查詢uid(也就是package)的許可權列表。一旦找到,就表示有相應的許可權。
  • 如果沒有找到,那麼再去PackageManagerService.mSystemPermissions中找。這些資訊是啟動時,從/system/etc/permissions/platform.xml中讀取的。這裡記錄了一些系統級的應用的 uid 對應的 permission 。
  • 返回結果 。
  •   CheckUriPermission函式
    1. 如果uid為0,說明是root使用者,那麼不控制許可權。

  • 否則,在ActivityManagerService維護的mGrantedUriPermissions這個表中查詢這個uid是否含有這個許可權,如果有再檢查其請求的是讀還是寫許可權。
  • URI許可權


      到目前為止我們討論的標準的permission系統對於content provider來說是不夠的。一個content provider可能想保護它的讀寫許可權,而同時與它對應的直屬客戶端也需要將特定的URI傳遞給其它應用程式,以便其它應用程式對該URI進行操作。一個典型的例子就是郵件程式處理帶有附件的郵件。進入郵件需要使用permission來保護,因為這些是敏感的使用者資料。然而,如果有一個指向圖片附件的URI需要傳遞給圖片瀏覽器,那個圖片瀏覽器是不會有訪問附件的權利的,因為他不可能擁有所有的郵件的訪問許可權。
      針對這個問題的解決方案就是per-URI permission:當啟動一個activity或者給一個activity返回結果的時候,呼叫方可以設定Intent.FLAG_GRANT_READ_URI_PERMISSION和/或 Intent.FLAG_GRANT_WRITE_URI_PERMISSION (另外一篇intent部落格中也有介紹到)。這會使接收該intent的activity獲取到進入該Intent指定的URI的許可權,而不論它是否有許可權進入該intent對應的content provider。
      這種機制允許一個通常的capability-style模型,這種模型是以使用者互動(如開啟一個附件, 從列表中選擇一個聯絡人)為驅動,特別獲取fine-grained permissions(更細粒化的許可權)。這是一種減少不必要許可權的重要方式,這種方式主要針對的就是那些和程式的行為直接相關的許可權。
      這些URI permission的獲取需要content provider(包含那些URI)的配合。強烈推薦在content provider中提供這種能力,並通過android:grantUriPermissions或者標籤來宣告支援。

    android:sharedUserId


      安裝在裝置中的每一個apk檔案,Android給每個APK程式分配一個單獨的使用者空間,其manifest中的userid(userid的特點: 作為APK身份的標識 ;userid對應一個Linux使用者,所以不同APK(使用者)間互相訪問資料預設是禁止的)就是對應一個Linux使用者都會被分配到一個屬於自己的統一的Linux使用者ID,並且為它建立一個沙箱,以防止影響其他應用程式(或者其他應用程式影響它)。使用者ID 在應用程式安裝到裝置中時被分配,並且在這個裝置中保持它的永久性。所以讓兩個apk使用相同的userID,這樣它們就可以看到對方的檔案,如果在此基礎之上再加上相同的android:process,他們就可以執行在一個程式中,能夠做更多的事情了。為了節省資源,具有相同ID的apk也可以在相同的linux程式中進行(注意,並不是一定要在一個程式裡面執行),共享一個虛擬機器。
      所以當兩個應用公用一個userid的時候就可以使用SharedPreference之類的機制進行檔案資料共享:

    //第一個應用程式為的menifest檔案程式碼如下:
    
    //第二個應用程式的menifest檔案程式碼如下:
    複製程式碼
      上面給出了兩個程式的menifest定義,兩者共用一個ShareUserId,下面我們看看從一個程式裡面如何獲取另外一個程式的Context。假設我們從package=“com.android.demo_A”的程式獲取package=”com.android.demo_B”的程式的context:
    Context ct=this.createPackageContext("com.android.demo_B", Context.CONTEXT_IGNORE_SECURITY);複製程式碼

      這樣我們就能夠獲取到另外一個應用的Application context了,獲取到上下文之後就能夠實現資料共享和通訊,具體可以檢視我的 IPC通訊部落格

    總結

      只要signature相同,就算不顯式宣告也能access設定了normal或dangerous許可權設定的資料或功能。
      擁有system級別許可權的使用者可以access其他普通signature許可權宣告設定過的功能。所以,設定為擁有system級別許可權即可。
      應用程式安裝的時候,應用程式請求的permissions是通過package installer來批准獲取的。package installer是通過檢查該應用程式的簽名來確定是否給予該程式request的許可權。在使用者使用過程中不會去檢查許可權,也就是說要麼在安裝的時候就批准該許可權,使其按照設計可以使用該許可權;要麼就不批准,這樣使用者也就根本無法使用該feature,也不會有任何提示告知使用者嘗試失敗。
    例如高階許可權用有system級別許可權設定的api時,需要使其apk擁有system許可權。比如在 android 的API中有供給SystemClock.setCurrentTimeMillis()函式來修改系統時間。有兩個方法:
      第1個方法簡單點,不過需要在Android系統原始碼的情況下用make來編譯:

    • 在應用程式的AndroidManifest.xml中的manifest節點中插手android:sharedUserId=”android.uid.system”這個屬性。
    • 修改Android.mk檔案,插手LOCAL_CERTIFICATE := platform這一行
    • 使用mm命令來編譯,生成的apk就有修改系統時間的職權範圍了

    • 第2個方法麻煩點,不過不用開虛擬機器跑到原始碼環境下用make來編譯:

      • 同上,插手android:sharedUserId=”android.uid.system”這個屬性。
      • 使用eclipse編譯出apk檔案,但是這個apk檔案是不能用的。
      • 用壓縮軟體開啟apk檔案,刪掉META-INF目錄下的CERT.SF和CERT.RSA兩個檔案。
      • 使用目標系統的platform金鑰來重新給apk檔案簽名。這步比較麻煩,首先找到金鑰檔案,在我的Android原始碼目錄中的位置是”build/target/product/security”,下面的platform.pk8和platform.x509.pem兩個檔案。然後用Android提供的Signapk工具來簽名,signapk的原始碼是在”build/tools/signapk”下,用法為”signapk platform.x509.pem platform.pk8 input.apk output.apk”,檔名最好使用絕對路徑防止找不到,也可以修改原始碼直接使用。

      • 引用文章:


        blog.csdn.net/xyz_lmn/art…

        blog.csdn.net/t12x3456/ar…

        yelinsen.iteye.com/blog/101274…

        berdy.iteye.com/blog/178285…

        my.oschina.net/u/589963/bl…

        blog.csdn.net/vshuang/art…

        www.chawenti.com/articles/12…

        ee.ofweek.com/2012-04/ART…

        blog.csdn.net/liujian885/…

        maoruibin.github.io/%E6%8A%80%E…

        www.zhihu.com/question/37…

        blog.csdn.net/wirelessqa/…


        相關文章