iOS安全系列之一:HTTPS

如何打造一個安全的App?這是每個移動開發者必須面對的問題。在移動App開發領域,開發工程師對於安全方面的考慮廣泛比較欠缺,而因爲iOS平臺的封閉性,遭遇到的安全問題相比於Android來講要少得多,這就致使了許多iOS開發人員對於安全性方面沒有太多的深刻,但對於一個合格的軟件開發者來講,安全知識是必備知識之一。html

對於未越獄的iOS設備來講,因爲強大的沙箱和受權機制,以及Apple本身掌控的App Store, 基本上杜絕了惡意軟件的入侵(非越獄)。但除系統安全以外,咱們仍是面臨不少的安全問題:網絡安全、數據安全等,每一項涉及也很是廣,安全是很是大的課題,本人並不是專業的安全專家,只是從開發者的角度,分析咱們常遇到的各項安全問題,並提出一般的解決方法,與各位同窗交流學習。ios

每個軟件工程師都有義務保護用戶數據的隱私和安全。git



首先是網絡安全,OSI模型各層都會面臨相應的網絡安全問題,涉及寬廣,而網絡安全也是安全領域發展最爲繁榮的領域。本文咱們只是從移動應用開發角度,以儘可能簡單的方式,講解HTTPS核心概念知識,以及在iOS平臺上的實現。建議如今還在使用HTTP的應用都升級到HTTPS。github

引讀:互聯網全站HTTPS的時代已經到來objective-c

 

1. HTTPS

其實HTTPS從最終的數據解析的角度,與HTTP沒有任何的區別,HTTPS就是將HTTP協議數據包放到SSL/TSL層加密後,在TCP/IP層組成IP數據報去傳輸,以此保證傳輸數據的安全;而對於接收端,在SSL/TSL將接收的數據包解密以後,將數據傳給HTTP協議層,就是普通的HTTP數據。HTTP和SSL/TSL都處於OSI模型的應用層。從HTTP切換到HTTPS是一個很是簡單的過程,在作具體的切換操做以前,咱們須要瞭解幾個概念:api



SSL/TSL

關於SSL/TSL,阮一峯的兩篇博客文章作了很好的介紹:數組

簡單的來講,SSL/TSL經過四次握手,主要交換三個信息:瀏覽器

  1. 數字證書:該證書包含了公鑰等信息,通常是由服務器發給客戶端,接收方經過驗證這個證書是否是由信賴的CA簽發,或者與本地的證書相對比,來判斷證書是否可信;假如須要雙向驗證,則服務器和客戶端都須要發送數字證書給對方驗證;
  2. 三個隨機數:這三個隨機數構成了後續通訊過程當中用來對數據進行對稱加密解密的「對話密鑰」安全

    首先客戶端先發第一個隨機數N1,而後服務器回了第二個隨機數N2(這個過程同時把以前提到的證書發給客戶端),這兩個隨機數都是明文的;而第三個隨機數N3(這個隨機數被稱爲Premaster secret),客戶端用數字證書的公鑰進行非對稱加密,發給服務器;而服務器用只有本身知道的私鑰來解密,獲取第三個隨機數。這樣,服務端和客戶端都有了三個隨機數N1+N2+N3,而後兩端就使用這三個隨機數來生成「對話密鑰」,在此以後的通訊都是使用這個「對話密鑰」來進行對稱加密解密。由於這個過程當中,服務端的私鑰只用來解密第三個隨機數,歷來沒有在網絡中傳輸過,這樣的話,只要私鑰沒有被泄露,那麼數據就是安全的。服務器

  3. 加密通訊協議:就是雙方商量使用哪種加密方式,假如二者支持的加密方式不匹配,則沒法進行通訊;

有個常見的問題,關於隨機數爲何要三個?只最後一個隨機數N3不能夠麼?

這是因爲SSL/TLS設計,就假設服務器不相信全部的客戶端都可以提供徹底隨機數,假如某個客戶端提供的隨機數不隨機的話,就大大增長了「對話密鑰」被破解的風險,因此由三組隨機數組成最後的隨機數,保證了隨機數的隨機性,以此來保證每次生成的「對話密鑰」安全性。



數字證書

數字證書是一個電子文檔,其中包含了持有者的信息、公鑰以及證實該證書有效的數字簽名。而數字證書以及相關的公鑰管理和驗證等技術組成了PKI(公鑰基礎設施)規範體系。通常來講,數字證書是由數字證書認證機構(Certificate authority,即CA)來負責簽發和管理,並承擔PKI體系中公鑰合法性的檢驗責任;數字證書的類型有不少,而HTTPS使用的是SSL證書。

怎麼來驗證數字證書是由CA簽發的,而不是第三方僞造的呢? 在回答這個問題前,咱們須要先了解CA的組織結構。首先,CA組織結構中,最頂層的就是根CA,根CA下能夠受權給多個二級CA,而二級CA又能夠受權多個三級CA,因此CA的組織結構是一個樹結構。對於SSL證書市場來講,主要被Symantec(旗下有VeriSign和GeoTrust)、Comodo SSL、Go Daddy 和 GlobalSign 瓜分。 瞭解了CA的組織結構後,來看看數字證書的簽發流程:


數字證書的簽發流程

數字證書的簽發機構CA,在接收到申請者的資料後進行覈對並肯定信息的真實有效,而後就會製做一份符合X.509標準的文件。證書中的證書內容包含的持有者信息和公鑰等都是由申請者提供的,而數字簽名則是CA機構對證書內容進行hash加密後獲得的,而這個數字簽名就是咱們驗證證書是不是有可信CA簽發的數據。


數字證書的驗證流程

假設上圖證書是由證書籤發機構CA1簽發的。

1)接收端接到一份數字證書Cer1後,對證書的內容作Hash獲得H1;

2)從簽發該證書的機構CA1的數字證書中找到公鑰,對證書上數字簽名進行解密,獲得證書Cer1簽名的Hash摘要H2;

3)對比H1和H2,如相等,則表示證書沒有被篡改。

4)但這個時候仍是不知道CA是不是合法的,咱們看到上圖中有CA機構的數字證書,這個證書是公開的,全部人均可以獲取到。而這個證書中的數字簽名是上一級生成的,因此能夠這樣一直遞歸驗證下去,直到根CA。根CA是自驗證的,即他的數字簽名是由本身的私鑰來生成的。合法的根CA會被瀏覽器和操做系統加入到權威信任CA列表中,這樣就完成了最終的驗證。因此,必定要保護好本身環境(瀏覽器/操做系統)中根CA信任列表,信任了根CA就表示信任全部根CA下全部子級CA所簽發的證書,不要隨便添加根CA證書。

通常操做系統和瀏覽器只包含根CA機構的證書,而在配置Web服務器的HTTPS時,也會將配置整個證書鏈,因此整個校驗流程是從最後的葉子節點證書開始,用父節點校驗子節點,一層層校驗整個證書鏈的可信性。

打個比喻:父(根CA數字證書)-子(CA數字證書)-孫(數字證書)三代人,假設父沒有其餘兄弟(至關於根CA機構是惟一的),假如子與父進行DNA親子鑑定,檢測DNA位點(即證書籤名)相同,那就基本肯定子是由父所生;孫與子同樣。這樣就可以肯定孫確定是源於父一脈,是父(根CA數字證書)的合法繼承人。數字證書的驗證就是基於一樣的原理。



Basic Constraint校驗漏洞

那是否無論多少層均可以這樣一直信任下去呢?理論上是可行的,但會遇到一個問題。假設我從可信CA機構購買了一張證書,使用這張證書籤發的證書是否也會被操做系統和瀏覽器信任呢?明顯是不該該相信的,由於我並非CA機構,假如我簽發的證書也被信任的話,那我徹底能夠本身簽發任何域名的證書來進行僞造攻擊。這就是著名的Basic Constraint校驗漏洞,X.509證書中的Basic Constraint包含了這是否是一個CA機構,以及有效證書路徑的最大深度(即,這個CA還可否繼續簽發CA機構證書及其簽發子CA證書的路徑深度)。但在幾年前,包括微軟和Apple都爆出了沒有正確校驗這些信息的漏洞。

Basic Constraint信息請看下圖:


Google Internet Authority G2

上圖是Google Internet Authority G2的證書,該證書是個CA機構證書;路徑深度爲0,表示該證書沒法再簽發CA證書,只能簽發客戶證書(client certificate)。


google.com

上圖是google.com的證書,這是個客戶證書(client certificate),不可再簽發子證書,因此由該證書籤發的子證書是不會被信任的。

瞭解了上面關於SSL/TSL通訊加密策略以及數字證書的概念以後,對HTTPS的安全機制就有了個初步的瞭解,下面咱們看如何在iOS上實現對HTTPS的支持。



2. 實現支持HTTPS

首先,須要明確你使用HTTP/HTTPS的用途,由於OSX和iOS平臺提供了多種API,來支持不一樣的用途,官方文檔《Making HTTP and HTTPS Requests》有詳細的說明,而文檔《HTTPS Server Trust Evaluation》則詳細講解了HTTPS驗證相關知識,這裏就很少說了。本文主要講解咱們最經常使用的NSURLConnection支持HTTPS的實現(NSURLSession的實現方法相似,只是要求受權證實的回調不同而已),以及怎麼樣使用AFNetworking這個很是流行的第三方庫來支持HTTPS。本文假設你對HTTP以及NSURLConnection的接口有了足夠的瞭解。

 

2.1 驗證證書的API

相關的Api在Security Framework中,驗證流程以下:

1). 第一步,先獲取須要驗證的信任對象(Trust Object)。這個Trust Object在不一樣的應用場景下獲取的方式都不同,對於NSURLConnection來講,是從delegate方法-connection:willSendRequestForAuthenticationChallenge:回調回來的參數challenge中獲取([challenge.protectionSpace serverTrust])。

2). 使用系統默認驗證方式驗證Trust Object。SecTrustEvaluate會根據Trust Object的驗證策略,一級一級往上,驗證證書鏈上每一級數字簽名的有效性(上一部分有講解),從而評估證書的有效性。

3). 如第二步驗證經過了,通常的安全要求下,就能夠直接驗證經過,進入到下一步:使用Trust Object生成一份憑證([NSURLCredential credentialForTrust:serverTrust]),傳入challenge的sender中([challenge.sender useCredential:cred forAuthenticationChallenge:challenge])處理,創建鏈接。

4). 假若有更強的安全要求,能夠繼續對Trust Object進行更嚴格的驗證。經常使用的方式是在本地導入證書,驗證Trust Object與導入的證書是否匹配。更多的方法能夠查看Enforcing Stricter Server Trust Evaluation,這一部分在講解AFNetworking源碼中會講解到。

5). 假如驗證失敗,取消這次Challenge-Response Authentication驗證流程,拒絕鏈接請求。

ps: 假如是自建證書的,則不使用第二步系統默認的驗證方式,由於自建證書的根CA的數字簽名未在操做系統的信任列表中。

iOS受權驗證的API和流程大概瞭解了,下面,咱們看看在NSURLConnection中的代碼實現:

 

2.2 使用NSURLConnection支持HTTPS的實現

// Now start the connection
NSURL * httpsURL = [NSURL URLWithString:@"https://www.google.com"];
self.connection = [NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:httpsURL] delegate:self];

    
//回調
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
     //1)獲取trust object
    SecTrustRef trust = challenge.protectionSpace.serverTrust;
    SecTrustResultType result;
    
    //2)SecTrustEvaluate對trust進行驗證
    OSStatus status = SecTrustEvaluate(trust, &result);
    if (status == errSecSuccess &&
        (result == kSecTrustResultProceed ||
           result == kSecTrustResultUnspecified)) {
           
           //3)驗證成功,生成NSURLCredential憑證cred,告知challenge的sender使用這個憑證來繼續鏈接
        NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
        [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
        
    } else {
    
        //5)驗證失敗,取消此次驗證流程
        [challenge.sender cancelAuthenticationChallenge:challenge];
        
  }
}

 

上面是代碼是經過系統默認驗證流程來驗證證書的。假如咱們是自建證書的呢?這樣Trust Object裏面服務器的證書由於不是可信任的CA簽發的,因此直接使用SecTrustEvaluate進行驗證是不會成功。又或者,即便服務器返回的證書是信任CA簽發的,又如何肯定這證書就是咱們想要的特定證書?這就須要先在本地導入證書,設置成須要參與驗證的Anchor Certificate(錨點證書,經過SecTrustSetAnchorCertificates設置了參與校驗錨點證書以後,假如驗證的數字證書是這個錨點證書的子節點,即驗證的數字證書是由錨點證書對應CA或子CA簽發的,或是該證書自己,則信任該證書),再調用SecTrustEvaluate來驗證。代碼以下

 

//先導入證書
NSString * cerPath = ...; //證書的路徑
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
SecCertificateRef certificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)(cerData));
self.trustedCertificates = @[CFBridgingRelease(certificate)];

//回調
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
     //1)獲取trust object
    SecTrustRef trust = challenge.protectionSpace.serverTrust;
    SecTrustResultType result;

    //注意:這裏將以前導入的證書設置成下面驗證的Trust Object的anchor certificate
      SecTrustSetAnchorCertificates(trust, (__bridge CFArrayRef)self.trustedCertificates);

    //2)SecTrustEvaluate會查找前面SecTrustSetAnchorCertificates設置的證書或者系統默認提供的證書,對trust進行驗證
    OSStatus status = SecTrustEvaluate(trust, &result);
    if (status == errSecSuccess &&
        (result == kSecTrustResultProceed ||
           result == kSecTrustResultUnspecified)) {
           
           //3)驗證成功,生成NSURLCredential憑證cred,告知challenge的sender使用這個憑證來繼續鏈接
        NSURLCredential *cred = [NSURLCredential credentialForTrust:trust];
        [challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
        
    } else {
    
        //5)驗證失敗,取消此次驗證流程
        [challenge.sender cancelAuthenticationChallenge:challenge];
        
  }
}
 

建議採用本地導入證書的方式驗證證書,來保證足夠的安全性。更多的驗證方法,請查看官方文檔《HTTPS Server Trust Evaluation》

 

2.3 使用AFNetworking來支持HTTPS

AFNetworking是iOS/OSX開發最流行的第三方開源庫之一,其做者是很是著名的iOS/OSX開發者Mattt Thompson,其博客NSHipster也是iOS/OSX開發者學習和開闊技術視野的好地方。AFNetworking已經將上面的邏輯代碼封裝好,甚至更完善,在AFSecurityPolicy文件中,有興趣能夠閱讀這個模塊的代碼;

AFNetworking上配置對HTTPS的支持很是簡單:

 

NSURL * url = [NSURL URLWithString:@"https://www.google.com"];
AFHTTPRequestOperationManager * requestOperationManager = [[AFHTTPRequestOperationManager alloc] initWithBaseURL:url];
dispatch_queue_t requestQueue = dispatch_create_serial_queue_for_name("kRequestCompletionQueue");
requestOperationManager.completionQueue = requestQueue;

AFSecurityPolicy * securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];

//allowInvalidCertificates 是否容許無效證書(也就是自建的證書),默認爲NO
//若是是須要驗證自建證書,須要設置爲YES
securityPolicy.allowInvalidCertificates = YES;

//validatesDomainName 是否須要驗證域名,默認爲YES;
//假如證書的域名與你請求的域名不一致,需把該項設置爲NO;如設成NO的話,即服務器使用其餘可信任機構頒發的證書,也能夠創建鏈接,這個很是危險,建議打開。
//置爲NO,主要用於這種狀況:客戶端請求的是子域名,而證書上的是另一個域名。由於SSL證書上的域名是獨立的,假如證書上註冊的域名是www.google.com,那麼mail.google.com是沒法驗證經過的;固然,有錢能夠註冊通配符的域名*.google.com,但這個仍是比較貴的。
//如置爲NO,建議本身添加對應域名的校驗邏輯。
securityPolicy.validatesDomainName = YES;

//validatesCertificateChain 是否驗證整個證書鏈,默認爲YES
//設置爲YES,會將服務器返回的Trust Object上的證書鏈與本地導入的證書進行對比,這就意味着,假如你的證書鏈是這樣的:
//GeoTrust Global CA 
//    Google Internet Authority G2
//        *.google.com
//那麼,除了導入*.google.com以外,還須要導入證書鏈上全部的CA證書(GeoTrust Global CA, Google Internet Authority G2);
//如是自建證書的時候,能夠設置爲YES,加強安全性;假如是信任的CA所簽發的證書,則建議關閉該驗證,由於整個證書鏈一一比對是徹底沒有必要(請查看源代碼);
securityPolicy.validatesCertificateChain = NO;

requestOperationManager.securityPolicy = securityPolicy;
 

這就是AFNetworking的支持HTTPS的主要配置說明,AFHTTPSessionManager與之基本一致,就不重複了。

參考連接: http://oncenote.com/2014/10/21/Security-1-HTTPS/

     http://www.cnblogs.com/oc-bowen/p/5896041.html

相關文章
相關標籤/搜索