【轉】代碼簽名探析

轉至:  objc中國

代碼簽名探析

分享文章
   

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

在 iOS 或 OS X 平臺上進行應用開發時,你所須要使用的 API 大多設計得簡潔明瞭。你能夠輕易地實現酷炫的動畫效果,便捷地進行應用發佈前測試,或是用 Core Data 將數據安全的存儲在本地。可是總有一天,你會碰上代碼簽名 (code signing) 和配置文件 (provisioning),大多數狀況下,這會是你在內心問候某些人祖宗的開始。html

若是你已經在 iOS 上開發過應用,那麼你多半已經與代碼簽名或設備配置文件打過交道了。即便是 OS X 開發者,若是你想發佈本身的應用到 Mac App Store 上去或者想參與蘋果的開發者項目,那麼也不得不開始爲本身的代碼進行簽名。java

大多數時候代碼簽名看上去像是一個難以理解的神祕黑盒。在這篇文章裏我會盡量揭示盒子內部的運做機理。git

一般來講,咱們沒法直接看到代碼簽名的運做過程,它們隱藏在 iOS 系統內部和 SDK 之中。但咱們能夠經過觀察設置代碼簽名所需工具的運做方式,來找出一些線索。除此以外,咱們還能夠參考 OS X 上的代碼簽名運做方式,畢竟 iOS 和 OS X 系出同源,咱們能夠從他們的對比之中獲得不少有用的信息。程序員

OS X 上代碼簽名技術和相應的 API 是在 Mac OS X Leopard 10.5 上首次出現的,這恰好是第一臺 iPhone 發佈的時候。這並不是巧合,由於在 iOS 上,代碼簽名起到的做用更加劇要。iPhone 是在衆多遊戲主機以後第一個大規模出售而且從頭就開始使用代碼簽名的計算平臺。只有在越獄以後,iOS 才能運行沒有簽名的代碼。越獄使應用能夠繞過代碼簽名和沙盒安全機制的所有限制,這會是一個很是危險的行爲。github

證書和密匙

做爲一個 iOS 開發者,在你開發使用的機器上應該已經有一個證書,一個公鑰,以及一個私鑰。這些是代碼簽名機制的核心。像 SSL 同樣,代碼簽名也依賴於採用 X.509 標準公開密鑰加密安全

在 OS X 上,X.509 的基本組成部分(譯者注:例如證書等)都是由一個叫鑰匙串訪問的工具來進行管理。打開你開發機器上的鑰匙串訪問應用,選擇類別選項下的「個人證書(My Certificates)」,你能夠看到全部你持有的私鑰相對應的證書。要用一個證書設置代碼簽名,你必須擁有私鑰,因此全部你擁有私鑰的證書都會被列在這裏。若是你擁有一個證書的私鑰,你能夠展開證書並將它的私鑰顯示出來:ruby

iOS Developer Certificate in the OS X keychain

若是你要導出證書,例如爲了備份(強烈建議進行),必定要記得展開證書那一條顯示出私鑰並將兩行都選中。markdown

還有一種能夠用來快速地顯示出你的系統中能用來對代碼進行簽名的認證的方法,那就是利用用途普遍的命令行工具 securityapp

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

歸納的講,一個證書是一個公鑰加上許多附加信息,這些附加信息都是被某個認證機構(Certificate Authority 簡稱 CA)進行簽名認證過的,認證這個證書中的信息是準確無誤的。對於 iOS 開發來講這個認證機構就是蘋果的認證部門 Apple Worldwide Developer Relations CA。認證的簽名有固定的有效期,這就意味着當前系統時間須要被正確設置,由於證書是基於當前時間進行覈對。這也是爲何將系統時間設定到過去會對 iOS 形成多方面破壞的緣由之一。

iOS Developer Certificate in detail

對於 iOS 開發來講,通常會有兩個證書:一個帶有前綴 iPhone Developer,另外一個帶有前綴 iPhone Distribution。前者用於使應用能夠在你的測試設備上運行,後者是在提交應用到 APP store 時用到。一個證書的用途取決於它所包含的內部信息,在鑰匙串訪問中雙擊打開一個證書文件,你能夠看到許多詳細條目,拖動到最下面有一條標記着 Apple Developer Certificate (Submission), 或者 Apple Developer Certificate (Development),具體你會看到哪種,取決於你所打開的證書是哪種類型,iOS 系統會利用這個信息來判斷你的應用是運行在開發模式下仍是發佈模式,並據此判斷以切換應用運行規則。

爲了讓擁有公鑰的證書起做用,咱們須要有私鑰。私鑰是你在爲組成應用的二進制文件進行簽名時派上用場的。沒有私鑰,你就沒法用證書和公鑰對任何東西設置簽名。

簽名過程自己是由命令行工具 codesign 來完成的。若是你在 Xcode 中編譯一個應用,這個應用構建完成以後會自動調用 codesign命令進行簽名,codesign 也正是給你提供了許多格式友好而且有用錯誤信息的那一個工具。你能夠在 Xcode 的 project settings 中設置代碼簽名信息。

Set up of the code signing identity in Xcode project settings

須要注意的是 Xcode 只容許你在有限的選項中進行選擇,這些選項都是你既擁有公鑰也擁有私鑰的證書。因此若是在選項中沒有出現你想要的那一個,那麼你須要檢查的第一件事情就是你是否擁有這個證書的私鑰。在這裏你須要區分開用於開發測試仍是用於發佈,若是你想要在機器上測試你的應用,你須要用用於開發測試的那一對密匙來進行簽名,若是你是要發佈應用,不管是給測試人員仍是發佈到 APP Store,你須要用用於發佈的那一對密匙來進行簽名。

一直以來,以上這些就是代碼簽名須要設置的所有,設置了這些就幾乎完成了。

可是在 Xcode 6 的 project settings 中出現了設置配置文件的選項。若是你選擇了某一個配置文件,你必須選擇這個配置文件的證書中所包含的公鑰所對應的那個密匙對,或者你能夠選擇讓 Xcode 自動完成正確的設置。關於這方面咱們稍後再詳細說明,首先仍是回到代碼簽名。

一個已簽名應用的組成

一個已簽名的可執行文件的簽名包含在 Mach-O 二進制文件格式中;對於例如腳本這樣的非 Mach-O 可執行文件,就存放在該文件系統的的擴展屬性中。這種作法使得在 OS X 和 iOS 上的任何可執行二進制文件均可以被設置簽名:不管是動態庫,命令行工具,仍是 .app 後綴的程序包。這也意味着設置簽名的過程實際上會改動可執行文件的文件內容,將簽名數據寫入二進制文件中。

若是你擁有一個證書和它的私鑰,那麼用 codesign 來設置簽名很是簡單,咱們如今嘗試用下面列出的這個證書來爲 Example.app設置簽名:

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

若是你想爲某一個 app 程序包從新設置簽名,那麼這個工具就頗有用了。爲了從新設置簽名,你必須帶上 -f 參數,有了這個參數,codesign 會用你選擇的簽名替換掉已經存在的那一個:

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

codesign 還能夠爲你提供有關一個可執行文件簽名狀態的信息,這些信息在出現不明錯誤時會提供巨大的幫助。舉例來講,$ codesign -vv -d Example.app 會列出一些有關 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 設置了簽名。在這裏固然是個人證書,iPhone Developer: Thomas Kollbach (7TPNXN7G6K)。個人這個證書則是被證書 Apple Worldwide Developer Relations Certification Authority 設置了簽名的,依此類推這個證書則是被證書 Apple Root CA 設置了簽名。

在 Format 中也包含了一些關於代碼的信息:Example.app 並不僅僅是一個可執行文件,它是一個程序包,其中包含了一個 arm64二進制文件。從 Executable 中的路徑信息你能夠看出,這是一個以測試爲目的的打包,因此是一個 Mach-O thin 的二進制文件。

在一堆診斷信息中還包含了兩個很是有趣的條目。 Identifier 是我在 Xcode 中設置的 bundle identifier。 TeamIdentifier 用於標識個人工做組(系統會用這個來判斷應用是不是由同一個開發者發佈)。此外用於發佈應用的證書中也包含這種標識,這種標識在區分同一名稱下的不一樣證書時很是有用。

如今這個二進制文件已經用證書設置好籤名。就像中世紀人用蠟來封印信封同樣,簽名就這樣封印了這個應用。下面咱們來檢查一下封印是否無缺:

$ codesign --verify Example.app $ 

就像大多數 UNIX 工具同樣,沒有任何輸出表明簽名是無缺的。那麼我下面破壞這個封印,只要修改一下這個二進制文件:

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

修改已經簽名的應用會破壞封印,從命令行輸出咱們能夠看到代碼簽名正如咱們所預期同樣起到了做用。

程序包和其餘資源文件

對於命令行工具和腳原本說,只是一個可執行文件被設置簽名,可是 iOS 和 OS X 的應用和框架則是包含了它們所須要的資源在其中的。這些資源包括圖片和不一樣的語言文件,資源中也包括很重要的應用組成部分例如 XIB/NIB 文件,存檔文件(archives),甚至是證書文件。因此爲一個程序包設置簽名時,這個包中的全部資源文件也都會被設置簽名。

爲了達到爲全部文件設置簽名的目的,簽名的過程當中會在程序包中新建一個叫作 _CodeSignatue/CodeResources 的文件,這個文件中存儲了被簽名的程序包中全部文件的簽名。你能夠本身去查看這個簽名列表文件,它僅僅是一個 plist 格式文件。

這個列表文件中不光包含了文件和它們的簽名的列表,還包含了一系列規則,這些規則決定了哪些資源文件應當被設置簽名。伴隨 OS X 10.10 DP 5 和 10.9.5 版本的發佈,蘋果改變了代碼簽名的格式,也改變了有關資源的規則。若是你使用10.9.5或者更高版本的 codesign 工具,在 CodeResources 文件中會有4個不一樣區域,其中的 rules 和 files 是爲老版本準備的,而 files2 和 rules2是爲新的第二版的代碼簽名準備的。最主要的區別是在新版本中你沒法再將某些資源文件排除在代碼簽名以外,在過去你是能夠的,只要在被設置簽名的程序包中添加一個名爲 ResourceRules.plist 的文件,這個文件會規定哪些資源文件在檢查代碼簽名是否無缺時應該被忽略。可是在新版本的代碼簽名中,這種作法再也不有效。全部的代碼文件和資源文件都必須設置簽名,再也不能夠有例外。在新版本的代碼簽名規定中,一個程序包中的可執行程序包,例如擴展 (extension),是一個獨立的須要設置簽名的個體,在檢查簽名是否完整時應當被單獨對待。

受權機制 (Entitlements) 和配置文件 (Provisioning)

到目前爲止,咱們都假設全部的證書起到的做用都是同樣的,而且假設若是咱們有了一個有效的證書代碼簽名也就相應的有效。然而這固然不是惟一的規則。操做系統有許多標準來檢測你的代碼是否容許運行。

這些標準並非一成不變的。舉例來講,在 OS X 上一個應用是否容許被開啓是由 Gatekeeper 的選項決定的,你能夠在系統設置的安全選項中改變選項。在 Gatekeeper 選項中選擇 「受信任的開發者或者來自 Mac App Store」 會要求被打開的應用必須被證書籤名,能夠是 Mac App Store 開發者的應用發佈證書也能夠是開發者 ID 證書。這些選項是由一個系統工具 spctl 來管理的,它管理着系統的全部安全評估策略。

在 iOS 上規則是不同的,不管是用戶仍是開發者都不能改變應用開啓策略,你必須有一個開發者賬號或者應用發佈證書才能讓應用運行在 iOS 系統上。

即便你可讓應用運行起來,在 iOS 上你的應用能作什麼依然是受限制的。這些限制是沙盒管理的。沙盒和代碼簽名機制是不一樣的,這很重要。代碼簽名保證了這個應用裏所包含的內容正如它所說的那樣很少很多,而沙盒則是限制了應用訪問系統的資源。這兩種技術是相互合做來發揮做用的,它們都能阻止你的應用運行,也都能在 Xcode 中引發奇怪的問題。可是在平常開發過程當中,沙盒可能會更常常引發問題。沙盒機制在何時會引發問題呢,大多數狀況下都是因爲一個叫作受權的機制決定的。

受權機制

受權機制決定了哪些系統資源在什麼狀況下容許被一個應用使用。簡單的說它就是一個沙盒的配置列表,上面列出了哪些行爲被容許,哪些會被拒絕。

極可能你已經猜到受權機制也是按照 plist 文件格式來列出的。Xcode 會將這個文件做爲 --entitlements 參數的內容傳給 codesign ,這個文件內部格式以下:

<?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文件,而後在須要的時候往裏面添加條目。當構建整個應用時,這個文件也會提交給 codesign 做爲應用所須要擁有哪些受權的參考。這些受權信息必須都在開發者中心的 App ID 中啓用,而且包含在配置文件中,稍後咱們會詳細討論這一點。在構建應用時須要使用的受權文件能夠在 Xcode build setting 中的 code signing entitlements 中設置。

在這個應用中我啓用了 iCloud 鍵值對存儲 (key-value storage) (com.apple.developer.ubiquity-kvstore-identifier) ,以及 iCloud 文檔存儲 (com.apple.developer.ubiquity-container-identifiers)。另外我還將應用添加進了一個 App Group (好比說爲了與擴展 (extensions) 共享數據,com.apple.security.application-groups), 最後開啓了推送功能 (aps-environment)。這是一個開發版本,我會有將它鏈接到調試器的需求,這就須要將 get-task-allow 設爲 true。另外,app id,也就是 bundle id 加上開發者 id,也被單獨列出來了。

固然你並不能爲所欲爲的取得受權,你的應用可否獲得某一項受權是有特定的規定的。舉例來講,當 get-task-allow 被設定爲 ture 時,應用只能在用於開發的證書籤名下運行。你被容許使用的推送環境 (aps-environment) 也存在相似的限制。

根據操做系統版本的不一樣咱們可選的受權項目是不同的,因此很難有一份列表能夠詳盡地列出全部條目。至少在文檔 Adding Capabilities 中提到的全部功能都是須要通過受權的。

受權信息會被包含在應用的簽名信息中。若是你在這方面遇到了問題,能夠嘗試查看簽名信息中具體包含了什麼受權信息:$ codesign -d --entitlements - Example.app 會列出一個和前面的很像的 XML 格式的屬性列表。你能夠將這個文件的內容添加進一個腳本,每次構建應用時用腳本檢查是否包含了推送服務的受權信息,以此確保推送服務工做正常。在這裏推送服務只是一個例子,你使用的服務越多,這樣的時候都添加推送通知的受權,以保證能夠註冊推送通知。在新版本的 Xcode 6 以後,受權信息列表會以 Example.app.xcent 這樣的名字的文件形式包含在應用包中。在我看來,這麼作是爲了在出現配置錯誤時提供更加有用的錯誤信息。

配置文件

在整個代碼簽名和沙盒機制中有一個組成部分將簽名,受權和沙盒聯繫了起來,那就是配置文件 (provisioning profiles)。

每個 iOS 開發者可能都花費過至關的時間研究如何設置配置文件,這個環節也正是會常常出問題的地方。

一個配置文件中存放了系統用於判斷你的應用是否容許運行的信息,這就意味着若是你的配置文件有問題,修復起來會至關煩人。

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

Project settings for selecting the provisioning profile

咱們下面來仔細研究一下配置文件。若是你要在本身的機器上找到配置文件,在這個目錄下 ~/Library/MobileDevice/Provisioning Profiles。Xcode 將從開發者中心下載的所有配置文件都放在了這裏。

不要驚訝,配置文件並非一個 plist 文件,它是一個根據密碼訊息語法 (Cryptographic Message Syntax) 加密的文件(下文中會簡稱 CMS,但不要用這個簡寫 Google,這不是一個很好的關鍵字)。若是你處理過 S/MIME 郵件或者證書你會對這種加密比較熟悉,詳細信息能夠查看互聯網工程任務組 (IETF) 制定的 RFC 3852

採用 CMS 格式進行加密使得配置文件能夠被設置簽名,因此在蘋果給你這個文件以後文件就不能被改變了。配置文件的簽名和應用的簽名不是一回事,它是由蘋果直接在開發者中心 (developer portal) 中設置好了的。

某些版本的 OpenSSL 能夠讀取這種格式,可是 OS X 自帶那個版本並不行。幸運的是命令行工具 security 也能夠解碼這個 CMS 格式,那麼咱們就用 security 來看看一個 .mobileprovision 文件內部是什麼樣子:

$ security cms -D -i example.mobileprovision

這個命令會輸出簽名信息中的內容,若是你親自試一下,接下來你會獲得一個 XML 格式的 plist 文件內容輸出。

這個列表中的內容是 iOS 用於判斷你的應用是否能運行在某個設備上真正須要的配置信息,每個配置文件都有它本身的 UUID 。Xcode 會用這個 UUID 來做爲標識,記錄你在 build settings 中選擇了哪個配置文件。

首先來看 DeveloperCertificates 這項,這一項是一個列表,包含了能夠爲使用這個配置文件的應用簽名的全部證書。若是你用了一個不在這個列表中的證書進行簽名,不管這個證書是否有效,這個應用都沒法運行。全部的證書都是基於 Base64 編碼符合 PEM (Privacy Enhanced Mail, RFC 1848) 格式的。要查看一個證書的詳細內容,將編碼過的文件內容複製粘貼到一個文件中去,像下面這樣:

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

而後讓 OpenSSL 來處理 openssl x509 -text -in file.pem

回到配置文件中繼續往下看,你可能會注意到在 Entitlements 一項中包含了你的應用的全部受權信息,鍵值就和以前在受權那節看到的如出一轍。

這些受權信息是你在開發者中心下載配置文件時在 App ID 中設置的,理想的狀況下,這個文件應該和 Xcode 爲應用設置簽名時使用的那一個同步,但這種同步並不能獲得保證。這個文件的不一致是比較難發現的問題之一。

舉例來講,若是你在 Xcode 中添加了 iCloud 鍵值對存儲受權 (com.apple.developer.ubiquity-kvstore-identifier),可是沒有更新,從新設置並下載新的配置文件,舊的配置文件規定你的應用並無這一項受權。那麼若是你的應用使用了這個功能,iOS 就會拒絕你的應用運行。這也是當你在開發者中心編輯了應用的受權,對應的配置文件會被標記爲無效的緣由。

若是你打開的是一個用於開發測試的證書,你會看到一項 ProvisionedDevices,在這一項裏包含了全部能夠用於測試的設備列表。由於配置文件須要被蘋果簽名,因此每次你添加了新的設備進去就要從新下載新的配置文件。

小結

代碼簽名和配置文件這一套大概是一個 iOS 開發者必須處理的僅次於編碼的最複雜的問題之一。與在 Mac 或 PC 上直接的編譯運行你的代碼不一樣,處理這些問題會是很是不一樣的經歷。

雖然瞭解每個部分是怎麼運做的頗有幫助,可是要控制好全部這些設置和工具實際上是一件很消耗時間的事情,特別是在一個開發團隊中,處處發送證書和配置文件顯然很不方便。雖然蘋果在最近幾回發佈的 Xcode 中都嘗試改善,可是我不是很肯定每一項改動都起到了好的做用。處理代碼簽名是每一個開發者必過的大坑。

雖然處理代碼簽名對於開發者來講很是繁瑣,但不能否認正是它使得 iOS 對於用戶來講是一個很是安全的操做系統。若是你注意安全相關的新聞,每一次出現號稱能在 iOS 上運行的木馬或者惡意軟件,例如不怎麼出名的 FinFisher,仔細看看詳細說明,都會寫明 「須要越獄」。說實話我還沒見過面向 iOS 的不須要越獄的病毒或者木馬。

因此爲代碼簽名和配置文件進行的這些麻煩設置並非徒勞無功。


話題 #17 下的更多文章

原文 Inside Code Signing

關於譯者

dopcn

dopcn

@dopcn 作不任性的程序員

http://weizhou.name

相關文章
相關標籤/搜索