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


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

 

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

(收錄一下供自己學習用的)

 

字數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。

所以對於iOS開發者來說,需要盡早解決HTTPS請求的問題。

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

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

這種方式不需要在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是未來的趨勢,建議盡早支持。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM