日本熟妇hd丰满老熟妇,中文字幕一区二区三区在线不卡 ,亚洲成片在线观看,免费女同在线一区二区

iOS端HTTPS場景使用HTTPDNS

本文主要介紹HTTPS(含SNI)業務場景下在iOS端使用HTTPDNS時實現”IP直連“的解決方案。

重要

當前最佳實踐文檔介紹在實際網絡請求場景中,如何使用HTTPDNS解析出的IP。關于HTTPDNS本身的解析服務,請先查看iOS SDK 開發手冊。

背景說明

1. HTTPS場景的特點

發送HTTPS請求首先要進行SSL/TLS握手,握手過程大致如下:

  1. 客戶端發起握手請求,攜帶隨機數、支持算法列表等參數。

  2. 服務端收到請求,選擇合適的算法,下發公鑰證書和隨機數。

  3. 客戶端對服務端證書進行校驗,并發送隨機數信息,該信息使用公鑰加密。

  4. 服務端通過私鑰獲取隨機數信息。

  5. 雙方根據以上交互的信息生成session ticket,用作該連接后續數據傳輸的加密密鑰。

上述過程中,和HTTPDNS有關的是第三步,客戶端需要驗證服務端下發的證書,驗證過程有以下兩個要點:

  1. 客戶端用本地保存的根證書解開證書鏈,確認服務端下發的證書是由可信任的機構頒發的。

  2. 客戶端需要檢查證書的domain域和擴展域,看是否包含本次請求的host。

如果上述兩點都校驗通過,就證明當前的服務端是可信任的,否則就是不可信任,應當中斷當前連接。

當客戶端使用HTTPDNS解析域名時,請求URL中的host會被替換成HTTPDNS解析出來的IP,所以在證書驗證的第2步,會出現domain不匹配的情況,導致SSL/TLS握手不成功。

2. 什么是SNI

SNI(Server Name Indication)是為了解決一個服務器使用多個域名和證書的SSL/TLS擴展。它的工作原理如下:

  1. 在連接到服務器建立SSL鏈接之前先發送要訪問站點的域名(Hostname)。

  2. 服務器根據這個域名返回一個合適的證書。

目前,大多數操作系統和瀏覽器都已經很好地支持SNI擴展,OpenSSL 0.9.8也已經內置這一功能。

上述過程中,當客戶端使用HTTPDNS解析域名時,請求URL中的host會被替換成HTTPDNS解析出來的IP,導致服務器獲取到的域名為解析后的IP,無法找到匹配的證書,只能返回默認的證書或者不返回,所以會出現SSL/TLS握手不成功的錯誤。

說明

比如當你需要通過HTTPS訪問CDN資源時,CDN的站點往往服務了很多的域名,所以需要通過SNI指定具體的域名證書進行通信。

非SNI場景解決方案

針對“domain不匹配”問題,可以采用如下方案解決:hook證書校驗過程第2步,將IP直接替換成原來的域名,再執行證書驗證。

說明

基于該方案發起網絡請求,若報出SSL校驗錯誤,比如iOS系統報錯kCFStreamErrorDomainSSL, -9813; The certificate for this server is invalid,請檢查應用場景是否為SNI(單IP多HTTPS域名)。

此示例針對NSURLSession接口。

/*
 * NSURLSession
 */
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
 completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * __nullable credential))completionHandler {
    if (!challenge) {
        return;
    }
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    NSURLCredential *credential = nil;
    /*
     * 獲取原始域名信息。
     */
    NSString* host = [[self.request allHTTPHeaderFields] objectForKey:@"host"];
    if (!host) {
        host = self.request.URL.host;
    }
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
        if ([self evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:host]) {
            disposition = NSURLSessionAuthChallengeUseCredential;
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        } else {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
    } else {
        disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    }
    // 對于其他的challenges直接使用默認的驗證方案
    completionHandler(disposition,credential);
}

SNI場景解決方案

SNI(單IP多HTTPS證書)場景下,iOS上層網絡庫NSURLSession沒有提供接口進行SNI字段的配置,因此需要Socket層級的底層網絡庫例如CFNetwork,來實現IP直連網絡請求適配方案。而基于CFNetwork的解決方案需要開發者考慮數據的收發、重定向、解碼、緩存等問題(CFNetwork是非常底層的網絡實現),希望開發者合理評估該場景的使用風險。

針對SNI場景,通過Socket層級的底層網絡庫實現網絡請求,有兩種主流方案:

  1. 自定義NSURLProtocol實現,基于CFNetwork完整實現HTTP請求邏輯,在其中hook證書校驗步驟。

  2. 使用基于原生支持設置SNI字段的更底層的庫,比如libcurl。

1. 自定義NSURLProtocol方案

整個實現比較復雜,我們在demo中提供了示例實現,可直接復用。參考 httpdns_ios_demo 中的 HttpDnsNSURLProtocolImpl.m 文件。

2. 其他底層網絡庫方案

libcurl為例,libcurl / cURL至少7.18.1(2008年3月30日)在SNI支持下編譯一個 SSL/TLS 工具包,curl中有一個--resolve方法可以實現使用指定IP訪問HTTPS網站。

在iOS實現中,代碼如下:

// {HTTPS域名}:443:{IP地址}
NSString *curlHost = ...;
_hosts_list = curl_slist_append(_hosts_list, curlHost.UTF8String);
curl_easy_setopt(_curl, CURLOPT_RESOLVE, _hosts_list);

其中curlHost形如:{HTTPS域名}:443:{IP地址}

_hosts_list 是結構體類型hosts_list,可以設置多個IP與Host之間的映射關系。curl_easy_setopt方法中傳入CURLOPT_RESOLVE 將該映射設置到 HTTPS 請求中。這樣就可以達到設置SNI的目的。