首先來分析一下什么是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ā)者提供了更多時間來做適配和支持。然而嘹屯,對于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)目