美文网首页iOS程序犭袁计算机技术一锅炖iOS知识
iOS用自签名证书实现HTTPS请求的原理实例讲解

iOS用自签名证书实现HTTPS请求的原理实例讲解

作者: LannisZheng | 来源:发表于2016-12-01 17:31 被阅读6377次

    更新:苹果官方发布公告,暂缓HTTPS的强制性要求,具体要求时间再另行通知,要配置的开发者们可以暂缓一口气了。


    在16年的WWDC中,Apple已表示将从2017年1月1日起,所有新提交的App必须强制性应用HTTPS协议来进行网络请求。默认情况下非HTTPS的网络访问是禁止的并且不能再通过简单粗暴的向Info.plist中添加NSAllowsArbitraryLoads设置绕过ATS(App Transport Security)的限制(否则须在应用审核时进行说明并很可能会被拒)。所以还未进行相应配置的公司需要尽快将升级为HTTPS的事项提上进程了。
    本文将简述HTTPS及配置数字证书的原理并以配置实例和出现的问题进行说明,希望能对你提供帮助。(比心~)

    HTTPS:

    简单来说,HTTPS就是HTTP协议上再加一层加密处理的SSL协议,即HTTP安全版。相比HTTP,HTTPS可以保证内容在传输过程中不会被第三方查看、及时发现被第三方篡改的传输内容、防止身份冒充,从而更有效的保证网络数据的安全。

    HTTPS客户端与服务器交互过程:
    1、 客户端第一次请求时,服务器会返回一个包含公钥的数字证书给客户端;
    2、 客户端生成对称加密密钥并用其得到的公钥对其加密后返回给服务器;
    3、 服务器使用自己私钥对收到的加密数据解密,得到对称加密密钥并保存;
    4、 然后双方通过对称加密的数据进行传输。

    数字证书:

    在HTTPS客户端与服务器第一次交互时,服务端返回给客户端的数字证书是让客户端验证这个数字证书是不是服务端的,证书所有者是不是该服务器,确保数据由正确的服务端发来,没有被第三方篡改。数字证书可以保证数字证书里的公钥确实是这个证书的所有者(Subject)的,或者证书可以用来确认对方身份。证书由公钥、证书主题(Subject)、数字签名(digital signature)等内容组成。其中数字签名就是证书的防伪标签,目前使用最广泛的SHA-RSA加密。

    证书一般分为两种:
    一种是向权威认证机构购买的证书,服务端使用该种证书时,因为苹果系统内置了其受信任的签名根证书,所以客户端不需额外的配置。为了证书安全,在证书发布机构公布证书时,证书的指纹算法都会加密后再和证书放到一起公布以防止他人伪造数字证书。而证书机构使用自己的私钥对其指纹算法加密,可以用内置在操作系统里的机构签名根证书来解密,以此保证证书的安全。
    另一种是自己制作的证书,即自签名证书。好处是不需要花钱购买,但使用这种证书是不会受信任的,所以需要我们在代码中将该证书配置为信任证书。这就是本文的主要目的。

    具体实现

    我们在使用自签名证书来实现HTTPS请求时,因为不像机构颁发的证书一样其签名根证书在系统中已经内置了,所以我们需要在App中内置自己服务器的签名根证书来验证数字证书。
    首先将服务端生成的.cer格式的根证书添加到项目中,注意在添加证书要一定要记得勾选要添加的targets。这里有个地方要注意:苹果的ATS要求服务端必须支持TLS 1.2或以上版本;必须使用支持前向保密的密码;证书必须使用SHA-256或者更好的签名hash算法来签名,如果证书无效,则会导致连接失败。由于我在生成的根证书时签名hash算法低于其要求,在配置完请求时一直报NSURLErrorServerCertificateUntrusted = -1202错误,希望大家可以注意到这一点。

    本文使用AFNetworking 3.0来配置证书校验。其中AFSecurityPolicy类中封装了证书校验的过程。
    AFSecurityPolicy分三种验证模式:
    1、AFSSLPinningModeNone:只验证证书是否在新人列表中
    2、AFSSLPinningModeCertificate:验证证书是否在信任列表中,然后再对比服务端证书和客户端证书是否一致
    3、 AFSSLPinningModePublicKey:只验证服务端与客户端证书的公钥是否一致
    这里我们选第二种模式,并且对AFSecurityPolicy的allowInvalidCertificatesvalidatesDomainName进行设置。
    AFSecurityPolicy具体配置代码如下:

    AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; 
    securityPolicy.allowInvalidCertificates = YES;      //是否允许使用自签名证书
    securityPolicy.validatesDomainName = NO;           //是否需要验证域名
    self.manager = [AFHTTPSessionManager manager];
    self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
    self.manager.securityPolicy = securityPolicy;
    

    服务端在接收到客户端请求时会有的情况需要验证客户端证书,要求客户端提供合适的证书,再决定是否返回数据。这种情况即为质询(challenge)认证,双方进行公钥和私钥的验证。
    为实现客户端验证,manager须设置需要身份验证回调的方法:

    - (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block
    

    并实现具体代码来替换AFNetworking的默认实现。其中参数challenge为身份验证质询,block返回对身份验证请求质询的配置。
    在接受到质询后,客户端要根据服务端传来的challenge来生成所需的NSURLSessionAuthChallengeDisposition dispositionNSURLCredential *credential。disposition指定应对这个质询的方法,而credential是客户端生成的质询证书,注意只有challenge中认证方法为NSURLAuthenticationMethodServerTrust的时候,才需要生成挑战证书。最后回应服务器的质询。

    具体实现代码如下:

      [self.manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {
            
      // 获取服务器的trust object
      SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
      //导入自签名证书
      NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];
      NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
      NSArray *cerArray = @[caCert];
      weakSelf.manager.securityPolicy.pinnedCertificates = cerArray;
            
      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;
            
            //NSURLAuthenticationMethodServerTrust质询认证方式
            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;
            }
            
            return disposition;
        }];
    

    详细代码发布在了github上,使用时请注意在ViewControllerNetworkManager#Warning的提示,将证书拖入项目并在NetworkManager中添加证书名称,在ViewController添加自己的URL。

    文章中如有错误和疏漏,欢迎留言指教讨论。

    参考文章:

    数字证书原理
    Certificate, Key, and Trust Services Tasks for iOS
    关于 iOS 10 中 ATS 的问题
    App Transport Security(ATS)
    AFNetworking之于https认证
    iOS使用自签名证书实现HTTPS请求

    相关文章

      网友评论

      • wokenshin:我先前使用的是证书验证模式,质询的方式更加安全一些。不过我又个疑问,为什么我采用两种方式都可以成功请求服务器呢?这个地方是不是 只要根证书配置正确 用那种方式都可以?还是我们的后端也需要做响应的配置?比如质询的地方。

        我的配置证书的代码是这么写的
        + (AFSecurityPolicy*)customSecurityPolicy
        {
        NSString *cerPath = [[NSBundle mainBundle] pathForResource:certRoot ofType:nil];//证书的路径
        NSData *certData = [NSData dataWithContentsOfFile:cerPath];
        AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
        securityPolicy.allowInvalidCertificates = YES;
        securityPolicy.validatesDomainName = NO;
        NSMutableSet *mSet = [NSMutableSet setWithObject:certData];
        securityPolicy.pinnedCertificates = mSet;

        return securityPolicy;
        }
      • 大树先生:楼主吗,你好,请问我按照您的demo集成,然后运行请求后就崩溃,然后报这个错Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'caRef is nil',请问下,是什么原因呢?是证书的问题么
        帥不過阿杜:@大树先生 我也遇到這個問題, 是導入證書不對的原因
      • 给我一支烟smoking:楼主 请问 - (void)setSessionDidReceiveAuthenticationChallengeBlock:(NSURLSessionAuthChallengeDisposition (^)(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential * __autoreleasing *credential))block 方法为什么有时候走有时候不走,而且不走的时候就找不到服务器
      • b820e307e59a:您好,我想问一下。如果说我验证证书了,但是如果运营商劫持,往里面添加广告神马的,这种情况就会连接不上。这种应该怎么办?(我使用证书验证,自己验证的逻辑。然后抓包会连接不上服务器,我们后台说,有可能存在运营商劫持,跟抓包这种情况类似。)
        b820e307e59a:@LannisZheng 谢谢,我这边已经差不多解决了。就是还是有的地方不是太懂,自己研究了。多谢。
        LannisZheng:@狗啃的青春 所以有条件尽量购买使用权威机构颁发的证书,这里只是提供一种实现的思路方法。多谢提醒。
        LannisZheng:@狗啃的青春 是的,使用自签名证书确实会存在HTTPS中间人攻击的风险。就是当入侵者控制计算机MITM虚拟放置在客户端和服务器之间。当HTTPS回话时,入侵者先截获SSL替换服务器的公钥证书,将公钥替换为自己的公钥,欺骗客户端,然后解密客户端的信息从而进行监听。所以使用这种自签名证书验证就可以跳过权威机构颁发的签名根证书验证,使中间人攻击的风险变大。
      • b523aa780226:楼主,问个问题 ,生成证书,自己网页也能访问8443,但是用你的demo访问报错:
        NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
        怎么搞
        LannisZheng:@huajie6225 因为你发的是http请求吧,iOS9后系统发送的网络请求将统一使用TLS 1.2 SSL。
      • JamesCaiLee:不错,可以介绍下如何自制证书吗? :smiley:
      • 简笑笑:securityPolicy.validatesDomainName = NO;
        自签证书设置YES之后就拿不到数据了,自签证书只能设置NO吗?
        简笑笑:@LannisZheng 嗯,那应该是他们给的证书有问题了,我只要设置成YES就会出问题
        LannisZheng:@简笑笑 假如证书的域名与你请求的域名不一致,需把该项设置为NO;设置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的。
      • Bingoliu:请问一下,我们老大现在有个需求就是可以配置多个证书,我把你demo封装了一下,但是还是崩溃,求解答
        LannisZheng:@SoliderBing 多个证书的话可以看参考文章中的《iOS使用自签名证书实现HTTPS请求》最后讲解,我等有时间会更新代码和文章。
      • wayward玉:我把证书加进去,然后改完URL之后没有响应,加个断点之后跳到 securityPolicy.pinnedCertificates = @[certData];
        这一行了是什么原因呢?
        wayward玉:@LannisZheng 已经解决了,就是在导入证书的时候没有勾选target,谢谢楼主!!!
        LannisZheng:@wayward玉 将证书添加时勾选target,在代码中将证书名字改为自己证书的名字了么,错误提示我看下。
      • 纠结的水瓶:现在正需要这个文章,感谢楼主
      • iOS开发工程师Echo:因为我们有个活动页面有可能URL的来源是第三方提供,不一定是https开头的
        iOS开发工程师Echo:@LannisZheng 谢了我去看看
        LannisZheng:@寻找我的AE86 如果使用的是第三方的 API,而他们没有提供 HTTPS 支持的话,需要在 NSExceptionDomains 中进行添加。详情可以看参考文章中的《关于 iOS 10 中 ATS 的问题》
      • iOS开发工程师Echo:请问一下,如果是webview怎么办呢,webview提供的URL不是服务器的http类型的,这个时候自制的签名证书有效果吗?
        LannisZheng:@寻找我的AE86 webView也可以使用自证书,验证方式可以参考 stackoverflow这个:http://stackoverflow.com/questions/11573164/uiwebview-to-view-self-signed-websites-no-private-api-not-nsurlconnection-i
      • 会跳舞的狮子:棒棒哒... 💯 正好需要

      本文标题:iOS用自签名证书实现HTTPS请求的原理实例讲解

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