iOS AFNetworking框架HTTPS請求配置

tgmv9437 7年前發布 | 19K 次閱讀 iOS開發 移動開發 AFNetworking HTTPS

iOS在Apple公司的強制要求下,數據傳輸必須按照ATS(App Transefer Security)條款。關于AFNetworking框架傳輸HTTPS數據。

一.AllowsArbitraryLoads 白名單機制

NSAllowsArbitraryLoads是ATS推廣過程中的產物,當然也許可持續很久甚至永久,為了訪問HTTP服務,一般需要繞過ATS限制,需要配置info.plist文件

<key>NSAppTransportSecurity</key> 
<dict> 
        <key>NSAllowsArbitraryLoads</key> 
        <true/> 
 </dict>  

這種機制實際上是允許了所有HTTP 和HTTPS的訪問,顯然,這種做法實際上很危險。設置為false就能避免繞開ATS,問題是我們真的需要完全關閉這個選項么?

比如某些文件服務器,CDN服務器配置HTTPS反而影響傳輸速度,這種情況下HTTP反而具有很高的優越性。因此,對于這類服務器的HTTP傳輸,我們其實也可以使用如下方式(設置白名單),白名單之外的必須使用HTTPS協議。

<key>NSAppTransportSecurity</key> 
    <dict> 
        <key>NSExceptionDomains</key> 
        <dict> 
            <key>lib.baidu.com</key> 
            <dict> 
                <key>NSIncludesSubdomains</key> 
                <true/> 
            </dict> 
            <key>oss.fastdfs.cn</key> 
            <dict> 
                <key>NSIncludesSubdomains</key> 
                <true/> 
            </dict> 
           </dict> 
   </dict>  

二.免證書驗證

免證書驗證,一般來說是client證書庫不會把server傳輸來的證書進行校驗。

舉個例子

AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 
//允許非權威機構頒發的證書 
manager.securityPolicy.allowInvalidCertificates = YES; 
//也不驗證域名一致性 
manager.securityPolicy.validatesDomainName = NO; 
//關閉緩存避免干擾測試 
manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData; 
 
[manager GET:@"https://www.baidu.com/s?wd=https" parameters:nil progress:nil  
success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) { 
        NSLog(@"%@",responseObject); 
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 
        NSLog(@"%@",error); 
}];  

這種方式導致我們的app容易遭到中間人攻擊,原因并不完全是我們設置了allowInvalidCertificates=YES 和validatesDomainName=NO,而是本地證書庫中沒有添加server的CA證書,只要是任意的https都能偽造為服務器。

因此,我們并不推薦使用這種方式。

三.證書驗證

3.1 加密標準分類

證書驗證分為2類,一類是單向認證,一類是雙認證。

此外,我們還需要區分證書與證書庫的區別,證書庫類型包括PKCS12,JKS,BKS等,其中,PKCS12是互聯網標準,可以跨平臺跨語言(支持Android,iOS,WP,PC,JAVA,PHP...),JKS是Java標準,只能在Java平臺和Java語言下使用,BKS是 Bouncy Castle公司的加密標準,作為Android平臺支持SSL/TLS加密套件PKCS12的補充,其加密強度很高,但是目前只用于Android平臺。證書的定義是保存在證書庫中的數字簽名信息或者單獨的證書文件,如crt,pem,cer等文件。在說加密之前,先來看看自簽名證書。

3.2 自簽名證書 vs 第三方權威機構證書

有人可能會有疑問,自簽名證書和第三方權威機構的證書的效用是否一樣?

實際上本人認為完全一樣,在互聯網中,基于B/S架構的服務中,B端通常導入了第三方權威機構的根證書(ROOT CA),實際上就是為了驗證網站的CA證書是不是安全的,并且驗證是不是由權威的安全機構簽發的證書。問題是,我們的App與Server是C/S架構,每個app最多也就只能訪問有限的幾個網址,我們自己做的app實際上本身就是信任自己的所設置的站點URL的,也就是說我們自己人相信自己人。我們的證書只是為了數據安全傳輸,不被中間人攻擊,因此完全沒必要使用(ROOT CA),但是具體來說,到目前為止也沒人試過這種方式是否可以通過審核。

我試圖找第三方CA證書機構交流,貌似他只是說這是蘋果的規定,實際上自簽名證書本質上沒什么問題,只要密鑰長度,復雜度,加密方式等達到要求即可。

因此,蘋果如果真的要求必須使用第三方CA ROOT簽名的規則,本身是不合理的。這些問題也沒法避免,不過,如果蘋果允許自簽名證書的話,設置allowInvalidCertificates=YES即可。

3.3 單向認證

需要準備的文件:服務端證書庫 , 服務端導出的證書

單向認證,實際上說的是只有Client端對Server端的證書進行驗證,Server不需要驗證Client端的證書。

自定義類:MyAFNetworking

+ (AFHTTPSessionManager *)manager; 
{ 
    static AFHTTPSessionManager *shareInstance = nil; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
 
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; 
        shareInstance = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:BaseHttpURLString] sessionConfiguration:configuration]; 
        //設置請求參數的類型:JSON 
        shareInstance.requestSerializer = [AFJSONRequestSerializer serializer]; 
        //設置服務器返回結果的類型:JSON (AFJSONResponseSerializer,AFHTTPResponseSerializer) 
        shareInstance.responseSerializer = [AFJSONResponseSerializer serializer]; 
        //設置請求的超時時間 
        shareInstance.requestSerializer.timeoutInterval = 20.0f; 
        //設置ContentType 
        shareInstance.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/html", @"text/json", @"text/plain", @"text/javascript", @"text/xml", @"image/jpeg",@"image/png", nil]; 
 
        // https配置 
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"你的證書名" ofType:@"cer"]; 
        NSData *certData = [NSData dataWithContentsOfFile:cerPath]; 
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:[[NSSet alloc] initWithObjects:certData, nil]]; 
 
        NSSet *dataSet = [[NSSet alloc] initWithObjects:certData, nil]; //這里可以添加多個server的證書 
 
        // setPinnedCertificates 設置證書文件(可能不止一個證書) 
        [securityPolicy setPinnedCertificates:dataSet]; 
        // allowInvalidCertificates 是否允許無效證書 
        [securityPolicy setAllowInvalidCertificates:NO]; 
        // validatesDomainName 是否需要驗證域名 
        [securityPolicy setValidatesDomainName:YES]; 
 
        shareInstance.securityPolicy = securityPolicy; 
    }); 
    return shareInstance; 
} 

注意:以上說的證書是從服務器端到處的cer或者crt證書,這類證書是X509 Der格式的二進制編碼證書,不是X509 PAM格式的Base64編碼證書

3.4 雙向認證

iOS和Android一樣,客戶端證書庫類型可以是PKCS12類型的pfx證書,此類證書包含私鑰,公鑰和證書,并且由密碼。

雙向認證一般用于安全要求比較高的產品,比如金融類app,政府app等特殊行業。

需要準備的文件:服務端證書庫,服務端證書信任庫 , 服務端導出的證書,客戶端證書庫,客戶端證書

注[1]:服務端證書庫可以和服務端信任證書庫使用同一個證書庫,唯一要做的是把客戶端證書導入進行。

注[2]:客戶端證書一般使用跨平臺的PKCS12證書庫(pfx或p12),必須記住證書庫密鑰,此類證書庫同時包含私鑰,公鑰和證書。

3.4.1 信任服務器

本步驟用來校驗客戶端證書,和單向認證完全相同

自定義類:MyAFNetworking

+ (AFHTTPSessionManager *)manager; 
{ 
    static AFHTTPSessionManager *shareInstance = nil; 
    static dispatch_once_t onceToken; 
    dispatch_once(&onceToken, ^{ 
 
        NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration]; 
        shareInstance = [[AFHTTPSessionManager alloc] initWithBaseURL:[NSURL URLWithString:BaseHttpURLString] sessionConfiguration:configuration]; 
        //設置請求參數的類型:JSON 
        shareInstance.requestSerializer = [AFJSONRequestSerializer serializer]; 
        //設置服務器返回結果的類型:JSON (AFJSONResponseSerializer,AFHTTPResponseSerializer) 
        shareInstance.responseSerializer = [AFJSONResponseSerializer serializer]; 
        //設置請求的超時時間 
        shareInstance.requestSerializer.timeoutInterval = 20.0f; 
        //設置ContentType 
        shareInstance.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/json", @"text/html", @"text/json", @"text/plain", @"text/javascript", @"text/xml", @"image/jpeg",@"image/png", nil]; 
 
        // https配置 
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"你的證書名" ofType:@"cer"]; 
        NSData *certData = [NSData dataWithContentsOfFile:cerPath]; 
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:[[NSSet alloc] initWithObjects:certData, nil]]; 
 
        NSSet *dataSet = [[NSSet alloc] initWithObjects:certData, nil]; //這里可以添加多個server的證書 
 
        // setPinnedCertificates 設置證書文件(可能不止一個證書) 
        [securityPolicy setPinnedCertificates:dataSet]; 
        // allowInvalidCertificates 是否允許無效證書 
        [securityPolicy setAllowInvalidCertificates:NO]; 
        // validatesDomainName 是否需要驗證域名 
        [securityPolicy setValidatesDomainName:YES]; 
 
        shareInstance.securityPolicy = securityPolicy; 
    }); 
    return shareInstance; 
} 

3.4.2 提供客戶端證書和證書庫

/* 
* 
** 
* 創建服務器信任客戶端的認證條件 
** 
*/ 
+(AFHTTPSessionManager *) createCredentialsClient 
{ 
__block AFHTTPSessionManager * manager = [MyAFNetworking manager]; 
[manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession*session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing*_credential) { 
    NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling; 
    __autoreleasing NSURLCredential *credential =nil; 
    if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) { 
        if([manager.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) { 
            credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]; 
            if(credential) { 
                disposition =NSURLSessionAuthChallengeUseCredential; 
            } else { 
                disposition =NSURLSessionAuthChallengePerformDefaultHandling; 
            } 
        } else { 
            disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge; 
        } 
    } else { 
        // client authentication 
        SecIdentityRef identity = NULL; 
        SecTrustRef trust = NULL; 
        NSString *p12 = [[NSBundle mainBundle] pathForResource:@"client"ofType:@"pfx"]; 
        NSFileManager *fileManager =[NSFileManager defaultManager]; 
 
        if(![fileManager fileExistsAtPath:p12]) 
        { 
            NSLog(@"client.p12:not exist"); 
        } 
        else 
        { 
            NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12]; 
            #加載PKCS12證書,pfx或p12 
            if ([MyAFNetworking extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data]) 
            { 
                SecCertificateRef certificate = NULL; 
                SecIdentityCopyCertificate(identity, &certificate); 
                const void*certs[] = {certificate}; 
                CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL); 
                credential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge  NSArray*)certArray persistence:NSURLCredentialPersistencePermanent]; 
                disposition =NSURLSessionAuthChallengeUseCredential; 
            } 
        } 
    } 
    *_credential = credential; 
    return disposition; 
}]; 
 
return manager; 
} 
 
/** 
**加載PKCS12證書,pfx或p12 
**  
**/ 
+(BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data { 
    OSStatus securityError = errSecSuccess; 
    //client certificate password 
    NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"你的p12密碼" 
                                                                forKey:(__bridge id)kSecImportExportPassphrase]; 
 
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL); 
    securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDictionary,&items); 
 
    if(securityError == 0) { 
        CFDictionaryRef myIdentityAndTrust =CFArrayGetValueAtIndex(items,0); 
        const void*tempIdentity =NULL; 
        tempIdentity= CFDictionaryGetValue (myIdentityAndTrust,kSecImportItemIdentity); 
        *outIdentity = (SecIdentityRef)tempIdentity; 
        const void*tempTrust =NULL; 
        tempTrust = CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust); 
        *outTrust = (SecTrustRef)tempTrust; 
    } else { 
        NSLog(@"Failedwith error code %d",(int)securityError); 
        return NO; 
    } 
    return YES; 
} 

通過以上方式,我們便能實現雙向認證了

AFHTTPSessionManager * manager = [MyAFNetworking createCredentialsClient];  

 

來自:http://mobile.51cto.com/iphone-537758.htm

 

 本文由用戶 tgmv9437 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!