鍍金池/ 教程/ iOS/ 代碼簽名探析
與四軸無人機(jī)的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實(shí)例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項目介紹
Swift 的強(qiáng)大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機(jī)制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機(jī)與照片
音頻 API 一覽
交互式動畫
常見的后臺實(shí)踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴(kuò)張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點(diǎn)互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機(jī)項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗(yàn)證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實(shí)戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴(kuò)展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機(jī)捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機(jī)工作原理
Build 過程

代碼簽名探析

"用戶會感激代碼簽名帶來的好處" – Apple Developer Library: Code Signing Guide

在 iOS 或 OS X 平臺上進(jìn)行應(yīng)用開發(fā)時,你所需要使用的 API 大多設(shè)計得簡潔明了。你可以輕易地實(shí)現(xiàn)酷炫的動畫效果,便捷地進(jìn)行應(yīng)用發(fā)布前測試,或是用 Core Data 將數(shù)據(jù)安全的存儲在本地。但是總有一天,你會碰上代碼簽名 (code signing) 和配置文件 (provisioning),大多數(shù)情況下,這會是你在心里問候某些人祖宗的開始。

如果你已經(jīng)在 iOS 上開發(fā)過應(yīng)用,那么你多半已經(jīng)與代碼簽名或設(shè)備配置文件打過交道了。即使是 OS X 開發(fā)者,如果你想發(fā)布自己的應(yīng)用到 Mac App Store 上去或者想?yún)⑴c蘋果的開發(fā)者項目,那么也不得不開始為自己的代碼進(jìn)行簽名。

大多數(shù)時候代碼簽名看上去像是一個難以理解的神秘黑盒。在這篇文章里我會盡可能揭示盒子內(nèi)部的運(yùn)作機(jī)理。

通常來說,我們無法直接看到代碼簽名的運(yùn)作過程,它們隱藏在 iOS 系統(tǒng)內(nèi)部和 SDK 之中。但我們可以通過觀察設(shè)置代碼簽名所需工具的運(yùn)作方式,來找出一些線索。除此之外,我們還可以參考 OS X 上的代碼簽名運(yùn)作方式,畢竟 iOS 和 OS X 系出同源,我們可以從他們的對比之中得到很多有用的信息。

OS X 上代碼簽名技術(shù)和相應(yīng)的 API 是在 Mac OS X Leopard 10.5 上首次出現(xiàn)的,這剛好是第一臺 iPhone 發(fā)布的時候。這并非巧合,因?yàn)樵?iOS 上,代碼簽名起到的作用更加重要。iPhone 是在眾多游戲主機(jī)之后第一個大規(guī)模出售并且從頭就開始使用代碼簽名的計算平臺。只有在越獄之后,iOS 才能運(yùn)行沒有簽名的代碼。越獄使應(yīng)用可以繞過代碼簽名和沙盒安全機(jī)制的全部限制,這會是一個非常危險的行為。

證書和密匙

作為一個 iOS 開發(fā)者,在你開發(fā)使用的機(jī)器上應(yīng)該已經(jīng)有一個證書,一個公鑰,以及一個私鑰。這些是代碼簽名機(jī)制的核心。像 SSL 一樣,代碼簽名也依賴于采用 X.509 標(biāo)準(zhǔn)公開密鑰加密。

在 OS X 上,X.509 的基本組成部分(譯者注:例如證書等)都是由一個叫鑰匙串訪問的工具來進(jìn)行管理。打開你開發(fā)機(jī)器上的鑰匙串訪問應(yīng)用,選擇類別選項下的“我的證書(My Certificates)”,你可以看到所有你持有的私鑰相對應(yīng)的證書。要用一個證書設(shè)置代碼簽名,你必須擁有私鑰,所以所有你擁有私鑰的證書都會被列在這里。如果你擁有一個證書的私鑰,你可以展開證書并將它的私鑰顯示出來:

http://wiki.jikexueyuan.com/project/objc/images/17-1.png" alt="" />

如果你要導(dǎo)出證書,例如為了備份(強(qiáng)烈建議進(jìn)行),一定要記得展開證書那一條顯示出私鑰并將兩行都選中。

還有一種可以用來快速地顯示出你的系統(tǒng)中能用來對代碼進(jìn)行簽名的認(rèn)證的方法,那就是利用用途廣泛的命令行工具 security

$ security find-identity -v -p codesigning                       
  1) 01C8E9712E9632E6D84EC533827B4478938A3B15 "iPhone Developer: Thomas Kollbach (7TPNXN7G6K)"

概括的講,一個證書是一個公鑰加上許多附加信息,這些附加信息都是被某個認(rèn)證機(jī)構(gòu)(Certificate Authority 簡稱 CA)進(jìn)行簽名認(rèn)證過的,認(rèn)證這個證書中的信息是準(zhǔn)確無誤的。對于 iOS 開發(fā)來說這個認(rèn)證機(jī)構(gòu)就是蘋果的認(rèn)證部門 Apple Worldwide Developer Relations CA。認(rèn)證的簽名有固定的有效期,這就意味著當(dāng)前系統(tǒng)時間需要被正確設(shè)置,因?yàn)樽C書是基于當(dāng)前時間進(jìn)行核對。這也是為什么將系統(tǒng)時間設(shè)定到過去會對 iOS 造成多方面破壞的原因之一。

http://wiki.jikexueyuan.com/project/objc/images/17-2.png" alt="" />

對于 iOS 開發(fā)來說,一般會有兩個證書:一個帶有前綴 iPhone Developer,另一個帶有前綴 iPhone Distribution。前者用于使應(yīng)用可以在你的測試設(shè)備上運(yùn)行,后者是在提交應(yīng)用到 APP store 時用到。一個證書的用途取決于它所包含的內(nèi)部信息,在鑰匙串訪問中雙擊打開一個證書文件,你可以看到許多詳細(xì)條目,拖動到最下面有一條標(biāo)記著 Apple Developer Certificate (Submission), 或者 Apple Developer Certificate (Development),具體你會看到哪一種,取決于你所打開的證書是哪一種類型,iOS 系統(tǒng)會利用這個信息來判斷你的應(yīng)用是運(yùn)行在開發(fā)模式下還是發(fā)布模式,并據(jù)此判斷以切換應(yīng)用運(yùn)行規(guī)則。

為了讓擁有公鑰的證書起作用,我們需要有私鑰。私鑰是你在為組成應(yīng)用的二進(jìn)制文件進(jìn)行簽名時派上用場的。沒有私鑰,你就無法用證書和公鑰對任何東西設(shè)置簽名。

簽名過程本身是由命令行工具 codesign 來完成的。如果你在 Xcode 中編譯一個應(yīng)用,這個應(yīng)用構(gòu)建完成之后會自動調(diào)用 codesign 命令進(jìn)行簽名,codesign 也正是給你提供了許多格式友好并且有用錯誤信息的那一個工具。你可以在 Xcode 的 project settings 中設(shè)置代碼簽名信息。

http://wiki.jikexueyuan.com/project/objc/images/17-3.png" alt="" />

需要注意的是 Xcode 只允許你在有限的選項中進(jìn)行選擇,這些選項都是你既擁有公鑰也擁有私鑰的證書。所以如果在選項中沒有出現(xiàn)你想要的那一個,那么你需要檢查的第一件事情就是你是否擁有這個證書的私鑰。在這里你需要區(qū)分開用于開發(fā)測試還是用于發(fā)布,如果你想要在機(jī)器上測試你的應(yīng)用,你需要用用于開發(fā)測試的那一對密匙來進(jìn)行簽名,如果你是要發(fā)布應(yīng)用,無論是給測試人員還是發(fā)布到 APP Store,你需要用用于發(fā)布的那一對密匙來進(jìn)行簽名。

一直以來,以上這些就是代碼簽名需要設(shè)置的全部,設(shè)置了這些就幾乎完成了。

但是在 Xcode 6 的 project settings 中出現(xiàn)了設(shè)置配置文件的選項。如果你選擇了某一個配置文件,你必須選擇這個配置文件的證書中所包含的公鑰所對應(yīng)的那個密匙對,或者你可以選擇讓 Xcode 自動完成正確的設(shè)置。關(guān)于這方面我們稍后再詳細(xì)說明,首先還是回到代碼簽名。

一個已簽名應(yīng)用的組成

一個已簽名的可執(zhí)行文件的簽名包含在 Mach-O 二進(jìn)制文件格式中;對于例如腳本這樣的非 Mach-O 可執(zhí)行文件,就存放在該文件系統(tǒng)的的擴(kuò)展屬性中。這種做法使得在 OS X 和 iOS 上的任何可執(zhí)行二進(jìn)制文件都可以被設(shè)置簽名:不論是動態(tài)庫,命令行工具,還是 .app 后綴的程序包。這也意味著設(shè)置簽名的過程實(shí)際上會改動可執(zhí)行文件的文件內(nèi)容,將簽名數(shù)據(jù)寫入二進(jìn)制文件中。

如果你擁有一個證書和它的私鑰,那么用 codesign 來設(shè)置簽名非常簡單,我們現(xiàn)在嘗試用下面列出的這個證書來為 Example.app 設(shè)置簽名:

$ codesign -s 'iPhone Developer: Thomas Kollbach (7TPNXN7G6K)' Example.app

如果你想為某一個 app 程序包重新設(shè)置簽名,那么這個工具就很有用了。為了重新設(shè)置簽名,你必須帶上 -f 參數(shù),有了這個參數(shù),codesign 會用你選擇的簽名替換掉已經(jīng)存在的那一個:

$ codesign -f -s 'iPhone Developer: Thomas Kollbach (7TPNXN7G6K)' Example.app

codesign 還可以為你提供有關(guān)一個可執(zhí)行文件簽名狀態(tài)的信息,這些信息在出現(xiàn)不明錯誤時會提供巨大的幫助。舉例來說,$ codesign -vv -d Example.app 會列出一些有關(guān) Example.app 的簽名信息:

Executable=/Users/toto/Library/Developer/Xcode/DerivedData/Example-cfsbhbvmswdivqhekxfykvkpngkg/Build/Products/Debug-iphoneos/Example.app/Example
Identifier=ch.kollba.example
Format=bundle with Mach-O thin (arm64)
CodeDirectory v=20200 size=26663 flags=0x0(none) hashes=1324+5 location=embedded
Signature size=4336
Authority=iPhone Developer: Thomas Kollbach (7TPNXN7G6K)
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
Signed Time=29.09.2014 22:29:07
Info.plist entries=33
TeamIdentifier=DZM8538E3E
Sealed Resources version=2 rules=4 files=120
Internal requirements count=1 size=184

你需要查看的第一件事是以 Authority 開頭的那三行。這三行告訴你到底是哪一個證書為這個 app 設(shè)置了簽名。在這里當(dāng)然是我的證書,iPhone Developer: Thomas Kollbach (7TPNXN7G6K)。我的這個證書則是被證書 Apple Worldwide Developer Relations Certification Authority 設(shè)置了簽名的,依此類推這個證書則是被證書 Apple Root CA 設(shè)置了簽名。

Format 中也包含了一些關(guān)于代碼的信息:Example.app 并不單單是一個可執(zhí)行文件,它是一個程序包,其中包含了一個 arm64 二進(jìn)制文件。從 Executable 中的路徑信息你可以看出,這是一個以測試為目的的打包,所以是一個 Mach-O thin 的二進(jìn)制文件。

在一堆診斷信息中還包含了兩個非常有趣的條目。 Identifier 是我在 Xcode 中設(shè)置的 bundle identifier。 TeamIdentifier 用于標(biāo)識我的工作組(系統(tǒng)會用這個來判斷應(yīng)用是否是由同一個開發(fā)者發(fā)布)。此外用于發(fā)布應(yīng)用的證書中也包含這種標(biāo)識,這種標(biāo)識在區(qū)分同一名稱下的不同證書時非常有用。

現(xiàn)在這個二進(jìn)制文件已經(jīng)用證書設(shè)置好簽名。就像中世紀(jì)人用蠟來封印信封一樣,簽名就這樣封印了這個應(yīng)用。下面我們來檢查一下封印是否完好:

$ codesign --verify Example.app
$ 

就像大多數(shù) UNIX 工具一樣,沒有任何輸出代表簽名是完好的。那么我下面破壞這個封印,只要修改一下這個二進(jìn)制文件:

$ echo 'lol' >> Example.app/Example
$ codesign --verify Example.app
Example.app: main executable failed strict validation

修改已經(jīng)簽名的應(yīng)用會破壞封印,從命令行輸出我們可以看到代碼簽名正如我們所預(yù)期一樣起到了作用。

程序包和其他資源文件

對于命令行工具和腳本來說,只是一個可執(zhí)行文件被設(shè)置簽名,但是 iOS 和 OS X 的應(yīng)用和框架則是包含了它們所需要的資源在其中的。這些資源包括圖片和不同的語言文件,資源中也包括很重要的應(yīng)用組成部分例如 XIB/NIB 文件,存檔文件(archives),甚至是證書文件。所以為一個程序包設(shè)置簽名時,這個包中的所有資源文件也都會被設(shè)置簽名。

為了達(dá)到為所有文件設(shè)置簽名的目的,簽名的過程中會在程序包中新建一個叫做 _CodeSignatue/CodeResources 的文件,這個文件中存儲了被簽名的程序包中所有文件的簽名。你可以自己去查看這個簽名列表文件,它僅僅是一個 plist 格式文件。

這個列表文件中不光包含了文件和它們的簽名的列表,還包含了一系列規(guī)則,這些規(guī)則決定了哪些資源文件應(yīng)當(dāng)被設(shè)置簽名。伴隨 OS X 10.10 DP 5 和 10.9.5 版本的發(fā)布,蘋果改變了代碼簽名的格式,也改變了有關(guān)資源的規(guī)則。如果你使用10.9.5或者更高版本的 codesign 工具,在 CodeResources 文件中會有4個不同區(qū)域,其中的 rulesfiles 是為老版本準(zhǔn)備的,而 files2rules2是為新的第二版的代碼簽名準(zhǔn)備的。最主要的區(qū)別是在新版本中你無法再將某些資源文件排除在代碼簽名之外,在過去你是可以的,只要在被設(shè)置簽名的程序包中添加一個名為 ResourceRules.plist 的文件,這個文件會規(guī)定哪些資源文件在檢查代碼簽名是否完好時應(yīng)該被忽略。但是在新版本的代碼簽名中,這種做法不再有效。所有的代碼文件和資源文件都必須設(shè)置簽名,不再可以有例外。在新版本的代碼簽名規(guī)定中,一個程序包中的可執(zhí)行程序包,例如擴(kuò)展 (extension),是一個獨(dú)立的需要設(shè)置簽名的個體,在檢查簽名是否完整時應(yīng)當(dāng)被單獨(dú)對待。

授權(quán)機(jī)制 (Entitlements) 和配置文件 (Provisioning)

到目前為止,我們都假設(shè)所有的證書起到的作用都是一樣的,并且假設(shè)如果我們有了一個有效的證書代碼簽名也就相應(yīng)的有效。然而這當(dāng)然不是唯一的規(guī)則。操作系統(tǒng)有許多標(biāo)準(zhǔn)來檢測你的代碼是否允許運(yùn)行。

這些標(biāo)準(zhǔn)并不是一成不變的。舉例來說,在 OS X 上一個應(yīng)用是否允許被開啟是由 Gatekeeper 的選項決定的,你可以在系統(tǒng)設(shè)置的安全選項中改變選項。在 Gatekeeper 選項中選擇 “受信任的開發(fā)者或者來自 Mac App Store” 會要求被打開的應(yīng)用必須被證書簽名,可以是 Mac App Store 開發(fā)者的應(yīng)用發(fā)布證書也可以是開發(fā)者 ID 證書。這些選項是由一個系統(tǒng)工具 spctl 來管理的,它管理著系統(tǒng)的所有安全評估策略。

在 iOS 上規(guī)則是不一樣的,無論是用戶還是開發(fā)者都不能改變應(yīng)用開啟策略,你必須有一個開發(fā)者帳號或者應(yīng)用發(fā)布證書才能讓應(yīng)用運(yùn)行在 iOS 系統(tǒng)上。

即使你可以讓應(yīng)用運(yùn)行起來,在 iOS 上你的應(yīng)用能做什么依然是受限制的。這些限制是沙盒管理的。沙盒和代碼簽名機(jī)制是不同的,這很重要。代碼簽名保證了這個應(yīng)用里所包含的內(nèi)容正如它所說的那樣不多不少,而沙盒則是限制了應(yīng)用訪問系統(tǒng)的資源。這兩種技術(shù)是相互合作來發(fā)揮作用的,它們都能阻止你的應(yīng)用運(yùn)行,也都能在 Xcode 中引起奇怪的問題。但是在日常開發(fā)過程中,沙盒可能會更經(jīng)常引起問題。沙盒機(jī)制在什么時候會引起問題呢,大多數(shù)情況下都是由于一個叫做授權(quán)的機(jī)制決定的。

授權(quán)機(jī)制

授權(quán)機(jī)制決定了哪些系統(tǒng)資源在什么情況下允許被一個應(yīng)用使用。簡單的說它就是一個沙盒的配置列表,上面列出了哪些行為被允許,哪些會被拒絕。

很可能你已經(jīng)猜到授權(quán)機(jī)制也是按照 plist 文件格式來列出的。Xcode 會將這個文件作為 --entitlements 參數(shù)的內(nèi)容傳給 codesign ,這個文件內(nèi)部格式如下:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>application-identifier</key>
        <string>7TPNXN7G6K.ch.kollba.example</string>
        <key>aps-environment</key>
        <string>development</string>
        <key>com.apple.developer.team-identifier</key>
        <string>7TPNXN7G6K</string>
        <key>com.apple.developer.ubiquity-container-identifiers</key>
        <array>
                <string>7TPNXN7G6K.ch.kollba.example</string>
        </array>
        <key>com.apple.developer.ubiquity-kvstore-identifier</key>
        <string>7TPNXN7G6K.ch.kollba.example</string>
        <key>com.apple.security.application-groups</key>
        <array>
                <string>group.ch.kollba.example</string>
        </array>
        <key>get-task-allow</key>
        <true/>
</dict>
</plist>

在 Xcode 的 Capabilities 選項卡下選擇一些選項之后,Xcode 就會生成這樣一段 XML。 Xcode 會自動生成一個 .entitlements 文件,然后在需要的時候往里面添加條目。當(dāng)構(gòu)建整個應(yīng)用時,這個文件也會提交給 codesign 作為應(yīng)用所需要擁有哪些授權(quán)的參考。這些授權(quán)信息必須都在開發(fā)者中心的 App ID 中啟用,并且包含在配置文件中,稍后我們會詳細(xì)討論這一點(diǎn)。在構(gòu)建應(yīng)用時需要使用的授權(quán)文件可以在 Xcode build setting 中的 code signing entitlements 中設(shè)置。

在這個應(yīng)用中我啟用了 iCloud 鍵值對存儲 (key-value storage) (com.apple.developer.ubiquity-kvstore-identifier) ,以及 iCloud 文檔存儲 (com.apple.developer.ubiquity-container-identifiers)。另外我還將應(yīng)用添加進(jìn)了一個 App Group (比如說為了與擴(kuò)展 (extensions) 共享數(shù)據(jù),com.apple.security.application-groups), 最后開啟了推送功能 (aps-environment)。這是一個開發(fā)版本,我會有將它連接到調(diào)試器的需求,這就需要將 get-task-allow 設(shè)為 true。另外,app id,也就是 bundle id 加上開發(fā)者 id,也被單獨(dú)列出來了。

當(dāng)然你并不能隨心所欲的取得授權(quán),你的應(yīng)用能否得到某一項授權(quán)是有特定的規(guī)定的。舉例來說,當(dāng) get-task-allow 被設(shè)定為 ture 時,應(yīng)用只能在用于開發(fā)的證書簽名下運(yùn)行。你被允許使用的推送環(huán)境 (aps-environment) 也存在類似的限制。

根據(jù)操作系統(tǒng)版本的不同我們可選的授權(quán)項目是不一樣的,所以很難有一份列表可以詳盡地列出所有條目。至少在文檔 Adding Capabilities 中提到的所有功能都是需要經(jīng)過授權(quán)的。

授權(quán)信息會被包含在應(yīng)用的簽名信息中。如果你在這方面遇到了問題,可以嘗試查看簽名信息中具體包含了什么授權(quán)信息:$ codesign -d --entitlements - Example.app 會列出一個和前面的很像的 XML 格式的屬性列表。你可以將這個文件的內(nèi)容添加進(jìn)一個腳本,每次構(gòu)建應(yīng)用時用腳本檢查是否包含了推送服務(wù)的授權(quán)信息,以此確保推送服務(wù)工作正常。在這里推送服務(wù)只是一個例子,你使用的服務(wù)越多,這樣的時候都添加推送通知的授權(quán),以保證可以注冊推送通知。在新版本的 Xcode 6 之后,授權(quán)信息列表會以 Example.app.xcent 這樣的名字的文件形式包含在應(yīng)用包中。在我看來,這么做是為了在出現(xiàn)配置錯誤時提供更加有用的錯誤信息。

配置文件

在整個代碼簽名和沙盒機(jī)制中有一個組成部分將簽名,授權(quán)和沙盒聯(lián)系了起來,那就是配置文件 (provisioning profiles)。

每一個 iOS 開發(fā)者可能都花費(fèi)過相當(dāng)?shù)臅r間研究如何設(shè)置配置文件,這個環(huán)節(jié)也正是會經(jīng)常出問題的地方。

一個配置文件中存放了系統(tǒng)用于判斷你的應(yīng)用是否允許運(yùn)行的信息,這就意味著如果你的配置文件有問題,修復(fù)起來會相當(dāng)煩人。

一個配置文件是一組信息的集合,這組信息決定了某一個應(yīng)用是否能夠在某一個特定的設(shè)備上運(yùn)行。配置文件可以用于讓應(yīng)用在你的開發(fā)設(shè)備上可以被運(yùn)行和調(diào)試,也可以用于內(nèi)部測試 (ad-hoc) 或者企業(yè)級應(yīng)用的發(fā)布。Xcode 會將你在 project setting 中選擇的配置文件打包進(jìn)應(yīng)用。前面提到了,選擇配置文件是 Xcode 6 才提供的功能,在 Xcode 5 或更早版本中,配置文件是 Xcode 根據(jù)你選擇的簽名證書來選擇的。事實(shí)上同一個證書可以擁有多個不同的配置文件,因此讓 Xcode 自行選擇可能存在一些不確定性,最好的方式是你自主去選擇,在 Xcode 6 中終于提供了這個功能。

http://wiki.jikexueyuan.com/project/objc/images/17-4.png" alt="" />

我們下面來仔細(xì)研究一下配置文件。如果你要在自己的機(jī)器上找到配置文件,在這個目錄下 ~/Library/MobileDevice/Provisioning Profiles。Xcode 將從開發(fā)者中心下載的全部配置文件都放在了這里。

不要驚訝,配置文件并不是一個 plist 文件,它是一個根據(jù)密碼訊息語法 (Cryptographic Message Syntax) 加密的文件(下文中會簡稱 CMS,但不要用這個簡寫 Google,這不是一個很好的關(guān)鍵字)。如果你處理過 S/MIME 郵件或者證書你會對這種加密比較熟悉,詳細(xì)信息可以查看互聯(lián)網(wǎng)工程任務(wù)組 (IETF) 制定的 RFC 3852。

采用 CMS 格式進(jìn)行加密使得配置文件可以被設(shè)置簽名,所以在蘋果給你這個文件之后文件就不能被改變了。配置文件的簽名和應(yīng)用的簽名不是一回事,它是由蘋果直接在開發(fā)者中心 (developer portal) 中設(shè)置好了的。

某些版本的 OpenSSL 可以讀取這種格式,但是 OS X 自帶那個版本并不行。幸運(yùn)的是命令行工具 security 也可以解碼這個 CMS 格式,那么我們就用 security 來看看一個 .mobileprovision 文件內(nèi)部是什么樣子:

$ security cms -D -i example.mobileprovision

這個命令會輸出簽名信息中的內(nèi)容,如果你親自試一下,接下來你會得到一個 XML 格式的 plist 文件內(nèi)容輸出。

這個列表中的內(nèi)容是 iOS 用于判斷你的應(yīng)用是否能運(yùn)行在某個設(shè)備上真正需要的配置信息,每一個配置文件都有它自己的 UUID 。Xcode 會用這個 UUID 來作為標(biāo)識,記錄你在 build settings 中選擇了哪一個配置文件。

首先來看 DeveloperCertificates 這項,這一項是一個列表,包含了可以為使用這個配置文件的應(yīng)用簽名的所有證書。如果你用了一個不在這個列表中的證書進(jìn)行簽名,無論這個證書是否有效,這個應(yīng)用都無法運(yùn)行。所有的證書都是基于 Base64 編碼符合 PEM (Privacy Enhanced Mail, RFC 1848) 格式的。要查看一個證書的詳細(xì)內(nèi)容,將編碼過的文件內(nèi)容復(fù)制粘貼到一個文件中去,像下面這樣:

-----BEGIN CERTIFICATE-----
MIIFnjCCBIagAwIBAgIIE/IgVItTuH4wDQYJKoZIhvcNAQEFBQAwgZYxCzA…
-----END CERTIFICATE-----`

然后讓 OpenSSL 來處理 openssl x509 -text -in file.pem。

回到配置文件中繼續(xù)往下看,你可能會注意到在 Entitlements 一項中包含了你的應(yīng)用的所有授權(quán)信息,鍵值就和之前在授權(quán)那節(jié)看到的一模一樣。

這些授權(quán)信息是你在開發(fā)者中心下載配置文件時在 App ID 中設(shè)置的,理想的情況下,這個文件應(yīng)該和 Xcode 為應(yīng)用設(shè)置簽名時使用的那一個同步,但這種同步并不能得到保證。這個文件的不一致是比較難發(fā)現(xiàn)的問題之一。

舉例來說,如果你在 Xcode 中添加了 iCloud 鍵值對存儲授權(quán) (com.apple.developer.ubiquity-kvstore-identifier),但是沒有更新,重新設(shè)置并下載新的配置文件,舊的配置文件規(guī)定你的應(yīng)用并沒有這一項授權(quán)。那么如果你的應(yīng)用使用了這個功能,iOS 就會拒絕你的應(yīng)用運(yùn)行。這也是當(dāng)你在開發(fā)者中心編輯了應(yīng)用的授權(quán),對應(yīng)的配置文件會被標(biāo)記為無效的原因。

如果你打開的是一個用于開發(fā)測試的證書,你會看到一項 ProvisionedDevices,在這一項里包含了所有可以用于測試的設(shè)備列表。因?yàn)榕渲梦募枰惶O果簽名,所以每次你添加了新的設(shè)備進(jìn)去就要重新下載新的配置文件。

小結(jié)

代碼簽名和配置文件這一套大概是一個 iOS 開發(fā)者必須處理的僅次于編碼的最復(fù)雜的問題之一。與在 Mac 或 PC 上直接的編譯運(yùn)行你的代碼不同,處理這些問題會是非常不同的經(jīng)歷。

雖然了解每一個部分是怎么運(yùn)作的很有幫助,但是要控制好所有這些設(shè)置和工具其實(shí)是一件很消耗時間的事情,特別是在一個開發(fā)團(tuán)隊中,到處發(fā)送證書和配置文件顯然很不方便。雖然蘋果在最近幾次發(fā)布的 Xcode 中都嘗試改善,但是我不是很確定每一項改動都起到了好的作用。處理代碼簽名是每個開發(fā)者必過的大坑。

雖然處理代碼簽名對于開發(fā)者來說非常繁瑣,但不可否認(rèn)正是它使得 iOS 對于用戶來說是一個非常安全的操作系統(tǒng)。如果你注意安全相關(guān)的新聞,每一次出現(xiàn)號稱能在 iOS 上運(yùn)行的木馬或者惡意軟件,例如不怎么出名的 FinFisher,仔細(xì)看看詳細(xì)說明,都會寫明 “需要越獄”。說實(shí)話我還沒見過面向 iOS 的不需要越獄的病毒或者木馬。

所以為代碼簽名和配置文件進(jìn)行的這些麻煩設(shè)置并不是徒勞無功。