一. HTTPS
其實HTTPS從最終的資料解析的角度,與HTTP沒有任何的差別,HTTPS就是将HTTP協定資料包放到SSL/TSL層加密後,在TCP/IP層組成IP資料報去傳輸,以此保證傳輸資料的安全;而對于接收端,在SSL/TSL将接收的資料包解密之後,将資料傳給HTTP協定層,就是普通的HTTP資料。HTTP和SSL/TSL都處于OSI模型的應用層。從HTTP切換到HTTPS是一個非常簡單的過程,在做具體的切換操作之前,我們需要了解幾個概念:
SSL/TLS
為了保證這些隐私資料能加密傳輸,于是網景公司設計了SSL(Secure Sockets Layer)協定用于對HTTP協定傳輸的資料進行加密,進而就誕生了HTTPS。SSL目前的版本是3.0,被IETF(Internet Engineering Task Force)定義在RFC 6101中,之後IETF對SSL 3.0進行了更新,于是出現了TLS(Transport Layer Security) 1.0,定義在RFC 2246。實際上我們現在的HTTPS都是用的TLS協定,但是由于SSL出現的時間比較早,并且依舊被現在浏覽器所支援,是以SSL依然是HTTPS的代名詞,但無論是TLS還是SSL都是上個世紀的事情,SSL最後一個版本是3.0,今後TLS将會繼承SSL優良血統繼續為我們進行加密服務。
- SSL/TLS協定運作機制的概述
- 圖解SSL/TLS協定
簡單的來說,SSL/TSL通過四次握手。SSL協定的工作流程:
伺服器認證階段:
用戶端向伺服器發送一個開始資訊“Hello”以便開始一個新的會話連接配接;
伺服器根據客戶的資訊确定是否需要生成新的主密鑰,如需要則伺服器在響應客戶的“Hello”資訊時将包含生成主密鑰所需的資訊;
客戶根據收到的伺服器響應資訊,産生一個主密鑰,并用伺服器的公開密鑰加密後傳給伺服器;
伺服器恢複該主密鑰,并傳回給客戶一個用主密鑰認證的資訊,以此讓客戶認證伺服器。
使用者認證階段:
在此之前,伺服器已經通過了客戶認證,這一階段主要完成對客戶的認證。
經認證的伺服器發送一個提問給客戶,客戶則傳回(數字)簽名後的提問和其公開密鑰,進而向伺服器提供認證。
二. App Transport Security
iOS9中新增App Transport Security(簡稱ATS)特性, 主要使到原來請求的時候用到的HTTP,都轉向TLS1.2協定進行傳輸。這也意味着所有的HTTP協定都強制使用了HTTPS協定進行傳輸。在 iOS 9 和 OS X 10.11 中,預設情況下非 HTTPS 的網絡通路是被禁止的。當然,因為這樣的推進影響面非常廣,作為緩沖,我們可以在 Info.plist 中添加
NSAppTransportSecurity
字典并且将
NSAllowsArbitraryLoads
設定為
YES
來禁用 ATS。
不過,WWDC 16 中,Apple 表示将繼續在 iOS 10 和 macOS 10.12 裡收緊對普通 HTTP 的通路限制。從 2017 年 1 月 1 日起,所有的新送出 app 預設是不允許使用
NSAllowsArbitraryLoads
來繞過 ATS 限制的,也就是說,我們最好保證 app 的所有網絡請求都是 HTTPS 加密的,否則可能會在應用稽核時遇到麻煩。
三. iOS10 NSAppTransportSecurity
通過在info.plist中配置這個鍵,開發者可以自定義網絡安全政策。例如:
允許針對個别伺服器的不安全通路。
允許不安全的 web 或媒體内容通路,但不影響整個app的ATS政策。
啟用新的安全特性,例如Certificate Transparency。
對NSAppTransportSecurity的支援自 iOS9.0,OS X v10.11 開始,适用于 app 和 app extension。
自 iOS10.0,macOS 10.12 開始,增加了對下列子鍵的支援:
- NSAllowsArbitraryLoadsInMedia
- NSAllowsArbitraryLoadsInWebContent
- NSRequiresCertificateTransparency
- NSAllowsLocalNetworking
ATS Configuration Basics / ATS 配置基礎知識
對于使用 iOS9.0, OS X v10.11 SDK 及以上的 app 來說,ATS(App Transport Security)預設開啟,NSAllowsArbitraryLoads是字典NSAppTransportSecurity的根鍵,預設值NO。
在啟用 ATS 的情況下,所有的 HTTP 請求必須為 HTTPS(RFC 2818) 連接配接。任何不安全的 HTTP 請求都将失敗。ATS 使用 TLS(Transport Layer Security)v1.2(RFC 5246)。
下面是字典NSAppTransportSecurity的總體結構,所有鍵都是非必填項:
NSAppTransportSecurity : Dictionary {
NSAllowsArbitraryLoads : Boolean
NSAllowsArbitraryLoadsInMedia : Boolean
NSAllowsArbitraryLoadsInWebContent : Boolean
NSAllowsLocalNetworking : Boolean
NSExceptionDomains : Dictionary {
<domain-name-string> : Dictionary {
NSIncludesSubdomains : Boolean
NSExceptionAllowsInsecureHTTPLoads : Boolean
NSExceptionMinimumTLSVersion : String
NSExceptionRequiresForwardSecrecy : Boolean // Default value is YES
NSRequiresCertificateTransparency : Boolean
}
}
}
可以看出,所有鍵可以分為兩類:主鍵,這些鍵用來定義 app 的總體 ATS 政策;子鍵,即NSExceptionDomains下面的鍵,使用這些鍵針對某個域名單獨配置。
主鍵包括:
-
NSAllowsArbitraryLoads
設定為 YES,解除整個 app 的 ATS 限制;但是,通過NSExceptionDomains進 行的配置依然有效。預設值為 NO。
注意:設定為 YES,會引發 App Stroe 的審查,開發者必須說明原因。
-
NSAllowsArbitraryLoadsInMedia
設定為 YES,解除通過 AV Foundation 架構通路媒體内容時的 ATS 限制;啟用這個 鍵,務必確定載入的媒體内容已經被加密,例如受FairPlay保護的檔案,或者是安全的 HLS流媒,其中不包含敏感的個人資訊。預設為 NO。
-
NSAllowsArbitraryLoadsInWebContent
設定為 YES,解除通過 web view 發出的網絡請求的 ATS 限制。啟用這個鍵,可以使 app 通路任意網頁内容,但不影響 app 的總體 ATS 政策。此鍵值預設為 NO。
-
NSAllowsLocalNetworking
設定為 YES,使得 app 可以載入任意本地資源,但不影響 app 的總體 ATS 政策。默 認為 NO。
-
NSExceptionDomains
為一個或多個域名單獨配置 ATS。
被單獨配置的域名,預設受到完全的 ATS 限制,不管NSAllowsArbitraryLoads的值 如何;需要通過子鍵,進一步配置
所有的子鍵都屬于NSExceptionDomain。向Info.plist中添加這一主鍵:
- 建立字典,針對一個或多個域名,以便進行 ATS 配置。
- 這意味着之前使用主鍵所做的設定,對于這個域名來說,已經無效。
例如,及時之前設定NSAllowsArbitraryLoadsInMedia為 YES,然而NSExceptionDomain所代表的域名依然不能通路不安全的媒體内容。
基于這樣的設定,可以針對域名進行 ATS 配置,增加或減少安全措施。例如:
- 将NSExceptionAllowsInsecureHTTPLoads設定為 YES,就 ;這樣做會引發 App Store 的審查,詳情見App Store Review for ATS。
- 通過配置NSExceptionRequiresForwardSecrecy為 NO,取消正向保密。
- 通過配置NSExceptionMinimumTLSVersion,更改 TLS 最低版本。
NSExceptionDomains字典構成:
-
<域名字元串>
代表想要配置的特定域名。可以添加多個域名(即添加多個這樣的鍵),為它們統一配置 ATS 政策。這個鍵對應一個字典,包含以下子鍵:
- NSIncludesSubdomains
* 設定為 YES,目前域名的 ATS 政策适用于其所有子域名。預設為 NO。
- NSExceptionAllowsInsecureHTTPLoads
* 設定為 YES,可以同時通過 HTTP 和 HTTPS 通路目前域名。預設為 NO。 注意,配置這個鍵值,将引發 App Store 的審查,開發者必須說明原因。
- NSExceptionMinimumTLSVersion
* 指定 TLS 的最低版本,是以可以使用版本較低,有安全漏洞的 TLS 協定。 注意,配置這個鍵值,将引發 App Store 的審查,開發者必須說明原因。
- NSExceptionRequiresForwardSecrecy
* 設定為 NO,允許針對目前域名使用不支援正向保密的 TLS 加密算法。預設為 YES。
- NSRequiresCertificateTransparency
* 設定為 YES,将驗證域名伺服器證書的Certificate Transparency時間戳 。預設為 NO。
- NSIncludesSubdomains
Requirements for Connecting Using ATS / 使用 ATS 的前提條件
在 ATS 完全開啟的情況下,系統要求 app 的 HTTPS 連接配接必須滿足以下要求:
X.509 數字證書必須滿足下列标準中的一項:
- 由作業系統内嵌的根證書頒發機構簽發
- 由通過作業系統管理者或使用者主動安裝的根證書頒發機構簽發
- TLS 版本必須為1.2,任何不使用或使用較低版本 TLS / SSL 的連接配接,都将失敗。
- 由通過作業系統管理者或使用者主動安裝的根證書頒發機構簽發
- 連接配接必須使用 AES-128 或 AES-256 對稱加密算法。 TLS 算法套裝必須以 ECDSA 密鑰交換的形式支援正向保密,加密算法必須為下面之一:
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
- TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
- 服務端的葉證書簽名密鑰必須為下面之一:
- 至少2048位的 RSA 密鑰
- 至少256位的 ECC 密鑰
-
此外,伺服器證書的雜湊演算法必須為 SHA-2,其摘要長度至少位256位(即 SHA-256 及以上)。
上面的标準,未來可能會發生變化。但不會影響到 app 二進制包的相容性。
App Store Review for ATS / App Store 對于 ATS 相關項的稽核
某些對 ATS 的配置會引發 App Store 的稽核,開發者必須說明原因。這些鍵有:
- NSAllowsArbitraryLoads
- NSExceptionAllowsInsecureHTTPLoads
- NSExceptionMinimumTLSVersion
以下是一些原因說明例子,供參考:
- 必須連接配接由其他機構控制的伺服器,其還不支援安全連接配接。
- 必須支援那些還未更新至可使用安全連接配接,不得不通過公共域名通路網絡的裝置。
- 必須通過 web 展示來源不一的各種網絡内容,但又不能完全使用NSAllowsArbitraryLoadsInWebContent所管理的類。
向 App Store 送出稽核時,開發者應主動提供足夠的資訊,以便解釋 app 無法使用安全連接配接的原因。
四. 實作支援安全ATS政策
<font color=red size=5>ATS相關設定對iOS8及以下系統無效</font>
需要解決的問題(iOS 9、iOS10及以上)
1、app内伺服器網絡請求通路支援https(即一般的請求)
2、webview内支援任意http通路
3、第三方sdk接入與支援http通路
主要是圍繞info.pilst配置檔案作相關的安全ATS政策
Info.plist檔案是向作業系統描述應用程式的XML屬性清單,是iPhone應用程式檔案夾包含所有重要的Info.plist檔案
你可能注意到一些關鍵字像是使用了一些其他關鍵字中的詞但是在前面加上了"ThirdParty"字樣,在功能上,這些關鍵字與不含有"ThirdParty"的關鍵字有同樣的效果。而且實際運作中所調用的代碼将會完全忽略是否使用"ThirdParty"關鍵字。
簡單粗暴的方案:
--------------------------------------------
NSExceptionDomains 的設定方法如下, 比如我們要将 weibo.com 這個域名排除在 ATS 驗證之外,就可以這樣:
key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>weibo.com</key>
<dict>
<key> NSIncludesSubdomains </key>
<true/>
<key> NSExceptionRequiresForwardSecrecy </key>
<false/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
注意:每個需添加的域都需要設定此三個屬性。如果請求的網絡圖檔是HTTP,也是需要設定的圖檔的域。
注意⚠️這個方案風險較大,有可能被拒絕。“需要通路的域名是第三方伺服器,他們沒有進行 HTTPS 對應”會是稽核時的一個可選理由,但是這應該隻需要針對特定域名,而非全面開放。如果通路的是自己的伺服器的話,可能這個理由會無法通過。
------------------------------------------------
實作方案
1、app内伺服器網絡請求通路支援https
解決方案:
搭建https伺服器
搭建https伺服器需要ssl證書
- ssl自制證書:稱自簽名ssl證書,容易被假冒僞造,浏覽器不信任。(稽核不通過)
- 免費CA憑證:部分CA機構提供免費的SSL證書,如wosign,starts等(App Store pass掉不通過)
- 付費CA憑證:多指企業級及以上的數字證書。
HTTPS伺服器滿足ATS預設的條件,而且SSL證書是通過權威的CA機構認證過的,那麼我們在使用Xcode開發的時候,對網絡的适配什麼都不用做,我們也能正常與伺服器通信。但是,當我們對安全性有更高的要求時或者我們自建證書時,我們需要本地導入證書來進行驗證。
使用AFNetworking來支援https
AFNetworking是iOS/OSX開發最流行的第三方開源庫之一,現在iOS oc 代碼90%以上都是用這個架構網絡請求。AFNetworking已經将上面的邏輯代碼封裝好,甚至更完善,在AFSecurityPolicy檔案中,有興趣可以閱讀這個子產品的代碼;以下就是在AFNetworking 2.6.0以前版本和3.0.0版本基于支援https的驗證方式
步驟:
- 建立一個manager
- 在mainBundle中尋找我們剛才拖進項目中的https.cer, 并且将相關的資料讀取出來
- 建立一個AFSecurityPolicy,并進行相應的配置
- 将這個AFSecurityPolicy 執行個體指派給manager
代碼實作:
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 3.0.0以上版本用的是AFHTTPSessionManager
AFHTTPSessionManager * manager = [AFHTTPSessionManager manager];
NSString * cerPath = [[NSBundle mainBundle] pathForResource:@"server" ofType:@"cer"];
NSData * cerData = [NSData dataWithContentsOfFile:cerPath];
NSLog(@"%@", cerData);
manager.securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:[[NSArray alloc] initWithObjects:cerData, nil]];
manager.securityPolicy.allowInvalidCertificates = YES;
[manager.securityPolicy setValidatesDomainName:NO];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
NSDictionary * parameter = @{@"username":self.username, @"password":self.password};
[manager POST:@"https://192.168.1.4:9777" parameters:parameter success:^(NSURLSessionDataTask * task, id responseObject) {
NSLog(@"success %@", responseObject);
}
failure:^(NSURLSessionDataTask * task, NSError * error) {
NSLog(@"failure %@", error);
}]
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key> //設定為 YES,解除整個app的ATS限制;但是通過NSExceptionDomains進行的配置依然有效
<false/>
<key>NSAllowsArbitraryLoadsInMedia</key> //設定為 YES,解除通過AVFoundation架構通路媒體内容時的 ATS 限制
<true/>
<key>NSAllowsArbitraryLoadsInWebContent</key> //設定為 YES,解除通過webview發出的網絡請求的ATS限制
<true/>
<key>NSAllowsLocalNetworking</key> //設定為 YES,使得app可以載入任意本地資源,但不影響app的總體 ATS 政策
<true/>
2、webview内支援任意http通路
對于網頁浏覽和視訊播放的行為,iOS 10 中新加入了 NSAllowsArbitraryLoadsInWebContent 鍵。通過将它設定為 YES,可以讓 app 中的 WKWebView 和使用 AVFoundation 播放的線上視訊不受 ATS 的限制。這也應該是絕大多數使用了相關特性的 app 的選擇。但是壞消息是這個鍵在 iOS 9 中并不會起作用。
如果app隻支援 iOS 10,并且有使用者可以自由輸入網址進行浏覽的功能,或者是線上視訊音頻播放功能的話,簡單地加入 NSAllowsArbitraryLoadsInWebContent,并且将元件換成 WKWebKit 或者 AVFoundation 就可以了。如果你還需要支援 iOS 9,并且需要通路網頁和視訊的話,可能隻能去開啟 NSAllowsArbitraryLoads 然後送出時進行說明,并且看 Apple 稽核員決定讓不讓通過了。
另外,當 NSAllowsArbitraryLoads 和 NSAllowsArbitraryLoadsInWebContent 同時存在時,根據系統不同,表現的行為也會不一樣。簡單說,iOS 9 隻看 NSAllowsArbitraryLoads,而 iOS 10 會先看 NSAllowsArbitraryLoadsInWebContent。在 iOS 10 中,要是 NSAllowsArbitraryLoadsInWebContent 存在的話,就忽略掉 NSAllowsArbitraryLoads,如果它不存在,則遵循 NSAllowsArbitraryLoads 的設定
UIWebView 在 NSAllowsArbitraryLoadsInWebContent 為 YES 時通路 HTTP,無效。WKWebView 在 NSAllowsArbitraryLoadsInWebContent 為 YES 時在iOS 10 中通路 HTTP,有效,iOS 9無效。如果用WkWebView替換UIWebView,iOS 7 将無法使用WkWebView,可做适配加載,沒有特殊的什麼需求的話,盡早将 UIWebView 全部換為 WkWebView 會比較好。是以為了能讓WebView在所有版本都能通路非https内容,隻能把NSAllowsArbitraryLoads設定為YES。
解決方案一:
開啟 NSAllowsArbitraryLoads 為 YES,然後送出時進行說明
解決方案二:
設定 NSExceptionDomains 屬性來通路指定域名,然後送出時進行說明
3、第三方sdk接入與支援http通路
但是按照國内的現狀,關閉這個限制也許是更實際的做法。至于原因就太多了,第三方SDK(幾乎都是通路http),合作夥伴接入(不能要求它們一定要支援https)
第三方sdk,同樣需要遵守ATS規則,即第三方sdk也有被ATS過濾的風險,微信,qq,分享,登陸功能都能正常,微網誌登陸不能正常通過。另在網上找到了一些可能存在有問題的sdk,目前已知的有:
- 友盟 (已經有最新的v1.4.0版本sdk,支援https,待驗證)
- 百度地圖
解決方案一:
更新最新sdk,接入并測試
解決方案二:
可以設定 NSExceptionDomains屬性來将需要排除強制驗證的域名寫進來:
五. 總結
開啟 NSAllowsArbitraryLoads 為 YES
對第三方通路的伺服器設定NSExceptionDomains方式添加白名單
送出稽核說明:
- 必須連接配接由其他機構控制的伺服器,其還不支援安全連接配接。
- 必須通過 web 展示來源不一的各種網絡内容,但又不能完全使用NSAllowsArbitraryLoadsInWebContent所管理的類。