iOS自簽名HTTPS證書(shū)單向校驗(yàn)方案

前言

這兩天公司選所謂的先進(jìn)個(gè)人,結(jié)果也是我最滿意的,沒(méi)選上但能得到自己身邊的伙伴對(duì)自己的認(rèn)可,很開(kāi)心,感冒也好了很多!雖然知道大家選我,只是對(duì)我工作層面的認(rèn)可,與人品無(wú)關(guān),但我寧愿伙伴能對(duì)我的人品層面也有同等認(rèn)可。

畢業(yè)已近兩年,出來(lái)工作時(shí)間總算起來(lái),也有快三年了。從開(kāi)始跟著師傅,到慢慢獨(dú)立,再到帶新伙伴一起做項(xiàng)目,這一路走來(lái),有過(guò)無(wú)助、有過(guò)懷疑,有過(guò)為伙伴“甘愿獨(dú)自離去”的沖動(dòng),有過(guò)低級(jí)趣味的誘惑,有過(guò)感動(dòng),有過(guò)堅(jiān)定,說(shuō)我傻B也好,說(shuō)我怎樣也罷,我都全盤接受。

說(shuō)實(shí)話,有時(shí)也會(huì)矛盾。我是一個(gè)“虛心聽(tīng)取別人意見(jiàn)”的人,別人的建議我反思后,除了性格使然部分,其他大部分我都會(huì)調(diào)整,當(dāng)然你也可以理解為沒(méi)有主見(jiàn),設(shè)計(jì)、測(cè)試怎么說(shuō)就怎么改,領(lǐng)導(dǎo)安排工作也盡量去做。慢慢的對(duì)自己的工作方式也有過(guò)疑問(wèn),這樣做是否合適?也有貴人給我提過(guò)一些東西,說(shuō)讓我學(xué)會(huì)表達(dá)自己,為此,我也做出些自己的改變,在技術(shù)方面,我開(kāi)始表達(dá)自己的想法,不過(guò)有時(shí)會(huì)不注意表達(dá)的方法和方式,也會(huì)給領(lǐng)導(dǎo)給自己造成些許困惑。

不管怎樣,猴年即將過(guò)去,這恐怕是年前最后一次項(xiàng)目上線了,給大家分享下前幾天HTTPS證書(shū)校驗(yàn)的一些東西。
說(shuō)好的寫(xiě)篇《iOS自簽名HTTPS證書(shū)單向校驗(yàn)方案》呢,又扯了這么多閑篇,對(duì)大家不住。

HTTPS簡(jiǎn)析

雖說(shuō)在去年圣誕節(jié)前夕蘋(píng)果發(fā)出公告要推遲ATS適配截止時(shí)間,但既然它在iOS9.0始推出App Transport Security,適配HTTPS是早晚的事,總要做好技術(shù)儲(chǔ)備。以鄙人淺顯的技術(shù)而言,我覺(jué)得iOS APP適配HTTPS主要涉及三方面適配:

  • 普通網(wǎng)絡(luò)請(qǐng)求;
  • H5頁(yè)面加載;
  • SDWebImage加載HTTPS圖片(一般公司測(cè)試庫(kù)會(huì)用到自簽名證書(shū))。

所謂HTTPS,即HTTP+SSL/TSL,底部也就是在HTTP協(xié)議層和TCP/IP協(xié)議層之間添加安全傳輸層協(xié)議SSL,從而達(dá)到對(duì)HTTP數(shù)據(jù)包加密傳輸?shù)哪康模?/p>

sequenceDiagram
    participant Client
    participant Server
    Client->>Server: 以明文傳輸數(shù)據(jù),主要有客戶端支持的SSL版本等客戶端支持的加密信息
    Server-->>Server: 服務(wù)端選擇加密方式
    
    Server-->>Client: 服務(wù)端給客戶端返回SSL版本、隨機(jī)數(shù)等信息
    Client->>Client: 校驗(yàn)服務(wù)端證書(shū)是否合法、產(chǎn)生隨機(jī)數(shù)
    
    Client->>Server: 使用服務(wù)端隨機(jī)數(shù)加密數(shù)據(jù)發(fā)給服務(wù)端
    Server-->>Server: 服務(wù)端使用私鑰解密客戶端數(shù)據(jù)
    
    Server-->>Client: 使用收到的隨機(jī)數(shù),加密數(shù)據(jù),返回給客戶端
    Client->>Client: 客戶端使用公鑰解密數(shù)據(jù)

簡(jiǎn)書(shū)不支持MarkDown高級(jí)語(yǔ)法,上面的MarkDown文本對(duì)應(yīng)下圖:

HTTPS sequenceDiagram.png

iOS適配HTTPS注意點(diǎn)

  • 網(wǎng)絡(luò)請(qǐng)求NSURLConnection/NSURLSession
  • H5頁(yè)面適配WKWebView/UIWebView
  • SDWebImage圖片加載適配

網(wǎng)絡(luò)請(qǐng)求部分

NSURLConnection適配

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) 
    {
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
    } 
    else 
    {
        if ([challenge previousFailureCount] == 0) 
        {
            [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
        }
        else 
        {
            [[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
        }
    }
}

NSURLSession適配

/** disposition:如何處理證書(shū)
    NSURLSessionAuthChallengeUseCredential 使用證書(shū)
    NSURLSessionAuthChallengePerformDefaultHandling  忽略證書(shū) 默認(rèn)的做法
    NSURLSessionAuthChallengeCancelAuthenticationChallenge 取消請(qǐng)求,忽略證書(shū)
    NSURLSessionAuthChallengeRejectProtectionSpace 拒絕,忽略證書(shū)
*/
#pragma mark - NSURLSessionDelegate代理方法 HTTPS ---開(kāi)始---
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
    __block NSURLCredential *credential = nil;
    // 判斷服務(wù)器返回的證書(shū)是否是服務(wù)器信任的
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        if (credential)
        {
            disposition = NSURLSessionAuthChallengeUseCredential; // 使用證書(shū)
        }
        else
        {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling; // 忽略證書(shū) 默認(rèn)的做法
        }
    }
    else
    {
        disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; // 取消請(qǐng)求,忽略證書(shū)
    }
    if (completionHandler)// 安裝證書(shū)
    {
        completionHandler(disposition, credential);
    }
}

H5頁(yè)面適配

基于WKWebView的H5頁(yè)面HTTPS適配
#pragma mark: WKWebView的https配置
- (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler
{
    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        NSURLCredential *card = [[NSURLCredential alloc]initWithTrust:challenge.protectionSpace.serverTrust];

        completionHandler(NSURLSessionAuthChallengeUseCredential,card);
    }
}
基于UIWebView的H5頁(yè)面HTTPS適配

詳情見(jiàn):Stretch's stackoverflow答案

#pragma mark - Webview delegate
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
{
    NSLog(@"Did start loading: %@ auth:%d", [[request URL] absoluteString], _authenticated);

    if (!_authenticated) 
    {
        _authenticated = NO;
        _urlConnection = [[NSURLConnection alloc] initWithRequest:_request delegate:self];
        [_urlConnection start];
        return NO;
    }
    return YES;
}

#pragma mark - NURLConnection delegate
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{
    NSLog(@"WebController Got auth challange via NSURLConnection");
    if ([challenge previousFailureCount] == 0)
    {
        _authenticated = YES;
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
    } else
    {
        [[challenge sender] cancelAuthenticationChallenge:challenge];
    }
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
{
    NSLog(@"WebController received response via NSURLConnection");

    // remake a webview call now that authentication has passed ok.
    _authenticated = YES;
    [_web loadRequest:_request];

    // Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!)
    [_urlConnection cancel];
}

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}

SDWebImage加載HTTPS圖片適配

低版本的SDWebImage是對(duì)NSURLConnection做的封裝,新版本是對(duì)NSURLSession做的封裝,適配HTTPS圖片,記得確定自己使用的SDWebImage版本的SDWebImageDownloaderOperation類是否做過(guò)校驗(yàn)。
如果SDWebImage使用的是NSURLConnection看看文件是否有:

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

否則查看SDWebImageDownloaderOperation文件中是否有

- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler

確定好之后,如果所有圖片均是HTTPS的,那么我們可以直接修改UIImageView+WebCache文件

- (void)sd_setImageWithURL:(nullable NSURL *)url 
{
//    [self sd_setImageWithURL:url placeholderImage:nil options:0 progress:nil completed:nil];
    [self sd_setImageWithURL:url placeholderImage:nil options:SDWebImageAllowInvalidSSLCertificates progress:nil completed:nil]; // HTTPS配置問(wèn)題
}
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder 
{
//    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
    [self sd_setImageWithURL:url placeholderImage:placeholder options:SDWebImageAllowInvalidSSLCertificates progress:nil completed:nil]; // HTTPS配置問(wèn)題
}

其實(shí)質(zhì)就是將option設(shè)置為SDWebImageAllowInvalidSSLCertificates,看SDWebImageDownloaderOperation文件你會(huì)發(fā)現(xiàn),SDWebImage采用直接忽略證書(shū)驗(yàn)證的方式加載的,所以設(shè)置SDWebImageAllowInvalidSSLCertificates才有效。

SDWebImageDownloaderloaderOperation校驗(yàn)忽略.png

如有個(gè)別特殊情況,要支持某些HTTP的圖片,按照上面方法適配后,還需在plist的Exception Domains添加響應(yīng)配置,詳情見(jiàn)蘋(píng)果APP接入HTTPS延遲截止時(shí)間是妥協(xié)嗎

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容