iOS開發(fā)HTTPS實(shí)現(xiàn)之信任SSL證書和自簽名證書

Apple ATS

首先來分析一下什么是HTTPS以及了解HTTPS對于iOS開發(fā)者的意義

HTTPS 以及SSL/TSL

  • 什么是SSL?

SSL(Secure Sockets Layer, 安全套接字層),因?yàn)樵然ヂ?lián)網(wǎng)上使用的 HTTP 協(xié)議是明文的躲胳,存在很多缺點(diǎn),比如傳輸內(nèi)容會被偷窺(嗅探)和篡改。 SSL 協(xié)議的作用就是在傳輸層對網(wǎng)絡(luò)連接進(jìn)行加密贪磺。

  • 何為TLS?

到了1999年诅愚,SSL 因?yàn)閼?yīng)用廣泛寒锚,已經(jīng)成為互聯(lián)網(wǎng)上的事實(shí)標(biāo)準(zhǔn)。IETF 就在那年把 SSL 標(biāo)準(zhǔn)化。標(biāo)準(zhǔn)化之后的名稱改為 TLS(Transport Layer Security刹前,傳輸層安全協(xié)議)泳赋。SSL與TLS可以視作同一個東西的不同階段

  • HTTPS

簡單來說,HTTPS = HTTP + SSL/TLS, 也就是 HTTP over SSL 或 HTTP over TLS喇喉,這是后面加 S 的由來 祖今。

HTTPS和HTTP異同:HTTP和HTTPS使用的是完全不同的連接方式,用的端口也不一樣拣技,前者是80渐逃,后者是443耘子。HTTP的連接很簡單走贪,是無狀態(tài)的吸耿;HTTPS協(xié)議是由SSL+HTTP協(xié)議構(gòu)建的可進(jìn)行加密傳輸、身份認(rèn)證的網(wǎng)絡(luò)協(xié)議掸绞,比HTTP協(xié)議安全泵三。

在WWDC 2016開發(fā)者大會上,蘋果宣布了一個最后期限:到2017年1月1日 App Store中的所有應(yīng)用都必須啟用 App Transport Security安全功能衔掸。App Transport Security(ATS)是蘋果在iOS 9中引入的一項(xiàng)隱私保護(hù)功能烫幕,屏蔽明文HTTP資源加載,連接必須經(jīng)過更安全的HTTPS敞映。蘋果目前允許開發(fā)者暫時可以繼續(xù)使用HTTP連接较曼,但到年底所有官方商店的應(yīng)用都必須強(qiáng)制性使用ATS。

以下是開發(fā)者網(wǎng)站公告原文:

應(yīng)用傳輸安全協(xié)議是與iOS9和OS X 10.11一同發(fā)布的振愿,該協(xié)議需要應(yīng)用程序通過HTTPS使用安全的網(wǎng)絡(luò)連接捷犹,以提高用戶的數(shù)據(jù)和隱私安全。
在2016年WWDC上我們宣布在今年年底之前冕末,提交到App Store的應(yīng)用程序必須支持應(yīng)用傳輸安全協(xié)議萍歉。為了給你們額外的時間去準(zhǔn)備,這個截止日期已被延長档桃,當(dāng)新的截止日期確定的時候枪孩,我們將及時提供相關(guān)信息。

2016年12月21日蘋果更新了截止日期藻肄,宣布延期執(zhí)行ATS支持要求Supporting App Transport Security蔑舞。

開發(fā)者網(wǎng)站截圖

** 此舉為開發(fā)者提供了更多時間來做適配和支持。然而嘹屯,對于iOS開發(fā)者來說攻询,盡早解決HTTPS請求的問題仍為上策。**

蘋果ATS對HTTPS證書的要求

啟用ATS必須符合以下標(biāo)準(zhǔn)抚垄,不滿足條件的HTTPS證書蜕窿,ATS都會拒絕連接:

  • 服務(wù)器所有的連接使用TLS1.2以上版本
  • HTTPS證書必須使用SHA256以上哈希算法簽名
  • HTTPS證書必須使用RSA 2048位或ECC 256位以上公鑰算法
  • 使用前向加密技術(shù)

此外谋逻,蘋果ATS支持CT證書透明呆馁,要求開發(fā)者使用支持CT證書透明度的SSL證書桐经,確保SSL證書合法透明,防止中間人攻擊浙滤。

發(fā)送HTTPS請求信任SSL證書和自簽名證書阴挣,分為三種情況

1.如果你的app服務(wù)端安裝的是SLL頒發(fā)的CA,可以使用系統(tǒng)方法直接實(shí)現(xiàn)信任SSL證書纺腊,關(guān)于Apple對SSL證書的要求請參考:蘋果官方文檔CertKeyTrustProgGuide

這種方式不需要在Bundle中引入CA文件畔咧,可以交給系統(tǒng)去判斷服務(wù)器端的證書是不是SSL證書,驗(yàn)證過程也不需要我們?nèi)ゾ唧w實(shí)現(xiàn)揖膜。
示例代碼:


    NSURL *URL = [NSURL URLWithString:URLString];
    NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
    //創(chuàng)建同步連接
    NSError *error = nil;
    NSData *receivedData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error];
    NSString *receivedInfo = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];

當(dāng)然誓沸,如果你需要同時信任SSL證書和自簽名證書的話還是需要在代碼中實(shí)現(xiàn)CA的驗(yàn)證,這種情況在后面會提到壹粟。

2.基于AFNetWorking的SSL特定服務(wù)器證書信任處理拜隧,重寫AFNetWorking的customSecurityPolicy方法,這里我創(chuàng)建了一個HttpRequest類,分別對GET和POST方法進(jìn)行了封裝趁仙,以GET方法為例:



+ (void)get:(NSString *)url params:(NSDictionary *)params success:(void (^)(id))success failure:(void (^)(NSError *))failure {
    // 1.獲得請求管理者
    AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
    // 2.申明返回的結(jié)果是text/html類型
    mgr.responseSerializer = [AFHTTPResponseSerializer serializer];
    // 3.設(shè)置超時時間為10s
    mgr.requestSerializer.timeoutInterval = 10;
    
    // 加上這行代碼洪添,https ssl 驗(yàn)證。
    if(openHttpsSSL) {
        [mgr setSecurityPolicy:[self customSecurityPolicy]];
    }
    
    // 4.發(fā)送GET請求
    [mgr GET:url parameters:params success:^(AFHTTPRequestOperation *operation, id responseObj){
        if (success) {
            success(responseObj);
        }
    } failure:^(AFHTTPRequestOperation *operation, NSError *error) {
        if (error) {
            failure(error);
        }
    }];
}
+ (AFSecurityPolicy*)customSecurityPolicy {
    // /先導(dǎo)入證書
    NSString *cerPath = [[NSBundle mainBundle] pathForResource:certificate ofType:@"cer"];//證書的路徑
    NSData *certData = [NSData dataWithContentsOfFile:cerPath];
    
    // AFSSLPinningModeCertificate 使用證書驗(yàn)證模式
    AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
    
    // allowInvalidCertificates 是否允許無效證書(也就是自建的證書)雀费,默認(rèn)為NO
    // 如果是需要驗(yàn)證自建證書干奢,需要設(shè)置為YES
    securityPolicy.allowInvalidCertificates = YES;
    
    //validatesDomainName 是否需要驗(yàn)證域名,默認(rèn)為YES盏袄;
    //假如證書的域名與你請求的域名不一致忿峻,需把該項(xiàng)設(shè)置為NO;如設(shè)成NO的話辕羽,即服務(wù)器使用其他可信任機(jī)構(gòu)頒發(fā)的證書炭菌,也可以建立連接,這個非常危險逛漫,建議打開黑低。
    //置為NO,主要用于這種情況:客戶端請求的是子域名酌毡,而證書上的是另外一個域名克握。因?yàn)镾SL證書上的域名是獨(dú)立的,假如證書上注冊的域名是www.google.com枷踏,那么mail.google.com是無法驗(yàn)證通過的菩暗;當(dāng)然,有錢可以注冊通配符的域名*.google.com旭蠕,但這個還是比較貴的停团。
    //如置為NO旷坦,建議自己添加對應(yīng)域名的校驗(yàn)邏輯。
    securityPolicy.validatesDomainName = NO;
    
    securityPolicy.pinnedCertificates = @[certData];
    
    return securityPolicy;
}


其中的cerPath就是app bundle中證書路徑佑稠,certificate為證書名稱的宏秒梅,僅支持cer格式,securityPolicy的相關(guān)配置尤為重要舌胶,請仔細(xì)閱讀customSecurityPolicy方法并根據(jù)實(shí)際情況設(shè)置其屬性捆蜀。

這樣,就能夠在AFNetWorking的基礎(chǔ)上使用HTTPS協(xié)議訪問特定服務(wù)器幔嫂,但是不能信任根證書的CA文件辆它,因此這種方式存在風(fēng)險,讀取pinnedCertificates中的證書數(shù)組的時候有可能失敗履恩,如果證書不符合锰茉,certData就會為nil。

3.更改系統(tǒng)方法切心,發(fā)送異步NSURLConnection請求飒筑。



- (void)getDataWithURLRequest {
    //connection
    NSString *urlStr = @"https://developer.apple.com/cn/";
    NSURL *url = [NSURL URLWithString:urlStr];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
    NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
    [connection start];
}

重點(diǎn)在于處理NSURLConnection的didReceiveAuthenticationChallenge代理方法,對CA文件進(jìn)行驗(yàn)證昙衅,并建立信任連接扬霜。


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

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    
 /*
    //直接驗(yàn)證服務(wù)器是否被認(rèn)證(serverTrust),這種方式直接忽略證書驗(yàn)證而涉,直接建立連接著瓶,但不能過濾其它URL連接,可以理解為一種折衷的處理方式啼县,實(shí)際上并不安全材原,因此不推薦。
    SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
    return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
                  forAuthenticationChallenge: challenge];
     */
    if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {
        do
        {
            SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
            NSCAssert(serverTrust != nil, @"serverTrust is nil");
            if(nil == serverTrust)
                break; /* failed */
            /**
             *  導(dǎo)入多張CA證書(Certification Authority季眷,支持SSL證書以及自簽名的CA)
             */
            NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"cloudwin" ofType:@"cer"];//自簽名證書
            NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
            
            NSString *cerPath2 = [[NSBundle mainBundle] pathForResource:@"apple" ofType:@"cer"];//SSL證書
            NSData * caCert2 = [NSData dataWithContentsOfFile:cerPath2];
            
            NSCAssert(caCert != nil, @"caCert is nil");
            if(nil == caCert)
                break; /* failed */
            
            NSCAssert(caCert2 != nil, @"caCert2 is nil");
            if (nil == caCert2) {
                break;
            }
            
            SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
            NSCAssert(caRef != nil, @"caRef is nil");
            if(nil == caRef)
                break; /* failed */
            
            SecCertificateRef caRef2 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert2);
            NSCAssert(caRef2 != nil, @"caRef2 is nil");
            if(nil == caRef2)
                break; /* failed */
            
            NSArray *caArray = @[(__bridge id)(caRef),(__bridge id)(caRef2)];
        
            NSCAssert(caArray != nil, @"caArray is nil");
            if(nil == caArray)
                break; /* failed */
            
            OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
            NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
            if(!(errSecSuccess == status))
                break; /* failed */
            
            SecTrustResultType result = -1;
            status = SecTrustEvaluate(serverTrust, &result);
            if(!(errSecSuccess == status))
                break; /* failed */
            NSLog(@"stutas:%d",(int)status);
            NSLog(@"Result: %d", result);
            
            BOOL allowConnect = (result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed);
            if (allowConnect) {
                NSLog(@"success");
            }else {
                NSLog(@"error");
            }
            /* https://developer.apple.com/library/ios/technotes/tn2232/_index.html */
            /* https://developer.apple.com/library/mac/qa/qa1360/_index.html */
            /* kSecTrustResultUnspecified and kSecTrustResultProceed are success */
            if(! allowConnect)
            {
            break; /* failed */
            }
            
#if 0
            /* Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success */
            /*   since the user will likely tap-through to see the dancing bunnies */
            if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError)
                break; /* failed to trust cert (good in this case) */
#endif
            
            // The only good exit point
            return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
                          forAuthenticationChallenge: challenge];
            
        } while(0);
    }
    
    // Bad dog
    return [[challenge sender] cancelAuthenticationChallenge: challenge];
    
}

這里的關(guān)鍵在于result參數(shù)的值余蟹,根據(jù)官方文檔的說明,判斷(result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed)的值子刮,若為1威酒,則該網(wǎng)站的CA被app信任成功,可以建立數(shù)據(jù)連接挺峡,這意味著所有由該CA簽發(fā)的各個服務(wù)器證書都被信任葵孤,而訪問其它沒有被信任的任何網(wǎng)站都會連接失敗。該CA文件既可以是SLL也可以是自簽名橱赠。

NSURLConnection的其它代理方法實(shí)現(xiàn)


#pragma mark -- connect的異步代理方法
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
    NSLog(@"請求被響應(yīng)");
    _mData = [[NSMutableData alloc]init];
}

-(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data {
    NSLog(@"開始返回數(shù)據(jù)片段");
    
    [_mData appendData:data];
}

-(void)connectionDidFinishLoading:(NSURLConnection *)connection {
    NSLog(@"鏈接完成");
    //可以在此解析數(shù)據(jù)
    NSString *receiveInfo = [NSJSONSerialization JSONObjectWithData:self.mData options:NSJSONReadingAllowFragments error:nil];
    NSLog(@"received data:\\\\n%@",self.mData);
    NSLog(@"received info:\\\\n%@",receiveInfo);
}

//鏈接出錯
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    NSLog(@"error - %@",error);
}

至此尤仍,HTTPS信任證書的問題得以解決,這不僅是為了響應(yīng)Apple強(qiáng)制性使用ATS的要求狭姨,也是為了實(shí)際生產(chǎn)環(huán)境安全性的考慮宰啦,HTTPS是未來的趨勢苏遥,建議盡早支持。

如需參考Demo請移步本人在Github上的開源項(xiàng)目

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赡模,一起剝皮案震驚了整個濱河市田炭,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌纺裁,老刑警劉巖诫肠,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件司澎,死亡現(xiàn)場離奇詭異欺缘,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)挤安,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門谚殊,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蛤铜,你說我怎么就攤上這事嫩絮。” “怎么了围肥?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵剿干,是天一觀的道長。 經(jīng)常有香客問我穆刻,道長置尔,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任氢伟,我火速辦了婚禮榜轿,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘朵锣。我一直安慰自己谬盐,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布诚些。 她就那樣靜靜地躺著飞傀,像睡著了一般。 火紅的嫁衣襯著肌膚如雪诬烹。 梳的紋絲不亂的頭發(fā)上砸烦,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機(jī)與錄音椅您,去河邊找鬼外冀。 笑死,一個胖子當(dāng)著我的面吹牛掀泳,可吹牛的內(nèi)容都是我干的雪隧。 我是一名探鬼主播西轩,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼脑沿!你這毒婦竟也來了藕畔?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤庄拇,失蹤者是張志新(化名)和其女友劉穎注服,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體措近,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溶弟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了瞭郑。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辜御。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖屈张,靈堂內(nèi)的尸體忽然破棺而出擒权,到底是詐尸還是另有隱情,我是刑警寧澤阁谆,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布碳抄,位于F島的核電站,受9級特大地震影響场绿,放射性物質(zhì)發(fā)生泄漏剖效。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一裳凸、第九天 我趴在偏房一處隱蔽的房頂上張望贱鄙。 院中可真熱鬧,春花似錦姨谷、人聲如沸逗宁。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瞎颗。三九已至,卻和暖如春捌议,著一層夾襖步出監(jiān)牢的瞬間哼拔,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工瓣颅, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留倦逐,地道東北人。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓宫补,卻偏偏與公主長得像檬姥,于是被迫代替她去往敵國和親曾我。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容