標籤:create 所有應用 關於 container 直接 設定 允許 set append
首先來分析一下什麼是HTTPS以及瞭解HTTPS對於iOS開發人員的意義
HTTPS 以及SSL/TSL
SSL(Secure Sockets Layer, 安全通訊端層),因為原先互連網上使用的 HTTP 協議是明文的,存在很多缺點,比如傳輸內容會被偷窺(嗅探)和篡改。 SSL 協議的作用就是在傳輸層對網路連接進行加密。
到了1999年,SSL 因為應用廣泛,已經成為互連網上的事實標準。IETF 就在那年把 SSL 標準化。標準化之後的名稱改為 TLS(Transport Layer Security,傳輸層安全性通訊協定)。SSL與TLS可以視作同一個東西的不同階段
簡單來說,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。蘋果目前允許開發人員暫時可以繼續使用HTTP串連,但到年底所有官方商店的應用都必須強制性使用ATS。
以下是開發人員網站公告原文:
應用傳輸安全性通訊協定是與iOS9和OS X 10.11一同發布的,該協議需要應用程式通過HTTPS使用安全的網路連接,以提高使用者的資料和隱私安全。
在2016年WWDC上我們宣布在今年年底之前,提交到App Store的應用程式必須支援應用傳輸安全性通訊協定。為了給你們額外的時間去準備,這個到期日已被延長,當新的到期日確定的時候,我們將及時提供相關資訊。
2016年12月21日蘋果更新了到期日,宣布延期執行ATS支援要求Supporting App Transport Security。
開發人員網站
** 此舉為開發人員提供了更多時間來做適配和支援。然而,對於iOS開發人員來說,儘早解決HTTPS請求的問題仍為上策。**
蘋果ATS對HTTPS認證的要求
啟用ATS必須符合以下標準,不滿足條件的HTTPS認證,ATS都會拒絕串連:
- 伺服器所有的串連使用TLS1.2以上版本
- HTTPS認證必須使用SHA256以上雜湊演算法簽名
- HTTPS認證必須使用RSA 2048位或ECC 256位以上公開金鑰演算法
- 使用前向加密技術
此外,蘋果ATS支援CT認證透明,要求開發人員使用支援CT認證透明度的SSL認證,確保SSL認證合法透明,防止中間人攻擊。
發送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是未來的趨勢,建議儘早支援。
如需參考Demo請移步本人在Github上的開源項目
iOS開發HTTPS實現之信任SSL認證和自我簽署憑證