iOS AFNetworking框架HTTPS請求配置
【引自IamOkay的博客】 iOS在Apple公司的強制要求下,數(shù)據(jù)傳輸必須按照ATS(App Transefer Security)條款。關(guān)于AFNetworking框架傳輸HTTPS數(shù)據(jù)。
一.AllowsArbitraryLoads 白名單機制
NSAllowsArbitraryLoads是ATS推廣過程中的產(chǎn)物,當(dāng)然也許可持續(xù)很久甚至永久,為了訪問HTTP服務(wù),一般需要繞過ATS限制,需要配置info.plist文件
- <key>NSAppTransportSecurity</key>
- <dict>
- <key>NSAllowsArbitraryLoads</key>
- <true/>
- </dict>
這種機制實際上是允許了所有HTTP 和HTTPS的訪問,顯然,這種做法實際上很危險。設(shè)置為false就能避免繞開ATS,問題是我們真的需要完全關(guān)閉這個選項么?
比如某些文件服務(wù)器,CDN服務(wù)器配置HTTPS反而影響傳輸速度,這種情況下HTTP反而具有很高的優(yōu)越性。因此,對于這類服務(wù)器的HTTP傳輸,我們其實也可以使用如下方式(設(shè)置白名單),白名單之外的必須使用HTTPS協(xié)議。
- <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];
- //允許非權(quán)威機構(gòu)頒發(fā)的證書
- manager.securityPolicy.allowInvalidCertificates = YES;
- //也不驗證域名一致性
- manager.securityPolicy.validatesDomainName = NO;
- //關(guān)閉緩存避免干擾測試
- 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);
- }];
這種方式導(dǎo)致我們的app容易遭到中間人攻擊,原因并不完全是我們設(shè)置了allowInvalidCertificates=YES 和validatesDomainName=NO,而是本地證書庫中沒有添加server的CA證書,只要是任意的https都能偽造為服務(wù)器。
因此,我們并不推薦使用這種方式。
三.證書驗證
3.1 加密標準分類
證書驗證分為2類,一類是單向認證,一類是雙認證。
此外,我們還需要區(qū)分證書與證書庫的區(qū)別,證書庫類型包括PKCS12,JKS,BKS等,其中,PKCS12是互聯(lián)網(wǎng)標準,可以跨平臺跨語言(支持Android,iOS,WP,PC,JAVA,PHP...),JKS是Java標準,只能在Java平臺和Java語言下使用,BKS是 Bouncy Castle公司的加密標準,作為Android平臺支持SSL/TLS加密套件PKCS12的補充,其加密強度很高,但是目前只用于Android平臺。證書的定義是保存在證書庫中的數(shù)字簽名信息或者單獨的證書文件,如crt,pem,cer等文件。在說加密之前,先來看看自簽名證書。
3.2 自簽名證書 vs 第三方權(quán)威機構(gòu)證書
有人可能會有疑問,自簽名證書和第三方權(quán)威機構(gòu)的證書的效用是否一樣?
實際上本人認為完全一樣,在互聯(lián)網(wǎng)中,基于B/S架構(gòu)的服務(wù)中,B端通常導(dǎo)入了第三方權(quán)威機構(gòu)的根證書(ROOT CA),實際上就是為了驗證網(wǎng)站的CA證書是不是安全的,并且驗證是不是由權(quán)威的安全機構(gòu)簽發(fā)的證書。問題是,我們的App與Server是C/S架構(gòu),每個app最多也就只能訪問有限的幾個網(wǎng)址,我們自己做的app實際上本身就是信任自己的所設(shè)置的站點URL的,也就是說我們自己人相信自己人。我們的證書只是為了數(shù)據(jù)安全傳輸,不被中間人攻擊,因此完全沒必要使用(ROOT CA),但是具體來說,到目前為止也沒人試過這種方式是否可以通過審核。
我試圖找第三方CA證書機構(gòu)交流,貌似他只是說這是蘋果的規(guī)定,實際上自簽名證書本質(zhì)上沒什么問題,只要密鑰長度,復(fù)雜度,加密方式等達到要求即可。
因此,蘋果如果真的要求必須使用第三方CA ROOT簽名的規(guī)則,本身是不合理的。這些問題也沒法避免,不過,如果蘋果允許自簽名證書的話,設(shè)置allowInvalidCertificates=YES即可。
3.3 單向認證
需要準備的文件:服務(wù)端證書庫 , 服務(wù)端導(dǎo)出的證書
單向認證,實際上說的是只有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];
- //設(shè)置請求參數(shù)的類型:JSON
- shareInstance.requestSerializer = [AFJSONRequestSerializer serializer];
- //設(shè)置服務(wù)器返回結(jié)果的類型:JSON (AFJSONResponseSerializer,AFHTTPResponseSerializer)
- shareInstance.responseSerializer = [AFJSONResponseSerializer serializer];
- //設(shè)置請求的超時時間
- shareInstance.requestSerializer.timeoutInterval = 20.0f;
- //設(shè)置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 設(shè)置證書文件(可能不止一個證書)
- [securityPolicy setPinnedCertificates:dataSet];
- // allowInvalidCertificates 是否允許無效證書
- [securityPolicy setAllowInvalidCertificates:NO];
- // validatesDomainName 是否需要驗證域名
- [securityPolicy setValidatesDomainName:YES];
- shareInstance.securityPolicy = securityPolicy;
- });
- return shareInstance;
- }
注意:以上說的證書是從服務(wù)器端到處的cer或者crt證書,這類證書是X509 Der格式的二進制編碼證書,不是X509 PAM格式的Base64編碼證書,關(guān)于自簽名證書的生成請參考如下地址
3.4 雙向認證
iOS和Android一樣,客戶端證書庫類型可以是PKCS12類型的pfx證書,此類證書包含私鑰,公鑰和證書,并且由密碼。
雙向認證一般用于安全要求比較高的產(chǎn)品,比如金融類app,政府a(chǎn)pp等特殊行業(yè)。
需要準備的文件:服務(wù)端證書庫,服務(wù)端證書信任庫 , 服務(wù)端導(dǎo)出的證書,客戶端證書庫,客戶端證書
注[1]:服務(wù)端證書庫可以和服務(wù)端信任證書庫使用同一個證書庫,唯一要做的是把客戶端證書導(dǎo)入進行。
注[2]:客戶端證書一般使用跨平臺的PKCS12證書庫(pfx或p12),必須記住證書庫密鑰,此類證書庫同時包含私鑰,公鑰和證書。
3.4.1 信任服務(wù)器
本步驟用來校驗客戶端證書,和單向認證完全相同
自定義類: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];
- //設(shè)置請求參數(shù)的類型:JSON
- shareInstance.requestSerializer = [AFJSONRequestSerializer serializer];
- //設(shè)置服務(wù)器返回結(jié)果的類型:JSON (AFJSONResponseSerializer,AFHTTPResponseSerializer)
- shareInstance.responseSerializer = [AFJSONResponseSerializer serializer];
- //設(shè)置請求的超時時間
- shareInstance.requestSerializer.timeoutInterval = 20.0f;
- //設(shè)置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 設(shè)置證書文件(可能不止一個證書)
- [securityPolicy setPinnedCertificates:dataSet];
- // allowInvalidCertificates 是否允許無效證書
- [securityPolicy setAllowInvalidCertificates:NO];
- // validatesDomainName 是否需要驗證域名
- [securityPolicy setValidatesDomainName:YES];
- shareInstance.securityPolicy = securityPolicy;
- });
- return shareInstance;
- }
3.4.2 提供客戶端證書和證書庫
- /*
- *
- **
- * 創(chuàng)建服務(wù)器信任客戶端的認證條件
- **
- */
- +(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;
- }
通過以上方式,我們便能實現(xiàn)雙向認證了
- AFHTTPSessionManager * manager = [MyAFNetworking createCredentialsClient];