iOS之支持https与ssl双向验证(包含:解决UIWebView加载不了https网页的图片,css,js等外部资源)

一,说明

在WWDC 2016开发者大会上,苹果宣布了一个最后期限:到2017年1月1日 App Store中的所有应用都必须启用 App Transport Security安全功能。App Transport Security(ATS)是苹果在iOS 9中引入的一项隐私保护功能,屏蔽明文HTTP资源加载,连接必须经过更安全的HTTPS。苹果目前允许开发者暂时关闭ATS,可以继续使用HTTP连接,但到年底所有官方商店的应用都必须强制性使用ATS。

补充:强制ATS的日期为2017年1月1日现已延期. 具体日期还没出来,今年可以过个好年了.链接: https://developer.apple.com/news/?id=12212016b


项目说明: 一开始听到这个消息,我对ATS的概念一知半解. 经过5天的时间,终于搞好了,在此记录一下.  我的项目中用到AFNetwork3.x,ASIHttpRequest,UIWebView.   在文章后面会一一讲解. 

我们后台开发的同学,用了nginx服务器,直接将http转为HTTPS.开发只用了一周.

首先后台会给你提供很多的证书,但是客户端只要几个证书,下面根据你的需求,选择证书.


(1),如果你的项目只支持ATS,单向验证,不需要ssl双向验证,你只需要金色的ca.cer证书(注:可将ca.crt转为ca.cer);
用openssl 将crt转为cer. 下面是2条命令:

(2),如果你的项目支持ATS,和ssl双向认证,那么你需要ca.cer证书,server.cer证书,client.p12证书(注:client.p12是client导入到钥匙串,在导出来的.注意导出时要记住密码"xxx"),这3个证书.
(3,)如果你的项目除了用到AFNetworking,还用到ASIHttpRequest, 那么你还需要client.pfx证书.(注:client.pfx是后台给的);

在项目开始前,在下图将plist里面的字段整个删掉:



1,如果你的项目只要支持ATS,不需要ssl双向验证. 那么你叫后台代码注释ssl双向验证功能(如果后台开启双向验证),只支持单向验证就可以.
我的所有接口用的是AFN3.x 所以我不会说AFN2.x(因为2.x不支持ipv6).  
下面是代码说明:
//只支持ATS,单向验证
_manager=[SHHttpRequestClient sharedSessionManager];
    _manager.responseSerializer=[AFHTTPResponseSerializer serializer];
    _manager.requestSerializer.timeoutInterval=12;
    SHAFSecurityPolicy *securitypolicy=[SHAFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
    _manager.securityPolicy=securitypolicy;
    _manager.securityPolicy.allowInvalidCertificates=YES;//是否允许CA不信任的证书通过
    [_manager.securityPolicy setValidatesDomainName:NO];//是否验证主机名
    __weak typeof(self) weakSelf = self;
    [_manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {
        
        SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
        /**
         *  导入多张CA证书(Certification Authority,支持SSL证书以及自签名的CA),请替换掉你的证书名称
         */
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];//自签名证书
        NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
        weakSelf.manager.securityPolicy.pinnedCertificates = [NSSet setWithObjects:caCert,nil];
        SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
        NSCAssert(caRef != nil, @"caRef is nil");
        NSArray *caArray = @[(__bridge id)(caRef)];
        NSCAssert(caArray != nil, @"caArray is nil");
        OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
        SecTrustSetAnchorCertificatesOnly(serverTrust,NO);
        //NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __autoreleasing NSURLCredential *credential = nil;
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if ([weakSelf.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 {
            disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        }
        *_credential=credential;
        return disposition;
    }];
    [_manager GET:path parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
    } success:^(NSURLSessionDataTask * _Nonnull task, id  _Nullable responseObject) {
    } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
    }];
将上面的代码拷贝到你的项目中,如果发现你的https接口请求时崩溃,那么是校验服务端证书个数和客户端信任个数不一致,那么此时要修改源码.

由于服务端使用.jks是一个证书库,客户端获取到的证书可能不止一本,具体获取到基本可通过SecTrustGetCertificateCount方法获取证书个数,AFNetworking在evaluateServerTrust:forDomain:方法中,AFSSLPinningMode的类型为AFSSLPinningModeCertificate和AFSSLPinningModePublicKey的时候都有校验服务端的证书个数与客户端信任的证书数量是否一样,如果不一样的话无法请求成功,所以这边我就修改他的源码,当有一个校验成功时即算成功。
在AFSecurityPolicy.m的-(BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain修改代码:
case AFSSLPinningModeCertificate: {
            NSMutableArray *pinnedCertificates = [NSMutableArray array];
            for (NSData *certificateData in self.pinnedCertificates) {
                [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)];
            }
            SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates);

            if (!AFServerTrustIsValid(serverTrust)) {
                return NO;
            }

            // obtain the chain after being validated, which *should* contain the pinned certificate in the last position (if it's the Root CA)
            NSUInteger trustedCertificateCount = 0;//这个地方修改
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    trustedCertificateCount++;//这个地方修改
                    //return YES;
                }
            }
            
            return trustedCertificateCount == [serverCertificates count] - 1;//这个地方修改
            //return NO;
        }
        case AFSSLPinningModePublicKey: {
            NSUInteger trustedPublicKeyCount = 0;
            NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);

            for (id trustChainPublicKey in publicKeys) {
                for (id pinnedPublicKey in self.pinnedPublicKeys) {
                    if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
                        trustedPublicKeyCount += 1;
                    }
                }
            }
            return trustedPublicKeyCount > 0 && ( trustedPublicKeyCount >= 1);//这个地方修改
            //return trustedPublicKeyCount > 0;
        }

同时,在AFHTTPSessionManager.m的

- (void)setSecurityPolicy:(SHAFSecurityPolicy *)securityPolicy

方法中将



  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 18
    评论
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值