iOS https 雙向認(rèn)證


//點擊請求數(shù)據(jù)
- (void)btnAction{

NSString*urlString=@"https://192.168.30.91/";

AFSecurityPolicy*securityPolicy = [AFSecurityPolicypolicyWithPinningMode:AFSSLPinningModeNone];

//是否允許,NO--不允許無效的證書...

//因為大多數(shù)都是自己給自己頒布的證書,對于蘋果來說是不被信任的,是無效的,所以要設(shè)置YES.

securityPolicy.allowInvalidCertificates=YES;

//設(shè)置證書如果不設(shè)置,可以自動讀取工程里的cer文件

// 1.zhengshu.cer ca.cer(一個是服務(wù)器證書,一個服務(wù)器CA根證書,哪一個都可以)

//service.cer 2.服務(wù)器證書

//securityPolicy.pinnedCertificates=set;

//validatesDomainName是否需要驗證域名,默認(rèn)為YES;

//假如證書的域名與你請求的域名不一致,需把該項設(shè)置為NO;如設(shè)成NO的話,即服務(wù)器使用其他可信任機構(gòu)頒發(fā)的證書,也可以建立連接,這個非常危險,建議打開。

//置為NO,主要用于這種情況:客戶端請求的是子域名,而證書上的是另外一個域名。因為SSL證書上的域名是獨立的,假如證書上注冊的域名是www.google.com,那么mail.google.com是無法驗證通過的;當(dāng)然,有錢可以注冊通配符的域名*.google.com,但這個還是比較貴的。

//如置為NO,建議自己添加對應(yīng)域名的校驗邏輯。

//一.zhengshu.cer[https://192.168.30.91/](https://192.168.30.91/)無需設(shè)置infoplist

//二.service.cer[https://192.168.30.55:8443/IEMS_APP/](https://192.168.30.55:8443/IEMS_APP/)//模擬器必須設(shè)置info.plist YES真機不用設(shè)置info.plist

//兩種都可以加密數(shù)據(jù)安全,防止中間人抓包工具

securityPolicy.validatesDomainName=NO;

AFHTTPSessionManager* manager = [AFHTTPSessionManagermanager];

manager.responseSerializer.acceptableContentTypes=[NSSetsetWithObjects:@"text/html",nil];

manager.requestSerializer.cachePolicy=NSURLRequestReloadIgnoringLocalCacheData;

__weaktypeof(self)weakSelf =self;

//重寫這個方法就能提供客戶端驗證

[managersetSessionDidBecomeInvalidBlock:^(NSURLSession*_Nonnullsession,NSError*_Nonnullerror) {

        NSLog(@"setSessionDidBecomeInvalidBlock");

}];

[managersetSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession*session,NSURLAuthenticationChallenge*challenge,NSURLCredential*__autoreleasing*_credential) {

NSURLSessionAuthChallengeDispositiondisposition =NSURLSessionAuthChallengePerformDefaultHandling;

    __autoreleasingNSURLCredential*credential =nil;

    if([challenge.protectionSpace.authenticationMethodisEqualToString:NSURLAuthenticationMethodServerTrust]) {

         if([manager.securityPolicyevaluateServerTrust:challenge.protectionSpace.serverTrustforDomain:challenge.protectionSpace.host]) {

                credential = [NSURLCredentialcredentialForTrust:challenge.protectionSpace.serverTrust];

                if(credential) {

                        disposition =NSURLSessionAuthChallengeUseCredential;

                }else{

                        disposition =NSURLSessionAuthChallengePerformDefaultHandling;

          }
  }else{

        disposition =NSURLSessionAuthChallengeCancelAuthenticationChallenge;

}

}else{

// client authentication

SecIdentityRefidentity =NULL;

SecTrustReftrust =NULL;

NSString*p12 = [[NSBundlemainBundle]pathForResource:@"client.key"ofType:@"p12"];

NSFileManager*fileManager =[NSFileManagerdefaultManager];

if(![fileManagerfileExistsAtPath:p12])

{

NSLog(@"client.p12:not exist");

}

else

{

NSData*PKCS12Data = [NSDatadataWithContentsOfFile:p12];

if([[weakSelfclass]extractIdentity:&identityandTrust:&trustfromPKCS12Data:PKCS12Data])

{

SecCertificateRefcertificate =NULL;

SecIdentityCopyCertificate(identity, &certificate);

constvoid*certs[] = {certificate};

CFArrayRefcertArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL);

credential =[NSURLCredentialcredentialWithIdentity:identitycertificates:(__bridgeNSArray*)certArraypersistence:NSURLCredentialPersistencePermanent];

disposition =NSURLSessionAuthChallengeUseCredential;

}

}

}

*_credential = credential;

returndisposition;

}];

manager.securityPolicy= securityPolicy;

manager.responseSerializer= [AFHTTPResponseSerializerserializer];

[managerGET:urlStringparameters:nilprogress:^(NSProgress*_NonnulldownloadProgress) {

}success:^(NSURLSessionDataTask*_Nonnulltask,id_NullableresponseObject) {

NSString*html=[[NSStringalloc]initWithData:responseObjectencoding:NSUTF8StringEncoding];

self.label.text=html;

}failure:^(NSURLSessionDataTask*_Nullabletask,NSError*_Nonnullerror) {

self.label.textColor=[UIColorredColor];

self.label.text=error.debugDescription;

}];

}

+(BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef*)outTrust fromPKCS12Data:(NSData*)inPKCS12Data {

OSStatussecurityError =errSecSuccess;

//client certificate password

NSDictionary*optionsDictionary = [NSDictionarydictionaryWithObject:@"123456"

forKey:(__bridgeid)kSecImportExportPassphrase];

CFArrayRefitems =CFArrayCreate(NULL,0,0,NULL);

securityError =SecPKCS12Import((__bridgeCFDataRef)inPKCS12Data,(__bridgeCFDictionaryRef)optionsDictionary,&items);

if(securityError ==0) {

CFDictionaryRefmyIdentityAndTrust =CFArrayGetValueAtIndex(items,0);

constvoid*tempIdentity =NULL;

tempIdentity=CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemIdentity);

*outIdentity = (SecIdentityRef)tempIdentity;

constvoid*tempTrust =NULL;

tempTrust =CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust);

*outTrust = (SecTrustRef)tempTrust;

}else{

NSLog(@"Failedwith error code %d",(int)securityError);

returnNO;

}

returnYES;

}

/**

一.非瀏覽器應(yīng)用(iOS app)與服務(wù)器AFNetworking HTTPS ssl認(rèn)證

雖然是HTTPS的網(wǎng)站,但是服務(wù)器端也要設(shè)置對客戶端提供認(rèn)證證書忽略(此處與瀏覽器與服務(wù)器SSl雙向認(rèn)證不太一樣).

AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModePublicKey];

//是否允許,NO--不允許無效的證書,設(shè)置允許(YES)(因為我們的證書一般都是自簽名的,并不是像谷歌那種大型網(wǎng)站權(quán)威頒布的官方認(rèn)證證書)

securityPolicy.allowInvalidCertificates=YES;

//validatesDomainName是否需要驗證域名,默認(rèn)為YES;

//假如證書的域名與你請求的域名不一致,需把該項設(shè)置為NO;如設(shè)成NO的話,即服務(wù)器使用其他可信任機構(gòu)頒發(fā)的證書,也可以建立連接,這個非常危險,建議打開。

//置為NO,主要用于這種情況:客戶端請求的是子域名,而證書上的是另外一個域名。因為SSL證書上的域名是獨立的,假如證書上注冊的域名是www.google.com,那么mail.google.com是無法驗證通過的;當(dāng)然,有錢可以注冊通配符的域名*.google.com,但這個還是比較貴的。

//如置為NO,建議自己添加對應(yīng)域名的校驗邏輯。

//securityPolicy.validatesDomainName=NO;

manager.securityPolicy = securityPolicy;

這幾句代碼是必需的,訪問HTTPS(比http多加這些).

AFSSLPinningModeNone,AFSSLPinningModePublicKey,AFSSLPinningModeCertificate,這三個屬性是上邊紅色位置的三種情況.

SSL Pinning可以理解為證書綁定,是指客戶端直接保存服務(wù)端的證書,建立https連接時直接對比服務(wù)端返回的和客戶端保存的兩個證書是否一樣,一樣就表明證書是真的,不再去系統(tǒng)的信任證書機構(gòu)里尋找驗證。這適用于非瀏覽器應(yīng)用,因為瀏覽器跟很多未知服務(wù)端打交道,無法把每個服務(wù)端的證書都保存到本地,但CS架構(gòu)的像手機APP事先已經(jīng)知道要進(jìn)行通信的服務(wù)端,可以直接在客戶端保存這個服務(wù)端的證書用于校驗。

為什么直接對比就能保證證書沒問題?如果中間人從客戶端取出證書,再偽裝成服務(wù)端跟其他客戶端通信,它發(fā)送給客戶端的這個證書不就能通過驗證嗎?確實可以通過驗證,但后續(xù)的流程走不下去,因為下一步客戶端會用證書里的公鑰加密,中間人沒有這個證書的私鑰就解不出內(nèi)容,也就截獲不到數(shù)據(jù),這個證書的私鑰只有真正的服務(wù)端有,中間人偽造證書主要偽造的是公鑰。

為什么要用SSLPinning?正常的驗證方式不夠嗎?如果服務(wù)端的證書是從受信任的的CA機構(gòu)頒發(fā)的,驗證是沒問題的,但CA機構(gòu)頒發(fā)證書比較昂貴,小企業(yè)或個人用戶可能會選擇自己頒發(fā)證書,這樣就無法通過系統(tǒng)受信任的CA機構(gòu)列表驗證這個證書的真?zhèn)瘟耍孕枰猄SL Pinning這樣的方式去驗證。

在iOS開發(fā)中,從Xcode7和iOS9開始,Apple提升了App的網(wǎng)絡(luò)安全性,App默認(rèn)只能進(jìn)行對采用權(quán)威機構(gòu)簽名頒發(fā)證書的Web站點進(jìn)行訪問(信任的HTTPS),而自簽名的證書的HTTPS站點也被列為屬于例外,所以我們需要在App的Info.plist中單獨為我們的域名設(shè)置Exception Domains"白名單”.也可以使用放開全部的設(shè)置NSAllowsArbitraryLoads為true.

1.AFSSLPinningModeNone

這個模式表示不做SSL pinning,只跟瀏覽器一樣在系統(tǒng)的信任機構(gòu)列表里驗證服務(wù)端返回的證書。若證書是信任機構(gòu)簽發(fā)的就會通過,若是自己服務(wù)器生成的證書,這里是不會通過的。

所以要用到上面securityPolicy.allowInvalidCertificates=YES;

必須修改infoplist文件即:像http請求一樣(App TransportSecurity Settings——>Allow Arbitrary LoadsYES.)

這個屬性使數(shù)據(jù)不安全,一些中間人(抓包工具)會截取到數(shù)據(jù)接口.

2.AFSSLPinningModeCertificate:

這個模式表示用證書綁定方式驗證證書,需要客戶端保存有服務(wù)端的證書拷貝,這里驗證分兩步,第一步驗證證書的域名/有效期等信息,第二步是對比服務(wù)端返回的證書跟客戶端返回的是否一致。

驗證服務(wù)器身份在沒有使用代理的時候可以正常訪問服務(wù)器的資源,但是一旦用戶給手機網(wǎng)絡(luò)設(shè)置使用了如Charle那樣的HTTPS/SSL代理服務(wù),則會出現(xiàn)服務(wù)器證書驗證失敗,SSL網(wǎng)絡(luò)連接會斷開,老板再也不用擔(dān)心數(shù)據(jù)接口被人抓包或者代理給扒出來了.故達(dá)到防止中間人攻擊的效果.

無需修改infoplist(自己測試了).

這個證書是服務(wù)器端的證書(.cer文件)(可以是根證書,也可以是證書,總之必須是服務(wù)器的),把這個證書直接copy到Xcode工程,程序會自動讀取.cer文件.

3.AFSSLPinningModePublicKey (和第二個差不多)

這個模式同樣是用證書綁定方式驗證,客戶端要有服務(wù)端的證書拷貝,只是驗證時只驗證證書里的公鑰,不驗證證書的有效期等信息。只要公鑰是正確的,就能保證通信不會被竊聽,因為中間人沒有私鑰,無法解開通過公鑰加密的數(shù)據(jù)。

二.客戶端瀏覽器與服務(wù)器端HTTPS雙向認(rèn)證

①瀏覽器發(fā)送一個連接請求給安全服務(wù)器。

②服務(wù)器將自己的證書,以及同證書相關(guān)的信息發(fā)送給客戶瀏覽器。

③客戶瀏覽器檢查服務(wù)器送過來的證書是否是由自己信賴的CA中心所簽發(fā)的。如果是,就繼續(xù)執(zhí)行協(xié)議;如果不是,客戶瀏覽器就給客戶一個警告消息:警告客戶這個證書不是可以信賴的,詢問客戶是否需要繼續(xù)。

④接著客戶瀏覽器比較證書里的消息,例如域名和公鑰,與服務(wù)器剛剛發(fā)送的相關(guān)消息是否一致,如果是一致的,客戶瀏覽器認(rèn)可這個服務(wù)器的合法身份。

⑤服務(wù)器要求客戶發(fā)送客戶自己的證書。收到后,服務(wù)器驗證客戶的證書,如果沒有通過驗證,拒絕連接;如果通過驗證,服務(wù)器獲得用戶的公鑰。

⑥客戶瀏覽器告訴服務(wù)器自己所能夠支持的通訊對稱密碼方案。

⑦服務(wù)器從客戶發(fā)送過來的密碼方案中,選擇一種加密程度最高的密碼方案,用客戶的公鑰加過密后通知瀏覽器。

⑧瀏覽器針對這個密碼方案,選擇一個通話密鑰,接著用服務(wù)器的公鑰加過密后發(fā)送給服務(wù)器。

⑨服務(wù)器接收到瀏覽器送過來的消息,用自己的私鑰解密,獲得通話密鑰。

⑩服務(wù)器、瀏覽器接下來的通訊都是用對稱密碼方案,對稱密鑰是加過密的。

這兩種形式雖然同樣是ssl認(rèn)證,本人感覺并不是一個意思.開始的時候是按照瀏覽器與服務(wù)器端的ssl雙向認(rèn)證來做AFNetworking,服務(wù)器端設(shè)置必須客戶端提供證書才能訪問網(wǎng)站,否則403,但是試了好多次都是請求失敗(萬念俱灰).查看別人的HTTPS接口例子不一樣和之前做的.于是認(rèn)為這兩種驗證方式是不同的.把服務(wù)器端必需的客戶端證書,改成忽略,經(jīng)過測試,用AFNetworking使用服務(wù)器端證書,也能達(dá)到加密的作用,而且AFNetworking,確實是這么應(yīng)用的.代碼很少,但是這里面是非瀏覽器應(yīng)用如何應(yīng)用,以及和瀏覽器的雙向認(rèn)證區(qū)分開.

我是從下面這些個說法中總結(jié)的:

//瀏覽器與服務(wù)端雙向認(rèn)證

http://www.itdecent.cn/p/8b4312c34808

http://www.itdecent.cn/p/20d5fb4cd76d

//https為什么還要改infoplist如何防止抓包(中間人)

http://www.itdecent.cn/p/c6a903da8346

非瀏覽器應(yīng)用AFNetworking SSLPINNing (雙向認(rèn)證)

http://www.cocoachina.com/ios/20140916/9632.html

HTTPS接口例子:

https://tv.diveinedu.com/channel/

https://daka.facenano.com/checkin/v1/app_binding?phone_number=18700000001&app_version_code=2&device=mobile_ios&company_tag=iPhone-demo&phone_imei=6D56F277-0AAA-4F32-AD01-6C55AEE75964&verification_code=3216

*/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容