https雙向認證 iOS客戶端處理

寫在前面:

1.相關https具體內容本篇就不再描述。

2.前幾天公司項目要求配置https雙向認證,由于是銀行業務,證書是cfca(中國金融認證中心)頒發的,因此 在這里我就不描述自簽名證書的具體。iOS自簽名證書,我沒有調試,據說是不認自簽名的。自簽名證書中,這里有篇博客講的非常好,這篇幫我在本地搭建了雙向認證的tomcat服務器。http://blog.csdn.net/jerryvon/article/details/8548802

3.這篇博客側重于 iOS客戶端相關內容的描述,OC語言,基于AFNetwork3.0版本。

本篇內容

1.https雙向認證中,iOS客戶端需要哪兩個證書,什么格式的。

2.ATS設置

3.普通https請求證書驗證代碼 ?與 ?uiwebview請求https頁面時驗證代碼

4.html中ajax請求https

1.服務器公鑰和p12文件。服務器公鑰網上很多代碼是.cer格式的,但是在AF3.0版本中,支持的是.der格式,這個坑請注意了。

對公鑰證書data的描述

這個方法添加的是公鑰證書的data數據

p12證書 對應的是服務器上的 .pfx證書(應該是,不太確定),pfx格式的可以轉換成p12證書,它里面包括了公鑰和私鑰,客戶端用p12證書應該是在做客戶端的驗證。

這是我自己配置的tomcat配置,p12證書對應的是 keystoreFile所對應的證書。

注意:在添加讀取證書的時候,Xcode7有個bug 但不是必現。當證書拖到項目中時,路徑讀不出來,找不到這個文件,但是Bundle Resource中卻偏偏存在,當時我一直認為是證書的問題,糾結了很久。

這里添加證書,一般可以解決無法讀取路徑的問題

這個證書路徑容易讀出來是 nil

2.ATS設置

這個也是我一個不是特別能想的通的問題,按道理說,這個是設置在iOS9之后請求http才會設置的,但是在請求https的時候我發現 不設置的話,證書并沒有進行驗證。

因此還是要設置一下。

這里的域名下,TLS傳輸協議需要服務器端的注意一下,iOS支持的是1.2版本開始。但是很多都是從1.0開始的。因此我們最好也配上去。

3.請求https服務源代碼

與正常的AFN請求http請求來說,添加一這一個設置

```

-(AFSecurityPolicy*) getCustomHttpsPolicy:(AFHTTPSessionManager*)manager{

//https 公鑰證書配置

NSString *certFilePath = [[NSBundle mainBundle] pathForResource:@"custom" ofType:@"der"];

NSData *certData = [NSData dataWithContentsOfFile:certFilePath];

NSSet *certSet = [NSSet setWithObject:certData];

AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certSet];

policy.allowInvalidCertificates = YES;

policy.validatesDomainName = NO;//是否校驗證書上域名與請求域名一致

//https回調 客戶端驗證

[manager setSessionDidBecomeInvalidBlock:^(NSURLSession * _Nonnull session, NSError * _Nonnull error) {

NSLog(@"setSessionDidBecomeInvalidBlock");

}];

__weak typeof(manager)weakManger = manager;

__weak typeof(self)weakSelf = self;

//客戶端請求驗證 重寫 setSessionDidReceiveAuthenticationChallengeBlock 方法

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

NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;

__autoreleasing NSURLCredential *credential =nil;

if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

if([weakManger.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:@"p12"];

NSFileManager *fileManager =[NSFileManager defaultManager];

if(![fileManager fileExistsAtPath:p12])

{

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

}

else

{

NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];

if ([[weakSelf class]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 policy;

}

```

```

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

OSStatus securityError = errSecSuccess;

//client certificate password

NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"111111"

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;

}

```

UIWebview加載雙向認證https頁面

這里需要注意一下,這個需要設置

設置,請求忽略本地緩存在性能上會有所犧牲。在https請求中,據說90%多cpu消耗都是花費在證書的校驗過程當中。但是,當html頁面中使用的ajax請求方式也是https的時候,這個就必須重新驗證。原因是,當uiwebview驗證完證書,之后,再次啟動app,webview會根據connection返回的驗證歷史信息,直接認為客戶端是安全的,不再進行證書校驗,這個時候,html頁面里面的ajax請求就不能正常的發送,會導致無法正常運行。

主要代碼:

```

#pragma mark - UIWebViewDelegate

- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {

NSString *tmp = [request.URL absoluteString];

NSLog(@"request url :%@",tmp);

if ([request.URL.scheme rangeOfString:@"https"].location != NSNotFound) {

//開啟同步的請求去雙向認證

if (!_Authenticated) {

originRequest = request;

NSURLConnection *conn = [NSURLConnection connectionWithRequest:request delegate:self];

[conn start];

[webView stopLoading];

return false;

}

}

return YES;

}

```

```

#pragma NSURLConnectionDelegate

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {

NSURLCredential * credential;

assert(challenge != nil);

credential = nil;

NSLog(@"----received challenge----");

NSString *authenticationMethod = [[challenge protectionSpace] authenticationMethod];

if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {

NSLog(@"----server verify client----");

NSString *host = challenge.protectionSpace.host;

SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;

BOOL validDomain = false;

NSMutableArray *polices = [NSMutableArray array];

if (validDomain) {

[polices addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)host)];

}else{

[polices addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];

}

SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)polices);

//pin mode for certificate

NSString *path = [[NSBundle mainBundle] pathForResource:@"custom" ofType:@"der"];

NSData *certData = [NSData dataWithContentsOfFile:path];

NSMutableArray *pinnedCerts = [NSMutableArray arrayWithObjects:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData), nil];

SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCerts);

credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];

} else {

NSLog(@"----client verify server----");

SecIdentityRef identity = NULL;

SecTrustRef trust = NULL;

NSString *p12 = [[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"];

NSFileManager *fileManager = [NSFileManager defaultManager];

if (![fileManager fileExistsAtPath:p12]) {

NSLog(@"client.p12 file not exist!");

}else{

NSData *pkcs12Data = [NSData dataWithContentsOfFile:p12];

if ([[self class] 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];

}

}

}

[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];

}

```

```

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

OSStatus securityErr = errSecSuccess;

//client certificate password

NSDictionary *optionsDic = [NSDictionary dictionaryWithObject:@"111111" forKey:(__bridge id)kSecImportExportPassphrase];

CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);

securityErr = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data, (__bridge CFDictionaryRef)optionsDic, &items);

if (securityErr == errSecSuccess) {

CFDictionaryRef mineIdentAndTrust = CFArrayGetValueAtIndex(items, 0);

const void *tmpIdentity = NULL;

tmpIdentity = CFDictionaryGetValue(mineIdentAndTrust, kSecImportItemIdentity);

*outIdentity = (SecIdentityRef)tmpIdentity;

const void *tmpTrust = NULL;

tmpTrust = CFDictionaryGetValue(mineIdentAndTrust, kSecImportItemTrust);

*outTrust = (SecTrustRef)tmpTrust;

}else{

return false;

}

return true;

}

```

```

-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)pResponse {

_Authenticated = YES;

//webview 重新加載請求。

[localWebView loadRequest:originRequest];

[connection cancel];

}

```

還值得注意的是:在https方式加載html的時候,我查到大量的資料中,都是在

```

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data;

- (void)connectionDidFinishLoading:(NSURLConnection *)connection;

- (void)loadHTMLString:(NSString *)string baseURL:(nullable NSURL *)baseURL;

```

這三個方法去處理的,方式是:將收到的data拼成一個全局的變量,在連接完成時將data轉換成字符串,用加載本地html的方式進行加載。

這個方法不可行,在加載非本地html的時候。

js、css、圖片的引入都是根據相對路徑的

在html前端頁面中,會有大量的外部資源引用。當將html頁面元素當做字符串去加載的時候,就會存在html依賴的外部資源無法加載,html頁面會出現圖片找不到或者布局混亂的現象。當使用loadHTMLString中的baseURL作為相對路徑的指導時,在加載本地資源時,使用的是bundle,但是加載遠程資源時,就會觸發https證書驗證的方法,無限次的循環,整個app高消耗內存和cpu,關鍵是還加載不到。

因此,在收到pResponse的時候,直接斷掉connection,重新加載該頁面就ok。這個時候,客戶端和服務器都是相互信任的,ssl通道已經建立起來,大家可以愉快的進行操作了。

ajax https請求

這個其實是個偽問題,在https方法加載的頁面當中,已經建立起來ssl通道的web環境,直接就可以進行https請求。因此是不需要進行像客戶端那樣配置。

但是,如果是http方式加載的頁面中,進行https請求,那么久存在跨域的問題。這個在下篇中再進行解釋。

因此,在html頁面是以https方法加載的情況下,不用考慮證書的問題,直接把請求方式改成https,完事。

來源: http://www.lxweimin.com/p/13f6d134a59a

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,936評論 6 535
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,744評論 3 421
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,879評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,181評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,935評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,325評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,384評論 3 443
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,534評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,084評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,892評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,067評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,623評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,322評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,735評論 0 27
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,990評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,800評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,084評論 2 375

推薦閱讀更多精彩內容