iOS支持https自定义证书验证

事件缘起

任性的苹果要求6月1日起所有上架app都需要支持ipv6。尼玛赶紧检查下代码是否能够满足要求,检查是否不兼容IPv6点击这里。结果是一个大大的懵逼啊。项目使用的AFNetworking根本就没升级还停留在2.0阶段,顿时心中策马奔腾啊。果断升级之,本以为很easy,分分钟搞定的事情,结果。。。

问题

AFNetworking 2.0 ->AFNetworking 3.0发生了很大变化,废弃了很多的类的同时,基本构架也发生了很大的变化。
AFNetworking 2.0使用NSURLConnection的基础API。
AFNetworking 3.0现已完全基于NSURLSession的API,这降低了维护的负担,同时支持苹果增强关于NSURLSession提供的任何额外功能。(以后苹果所有基于网络的功能,都会使用NSURLSession来扩展。在NSURLConnection类中也说明 DEPRECATED: The NSURLConnection class should no longer be used. NSURLSession is the replacement for NSURLConnection)为什么要使用NSURLConnection这里一篇文章从性能方面进行了分析

AFNetworking 3.0相对于2.0具体来说

  • 弃用类
AFURLConnectionOperation
AFHTTPRequestOperation
AFHTTPRequestOperationManager
  • 修改的类
    下面的类包含基于NSURLConnection的API的内部实现。他们已经被使用NSURLSession重构:
UIImageView+AFNetworking
UIWebView+AFNetworking
UIButton+AFNetworking

3.0和2.0类对比


AFNetworking2.0

AFNetworking3.0

检查我们原来的代码实现,发现我们恰恰就使用了废弃的类AFHTTPRequestOperation。我们原来的网络库竟然一直不升级我也是醉了。对此我只能说:让我来。
使用最新的AFURLSessionManager来发送网络请求就是了。

    NSURL *URL = [NSURL URLWithString:MyURL];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
    [request setHTTPMethod:@"POST"];
    [request setTimeoutInterval:10];
    [request setHTTPBody:[jsonStr dataUsingEncoding:NSUTF8StringEncoding]];
    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;
    
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.HTTPMaximumConnectionsPerHost = 1;
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:config];
    
    NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {

        if (error) {
            NSLog(@"没有网络--- error---%@", error);
        } else {
            NSLog(@"网络是通的");
            NSDictionary* dict = responseObject;
        }
    }];
    
    [dataTask resume];

发现打印结果:没有网络 NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)

检查Info.plist中退回到http配置

<key>NSAppTransportSecurity</key>
<dict>
    <key>NSAllowsArbitraryLoads</key>
    <true/>
</dict>

也增加了。why?
结果:原因是苹果的 官方资料 说首先必须要基于TLS 1.2版本协议。然后证书的加密的算法还需要达到SHA256或者更高位的RSA密钥或ECC密钥,如果不符合,请求将被中断并返回nil。原来我们需要验证证书啊。

解决办法

在AFNetworking中,只要通过下面的代码,你就可以使用自签证书来访问HTTPS

    AFSecurityPolicy *securityPolicy = [AFSecurityPolicy defaultPolicy];
    //是否允许不信任的证书(证书无效、证书时间过期)通过验证 ,默认为NO
    securityPolicy.allowInvalidCertificates = YES;

这么做有个问题,就是你无法验证证书是否是你的服务器后端的证书,给中间人攻击留下了漏洞,可以通过重定向路由来分析伪造你的服务器端打开了大门。

AFNetworking是允许内嵌证书的,通过内嵌证书,AFNetworking就通过比对服务器端证书、内嵌的证书、站点域名是否一致来验证连接的服务器是否正确。由于CA证书验证是通过站点域名进行验证的,如果你的服务器后端有绑定的域名,这是最方便的。将你的服务器端证书,如果是pem格式的,用下面的命令转成cer格式

openssl x509 - in <你的服务器证书>.pem -outform der -out test.cer

然后将生成的test.cer文件,如果有自建ca,再加上ca的cer格式证书,引入到app的bundle里,然后在网络请求的地方做如下设置

AFSecurityPolicy *securityPolicy = [AFSecurityPolicy AFSSLPinningModeCertificate];
或者
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy AFSSLPinningModePublicKey];
securityPolicy.allowInvalidCertificates = YES; 

如果app的bundle没有这个证书,会报错:
In order to validate a domain name for self signed certificates, you MUST use pinning.
这个pinning,指的是证书锁定,意思就是只有client包含的证书和服务器的证书一致时,才能通过验证。
查看AFNetworking的源代码,有如下核心方法
- evaluateServerTrust:forDomain:详情参考这里

- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
                  forDomain:(NSString *)domain
{
    if (domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) {
        // https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
        //  According to the docs, you should only trust your provided certs for evaluation.
        //  Pinned certificates are added to the trust. Without pinned certificates,
        //  there is nothing to evaluate against.
        //
        //  From Apple Docs:
        //          "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors).
        //           Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
        NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
        return NO;
    }

    NSMutableArray *policies = [NSMutableArray array];
    if (self.validatesDomainName) {
        [policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
    } else {
        [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()];
    }

    SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies);

    if (self.SSLPinningMode == AFSSLPinningModeNone) {
        return self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust);
    } else if (!AFServerTrustIsValid(serverTrust) && !self.allowInvalidCertificates) {
        return NO;
    }

    switch (self.SSLPinningMode) {
        case AFSSLPinningModeNone:
        default:
            return NO;
        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)
            NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
            
            for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
                if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
                    return YES;
                }
            }
            
            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;
        }
    }
    
    return NO;
}

关于签名证书:AFnetworking默认会去程序中寻找所有cer文件,并找符合要求的。当然我们也可以指定的cer文件名称:

    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.HTTPMaximumConnectionsPerHost = 1;
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:config];
    
    AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode: AFSSLPinningModeCertificate];
    NSString *certificatePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"cer"];
    NSData *certificateData = [NSData dataWithContentsOfFile:certificatePath];
    
    NSSet *certificateSet  = [[NSSet alloc] initWithObjects:certificateData, nil];
    [securityPolicy setPinnedCertificates:certificateSet];
    securityPolicy.allowInvalidCertificates = YES;
    securityPolicy.validatesDomainName = NO;
    manager.securityPolicy = securityPolicy;

至此AFSecurityPolicy就只会比对服务器证书和内嵌证书是否一致,不会再验证证书是否和站点域名一致了。
  这么做为什么是安全的?了解HTTPS的人都知道,整个验证体系中,最核心的实际上是服务器的私钥。私钥永远,永远也不会离开服务器,或者以任何形式向外传输。私钥和公钥是配对的,如果事先在客户端预留了公钥,只要服务器端的公钥和预留的公钥一致,实际上就已经可以排除中间人攻击了。

最后

最后完整验证证书代码

    NSURL *URL = [NSURL URLWithString:URL_FRONT];
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
    [request setHTTPMethod:@"POST"];
    [request setTimeoutInterval:10];//设置超时
    [request setHTTPBody:[jsonStr dataUsingEncoding:NSUTF8StringEncoding]];
    request.cachePolicy = NSURLRequestReturnCacheDataElseLoad;// 设置缓存策略
    
    NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
    config.HTTPMaximumConnectionsPerHost = 1;
    AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:config];
    
    // 安全验证
    AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode: AFSSLPinningModeCertificate];
    NSString *certificatePath = [[NSBundle mainBundle] pathForResource:@"test" ofType:@"cer"];
    NSData *certificateData = [NSData dataWithContentsOfFile:certificatePath];
    
    NSSet *certificateSet  = [[NSSet alloc] initWithObjects:certificateData, nil];
    [securityPolicy setPinnedCertificates:certificateSet];
    securityPolicy.allowInvalidCertificates = YES;
    securityPolicy.validatesDomainName = NO;
    manager.securityPolicy = securityPolicy;
    
    NSURLSessionDataTask *dataTask = [manager dataTaskWithRequest:request completionHandler:^(NSURLResponse * _Nonnull response, id  _Nullable responseObject, NSError * _Nullable error) {

        if (error) {
            NSLog(@"没有网络--- error---%@", error);

        } else {
            NSLog(@"网络是通的");
            NSLog(@"%@ %@", response, responseObject);
        }
        
    }];
    
    [dataTask resume];

如有错误,欢迎指正!

参考:Use AFN request https #3524
https

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容

  • 这篇文章是我一边学习证书验证一边记录的内容,稍微整理了下,共扯了三部分内容: HTTPS 简要原理;数字证书的内容...
    左边飞来一只狗阅读 3,259评论 2 5
  • 原文地址 http://blog.csdn.net/u012409247/article/details/4985...
    0fbf551ff6fb阅读 3,512评论 0 13
  • 一、作用 不使用SSL/TLS的HTTP通信,就是不加密的通信。所有信息明文传播,带来了三大风险。 (1)窃听风险...
    XLsn0w阅读 10,477评论 2 44
  • 在 WWDC 16 中,Apple 表示, 从 2017年1月1日起(最新消息, 实施时间已延期),所有新提交的 ...
    kmplayer阅读 2,567评论 1 9
  • iOS 9.0之后苹果开始要求使用Https进行通信。ATS是iOS9和OS X El Capitan的一个新特性...
    jiodg45阅读 660评论 0 4