iOS開發app實現支持HTTPS

什么是HTTPS?

在說HTTPS之前先說說什么是HTTP,HTTP就是我們平時瀏覽網頁時候使用的一種協議。HTTP協議傳輸的數據都是未加密的,也就是明文的,因此使用HTTP協議傳輸隱私信息非常不安全。為了保證這些隱私數據能加密傳輸,于是網景公司設計了SSL(Secure Sockets Layer)協議用于對HTTP協議傳輸的數據進行加密,從而就誕生了HTTPS。

如果從最終的數據解析角度去看HTTPS,HTTPS與HTTP沒有任何區別,HTTPS就是將HTTP協議數據包放到SSL/TSL層加密后,在TCP/IP層組成IP數據報去傳輸,以此保證傳輸數據的安全;在接受端,SSL/TSL將接收的數據包解密之后,將數據傳給HTTP協議層,獲得普通的HTTP數據,簡單地概括為 HTTPS = HTTP + SSL/TLS + TCP。

在WWDC 2016開發者大會上,蘋果宣布在2016年底前所有iOS應用都必須啟用App Transport Security安全功能,對于未啟用App Transport Security安全功能的app蘋果都會進行更嚴格的審核,如果沒有充分的理由,很可能你的app會被拒絕上架。

此處引用蘋果官方文檔內容:

App Store Review for ATS
此處省略一大段蘋果官方文檔內容.....
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頒發的證書,服務器配置符合蘋果ATS要求:

App Transport Security 要求 TLS 1.2,而且它要求站點使用支持forward secrecy協議的密碼。證書也要求是符合ATS規格的,ATS只信任知名CA頒發的證書,小公司所使用的 self signed certificate,還是會被ATS攔截。因此慎重檢查與你的應用交互的服務器是不是符合ATS的要求非常重要。

官方文檔App Transport Security Technote對CA頒發的證書要求:

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文件,可以交給系統去判斷服務器端的證書是不是SSL證書,驗證過程也不需要我們去具體實現。

二:基于AFNetWorking的SSL特定服務器證書信任處理,重寫AFNetWorking的customSecurityPolicy方法,這里我創建了一個HttpRequest工具類,分別對GET和POST方法進行了封裝,以GET方法為例:
+ (void)getWithUrl:(NSString *)url
            params:(NSDictionary *)params
           success:(void (^)(id))success
              fail:(void (^)(NSError *))fail
{
 //初始化AFHTTPSessionManager 
    AFHTTPSessionManager *manager = [self manager];

// 發出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 {
    // /先導入證書
    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要設置為YES
    securityPolicy.allowInvalidCertificates = YES;
    //是否需要驗證域名
    securityPolicy.validatesDomainName = NO;
    
    securityPolicy.pinnedCertificates = [NSSet setWithArray:@[certFileData]];
    
    return securityPolicy;
}

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

三:更改系統方法,發送異步NSURLConnection請求。
//發送https網絡請求
- (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文件進行驗證,并建立信任連接。

- (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);
    }
    
    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信任證書的問題得以解決。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • 原文地址 http://blog.csdn.net/u012409247/article/details/4985...
    0fbf551ff6fb閱讀 3,566評論 0 13
  • 一、作用 不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文傳播,帶來了三大風險。 (1)竊聽風險...
    XLsn0w閱讀 10,621評論 2 44
  • 一. HTTPS 其實HTTPS從最終的數據解析的角度,與HTTP沒有任何的區別,HTTPS就是將HTTP協議數據...
    獨酌丿紅顏閱讀 3,689評論 4 122
  • 天空灰白 像吸滿水的棉 行人緊張的看著天 腳步匆匆 擔心不知道哪一秒大雨點就會被風吹下來 終于到了目的地 放松的看...
    貓二蛋閱讀 179評論 0 0
  • 世事怎能念曾經 今天一早看到這樣一句話:命運嘛,休論公道! 短短七個字,平靜之中有著點無奈、清醒、看透,但在這通透...
    狗西西閱讀 933評論 0 0