https双向认证 iOS客户端处理

作者: 阿猿 | 来源:发表于2016-08-02 13:31 被阅读7212次

写在前面:

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,完事。

相关文章

  • https双向认证 iOS客户端处理

    写在前面: 1.相关https具体内容本篇就不再描述。 2.前几天公司项目要求配置https双向认证,由于是银行业...

  • https双向认证 iOS客户端处理

    写在前面: 1.相关https具体内容本篇就不再描述。 2.前几天公司项目要求配置https双向认证,由于是银行业...

  • iOS HTTPS 双向认证

    iOS HTTPS 双向认证 @(iOS)[网络,HTTPS] 搞了半天,记录一下,坑很多。双向认证,就是在访问网...

  • iOS客户端https连接双向认证

    从iOS10开始,必须强制使用https,利用系统自带的NSURLSession网络请求,设置代理 如果是http...

  • iOS https 双向认证

    /** 一.非浏览器应用(iOS app)与服务器AFNetworking HTTPS ssl认证 虽然是HTTP...

  • iOS HTTPS双向认证

    一般情况下对于HTTPS,如果公司申请认证之后,客户端是不需要做什么操作。但是如果是自建证书,没有通过CA认证的话...

  • iOS_HTTPS认证

    HTTPS认证 单向认证:指客户端验证服务端的身份,服务端不验证客户端身份双向认证:指客户端验证服务端身份,服务端...

  • iOS中的安全与加密

    iOS中的安全与加密 一。HTTPS双向认证 Charles是大家所熟悉的抓包工具,如果网络请求未经过双向认证,那...

  • iOS 用AFN与后台双向https证书验证

    iOS常规的https单向验证不需要客户端做处理,但是客户端与服务端双向验证则需要客户端注入证书再请求。1.先将p...

  • iOS中的HTTPS认证

    一、HTTPS认证 1. 会话认证机制 iOS 中会话认证机制共有四种,大体分为两种类型: 单向认证双向认证; N...

网友评论

  • Ozhy1991yhzO:博主在不在啊。心好伤,调试HTTPS双向认证,出现Error Domain=NSURLErrorDomain Code=-1206 "The server “www.letouwealth.com” requires a client certificate.,心痛,代码写的感觉是没有问题的。不知道问题出在哪
  • hhfa008:UIWebView https 双向认证最佳解决方案:https://github.com/hhfa008/HTTPSURLProtocol
    巫添粱:遇到单页面没有问题, 遇到作者说的最后一种情况的时候, 就会加载不出来其他js的
  • 愚人船ios:楼主 我就是用加载html字符串 然后外部资源都不能加载 加上baseUrl 后又不断的验证证书 怎么解决呀?
  • virusbo:巧了,我现在也是在做HX银行新直销 也是要求做SSL双向验证,可否留个联系方式,请教一下?
  • MrSong:错误,提示我添加baseURL,这个是什么东西?
    MrSong:这个我改了,但是还有一点不明白就是那个p12 校验什么时候会走
    iU啊:@MrSong _manager = [[AFHTTPSessionManager manager]initWithBaseURL:[NSURL URLWithString:ServerIPUrl]];
    AF3.0内的一个判断需要根据这个Url判断是否是https
    MrSong:*** Terminating app due to uncaught exception 'Invalid Security Policy', reason: 'A security policy configured with `AFSSLPinningModeCertificate` can only be applied on a manager with a secure base URL (i.e. https)'
  • 麦田里的稻香:您好,楼主想问下你 ,, 双向验证的时候 什么时候 会走 else{ p12 这里内部的 代码}是在客户端发送请求的时候?还是 客户端响应服务端的时候 。。 。 我感觉因为前面有个if ([[weakSelf class]extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data]) 这个判断逻辑 就不会走 else{ p12 这里 的 验证逻辑了}?? 还有这里的判断([[weakSelf class]extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data]) 是 验证 cer的 ??? 还是 什么
    MrSong:我也有点好奇,什么时候走这个else ,打了断点一直没走过
  • 管你爱不爱:WKWebView 的双向验证呢?
  • 小罗哦吧:你好我加载https双向不走代理试为什么呢
  • 55fd49276d2c:两个证书是怎么制作的
    山林间迷雾能不能当障眼法的内容:@阿猿 买的证书 是CA认证的 那为什么还要配置客户端代码????
    阿猿:@Jack_wanghf 买的
  • brilliance_Liu:您好,请教一下。我们的后台是HTTPS TLS1.0的单向验证。ATS单纯设置Allows loads为No无法正常请求,但是ATS按照你的设置方法就正常请求成功了,这是不是说明我客户端现在已经适配https了?
    阿猿:@brilliance_Liu 这个按照我的理解 是没有完成适配的,首先 TLS最少要1.2版本以上 好像是
  • 给你快乐:按照你写的,请求错误码code = -1206 服务器需要客户端证书?搞不明白哪里错了?求指点
  • EchoZuo:您好,请教一下。如果是双向认证的话,是需要在iOS内置两个证书是吧?一个是der,一个是p12这个对吗?如果是单向认证,只需要der就可以了是吗?博主有单向认证的代码吗?方便的话贴出来,非常感谢。
    doudo:@阿猿 “双向的意思就是 在客户端也要验证证书,单项就是只有服务端验证证书。”这句话你是不是说反了
    Alan_yo:@阿猿 请问下双向认证,一直出现那个Error: Error Domain=NSURLErrorDomain Code=-999 "cancelled"报错是哪里没设置好吗?已经加入 cer 和认证p12
    阿猿:@EchoZuo 双向的意思就是 在客户端也要验证证书,单项就是只有服务端验证证书。单项的话,前端就不需要p12文件了 最近忙 没怎么关注 回复的慢了 不好意思
  • Luke_coder:求请教博主,本地证书从哪搞啊,从网站导出还是问服务器要,p12是服务器给导出的还是自己本地导出。
    阿猿:@LeeShura 只要是可以连接上一次,按说就不该有问题的。你说的频繁请求会出现问题,我觉得你们可以向服务器那里检查一下,做一个简单的压力测试。
    如果是单向认证的话,app端不需要设置什么东西,这个如果需要的话 我可以给你贴出来点代码,af3.0版本支持的。
    Luke_coder:@阿猿 嗯嗯,谢谢楼主,之前用单向认证也凑合能用,刚好这两天发现有时频繁的刷HTTPS的接口会出现SSL内部链接错误,但是重启手机或者切换网络就会恢复,所以才会想换个双向认证看能不能解决问题!
    阿猿:@LeeShura 你说的 本地证书 指的是.der证书吧? 那个应该是服务器公钥,是后台给的。p12文件,是后台配置的那个pfx证书 这个里面包含了 公钥和私钥,拿到这个pfx查一下 可以转成p12的格式,希望可以帮到你。
    另外,我用的证书是cfca签名颁发的 ,这个证书一年好像是要几千块钱。如果后台是自己签名给你的证书,在iOS客户端好像是验证不过去的,苹果不认这些自签名的东西,你可以试一下,我也是听说。

本文标题:https双向认证 iOS客户端处理

本文链接:https://www.haomeiwen.com/subject/lypfsttx.html