iOS開發HTTPS實現之信任SSL證書和自簽名證書

iOS開發HTTPS實現之信任SSL證書和自簽名證書

 

轉自:http://www.jianshu.com/p/6b9c8bd5005a/comments/5539345html

(收錄一下供本身學習用的)ios

 

字數1566 閱讀5025 評論76

首先來分析一下什麼是HTTPS以及瞭解HTTPS對於iOS開發者的意義

HTTPS 以及SSL/TSL

  • 什麼是SSL?

SSL(Secure Sockets Layer, 安全套接字層),由於原先互聯網上使用的 HTTP 協議是明文的,存在不少缺點,好比傳輸內容會被偷窺(嗅探)和篡改。 SSL 協議的做用就是在傳輸層對網絡鏈接進行加密。數組

  • 何爲TLS?

到了1999年,SSL 由於應用普遍,已經成爲互聯網上的事實標準。IETF 就在那年把 SSL 標準化。標準化以後的名稱改成 TLS(Transport Layer Security,傳輸層安全協議)。SSL與TLS能夠視做同一個東西的不一樣階段安全

  • HTTPS

簡單來講,HTTPS = HTTP + SSL/TLS, 也就是 HTTP over SSL 或 HTTP over TLS,這是後面加 S 的由來 。服務器

HTTPS和HTTP異同:HTTP和HTTPS使用的是徹底不一樣的鏈接方式,用的端口也不同,前者是80,後者是443。HTTP的鏈接很簡單,是無狀態的;HTTPS協議是由SSL+HTTP協議構建的可進行加密傳輸、身份認證的網絡協議,比HTTP協議安全。網絡

在WWDC 2016開發者大會上,蘋果宣佈了一個最後期限:到2017年1月1日 App Store中的全部應用都必須啓用 App Transport Security安全功能。App Transport Security(ATS)是蘋果在iOS 9中引入的一項隱私保護功能,屏蔽明文HTTP資源加載,鏈接必須通過更安全的HTTPS。蘋果目前容許開發者暫時關閉ATS,能夠繼續使用HTTP鏈接,但到年末全部官方商店的應用都必須強制性使用ATS。app

因此對於iOS開發者來講,須要儘早解決HTTPS請求的問題。異步

發送HTTPS請求信任SSL證書和自簽名證書,分爲三種狀況

1.若是你的app服務端安裝的是SLL頒發的CA,可使用系統方法直接實現信任SSL證書,關於Apple對SSL證書的要求請參考:蘋果官方文檔CertKeyTrustProgGuideide

這種方式不須要在Bundle中引入CA文件,能夠交給系統去判斷服務器端的證書是否是SSL證書,驗證過程也不須要咱們去具體實現。學習

示例代碼:

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

固然,若是你須要同時信任SSL證書和自簽名證書的話仍是須要在代碼中實現CA的驗證,這種狀況在後面會提到。

2.基於AFNetWorking的SSL特定服務器證書信任處理,重寫AFNetWorking的customSecurityPolicy方法,這裏我建立了一個HttpRequest類,分別對GET和POST方法進行了封裝,以GET方法爲例:

+ (void)get:(NSString *)url params:(NSDictionary *)params success:(void (^)(id))success failure:(void (^)(NSError *))failure { // 1.得到請求管理者 AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager]; // 2.申明返回的結果是text/html類型 mgr.responseSerializer = [AFHTTPResponseSerializer serializer]; // 3.設置超時時間爲10s mgr.requestSerializer.timeoutInterval = 10; // 加上這行代碼,https ssl 驗證。 if(openHttpsSSL) { [mgr setSecurityPolicy:[self customSecurityPolicy]]; } // 4.發送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 {
    // /先導入證書 NSString *cerPath = [[NSBundle mainBundle] pathForResource:certificate ofType:@"cer"];//證書的路徑 NSData *certData = [NSData dataWithContentsOfFile:cerPath]; // AFSSLPinningModeCertificate 使用證書驗證模式 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 = NO; securityPolicy.pinnedCertificates = @[certData]; return securityPolicy; }

其中的cerPath就是app bundle中證書路徑,certificate爲證書名稱的宏,僅支持cer格式,securityPolicy的相關配置尤其重要,請仔細閱讀customSecurityPolicy方法並根據實際狀況設置其屬性。

這樣,就可以在AFNetWorking的基礎上使用HTTPS協議訪問特定服務器,可是不能信任根證書的CA文件,所以這種方式存在風險,讀取pinnedCertificates中的證書數組的時候有可能失敗,若是證書不符合,certData就會爲nil。

3.更改系統方法,發送異步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]; }

重點在於處理NSURLConnection的didReceiveAuthenticationChallenge代理方法,對CA文件進行驗證,並創建信任鏈接。

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]; } - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { /* //直接驗證服務器是否被認證(serverTrust),這種方式直接忽略證書驗證,直接創建鏈接,但不能過濾其它URL鏈接,能夠理解爲一種折衷的處理方式,實際上並不安全,所以不推薦。 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 */ /** * 導入多張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]; }

這裏的關鍵在於result參數的值,根據官方文檔的說明,判斷(result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed)的值,若爲1,則該網站的CA被app信任成功,能夠創建數據鏈接,這意味着全部由該CA簽發的各個服務器證書都被信任,而訪問其它沒有被信任的任何網站都會鏈接失敗。該CA文件既能夠是SLL也能夠是自簽名。

NSURLConnection的其它代理方法實現

#pragma mark -- connect的異步代理方法 -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response { NSLog(@"請求被響應"); _mData = [[NSMutableData alloc]init]; } -(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data { NSLog(@"開始返回數據片斷"); [_mData appendData:data]; } -(void)connectionDidFinishLoading:(NSURLConnection *)connection { NSLog(@"連接完成"); //能夠在此解析數據 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信任證書的問題得以解決,這不只是爲了響應Apple強制性使用ATS的要求,也是爲了實際生產環境安全性的考慮,HTTPS是將來的趨勢,建議儘早支持。

相關文章
相關標籤/搜索