什么是HTTPS床佳?
在說HTTPS之前先說說什么是HTTP蔓彩,HTTP就是我們平時瀏覽網(wǎng)頁時候使用的一種協(xié)議导俘。HTTP協(xié)議傳輸?shù)臄?shù)據(jù)都是未加密的峦耘,也就是明文的,因此使用HTTP協(xié)議傳輸隱私信息非常不安全旅薄。為了保證這些隱私數(shù)據(jù)能加密傳輸辅髓,于是網(wǎng)景公司設(shè)計了SSL(Secure Sockets Layer)協(xié)議用于對HTTP協(xié)議傳輸?shù)臄?shù)據(jù)進(jìn)行加密,從而就誕生了HTTPS赋秀。
如果從最終的數(shù)據(jù)解析角度去看HTTPS利朵,HTTPS與HTTP沒有任何區(qū)別律想,HTTPS就是將HTTP協(xié)議數(shù)據(jù)包放到SSL/TSL層加密后猎莲,在TCP/IP層組成IP數(shù)據(jù)報去傳輸,以此保證傳輸數(shù)據(jù)的安全技即;在接受端著洼,SSL/TSL將接收的數(shù)據(jù)包解密之后,將數(shù)據(jù)傳給HTTP協(xié)議層而叼,獲得普通的HTTP數(shù)據(jù),簡單地概括為 HTTPS = HTTP + SSL/TLS + TCP身笤。
在WWDC 2016開發(fā)者大會上,蘋果宣布在2016年底前所有iOS應(yīng)用都必須啟用App Transport Security安全功能葵陵,對于未啟用App Transport Security安全功能的app蘋果都會進(jìn)行更嚴(yán)格的審核液荸,如果沒有充分的理由,很可能你的app會被拒絕上架脱篙。
此處引用蘋果官方文檔內(nèi)容:
App Store Review for ATS
此處省略一大段蘋果官方文檔內(nèi)容.....
When submitting your app to the App Store, provide sufficient information for the App Store to determine why your app cannot make secure connections by default.
一:公司使用的是知名CA頒發(fā)的證書,服務(wù)器配置符合蘋果ATS要求:
App Transport Security 要求 TLS 1.2族操,而且它要求站點使用支持forward secrecy協(xié)議的密碼队寇。證書也要求是符合ATS規(guī)格的,ATS只信任知名CA頒發(fā)的證書,小公司所使用的 self signed certificate冒掌,還是會被ATS攔截。因此慎重檢查與你的應(yīng)用交互的服務(wù)器是不是符合ATS的要求非常重要丹莲。
官方文檔App Transport Security Technote對CA頒發(fā)的證書要求:
The leaf server certificate must be signed with one of the following types of keys:
1.Rivest-Shamir-Adleman (RSA) key with a length of at least 2048 bits
2.Elliptic-Curve Cryptography (ECC) key with a size of at least 256 bits
In addition, the leaf server certificate hashing algorithm must be Secure Hash Algorithm 2 (SHA-2) with a digest length, sometimes called a “fingerprint,” of at least 256 (that is, SHA-256 or greater).
這種情況不需要在Bundle中引入CA文件昼汗,可以交給系統(tǒng)去判斷服務(wù)器端的證書是不是SSL證書,驗證過程也不需要我們?nèi)ゾ唧w實現(xiàn)取视。
二:基于AFNetWorking的SSL特定服務(wù)器證書信任處理硝皂,重寫AFNetWorking的customSecurityPolicy方法,這里我創(chuàng)建了一個HttpRequest工具類,分別對GET和POST方法進(jìn)行了封裝作谭,以GET方法為例:
+ (void)getWithUrl:(NSString *)url
params:(NSDictionary *)params
success:(void (^)(id))success
fail:(void (^)(NSError *))fail
{
//初始化AFHTTPSessionManager
AFHTTPSessionManager *manager = [self manager];
// 發(fā)出GET請求
[manager GET:url parameters:params success:^(AFHTTPRequestOperation *operation, id response){
if (success) {
success(response);
}
} fail:^(AFHTTPRequestOperation *operation, NSError *error) {
if (error) {
fail(error);
}
}];
}
+ (AFHTTPSessionManager *)manager {
//初始化AFHTTPSessionManager
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
switch (sg_requestType) {
case kZHRequestTypeJSON: {
manager.requestSerializer = [AFJSONRequestSerializer serializer];
break;
}
case kZHRequestTypePlainText: {
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
break;
}
}
// https ssl 驗證 KOpenHttpsSSL為證書名稱的宏
if (KOpenHttpsSSL) {
[manager setSecurityPolicy:[self customSecurityPolicy]];
}
return manager;
}
+ (AFSecurityPolicy*)customSecurityPolicy {
// /先導(dǎo)入證書
NSString *cerFilePath = [[NSBundle mainBundle] pathForResource:KCertificateName ofType:@"cer"];//證書的路徑
NSData *certFileData = [NSData dataWithContentsOfFile:cerFilePath];
// AFSSLPinningModeCertificate 證書驗證模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// allowInvalidCertificates 是否允許 self signed certificate
// 驗證 self signed certificate要設(shè)置為YES
securityPolicy.allowInvalidCertificates = YES;
//是否需要驗證域名
securityPolicy.validatesDomainName = NO;
securityPolicy.pinnedCertificates = [NSSet setWithArray:@[certFileData]];
return securityPolicy;
}
這樣吧彪,就能夠在AFNetWorking的基礎(chǔ)上使用HTTPS協(xié)議訪問特定服務(wù)器,但是不能信任根證書的CA文件丢早,因此這種方式存在風(fēng)險姨裸,讀取pinnedCertificates中的證書數(shù)組的時候有可能失敗秧倾,如果證書不符合,certFileData就會為nil傀缩。
三:更改系統(tǒng)方法那先,發(fā)送異步NSURLConnection請求。
//發(fā)送https網(wǎng)絡(luò)請求
- (void)sendHttpsRequest
{
NSString *urlStr = @"https://api.weibo.com/";
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文件進(jìn)行驗證售淡,并建立信任連接。
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
/*
//直接驗證服務(wù)器是否被認(rèn)證(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 */
/**
* 導(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);
}
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的其它代理方法實現(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信任證書的問題得以解決傲宜。